commands.py 43.3 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
demandload(globals(), "os re sys signal shutil")
10
demandload(globals(), "fancyopts ui hg util")
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
11
demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12
demandload(globals(), "errno socket version struct")
13

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

17 18 19
class Abort(Exception):
    """Raised if a command needs to print an error and exit."""

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

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

29
def relfilter(repo, files):
30 31 32
    cwd = repo.getcwd()
    if cwd:
        return filterfiles([util.pconvert(cwd)], files)
33
    return files
34

35
def relpath(repo, args):
36 37
    cwd = repo.getcwd()
    if cwd:
38 39
        return [util.pconvert(os.path.normpath(os.path.join(cwd, x)))
                for x in args]
40
    return args
41

mpm@selenic.com's avatar
mpm@selenic.com committed
42 43 44 45 46
def matchpats(cwd, pats = [], opts = {}, head = ''):
    return util.matcher(cwd, pats, opts.get('include'),
                        opts.get('exclude'), head)

def walk(repo, pats, opts, head = ''):
47
    cwd = repo.getcwd()
mpm@selenic.com's avatar
mpm@selenic.com committed
48
    c = 0
49
    if cwd: c = len(cwd) + 1
mpm@selenic.com's avatar
mpm@selenic.com committed
50 51
    for src, fn in repo.walk(match = matchpats(cwd, pats, opts, head)):
        yield src, fn, fn[c:]
52

mpm@selenic.com's avatar
mpm@selenic.com committed
53 54
revrangesep = ':'

55
def revrange(ui, repo, revs, revlog=None):
mpm@selenic.com's avatar
mpm@selenic.com committed
56 57 58 59
    if revlog is None:
        revlog = repo.changelog
    revcount = revlog.count()
    def fix(val, defval):
60 61
        if not val:
            return defval
mpm@selenic.com's avatar
mpm@selenic.com committed
62 63
        try:
            num = int(val)
64 65 66 67
            if str(num) != val:
                raise ValueError
            if num < 0:
                num += revcount
mpm@selenic.com's avatar
mpm@selenic.com committed
68 69 70 71 72 73 74 75 76
            if not (0 <= num < revcount):
                raise ValueError
        except ValueError:
            try:
                num = repo.changelog.rev(repo.lookup(val))
            except KeyError:
                try:
                    num = revlog.rev(revlog.lookup(val))
                except KeyError:
77
                    raise Abort('invalid revision identifier %s', val)
mpm@selenic.com's avatar
mpm@selenic.com committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
        return num
    for spec in revs:
        if spec.find(revrangesep) >= 0:
            start, end = spec.split(revrangesep, 1)
            start = fix(start, 0)
            end = fix(end, revcount - 1)
            if end > start:
                end += 1
                step = 1
            else:
                end -= 1
                step = -1
            for rev in xrange(start, end, step):
                yield str(rev)
        else:
            yield spec

95 96
def make_filename(repo, r, pat, node=None,
                  total=None, seqno=None, revwidth=None):
97 98 99 100 101 102 103 104 105 106
    node_expander = {
        'H': lambda: hg.hex(node),
        'R': lambda: str(r.rev(node)),
        'h': lambda: hg.short(node),
        }
    expander = {
        '%': lambda: '%',
        'b': lambda: os.path.basename(repo.root),
        }

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    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)))

        newname = []
        patlen = len(pat)
        i = 0
        while i < patlen:
123
            c = pat[i]
124 125 126 127 128 129
            if c == '%':
                i += 1
                c = pat[i]
                c = expander[c]()
            newname.append(c)
            i += 1
130
        return ''.join(newname)
131 132 133
    except KeyError, inst:
        raise Abort("invalid format spec '%%%s' in output file name",
                    inst.args[0])
134

135 136 137 138 139 140 141 142 143 144 145 146
def make_file(repo, r, pat, node=None,
              total=None, seqno=None, revwidth=None, mode='wb'):
    if not pat or pat == '-':
        if 'w' in mode: return sys.stdout
        else: return sys.stdin
    if hasattr(pat, 'write') and 'w' in mode:
        return pat
    if hasattr(pat, 'read') and 'r' in mode:
        return pat
    return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
                mode)

147
def dodiff(fp, ui, repo, files=None, node1=None, node2=None):
148 149 150
    def date(c):
        return time.asctime(time.gmtime(float(c[2].split(' ')[0])))

mpm@selenic.com's avatar
mpm@selenic.com committed
151
    (c, a, d, u) = repo.changes(node1, node2, files)
152 153 154 155 156 157
    if files:
        c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))

    if not c and not a and not d:
        return

158 159 160 161
    if node2:
        change = repo.changelog.read(node2)
        mmap2 = repo.manifest.read(change[0])
        date2 = date(change)
162 163
        def read(f):
            return repo.file(f).read(mmap2[f])
164 165 166 167
    else:
        date2 = time.asctime()
        if not node1:
            node1 = repo.dirstate.parents()[0]
168 169
        def read(f):
            return repo.wfile(f).read()
170

171 172 173 174 175 176
    if ui.quiet:
        r = None
    else:
        hexfunc = ui.verbose and hg.hex or hg.short
        r = [hexfunc(node) for node in [node1, node2] if node]

177 178 179 180 181
    change = repo.changelog.read(node1)
    mmap = repo.manifest.read(change[0])
    date1 = date(change)

    for f in c:
mpm@selenic.com's avatar
mpm@selenic.com committed
182 183 184
        to = None
        if f in mmap:
            to = repo.file(f).read(mmap[f])
185
        tn = read(f)
mpm@selenic.com's avatar
mpm@selenic.com committed
186
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
187
    for f in a:
188
        to = None
189
        tn = read(f)
mpm@selenic.com's avatar
mpm@selenic.com committed
190
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
191 192
    for f in d:
        to = repo.file(f).read(mmap[f])
193
        tn = None
mpm@selenic.com's avatar
mpm@selenic.com committed
194
        fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
195 196 197 198 199 200 201 202 203 204 205 206

def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
    """show a single changeset or file revision"""
    changelog = repo.changelog
    if filelog:
        log = filelog
        filerev = rev
        node = filenode = filelog.node(filerev)
        changerev = filelog.linkrev(filenode)
        changenode = changenode or changelog.node(changerev)
    else:
        log = changelog
207
        changerev = rev
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
        if changenode is None:
            changenode = changelog.node(changerev)
        elif not changerev:
            rev = changerev = changelog.rev(changenode)
        node = changenode

    if ui.quiet:
        ui.write("%d:%s\n" % (rev, hg.hex(node)))
        return

    changes = changelog.read(changenode)

    parents = [(log.rev(parent), hg.hex(parent))
               for parent in log.parents(node)
               if ui.debugflag or parent != hg.nullid]
    if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
        parents = []

Matt Mackall's avatar
Matt Mackall committed
226 227 228 229 230
    ui.write("changeset:   %d:%s\n" % (changerev, hg.hex(changenode)))
    for tag in repo.nodetags(changenode):
        ui.status("tag:         %s\n" % tag)
    for parent in parents:
        ui.write("parent:      %d:%s\n" % parent)
231
    if filelog:
Matt Mackall's avatar
Matt Mackall committed
232 233 234
        ui.debug("file rev:    %d:%s\n" % (filerev, hg.hex(filenode)))
    ui.note("manifest:    %d:%s\n" % (repo.manifest.rev(changes[0]),
                                      hg.hex(changes[0])))
235 236 237
    ui.status("user:        %s\n" % changes[1])
    ui.status("date:        %s\n" % time.asctime(
        time.localtime(float(changes[2].split(' ')[0]))))
238
    if ui.debugflag:
239
        files = repo.changes(changelog.parents(changenode)[0], changenode)
240 241 242
        for key, value in zip(["files:", "files+:", "files-:"], files):
            if value:
                ui.note("%-12s %s\n" % (key, " ".join(value)))
243 244
    else:
        ui.note("files:       %s\n" % " ".join(changes[3]))
245
    description = changes[4].strip()
246
    if description:
mpm@selenic.com's avatar
mpm@selenic.com committed
247 248
        if ui.verbose:
            ui.status("description:\n")
249
            ui.status(description)
250
            ui.status("\n\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
251
        else:
252
            ui.status("summary:     %s\n" % description.splitlines()[0])
253 254
    ui.status("\n")

255
def show_version(ui):
256 257 258 259 260 261 262 263 264
    """output version and copyright information"""
    ui.write("Mercurial version %s\n" % version.get_version())
    ui.status(
        "\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"
    )

265 266
def help_(ui, cmd=None):
    """show help for a given command or all commands"""
267 268 269 270
    if cmd:
        try:
            i = find(cmd)
            ui.write("%s\n\n" % i[2])
mpm@selenic.com's avatar
mpm@selenic.com committed
271 272 273

            if i[1]:
                for s, l, d, c in i[1]:
274 275 276 277 278 279 280
                    opt = ' '
                    if s:
                        opt = opt + '-' + s + ' '
                    if l:
                        opt = opt + '--' + l + ' '
                    if d:
                        opt = opt + '(' + str(d) + ')'
mpm@selenic.com's avatar
mpm@selenic.com committed
281
                    ui.write(opt, "\n")
282 283
                    if c:
                        ui.write('   %s\n' % c)
mpm@selenic.com's avatar
mpm@selenic.com committed
284 285
                ui.write("\n")

286 287
            ui.write(i[0].__doc__, "\n")
        except UnknownCommand:
mpm@selenic.com's avatar
mpm@selenic.com committed
288
            ui.warn("hg: unknown command %s\n" % cmd)
289
        sys.exit(0)
mpm@selenic.com's avatar
mpm@selenic.com committed
290
    else:
mpm@selenic.com's avatar
mpm@selenic.com committed
291
        if ui.verbose:
292
            show_version(ui)
293
            ui.write('\n')
mpm@selenic.com's avatar
mpm@selenic.com committed
294 295 296
        if ui.verbose:
            ui.write('hg commands:\n\n')
        else:
297
            ui.write('basic hg commands (use "hg help -v" for more):\n\n')
mpm@selenic.com's avatar
mpm@selenic.com committed
298 299

        h = {}
300 301
        for c, e in table.items():
            f = c.split("|")[0]
mpm@selenic.com's avatar
mpm@selenic.com committed
302 303 304
            if not ui.verbose and not f.startswith("^"):
                continue
            if not ui.debugflag and f.startswith("debug"):
305
                continue
mpm@selenic.com's avatar
mpm@selenic.com committed
306
            f = f.lstrip("^")
mpm@selenic.com's avatar
mpm@selenic.com committed
307
            d = ""
308 309 310
            if e[0].__doc__:
                d = e[0].__doc__.splitlines(0)[0].rstrip()
            h[f] = d
mpm@selenic.com's avatar
mpm@selenic.com committed
311 312 313 314 315

        fns = h.keys()
        fns.sort()
        m = max(map(len, fns))
        for f in fns:
316
            ui.write(' %-*s   %s\n' % (m, f, h[f]))
mpm@selenic.com's avatar
mpm@selenic.com committed
317 318

# Commands start here, listed alphabetically
319

320
def add(ui, repo, *pats, **opts):
321
    '''add the specified files on the next commit'''
322 323
    names = []
    q = dict(zip(pats, pats))
324
    for src, abs, rel in walk(repo, pats, opts):
325 326 327 328 329 330
        if rel in q or abs in q:
            names.append(abs)
        elif repo.dirstate.state(abs) == '?':
            ui.status('adding %s\n' % rel)
            names.append(abs)
    repo.add(names)
331

332
def addremove(ui, repo, *files):
mpm@selenic.com's avatar
mpm@selenic.com committed
333
    """add all new files, delete all missing files"""
334 335 336 337 338 339 340 341 342 343 344 345 346
    if files:
        files = relpath(repo, files)
        d = []
        u = []
        for f in files:
            p = repo.wjoin(f)
            s = repo.dirstate.state(f)
            isfile = os.path.isfile(p)
            if s != 'r' and not isfile:
                d.append(f)
            elif s not in 'nmai' and isfile:
                u.append(f)
    else:
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
347
        (c, a, d, u) = repo.changes()
mpm@selenic.com's avatar
mpm@selenic.com committed
348
    repo.add(u)
349
    repo.remove(d)
350

351
def annotate(ui, repo, *pats, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
352
    """show changeset information per file line"""
353 354 355 356 357 358 359 360 361 362 363 364
    def getnode(rev):
        return hg.short(repo.changelog.node(rev))

    def getname(rev):
        try:
            return bcache[rev]
        except KeyError:
            cl = repo.changelog.read(repo.changelog.node(rev))
            name = cl[1]
            f = name.find('@')
            if f >= 0:
                name = name[:f]
365 366 367
            f = name.find('<')
            if f >= 0:
                name = name[f+1:]
368 369
            bcache[rev] = name
            return name
370

371 372 373
    if not pats:
        raise Abort('at least one file name or pattern required')

374 375
    bcache = {}
    opmap = [['user', getname], ['number', str], ['changeset', getnode]]
376 377
    if not opts['user'] and not opts['changeset']:
        opts['number'] = 1
378

379 380
    if opts['rev']:
        node = repo.changelog.lookup(opts['rev'])
381 382
    else:
        node = repo.dirstate.parents()[0]
383 384
    change = repo.changelog.read(node)
    mmap = repo.manifest.read(change[0])
385
    for src, abs, rel in walk(repo, pats, opts):
386
        lines = repo.file(abs).annotate(mmap[abs])
387 388 389
        pieces = []

        for o, f in opmap:
390
            if opts[o]:
391
                l = [f(n) for n, dummy in lines]
392
                m = max(map(len, l))
393
                pieces.append(["%*s" % (m, x) for x in l])
394

395
        for p, l in zip(zip(*pieces), lines):
396
            ui.write("%s: %s" % (" ".join(p), l[1]))
397

398
def cat(ui, repo, file1, rev=None, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
399
    """output the latest or given revision of a file"""
400 401 402 403 404
    r = repo.file(relpath(repo, [file1])[0])
    if rev:
        n = r.lookup(rev)
    else:
        n = r.tip()
405
    fp = make_file(repo, r, opts['output'], node=n)
406
    fp.write(r.read(n))
mpm@selenic.com's avatar
mpm@selenic.com committed
407

408
def clone(ui, source, dest=None, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
409 410
    """make a copy of an existing repository"""
    if dest is None:
411
        dest = os.path.basename(os.path.normpath(source))
412 413 414 415

    if os.path.exists(dest):
        ui.warn("abort: destination '%s' already exists\n" % dest)
        return 1
416

417 418
    class Dircleanup:
        def __init__(self, dir_):
Matt Mackall's avatar
Matt Mackall committed
419
            self.rmtree = shutil.rmtree
420 421
            self.dir_ = dir_
            os.mkdir(dir_)
422
        def close(self):
423
            self.dir_ = None
424
        def __del__(self):
425 426
            if self.dir_:
                self.rmtree(self.dir_, True)
427

428
    d = Dircleanup(dest)
429
    abspath = source
430 431 432
    source = ui.expandpath(source)
    other = hg.repository(ui, source)

Mikael Berthe's avatar
Mikael Berthe committed
433 434
    if other.dev() != -1:
        abspath = os.path.abspath(source)
435 436 437 438 439 440
        copyfile = (os.stat(dest).st_dev == other.dev()
                    and getattr(os, 'link', None) or shutil.copy2)
        if copyfile is not shutil.copy2:
            ui.note("cloning by hardlink\n")
        util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
                      copyfile)
441
        try:
442 443 444
            os.unlink(os.path.join(dest, ".hg", "dirstate"))
        except IOError:
            pass
mpm@selenic.com's avatar
mpm@selenic.com committed
445

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

448 449
    else:
        repo = hg.repository(ui, dest, create=1)
Matt Mackall's avatar
Matt Mackall committed
450
        repo.pull(other)
451

452 453
    f = repo.opener("hgrc", "w")
    f.write("[paths]\n")
454
    f.write("default = %s\n" % abspath)
455

456 457 458 459
    if not opts['noupdate']:
        update(ui, repo)

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

461
def commit(ui, repo, *files, **opts):
462
    """commit the specified files or all outstanding changes"""
463 464 465
    if opts['text']:
        ui.warn("Warning: -t and --text is deprecated,"
                " please use -m or --message instead.\n")
466
    message = opts['message'] or opts['text']
467
    logfile = opts['logfile']
468
    if not message and logfile:
469
        try:
470
            message = open(logfile).read()
471
        except IOError, why:
472
            ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
473

mpm@selenic.com's avatar
mpm@selenic.com committed
474 475
    if opts['addremove']:
        addremove(ui, repo, *files)
476
    repo.commit(relpath(repo, files), message, opts['user'], opts['date'])
477

mpm@selenic.com's avatar
mpm@selenic.com committed
478 479 480 481
def copy(ui, repo, source, dest):
    """mark a file as copied or renamed for the next commit"""
    return repo.copy(*relpath(repo, (source, dest)))

482 483
def debugcheckstate(ui, repo):
    """validate the correctness of the current dirstate"""
484
    parent1, parent2 = repo.dirstate.parents()
485 486
    repo.dirstate.read()
    dc = repo.dirstate.map
487 488 489 490 491 492 493 494 495 496
    keys = dc.keys()
    keys.sort()
    m1n = repo.changelog.read(parent1)[0]
    m2n = repo.changelog.read(parent2)[0]
    m1 = repo.manifest.read(m1n)
    m2 = repo.manifest.read(m2n)
    errors = 0
    for f in dc:
        state = repo.dirstate.state(f)
        if state in "nr" and f not in m1:
497
            ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
498 499
            errors += 1
        if state in "a" and f in m1:
500
            ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
501 502
            errors += 1
        if state in "m" and f not in m1 and f not in m2:
503 504
            ui.warn("%s in state %s, but not in either manifest\n" %
                    (f, state))
505 506 507 508
            errors += 1
    for f in m1:
        state = repo.dirstate.state(f)
        if state not in "nrm":
509
            ui.warn("%s in manifest1, but listed as state %s" % (f, state))
510 511
            errors += 1
    if errors:
512
        raise Abort(".hg/dirstate inconsistent with current parent's manifest")
513

514 515
def debugstate(ui, repo):
    """show the contents of the current dirstate"""
516 517
    repo.dirstate.read()
    dc = repo.dirstate.map
518 519
    keys = dc.keys()
    keys.sort()
520 521
    for file_ in keys:
        ui.write("%c %s\n" % (dc[file_][0], file_))
522

523
def debugindex(ui, file_):
524
    """dump the contents of an index file"""
525
    r = hg.revlog(hg.opener(""), file_, "")
526 527
    ui.write("   rev    offset  length   base linkrev" +
             " p1           p2           nodeid\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
528 529
    for i in range(r.count()):
        e = r.index[i]
530 531 532
        ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
                i, e[0], e[1], e[2], e[3],
            hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
mpm@selenic.com's avatar
mpm@selenic.com committed
533

534
def debugindexdot(ui, file_):
535
    """dump an index DAG as a .dot file"""
536
    r = hg.revlog(hg.opener(""), file_, "")
537
    ui.write("digraph G {\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
538 539
    for i in range(r.count()):
        e = r.index[i]
540
        ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
mpm@selenic.com's avatar
mpm@selenic.com committed
541
        if e[5] != hg.nullid:
542 543
            ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
    ui.write("}\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
544

545
def diff(ui, repo, *pats, **opts):
mpm@selenic.com's avatar
mpm@selenic.com committed
546
    """diff working directory (or selected files)"""
547 548 549
    revs = []
    if opts['rev']:
        revs = map(lambda x: repo.lookup(x), opts['rev'])
550

551
    if len(revs) > 2:
552
        raise Abort("too many revisions to diff")
553

554 555 556
    files = []
    for src, abs, rel in walk(repo, pats, opts):
        files.append(abs)
mpm@selenic.com's avatar
mpm@selenic.com committed
557
    dodiff(sys.stdout, ui, repo, files, *revs)
558

mpm@selenic.com's avatar
mpm@selenic.com committed
559
def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
560 561 562
    node = repo.lookup(changeset)
    prev, other = repo.changelog.parents(node)
    change = repo.changelog.read(node)
563

564 565 566 567
    fp = make_file(repo, repo.changelog, opts['output'],
                   node=node, total=total, seqno=seqno,
                   revwidth=revwidth)
    if fp != sys.stdout:
568
        ui.note("%s\n" % fp.name)
mpm@selenic.com's avatar
mpm@selenic.com committed
569

570 571 572 573
    fp.write("# HG changeset patch\n")
    fp.write("# User %s\n" % change[1])
    fp.write("# Node ID %s\n" % hg.hex(node))
    fp.write("# Parent  %s\n" % hg.hex(prev))
mpm@selenic.com's avatar
mpm@selenic.com committed
574
    if other != hg.nullid:
575 576 577
        fp.write("# Parent  %s\n" % hg.hex(other))
    fp.write(change[4].rstrip())
    fp.write("\n\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
578 579

    dodiff(fp, ui, repo, None, prev, node)
580
    if fp != sys.stdout: fp.close()
mpm@selenic.com's avatar
mpm@selenic.com committed
581 582 583

def export(ui, repo, *changesets, **opts):
    """dump the header and diffs for one or more changesets"""
584
    if not changesets:
585
        raise Abort("export requires at least one changeset")
mpm@selenic.com's avatar
mpm@selenic.com committed
586 587 588 589
    seqno = 0
    revs = list(revrange(ui, repo, changesets))
    total = len(revs)
    revwidth = max(len(revs[0]), len(revs[-1]))
590
    ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
mpm@selenic.com's avatar
mpm@selenic.com committed
591 592 593
    for cset in revs:
        seqno += 1
        doexport(ui, repo, cset, seqno, total, revwidth, opts)
594

595
def forget(ui, repo, file1, *files):
596
    """don't add the specified files on the next commit"""
597
    repo.forget(relpath(repo, (file1,) + files))
598

599
def heads(ui, repo):
600
    """show current repository heads"""
601
    for n in repo.changelog.heads():
602
        show_changeset(ui, repo, changenode=n)
603

604 605
def identify(ui, repo):
    """print information about the working copy"""
mpm@selenic.com's avatar
mpm@selenic.com committed
606
    parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
607
    if not parents:
mpm@selenic.com's avatar
mpm@selenic.com committed
608
        ui.write("unknown\n")
609 610
        return

611
    hexfunc = ui.verbose and hg.hex or hg.short
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
612
    (c, a, d, u) = repo.changes()
613 614 615
    output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
                        (c or a or d) and "+" or "")]

616
    if not ui.quiet:
617 618 619 620
        # multiple tags for a single parent separated by '/'
        parenttags = ['/'.join(tags)
                      for tags in map(repo.nodetags, parents) if tags]
        # tags for multiple parents separated by ' + '
621 622
        if parenttags:
            output.append(' + '.join(parenttags))
623

624
    ui.write("%s\n" % ' '.join(output))
625

mpm@selenic.com's avatar
mpm@selenic.com committed
626 627 628 629 630
def import_(ui, repo, patch1, *patches, **opts):
    """import an ordered set of patches"""
    try:
        import psyco
        psyco.full()
631
    except ImportError:
mpm@selenic.com's avatar
mpm@selenic.com committed
632 633 634
        pass

    patches = (patch1,) + patches
635

mpm@selenic.com's avatar
mpm@selenic.com committed
636 637 638 639 640 641 642
    d = opts["base"]
    strip = opts["strip"]

    for patch in patches:
        ui.status("applying %s\n" % patch)
        pf = os.path.join(d, patch)

643
        message = []
644 645 646 647 648
        user = None
        hgpatch = False
        for line in file(pf):
            line = line.rstrip()
            if line.startswith("--- ") or line.startswith("diff -r"):
649
                break
650 651 652 653
            elif hgpatch:
                # parse values when importing the result of an hg export
                if line.startswith("# User "):
                    user = line[7:]
654
                    ui.debug('User: %s\n' % user)
655
                elif not line.startswith("# ") and line:
656
                    message.append(line)
657 658 659 660
                    hgpatch = False
            elif line == '# HG changeset patch':
                hgpatch = True
            else:
661
                message.append(line)
662

663 664 665
        # make sure message isn't empty
        if not message:
            message = "imported patch %s\n" % patch
666
        else:
667 668
            message = "%s\n" % '\n'.join(message)
        ui.debug('message:\n%s\n' % message)
mpm@selenic.com's avatar
mpm@selenic.com committed
669 670 671 672 673

        f = os.popen("patch -p%d < %s" % (strip, pf))
        files = []
        for l in f.read().splitlines():
            l.rstrip('\r\n');
674
            ui.status("%s\n" % l)
675
            if l.startswith('patching file '):
mpm@selenic.com's avatar
mpm@selenic.com committed
676 677 678 679 680
                pf = l[14:]
                if pf not in files:
                    files.append(pf)
        patcherr = f.close()
        if patcherr:
681
            raise Abort("patch failed")
mpm@selenic.com's avatar
mpm@selenic.com committed
682 683 684

        if len(files) > 0:
            addremove(ui, repo, *files)
685
        repo.commit(files, message, user)
mpm@selenic.com's avatar
mpm@selenic.com committed
686

mpm@selenic.com's avatar
mpm@selenic.com committed
687 688
def init(ui, source=None):
    """create a new repository in the current directory"""
689 690

    if source:
691
        raise Abort("no longer supported: use \"hg clone\" instead")
692
    hg.repository(ui, ".", create=1)
693

Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
694 695
def locate(ui, repo, *pats, **opts):
    """locate files matching specific patterns"""
mpm@selenic.com's avatar
mpm@selenic.com committed
696
    end = '\n'
697
    if opts['print0']: end = '\0'
mpm@selenic.com's avatar
mpm@selenic.com committed
698 699

    for src, abs, rel in walk(repo, pats, opts, '(?:.*/|)'):
700
        if repo.dirstate.state(abs) == '?': continue
701
        if opts['fullpath']:
702 703 704
            ui.write(os.path.join(repo.root, abs), end)
        else:
            ui.write(rel, end)
Bryan O'Sullivan's avatar
Bryan O'Sullivan committed
705

706
def log(ui, repo, f=None, **opts):
707 708
    """show the revision history of the repository or a single file"""
    if f:
709 710
        files = relpath(repo, [f])
        filelog = repo.file(files[0])
711 712
        log = filelog
        lookup = filelog.lookup
713
    else:
714
        files = None
715 716 717 718 719 720 721 722 723 724 725 726 727
        filelog = None
        log = repo.changelog
        lookup = repo.lookup
    revlist = []
    revs = [log.rev(lookup(rev)) for rev in opts['rev']]
    while revs:
        if len(revs) == 1:
            revlist.append(revs.pop(0))
        else:
            a = revs.pop(0)
            b = revs.pop(0)
            off = a > b and -1 or 1
            revlist.extend(range(a, b + off, off))
728

729 730
    for i in revlist or range(log.count() - 1, -1, -1):
        show_changeset(ui, repo, filelog=filelog, rev=i)
731 732 733 734 735 736 737
        if opts['patch']:
            if filelog:
                filenode = filelog.node(i)
                i = filelog.linkrev(filenode)
            changenode = repo.changelog.node(i)
            prev, other = repo.changelog.parents(changenode)
            dodiff(sys.stdout, ui, repo, files, prev, changenode)
738
            ui.write("\n\n")
739

740
def manifest(ui, repo, rev=None):
mpm@selenic.com's avatar
mpm@selenic.com committed
741
    """output the latest or given revision of the project manifest"""
mpm@selenic.com's avatar
mpm@selenic.com committed
742
    if rev:
743 744 745 746 747
        try:
            # assume all revision numbers are for changesets
            n = repo.lookup(rev)
            change = repo.changelog.read(n)
            n = change[0]
748
        except hg.RepoError:
749
            n = repo.manifest.lookup(rev)
750 751
    else:
        n = repo.manifest.tip()
mpm@selenic.com's avatar
mpm@selenic.com committed
752
    m = repo.manifest.read(n)
753
    mf = repo.manifest.readflags(n)
mpm@selenic.com's avatar
mpm@selenic.com committed
754 755 756 757
    files = m.keys()
    files.sort()

    for f in files:
758
        ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
mpm@selenic.com's avatar
mpm@selenic.com committed
759

760 761 762 763
def parents(ui, repo, rev=None):
    """show the parents of the working dir or revision"""
    if rev:
        p = repo.changelog.parents(repo.lookup(rev))
764 765 766 767 768
    else:
        p = repo.dirstate.parents()

    for n in p:
        if n != hg.nullid:
769
            show_changeset(ui, repo, changenode=n)
770

mpm@selenic.com's avatar
mpm@selenic.com committed
771
def pull(ui, repo, source="default", **opts):
772
    """pull changes from the specified source"""
773
    source = ui.expandpath(source)
mpm@selenic.com's avatar
mpm@selenic.com committed
774
    ui.status('pulling from %s\n' % (source))
775

776
    other = hg.repository(ui, source)
Matt Mackall's avatar
Matt Mackall committed
777 778
    r = repo.pull(other)
    if not r:
mpm@selenic.com's avatar
mpm@selenic.com committed
779 780
        if opts['update']:
            return update(ui, repo)
mpm@selenic.com's avatar
mpm@selenic.com committed
781
        else:
mpm@selenic.com's avatar
mpm@selenic.com committed
782 783 784
            ui.status("(run 'hg update' to get a working copy)\n")

    return r
785

786
def push(ui, repo, dest="default-push"):
mpm@selenic.com's avatar
mpm@selenic.com committed
787
    """push changes to the specified destination"""
<