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

8
from demandload import demandload
9
from node import *
10
from i18n import gettext as _
mpm@selenic.com's avatar
mpm@selenic.com committed
11
demandload(globals(), "os re sys signal shutil imp urllib pdb")
12
demandload(globals(), "fancyopts ui hg util lock revlog")
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
13
demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14
demandload(globals(), "errno socket version struct atexit sets bz2")
15

16 17
class UnknownCommand(Exception):
    """Exception raised if command is not in the command table."""
18

19
def filterfiles(filters, files):
20
    l = [x for x in files if x in filters]
21

22
    for t in filters:
23 24 25
        if t and t[-1] != "/":
            t += "/"
        l += [x for x in files if x.startswith(t)]
26 27
    return l

28
def relpath(repo, args):
29 30
    cwd = repo.getcwd()
    if cwd:
31
        return [util.normpath(os.path.join(cwd, x)) for x in args]
32
    return args
33

34
def matchpats(repo, cwd, pats=[], opts={}, head=''):
35
    return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
mpm@selenic.com's avatar
mpm@selenic.com committed
36 37
                        opts.get('exclude'), head)

38
def makewalk(repo, pats, opts, head=''):
39
    cwd = repo.getcwd()
40
    files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
41
    exact = dict(zip(files, files))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
42
    def walk():
43
        for src, fn in repo.walk(files=files, match=matchfn):
44
            yield src, fn, util.pathto(cwd, fn), fn in exact
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
45 46
    return files, matchfn, walk()

47
def walk(repo, pats, opts, head=''):
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
48
    files, matchfn, results = makewalk(repo, pats, opts, head)
Thomas Arendsen Hein's avatar
Thomas Arendsen Hein committed
49 50
    for r in results:
        yield r
51

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
52
def walkchangerevs(ui, repo, cwd, pats, opts):
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
    '''Iterate over files and the revs they changed in.

    Callers most commonly need to iterate backwards over the history
    it is interested in.  Doing so has awful (quadratic-looking)
    performance, so we use iterators in a "windowed" way.

    We walk a window of revisions in the desired order.  Within the
    window, we first walk forwards to gather data, then in the desired
    order (usually backwards) to display it.

    This function returns an (iterator, getchange) pair.  The
    getchange function returns the changelog entry for a numeric
    revision.  The iterator yields 3-tuples.  They will be of one of
    the following forms:

    "window", incrementing, lastrev: stepping through a window,
    positive if walking forwards through revs, last rev in the
    sequence iterated over - use to reset state for the current window

    "add", rev, fns: out-of-order traversal of the given file names
    fns, which changed during revision rev - use to gather data for
    possible display

    "iter", rev, None: in-order traversal of the revs earlier iterated
    over with "add" - use to display data'''
78 79 80 81

    if repo.changelog.count() == 0:
        return [], False

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
82 83 84 85 86 87 88 89 90 91 92
    cwd = repo.getcwd()
    if not pats and cwd:
        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
    files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
                                        pats, opts)
    revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
    wanted = {}
    slowpath = anypats
    window = 300
    fncache = {}
93 94 95 96 97 98 99 100

    chcache = {}
    def getchange(rev):
        ch = chcache.get(rev)
        if ch is None:
            chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
        return ch

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    if not slowpath and not files:
        # No files, no patterns.  Display all revs.
        wanted = dict(zip(revs, revs))
    if not slowpath:
        # Only files, no patterns.  Check the history of each file.
        def filerevgen(filelog):
            for i in xrange(filelog.count() - 1, -1, -window):
                revs = []
                for j in xrange(max(0, i - window), i + 1):
                    revs.append(filelog.linkrev(filelog.node(j)))
                revs.reverse()
                for rev in revs:
                    yield rev

        minrev, maxrev = min(revs), max(revs)
        for file in files:
            filelog = repo.file(file)
            # A zero count may be a directory or deleted file, so
            # try to find matching entries on the slow path.
            if filelog.count() == 0:
                slowpath = True
                break
            for rev in filerevgen(filelog):
                if rev <= maxrev:
Thomas Arendsen Hein's avatar
Thomas Arendsen Hein committed
125 126
                    if rev < minrev:
                        break
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
127 128 129 130 131 132 133 134
                    fncache.setdefault(rev, [])
                    fncache[rev].append(file)
                    wanted[rev] = 1
    if slowpath:
        # The slow path checks files modified in every changeset.
        def changerevgen():
            for i in xrange(repo.changelog.count() - 1, -1, -window):
                for j in xrange(max(0, i - window), i + 1):
135
                    yield j, getchange(j)[3]
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
136 137 138 139 140 141 142

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

143 144 145 146 147 148 149 150 151 152 153 154 155
    def iterate():
        for i in xrange(0, len(revs), window):
            yield 'window', revs[0] < revs[-1], revs[-1]
            nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
                     if rev in wanted]
            srevs = list(nrevs)
            srevs.sort()
            for rev in srevs:
                fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
                yield 'add', rev, fns
            for rev in nrevs:
                yield 'iter', rev, None
    return iterate(), getchange
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
156

mpm@selenic.com's avatar
mpm@selenic.com committed
157 158
revrangesep = ':'

159
def revrange(ui, repo, revs, revlog=None):
160
    """Yield revision as strings from a list of revision specifications."""
mpm@selenic.com's avatar
mpm@selenic.com committed
161 162 163 164
    if revlog is None:
        revlog = repo.changelog
    revcount = revlog.count()
    def fix(val, defval):
165 166
        if not val:
            return defval
mpm@selenic.com's avatar
mpm@selenic.com committed
167 168
        try:
            num = int(val)
169 170
            if str(num) != val:
                raise ValueError
171 172 173
            if num < 0: num += revcount
            if num < 0: num = 0
            elif num >= revcount:
mpm@selenic.com's avatar
mpm@selenic.com committed
174 175 176 177 178 179 180 181
                raise ValueError
        except ValueError:
            try:
                num = repo.changelog.rev(repo.lookup(val))
            except KeyError:
                try:
                    num = revlog.rev(revlog.lookup(val))
                except KeyError:
182
                    raise util.Abort(_('invalid revision identifier %s'), val)
mpm@selenic.com's avatar
mpm@selenic.com committed
183
        return num
184
    seen = {}
mpm@selenic.com's avatar
mpm@selenic.com committed
185 186 187 188 189
    for spec in revs:
        if spec.find(revrangesep) >= 0:
            start, end = spec.split(revrangesep, 1)
            start = fix(start, 0)
            end = fix(end, revcount - 1)
190 191
            step = start > end and -1 or 1
            for rev in xrange(start, end+step, step):
192 193
                if rev in seen: continue
                seen[rev] = 1
mpm@selenic.com's avatar
mpm@selenic.com committed
194 195
                yield str(rev)
        else:
196 197 198 199
            rev = fix(spec, None)
            if rev in seen: continue
            seen[rev] = 1
            yield str(rev)
mpm@selenic.com's avatar
mpm@selenic.com committed
200

201
def make_filename(repo, r, pat, node=None,
202
                  total=None, seqno=None, revwidth=None, pathname=None):
203
    node_expander = {
204
        'H': lambda: hex(node),
205
        'R': lambda: str(r.rev(node)),
206
        'h': lambda: short(node),
207 208 209 210 211 212
        }
    expander = {
        '%': lambda: '%',
        'b': lambda: os.path.basename(repo.root),
        }

213 214 215 216 217 218 219 220 221 222 223
    try:
        if node:
            expander.update(node_expander)
        if node and revwidth is not None:
            expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
        if total is not None:
            expander['N'] = lambda: str(total)
        if seqno is not None:
            expander['n'] = lambda: str(seqno)
        if total is not None and seqno is not None:
            expander['n'] = lambda:str(seqno).zfill(len(str(total)))
224 225 226 227
        if pathname is not None:
            expander['s'] = lambda: os.path.basename(pathname)
            expander['d'] = lambda: os.path.dirname(pathname) or '.'
            expander['p'] = lambda: pathname
228 229 230 231 232

        newname = []
        patlen = len(pat)
        i = 0
        while i < patlen:
233
            c = pat[i]
234 235 236 237 238 239
            if c == '%':
                i += 1
                c = pat[i]
                c = expander[c]()
            newname.append(c)
            i += 1
240
        return ''.join(newname)
241
    except KeyError, inst:
242
        raise util.Abort(_("invalid format spec '%%%s' in output file name"),
243
                    inst.args[0])
244

245
def make_file(repo, r, pat, node=None,
246
              total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
247
    if not pat or pat == '-':
Thomas Arendsen Hein's avatar
Thomas Arendsen Hein committed
248
        return 'w' in mode and sys.stdout or sys.stdin
249 250 251 252
    if hasattr(pat, 'write') and 'w' in mode:
        return pat
    if hasattr(pat, 'read') and 'r' in mode:
        return pat
253 254
    return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
                              pathname),
255 256
                mode)

257
def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
258
           changes=None, text=False):
259
    if not changes:
260
        (c, a, d, u) = repo.changes(node1, node2, files, match=match)
261 262
    else:
        (c, a, d, u) = changes
263 264 265 266 267 268
    if files:
        c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))

    if not c and not a and not d:
        return

269 270 271
    if node2:
        change = repo.changelog.read(node2)
        mmap2 = repo.manifest.read(change[0])
272
        date2 = util.datestr(change[2])
273 274
        def read(f):
            return repo.file(f).read(mmap2[f])
275
    else:
276
        date2 = util.datestr()
277 278
        if not node1:
            node1 = repo.dirstate.parents()[0]
279 280
        def read(f):
            return repo.wfile(f).read()
281

282 283 284
    if ui.quiet:
        r = None
    else:
285
        hexfunc = ui.verbose and hex or short
286 287
        r = [hexfunc(node) for node in [node1, node2] if node]

288 289
    change = repo.changelog.read(node1)
    mmap = repo.manifest.read(change[0])
290
    date1 = util.datestr(change[2])
291 292

    for f in c:
mpm@selenic.com's avatar
mpm@selenic.com committed
293 294 295
        to = None
        if f in mmap:
            to = repo.file(f).read(mmap[f])
296
        tn = read(f)
297
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
298
    for f in a:
299
        to = None
300
        tn = read(f)
301
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
302 303
    for f in d:
        to = repo.file(f).read(mmap[f])
304
        tn = None
305
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
306

307
def trimuser(ui, name, rev, revcache):
308
    """trim the name of the user who committed a change"""
309 310 311 312
    user = revcache.get(rev)
    if user is None:
        user = revcache[rev] = ui.shortuser(name)
    return user
313

314
def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
315
    """show a single changeset or file revision"""
316 317 318 319 320
    log = repo.changelog
    if changenode is None:
        changenode = log.node(rev)
    elif not rev:
        rev = log.rev(changenode)
321 322

    if ui.quiet:
323
        ui.write("%d:%s\n" % (rev, short(changenode)))
324 325
        return

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

329
    parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
330
               for p in log.parents(changenode)
331
               if ui.debugflag or p != nullid]
332 333 334
    if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
        parents = []

335
    if ui.verbose:
336
        ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
337
    else:
338
        ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
339

Matt Mackall's avatar
Matt Mackall committed
340
    for tag in repo.nodetags(changenode):
341
        ui.status(_("tag:         %s\n") % tag)
Matt Mackall's avatar
Matt Mackall committed
342
    for parent in parents:
343
        ui.write(_("parent:      %d:%s\n") % parent)
344

345 346
    if brinfo and changenode in brinfo:
        br = brinfo[changenode]
347
        ui.write(_("branch:      %s\n") % " ".join(br))
348

349
    ui.debug(_("manifest:    %d:%s\n") % (repo.manifest.rev(changes[0]),
350
                                      hex(changes[0])))
351 352
    ui.status(_("user:        %s\n") % changes[1])
    ui.status(_("date:        %s\n") % date)
353

354
    if ui.debugflag:
355
        files = repo.changes(log.parents(changenode)[0], changenode)
356
        for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
357 358
            if value:
                ui.note("%-12s %s\n" % (key, " ".join(value)))
359
    else:
360
        ui.note(_("files:       %s\n") % " ".join(changes[3]))
361

362
    description = changes[4].strip()
363
    if description:
mpm@selenic.com's avatar
mpm@selenic.com committed
364
        if ui.verbose:
365
            ui.status(_("description:\n"))
366
            ui.status(description)
367
            ui.status("\n\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
368
        else:
369
            ui.status(_("summary:     %s\n") % description.splitlines()[0])
370 371
    ui.status("\n")

372
def show_version(ui):
373
    """output version and copyright information"""
374
    ui.write(_("Mercurial Distributed SCM (version %s)\n")
375
             % version.get_version())
376
    ui.status(_(
377 378 379 380
        "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
        "This is free software; see the source for copying conditions. "
        "There is NO\nwarranty; "
        "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
381
    ))
382

383
def help_(ui, cmd=None, with_version=False):
384
    """show help for a given command or all commands"""
385
    option_lists = []
386
    if cmd and cmd != 'shortlist':
387 388 389
        if with_version:
            show_version(ui)
            ui.write('\n')
390 391
        key, i = find(cmd)
        # synopsis
392 393
        ui.write("%s\n\n" % i[2])

394 395 396 397 398 399 400
        # description
        doc = i[0].__doc__
        if ui.quiet:
            doc = doc.splitlines(0)[0]
        ui.write("%s\n" % doc.rstrip())

        if not ui.quiet:
401
            # aliases
402 403
            aliases = ', '.join(key.split('|')[1:])
            if aliases:
404
                ui.write(_("\naliases: %s\n") % aliases)
405

406 407 408
            # options
            if i[1]:
                option_lists.append(("options", i[1]))
409

mpm@selenic.com's avatar
mpm@selenic.com committed
410
    else:
411
        # program name
412
        if ui.verbose or with_version:
413
            show_version(ui)
mpm@selenic.com's avatar
mpm@selenic.com committed
414
        else:
415
            ui.status(_("Mercurial Distributed SCM\n"))
416
        ui.status('\n')
417

418
        # list of commands
419
        if cmd == "shortlist":
420 421
            ui.status(_('basic commands (use "hg help" '
                        'for the full list or option "-v" for details):\n\n'))
422
        elif ui.verbose:
423
            ui.status(_('list of commands:\n\n'))
424
        else:
425 426
            ui.status(_('list of commands (use "hg help -v" '
                        'to show aliases and global options):\n\n'))
mpm@selenic.com's avatar
mpm@selenic.com committed
427 428

        h = {}
429
        cmds = {}
430 431
        for c, e in table.items():
            f = c.split("|")[0]
432
            if cmd == "shortlist" and not f.startswith("^"):
mpm@selenic.com's avatar
mpm@selenic.com committed
433
                continue
434
            f = f.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
435
            if not ui.debugflag and f.startswith("debug"):
436
                continue
mpm@selenic.com's avatar
mpm@selenic.com committed
437
            d = ""
438 439 440
            if e[0].__doc__:
                d = e[0].__doc__.splitlines(0)[0].rstrip()
            h[f] = d
441
            cmds[f]=c.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
442 443 444 445 446

        fns = h.keys()
        fns.sort()
        m = max(map(len, fns))
        for f in fns:
447 448 449 450 451
            if ui.verbose:
                commands = cmds[f].replace("|",", ")
                ui.write(" %s:\n      %s\n"%(commands,h[f]))
            else:
                ui.write(' %-*s   %s\n' % (m, f, h[f]))
mpm@selenic.com's avatar
mpm@selenic.com committed
452

453 454
    # global options
    if ui.verbose:
455 456 457 458 459 460 461 462 463 464
        option_lists.append(("global options", globalopts))

    # list all option lists
    opt_output = []
    for title, options in option_lists:
        opt_output.append(("\n%s:\n" % title, None))
        for shortopt, longopt, default, desc in options:
            opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
                                          longopt and " --%s" % longopt),
                               "%s%s" % (desc,
465
                                         default and _(" (default: %s)") % default
466 467 468 469 470 471 472 473 474
                                         or "")))

    if opt_output:
        opts_len = max([len(line[0]) for line in opt_output if line[1]])
        for first, second in opt_output:
            if second:
                ui.write(" %-*s  %s\n" % (opts_len, first, second))
            else:
                ui.write("%s\n" % first)
mpm@selenic.com's avatar
mpm@selenic.com committed
475 476

# Commands start here, listed alphabetically
477

478
def add(ui, repo, *pats, **opts):
479
    '''add the specified files on the next commit'''
480
    names = []
481 482
    for src, abs, rel, exact in walk(repo, pats, opts):
        if exact:
483
            if ui.verbose: ui.status(_('adding %s\n') % rel)
484 485
            names.append(abs)
        elif repo.dirstate.state(abs) == '?':
486
            ui.status(_('adding %s\n') % rel)
487 488
            names.append(abs)
    repo.add(names)
489

490
def addremove(ui, repo, *pats, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
491
    """add all new files, delete all missing files"""
492
    add, remove = [], []
493
    for src, abs, rel, exact in walk(repo, pats, opts):
494 495
        if src == 'f' and repo.dirstate.state(abs) == '?':
            add.append(abs)
496
            if ui.verbose or not exact:
497
                ui.status(_('adding %s\n') % rel)
498
        if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
499
            remove.append(abs)
500
            if ui.verbose or not exact:
501
                ui.status(_('removing %s\n') % rel)
502 503
    repo.add(add)
    repo.remove(remove)
504

505
def annotate(ui, repo, *pats, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
506
    """show changeset information per file line"""
507
    def getnode(rev):
508
        return short(repo.changelog.node(rev))
509

510
    ucache = {}
511
    def getname(rev):
512
        cl = repo.changelog.read(repo.changelog.node(rev))
513
        return trimuser(ui, cl[1], rev, ucache)
514

515
    if not pats:
516
        raise util.Abort(_('at least one file name or pattern required'))
517

518
    opmap = [['user', getname], ['number', str], ['changeset', getnode]]
519 520
    if not opts['user'] and not opts['changeset']:
        opts['number'] = 1
521

522 523
    if opts['rev']:
        node = repo.changelog.lookup(opts['rev'])
524 525
    else:
        node = repo.dirstate.parents()[0]
526 527
    change = repo.changelog.read(node)
    mmap = repo.manifest.read(change[0])
528

529
    for src, abs, rel, exact in walk(repo, pats, opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
530
        if abs not in mmap:
531
            ui.warn(_("warning: %s is not in the repository!\n") % rel)
mpm@selenic.com's avatar
mpm@selenic.com committed
532 533
            continue

534 535
        f = repo.file(abs)
        if not opts['text'] and util.binary(f.read(mmap[abs])):
536
            ui.write(_("%s: binary file\n") % rel)
537 538 539
            continue

        lines = f.annotate(mmap[abs])
540 541 542
        pieces = []

        for o, f in opmap:
543
            if opts[o]:
544
                l = [f(n) for n, dummy in lines]
mpm@selenic.com's avatar
mpm@selenic.com committed
545 546 547
                if l:
                    m = max(map(len, l))
                    pieces.append(["%*s" % (m, x) for x in l])
548

mpm@selenic.com's avatar
mpm@selenic.com committed
549 550 551
        if pieces:
            for p, l in zip(zip(*pieces), lines):
                ui.write("%s: %s" % (" ".join(p), l[1]))
552

553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
def bundle(ui, repo, fname, dest="default-push", **opts):
    """create a changegroup file"""
    f = open(fname, "wb")
    dest = ui.expandpath(dest)
    other = hg.repository(ui, dest)
    o = repo.findoutgoing(other)
    cg = repo.changegroup(o)

    try:
        f.write("HG10")
        z = bz2.BZ2Compressor(9)
        while 1:
            chunk = cg.read(4096)
            if not chunk:
                break
            f.write(z.compress(chunk))
        f.write(z.flush())
    except:
        os.unlink(fname)
572
        raise
573

574 575 576 577 578 579 580 581 582
def cat(ui, repo, file1, *pats, **opts):
    """output the latest or given revisions of files"""
    mf = {}
    if opts['rev']:
        change = repo.changelog.read(repo.lookup(opts['rev']))
        mf = repo.manifest.read(change[0])
    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
        r = repo.file(abs)
        if opts['rev']:
583
            try:
584 585 586 587 588
                n = mf[abs]
            except (hg.RepoError, KeyError):
                try:
                    n = r.lookup(rev)
                except KeyError, inst:
589
                    raise util.Abort(_('cannot find file %s in rev %s'), rel, rev)
590 591 592 593
        else:
            n = r.tip()
        fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
        fp.write(r.read(n))
mpm@selenic.com's avatar
mpm@selenic.com committed
594

595
def clone(ui, source, dest=None, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
596 597
    """make a copy of an existing repository"""
    if dest is None:
598
        dest = os.path.basename(os.path.normpath(source))
599 600

    if os.path.exists(dest):
601
        raise util.Abort(_("destination '%s' already exists"), dest)
602

603 604
    dest = os.path.realpath(dest)

605 606
    class Dircleanup:
        def __init__(self, dir_):
Matt Mackall's avatar
Matt Mackall committed
607
            self.rmtree = shutil.rmtree
608 609
            self.dir_ = dir_
            os.mkdir(dir_)
610
        def close(self):
611
            self.dir_ = None
612
        def __del__(self):
613 614
            if self.dir_:
                self.rmtree(self.dir_, True)
615

616 617 618 619 620
    if opts['ssh']:
        ui.setconfig("ui", "ssh", opts['ssh'])
    if opts['remotecmd']:
        ui.setconfig("ui", "remotecmd", opts['remotecmd'])

621 622
    if not os.path.exists(source):
        source = ui.expandpath(source)
623 624

    d = Dircleanup(dest)
625
    abspath = source
626 627
    other = hg.repository(ui, source)

628
    copy = False
629
    if other.dev() != -1:
Mikael Berthe's avatar
Mikael Berthe committed
630
        abspath = os.path.abspath(source)
631 632
        if not opts['pull']:
            copy = True
633

634 635 636 637 638 639 640 641 642 643 644 645
    if copy:
        try:
            # we use a lock here because if we race with commit, we
            # can end up with extra data in the cloned revlogs that's
            # not pointed to by changesets, thus causing verify to
            # fail
            l1 = lock.lock(os.path.join(source, ".hg", "lock"))
        except OSError:
            copy = False

    if copy:
        # we lock here to avoid premature writing to the target
646
        os.mkdir(os.path.join(dest, ".hg"))
mpm@selenic.com's avatar
mpm@selenic.com committed
647
        l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
648

649 650 651 652
        files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
        for f in files.split():
            src = os.path.join(source, ".hg", f)
            dst = os.path.join(dest, ".hg", f)
653
            util.copyfiles(src, dst)
mpm@selenic.com's avatar
mpm@selenic.com committed
654

655
        repo = hg.repository(ui, dest)
mpm@selenic.com's avatar
mpm@selenic.com committed
656

657 658
    else:
        repo = hg.repository(ui, dest, create=1)
Matt Mackall's avatar
Matt Mackall committed
659
        repo.pull(other)
660

661
    f = repo.opener("hgrc", "w", text=True)
mpm@selenic.com's avatar
mpm@selenic.com committed
662 663
    f.write("[paths]\n")
    f.write("default = %s\n" % abspath)
664

665 666 667 668
    if not opts['noupdate']:
        update(ui, repo)

    d.close()
mpm@selenic.com's avatar
mpm@selenic.com committed
669

670
def commit(ui, repo, *pats, **opts):
671
    """commit the specified files or all outstanding changes"""
672
    if opts['text']:
673 674
        ui.warn(_("Warning: -t and --text is deprecated,"
                  " please use -m or --message instead.\n"))
675
    message = opts['message'] or opts['text']
676
    logfile = opts['logfile']
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
677 678

    if message and logfile:
679 680
        raise util.Abort(_('options --message and --logfile are mutually '
                           'exclusive'))
681
    if not message and logfile:
682
        try:
683 684 685 686
            if logfile == '-':
                message = sys.stdin.read()
            else:
                message = open(logfile).read()
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
687
        except IOError, inst:
688
            raise util.Abort(_("can't read commit message '%s': %s") %
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
689
                             (logfile, inst.strerror))
690

mpm@selenic.com's avatar
mpm@selenic.com committed
691
    if opts['addremove']:
692 693 694 695 696
        addremove(ui, repo, *pats, **opts)
    cwd = repo.getcwd()
    if not pats and cwd:
        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
697 698
    fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
                                    pats, opts)
699
    if pats:
700
        c, a, d, u = repo.changes(files=fns, match=match)
701 702 703
        files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
    else:
        files = []
704 705 706 707
    try:
        repo.commit(files, message, opts['user'], opts['date'], match)
    except ValueError, inst:
        raise util.Abort(str(inst))
708

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
709
def docopy(ui, repo, pats, opts):
710
    if not pats:
711
        raise util.Abort(_('no source or destination specified'))
712
    elif len(pats) == 1:
713
        raise util.Abort(_('no destination specified'))
714 715 716 717 718
    pats = list(pats)
    dest = pats.pop()
    sources = []

    def okaytocopy(abs, rel, exact):
719 720
        reasons = {'?': _('is not managed'),
                   'a': _('has been marked for add')}
721 722
        reason = reasons.get(repo.dirstate.state(abs))
        if reason:
723
            if exact: ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
724 725 726 727 728 729 730
        else:
            return True

    for src, abs, rel, exact in walk(repo, pats, opts):
        if okaytocopy(abs, rel, exact):
            sources.append((abs, rel, exact))
    if not sources:
731
        raise util.Abort(_('no files to copy'))
732 733 734 735 736 737 738 739 740 741 742

    cwd = repo.getcwd()
    absdest = util.canonpath(repo.root, cwd, dest)
    reldest = util.pathto(cwd, absdest)
    if os.path.exists(reldest):
        destisfile = not os.path.isdir(reldest)
    else:
        destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'

    if destisfile:
        if opts['parents']:
743
            raise util.Abort(_('with --parents, destination must be a directory'))
744
        elif len(sources) > 1:
745 746
            raise util.Abort(_('with multiple sources, destination must be a '
                               'directory'))
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
747
    errs, copied = 0, []
748 749 750 751 752 753 754 755 756 757
    for abs, rel, exact in sources:
        if opts['parents']:
            mydest = os.path.join(dest, rel)
        elif destisfile:
            mydest = reldest
        else:
            mydest = os.path.join(dest, os.path.basename(rel))
        myabsdest = util.canonpath(repo.root, cwd, mydest)
        myreldest = util.pathto(cwd, myabsdest)
        if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
758
            ui.warn(_('%s: not overwriting - file already managed\n') % myreldest)
759