Mercurial > public > mercurial-scm > hg
comparison mercurial/commands.py @ 2874:4ec58b157265
refactor text diff/patch code.
rename commands.dodiff to patch.diff.
rename commands.doexport to patch.export.
move some functions from commands to new mercurial.cmdutil module.
turn list of diff options into mdiff.diffopts class.
patch.diff and patch.export now has clean api for call from 3rd party
python code.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Sat, 12 Aug 2006 16:13:27 -0700 |
parents | ffa2be02c4e5 |
children | 3d6efcbbd1c9 |
comparison
equal
deleted
inserted
replaced
2873:5dd6631c8238 | 2874:4ec58b157265 |
---|---|
8 from demandload import demandload | 8 from demandload import demandload |
9 from node import * | 9 from node import * |
10 from i18n import gettext as _ | 10 from i18n import gettext as _ |
11 demandload(globals(), "os re sys signal shutil imp urllib pdb") | 11 demandload(globals(), "os re sys signal shutil imp urllib pdb") |
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") | 12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") |
13 demandload(globals(), "fnmatch mdiff difflib patch random signal tempfile time") | 13 demandload(globals(), "fnmatch difflib patch random signal tempfile time") |
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2") | 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2") |
15 demandload(globals(), "archival cStringIO changegroup") | 15 demandload(globals(), "archival cStringIO changegroup") |
16 demandload(globals(), "hgweb.server sshserver") | 16 demandload(globals(), "cmdutil hgweb.server sshserver") |
17 | 17 |
18 class UnknownCommand(Exception): | 18 class UnknownCommand(Exception): |
19 """Exception raised if command is not in the command table.""" | 19 """Exception raised if command is not in the command table.""" |
20 class AmbiguousCommand(Exception): | 20 class AmbiguousCommand(Exception): |
21 """Exception raised if command shortcut matches more than one command.""" | 21 """Exception raised if command shortcut matches more than one command.""" |
22 | 22 |
23 def bail_if_changed(repo): | 23 def bail_if_changed(repo): |
24 modified, added, removed, deleted, unknown = repo.changes() | 24 modified, added, removed, deleted, unknown = repo.changes() |
25 if modified or added or removed or deleted: | 25 if modified or added or removed or deleted: |
26 raise util.Abort(_("outstanding uncommitted changes")) | 26 raise util.Abort(_("outstanding uncommitted changes")) |
27 | |
28 def filterfiles(filters, files): | |
29 l = [x for x in files if x in filters] | |
30 | |
31 for t in filters: | |
32 if t and t[-1] != "/": | |
33 t += "/" | |
34 l += [x for x in files if x.startswith(t)] | |
35 return l | |
36 | 27 |
37 def relpath(repo, args): | 28 def relpath(repo, args): |
38 cwd = repo.getcwd() | 29 cwd = repo.getcwd() |
39 if cwd: | 30 if cwd: |
40 return [util.normpath(os.path.join(cwd, x)) for x in args] | 31 return [util.normpath(os.path.join(cwd, x)) for x in args] |
342 if rev in seen: | 333 if rev in seen: |
343 continue | 334 continue |
344 seen[rev] = 1 | 335 seen[rev] = 1 |
345 yield str(rev) | 336 yield str(rev) |
346 | 337 |
347 def make_filename(repo, pat, node, | |
348 total=None, seqno=None, revwidth=None, pathname=None): | |
349 node_expander = { | |
350 'H': lambda: hex(node), | |
351 'R': lambda: str(repo.changelog.rev(node)), | |
352 'h': lambda: short(node), | |
353 } | |
354 expander = { | |
355 '%': lambda: '%', | |
356 'b': lambda: os.path.basename(repo.root), | |
357 } | |
358 | |
359 try: | |
360 if node: | |
361 expander.update(node_expander) | |
362 if node and revwidth is not None: | |
363 expander['r'] = (lambda: | |
364 str(repo.changelog.rev(node)).zfill(revwidth)) | |
365 if total is not None: | |
366 expander['N'] = lambda: str(total) | |
367 if seqno is not None: | |
368 expander['n'] = lambda: str(seqno) | |
369 if total is not None and seqno is not None: | |
370 expander['n'] = lambda:str(seqno).zfill(len(str(total))) | |
371 if pathname is not None: | |
372 expander['s'] = lambda: os.path.basename(pathname) | |
373 expander['d'] = lambda: os.path.dirname(pathname) or '.' | |
374 expander['p'] = lambda: pathname | |
375 | |
376 newname = [] | |
377 patlen = len(pat) | |
378 i = 0 | |
379 while i < patlen: | |
380 c = pat[i] | |
381 if c == '%': | |
382 i += 1 | |
383 c = pat[i] | |
384 c = expander[c]() | |
385 newname.append(c) | |
386 i += 1 | |
387 return ''.join(newname) | |
388 except KeyError, inst: | |
389 raise util.Abort(_("invalid format spec '%%%s' in output file name"), | |
390 inst.args[0]) | |
391 | |
392 def make_file(repo, pat, node=None, | |
393 total=None, seqno=None, revwidth=None, mode='wb', pathname=None): | |
394 if not pat or pat == '-': | |
395 return 'w' in mode and sys.stdout or sys.stdin | |
396 if hasattr(pat, 'write') and 'w' in mode: | |
397 return pat | |
398 if hasattr(pat, 'read') and 'r' in mode: | |
399 return pat | |
400 return open(make_filename(repo, pat, node, total, seqno, revwidth, | |
401 pathname), | |
402 mode) | |
403 | |
404 def write_bundle(cg, filename=None, compress=True): | 338 def write_bundle(cg, filename=None, compress=True): |
405 """Write a bundle file and return its filename. | 339 """Write a bundle file and return its filename. |
406 | 340 |
407 Existing files will not be overwritten. | 341 Existing files will not be overwritten. |
408 If no filename is specified, a temporary file is created. | 342 If no filename is specified, a temporary file is created. |
450 finally: | 384 finally: |
451 if fh is not None: | 385 if fh is not None: |
452 fh.close() | 386 fh.close() |
453 if cleanup is not None: | 387 if cleanup is not None: |
454 os.unlink(cleanup) | 388 os.unlink(cleanup) |
455 | |
456 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, | |
457 changes=None, text=False, opts={}): | |
458 if not node1: | |
459 node1 = repo.dirstate.parents()[0] | |
460 # reading the data for node1 early allows it to play nicely | |
461 # with repo.changes and the revlog cache. | |
462 change = repo.changelog.read(node1) | |
463 mmap = repo.manifest.read(change[0]) | |
464 date1 = util.datestr(change[2]) | |
465 | |
466 if not changes: | |
467 changes = repo.changes(node1, node2, files, match=match) | |
468 modified, added, removed, deleted, unknown = changes | |
469 if files: | |
470 modified, added, removed = map(lambda x: filterfiles(files, x), | |
471 (modified, added, removed)) | |
472 | |
473 if not modified and not added and not removed: | |
474 return | |
475 | |
476 if node2: | |
477 change = repo.changelog.read(node2) | |
478 mmap2 = repo.manifest.read(change[0]) | |
479 _date2 = util.datestr(change[2]) | |
480 def date2(f): | |
481 return _date2 | |
482 def read(f): | |
483 return repo.file(f).read(mmap2[f]) | |
484 else: | |
485 tz = util.makedate()[1] | |
486 _date2 = util.datestr() | |
487 def date2(f): | |
488 try: | |
489 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz)) | |
490 except OSError, err: | |
491 if err.errno != errno.ENOENT: raise | |
492 return _date2 | |
493 def read(f): | |
494 return repo.wread(f) | |
495 | |
496 if ui.quiet: | |
497 r = None | |
498 else: | |
499 hexfunc = ui.verbose and hex or short | |
500 r = [hexfunc(node) for node in [node1, node2] if node] | |
501 | |
502 diffopts = ui.diffopts() | |
503 showfunc = opts.get('show_function') or diffopts['showfunc'] | |
504 ignorews = opts.get('ignore_all_space') or diffopts['ignorews'] | |
505 ignorewsamount = opts.get('ignore_space_change') or \ | |
506 diffopts['ignorewsamount'] | |
507 ignoreblanklines = opts.get('ignore_blank_lines') or \ | |
508 diffopts['ignoreblanklines'] | |
509 | |
510 all = modified + added + removed | |
511 all.sort() | |
512 for f in all: | |
513 to = None | |
514 tn = None | |
515 if f in mmap: | |
516 to = repo.file(f).read(mmap[f]) | |
517 if f not in removed: | |
518 tn = read(f) | |
519 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text, | |
520 showfunc=showfunc, ignorews=ignorews, | |
521 ignorewsamount=ignorewsamount, | |
522 ignoreblanklines=ignoreblanklines)) | |
523 | 389 |
524 def trimuser(ui, name, rev, revcache): | 390 def trimuser(ui, name, rev, revcache): |
525 """trim the name of the user who committed a change""" | 391 """trim the name of the user who committed a change""" |
526 user = revcache.get(rev) | 392 user = revcache.get(rev) |
527 if user is None: | 393 if user is None: |
920 node, p2 = repo.dirstate.parents() | 786 node, p2 = repo.dirstate.parents() |
921 if p2 != nullid: | 787 if p2 != nullid: |
922 raise util.Abort(_('uncommitted merge - please provide a ' | 788 raise util.Abort(_('uncommitted merge - please provide a ' |
923 'specific revision')) | 789 'specific revision')) |
924 | 790 |
925 dest = make_filename(repo, dest, node) | 791 dest = cmdutil.make_filename(repo, dest, node) |
926 if os.path.realpath(dest) == repo.root: | 792 if os.path.realpath(dest) == repo.root: |
927 raise util.Abort(_('repository root cannot be destination')) | 793 raise util.Abort(_('repository root cannot be destination')) |
928 dummy, matchfn, dummy = matchpats(repo, [], opts) | 794 dummy, matchfn, dummy = matchpats(repo, [], opts) |
929 kind = opts.get('type') or 'files' | 795 kind = opts.get('type') or 'files' |
930 prefix = opts['prefix'] | 796 prefix = opts['prefix'] |
931 if dest == '-': | 797 if dest == '-': |
932 if kind == 'files': | 798 if kind == 'files': |
933 raise util.Abort(_('cannot archive plain files to stdout')) | 799 raise util.Abort(_('cannot archive plain files to stdout')) |
934 dest = sys.stdout | 800 dest = sys.stdout |
935 if not prefix: prefix = os.path.basename(repo.root) + '-%h' | 801 if not prefix: prefix = os.path.basename(repo.root) + '-%h' |
936 prefix = make_filename(repo, prefix, node) | 802 prefix = cmdutil.make_filename(repo, prefix, node) |
937 archival.archive(repo, dest, node, kind, not opts['no_decode'], | 803 archival.archive(repo, dest, node, kind, not opts['no_decode'], |
938 matchfn, prefix) | 804 matchfn, prefix) |
939 | 805 |
940 def backout(ui, repo, rev, **opts): | 806 def backout(ui, repo, rev, **opts): |
941 '''reverse effect of earlier changeset | 807 '''reverse effect of earlier changeset |
1036 %d dirname of file being printed, or '.' if in repo root | 902 %d dirname of file being printed, or '.' if in repo root |
1037 %p root-relative path name of file being printed | 903 %p root-relative path name of file being printed |
1038 """ | 904 """ |
1039 ctx = repo.changectx(opts['rev'] or "-1") | 905 ctx = repo.changectx(opts['rev'] or "-1") |
1040 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()): | 906 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()): |
1041 fp = make_file(repo, opts['output'], ctx.node(), pathname=abs) | 907 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs) |
1042 fp.write(ctx.filectx(abs).data()) | 908 fp.write(ctx.filectx(abs).data()) |
1043 | 909 |
1044 def clone(ui, source, dest=None, **opts): | 910 def clone(ui, source, dest=None, **opts): |
1045 """make a copy of an existing repository | 911 """make a copy of an existing repository |
1046 | 912 |
1505 """ | 1371 """ |
1506 node1, node2 = revpair(ui, repo, opts['rev']) | 1372 node1, node2 = revpair(ui, repo, opts['rev']) |
1507 | 1373 |
1508 fns, matchfn, anypats = matchpats(repo, pats, opts) | 1374 fns, matchfn, anypats = matchpats(repo, pats, opts) |
1509 | 1375 |
1510 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn, | 1376 patch.diff(repo, node1, node2, fns, match=matchfn, |
1511 text=opts['text'], opts=opts) | 1377 opts=ui.diffopts(opts)) |
1512 | |
1513 def doexport(ui, repo, changeset, seqno, total, revwidth, opts): | |
1514 node = repo.lookup(changeset) | |
1515 parents = [p for p in repo.changelog.parents(node) if p != nullid] | |
1516 if opts['switch_parent']: | |
1517 parents.reverse() | |
1518 prev = (parents and parents[0]) or nullid | |
1519 change = repo.changelog.read(node) | |
1520 | |
1521 fp = make_file(repo, opts['output'], node, total=total, seqno=seqno, | |
1522 revwidth=revwidth) | |
1523 if fp != sys.stdout: | |
1524 ui.note("%s\n" % fp.name) | |
1525 | |
1526 fp.write("# HG changeset patch\n") | |
1527 fp.write("# User %s\n" % change[1]) | |
1528 fp.write("# Date %d %d\n" % change[2]) | |
1529 fp.write("# Node ID %s\n" % hex(node)) | |
1530 fp.write("# Parent %s\n" % hex(prev)) | |
1531 if len(parents) > 1: | |
1532 fp.write("# Parent %s\n" % hex(parents[1])) | |
1533 fp.write(change[4].rstrip()) | |
1534 fp.write("\n\n") | |
1535 | |
1536 dodiff(fp, ui, repo, prev, node, text=opts['text']) | |
1537 if fp != sys.stdout: | |
1538 fp.close() | |
1539 | 1378 |
1540 def export(ui, repo, *changesets, **opts): | 1379 def export(ui, repo, *changesets, **opts): |
1541 """dump the header and diffs for one or more changesets | 1380 """dump the header and diffs for one or more changesets |
1542 | 1381 |
1543 Print the changeset header and diffs for one or more revisions. | 1382 Print the changeset header and diffs for one or more revisions. |
1564 With the --switch-parent option, the diff will be against the second | 1403 With the --switch-parent option, the diff will be against the second |
1565 parent. It can be useful to review a merge. | 1404 parent. It can be useful to review a merge. |
1566 """ | 1405 """ |
1567 if not changesets: | 1406 if not changesets: |
1568 raise util.Abort(_("export requires at least one changeset")) | 1407 raise util.Abort(_("export requires at least one changeset")) |
1569 seqno = 0 | |
1570 revs = list(revrange(ui, repo, changesets)) | 1408 revs = list(revrange(ui, repo, changesets)) |
1571 total = len(revs) | 1409 if len(revs) > 1: |
1572 revwidth = max(map(len, revs)) | 1410 ui.note(_('exporting patches:\n')) |
1573 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n") | 1411 else: |
1574 ui.note(msg) | 1412 ui.note(_('exporting patch:\n')) |
1575 for cset in revs: | 1413 patch.export(repo, map(repo.lookup, revs), template=opts['output'], |
1576 seqno += 1 | 1414 switch_parent=opts['switch_parent'], opts=ui.diffopts(opts)) |
1577 doexport(ui, repo, cset, seqno, total, revwidth, opts) | |
1578 | 1415 |
1579 def forget(ui, repo, *pats, **opts): | 1416 def forget(ui, repo, *pats, **opts): |
1580 """don't add the specified files on the next commit (DEPRECATED) | 1417 """don't add the specified files on the next commit (DEPRECATED) |
1581 | 1418 |
1582 (DEPRECATED) | 1419 (DEPRECATED) |
1961 if opts['no_merges'] and len(parents) == 2: | 1798 if opts['no_merges'] and len(parents) == 2: |
1962 continue | 1799 continue |
1963 displayer.show(changenode=n) | 1800 displayer.show(changenode=n) |
1964 if opts['patch']: | 1801 if opts['patch']: |
1965 prev = (parents and parents[0]) or nullid | 1802 prev = (parents and parents[0]) or nullid |
1966 dodiff(ui, ui, other, prev, n) | 1803 patch.diff(repo, other, prev, n) |
1967 ui.write("\n") | 1804 ui.write("\n") |
1968 finally: | 1805 finally: |
1969 if hasattr(other, 'close'): | 1806 if hasattr(other, 'close'): |
1970 other.close() | 1807 other.close() |
1971 if cleanup: | 1808 if cleanup: |
2112 br = repo.branchlookup([repo.changelog.node(rev)]) | 1949 br = repo.branchlookup([repo.changelog.node(rev)]) |
2113 | 1950 |
2114 displayer.show(rev, brinfo=br) | 1951 displayer.show(rev, brinfo=br) |
2115 if opts['patch']: | 1952 if opts['patch']: |
2116 prev = (parents and parents[0]) or nullid | 1953 prev = (parents and parents[0]) or nullid |
2117 dodiff(du, du, repo, prev, changenode, match=matchfn) | 1954 patch.diff(repo, prev, changenode, match=matchfn, fp=du) |
2118 du.write("\n\n") | 1955 du.write("\n\n") |
2119 elif st == 'iter': | 1956 elif st == 'iter': |
2120 if count == limit: break | 1957 if count == limit: break |
2121 if du.header[rev]: | 1958 if du.header[rev]: |
2122 for args in du.header[rev]: | 1959 for args in du.header[rev]: |
2193 if opts['no_merges'] and len(parents) == 2: | 2030 if opts['no_merges'] and len(parents) == 2: |
2194 continue | 2031 continue |
2195 displayer.show(changenode=n) | 2032 displayer.show(changenode=n) |
2196 if opts['patch']: | 2033 if opts['patch']: |
2197 prev = (parents and parents[0]) or nullid | 2034 prev = (parents and parents[0]) or nullid |
2198 dodiff(ui, ui, repo, prev, n) | 2035 patch.diff(repo, prev, n) |
2199 ui.write("\n") | 2036 ui.write("\n") |
2200 | 2037 |
2201 def parents(ui, repo, file_=None, rev=None, branches=None, **opts): | 2038 def parents(ui, repo, file_=None, rev=None, branches=None, **opts): |
2202 """show the parents of the working dir or revision | 2039 """show the parents of the working dir or revision |
2203 | 2040 |
2841 br = None | 2678 br = None |
2842 if opts['branches']: | 2679 if opts['branches']: |
2843 br = repo.branchlookup([n]) | 2680 br = repo.branchlookup([n]) |
2844 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br) | 2681 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br) |
2845 if opts['patch']: | 2682 if opts['patch']: |
2846 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n) | 2683 patch.diff(repo, repo.changelog.parents(n)[0], n) |
2847 | 2684 |
2848 def unbundle(ui, repo, fname, **opts): | 2685 def unbundle(ui, repo, fname, **opts): |
2849 """apply a changegroup file | 2686 """apply a changegroup file |
2850 | 2687 |
2851 Apply a compressed changegroup file generated by the bundle | 2688 Apply a compressed changegroup file generated by the bundle |