commands.py 111 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
import demandimport; demandimport.enable()
9
from node import *
Matt Mackall's avatar
Matt Mackall committed
10
from i18n import _
11 12
import bisect, os, re, sys, urllib, shlex, stat
import ui, hg, util, revlog, bundlerepo, extensions
13
import difflib, patch, time, help, mdiff, tempfile
14
import errno, version, socket
15
import archival, changegroup, cmdutil, hgweb.server, sshserver
16

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

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

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

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

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

30
    names = []
31
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
32
        if exact:
33 34
            if ui.verbose:
                ui.status(_('adding %s\n') % rel)
35 36
            names.append(abs)
        elif repo.dirstate.state(abs) == '?':
37
            ui.status(_('adding %s\n') % rel)
38
            names.append(abs)
39
    if not opts.get('dry_run'):
Vadim Gelfer's avatar
Vadim Gelfer committed
40
        repo.add(names)
41

42
def addremove(ui, repo, *pats, **opts):
43
    """add all new files, delete all missing files
44 45 46 47 48

    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.
49 50 51 52 53 54

    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.
55
    """
56 57 58 59
    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.)
60

61
def annotate(ui, repo, *pats, **opts):
62 63 64 65 66 67 68 69 70 71 72
    """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.
    """
73
    getdate = util.cachefunc(lambda x: util.datestr(x.date()))
74

75
    if not pats:
76
        raise util.Abort(_('at least one file name or pattern required'))
77

78 79 80
    opmap = [['user', lambda x: ui.shortuser(x.user())],
             ['number', lambda x: str(x.rev())],
             ['changeset', lambda x: short(x.node())],
81 82 83
             ['date', getdate], ['follow', lambda x: x.path()]]
    if (not opts['user'] and not opts['changeset'] and not opts['date']
        and not opts['follow']):
84
        opts['number'] = 1
85

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

88 89
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
                                             node=ctx.node()):
90 91
        fctx = ctx.filectx(abs)
        if not opts['text'] and util.binary(fctx.data()):
92
            ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
93 94
            continue

95
        lines = fctx.annotate(follow=opts.get('follow'))
96 97 98
        pieces = []

        for o, f in opmap:
99
            if opts[o]:
100
                l = [f(n) for n, dummy in lines]
mpm@selenic.com's avatar
mpm@selenic.com committed
101 102 103
                if l:
                    m = max(map(len, l))
                    pieces.append(["%*s" % (m, x) for x in l])
104

mpm@selenic.com's avatar
mpm@selenic.com committed
105 106 107
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
108

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
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
133
    node = repo.changectx(opts['rev']).node()
Vadim Gelfer's avatar
Vadim Gelfer committed
134
    dest = cmdutil.make_filename(repo, dest, node)
135 136
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
137
    dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
138 139 140 141 142 143 144
    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
145
    prefix = cmdutil.make_filename(repo, prefix, node)
146 147
    archival.archive(repo, dest, node, kind, not opts['no_decode'],
                     matchfn, prefix)
148

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

Vadim Gelfer's avatar
Vadim Gelfer committed
152 153
    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
154 155

    If you back out a changeset other than the tip, a new head is
Vadim Gelfer's avatar
Vadim Gelfer committed
156 157 158 159 160 161 162 163 164 165
    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.'''
Daniel Holth's avatar
Daniel Holth committed
166 167 168 169 170
    if rev and node:
        raise util.Abort(_("please specify just one revision"))

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

172
    cmdutil.bail_if_changed(repo)
Vadim Gelfer's avatar
Vadim Gelfer committed
173 174 175 176
    op1, op2 = repo.dirstate.parents()
    if op2 != nullid:
        raise util.Abort(_('outstanding uncommitted merge'))
    node = repo.lookup(rev)
177 178
    p1, p2 = repo.changelog.parents(node)
    if p1 == nullid:
Vadim Gelfer's avatar
Vadim Gelfer committed
179 180
        raise util.Abort(_('cannot back out a change with no parents'))
    if p2 != nullid:
181 182 183 184 185
        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):
186
            raise util.Abort(_('%s is not a parent of %s') %
187
                             (short(p), short(node)))
188 189 190 191 192
        parent = p
    else:
        if opts['parent']:
            raise util.Abort(_('cannot use --parent on non-merge changeset'))
        parent = p1
193
    hg.clean(repo, node, show_stats=False)
Vadim Gelfer's avatar
Vadim Gelfer committed
194
    revert_opts = opts.copy()
195
    revert_opts['date'] = None
196
    revert_opts['all'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
197 198 199 200
    revert_opts['rev'] = hex(parent)
    revert(ui, repo, **revert_opts)
    commit_opts = opts.copy()
    commit_opts['addremove'] = False
201
    if not commit_opts['message'] and not commit_opts['logfile']:
Vadim Gelfer's avatar
Vadim Gelfer committed
202
        commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
203
        commit_opts['force_editor'] = True
Vadim Gelfer's avatar
Vadim Gelfer committed
204 205 206 207 208
    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)))
209 210 211
    if op1 != node:
        if opts['merge']:
            ui.status(_('merging with changeset %s\n') % nice(op1))
212
            hg.merge(repo, hex(op1))
213 214 215
        else:
            ui.status(_('the backout changeset is a new head - '
                        'do not forget to merge\n'))
216 217
            ui.status(_('(use "backout --merge" '
                        'if you want to auto-merge)\n'))
Vadim Gelfer's avatar
Vadim Gelfer committed
218

219
def branch(ui, repo, label=None, **opts):
220 221
    """set or show the current branch name

222 223 224
    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).
225 226 227

    Unless --force is specified, branch will not let you set a
    branch name that shadows an existing branch.
228 229
    """

230
    if label:
231 232 233 234
        if not opts.get('force') and label in repo.branchtags():
            if label not in [p.branch() for p in repo.workingctx().parents()]:
                raise util.Abort(_('a branch of the same name already exists'
                                   ' (use --force to override)'))
235
        repo.dirstate.setbranch(util.fromlocal(label))
236
        ui.status(_('marked working directory as branch %s\n') % label)
237
    else:
238
        ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
239 240 241 242 243 244 245

def branches(ui, repo):
    """list repository named branches

    List the repository's named branches.
    """
    b = repo.branchtags()
246
    l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
247 248 249 250 251 252
    l.sort()
    for r, n, t in l:
        hexfunc = ui.debugflag and hex or short
        if ui.quiet:
            ui.write("%s\n" % t)
        else:
253 254
            spaces = " " * (30 - util.locallen(t))
            ui.write("%s%s %s:%s\n" % (t, spaces, -r, hexfunc(n)))
255

256
def bundle(ui, repo, fname, dest=None, **opts):
257 258
    """create a changegroup file

259 260
    Generate a compressed changegroup file collecting changesets not
    found in the other repository.
261

262 263
    If no destination repository is specified the destination is assumed
    to have all the nodes specified by one or more --base parameters.
264

265 266 267 268
    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.
269

270 271
    Applying bundles preserves all changeset contents including
    permissions, copy/rename information, and revision history.
272
    """
273 274 275 276 277 278 279 280
    revs = opts.get('rev') or None
    if revs:
        revs = [repo.lookup(rev) for rev in revs]
    base = opts.get('base')
    if base:
        if dest:
            raise util.Abort(_("--base is incompatible with specifiying "
                               "a destination"))
281 282 283
        base = [repo.lookup(rev) for rev in base]
        # create the right base
        # XXX: nodesbetween / changegroup* should be "fixed" instead
284
        o = []
Matt Mackall's avatar
Matt Mackall committed
285
        has = {nullid: None}
286
        for n in base:
Matt Mackall's avatar
Matt Mackall committed
287
            has.update(repo.changelog.reachable(n))
288
        if revs:
289
            visit = list(revs)
290
        else:
291
            visit = repo.changelog.heads()
Matt Mackall's avatar
Matt Mackall committed
292
        seen = {}
293 294
        while visit:
            n = visit.pop(0)
Matt Mackall's avatar
Matt Mackall committed
295
            parents = [p for p in repo.changelog.parents(n) if p not in has]
296 297 298
            if len(parents) == 0:
                o.insert(0, n)
            else:
299 300
                for p in parents:
                    if p not in seen:
Matt Mackall's avatar
Matt Mackall committed
301
                        seen[p] = 1
302
                        visit.append(p)
303
    else:
304
        cmdutil.setremoteconfig(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
305 306
        dest, revs = cmdutil.parseurl(
            ui.expandpath(dest or 'default-push', dest or 'default'), revs)
307 308 309 310 311 312 313
        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')
314
    changegroup.writebundle(cg, fname, "HG10BZ")
315

316
def cat(ui, repo, file1, *pats, **opts):
317
    """output the current or given revision of files
318 319

    Print the specified files as they were at the given revision.
320 321
    If no revision is given, the parent of the working directory is used,
    or tip if no revision is checked out.
322 323 324 325 326 327 328 329 330

    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
331
    ctx = repo.changectx(opts['rev'])
332 333
    for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
                                             ctx.node()):
Vadim Gelfer's avatar
Vadim Gelfer committed
334
        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
335
        fp.write(ctx.filectx(abs).data())
mpm@selenic.com's avatar
mpm@selenic.com committed
336

337
def clone(ui, source, dest=None, **opts):
338 339 340 341 342 343 344 345 346 347 348
    """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
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
    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).
364

365 366 367
    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.
368 369

    See pull for valid source format details.
370 371

    It is possible to specify an ssh:// URL as the destination, but no
372 373 374
    .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.
375
    """
376
    cmdutil.setremoteconfig(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
377
    hg.clone(ui, source, dest,
378
             pull=opts['pull'],
379
             stream=opts['uncompressed'],
380 381
             rev=opts['rev'],
             update=not opts['noupdate'])
mpm@selenic.com's avatar
mpm@selenic.com committed
382

383
def commit(ui, repo, *pats, **opts):
384 385 386 387 388
    """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"
389
    will be committed.
390

391 392
    If no commit message is specified, the editor configured in your hgrc
    or in the EDITOR environment variable is started to enter a message.
393
    """
394
    message = cmdutil.logmessage(opts)
395

mpm@selenic.com's avatar
mpm@selenic.com committed
396
    if opts['addremove']:
397
        cmdutil.addremove(repo, pats, opts)
398
    fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
399
    if pats:
400 401
        status = repo.status(files=fns, match=match)
        modified, added, removed, deleted, unknown = status[:5]
402
        files = modified + added + removed
403
        slist = None
404
        for f in fns:
405 406
            if f == '.':
                continue
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
            if f not in files:
                rf = repo.wjoin(f)
                try:
                    mode = os.lstat(rf)[stat.ST_MODE]
                except OSError:
                    raise util.Abort(_("file %s not found!") % rf)
                if stat.S_ISDIR(mode):
                    name = f + '/'
                    if slist is None:
                        slist = list(files)
                        slist.sort()
                    i = bisect.bisect(slist, name)
                    if i >= len(slist) or not slist[i].startswith(name):
                        raise util.Abort(_("no match under directory %s!")
                                         % rf)
422
                elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
423 424
                    raise util.Abort(_("can't commit %s: "
                                       "unsupported file type!") % rf)
425 426
                elif repo.dirstate.state(f) == '?':
                    raise util.Abort(_("file %s not tracked!") % rf)
427 428
    else:
        files = []
429
    try:
430
        repo.commit(files, message, opts['user'], opts['date'], match,
431
                    force_editor=opts.get('force_editor'))
432 433
    except ValueError, inst:
        raise util.Abort(str(inst))
434

435 436
def docopy(ui, repo, pats, opts, wlock):
    # called with the repo lock held
437 438 439
    #
    # hgsep => pathname that uses "/" to separate directories
    # ossep => pathname that uses os.sep to separate directories
440 441 442
    cwd = repo.getcwd()
    errors = 0
    copied = []
443
    targets = {}
444

445 446 447
    # abs: hgsep
    # rel: ossep
    # return: hgsep
448
    def okaytocopy(abs, rel, exact):
449
        reasons = {'?': _('is not managed'),
450 451
                   'a': _('has been marked for add'),
                   'r': _('has been marked for remove')}
Robin Farine's avatar
Robin Farine committed
452 453
        state = repo.dirstate.state(abs)
        reason = reasons.get(state)
454
        if reason:
Robin Farine's avatar
Robin Farine committed
455 456 457 458
            if state == 'a':
                origsrc = repo.dirstate.copied(abs)
                if origsrc is not None:
                    return origsrc
459 460
            if exact:
                ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
461
        else:
Robin Farine's avatar
Robin Farine committed
462
            return abs
463

464 465 466
    # origsrc: hgsep
    # abssrc: hgsep
    # relsrc: ossep
467 468 469
    # otarget: ossep
    def copy(origsrc, abssrc, relsrc, otarget, exact):
        abstarget = util.canonpath(repo.root, cwd, otarget)
470
        reltarget = repo.pathto(abstarget, cwd)
471
        prevsrc = targets.get(abstarget)
472 473
        src = repo.wjoin(abssrc)
        target = repo.wjoin(abstarget)
474 475
        if prevsrc is not None:
            ui.warn(_('%s: not overwriting - %s collides with %s\n') %
476 477
                    (reltarget, repo.pathto(abssrc, cwd),
                     repo.pathto(prevsrc, cwd)))
478
            return
479
        if (not opts['after'] and os.path.exists(target) or
480
            opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
Robin Farine's avatar
Robin Farine committed
481
            if not opts['force']:
482 483 484
                ui.warn(_('%s: not overwriting - file exists\n') %
                        reltarget)
                return
485
            if not opts['after'] and not opts.get('dry_run'):
486
                os.unlink(target)
Robin Farine's avatar
Robin Farine committed
487
        if opts['after']:
488
            if not os.path.exists(target):
Robin Farine's avatar
Robin Farine committed
489 490
                return
        else:
491
            targetdir = os.path.dirname(target) or '.'
492
            if not os.path.isdir(targetdir) and not opts.get('dry_run'):
493
                os.makedirs(targetdir)
494
            try:
495
                restore = repo.dirstate.state(abstarget) == 'r'
496
                if restore and not opts.get('dry_run'):
497 498
                    repo.undelete([abstarget], wlock)
                try:
499
                    if not opts.get('dry_run'):
500
                        util.copyfile(src, target)
501 502 503
                    restore = False
                finally:
                    if restore:
504
                        repo.remove([abstarget], wlock=wlock)
505 506
            except IOError, inst:
                if inst.errno == errno.ENOENT:
507
                    ui.warn(_('%s: deleted in working copy\n') % relsrc)
508
                else:
509 510 511 512
                    ui.warn(_('%s: cannot copy - %s\n') %
                            (relsrc, inst.strerror))
                    errors += 1
                    return
Robin Farine's avatar
Robin Farine committed
513 514
        if ui.verbose or not exact:
            ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
515
        targets[abstarget] = abssrc
516
        if abstarget != origsrc and not opts.get('dry_run'):
517
            repo.copy(origsrc, abstarget, wlock)
518 519
        copied.append((abssrc, relsrc, exact))

520 521 522 523
    # pat: ossep
    # dest ossep
    # srcs: list of (hgsep, hgsep, ossep, bool)
    # return: function that takes hgsep and returns ossep
Robin Farine's avatar
Robin Farine committed
524 525
    def targetpathfn(pat, dest, srcs):
        if os.path.isdir(pat):
526
            abspfx = util.canonpath(repo.root, cwd, pat)
527
            abspfx = util.localpath(abspfx)
Robin Farine's avatar
Robin Farine committed
528
            if destdirexists:
529
                striplen = len(os.path.split(abspfx)[0])
Robin Farine's avatar
Robin Farine committed
530
            else:
531
                striplen = len(abspfx)
Robin Farine's avatar
Robin Farine committed
532 533
            if striplen:
                striplen += len(os.sep)
534
            res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
Robin Farine's avatar
Robin Farine committed
535
        elif destdirexists:
536 537
            res = lambda p: os.path.join(dest,
                                         os.path.basename(util.localpath(p)))
Robin Farine's avatar
Robin Farine committed
538 539 540 541
        else:
            res = lambda p: dest
        return res

542 543 544 545
    # pat: ossep
    # dest ossep
    # srcs: list of (hgsep, hgsep, ossep, bool)
    # return: function that takes hgsep and returns ossep
Robin Farine's avatar
Robin Farine committed
546 547 548
    def targetpathafterfn(pat, dest, srcs):
        if util.patkind(pat, None)[0]:
            # a mercurial pattern
549 550
            res = lambda p: os.path.join(dest,
                                         os.path.basename(util.localpath(p)))
Robin Farine's avatar
Robin Farine committed
551
        else:
552 553 554 555 556 557 558
            abspfx = util.canonpath(repo.root, cwd, pat)
            if len(abspfx) < len(srcs[0][0]):
                # A directory. Either the target path contains the last
                # component of the source path or it does not.
                def evalpath(striplen):
                    score = 0
                    for s in srcs:
559
                        t = os.path.join(dest, util.localpath(s[0])[striplen:])
560 561 562 563
                        if os.path.exists(t):
                            score += 1
                    return score

564
                abspfx = util.localpath(abspfx)
565 566 567 568 569 570 571 572 573 574
                striplen = len(abspfx)
                if striplen:
                    striplen += len(os.sep)
                if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
                    score = evalpath(striplen)
                    striplen1 = len(os.path.split(abspfx)[0])
                    if striplen1:
                        striplen1 += len(os.sep)
                    if evalpath(striplen1) > score:
                        striplen = striplen1
575 576
                res = lambda p: os.path.join(dest,
                                             util.localpath(p)[striplen:])
Robin Farine's avatar
Robin Farine committed
577
            else:
578 579
                # a file
                if destdirexists:
580 581
                    res = lambda p: os.path.join(dest,
                                        os.path.basename(util.localpath(p)))
582 583
                else:
                    res = lambda p: dest
Robin Farine's avatar
Robin Farine committed
584 585 586
        return res


587
    pats = util.expand_glob(pats)
588 589 590 591 592 593
    if not pats:
        raise util.Abort(_('no source or destination specified'))
    if len(pats) == 1:
        raise util.Abort(_('no destination specified'))
    dest = pats.pop()
    destdirexists = os.path.isdir(dest)
Robin Farine's avatar
Robin Farine committed
594
    if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
595 596
        raise util.Abort(_('with multiple sources, destination must be an '
                         'existing directory'))
Robin Farine's avatar
Robin Farine committed
597 598 599 600 601
    if opts['after']:
        tfn = targetpathafterfn
    else:
        tfn = targetpathfn
    copylist = []
602
    for pat in pats:
Robin Farine's avatar
Robin Farine committed
603
        srcs = []
604 605
        for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
                                                       globbed=True):
Robin Farine's avatar
Robin Farine committed
606 607 608
            origsrc = okaytocopy(abssrc, relsrc, exact)
            if origsrc:
                srcs.append((origsrc, abssrc, relsrc, exact))
Robin Farine's avatar
Robin Farine committed
609 610 611 612 613 614
        if not srcs:
            continue
        copylist.append((tfn(pat, dest, srcs), srcs))
    if not copylist:
        raise util.Abort(_('no files to copy'))

Vadim Gelfer's avatar
Vadim Gelfer committed
615 616 617
    for targetpath, srcs in copylist:
        for origsrc, abssrc, relsrc, exact in srcs:
            copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
618 619

    if errors:
620
        ui.warn(_('(consider using --after)\n'))
621
    return errors, copied
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
622 623

def copy(ui, repo, *pats, **opts):
624 625 626 627 628 629 630 631 632 633
    """mark files as copied for the next commit

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

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

634 635
    This command takes effect in the next commit. To undo a copy
    before that, see hg revert.
636
    """
637 638
    wlock = repo.wlock(0)
    errs, copied = docopy(ui, repo, pats, opts, wlock)
639
    return errs
mpm@selenic.com's avatar
mpm@selenic.com committed
640

mpm@selenic.com's avatar
mpm@selenic.com committed
641 642
def debugancestor(ui, index, rev1, rev2):
    """find the ancestor revision of two revisions in a given index"""
643
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
mpm@selenic.com's avatar
mpm@selenic.com committed
644 645 646
    a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
    ui.write("%d:%s\n" % (r.rev(a), hex(a)))

647
def debugcomplete(ui, cmd='', **opts):
Benoit Boissinot's avatar
Benoit Boissinot committed
648
    """returns the completion list associated with the given command"""
649 650 651 652 653

    if opts['options']:
        options = []
        otables = [globalopts]
        if cmd:
654
            aliases, entry = cmdutil.findcmd(ui, cmd)
655 656 657 658 659 660 661 662 663
            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

664
    clist = cmdutil.findpossible(ui, cmd).keys()
Benoit Boissinot's avatar
Benoit Boissinot committed
665
    clist.sort()
666
    ui.write("%s\n" % "\n".join(clist))
Benoit Boissinot's avatar
Benoit Boissinot committed
667

668
def debugrebuildstate(ui, repo, rev=""):
669
    """rebuild the dirstate as it would look like for the given revision"""
670
    if rev == "":
671
        rev = repo.changelog.tip()
672 673
    ctx = repo.changectx(rev)
    files = ctx.manifest()
674
    wlock = repo.wlock()
675
    repo.dirstate.rebuild(rev, files)
676

677 678
def debugcheckstate(ui, repo):
    """validate the correctness of the current dirstate"""
679
    parent1, parent2 = repo.dirstate.parents()
Matt Mackall's avatar
Matt Mackall committed
680
    dc = repo.dirstate
681 682
    m1 = repo.changectx(parent1).manifest()
    m2 = repo.changectx(parent2).manifest()
683 684 685 686
    errors = 0
    for f in dc:
        state = repo.dirstate.state(f)
        if state in "nr" and f not in m1:
687
            ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
688 689
            errors += 1
        if state in "a" and f in m1:
690
            ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
691 692
            errors += 1
        if state in "m" and f not in m1 and f not in m2:
693
            ui.warn(_("%s in state %s, but not in either manifest\n") %
694
                    (f, state))
695 696 697 698
            errors += 1
    for f in m1:
        state = repo.dirstate.state(f)
        if state not in "nrm":
699
            ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
700 701
            errors += 1
    if errors:
702 703
        error = _(".hg/dirstate inconsistent with current parent's manifest")
        raise util.Abort(error)
704

705
def showconfig(ui, repo, *values, **opts):
706 707 708 709 710 711 712 713 714 715
    """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."""

716
    untrusted = bool(opts.get('untrusted'))
717 718 719
    if values:
        if len([v for v in values if '.' in v]) > 1:
            raise util.Abort(_('only one config item permitted'))
720
    for section, name, value in ui.walkconfig(untrusted=untrusted):
721 722 723 724 725 726 727 728 729
        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
730

Matt Mackall's avatar
Matt Mackall committed
731
def debugsetparents(ui, repo, rev1, rev2=None):
TK Soh's avatar
TK Soh committed
732
    """manually set the parents of the current working directory
Matt Mackall's avatar
Matt Mackall committed
733 734 735 736 737 738 739 740

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

    if not rev2:
        rev2 = hex(nullid)

741 742 743 744 745
    wlock = repo.wlock()
    try:
        repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
    finally:
        wlock.release()
Matt Mackall's avatar
Matt Mackall committed
746

747 748
def debugstate(ui, repo):
    """show the contents of the current dirstate"""
Matt Mackall's avatar
Matt Mackall committed
749 750
    dc = repo.dirstate
    for file_ in dc:
751 752 753 754 755 756 757
        if dc[file_][3] == -1:
            # Pad or slice to locale representation
            locale_len = len(time.strftime("%x %X", time.localtime(0)))
            timestr = 'unset'
            timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
        else:
            timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
758 759
        ui.write("%c %3o %10d %s %s\n"
                 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
760
                    timestr, file_))
761 762
    for f in repo.dirstate.copies():
        ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
763

764
def debugdata(ui, file_, rev):
765 766
    """dump the contents of a data file revision"""
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
767 768 769
    try:
        ui.write(r.revision(r.lookup(rev)))
    except KeyError:
770
        raise util.Abort(_('invalid revision identifier %s') % rev)
771

Matt Mackall's avatar
Matt Mackall committed
772
def debugdate(ui, date, range=None, **opts):
Matt Mackall's avatar
Matt Mackall committed
773
    """parse and display a date"""
Matt Mackall's avatar
Matt Mackall committed
774 775 776 777
    if opts["extended"]:
        d = util.parsedate(date, util.extendeddateformats)
    else:
        d = util.parsedate(date)
Matt Mackall's avatar
Matt Mackall committed
778 779
    ui.write("internal: %s %s\n" % d)
    ui.write("standard: %s\n" % util.datestr(d))
Matt Mackall's avatar
Matt Mackall committed
780 781 782
    if range:
        m = util.matchdate(range)
        ui.write("match: %s\n" % m(d[0]))
Matt Mackall's avatar
Matt Mackall committed
783

784
def debugindex(ui, file_):
785
    """dump the contents of an index file"""
786
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
787
    ui.write("   rev    offset  length   base linkrev" +
mpm@selenic.com's avatar
mpm@selenic.com committed
788
             " nodeid       p1           p2\n")
Benoit Boissinot's avatar
Benoit Boissinot committed
789
    for i in xrange(r.count()):
mason@suse.com's avatar
mason@suse.com committed
790 791
        node = r.node(i)
        pp = r.parents(node)
mpm@selenic.com's avatar
mpm@selenic.com committed
792
        ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
mason@suse.com's avatar
mason@suse.com committed
793 794
                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
795