commands.py 126 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 shlex")
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
    if follow:
Brendan Cully's avatar
Brendan Cully committed
102
        defrange = '%s:0' % repo.changectx().rev()
103 104
    else:
        defrange = 'tip:0'
105
    revs = map(int, cmdutil.revrange(ui, repo, opts['rev'] or [defrange]))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
106 107 108
    wanted = {}
    slowpath = anypats
    fncache = {}
109 110 111 112 113 114 115 116

    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
117 118 119
    if not slowpath and not files:
        # No files, no patterns.  Display all revs.
        wanted = dict(zip(revs, revs))
120
    copies = []
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
121 122
    if not slowpath:
        # Only files, no patterns.  Check the history of each file.
123
        def filerevgen(filelog, node):
124
            cl_count = repo.changelog.count()
125 126 127 128 129
            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
130
                revs = []
131
                for j in xrange(i - window, i + 1):
132 133 134
                    n = filelog.node(j)
                    revs.append((filelog.linkrev(n),
                                 follow and filelog.renamed(n)))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
135 136
                revs.reverse()
                for rev in revs:
137 138
                    # only yield rev for which we have the changelog, it can
                    # happen while doing "hg log" during a pull or commit
139
                    if rev[0] < cl_count:
140
                        yield rev
141 142 143 144 145
        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
146
        minrev, maxrev = min(revs), max(revs)
147
        for file_, node in iterfiles():
148
            filelog = repo.file(file_)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
149 150 151 152 153
            # 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
154
            for rev, copied in filerevgen(filelog, node):
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
155
                if rev <= maxrev:
Thomas Arendsen Hein's avatar
Thomas Arendsen Hein committed
156 157
                    if rev < minrev:
                        break
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
158
                    fncache.setdefault(rev, [])
159
                    fncache[rev].append(file_)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
160
                    wanted[rev] = 1
161 162
                    if follow and copied:
                        copies.append(copied)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
163
    if slowpath:
164 165 166 167
        if follow:
            raise util.Abort(_('can only follow copies/renames for explicit '
                               'file names'))

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
168 169
        # The slow path checks files modified in every changeset.
        def changerevgen():
170
            for i, window in increasing_windows(repo.changelog.count()-1, -1):
171
                for j in xrange(i - window, i + 1):
172
                    yield j, getchange(j)[3]
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
173 174 175 176 177 178 179

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

Brendan Cully's avatar
Brendan Cully committed
180 181 182 183 184 185 186 187 188 189
    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]
190
                else:
Brendan Cully's avatar
Brendan Cully committed
191 192 193 194 195 196 197 198 199 200 201 202 203
                    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)
204
                        return True
Brendan Cully's avatar
Brendan Cully committed
205 206 207 208 209 210 211 212
            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
213

Brendan Cully's avatar
Brendan Cully committed
214 215 216 217
            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
218
    for rev in opts.get('prune', ()):
Brendan Cully's avatar
Brendan Cully committed
219 220 221 222 223 224
        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]
225

226
    def iterate():
227
        if follow and not files:
228
            ff = followfilter(onlyfirst=opts.get('follow_first'))
229
            def want(rev):
Brendan Cully's avatar
Brendan Cully committed
230 231 232
                if ff.match(rev) and rev in wanted:
                    return True
                return False
233 234 235 236
        else:
            def want(rev):
                return rev in wanted

237
        for i, window in increasing_windows(0, len(revs)):
238
            yield 'window', revs[0] < revs[-1], revs[-1]
239
            nrevs = [rev for rev in revs[i:i+window] if want(rev)]
240 241 242 243 244 245 246
            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
247
    return iterate(), getchange, matchfn
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
248

249 250
def write_bundle(cg, filename=None, compress=True):
    """Write a bundle file and return its filename.
251

252 253 254 255
    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.
256
    """
257 258 259 260 261
    class nocompress(object):
        def compress(self, x):
            return x
        def flush(self):
            return ""
262 263 264

    fh = None
    cleanup = None
265
    try:
266 267
        if filename:
            if os.path.exists(filename):
268
                raise util.Abort(_("file '%s' already exists") % filename)
269 270
            fh = open(filename, "wb")
        else:
271
            fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
272 273 274
            fh = os.fdopen(fd, "wb")
        cleanup = filename

275 276 277 278
        if compress:
            fh.write("HG10")
            z = bz2.BZ2Compressor(9)
        else:
279
            fh.write("HG10UN")
280
            z = nocompress()
281 282 283 284 285 286 287 288 289 290 291
        # 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()))
292
        fh.write(z.flush())
293 294 295 296 297 298 299
        cleanup = None
        return filename
    finally:
        if fh is not None:
            fh.close()
        if cleanup is not None:
            os.unlink(cleanup)
300

301
def trimuser(ui, name, rev, revcache):
302
    """trim the name of the user who committed a change"""
303 304 305 306
    user = revcache.get(rev)
    if user is None:
        user = revcache[rev] = ui.shortuser(name)
    return user
307

308
class changeset_printer(object):
309 310
    '''show changeset information when templating not requested.'''

311 312 313 314 315
    def __init__(self, ui, repo):
        self.ui = ui
        self.repo = repo

    def show(self, rev=0, changenode=None, brinfo=None):
316
        '''show a single changeset or file revision'''
317 318 319 320 321 322 323 324 325
        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
326

327 328
        changes = log.read(changenode)
        date = util.datestr(changes[2])
329

330 331 332
        hexfunc = self.ui.debugflag and hex or short

        parents = [(log.rev(p), hexfunc(p)) for p in log.parents(changenode)
333 334 335 336
                   if self.ui.debugflag or p != nullid]
        if (not self.ui.debugflag and len(parents) == 1 and
            parents[0][0] == rev-1):
            parents = []
337

338
        self.ui.write(_("changeset:   %d:%s\n") % (rev, hexfunc(changenode)))
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354

        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
355
            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
356 357 358 359
            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
360
        else:
361 362 363 364 365 366 367 368 369 370 371 372 373 374
            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):
375
    '''show one changeset.  uses template or regular display.  caller
376
    can pass in 'style' and 'template' options in opts.'''
377

378 379 380 381 382 383
    tmpl = opts.get('template')
    if tmpl:
        tmpl = templater.parsestring(tmpl, quoted=False)
    else:
        tmpl = ui.config('ui', 'logtemplate')
        if tmpl: tmpl = templater.parsestring(tmpl)
384
    mapfile = opts.get('style') or ui.config('ui', 'style')
385 386 387
    if tmpl or mapfile:
        if mapfile:
            if not os.path.isfile(mapfile):
388 389
                mapname = templater.templatepath('map-cmdline.' + mapfile)
                if not mapname: mapname = templater.templatepath(mapfile)
390 391
                if mapname: mapfile = mapname
        try:
392
            t = templater.changeset_templater(ui, repo, mapfile)
393 394 395 396 397
        except SyntaxError, inst:
            raise util.Abort(inst.args[0])
        if tmpl: t.use_template(tmpl)
        return t
    return changeset_printer(ui, repo)
398

Matt Mackall's avatar
Matt Mackall committed
399 400 401 402 403 404 405
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'])

406
def show_version(ui):
407
    """output version and copyright information"""
408
    ui.write(_("Mercurial Distributed SCM (version %s)\n")
409
             % version.get_version())
410
    ui.status(_(
Vadim Gelfer's avatar
Vadim Gelfer committed
411
        "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
412 413 414
        "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"
415
    ))
416

417 418 419 420
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
421

422 423 424 425
    Given a command name, print help for that command.

    Given an extension name, print help for that extension, and the
    commands it provides."""
426
    option_lists = []
427 428

    def helpcmd(name):
429 430 431
        if with_version:
            show_version(ui)
            ui.write('\n')
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
432
        aliases, i = findcmd(ui, name)
433
        # synopsis
434 435
        ui.write("%s\n\n" % i[2])

436 437
        # description
        doc = i[0].__doc__
438 439
        if not doc:
            doc = _("(No help text available)")
440 441 442 443 444
        if ui.quiet:
            doc = doc.splitlines(0)[0]
        ui.write("%s\n" % doc.rstrip())

        if not ui.quiet:
445
            # aliases
446 447
            if len(aliases) > 1:
                ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
448

449 450 451
            # options
            if i[1]:
                option_lists.append(("options", i[1]))
Vadim Gelfer's avatar
Vadim Gelfer committed
452

453
    def helplist(select=None):
mpm@selenic.com's avatar
mpm@selenic.com committed
454
        h = {}
455
        cmds = {}
456
        for c, e in table.items():
457 458 459 460
            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
461
                continue
462
            f = f.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
463
            if not ui.debugflag and f.startswith("debug"):
464
                continue
465 466 467 468
            doc = e[0].__doc__
            if not doc:
                doc = _("(No help text available)")
            h[f] = doc.splitlines(0)[0].rstrip()
469
            cmds[f] = c.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
470 471 472 473 474

        fns = h.keys()
        fns.sort()
        m = max(map(len, fns))
        for f in fns:
475 476
            if ui.verbose:
                commands = cmds[f].replace("|",", ")
477
                ui.write(" %s:\n      %s\n"%(commands, h[f]))
478 479
            else:
                ui.write(' %-*s   %s\n' % (m, f, h[f]))
mpm@selenic.com's avatar
mpm@selenic.com committed
480

481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
    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()

528 529
    # global options
    if ui.verbose:
530 531 532 533 534 535 536 537 538 539
        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,
540 541
                                         default
                                         and _(" (default: %s)") % default
542 543 544 545 546 547 548 549 550
                                         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
551 552

# Commands start here, listed alphabetically
553

554
def add(ui, repo, *pats, **opts):
555 556 557 558 559 560
    """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.

561
    If no names are given, add all files in the repository.
562 563
    """

564
    names = []
565
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
566
        if exact:
567 568
            if ui.verbose:
                ui.status(_('adding %s\n') % rel)
569 570
            names.append(abs)
        elif repo.dirstate.state(abs) == '?':
571
            ui.status(_('adding %s\n') % rel)
572
            names.append(abs)
573
    if not opts.get('dry_run'):
Vadim Gelfer's avatar
Vadim Gelfer committed
574
        repo.add(names)
575

576
def addremove(ui, repo, *pats, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
577
    """add all new files, delete all missing files (DEPRECATED)
578 579 580 581 582

    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.
583 584 585 586 587 588

    Use the -s option to detect renamed files.  With a parameter > 0,
    this compares every removed file with every added file and records
    those similar enough as renames.  This option takes a percentage
    between 0 (disabled) and 100 (files must be identical) as its
    parameter.  Detecting renamed files this way can be expensive.
589
    """
590 591 592 593
    sim = float(opts.get('similarity') or 0)
    if sim < 0 or sim > 100:
        raise util.Abort(_('similarity must be between 0 and 100'))
    return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
594

595
def annotate(ui, repo, *pats, **opts):
596 597 598 599 600 601 602 603 604 605 606
    """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.
    """
607
    getdate = util.cachefunc(lambda x: util.datestr(x.date()))
608

609
    if not pats:
610
        raise util.Abort(_('at least one file name or pattern required'))
611

612 613 614
    opmap = [['user', lambda x: ui.shortuser(x.user())],
             ['number', lambda x: str(x.rev())],
             ['changeset', lambda x: short(x.node())],
615 616 617
             ['date', getdate], ['follow', lambda x: x.path()]]
    if (not opts['user'] and not opts['changeset'] and not opts['date']
        and not opts['follow']):
618
        opts['number'] = 1
619

Brendan Cully's avatar
Brendan Cully committed
620
    ctx = repo.changectx(opts['rev'])
621

622 623
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
                                             node=ctx.node()):
624 625
        fctx = ctx.filectx(abs)
        if not opts['text'] and util.binary(fctx.data()):
626
            ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
627 628
            continue

629
        lines = fctx.annotate(follow=opts.get('follow'))
630 631 632
        pieces = []

        for o, f in opmap:
633
            if opts[o]:
634
                l = [f(n) for n, dummy in lines]
mpm@selenic.com's avatar
mpm@selenic.com committed
635 636 637
                if l:
                    m = max(map(len, l))
                    pieces.append(["%*s" % (m, x) for x in l])
638

mpm@selenic.com's avatar
mpm@selenic.com committed
639 640 641
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
642

643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
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.
    '''

Brendan Cully's avatar
Brendan Cully committed
667
    node = repo.changectx(opts['rev']).node()
Vadim Gelfer's avatar
Vadim Gelfer committed
668
    dest = cmdutil.make_filename(repo, dest, node)
669 670
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
671
    dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
672 673 674 675 676 677 678
    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
679
    prefix = cmdutil.make_filename(repo, prefix, node)
680 681
    archival.archive(repo, dest, node, kind, not opts['no_decode'],
                     matchfn, prefix)
682

Vadim Gelfer's avatar
Vadim Gelfer committed
683 684 685
def backout(ui, repo, rev, **opts):
    '''reverse effect of earlier changeset

Vadim Gelfer's avatar
Vadim Gelfer committed
686 687
    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
688 689

    If you back out a changeset other than the tip, a new head is
Vadim Gelfer's avatar
Vadim Gelfer committed
690 691 692 693 694 695 696 697 698 699
    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
700 701 702 703 704 705

    bail_if_changed(repo)
    op1, op2 = repo.dirstate.parents()
    if op2 != nullid:
        raise util.Abort(_('outstanding uncommitted merge'))
    node = repo.lookup(rev)
706 707
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
Vadim Gelfer's avatar
Vadim Gelfer committed
708 709
        raise util.Abort(_('cannot back out a change with no parents'))
    if p2 != nullid:
710 711 712 713 714 715 716 717 718 719 720 721
        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
722
    hg.clean(repo, node, show_stats=False)
Vadim Gelfer's avatar
Vadim Gelfer committed
723
    revert_opts = opts.copy()
724
    revert_opts['all'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
725 726 727 728
    revert_opts['rev'] = hex(parent)
    revert(ui, repo, **revert_opts)
    commit_opts = opts.copy()
    commit_opts['addremove'] = False
729
    if not commit_opts['message'] and not commit_opts['logfile']:
Vadim Gelfer's avatar
Vadim Gelfer committed
730
        commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
731
        commit_opts['force_editor'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
732 733 734 735 736
    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)))
737 738 739
    if op1 != node:
        if opts['merge']:
            ui.status(_('merging with changeset %s\n') % nice(op1))
740
            n = _lookup(repo, hex(op1))
741
            hg.merge(repo, n)
742 743 744
        else:
            ui.status(_('the backout changeset is a new head - '
                        'do not forget to merge\n'))
745 746
            ui.status(_('(use "backout --merge" '
                        'if you want to auto-merge)\n'))
Vadim Gelfer's avatar
Vadim Gelfer committed
747

748
def bundle(ui, repo, fname, dest=None, **opts):
749 750 751 752 753 754 755 756 757 758 759 760 761 762
    """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.
    """
763
    dest = ui.expandpath(dest or 'default-push', dest or 'default')
764
    other = hg.repository(ui, dest)
765
    o = repo.findoutgoing(other, force=opts['force'])
766
    cg = repo.changegroup(o, 'bundle')
767
    write_bundle(cg, fname)
768

769
def cat(ui, repo, file1, *pats, **opts):
770 771 772
    """output the latest or given revisions of files

    Print the specified files as they were at the given revision.
773 774
    If no revision is given then working dir parent is used, or tip
    if no revision is checked out.
775 776 777 778 779 780 781 782 783

    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
    """
Brendan Cully's avatar
Brendan Cully committed
784
    ctx = repo.changectx(opts['rev'])
785 786
    for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
                                             ctx.node()):
Vadim Gelfer's avatar
Vadim Gelfer committed
787
        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
788
        fp.write(ctx.filectx(abs).data())
mpm@selenic.com's avatar
mpm@selenic.com committed
789

790
def clone(ui, source, dest=None, **opts):
791 792 793 794 795 796 797 798 799 800 801
    """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
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
    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).
817

818 819 820
    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.
821 822

    See pull for valid source format details.
823 824 825 826

    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.
827
    """
Matt Mackall's avatar
Matt Mackall committed
828
    setremoteconfig(ui, opts)
829 830
    hg.clone(ui, ui.expandpath(source), dest,
             pull=opts['pull'],
831
             stream=opts['uncompressed'],
832 833
             rev=opts['rev'],
             update=not opts['noupdate'])
mpm@selenic.com's avatar
mpm@selenic.com committed
834

835
def commit(ui, repo, *pats, **opts):
836 837 838 839 840
    """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"
841
    will be committed.
842

843 844
    If no commit message is specified, the editor configured in your hgrc
    or in the EDITOR environment variable is started to enter a message.
845
    """
846
    message = logmessage(opts)
847

mpm@selenic.com's avatar
mpm@selenic.com committed
848
    if opts['addremove']:
849
        cmdutil.addremove(repo, pats, opts)
850
    fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
851
    if pats:
Vadim Gelfer's avatar
Vadim Gelfer committed
852
        modified, added, removed = repo.status(files=fns, match=match)[:3]
853
        files = modified + added + removed
854 855
    else:
        files = []
856
    try:
857
        repo.commit(files, message, opts['user'], opts['date'], match,
858
                    force_editor=opts.get('force_editor'))
859 860
    except ValueError, inst:
        raise util.Abort(str(inst))
861

862 863
def docopy(ui, repo, pats, opts, wlock):
    # called with the repo lock held
864 865 866
    cwd = repo.getcwd()
    errors = 0
    copied = []
867
    targets = {}
868 869

    def okaytocopy(abs, rel, exact):
870
        reasons = {'?': _('is not managed'),
871 872
                   'a': _('has been marked for add'),
                   'r': _('has been marked for remove')}
Robin Farine's avatar
Robin Farine committed
873 874
        state = repo.dirstate.state(abs)
        reason = reasons.get(state)
875
        if reason:
Robin Farine's avatar
Robin Farine committed
876 877 878 879
            if state == 'a':
                origsrc = repo.dirstate.copied(abs)
                if origsrc is not None:
                    return origsrc
880 881
            if exact:
                ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
882
        else:
Robin Farine's avatar
Robin Farine committed
883
            return abs
884

Robin Farine's avatar
Robin Farine committed
885
    def copy(origsrc, abssrc, relsrc, target, exact):
886 887
        abstarget = util.canonpath(repo.root, cwd, target)
        reltarget = util.pathto(cwd, abstarget)
888 889 890 891 892
        prevsrc = targets.get(abstarget)
        if prevsrc is not None:
            ui.warn(_('%s: not overwriting - %s collides with %s\n') %
                    (reltarget, abssrc, prevsrc))
            return
Robin Farine's avatar
Robin Farine committed
893 894 895
        if (not opts['after'] and os.path.exists(reltarget) or
            opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
            if not opts['force']:
896 897 898
                ui.warn(_('%s: not overwriting - file exists\n') %
                        reltarget)
                return
899
            if not opts['after'] and not opts.get('dry_run'):
Robin Farine's avatar
Robin Farine committed
900 901 902 903 904
                os.unlink(reltarget)
        if opts['after']:
            if not os.