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

8
from node import hex, nullid, nullrev, short
9
from lock import release
10
from i18n import _, gettext
11
import os, re, sys, textwrap, subprocess, difflib, time
Matt Mackall's avatar
Matt Mackall committed
12
import hg, util, revlog, bundlerepo, extensions, copies, context, error
13 14 15
import patch, help, mdiff, tempfile, url, encoding
import archival, changegroup, cmdutil, sshserver, hbisect
from hgweb import server
Matt Mackall's avatar
Matt Mackall committed
16
import merge as merge_
17

mpm@selenic.com's avatar
mpm@selenic.com committed
18
# Commands start here, listed alphabetically
19

20
def add(ui, repo, *pats, **opts):
21 22
    """add the specified files on the next commit

23 24
    Schedule files to be version controlled and added to the
    repository.
25

26 27
    The files will be added to the repository at the next commit. To
    undo an add before that, see hg revert.
28

29
    If no names are given, add all files to the repository.
30 31
    """

32
    bad = []
33
    exacts = {}
34
    names = []
35
    m = cmdutil.match(repo, pats, opts)
36 37 38 39 40 41 42 43 44
    oldbad = m.bad
    m.bad = lambda x,y: bad.append(x) or oldbad(x,y)

    for f in repo.walk(m):
        exact = m.exact(f)
        if exact or f not in repo.dirstate:
            names.append(f)
            if ui.verbose or not exact:
                ui.status(_('adding %s\n') % m.rel(f))
45
    if not opts.get('dry_run'):
46 47
        bad += [f for f in repo.add(names) if f in m.files()]
    return bad and 1 or 0
48

49
def addremove(ui, repo, *pats, **opts):
50
    """add all new files, delete all missing files
51

52 53
    Add all new files and remove all missing files from the
    repository.
54

55 56 57
    New files are ignored if they match any of the patterns in
    .hgignore. As with add, these changes take effect at the next
    commit.
58

59 60 61 62 63 64
    Use the -s/--similarity 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.
65
    """
66 67 68 69
    try:
        sim = float(opts.get('similarity') or 0)
    except ValueError:
        raise util.Abort(_('similarity must be a number'))
70 71 72
    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.)
73

74
def annotate(ui, repo, *pats, **opts):
75 76
    """show changeset information per file line

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

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

83 84 85
    Without the -a/--text option, annotate will avoid processing files
    it detects as binary. With -a, annotate will generate an
    annotation anyway, probably with undesirable results.
86
    """
87 88
    datefunc = ui.quiet and util.shortdate or util.datestr
    getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
89

90
    if not pats:
91
        raise util.Abort(_('at least one filename or pattern is required'))
92

93 94 95 96 97 98 99
    opmap = [('user', lambda x: ui.shortuser(x[0].user())),
             ('number', lambda x: str(x[0].rev())),
             ('changeset', lambda x: short(x[0].node())),
             ('date', getdate),
             ('follow', lambda x: x[0].path()),
            ]

100 101
    if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
        and not opts.get('follow')):
102
        opts['number'] = 1
103

104
    linenumber = opts.get('line_number') is not None
105
    if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
106 107 108 109 110 111 112
        raise util.Abort(_('at least one of -n/-c is required for -l'))

    funcmap = [func for op, func in opmap if opts.get(op)]
    if linenumber:
        lastfunc = funcmap[-1]
        funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])

113
    ctx = repo[opts.get('rev')]
114

115
    m = cmdutil.match(repo, pats, opts)
Matt Mackall's avatar
Matt Mackall committed
116 117
    for abs in ctx.walk(m):
        fctx = ctx[abs]
118
        if not opts.get('text') and util.binary(fctx.data()):
119
            ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
120 121
            continue

122 123
        lines = fctx.annotate(follow=opts.get('follow'),
                              linenumber=linenumber)
124 125
        pieces = []

126 127 128
        for f in funcmap:
            l = [f(n) for n, dummy in lines]
            if l:
129 130
                ml = max(map(len, l))
                pieces.append(["%*s" % (ml, x) for x in l])
131

mpm@selenic.com's avatar
mpm@selenic.com committed
132 133 134
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
135

136 137 138 139
def archive(ui, repo, dest, **opts):
    '''create unversioned archive of a repository revision

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

142 143
    To specify the type of archive to create, use -t/--type. Valid
    types are:
144 145 146 147 148 149 150 151 152

    "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
153
    using a format string; see 'hg help export' for details.
154 155

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

161
    ctx = repo[opts.get('rev')]
162
    if not ctx:
163
        raise util.Abort(_('no working directory: please specify a revision'))
164
    node = ctx.node()
Vadim Gelfer's avatar
Vadim Gelfer committed
165
    dest = cmdutil.make_filename(repo, dest, node)
166 167
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
168
    matchfn = cmdutil.match(repo, [], opts)
169
    kind = opts.get('type') or 'files'
170
    prefix = opts.get('prefix')
171 172 173 174 175
    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
176
    prefix = cmdutil.make_filename(repo, prefix, node)
177
    archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
178
                     matchfn, prefix)
179

Daniel Holth's avatar
Daniel Holth committed
180
def backout(ui, repo, node=None, rev=None, **opts):
Vadim Gelfer's avatar
Vadim Gelfer committed
181 182
    '''reverse effect of earlier changeset

183
    Commit the backed out changes as a new changeset. The new
Vadim Gelfer's avatar
Vadim Gelfer committed
184
    changeset is a child of the backed out changeset.
Vadim Gelfer's avatar
Vadim Gelfer committed
185 186

    If you back out a changeset other than the tip, a new head is
187
    created. This head will be the new tip and you should merge this
188
    backout changeset with another head (current one by default).
Vadim Gelfer's avatar
Vadim Gelfer committed
189 190 191

    The --merge option remembers the parent of the working directory
    before starting the backout, then merges the new head with that
192 193
    changeset afterwards. This saves you from doing the merge by hand.
    The result of this merge is not committed, as with a normal merge.
194

195
    See 'hg help dates' for a list of formats valid for -d/--date.
196
    '''
Daniel Holth's avatar
Daniel Holth committed
197 198 199 200 201
    if rev and node:
        raise util.Abort(_("please specify just one revision"))

    if not rev:
        rev = node
Vadim Gelfer's avatar
Vadim Gelfer committed
202

203 204 205
    if not rev:
        raise util.Abort(_("please specify a revision to backout"))

206 207 208 209
    date = opts.get('date')
    if date:
        opts['date'] = util.parsedate(date)

210
    cmdutil.bail_if_changed(repo)
Vadim Gelfer's avatar
Vadim Gelfer committed
211
    node = repo.lookup(rev)
212

213
    op1, op2 = repo.dirstate.parents()
214 215 216 217
    a = repo.changelog.ancestor(op1, node)
    if a != node:
        raise util.Abort(_('cannot back out change on a different branch'))

218 219
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
Vadim Gelfer's avatar
Vadim Gelfer committed
220 221
        raise util.Abort(_('cannot back out a change with no parents'))
    if p2 != nullid:
222
        if not opts.get('parent'):
223 224 225 226
            raise util.Abort(_('cannot back out a merge changeset without '
                               '--parent'))
        p = repo.lookup(opts['parent'])
        if p not in (p1, p2):
227
            raise util.Abort(_('%s is not a parent of %s') %
228
                             (short(p), short(node)))
229 230
        parent = p
    else:
231
        if opts.get('parent'):
232 233
            raise util.Abort(_('cannot use --parent on non-merge changeset'))
        parent = p1
234

235 236
    # the backout should appear on the same branch
    branch = repo.dirstate.branch()
237
    hg.clean(repo, node, show_stats=False)
238
    repo.dirstate.setbranch(branch)
Vadim Gelfer's avatar
Vadim Gelfer committed
239
    revert_opts = opts.copy()
240
    revert_opts['date'] = None
241
    revert_opts['all'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
242
    revert_opts['rev'] = hex(parent)
243
    revert_opts['no_backup'] = None
Vadim Gelfer's avatar
Vadim Gelfer committed
244 245 246
    revert(ui, repo, **revert_opts)
    commit_opts = opts.copy()
    commit_opts['addremove'] = False
247
    if not commit_opts['message'] and not commit_opts['logfile']:
248
        commit_opts['message'] = _("Backed out changeset %s") % (short(node))
249
        commit_opts['force_editor'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
250 251 252 253 254
    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)))
255
    if op1 != node:
256
        hg.clean(repo, op1, show_stats=False)
257
        if opts.get('merge'):
258 259
            ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
            hg.merge(repo, hex(repo.changelog.tip()))
260 261 262
        else:
            ui.status(_('the backout changeset is a new head - '
                        'do not forget to merge\n'))
263 264
            ui.status(_('(use "backout --merge" '
                        'if you want to auto-merge)\n'))
Vadim Gelfer's avatar
Vadim Gelfer committed
265

266
def bisect(ui, repo, rev=None, extra=None, command=None,
267 268 269
               reset=None, good=None, bad=None, skip=None, noupdate=None):
    """subdivision search of changesets

270 271 272 273
    This command helps to find changesets which introduce problems. To
    use, mark the earliest changeset you know exhibits the problem as
    bad, then mark the latest changeset which is free from the problem
    as good. Bisect will update your working directory to a revision
274 275 276 277
    for testing (unless the -U/--noupdate option is specified). Once
    you have performed tests, mark the working directory as bad or
    good and bisect will either update to another candidate changeset
    or announce that it has found the bad revision.
278

279 280
    As a shortcut, you can also use the revision argument to mark a
    revision as good or bad without checking it out first.
281

282 283 284 285 286
    If you supply a command it will be used for automatic bisection.
    Its exit status will be used as flag to mark revision as bad or
    good. In case exit status is 0 the revision is marked as good, 125
    - skipped, 127 (command not found) - bisection will be aborted;
    any other status bigger than 0 will mark revision as bad.
287
    """
288 289 290 291
    def print_result(nodes, good):
        displayer = cmdutil.show_changeset(ui, repo, {})
        if len(nodes) == 1:
            # narrowed it down to a single revision
292 293 294 295
            if good:
                ui.write(_("The first good revision is:\n"))
            else:
                ui.write(_("The first bad revision is:\n"))
296
            displayer.show(repo[nodes[0]])
297 298
        else:
            # multiple possible revisions
299 300 301 302 303 304
            if good:
                ui.write(_("Due to skipped revisions, the first "
                        "good revision could be any of:\n"))
            else:
                ui.write(_("Due to skipped revisions, the first "
                        "bad revision could be any of:\n"))
305
            for n in nodes:
306
                displayer.show(repo[n])
307 308 309 310 311 312 313 314 315 316 317

    def check_state(state, interactive=True):
        if not state['good'] or not state['bad']:
            if (good or bad or skip or reset) and interactive:
                return
            if not state['good']:
                raise util.Abort(_('cannot bisect (no known good revisions)'))
            else:
                raise util.Abort(_('cannot bisect (no known bad revisions)'))
        return True

318 319 320 321 322 323 324 325 326 327
    # backward compatibility
    if rev in "good bad reset init".split():
        ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
        cmd, rev, extra = rev, extra, None
        if cmd == "good":
            good = True
        elif cmd == "bad":
            bad = True
        else:
            reset = True
328
    elif extra or good + bad + skip + reset + bool(command) > 1:
329
        raise util.Abort(_('incompatible arguments'))
330 331 332 333 334 335 336

    if reset:
        p = repo.join("bisect.state")
        if os.path.exists(p):
            os.unlink(p)
        return

337 338 339
    state = hbisect.load_state(repo)

    if command:
340
        commandpath = util.find_exe(command)
341
        changesets = 1
342 343 344
        try:
            while changesets:
                # update state
345
                status = subprocess.call([commandpath])
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
                if status == 125:
                    transition = "skip"
                elif status == 0:
                    transition = "good"
                # status < 0 means process was killed
                elif status == 127:
                    raise util.Abort(_("failed to execute %s") % command)
                elif status < 0:
                    raise util.Abort(_("%s killed") % command)
                else:
                    transition = "bad"
                node = repo.lookup(rev or '.')
                state[transition].append(node)
                ui.note(_('Changeset %s: %s\n') % (short(node), transition))
                check_state(state, interactive=False)
                # bisect
                nodes, changesets, good = hbisect.bisect(repo.changelog, state)
                # update to next check
                cmdutil.bail_if_changed(repo)
                hg.clean(repo, nodes[0], show_stats=False)
        finally:
            hbisect.save_state(repo, state)
368
        return print_result(nodes, not status)
369 370 371 372 373 374 375 376 377 378

    # update state
    node = repo.lookup(rev or '.')
    if good:
        state['good'].append(node)
    elif bad:
        state['bad'].append(node)
    elif skip:
        state['skip'].append(node)

379
    hbisect.save_state(repo, state)
380

381 382
    if not check_state(state):
        return
383 384

    # actually bisect
385
    nodes, changesets, good = hbisect.bisect(repo.changelog, state)
386
    if changesets == 0:
387
        print_result(nodes, good)
388 389 390
    else:
        assert len(nodes) == 1 # only a single node can be tested next
        node = nodes[0]
391 392 393 394 395 396 397
        # compute the approximate number of remaining tests
        tests, size = 0, 2
        while size <= changesets:
            tests, size = tests + 1, size * 2
        rev = repo.changelog.rev(node)
        ui.write(_("Testing changeset %s:%s "
                   "(%s changesets remaining, ~%s tests)\n")
398
                 % (rev, short(node), changesets, tests))
399 400 401 402
        if not noupdate:
            cmdutil.bail_if_changed(repo)
            return hg.clean(repo, node)

403
def branch(ui, repo, label=None, **opts):
404 405
    """set or show the current branch name

406
    With no argument, show the current branch name. With one argument,
407 408 409
    set the working directory branch name (the branch does not exist
    in the repository until the next commit). It is recommended to use
    the 'default' branch as your primary development branch.
410

411 412
    Unless -f/--force is specified, branch will not let you set a
    branch name that shadows an existing branch.
413

414 415
    Use -C/--clean to reset the working directory branch to that of
    the parent of the working directory, negating a previous branch
416
    change.
417

418
    Use the command 'hg update' to switch to an existing branch.
419 420
    """

421 422 423 424 425
    if opts.get('clean'):
        label = repo[None].parents()[0].branch()
        repo.dirstate.setbranch(label)
        ui.status(_('reset working directory to branch %s\n') % label)
    elif label:
426
        if not opts.get('force') and label in repo.branchtags():
427
            if label not in [p.branch() for p in repo.parents()]:
428 429
                raise util.Abort(_('a branch of the same name already exists'
                                   ' (use --force to override)'))
430
        repo.dirstate.setbranch(encoding.fromlocal(label))
431
        ui.status(_('marked working directory as branch %s\n') % label)
432
    else:
433
        ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
434

435
def branches(ui, repo, active=False):
436 437
    """list repository named branches

438
    List the repository's named branches, indicating which ones are
439
    inactive. If active is specified, only show active branches.
440

441
    A branch is considered active if it contains repository heads.
442 443

    Use the command 'hg update' to switch to an existing branch.
444
    """
445
    hexfunc = ui.debugflag and hex or short
446
    activebranches = [encoding.tolocal(repo[n].branch())
447
                            for n in repo.heads(closed=False)]
448
    branches = sorted([(tag in activebranches, repo.changelog.rev(node), tag)
449 450
                          for tag, node in repo.branchtags().items()],
                      reverse=True)
451 452 453

    for isactive, node, tag in branches:
        if (not active) or isactive:
454
            if ui.quiet:
455
                ui.write("%s\n" % tag)
456
            else:
457 458 459 460 461 462 463
                hn = repo.lookup(node)
                if isactive:
                    notice = ''
                elif hn not in repo.branchheads(tag, closed=False):
                    notice = ' (closed)'
                else:
                    notice = ' (inactive)'
464
                rev = str(node).rjust(31 - encoding.colwidth(tag))
465
                data = tag, rev, hexfunc(hn), notice
466
                ui.write("%s %s:%s%s\n" % data)
467

468
def bundle(ui, repo, fname, dest=None, **opts):
469 470
    """create a changegroup file

471
    Generate a compressed changegroup file collecting changesets not
472
    known to be in another repository.
473

474 475
    If no destination repository is specified the destination is
    assumed to have all the nodes specified by one or more --base
476
    parameters. To create a bundle containing all changesets, use
477 478 479
    -a/--all (or --base null). To change the compression method
    applied, use the -t/--type option (by default, bundles are
    compressed using bz2).
480

481 482 483 484
    The bundle file can then be transferred using conventional means
    and applied to another repository with the unbundle or pull
    command. This is useful when direct push and pull are not
    available or when exporting an entire repository is undesirable.
485

486 487
    Applying bundles preserves all changeset contents including
    permissions, copy/rename information, and revision history.
488
    """
489 490 491
    revs = opts.get('rev') or None
    if revs:
        revs = [repo.lookup(rev) for rev in revs]
492 493 494 495
    if opts.get('all'):
        base = ['null']
    else:
        base = opts.get('base')
496 497
    if base:
        if dest:
498
            raise util.Abort(_("--base is incompatible with specifying "
499
                               "a destination"))
500 501 502
        base = [repo.lookup(rev) for rev in base]
        # create the right base
        # XXX: nodesbetween / changegroup* should be "fixed" instead
503
        o = []
504
        has = set((nullid,))
505
        for n in base:
Matt Mackall's avatar
Matt Mackall committed
506
            has.update(repo.changelog.reachable(n))
507
        if revs:
508
            visit = list(revs)
509
        else:
510
            visit = repo.changelog.heads()
Matt Mackall's avatar
Matt Mackall committed
511
        seen = {}
512 513
        while visit:
            n = visit.pop(0)
Matt Mackall's avatar
Matt Mackall committed
514
            parents = [p for p in repo.changelog.parents(n) if p not in has]
515 516 517
            if len(parents) == 0:
                o.insert(0, n)
            else:
518 519
                for p in parents:
                    if p not in seen:
Matt Mackall's avatar
Matt Mackall committed
520
                        seen[p] = 1
521
                        visit.append(p)
522
    else:
Alexis S. L. Carvalho's avatar
Alexis S. L. Carvalho committed
523
        dest, revs, checkout = hg.parseurl(
Matt Mackall's avatar
Matt Mackall committed
524
            ui.expandpath(dest or 'default-push', dest or 'default'), revs)
Matt Mackall's avatar
Matt Mackall committed
525
        other = hg.repository(cmdutil.remoteui(repo, opts), dest)
526
        o = repo.findoutgoing(other, force=opts.get('force'))
527 528 529 530 531

    if revs:
        cg = repo.changegroupsubset(o, revs, 'bundle')
    else:
        cg = repo.changegroup(o, 'bundle')
532 533 534 535 536 537 538 539

    bundletype = opts.get('type', 'bzip2').lower()
    btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
    bundletype = btypes.get(bundletype)
    if bundletype not in changegroup.bundletypes:
        raise util.Abort(_('unknown bundle type specified with --type'))

    changegroup.writebundle(cg, fname, bundletype)
540

541
def cat(ui, repo, file1, *pats, **opts):
542
    """output the current or given revision of files
543

544 545
    Print the specified files as they were at the given revision. If
    no revision is given, the parent of the working directory is used,
546
    or tip if no revision is checked out.
547 548

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

    %s   basename of file being printed
553
    %d   dirname of file being printed, or '.' if in repository root
554 555
    %p   root-relative path name of file being printed
    """
556
    ctx = repo[opts.get('rev')]
Matt Mackall's avatar
Matt Mackall committed
557
    err = 1
558
    m = cmdutil.match(repo, (file1,) + pats, opts)
Matt Mackall's avatar
Matt Mackall committed
559
    for abs in ctx.walk(m):
560
        fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
Matt Mackall's avatar
Matt Mackall committed
561
        data = ctx[abs].data()
562
        if opts.get('decode'):
563 564
            data = repo.wwritedata(abs, data)
        fp.write(data)
Matt Mackall's avatar
Matt Mackall committed
565 566
        err = 0
    return err
mpm@selenic.com's avatar
mpm@selenic.com committed
567

568
def clone(ui, source, dest=None, **opts):
569 570 571 572 573 574 575 576 577 578
    """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.

579 580 581 582
    If you use the -r/--rev option to clone up to a specific revision,
    no subsequent revisions (including subsequent tags) will be
    present in the cloned repository. This option implies --pull, even
    on local repositories.
583 584

    By default, clone will check out the head of the 'default' branch.
585 586 587
    If the -U/--noupdate option is used, the new clone will contain
    only a repository (.hg) and no working copy (the working copy
    parent is the null revision).
588 589 590 591 592

    See 'hg help urls' for valid source format details.

    It is possible to specify an ssh:// URL as the destination, but no
    .hg/hgrc and working directory will be created on the remote side.
593 594
    Look at the help text for URLs for important details about ssh://
    URLs.
595

596
    For efficiency, hardlinks are used for cloning whenever the source
597
    and destination are on the same filesystem (note this applies only
598
    to the repository data, not to the checked out files). Some
599
    filesystems, such as AFS, implement hardlinking incorrectly, but
600
    do not report errors. In these cases, use the --pull option to
601 602
    avoid hardlinking.

603 604
    In some cases, you can clone repositories and checked out files
    using full hardlinks with
605 606 607

      $ cp -al REPO REPOCLONE

608
    This is the fastest way to clone, but it is not always safe. The
609 610
    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
611
    breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
612 613
    this is not compatible with certain extensions that place their
    metadata under the .hg directory, such as mq.
614

615
    """
Matt Mackall's avatar
Matt Mackall committed
616
    hg.clone(cmdutil.remoteui(ui, opts), source, dest,
617 618 619 620
             pull=opts.get('pull'),
             stream=opts.get('uncompressed'),
             rev=opts.get('rev'),
             update=not opts.get('noupdate'))
mpm@selenic.com's avatar
mpm@selenic.com committed
621

622
def commit(ui, repo, *pats, **opts):
623 624
    """commit the specified files or all outstanding changes

625 626
    Commit changes to the given files into the repository. Unlike a
    centralized RCS, this operation is a local operation. See hg push
627
    for means to actively distribute your changes.
628 629

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

632
    If you are committing the result of a merge, do not provide any
633
    filenames or -I/-X filters.
634

635 636
    If no commit message is specified, the configured editor is
    started to prompt you for a message.
637 638

    See 'hg help dates' for a list of formats valid for -d/--date.
639
    """
640 641 642
    extra = {}
    if opts.get('close_branch'):
        extra['close'] = 1
643 644 645 646
    e = cmdutil.commiteditor
    if opts.get('force_editor'):
        e = cmdutil.commitforceeditor

647
    def commitfunc(ui, repo, message, match, opts):
648 649
        return repo.commit(message, opts.get('user'), opts.get('date'), match,
                           editor=e, extra=extra)
650

651 652 653 654 655 656 657 658
    node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
    if not node:
        return
    cl = repo.changelog
    rev = cl.rev(node)
    parents = cl.parentrevs(rev)
    if rev - 1 in parents:
        # one of the parents was the old tip
659 660 661 662
        pass
    elif (parents == (nullrev, nullrev) or
          len(cl.heads(cl.node(parents[0]))) > 1 and
          (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
663
        ui.status(_('created new head\n'))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
664

665 666 667 668 669
    if ui.debugflag:
        ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
    elif ui.verbose:
        ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
670
def copy(ui, repo, *pats, **opts):
671 672
    """mark files as copied for the next commit

673 674
    Mark dest as having copies of source files. If dest is a
    directory, copies are put in that directory. If dest is a file,
675
    the source must be a single file.
676 677

    By default, this command copies the contents of files as they
678
    stand in the working directory. If invoked with -A/--after, the
679 680
    operation is recorded, but no copying is performed.

681
    This command takes effect with the next commit. To undo a copy
682
    before that, see hg revert.
683
    """
Matt Mackall's avatar
Matt Mackall committed
684
    wlock = repo.wlock(False)
685
    try:
Matt Mackall's avatar
Matt Mackall committed
686
        return cmdutil.copy(ui, repo, pats, opts)
687
    finally:
688
        wlock.release()
mpm@selenic.com's avatar
mpm@selenic.com committed
689

690
def debugancestor(ui, repo, *args):
mpm@selenic.com's avatar
mpm@selenic.com committed
691
    """find the ancestor revision of two revisions in a given index"""
692 693
    if len(args) == 3:
        index, rev1, rev2 = args
694
        r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
695
        lookup = r.lookup
696
    elif len(args) == 2:
697 698 699
        if not repo:
            raise util.Abort(_("There is no Mercurial repository here "
                               "(.hg not found)"))
700
        rev1, rev2 = args
701
        r = repo.changelog
702
        lookup = repo.lookup
703 704
    else:
        raise util.Abort(_('either two or three arguments required'))
705
    a = r.ancestor(lookup(rev1), lookup(rev2))
mpm@selenic.com's avatar
mpm@selenic.com committed
706 707
    ui.write("%d:%s\n" % (r.rev(a), hex(a)))

708
def debugcommands(ui, cmd='', *args):
709
    for cmd, vals in sorted(table.iteritems()):
710 711 712 713
        cmd = cmd.split('|')[0].strip('^')
        opts = ', '.join([i[1] for i in vals[1]])
        ui.write('%s: %s\n' % (cmd, opts))

714
def debugcomplete(ui, cmd='', **opts):
Benoit Boissinot's avatar
Benoit Boissinot committed
715
    """returns the completion list associated with the given command"""
716

717
    if opts.get('options'):
718 719 720
        options = []
        otables = [globalopts]
        if cmd:
721
            aliases, entry = cmdutil.findcmd(cmd, table, False)
722 723 724 725 726 727 728 729 730
            otables.append(entry[1])
        for t in otables:
            for o in t:
                if o[0]:
                    options.append('-%s' % o[0])
                options.append('--%s' % o[1])
        ui.write("%s\n" % "\n".join(options))
        return

731 732 733
    cmdlist = cmdutil.findpossible(cmd, table)
    if ui.verbose:
        cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
734
    ui.write("%s\n" % "\n".join(sorted(cmdlist)))
Benoit Boissinot's avatar
Benoit Boissinot committed
735

736 737 738 739
def debugfsinfo(ui, path = "."):
    file('.debugfsinfo', 'w').write('')
    ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
    ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
740
    ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
741 742 743
                                and 'yes' or 'no'))
    os.unlink('.debugfsinfo')

744
def debugrebuildstate(ui, repo, rev="tip"):
745
    """rebuild the dirstate as it would look like for the given revision"""
746
    ctx = repo[rev]
747
    wlock = repo.wlock()
748
    try:
749
        repo.dirstate.rebuild(ctx.node(), ctx.manifest())
750
    finally:
751
        wlock.release()