comparison mercurial/commands.py @ 2873: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
2872:5dd6631c8238 2873: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