commands.py 116 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
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[0].date()))
74

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

78 79 80 81 82 83 84
    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()),
            ]

85 86
    if (not opts['user'] and not opts['changeset'] and not opts['date']
        and not opts['follow']):
87
        opts['number'] = 1
88

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

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

100 101
    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
                                             node=ctx.node()):
102 103
        fctx = ctx.filectx(abs)
        if not opts['text'] and util.binary(fctx.data()):
104
            ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
105 106
            continue

107 108
        lines = fctx.annotate(follow=opts.get('follow'),
                              linenumber=linenumber)
109 110
        pieces = []

111 112 113 114 115
        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])
116

mpm@selenic.com's avatar
mpm@selenic.com committed
117 118 119
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
120

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
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
145
    node = repo.changectx(opts['rev']).node()
Vadim Gelfer's avatar
Vadim Gelfer committed
146
    dest = cmdutil.make_filename(repo, dest, node)
147 148
    if os.path.realpath(dest) == repo.root:
        raise util.Abort(_('repository root cannot be destination'))
149
    dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
150 151 152 153 154 155 156
    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
157
    prefix = cmdutil.make_filename(repo, prefix, node)
158 159
    archival.archive(repo, dest, node, kind, not opts['no_decode'],
                     matchfn, prefix)
160

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

Vadim Gelfer's avatar
Vadim Gelfer committed
164 165
    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
166 167

    If you back out a changeset other than the tip, a new head is
Vadim Gelfer's avatar
Vadim Gelfer committed
168 169 170 171 172 173 174 175 176 177
    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
178 179 180 181 182
    if rev and node:
        raise util.Abort(_("please specify just one revision"))

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

184 185 186
    if not rev:
        raise util.Abort(_("please specify a revision to backout"))

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

234
def branch(ui, repo, label=None, **opts):
235 236
    """set or show the current branch name

237 238 239
    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).
240 241 242

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

245
    if label:
246 247 248 249
        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)'))
250
        repo.dirstate.setbranch(util.fromlocal(label))
251
        ui.status(_('marked working directory as branch %s\n') % label)
252
    else:
253
        ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
254

255
def branches(ui, repo, active=False):
256 257
    """list repository named branches

258 259 260 261
    List the repository's named branches, indicating which ones are
    inactive.  If active is specified, only show active branches.

    A branch is considered active if it contains unmerged heads.
262 263
    """
    b = repo.branchtags()
264 265
    heads = dict.fromkeys(repo.heads(), 1)
    l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
266
    l.sort()
267 268 269 270 271 272
    l.reverse()
    for ishead, r, n, t in l:
        if active and not ishead:
            # If we're only displaying active branches, abort the loop on
            # encountering the first inactive head
            break
273
        else:
274 275 276 277 278 279 280 281 282
            hexfunc = ui.debugflag and hex or short
            if ui.quiet:
                ui.write("%s\n" % t)
            else:
                spaces = " " * (30 - util.locallen(t))
                # The code only gets here if inactive branches are being
                # displayed or the branch is active.
                isinactive = ((not ishead) and " (inactive)") or ''
                ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
283

284
def bundle(ui, repo, fname, dest=None, **opts):
285 286
    """create a changegroup file

287 288
    Generate a compressed changegroup file collecting changesets not
    found in the other repository.
289

290 291
    If no destination repository is specified the destination is assumed
    to have all the nodes specified by one or more --base parameters.
292

293 294 295 296
    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.
297

298 299
    Applying bundles preserves all changeset contents including
    permissions, copy/rename information, and revision history.
300
    """
301 302 303 304 305 306 307 308
    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"))
309 310 311
        base = [repo.lookup(rev) for rev in base]
        # create the right base
        # XXX: nodesbetween / changegroup* should be "fixed" instead
312
        o = []
Matt Mackall's avatar
Matt Mackall committed
313
        has = {nullid: None}
314
        for n in base:
Matt Mackall's avatar
Matt Mackall committed
315
            has.update(repo.changelog.reachable(n))
316
        if revs:
317
            visit = list(revs)
318
        else:
319
            visit = repo.changelog.heads()
Matt Mackall's avatar
Matt Mackall committed
320
        seen = {}
321 322
        while visit:
            n = visit.pop(0)
Matt Mackall's avatar
Matt Mackall committed
323
            parents = [p for p in repo.changelog.parents(n) if p not in has]
324 325 326
            if len(parents) == 0:
                o.insert(0, n)
            else:
327 328
                for p in parents:
                    if p not in seen:
Matt Mackall's avatar
Matt Mackall committed
329
                        seen[p] = 1
330
                        visit.append(p)
331
    else:
332
        cmdutil.setremoteconfig(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
333 334
        dest, revs = cmdutil.parseurl(
            ui.expandpath(dest or 'default-push', dest or 'default'), revs)
335 336 337 338 339 340 341
        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')
342
    changegroup.writebundle(cg, fname, "HG10BZ")
343

344
def cat(ui, repo, file1, *pats, **opts):
345
    """output the current or given revision of files
346 347

    Print the specified files as they were at the given revision.
348 349
    If no revision is given, the parent of the working directory is used,
    or tip if no revision is checked out.
350 351 352 353 354 355 356 357 358

    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
359
    ctx = repo.changectx(opts['rev'])
Matt Mackall's avatar
Matt Mackall committed
360
    err = 1
361 362
    for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
                                             ctx.node()):
Vadim Gelfer's avatar
Vadim Gelfer committed
363
        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
364
        fp.write(ctx.filectx(abs).data())
Matt Mackall's avatar
Matt Mackall committed
365 366
        err = 0
    return err
mpm@selenic.com's avatar
mpm@selenic.com committed
367

368
def clone(ui, source, dest=None, **opts):
369 370 371 372 373 374 375 376 377 378 379
    """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
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
    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).
395

396 397 398
    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.
399 400

    See pull for valid source format details.
401 402

    It is possible to specify an ssh:// URL as the destination, but no
403 404 405
    .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.
406
    """
407
    cmdutil.setremoteconfig(ui, opts)
Matt Mackall's avatar
Matt Mackall committed
408
    hg.clone(ui, source, dest,
409
             pull=opts['pull'],
410
             stream=opts['uncompressed'],
411 412
             rev=opts['rev'],
             update=not opts['noupdate'])
mpm@selenic.com's avatar
mpm@selenic.com committed
413

414
def commit(ui, repo, *pats, **opts):
415 416 417 418 419
    """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"
420
    will be committed.
421

422 423
    If no commit message is specified, the editor configured in your hgrc
    or in the EDITOR environment variable is started to enter a message.
424
    """
425
    message = cmdutil.logmessage(opts)
426

mpm@selenic.com's avatar
mpm@selenic.com committed
427
    if opts['addremove']:
428
        cmdutil.addremove(repo, pats, opts)
429
    fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
430
    if pats:
431 432
        status = repo.status(files=fns, match=match)
        modified, added, removed, deleted, unknown = status[:5]
433
        files = modified + added + removed
434
        slist = None
435
        for f in fns:
436 437
            if f == '.':
                continue
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
            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)
453
                elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
454 455
                    raise util.Abort(_("can't commit %s: "
                                       "unsupported file type!") % rf)
456 457
                elif repo.dirstate.state(f) == '?':
                    raise util.Abort(_("file %s not tracked!") % rf)
458 459
    else:
        files = []
460
    try:
461
        repo.commit(files, message, opts['user'], opts['date'], match,
462
                    force_editor=opts.get('force_editor'))
463 464
    except ValueError, inst:
        raise util.Abort(str(inst))
465

466 467
def docopy(ui, repo, pats, opts, wlock):
    # called with the repo lock held
468 469 470
    #
    # hgsep => pathname that uses "/" to separate directories
    # ossep => pathname that uses os.sep to separate directories
471 472 473
    cwd = repo.getcwd()
    errors = 0
    copied = []
474
    targets = {}
475

476 477 478
    # abs: hgsep
    # rel: ossep
    # return: hgsep
479
    def okaytocopy(abs, rel, exact):
480
        reasons = {'?': _('is not managed'),
481
                   'r': _('has been marked for remove')}
Robin Farine's avatar
Robin Farine committed
482 483
        state = repo.dirstate.state(abs)
        reason = reasons.get(state)
484
        if reason:
485 486 487
            if exact:
                ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
        else:
Robin Farine's avatar
Robin Farine committed
488 489 490 491 492
            if state == 'a':
                origsrc = repo.dirstate.copied(abs)
                if origsrc is not None:
                    return origsrc
            return abs
493

494 495 496
    # origsrc: hgsep
    # abssrc: hgsep
    # relsrc: ossep
497 498 499
    # otarget: ossep
    def copy(origsrc, abssrc, relsrc, otarget, exact):
        abstarget = util.canonpath(repo.root, cwd, otarget)
500
        reltarget = repo.pathto(abstarget, cwd)
501
        prevsrc = targets.get(abstarget)
502 503
        src = repo.wjoin(abssrc)
        target = repo.wjoin(abstarget)
504 505
        if prevsrc is not None:
            ui.warn(_('%s: not overwriting - %s collides with %s\n') %
506 507
                    (reltarget, repo.pathto(abssrc, cwd),
                     repo.pathto(prevsrc, cwd)))
508
            return
509
        if (not opts['after'] and os.path.exists(target) or
510
            opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
Robin Farine's avatar
Robin Farine committed
511
            if not opts['force']:
512 513 514
                ui.warn(_('%s: not overwriting - file exists\n') %
                        reltarget)
                return
515
            if not opts['after'] and not opts.get('dry_run'):
516
                os.unlink(target)
Robin Farine's avatar
Robin Farine committed
517
        if opts['after']:
518
            if not os.path.exists(target):
Robin Farine's avatar
Robin Farine committed
519 520
                return
        else:
521
            targetdir = os.path.dirname(target) or '.'
522
            if not os.path.isdir(targetdir) and not opts.get('dry_run'):
523
                os.makedirs(targetdir)
524
            try:
525
                restore = repo.dirstate.state(abstarget) == 'r'
526
                if restore and not opts.get('dry_run'):
527 528
                    repo.undelete([abstarget], wlock)
                try:
529
                    if not opts.get('dry_run'):
530
                        util.copyfile(src, target)
531 532 533
                    restore = False
                finally:
                    if restore:
534
                        repo.remove([abstarget], wlock=wlock)
535 536
            except IOError, inst:
                if inst.errno == errno.ENOENT:
537
                    ui.warn(_('%s: deleted in working copy\n') % relsrc)
538
                else:
539 540 541 542
                    ui.warn(_('%s: cannot copy - %s\n') %
                            (relsrc, inst.strerror))
                    errors += 1
                    return
Robin Farine's avatar
Robin Farine committed
543 544
        if ui.verbose or not exact:
            ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
545
        targets[abstarget] = abssrc
546 547 548 549 550 551 552 553 554
        if abstarget != origsrc:
            if repo.dirstate.state(origsrc) == 'a':
                ui.warn(_("%s was marked for addition. "
                          "%s will not be committed as a copy.\n")
                        % (repo.pathto(origsrc, cwd), reltarget))
                if abstarget not in repo.dirstate and not opts.get('dry_run'):
                    repo.add([abstarget], wlock)
            elif not opts.get('dry_run'):
                repo.copy(origsrc, abstarget, wlock)
555 556
        copied.append((abssrc, relsrc, exact))

557 558 559 560
    # 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
561 562
    def targetpathfn(pat, dest, srcs):
        if os.path.isdir(pat):
563
            abspfx = util.canonpath(repo.root, cwd, pat)
564
            abspfx = util.localpath(abspfx)
Robin Farine's avatar
Robin Farine committed
565
            if destdirexists:
566
                striplen = len(os.path.split(abspfx)[0])
Robin Farine's avatar
Robin Farine committed
567
            else:
568
                striplen = len(abspfx)
Robin Farine's avatar
Robin Farine committed
569 570
            if striplen:
                striplen += len(os.sep)
571
            res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
Robin Farine's avatar
Robin Farine committed
572
        elif destdirexists:
573 574
            res = lambda p: os.path.join(dest,
                                         os.path.basename(util.localpath(p)))
Robin Farine's avatar
Robin Farine committed
575 576 577 578
        else:
            res = lambda p: dest
        return res

579 580 581 582
    # 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
583 584 585
    def targetpathafterfn(pat, dest, srcs):
        if util.patkind(pat, None)[0]:
            # a mercurial pattern
586 587
            res = lambda p: os.path.join(dest,
                                         os.path.basename(util.localpath(p)))
Robin Farine's avatar
Robin Farine committed
588
        else:
589 590 591 592 593 594 595
            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:
596
                        t = os.path.join(dest, util.localpath(s[0])[striplen:])
597 598 599 600
                        if os.path.exists(t):
                            score += 1
                    return score

601
                abspfx = util.localpath(abspfx)
602 603 604 605 606 607 608 609 610 611
                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
612 613
                res = lambda p: os.path.join(dest,
                                             util.localpath(p)[striplen:])
Robin Farine's avatar
Robin Farine committed
614
            else:
615 616
                # a file
                if destdirexists:
617 618
                    res = lambda p: os.path.join(dest,
                                        os.path.basename(util.localpath(p)))
619 620
                else:
                    res = lambda p: dest
Robin Farine's avatar
Robin Farine committed
621 622 623
        return res


624
    pats = util.expand_glob(pats)
625 626 627 628 629 630
    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
631
    if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
632 633
        raise util.Abort(_('with multiple sources, destination must be an '
                         'existing directory'))
Robin Farine's avatar
Robin Farine committed
634 635 636 637 638
    if opts['after']:
        tfn = targetpathafterfn
    else:
        tfn = targetpathfn
    copylist = []
639
    for pat in pats:
Robin Farine's avatar
Robin Farine committed
640
        srcs = []
641 642
        for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
                                                       globbed=True):
Robin Farine's avatar
Robin Farine committed
643 644 645
            origsrc = okaytocopy(abssrc, relsrc, exact)
            if origsrc:
                srcs.append((origsrc, abssrc, relsrc, exact))
Robin Farine's avatar
Robin Farine committed
646 647 648 649 650 651
        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
652 653 654
    for targetpath, srcs in copylist:
        for origsrc, abssrc, relsrc, exact in srcs:
            copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
655 656

    if errors:
657
        ui.warn(_('(consider using --after)\n'))
658
    return errors, copied
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
659 660

def copy(ui, repo, *pats, **opts):
661 662 663 664 665 666 667 668 669 670
    """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.

671 672
    This command takes effect in the next commit. To undo a copy
    before that, see hg revert.
673
    """
674 675
    wlock = repo.wlock(0)
    errs, copied = docopy(ui, repo, pats, opts, wlock)
676
    return errs
mpm@selenic.com's avatar
mpm@selenic.com committed
677

mpm@selenic.com's avatar
mpm@selenic.com committed
678 679
def debugancestor(ui, index, rev1, rev2):
    """find the ancestor revision of two revisions in a given index"""
680
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
mpm@selenic.com's avatar
mpm@selenic.com committed
681 682 683
    a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
    ui.write("%d:%s\n" % (r.rev(a), hex(a)))

684
def debugcomplete(ui, cmd='', **opts):
Benoit Boissinot's avatar
Benoit Boissinot committed
685
    """returns the completion list associated with the given command"""
686 687 688 689 690

    if opts['options']:
        options = []
        otables = [globalopts]
        if cmd:
691
            aliases, entry = cmdutil.findcmd(ui, cmd)
692 693 694 695 696 697 698 699 700
            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

701
    clist = cmdutil.findpossible(ui, cmd).keys()
Benoit Boissinot's avatar
Benoit Boissinot committed
702
    clist.sort()
703
    ui.write("%s\n" % "\n".join(clist))
Benoit Boissinot's avatar
Benoit Boissinot committed
704

705
def debugrebuildstate(ui, repo, rev=""):
706
    """rebuild the dirstate as it would look like for the given revision"""
707
    if rev == "":
708
        rev = repo.changelog.tip()
709 710
    ctx = repo.changectx(rev)
    files = ctx.manifest()
711
    wlock = repo.wlock()
712
    repo.dirstate.rebuild(rev, files)
713

714 715
def debugcheckstate(ui, repo):
    """validate the correctness of the current dirstate"""
716
    parent1, parent2 = repo.dirstate.parents()
Matt Mackall's avatar
Matt Mackall committed
717
    dc = repo.dirstate
718 719
    m1 = repo.changectx(parent1).manifest()
    m2 = repo.changectx(parent2).manifest()
720 721 722 723
    errors = 0
    for f in dc:
        state = repo.dirstate.state(f)
        if state in "nr" and f not in m1:
724
            ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
725 726
            errors += 1
        if state in "a" and f in m1:
727
            ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
728 729
            errors += 1
        if state in "m" and f not in m1 and f not in m2:
730
            ui.warn(_("%s in state %s, but not in either manifest\n") %
731
                    (f, state))
732 733 734 735
            errors += 1
    for f in m1:
        state = repo.dirstate.state(f)
        if state not in "nrm":
736
            ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
737 738
            errors += 1
    if errors:
739 740
        error = _(".hg/dirstate inconsistent with current parent's manifest")
        raise util.Abort(error)
741

742
def showconfig(ui, repo, *values, **opts):
743 744 745 746 747 748 749 750 751 752
    """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."""

753
    untrusted = bool(opts.get('untrusted'))
754 755 756
    if values:
        if len([v for v in values if '.' in v]) > 1:
            raise util.Abort(_('only one config item permitted'))
757
    for section, name, value in ui.walkconfig(untrusted=untrusted):
758 759 760 761 762 763 764 765 766
        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
767

Matt Mackall's avatar
Matt Mackall committed
768
def debugsetparents(ui, repo, rev1, rev2=None):
TK Soh's avatar
TK Soh committed
769
    """manually set the parents of the current working directory
Matt Mackall's avatar
Matt Mackall committed
770 771 772 773 774 775 776 777

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

    if not rev2:
        rev2 = hex(nullid)

778 779 780 781 782
    wlock = repo.wlock()
    try:
        repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
    finally:
        wlock.release()
Matt Mackall's avatar
Matt Mackall committed
783

784 785
def debugstate(ui, repo):
    """show the contents of the current dirstate"""
Matt Mackall's avatar
Matt Mackall committed
786 787
    dc = repo.dirstate
    for file_ in dc:
788 789 790 791 792 793 794
        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]))
795 796
        ui.write("%c %3o %10d %s %s\n"
                 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
797
                    timestr, file_))
798 799
    for f in repo.dirstate.copies():
        ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
800

801
def debugdata(ui, file_, rev):
802 803
    """dump the contents of a data file revision"""
    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
804 805 806
    try:
        ui.write(r.revision(r.lookup(rev)))
    except KeyError:
807
        raise util.Abort(_('invalid revision identifier %s') % rev)
808

Matt Mackall's avatar
Matt Mackall committed
809
def debugdate(ui, date, range=None, **opts):
Matt Mackall's avatar
Matt Mackall committed
810
    """parse and display a date"""
Matt Mackall's avatar
Matt Mackall committed
811 812 813 814
    if opts["extended"]:
        d = util.parsedate(date, util.extendeddateformats)
    else:
        d = util.parsedate(date)
Matt Mackall's avatar
Matt Mackall committed
815 816
    ui.write("internal: %s %s\n" % d)
    ui.write("standard: %s\n" % util.datestr(d))