comparison mercurial/commands.py @ 1060:e453d2053b2e

Merge from BOS, fix help
author mpm@selenic.com
date Fri, 26 Aug 2005 01:56:49 -0700
parents 5139ee9bfc38 4eab07ef66e2
children fed8d078840b
comparison
equal deleted inserted replaced
1055:ea465485f54f 1060:e453d2053b2e
43 return files, matchfn, walk() 43 return files, matchfn, walk()
44 44
45 def walk(repo, pats, opts, head = ''): 45 def walk(repo, pats, opts, head = ''):
46 files, matchfn, results = makewalk(repo, pats, opts, head) 46 files, matchfn, results = makewalk(repo, pats, opts, head)
47 for r in results: yield r 47 for r in results: yield r
48
49 def walkchangerevs(ui, repo, cwd, pats, opts):
50 # This code most commonly needs to iterate backwards over the
51 # history it is interested in. Doing so has awful
52 # (quadratic-looking) performance, so we use iterators in a
53 # "windowed" way. Walk forwards through a window of revisions,
54 # yielding them in the desired order, and walk the windows
55 # themselves backwards.
56 cwd = repo.getcwd()
57 if not pats and cwd:
58 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
59 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
60 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
61 pats, opts)
62 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
63 wanted = {}
64 slowpath = anypats
65 window = 300
66 fncache = {}
67 if not slowpath and not files:
68 # No files, no patterns. Display all revs.
69 wanted = dict(zip(revs, revs))
70 if not slowpath:
71 # Only files, no patterns. Check the history of each file.
72 def filerevgen(filelog):
73 for i in xrange(filelog.count() - 1, -1, -window):
74 revs = []
75 for j in xrange(max(0, i - window), i + 1):
76 revs.append(filelog.linkrev(filelog.node(j)))
77 revs.reverse()
78 for rev in revs:
79 yield rev
80
81 minrev, maxrev = min(revs), max(revs)
82 for file in files:
83 filelog = repo.file(file)
84 # A zero count may be a directory or deleted file, so
85 # try to find matching entries on the slow path.
86 if filelog.count() == 0:
87 slowpath = True
88 break
89 for rev in filerevgen(filelog):
90 if rev <= maxrev:
91 if rev < minrev: break
92 fncache.setdefault(rev, [])
93 fncache[rev].append(file)
94 wanted[rev] = 1
95 if slowpath:
96 # The slow path checks files modified in every changeset.
97 def changerevgen():
98 for i in xrange(repo.changelog.count() - 1, -1, -window):
99 for j in xrange(max(0, i - window), i + 1):
100 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
101
102 for rev, changefiles in changerevgen():
103 matches = filter(matchfn, changefiles)
104 if matches:
105 fncache[rev] = matches
106 wanted[rev] = 1
107
108 for i in xrange(0, len(revs), window):
109 yield 'window', revs[0] < revs[-1], revs[-1]
110 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
111 if rev in wanted]
112 srevs = list(nrevs)
113 srevs.sort()
114 for rev in srevs:
115 fns = fncache.get(rev)
116 if not fns:
117 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
118 fns = filter(matchfn, fns)
119 yield 'add', rev, fns
120 for rev in nrevs:
121 yield 'iter', rev, None
48 122
49 revrangesep = ':' 123 revrangesep = ':'
50 124
51 def revrange(ui, repo, revs, revlog=None): 125 def revrange(ui, repo, revs, revlog=None):
52 if revlog is None: 126 if revlog is None:
643 717
644 def debugwalk(ui, repo, *pats, **opts): 718 def debugwalk(ui, repo, *pats, **opts):
645 """show how files match on given patterns""" 719 """show how files match on given patterns"""
646 items = list(walk(repo, pats, opts)) 720 items = list(walk(repo, pats, opts))
647 if not items: return 721 if not items: return
648 fmt = '%%s %%-%ds %%-%ds %%s' % ( 722 fmt = '%%s %%-%ds %%-%ds %%s\n' % (
649 max([len(abs) for (src, abs, rel, exact) in items]), 723 max([len(abs) for (src, abs, rel, exact) in items]),
650 max([len(rel) for (src, abs, rel, exact) in items])) 724 max([len(rel) for (src, abs, rel, exact) in items]))
651 exactly = {True: 'exact', False: ''} 725 exactly = {True: 'exact', False: ''}
652 for src, abs, rel, exact in items: 726 for src, abs, rel, exact in items:
653 print fmt % (src, abs, rel, exactly[exact]) 727 ui.write(fmt % (src, abs, rel, exactly[exact]))
654 728
655 def diff(ui, repo, *pats, **opts): 729 def diff(ui, repo, *pats, **opts):
656 """diff working directory (or selected files)""" 730 """diff working directory (or selected files)"""
657 node1, node2 = None, None 731 node1, node2 = None, None
658 revs = [repo.lookup(x) for x in opts['rev']] 732 revs = [repo.lookup(x) for x in opts['rev']]
716 for src, abs, rel, exact in walk(repo, pats, opts): 790 for src, abs, rel, exact in walk(repo, pats, opts):
717 if repo.dirstate.state(abs) == 'a': 791 if repo.dirstate.state(abs) == 'a':
718 forget.append(abs) 792 forget.append(abs)
719 if not exact: ui.status('forgetting ', rel, '\n') 793 if not exact: ui.status('forgetting ', rel, '\n')
720 repo.forget(forget) 794 repo.forget(forget)
795
796 def grep(ui, repo, pattern = None, *pats, **opts):
797 """search for a pattern in specified files and revisions"""
798 if pattern is None: pattern = opts['regexp']
799 if not pattern: raise util.Abort('no pattern to search for')
800 reflags = 0
801 if opts['ignore_case']: reflags |= re.I
802 regexp = re.compile(pattern, reflags)
803 sep, end = ':', '\n'
804 if opts['null'] or opts['print0']: sep = end = '\0'
805
806 fcache = {}
807 def getfile(fn):
808 if fn not in fcache:
809 fcache[fn] = repo.file(fn)
810 return fcache[fn]
811
812 def matchlines(body):
813 begin = 0
814 linenum = 0
815 while True:
816 match = regexp.search(body, begin)
817 if not match: break
818 mstart, mend = match.span()
819 linenum += body.count('\n', begin, mstart) + 1
820 lstart = body.rfind('\n', begin, mstart) + 1 or begin
821 lend = body.find('\n', mend)
822 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
823 begin = lend + 1
824
825 class linestate:
826 def __init__(self, line, linenum, colstart, colend):
827 self.line = line
828 self.linenum = linenum
829 self.colstart = colstart
830 self.colend = colend
831 def __eq__(self, other): return self.line == other.line
832 def __hash__(self): return hash(self.line)
833
834 matches = {}
835 def grepbody(fn, rev, body):
836 matches[rev].setdefault(fn, {})
837 m = matches[rev][fn]
838 for lnum, cstart, cend, line in matchlines(body):
839 s = linestate(line, lnum, cstart, cend)
840 m[s] = s
841
842 prev = {}
843 def display(fn, rev, states, prevstates):
844 diff = list(set(states).symmetric_difference(set(prevstates)))
845 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
846 for l in diff:
847 if incrementing:
848 change = ((l in prevstates) and '-') or '+'
849 r = rev
850 else:
851 change = ((l in states) and '-') or '+'
852 r = prev[fn]
853 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
854
855 fstate = {}
856 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
857 if st == 'window':
858 incrementing = rev
859 matches.clear()
860 elif st == 'add':
861 change = repo.changelog.read(repo.lookup(str(rev)))
862 mf = repo.manifest.read(change[0])
863 matches[rev] = {}
864 for fn in fns:
865 fstate.setdefault(fn, {})
866 try:
867 grepbody(fn, rev, getfile(fn).read(mf[fn]))
868 except KeyError:
869 pass
870 elif st == 'iter':
871 states = matches[rev].items()
872 states.sort()
873 for fn, m in states:
874 if incrementing or fstate[fn]:
875 display(fn, rev, m, fstate[fn])
876 fstate[fn] = m
877 prev[fn] = rev
878
879 if not incrementing:
880 fstate = fstate.items()
881 fstate.sort()
882 for fn, state in fstate:
883 display(fn, rev, {}, state)
721 884
722 def heads(ui, repo, **opts): 885 def heads(ui, repo, **opts):
723 """show current repository heads""" 886 """show current repository heads"""
724 heads = repo.changelog.heads() 887 heads = repo.changelog.heads()
725 br = None 888 br = None
846 else: 1009 else:
847 ui.write(rel, end) 1010 ui.write(rel, end)
848 1011
849 def log(ui, repo, *pats, **opts): 1012 def log(ui, repo, *pats, **opts):
850 """show revision history of entire repository or files""" 1013 """show revision history of entire repository or files"""
851 # This code most commonly needs to iterate backwards over the 1014 class dui:
852 # history it is interested in. This has awful (quadratic-looking) 1015 # Implement and delegate some ui protocol. Save hunks of
853 # performance, so we use iterators that walk forwards through 1016 # output for later display in the desired order.
854 # windows of revisions, yielding revisions in reverse order, while 1017 def __init__(self, ui):
855 # walking the windows backwards. 1018 self.ui = ui
1019 self.hunk = {}
1020 def bump(self, rev):
1021 self.rev = rev
1022 self.hunk[rev] = []
1023 def note(self, *args):
1024 if self.verbose: self.write(*args)
1025 def status(self, *args):
1026 if not self.quiet: self.write(*args)
1027 def write(self, *args):
1028 self.hunk[self.rev].append(args)
1029 def __getattr__(self, key):
1030 return getattr(self.ui, key)
856 cwd = repo.getcwd() 1031 cwd = repo.getcwd()
857 if not pats and cwd: 1032 if not pats and cwd:
858 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] 1033 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
859 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] 1034 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
860 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', 1035 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
861 pats, opts) 1036 opts):
862 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) 1037 if st == 'window':
863 wanted = {}
864 slowpath = anypats
865 window = 300
866 if not slowpath and not files:
867 # No files, no patterns. Display all revs.
868 wanted = dict(zip(revs, revs))
869 if not slowpath:
870 # Only files, no patterns. Check the history of each file.
871 def filerevgen(filelog):
872 for i in xrange(filelog.count() - 1, -1, -window):
873 revs = []
874 for j in xrange(max(0, i - window), i + 1):
875 revs.append(filelog.linkrev(filelog.node(j)))
876 revs.reverse()
877 for rev in revs:
878 yield rev
879
880 minrev, maxrev = min(revs), max(revs)
881 for filelog in map(repo.file, files):
882 # A zero count may be a directory or deleted file, so
883 # try to find matching entries on the slow path.
884 if filelog.count() == 0:
885 slowpath = True
886 break
887 for rev in filerevgen(filelog):
888 if rev <= maxrev:
889 if rev < minrev: break
890 wanted[rev] = 1
891 if slowpath:
892 # The slow path checks files modified in every changeset.
893 def mfrevgen():
894 for i in xrange(repo.changelog.count() - 1, -1, -window):
895 for j in xrange(max(0, i - window), i + 1):
896 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
897
898 for rev, mf in mfrevgen():
899 if filter(matchfn, mf):
900 wanted[rev] = 1
901
902 def changerevgen():
903 class dui:
904 # Implement and delegate some ui protocol. Save hunks of
905 # output for later display in the desired order.
906 def __init__(self, ui):
907 self.ui = ui
908 self.hunk = {}
909 def bump(self, rev):
910 self.rev = rev
911 self.hunk[rev] = []
912 def note(self, *args):
913 if self.verbose: self.write(*args)
914 def status(self, *args):
915 if not self.quiet: self.write(*args)
916 def write(self, *args):
917 self.hunk[self.rev].append(args)
918 def __getattr__(self, key):
919 return getattr(self.ui, key)
920 for i in xrange(0, len(revs), window):
921 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
922 if rev in wanted]
923 srevs = list(nrevs)
924 srevs.sort()
925 du = dui(ui) 1038 du = dui(ui)
926 for rev in srevs: 1039 elif st == 'add':
927 du.bump(rev) 1040 du.bump(rev)
928 yield rev, du 1041 show_changeset(du, repo, rev)
929 for rev in nrevs: 1042 if opts['patch']:
930 for args in du.hunk[rev]: 1043 changenode = repo.changelog.node(rev)
931 ui.write(*args) 1044 prev, other = repo.changelog.parents(changenode)
932 1045 dodiff(du, du, repo, prev, changenode, fns)
933 for rev, dui in changerevgen(): 1046 du.write("\n\n")
934 show_changeset(dui, repo, rev) 1047 elif st == 'iter':
935 if opts['patch']: 1048 for args in du.hunk[rev]:
936 changenode = repo.changelog.node(rev) 1049 ui.write(*args)
937 prev, other = repo.changelog.parents(changenode)
938 dodiff(dui, dui, repo, prev, changenode, files)
939 dui.write("\n\n")
940 1050
941 def manifest(ui, repo, rev=None): 1051 def manifest(ui, repo, rev=None):
942 """output the latest or given revision of the project manifest""" 1052 """output the latest or given revision of the project manifest"""
943 if rev: 1053 if rev:
944 try: 1054 try:
1408 "forget": 1518 "forget":
1409 (forget, 1519 (forget,
1410 [('I', 'include', [], 'include path in search'), 1520 [('I', 'include', [], 'include path in search'),
1411 ('X', 'exclude', [], 'exclude path from search')], 1521 ('X', 'exclude', [], 'exclude path from search')],
1412 "hg forget [OPTION]... FILE..."), 1522 "hg forget [OPTION]... FILE..."),
1523 "grep": (grep,
1524 [('0', 'print0', None, 'terminate file names with NUL'),
1525 ('I', 'include', [], 'include path in search'),
1526 ('X', 'exclude', [], 'include path in search'),
1527 ('Z', 'null', None, 'terminate file names with NUL'),
1528 ('a', 'all-revs', '', 'search all revs'),
1529 ('e', 'regexp', '', 'pattern to search for'),
1530 ('f', 'full-path', None, 'print complete paths'),
1531 ('i', 'ignore-case', None, 'ignore case when matching'),
1532 ('l', 'files-with-matches', None, 'print names of files with matches'),
1533 ('n', 'line-number', '', 'print line numbers'),
1534 ('r', 'rev', [], 'search in revision rev'),
1535 ('s', 'no-messages', None, 'do not print error messages'),
1536 ('v', 'invert-match', None, 'select non-matching lines')],
1537 "hg grep [options] [pat] [files]"),
1413 "heads": 1538 "heads":
1414 (heads, 1539 (heads,
1415 [('b', 'branches', None, 'find branch info')], 1540 [('b', 'branches', None, 'find branch info')],
1416 'hg heads [-b]'), 1541 'hg heads [-b]'),
1417 "help": (help_, [], 'hg help [COMMAND]'), 1542 "help": (help_, [], 'hg help [COMMAND]'),