commands.py 128 KB
Newer Older
mpm@selenic.com's avatar
mpm@selenic.com committed
1 2
# commands.py - command processing for mercurial
#
Vadim Gelfer's avatar
Vadim Gelfer committed
3
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
mpm@selenic.com's avatar
mpm@selenic.com committed
4 5 6 7
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

8
from demandload import demandload
9
from node import *
10
from i18n import gettext as _
11
demandload(globals(), "os re sys signal shutil imp urllib pdb")
12
demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
Vadim Gelfer's avatar
Vadim Gelfer committed
13
demandload(globals(), "fnmatch difflib patch random signal tempfile time")
14
demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15
demandload(globals(), "archival cStringIO changegroup")
Vadim Gelfer's avatar
Vadim Gelfer committed
16
demandload(globals(), "cmdutil hgweb.server sshserver")
17

18 19
class UnknownCommand(Exception):
    """Exception raised if command is not in the command table."""
20 21
class AmbiguousCommand(Exception):
    """Exception raised if command shortcut matches more than one command."""
22

Vadim Gelfer's avatar
Vadim Gelfer committed
23
def bail_if_changed(repo):
Vadim Gelfer's avatar
Vadim Gelfer committed
24
    modified, added, removed, deleted = repo.status()[:4]
Vadim Gelfer's avatar
Vadim Gelfer committed
25 26 27
    if modified or added or removed or deleted:
        raise util.Abort(_("outstanding uncommitted changes"))

28
def relpath(repo, args):
29 30
    cwd = repo.getcwd()
    if cwd:
31
        return [util.normpath(os.path.join(cwd, x)) for x in args]
32
    return args
33

34
def logmessage(opts):
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
    """ get the log message according to -m and -l option """
    message = opts['message']
    logfile = opts['logfile']

    if message and logfile:
        raise util.Abort(_('options --message and --logfile are mutually '
                           'exclusive'))
    if not message and logfile:
        try:
            if logfile == '-':
                message = sys.stdin.read()
            else:
                message = open(logfile).read()
        except IOError, inst:
            raise util.Abort(_("can't read commit message '%s': %s") %
                             (logfile, inst.strerror))
    return message
52

53
def walkchangerevs(ui, repo, pats, opts):
54 55 56 57 58 59 60 61 62 63
    '''Iterate over files and the revs they changed in.

    Callers most commonly need to iterate backwards over the history
    it is interested in.  Doing so has awful (quadratic-looking)
    performance, so we use iterators in a "windowed" way.

    We walk a window of revisions in the desired order.  Within the
    window, we first walk forwards to gather data, then in the desired
    order (usually backwards) to display it.

64
    This function returns an (iterator, getchange, matchfn) tuple.  The
65 66 67 68 69 70 71 72 73 74 75 76 77 78
    getchange function returns the changelog entry for a numeric
    revision.  The iterator yields 3-tuples.  They will be of one of
    the following forms:

    "window", incrementing, lastrev: stepping through a window,
    positive if walking forwards through revs, last rev in the
    sequence iterated over - use to reset state for the current window

    "add", rev, fns: out-of-order traversal of the given file names
    fns, which changed during revision rev - use to gather data for
    possible display

    "iter", rev, None: in-order traversal of the revs earlier iterated
    over with "add" - use to display data'''
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    def increasing_windows(start, end, windowsize=8, sizelimit=512):
        if start < end:
            while start < end:
                yield start, min(windowsize, end-start)
                start += windowsize
                if windowsize < sizelimit:
                    windowsize *= 2
        else:
            while start > end:
                yield start, min(windowsize, start-end-1)
                start -= windowsize
                if windowsize < sizelimit:
                    windowsize *= 2


95
    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
96
    follow = opts.get('follow') or opts.get('follow_first')
97

98
    if repo.changelog.count() == 0:
99
        return [], False, matchfn
100

101 102 103 104 105 106 107 108 109 110 111
    if follow:
        p = repo.dirstate.parents()[0]
        if p == nullid:
            ui.warn(_('No working directory revision; defaulting to tip\n'))
            start = 'tip'
        else:
            start = repo.changelog.rev(p)
        defrange = '%s:0' % start
    else:
        defrange = 'tip:0'
    revs = map(int, revrange(ui, repo, opts['rev'] or [defrange]))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
112 113 114
    wanted = {}
    slowpath = anypats
    fncache = {}
115 116 117 118 119 120 121 122

    chcache = {}
    def getchange(rev):
        ch = chcache.get(rev)
        if ch is None:
            chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
        return ch

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
123 124 125
    if not slowpath and not files:
        # No files, no patterns.  Display all revs.
        wanted = dict(zip(revs, revs))
126
    copies = []
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
127 128
    if not slowpath:
        # Only files, no patterns.  Check the history of each file.
129
        def filerevgen(filelog, node):
130
            cl_count = repo.changelog.count()
131 132 133 134 135
            if node is None:
                last = filelog.count() - 1
            else:
                last = filelog.rev(node)
            for i, window in increasing_windows(last, -1):
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
136
                revs = []
137
                for j in xrange(i - window, i + 1):
138 139 140
                    n = filelog.node(j)
                    revs.append((filelog.linkrev(n),
                                 follow and filelog.renamed(n)))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
141 142
                revs.reverse()
                for rev in revs:
143 144
                    # only yield rev for which we have the changelog, it can
                    # happen while doing "hg log" during a pull or commit
145
                    if rev[0] < cl_count:
146
                        yield rev
147 148 149 150 151
        def iterfiles():
            for filename in files:
                yield filename, None
            for filename_node in copies:
                yield filename_node
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
152
        minrev, maxrev = min(revs), max(revs)
153
        for file_, node in iterfiles():
154
            filelog = repo.file(file_)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
155 156 157 158 159
            # A zero count may be a directory or deleted file, so
            # try to find matching entries on the slow path.
            if filelog.count() == 0:
                slowpath = True
                break
160
            for rev, copied in filerevgen(filelog, node):
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
161
                if rev <= maxrev:
Thomas Arendsen Hein's avatar
Thomas Arendsen Hein committed
162 163
                    if rev < minrev:
                        break
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
164
                    fncache.setdefault(rev, [])
165
                    fncache[rev].append(file_)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
166
                    wanted[rev] = 1
167 168
                    if follow and copied:
                        copies.append(copied)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
169
    if slowpath:
170 171 172 173
        if follow:
            raise util.Abort(_('can only follow copies/renames for explicit '
                               'file names'))

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
174 175
        # The slow path checks files modified in every changeset.
        def changerevgen():
176
            for i, window in increasing_windows(repo.changelog.count()-1, -1):
177
                for j in xrange(i - window, i + 1):
178
                    yield j, getchange(j)[3]
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
179 180 181 182 183 184 185

        for rev, changefiles in changerevgen():
            matches = filter(matchfn, changefiles)
            if matches:
                fncache[rev] = matches
                wanted[rev] = 1

Brendan Cully's avatar
Brendan Cully committed
186 187 188 189 190 191 192 193 194 195
    class followfilter:
        def __init__(self, onlyfirst=False):
            self.startrev = -1
            self.roots = []
            self.onlyfirst = onlyfirst

        def match(self, rev):
            def realparents(rev):
                if self.onlyfirst:
                    return repo.changelog.parentrevs(rev)[0:1]
196
                else:
Brendan Cully's avatar
Brendan Cully committed
197 198 199 200 201 202 203 204 205 206 207 208 209
                    return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))

            if self.startrev == -1:
                self.startrev = rev
                return True

            if rev > self.startrev:
                # forward: all descendants
                if not self.roots:
                    self.roots.append(self.startrev)
                for parent in realparents(rev):
                    if parent in self.roots:
                        self.roots.append(rev)
210
                        return True
Brendan Cully's avatar
Brendan Cully committed
211 212 213 214 215 216 217 218
            else:
                # backwards: all parents
                if not self.roots:
                    self.roots.extend(realparents(self.startrev))
                if rev in self.roots:
                    self.roots.remove(rev)
                    self.roots.extend(realparents(rev))
                    return True
219

Brendan Cully's avatar
Brendan Cully committed
220 221 222 223
            return False

    # it might be worthwhile to do this in the iterator if the rev range
    # is descending and the prune args are all within that range
Brendan Cully's avatar
Brendan Cully committed
224
    for rev in opts.get('prune', ()):
Brendan Cully's avatar
Brendan Cully committed
225 226 227 228 229 230
        rev = repo.changelog.rev(repo.lookup(rev))
        ff = followfilter()
        stop = min(revs[0], revs[-1])
        for x in range(rev, stop-1, -1):
            if ff.match(x) and wanted.has_key(x):
                del wanted[x]
231

232
    def iterate():
233
        if follow and not files:
234
            ff = followfilter(onlyfirst=opts.get('follow_first'))
235
            def want(rev):
Brendan Cully's avatar
Brendan Cully committed
236 237 238
                if ff.match(rev) and rev in wanted:
                    return True
                return False
239 240 241 242
        else:
            def want(rev):
                return rev in wanted

243
        for i, window in increasing_windows(0, len(revs)):
244
            yield 'window', revs[0] < revs[-1], revs[-1]
245
            nrevs = [rev for rev in revs[i:i+window] if want(rev)]
246 247 248 249 250 251 252
            srevs = list(nrevs)
            srevs.sort()
            for rev in srevs:
                fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
                yield 'add', rev, fns
            for rev in nrevs:
                yield 'iter', rev, None
253
    return iterate(), getchange, matchfn
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
254

mpm@selenic.com's avatar
mpm@selenic.com committed
255 256
revrangesep = ':'

257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
def revfix(repo, val, defval):
    '''turn user-level id of changeset into rev number.
    user-level id can be tag, changeset, rev number, or negative rev
    number relative to number of revs (-1 is tip, etc).'''
    if not val:
        return defval
    try:
        num = int(val)
        if str(num) != val:
            raise ValueError
        if num < 0:
            num += repo.changelog.count()
        if num < 0:
            num = 0
        elif num >= repo.changelog.count():
            raise ValueError
    except ValueError:
mpm@selenic.com's avatar
mpm@selenic.com committed
274
        try:
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
            num = repo.changelog.rev(repo.lookup(val))
        except KeyError:
            raise util.Abort(_('invalid revision identifier %s'), val)
    return num

def revpair(ui, repo, revs):
    '''return pair of nodes, given list of revisions. second item can
    be None, meaning use working dir.'''
    if not revs:
        return repo.dirstate.parents()[0], None
    end = None
    if len(revs) == 1:
        start = revs[0]
        if revrangesep in start:
            start, end = start.split(revrangesep, 1)
            start = revfix(repo, start, 0)
            end = revfix(repo, end, repo.changelog.count() - 1)
        else:
            start = revfix(repo, start, None)
    elif len(revs) == 2:
        if revrangesep in revs[0] or revrangesep in revs[1]:
            raise util.Abort(_('too many revisions specified'))
        start = revfix(repo, revs[0], None)
        end = revfix(repo, revs[1], None)
    else:
        raise util.Abort(_('too many revisions specified'))
    if end is not None: end = repo.lookup(str(end))
    return repo.lookup(str(start)), end

def revrange(ui, repo, revs):
    """Yield revision as strings from a list of revision specifications."""
306
    seen = {}
mpm@selenic.com's avatar
mpm@selenic.com committed
307
    for spec in revs:
308
        if revrangesep in spec:
mpm@selenic.com's avatar
mpm@selenic.com committed
309
            start, end = spec.split(revrangesep, 1)
310 311
            start = revfix(repo, start, 0)
            end = revfix(repo, end, repo.changelog.count() - 1)
312 313
            step = start > end and -1 or 1
            for rev in xrange(start, end+step, step):
314 315
                if rev in seen:
                    continue
316
                seen[rev] = 1
mpm@selenic.com's avatar
mpm@selenic.com committed
317 318
                yield str(rev)
        else:
319
            rev = revfix(repo, spec, None)
320 321
            if rev in seen:
                continue
322 323
            seen[rev] = 1
            yield str(rev)
mpm@selenic.com's avatar
mpm@selenic.com committed
324

325 326
def write_bundle(cg, filename=None, compress=True):
    """Write a bundle file and return its filename.
327

328 329 330 331
    Existing files will not be overwritten.
    If no filename is specified, a temporary file is created.
    bz2 compression can be turned off.
    The bundle file will be deleted in case of errors.
332
    """
333 334 335 336 337
    class nocompress(object):
        def compress(self, x):
            return x
        def flush(self):
            return ""
338 339 340

    fh = None
    cleanup = None
341
    try:
342 343 344 345 346
        if filename:
            if os.path.exists(filename):
                raise util.Abort(_("file '%s' already exists"), filename)
            fh = open(filename, "wb")
        else:
347
            fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
348 349 350
            fh = os.fdopen(fd, "wb")
        cleanup = filename

351 352 353 354
        if compress:
            fh.write("HG10")
            z = bz2.BZ2Compressor(9)
        else:
355
            fh.write("HG10UN")
356
            z = nocompress()
357 358 359 360 361 362 363 364 365 366 367
        # parse the changegroup data, otherwise we will block
        # in case of sshrepo because we don't know the end of the stream

        # an empty chunkiter is the end of the changegroup
        empty = False
        while not empty:
            empty = True
            for chunk in changegroup.chunkiter(cg):
                empty = False
                fh.write(z.compress(changegroup.genchunk(chunk)))
            fh.write(z.compress(changegroup.closechunk()))
368
        fh.write(z.flush())
369 370 371 372 373 374 375
        cleanup = None
        return filename
    finally:
        if fh is not None:
            fh.close()
        if cleanup is not None:
            os.unlink(cleanup)
376

377
def trimuser(ui, name, rev, revcache):
378
    """trim the name of the user who committed a change"""
379 380 381 382
    user = revcache.get(rev)
    if user is None:
        user = revcache[rev] = ui.shortuser(name)
    return user
383

384
class changeset_printer(object):
385 386
    '''show changeset information when templating not requested.'''

387 388 389 390 391
    def __init__(self, ui, repo):
        self.ui = ui
        self.repo = repo

    def show(self, rev=0, changenode=None, brinfo=None):
392
        '''show a single changeset or file revision'''
393 394 395 396 397 398 399 400 401
        log = self.repo.changelog
        if changenode is None:
            changenode = log.node(rev)
        elif not rev:
            rev = log.rev(changenode)

        if self.ui.quiet:
            self.ui.write("%d:%s\n" % (rev, short(changenode)))
            return
402

403 404
        changes = log.read(changenode)
        date = util.datestr(changes[2])
405

406 407 408 409 410 411
        parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
                   for p in log.parents(changenode)
                   if self.ui.debugflag or p != nullid]
        if (not self.ui.debugflag and len(parents) == 1 and
            parents[0][0] == rev-1):
            parents = []
412

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
        if self.ui.verbose:
            self.ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
        else:
            self.ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))

        for tag in self.repo.nodetags(changenode):
            self.ui.status(_("tag:         %s\n") % tag)
        for parent in parents:
            self.ui.write(_("parent:      %d:%s\n") % parent)

        if brinfo and changenode in brinfo:
            br = brinfo[changenode]
            self.ui.write(_("branch:      %s\n") % " ".join(br))

        self.ui.debug(_("manifest:    %d:%s\n") %
                      (self.repo.manifest.rev(changes[0]), hex(changes[0])))
        self.ui.status(_("user:        %s\n") % changes[1])
        self.ui.status(_("date:        %s\n") % date)

        if self.ui.debugflag:
Vadim Gelfer's avatar
Vadim Gelfer committed
433
            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
434 435 436 437
            for key, value in zip([_("files:"), _("files+:"), _("files-:")],
                                  files):
                if value:
                    self.ui.note("%-12s %s\n" % (key, " ".join(value)))
mpm@selenic.com's avatar
mpm@selenic.com committed
438
        else:
439 440 441 442 443 444 445 446 447 448 449 450 451 452
            self.ui.note(_("files:       %s\n") % " ".join(changes[3]))

        description = changes[4].strip()
        if description:
            if self.ui.verbose:
                self.ui.status(_("description:\n"))
                self.ui.status(description)
                self.ui.status("\n\n")
            else:
                self.ui.status(_("summary:     %s\n") %
                               description.splitlines()[0])
        self.ui.status("\n")

def show_changeset(ui, repo, opts):
453
    '''show one changeset.  uses template or regular display.  caller
454
    can pass in 'style' and 'template' options in opts.'''
455

456 457 458 459 460 461
    tmpl = opts.get('template')
    if tmpl:
        tmpl = templater.parsestring(tmpl, quoted=False)
    else:
        tmpl = ui.config('ui', 'logtemplate')
        if tmpl: tmpl = templater.parsestring(tmpl)
462
    mapfile = opts.get('style') or ui.config('ui', 'style')
463 464 465
    if tmpl or mapfile:
        if mapfile:
            if not os.path.isfile(mapfile):
466 467
                mapname = templater.templatepath('map-cmdline.' + mapfile)
                if not mapname: mapname = templater.templatepath(mapfile)
468 469
                if mapname: mapfile = mapname
        try:
470
            t = templater.changeset_templater(ui, repo, mapfile)
471 472 473 474 475
        except SyntaxError, inst:
            raise util.Abort(inst.args[0])
        if tmpl: t.use_template(tmpl)
        return t
    return changeset_printer(ui, repo)
476

Matt Mackall's avatar
Matt Mackall committed
477 478 479 480 481 482 483
def setremoteconfig(ui, opts):
    "copy remote options to ui tree"
    if opts.get('ssh'):
        ui.setconfig("ui", "ssh", opts['ssh'])
    if opts.get('remotecmd'):
        ui.setconfig("ui", "remotecmd", opts['remotecmd'])

484
def show_version(ui):
485
    """output version and copyright information"""
486
    ui.write(_("Mercurial Distributed SCM (version %s)\n")
487
             % version.get_version())
488
    ui.status(_(
Vadim Gelfer's avatar
Vadim Gelfer committed
489
        "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
490 491 492
        "This is free software; see the source for copying conditions. "
        "There is NO\nwarranty; "
        "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
493
    ))
494

495 496 497 498
def help_(ui, name=None, with_version=False):
    """show help for a command, extension, or list of commands

    With no arguments, print a list of commands and short help.
Vadim Gelfer's avatar
Vadim Gelfer committed
499

500 501 502 503
    Given a command name, print help for that command.

    Given an extension name, print help for that extension, and the
    commands it provides."""
504
    option_lists = []
505 506

    def helpcmd(name):
507 508 509
        if with_version:
            show_version(ui)
            ui.write('\n')
510
        aliases, i = findcmd(name)
511
        # synopsis
512 513
        ui.write("%s\n\n" % i[2])

514 515
        # description
        doc = i[0].__doc__
516 517
        if not doc:
            doc = _("(No help text available)")
518 519 520 521 522
        if ui.quiet:
            doc = doc.splitlines(0)[0]
        ui.write("%s\n" % doc.rstrip())

        if not ui.quiet:
523
            # aliases
524 525
            if len(aliases) > 1:
                ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
526

527 528 529
            # options
            if i[1]:
                option_lists.append(("options", i[1]))
Vadim Gelfer's avatar
Vadim Gelfer committed
530

531
    def helplist(select=None):
mpm@selenic.com's avatar
mpm@selenic.com committed
532
        h = {}
533
        cmds = {}
534
        for c, e in table.items():
535 536 537 538
            f = c.split("|", 1)[0]
            if select and not select(f):
                continue
            if name == "shortlist" and not f.startswith("^"):
mpm@selenic.com's avatar
mpm@selenic.com committed
539
                continue
540
            f = f.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
541
            if not ui.debugflag and f.startswith("debug"):
542
                continue
543 544 545 546
            doc = e[0].__doc__
            if not doc:
                doc = _("(No help text available)")
            h[f] = doc.splitlines(0)[0].rstrip()
547
            cmds[f] = c.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
548 549 550 551 552

        fns = h.keys()
        fns.sort()
        m = max(map(len, fns))
        for f in fns:
553 554
            if ui.verbose:
                commands = cmds[f].replace("|",", ")
555
                ui.write(" %s:\n      %s\n"%(commands, h[f]))
556 557
            else:
                ui.write(' %-*s   %s\n' % (m, f, h[f]))
mpm@selenic.com's avatar
mpm@selenic.com committed
558

559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
    def helpext(name):
        try:
            mod = findext(name)
        except KeyError:
            raise UnknownCommand(name)

        doc = (mod.__doc__ or _('No help text available')).splitlines(0)
        ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
        for d in doc[1:]:
            ui.write(d, '\n')

        ui.status('\n')
        if ui.verbose:
            ui.status(_('list of commands:\n\n'))
        else:
            ui.status(_('list of commands (use "hg help -v %s" '
                        'to show aliases and global options):\n\n') % name)

        modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
        helplist(modcmds.has_key)

    if name and name != 'shortlist':
        try:
            helpcmd(name)
        except UnknownCommand:
            helpext(name)

    else:
        # program name
        if ui.verbose or with_version:
            show_version(ui)
        else:
            ui.status(_("Mercurial Distributed SCM\n"))
        ui.status('\n')

        # list of commands
        if name == "shortlist":
            ui.status(_('basic commands (use "hg help" '
                        'for the full list or option "-v" for details):\n\n'))
        elif ui.verbose:
            ui.status(_('list of commands:\n\n'))
        else:
            ui.status(_('list of commands (use "hg help -v" '
                        'to show aliases and global options):\n\n'))

        helplist()

606 607
    # global options
    if ui.verbose:
608 609 610 611 612 613 614 615 616 617
        option_lists.append(("global options", globalopts))

    # list all option lists
    opt_output = []
    for title, options in option_lists:
        opt_output.append(("\n%s:\n" % title, None))
        for shortopt, longopt, default, desc in options:
            opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
                                          longopt and " --%s" % longopt),
                               "%s%s" % (desc,
618 619
                                         default
                                         and _(" (default: %s)") % default
620 621 622 623 624 625 626 627 628
                                         or "")))

    if opt_output:
        opts_len = max([len(line[0]) for line in opt_output if line[1]])
        for first, second in opt_output:
            if second:
                ui.write(" %-*s  %s\n" % (opts_len, first, second))
            else:
                ui.write("%s\n" % first)
mpm@selenic.com's avatar
mpm@selenic.com committed
629 630

# Commands start here, listed alphabetically
631

632
def add(ui, repo, *pats, **opts):
633 634 635 636 637 638
    """add the specified files on the next commit

    Schedule files to be version controlled and added to the repository.

    The files will be added to the repository at the next commit.

639
    If no names are given, add all files in the repository.
640 641
    """

642
    names = []
643
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
644
        if exact:
645 646
            if ui.verbose:
                ui.status(_('adding %s\n') % rel)
647 648
            names.append(abs)
        elif repo.dirstate.state(abs) == '?':
649
            ui.status(_('adding %s\n') % rel)
650
            names.append(abs)
651
    if not opts.get('dry_run'):
Vadim Gelfer's avatar
Vadim Gelfer committed
652
        repo.add(names)
653

654
def addremove(ui, repo, *pats, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
655
    """add all new files, delete all missing files (DEPRECATED)
656 657 658 659 660 661

    Add all new files and remove all missing files from the repository.

    New files are ignored if they match any of the patterns in .hgignore. As
    with add, these changes take effect at the next commit.
    """
662
    return cmdutil.addremove(repo, pats, opts)
663

664
def annotate(ui, repo, *pats, **opts):
665 666 667 668 669 670 671 672 673 674 675
    """show changeset information per file line

    List changes in files, showing the revision id responsible for each line

    This command is useful to discover who did a change or when a change took
    place.

    Without the -a option, annotate will avoid processing files it
    detects as binary. With -a, annotate will generate an annotation
    anyway, probably with undesirable results.
    """
676
    def getnode(rev):
677
        return short(repo.changelog.node(rev))
678

679
    ucache = {}
680
    def getname(rev):
681 682 683 684 685 686
        try:
            return ucache[rev]
        except:
            u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
            ucache[rev] = u
            return u
687

688 689
    dcache = {}
    def getdate(rev):
690
        datestr = dcache.get(rev)
691
        if datestr is None:
692
            datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
693
        return datestr
694

695
    if not pats:
696
        raise util.Abort(_('at least one file name or pattern required'))
697

Matt Mackall's avatar
Matt Mackall committed
698 699
    opmap = [['user', getname], ['number', str], ['changeset', getnode],
             ['date', getdate]]
700
    if not opts['user'] and not opts['changeset'] and not opts['date']:
701
        opts['number'] = 1
702

703
    ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
704

705 706
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
                                             node=ctx.node()):
707 708
        fctx = ctx.filectx(abs)
        if not opts['text'] and util.binary(fctx.data()):
709
            ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
710 711
            continue

712
        lines = fctx.annotate()
713 714 715
        pieces = []

        for o, f in opmap:
716
            if opts[o]:
717
                l = [f(n) for n, dummy in lines]
mpm@selenic.com's avatar
mpm@selenic.com committed
718 719 720
                if l:
                    m = max(map(len, l))
                    pieces.append(["%*s" % (m, x) for x in l])
721

mpm@selenic.com's avatar
mpm@selenic.com committed
722 723 724
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
725

726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
def archive(ui, repo, dest, **opts):
    '''create unversioned archive of a repository revision

    By default, the revision used is the parent of the working
    directory; use "-r" to specify a different revision.

    To specify the type of archive to create, use "-t".  Valid
    types are:

    "files" (default): a directory full of files
    "tar": tar archive, uncompressed
    "tbz2": tar archive, compressed using bzip2
    "tgz": tar archive, compressed using gzip
    "uzip": zip archive, uncompressed
    "zip": zip archive, compressed using deflate

    The exact name of the destination archive or directory is given
    using a format string; see "hg help export" for details.

    Each member added to an archive file has a directory prefix
    prepended.  Use "-p" to specify a format string for the prefix.
    The default is the basename of the archive, with suffixes removed.
    '''

    if opts['rev']:
        node = repo.lookup(opts['rev'])
    else:
        node, p2 = repo.dirstate.parents()
        if p2 != nullid:
            raise util.Abort(_('uncommitted merge - please provide a '
                               'specific revision'))

Vadim Gelfer's avatar
Vadim Gelfer committed
758
    dest = cmdutil.make_filename(repo, dest, node)
759 760
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
761
    dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
762 763 764 765 766 767 768
    kind = opts.get('type') or 'files'
    prefix = opts['prefix']
    if dest == '-':
        if kind == 'files':
            raise util.Abort(_('cannot archive plain files to stdout'))
        dest = sys.stdout
        if not prefix: prefix = os.path.basename(repo.root) + '-%h'
Vadim Gelfer's avatar
Vadim Gelfer committed
769
    prefix = cmdutil.make_filename(repo, prefix, node)
770 771
    archival.archive(repo, dest, node, kind, not opts['no_decode'],
                     matchfn, prefix)
772

Vadim Gelfer's avatar
Vadim Gelfer committed
773 774 775
def backout(ui, repo, rev, **opts):
    '''reverse effect of earlier changeset

Vadim Gelfer's avatar
Vadim Gelfer committed
776 777
    Commit the backed out changes as a new changeset.  The new
    changeset is a child of the backed out changeset.
Vadim Gelfer's avatar
Vadim Gelfer committed
778 779

    If you back out a changeset other than the tip, a new head is
Vadim Gelfer's avatar
Vadim Gelfer committed
780 781 782 783 784 785 786 787 788 789
    created.  This head is the parent of the working directory.  If
    you back out an old changeset, your working directory will appear
    old after the backout.  You should merge the backout changeset
    with another head.

    The --merge option remembers the parent of the working directory
    before starting the backout, then merges the new head with that
    changeset afterwards.  This saves you from doing the merge by
    hand.  The result of this merge is not committed, as for a normal
    merge.'''
Vadim Gelfer's avatar
Vadim Gelfer committed
790 791 792 793 794 795

    bail_if_changed(repo)
    op1, op2 = repo.dirstate.parents()
    if op2 != nullid:
        raise util.Abort(_('outstanding uncommitted merge'))
    node = repo.lookup(rev)
796 797
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
Vadim Gelfer's avatar
Vadim Gelfer committed
798 799
        raise util.Abort(_('cannot back out a change with no parents'))
    if p2 != nullid:
800 801 802 803 804 805 806 807 808 809 810 811
        if not opts['parent']:
            raise util.Abort(_('cannot back out a merge changeset without '
                               '--parent'))
        p = repo.lookup(opts['parent'])
        if p not in (p1, p2):
            raise util.Abort(_('%s is not a parent of %s' %
                               (short(p), short(node))))
        parent = p
    else:
        if opts['parent']:
            raise util.Abort(_('cannot use --parent on non-merge changeset'))
        parent = p1
812
    hg.clean(repo, node, show_stats=False)
Vadim Gelfer's avatar
Vadim Gelfer committed
813 814 815 816 817
    revert_opts = opts.copy()
    revert_opts['rev'] = hex(parent)
    revert(ui, repo, **revert_opts)
    commit_opts = opts.copy()
    commit_opts['addremove'] = False
818
    if not commit_opts['message'] and not commit_opts['logfile']:
Vadim Gelfer's avatar
Vadim Gelfer committed
819
        commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
820
        commit_opts['force_editor'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
821 822 823 824 825
    commit(ui, repo, **commit_opts)
    def nice(node):
        return '%d:%s' % (repo.changelog.rev(node), short(node))
    ui.status(_('changeset %s backs out changeset %s\n') %
              (nice(repo.changelog.tip()), nice(node)))
826 827 828
    if op1 != node:
        if opts['merge']:
            ui.status(_('merging with changeset %s\n') % nice(op1))
829
            n = _lookup(repo, hex(op1))
830
            hg.merge(repo, n)
831 832 833
        else:
            ui.status(_('the backout changeset is a new head - '
                        'do not forget to merge\n'))
834 835
            ui.status(_('(use "backout --merge" '
                        'if you want to auto-merge)\n'))
Vadim Gelfer's avatar
Vadim Gelfer committed
836

837
def bundle(ui, repo, fname, dest=None, **opts):
838 839 840 841 842 843 844 845 846 847 848 849 850 851
    """create a changegroup file

    Generate a compressed changegroup file collecting all changesets
    not found in the other repository.

    This file can then be transferred using conventional means and
    applied to another repository with the unbundle command. This is
    useful when native push and pull are not available or when
    exporting an entire repository is undesirable. The standard file
    extension is ".hg".

    Unlike import/export, this exactly preserves all changeset
    contents including permissions, rename data, and revision history.
    """
852
    dest = ui.expandpath(dest or 'default-push', dest or 'default')
853
    other = hg.repository(ui, dest)
854
    o = repo.findoutgoing(other, force=opts['force'])
855
    cg = repo.changegroup(o, 'bundle')
856
    write_bundle(cg, fname)
857

858
def cat(ui, repo, file1, *pats, **opts):
859 860 861 862 863 864 865 866 867 868 869 870 871
    """output the latest or given revisions of files

    Print the specified files as they were at the given revision.
    If no revision is given then the tip is used.

    Output may be to a file, in which case the name of the file is
    given using a format string.  The formatting rules are the same as
    for the export command, with the following additions:

    %s   basename of file being printed
    %d   dirname of file being printed, or '.' if in repo root
    %p   root-relative path name of file being printed
    """
872
    ctx = repo.changectx(opts['rev'] or "-1")
873 874
    for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
                                             ctx.node()):
Vadim Gelfer's avatar
Vadim Gelfer committed
875
        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
876
        fp.write(ctx.filectx(abs).data())
mpm@selenic.com's avatar
mpm@selenic.com committed
877

878
def clone(ui, source, dest=None, **opts):
879 880 881 882 883 884 885 886 887 888 889
    """make a copy of an existing repository

    Create a copy of an existing repository in a new directory.

    If no destination directory name is specified, it defaults to the
    basename of the source.

    The location of the source is added to the new repository's
    .hg/hgrc file, as the default to be used for future pulls.

    For efficiency, hardlinks are used for cloning whenever the source
890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
    and destination are on the same filesystem (note this applies only
    to the repository data, not to the checked out files).  Some
    filesystems, such as AFS, implement hardlinking incorrectly, but
    do not report errors.  In these cases, use the --pull option to
    avoid hardlinking.

    You can safely clone repositories and checked out files using full
    hardlinks with

      $ cp -al REPO REPOCLONE

    which is the fastest way to clone. However, the operation is not
    atomic (making sure REPO is not modified during the operation is
    up to you) and you have to make sure your editor breaks hardlinks
    (Emacs and most Linux Kernel tools do so).
905

906 907 908
    If you use the -r option to clone up to a specific revision, no
    subsequent revisions will be present in the cloned repository.
    This option implies --pull, even on local repositories.
909 910

    See pull for valid source format details.
911 912 913 914

    It is possible to specify an ssh:// URL as the destination, but no
    .hg/hgrc will be created on the remote side. Look at the help text
    for the pull command for important details about ssh:// URLs.
915
    """
Matt Mackall's avatar
Matt Mackall committed
916
    setremoteconfig(ui, opts)
917 918
    hg.clone(ui, ui.expandpath(source), dest,
             pull=opts['pull'],
919
             stream=opts['uncompressed'],
920 921
             rev=opts['rev'],
             update=not opts['noupdate'])
mpm@selenic.com's avatar
mpm@selenic.com committed
922

923
def commit(ui, repo, *pats, **opts):
924 925 926 927 928
    """commit the specified files or all outstanding changes

    Commit changes to the given files into the repository.

    If a list of files is omitted, all changes reported by "hg status"
929
    will be committed.
930

931 932
    If no commit message is specified, the editor configured in your hgrc
    or in the EDITOR environment variable is started to enter a message.
933
    """
934
    message = logmessage(opts)
935

mpm@selenic.com's avatar
mpm@selenic.com committed
936
    if opts['addremove']:
937
        cmdutil.addremove(repo, pats, opts)
938
    fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
939
    if pats:
Vadim Gelfer's avatar
Vadim Gelfer committed
940
        modified, added, removed = repo.status(files=fns, match=match)[:3]
941
        files = modified + added + removed