commands.py 119 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 7
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

8
from node import hex, nullid, nullrev, short
9
from repo import RepoError, NoCapability
Matt Mackall's avatar
Matt Mackall committed
10
from i18n import _
11
import os, re, sys, urllib
12
import hg, util, revlog, bundlerepo, extensions, copies
13
import difflib, patch, time, help, mdiff, tempfile
Joel Rosdahl's avatar
Joel Rosdahl committed
14
import version, socket
15
import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
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 23 24
    """add the specified files on the next commit

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

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

28
    If no names are given, add all files in the repository.
29 30
    """

31 32
    rejected = None
    exacts = {}
33
    names = []
34 35
    m = cmdutil.match(repo, pats, opts)
    m.bad = lambda x,y: True
Matt Mackall's avatar
Matt Mackall committed
36
    for abs in repo.walk(m):
37
        if m.exact(abs):
38
            if ui.verbose:
39
                ui.status(_('adding %s\n') % m.rel(abs))
40
            names.append(abs)
41
            exacts[abs] = 1
42
        elif abs not in repo.dirstate:
43
            ui.status(_('adding %s\n') % m.rel(abs))
44
            names.append(abs)
45
    if not opts.get('dry_run'):
46 47 48
        rejected = repo.add(names)
        rejected = [p for p in rejected if p in exacts]
    return rejected and 1 or 0
49

50
def addremove(ui, repo, *pats, **opts):
51
    """add all new files, delete all missing files
52 53 54 55 56

    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.
57

58
    Use the -s option to detect renamed files. With a parameter > 0,
59
    this compares every removed file with every added file and records
60
    those similar enough as renames. This option takes a percentage
61
    between 0 (disabled) and 100 (files must be identical) as its
62
    parameter. Detecting renamed files this way can be expensive.
63
    """
64 65 66 67
    try:
        sim = float(opts.get('similarity') or 0)
    except ValueError:
        raise util.Abort(_('similarity must be a number'))
68 69 70
    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.)
71

72
def annotate(ui, repo, *pats, **opts):
73 74 75 76 77 78 79 80 81 82 83
    """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.
    """
84 85
    datefunc = ui.quiet and util.shortdate or util.datestr
    getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
86

87
    if not pats:
88
        raise util.Abort(_('at least one file name or pattern required'))
89

90 91 92 93 94 95 96
    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()),
            ]

97 98
    if (not opts['user'] and not opts['changeset'] and not opts['date']
        and not opts['follow']):
99
        opts['number'] = 1
100

101 102 103 104 105 106 107 108 109
    linenumber = opts.get('line_number') is not None
    if (linenumber and (not opts['changeset']) and (not opts['number'])):
        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])

110
    ctx = repo[opts['rev']]
111

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

119 120
        lines = fctx.annotate(follow=opts.get('follow'),
                              linenumber=linenumber)
121 122
        pieces = []

123 124 125 126 127
        for f in funcmap:
            l = [f(n) for n, dummy in lines]
            if l:
                m = max(map(len, l))
                pieces.append(["%*s" % (m, x) for x in l])
128

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

133 134 135 136 137 138
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.

139
    To specify the type of archive to create, use "-t". Valid
140 141 142 143 144 145 146 147 148 149 150 151 152
    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
153
    prepended. Use "-p" to specify a format string for the prefix.
154 155 156
    The default is the basename of the archive, with suffixes removed.
    '''

157
    ctx = repo[opts['rev']]
158 159 160
    if not ctx:
        raise util.Abort(_('repository has no revisions'))
    node = ctx.node()
Vadim Gelfer's avatar
Vadim Gelfer committed
161
    dest = cmdutil.make_filename(repo, dest, node)
162 163
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
164
    matchfn = cmdutil.match(repo, [], opts)
165 166 167 168 169 170 171
    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
172
    prefix = cmdutil.make_filename(repo, prefix, node)
173 174
    archival.archive(repo, dest, node, kind, not opts['no_decode'],
                     matchfn, prefix)
175

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

179
    Commit the backed out changes as a new changeset. The new
Vadim Gelfer's avatar
Vadim Gelfer committed
180
    changeset is a child of the backed out changeset.
Vadim Gelfer's avatar
Vadim Gelfer committed
181 182

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

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

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

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

200 201 202
    if not rev:
        raise util.Abort(_("please specify a revision to backout"))

203 204 205 206
    date = opts.get('date')
    if date:
        opts['date'] = util.parsedate(date)

207
    cmdutil.bail_if_changed(repo)
Vadim Gelfer's avatar
Vadim Gelfer committed
208
    node = repo.lookup(rev)
209

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

215 216
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
Vadim Gelfer's avatar
Vadim Gelfer committed
217 218
        raise util.Abort(_('cannot back out a change with no parents'))
    if p2 != nullid:
219 220 221 222 223
        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):
224
            raise util.Abort(_('%s is not a parent of %s') %
225
                             (short(p), short(node)))
226 227 228 229 230
        parent = p
    else:
        if opts['parent']:
            raise util.Abort(_('cannot use --parent on non-merge changeset'))
        parent = p1
231

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

263 264 265 266 267 268 269 270 271 272 273 274 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 306 307 308 309 310 311 312 313 314 315 316 317 318 319
def bisect(ui, repo, rev=None, extra=None,
               reset=None, good=None, bad=None, skip=None, noupdate=None):
    """subdivision search of changesets

    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 for testing. 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.
    """
    # 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
    elif extra or good + bad + skip + reset > 1:
        raise util.Abort("Incompatible arguments")

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

    # load state
    state = {'good': [], 'bad': [], 'skip': []}
    if os.path.exists(repo.join("bisect.state")):
        for l in repo.opener("bisect.state"):
            kind, node = l[:-1].split()
            node = repo.lookup(node)
            if kind not in state:
                raise util.Abort(_("unknown bisect kind %s") % kind)
            state[kind].append(node)

    # 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)

    # save state
    f = repo.opener("bisect.state", "w", atomictemp=True)
    wlock = repo.wlock()
    try:
        for kind in state:
            for node in state[kind]:
320
                f.write("%s %s\n" % (kind, hex(node)))
321 322 323 324 325 326 327 328
        f.rename()
    finally:
        del wlock

    if not state['good'] or not state['bad']:
        return

    # actually bisect
329
    node, changesets, good = hbisect.bisect(repo.changelog, state)
330
    if changesets == 0:
331
        ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
332 333 334 335 336 337 338 339 340 341
        displayer = cmdutil.show_changeset(ui, repo, {})
        displayer.show(changenode=node)
    elif node is not None:
        # 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")
342
                 % (rev, short(node), changesets, tests))
343 344 345 346
        if not noupdate:
            cmdutil.bail_if_changed(repo)
            return hg.clean(repo, node)

347
def branch(ui, repo, label=None, **opts):
348 349
    """set or show the current branch name

350 351 352
    With no argument, show the current branch name. With one argument,
    set the working directory branch name (the branch does not exist in
    the repository until the next commit).
353 354 355

    Unless --force is specified, branch will not let you set a
    branch name that shadows an existing branch.
356 357

    Use the command 'hg update' to switch to an existing branch.
358 359
    """

360
    if label:
361
        if not opts.get('force') and label in repo.branchtags():
362
            if label not in [p.branch() for p in repo.parents()]:
363 364
                raise util.Abort(_('a branch of the same name already exists'
                                   ' (use --force to override)'))
365
        repo.dirstate.setbranch(util.fromlocal(label))
366
        ui.status(_('marked working directory as branch %s\n') % label)
367
    else:
368
        ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
369

370
def branches(ui, repo, active=False):
371 372
    """list repository named branches

373
    List the repository's named branches, indicating which ones are
374
    inactive. If active is specified, only show active branches.
375

376
    A branch is considered active if it contains repository heads.
377 378

    Use the command 'hg update' to switch to an existing branch.
379
    """
380
    hexfunc = ui.debugflag and hex or short
381
    activebranches = [util.tolocal(repo[n].branch())
382
                            for n in repo.heads()]
Matt Mackall's avatar
Matt Mackall committed
383 384
    branches = util.sort([(tag in activebranches, repo.changelog.rev(node), tag)
                          for tag, node in repo.branchtags().items()])
385
    branches.reverse()
386 387 388

    for isactive, node, tag in branches:
        if (not active) or isactive:
389
            if ui.quiet:
390
                ui.write("%s\n" % tag)
391
            else:
392 393 394 395
                rev = str(node).rjust(32 - util.locallen(tag))
                isinactive = ((not isactive) and " (inactive)") or ''
                data = tag, rev, hexfunc(repo.lookup(node)), isinactive
                ui.write("%s%s:%s%s\n" % data)
396

397
def bundle(ui, repo, fname, dest=None, **opts):
398 399
    """create a changegroup file

400 401
    Generate a compressed changegroup file collecting changesets not
    found in the other repository.
402

403 404
    If no destination repository is specified the destination is
    assumed to have all the nodes specified by one or more --base
405
    parameters. To create a bundle containing all changesets, use
406 407
    --all (or --base null). To change the compression method applied,
    use the -t option (by default, bundles are compressed using bz2).
408

409 410 411 412
    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.
413

414 415
    Applying bundles preserves all changeset contents including
    permissions, copy/rename information, and revision history.
416
    """
417 418 419
    revs = opts.get('rev') or None
    if revs:
        revs = [repo.lookup(rev) for rev in revs]
420 421 422 423
    if opts.get('all'):
        base = ['null']
    else:
        base = opts.get('base')
424 425 426 427
    if base:
        if dest:
            raise util.Abort(_("--base is incompatible with specifiying "
                               "a destination"))
428 429 430
        base = [repo.lookup(rev) for rev in base]
        # create the right base
        # XXX: nodesbetween / changegroup* should be "fixed" instead
431
        o = []
Matt Mackall's avatar
Matt Mackall committed
432
        has = {nullid: None}
433
        for n in base:
Matt Mackall's avatar
Matt Mackall committed
434
            has.update(repo.changelog.reachable(n))
435
        if revs:
436
            visit = list(revs)
437
        else:
438
            visit = repo.changelog.heads()
Matt Mackall's avatar
Matt Mackall committed
439
        seen = {}
440 441
        while visit:
            n = visit.pop(0)
Matt Mackall's avatar
Matt Mackall committed
442
            parents = [p for p in repo.changelog.parents(n) if p not in has]
443 444 445
            if len(parents) == 0:
                o.insert(0, n)
            else:
446 447
                for p in parents:
                    if p not in seen:
Matt Mackall's avatar
Matt Mackall committed
448
                        seen[p] = 1
449
                        visit.append(p)
450
    else:
451
        cmdutil.setremoteconfig(ui, opts)
Alexis S. L. Carvalho's avatar
Alexis S. L. Carvalho committed
452
        dest, revs, checkout = hg.parseurl(
Matt Mackall's avatar
Matt Mackall committed
453
            ui.expandpath(dest or 'default-push', dest or 'default'), revs)
454 455 456 457 458 459 460
        other = hg.repository(ui, dest)
        o = repo.findoutgoing(other, force=opts['force'])

    if revs:
        cg = repo.changegroupsubset(o, revs, 'bundle')
    else:
        cg = repo.changegroup(o, 'bundle')
461 462 463 464 465 466 467 468

    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)
469

470
def cat(ui, repo, file1, *pats, **opts):
471
    """output the current or given revision of files
472 473

    Print the specified files as they were at the given revision.
474 475
    If no revision is given, the parent of the working directory is used,
    or tip if no revision is checked out.
476 477

    Output may be to a file, in which case the name of the file is
478
    given using a format string. The formatting rules are the same as
479 480 481 482 483 484
    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
    """
485
    ctx = repo[opts['rev']]
Matt Mackall's avatar
Matt Mackall committed
486
    err = 1
487
    m = cmdutil.match(repo, (file1,) + pats, opts)
Matt Mackall's avatar
Matt Mackall committed
488
    for abs in repo.walk(m, ctx.node()):
Vadim Gelfer's avatar
Vadim Gelfer committed
489
        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
490
        data = ctx.filectx(abs).data()
491
        if opts.get('decode'):
492 493
            data = repo.wwritedata(abs, data)
        fp.write(data)
Matt Mackall's avatar
Matt Mackall committed
494 495
        err = 0
    return err
mpm@selenic.com's avatar
mpm@selenic.com committed
496

497
def clone(ui, source, dest=None, **opts):
498 499 500 501 502 503 504 505 506 507 508
    """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
509
    and destination are on the same filesystem (note this applies only
510
    to the repository data, not to the checked out files). Some
511
    filesystems, such as AFS, implement hardlinking incorrectly, but
512
    do not report errors. In these cases, use the --pull option to
513 514
    avoid hardlinking.

515 516
    In some cases, you can clone repositories and checked out files
    using full hardlinks with
517 518 519

      $ cp -al REPO REPOCLONE

520 521 522 523 524 525
    This is the fastest way to clone, but it is not always safe.  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).  Also,
    this is not compatible with certain extensions that place their
    metadata under the .hg directory, such as mq.
526

527 528 529
    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.
530

531 532 533
    If the -U option is used, the new clone will contain only a repository
    (.hg) and no working copy (the working copy parent is the null revision).

534
    See pull for valid source format details.
535 536

    It is possible to specify an ssh:// URL as the destination, but no
537 538 539
    .hg/hgrc and working directory will be created on the remote side.
    Look at the help text for the pull command for important details
    about ssh:// URLs.
540
    """
541
    cmdutil.setremoteconfig(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
542
    hg.clone(ui, source, dest,
543
             pull=opts['pull'],
544
             stream=opts['uncompressed'],
545 546
             rev=opts['rev'],
             update=not opts['noupdate'])
mpm@selenic.com's avatar
mpm@selenic.com committed
547

548
def commit(ui, repo, *pats, **opts):
549 550 551 552 553
    """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"
554
    will be committed.
555

556 557 558
    If you are committing the result of a merge, do not provide any
    file names or -I/-X filters.

559 560
    If no commit message is specified, the configured editor is started to
    enter a message.
561 562

    See 'hg help dates' for a list of formats valid for -d/--date.
563
    """
564 565 566
    def commitfunc(ui, repo, message, match, opts):
        return repo.commit(match.files(), message, opts['user'], opts['date'],
                           match, force_editor=opts.get('force_editor'))
567

568 569 570 571 572 573 574 575 576 577 578 579
    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
        return
    if (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)):
580
        ui.status(_('created new head\n'))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
581 582

def copy(ui, repo, *pats, **opts):
583 584
    """mark files as copied for the next commit

585 586
    Mark dest as having copies of source files. If dest is a
    directory, copies are put in that directory. If dest is a file,
587 588 589
    there can only be one source.

    By default, this command copies the contents of files as they
590
    stand in the working directory. If invoked with --after, the
591 592
    operation is recorded, but no copying is performed.

593 594
    This command takes effect in the next commit. To undo a copy
    before that, see hg revert.
595
    """
Matt Mackall's avatar
Matt Mackall committed
596
    wlock = repo.wlock(False)
597
    try:
Matt Mackall's avatar
Matt Mackall committed
598
        return cmdutil.copy(ui, repo, pats, opts)
599 600
    finally:
        del wlock
mpm@selenic.com's avatar
mpm@selenic.com committed
601

602
def debugancestor(ui, repo, *args):
mpm@selenic.com's avatar
mpm@selenic.com committed
603
    """find the ancestor revision of two revisions in a given index"""
604 605
    if len(args) == 3:
        index, rev1, rev2 = args
606
        r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
607
        lookup = r.lookup
608
    elif len(args) == 2:
609 610 611
        if not repo:
            raise util.Abort(_("There is no Mercurial repository here "
                               "(.hg not found)"))
612
        rev1, rev2 = args
613
        r = repo.changelog
614
        lookup = repo.lookup
615 616
    else:
        raise util.Abort(_('either two or three arguments required'))
617
    a = r.ancestor(lookup(rev1), lookup(rev2))
mpm@selenic.com's avatar
mpm@selenic.com committed
618 619
    ui.write("%d:%s\n" % (r.rev(a), hex(a)))

620
def debugcomplete(ui, cmd='', **opts):
Benoit Boissinot's avatar
Benoit Boissinot committed
621
    """returns the completion list associated with the given command"""
622 623 624 625 626

    if opts['options']:
        options = []
        otables = [globalopts]
        if cmd:
627
            aliases, entry = cmdutil.findcmd(ui, cmd, table)
628 629 630 631 632 633 634 635 636
            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

Matt Mackall's avatar
Matt Mackall committed
637
    ui.write("%s\n" % "\n".join(util.sort(cmdutil.findpossible(ui, cmd, table))))
Benoit Boissinot's avatar
Benoit Boissinot committed
638

639 640 641 642
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'))
643
    ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
644 645 646
                                and 'yes' or 'no'))
    os.unlink('.debugfsinfo')

647
def debugrebuildstate(ui, repo, rev="tip"):
648
    """rebuild the dirstate as it would look like for the given revision"""
649
    ctx = repo[rev]
650
    wlock = repo.wlock()
651
    try:
652
        repo.dirstate.rebuild(ctx.node(), ctx.manifest())
653 654
    finally:
        del wlock
655

656 657
def debugcheckstate(ui, repo):
    """validate the correctness of the current dirstate"""
658
    parent1, parent2 = repo.dirstate.parents()
659 660
    m1 = repo[parent1].manifest()
    m2 = repo[parent2].manifest()
661
    errors = 0
662 663
    for f in repo.dirstate:
        state = repo.dirstate[f]
664
        if state in "nr" and f not in m1:
665
            ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
666 667
            errors += 1
        if state in "a" and f in m1:
668
            ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
669 670
            errors += 1
        if state in "m" and f not in m1 and f not in m2:
671
            ui.warn(_("%s in state %s, but not in either manifest\n") %
672
                    (f, state))
673 674
            errors += 1
    for f in m1:
675
        state = repo.dirstate[f]
676
        if state not in "nrm":
677
            ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
678 679
            errors += 1
    if errors:
680 681
        error = _(".hg/dirstate inconsistent with current parent's manifest")
        raise util.Abort(error)
682

683
def showconfig(ui, repo, *values, **opts):
684 685 686 687 688 689 690 691 692 693
    """show combined config settings from all hgrc files

    With no args, print names and values of all config items.

    With one arg of the form section.name, print just the value of
    that config item.

    With multiple args, print names and values of all config items
    with matching section names."""

694
    untrusted = bool(opts.get('untrusted'))
695 696 697
    if values:
        if len([v for v in values if '.' in v]) > 1:
            raise util.Abort(_('only one config item permitted'))
698
    for section, name, value in ui.walkconfig(untrusted=untrusted):
699 700 701 702 703 704 705 706 707
        sectname = section + '.' + name
        if values:
            for v in values:
                if v == section:
                    ui.write('%s=%s\n' % (sectname, value))
                elif v == sectname:
                    ui.write(value, '\n')
        else:
            ui.write('%s=%s\n' % (sectname, value))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
708

Matt Mackall's avatar
Matt Mackall committed
709
def debugsetparents(ui, repo, rev1, rev2=None):
TK Soh's avatar
TK Soh committed
710
    """manually set the parents of the current working directory
Matt Mackall's avatar
Matt Mackall committed
711 712 713 714 715 716 717 718

    This is useful for writing repository conversion tools, but should
    be used with care.
    """

    if not rev2:
        rev2 = hex(nullid)

719 720 721 722
    wlock = repo.wlock()
    try:
        repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
    finally:
723
        del wlock
Matt Mackall's avatar
Matt Mackall committed
724

725
def debugstate(ui, repo, nodates=None):
726
    """show the contents of the current dirstate"""
727 728
    timestr = ""
    showdate = not nodates
Matt Mackall's avatar
Matt Mackall committed
729
    for file_, ent in util.sort(repo.dirstate._map.items()):
730 731 732 733 734 735 736 737
        if showdate:
            if ent[3] == -1:
                # Pad or slice to locale representation
                locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
                timestr = 'unset'
                timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
            else:
                timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
738 739 740 741
        if ent[1] & 020000:
            mode = 'lnk'
        else:
            mode = '%3o' % (ent[1] & 0777)
742
        ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
743 744
    for f in repo.dirstate.copies():
        ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
745

746
def debugdata(ui, file_, rev):
747 748
    """dump the contents of a data file revision"""
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
749 750 751
    try:
        ui.write(r.revision(r.lookup(rev)))
    except KeyError:
752
        raise util.Abort(_('invalid revision identifier %s') % rev)
753

Matt Mackall's avatar
Matt Mackall committed
754
def debugdate(ui, date, range=None, **opts):
Matt Mackall's avatar
Matt Mackall committed
755
    """parse and display a date"""
Matt Mackall's avatar
Matt Mackall committed
756 757 758 759
    if opts["extended"]:
        d = util.parsedate(date, util.extendeddateformats)
    else:
        d = util.parsedate(date)
Matt Mackall's avatar
Matt Mackall committed
760 761
    ui.write("internal: %s %s\n" % d)
    ui.write("standard: %s\n" % util.datestr(d))
Matt Mackall's avatar
Matt Mackall committed
762 763 764
    if range:
        m = util.matchdate(range)
        ui.write("match: %s\n" % m(d[0]))
Matt Mackall's avatar
Matt Mackall committed
765

766
def debugindex(ui, file_):
767
    """dump the contents of an index file"""
768
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
769
    ui.write("   rev    offset  length   base linkrev" +
mpm@selenic.com's avatar
mpm@selenic.com committed
770
             " nodeid       p1           p2\n")
771
    for i in r:
mason@suse.com's avatar
mason@suse.com committed
772
        node = r.node(i)
773 774 775 776
        try:
            pp = r.parents(node)
        except:
            pp = [nullid, nullid]
mpm@selenic.com's avatar
mpm@selenic.com committed
777
        ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
mason@suse.com's avatar
mason@suse.com committed
778 779
                i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
            short(node), short(pp[0]), short(pp[1])))
mpm@selenic.com's avatar
mpm@selenic.com committed
780

781
def debugindexdot(ui, file_):
782
    """dump an index DAG as a .dot file"""
783
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
784
    ui.write("digraph G {\n")
785
    for i in r:
786 787 788 789 790
        node = r.node(i)
        pp = r.parents(node)
        ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
        if pp[1] != nullid:
            ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
mpm@selenic.com's avatar