47 files, matchfn, results = makewalk(repo, pats, opts, head) |
47 files, matchfn, results = makewalk(repo, pats, opts, head) |
48 for r in results: |
48 for r in results: |
49 yield r |
49 yield r |
50 |
50 |
51 def walkchangerevs(ui, repo, cwd, pats, opts): |
51 def walkchangerevs(ui, repo, cwd, pats, opts): |
52 # This code most commonly needs to iterate backwards over the |
52 '''Iterate over files and the revs they changed in. |
53 # history it is interested in. Doing so has awful |
53 |
54 # (quadratic-looking) performance, so we use iterators in a |
54 Callers most commonly need to iterate backwards over the history |
55 # "windowed" way. Walk forwards through a window of revisions, |
55 it is interested in. Doing so has awful (quadratic-looking) |
56 # yielding them in the desired order, and walk the windows |
56 performance, so we use iterators in a "windowed" way. |
57 # themselves backwards. |
57 |
|
58 We walk a window of revisions in the desired order. Within the |
|
59 window, we first walk forwards to gather data, then in the desired |
|
60 order (usually backwards) to display it. |
|
61 |
|
62 This function returns an (iterator, getchange) pair. The |
|
63 getchange function returns the changelog entry for a numeric |
|
64 revision. The iterator yields 3-tuples. They will be of one of |
|
65 the following forms: |
|
66 |
|
67 "window", incrementing, lastrev: stepping through a window, |
|
68 positive if walking forwards through revs, last rev in the |
|
69 sequence iterated over - use to reset state for the current window |
|
70 |
|
71 "add", rev, fns: out-of-order traversal of the given file names |
|
72 fns, which changed during revision rev - use to gather data for |
|
73 possible display |
|
74 |
|
75 "iter", rev, None: in-order traversal of the revs earlier iterated |
|
76 over with "add" - use to display data''' |
58 cwd = repo.getcwd() |
77 cwd = repo.getcwd() |
59 if not pats and cwd: |
78 if not pats and cwd: |
60 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
61 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
62 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', |
81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', |
64 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) |
83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) |
65 wanted = {} |
84 wanted = {} |
66 slowpath = anypats |
85 slowpath = anypats |
67 window = 300 |
86 window = 300 |
68 fncache = {} |
87 fncache = {} |
|
88 |
|
89 chcache = {} |
|
90 def getchange(rev): |
|
91 ch = chcache.get(rev) |
|
92 if ch is None: |
|
93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev))) |
|
94 return ch |
|
95 |
69 if not slowpath and not files: |
96 if not slowpath and not files: |
70 # No files, no patterns. Display all revs. |
97 # No files, no patterns. Display all revs. |
71 wanted = dict(zip(revs, revs)) |
98 wanted = dict(zip(revs, revs)) |
72 if not slowpath: |
99 if not slowpath: |
73 # Only files, no patterns. Check the history of each file. |
100 # Only files, no patterns. Check the history of each file. |
98 if slowpath: |
125 if slowpath: |
99 # The slow path checks files modified in every changeset. |
126 # The slow path checks files modified in every changeset. |
100 def changerevgen(): |
127 def changerevgen(): |
101 for i in xrange(repo.changelog.count() - 1, -1, -window): |
128 for i in xrange(repo.changelog.count() - 1, -1, -window): |
102 for j in xrange(max(0, i - window), i + 1): |
129 for j in xrange(max(0, i - window), i + 1): |
103 yield j, repo.changelog.read(repo.lookup(str(j)))[3] |
130 yield j, getchange(j)[3] |
104 |
131 |
105 for rev, changefiles in changerevgen(): |
132 for rev, changefiles in changerevgen(): |
106 matches = filter(matchfn, changefiles) |
133 matches = filter(matchfn, changefiles) |
107 if matches: |
134 if matches: |
108 fncache[rev] = matches |
135 fncache[rev] = matches |
109 wanted[rev] = 1 |
136 wanted[rev] = 1 |
110 |
137 |
111 for i in xrange(0, len(revs), window): |
138 def iterate(): |
112 yield 'window', revs[0] < revs[-1], revs[-1] |
139 for i in xrange(0, len(revs), window): |
113 nrevs = [rev for rev in revs[i:min(i+window, len(revs))] |
140 yield 'window', revs[0] < revs[-1], revs[-1] |
114 if rev in wanted] |
141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))] |
115 srevs = list(nrevs) |
142 if rev in wanted] |
116 srevs.sort() |
143 srevs = list(nrevs) |
117 for rev in srevs: |
144 srevs.sort() |
118 fns = fncache.get(rev) |
145 for rev in srevs: |
119 if not fns: |
146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3]) |
120 fns = repo.changelog.read(repo.lookup(str(rev)))[3] |
147 yield 'add', rev, fns |
121 fns = filter(matchfn, fns) |
148 for rev in nrevs: |
122 yield 'add', rev, fns |
149 yield 'iter', rev, None |
123 for rev in nrevs: |
150 return iterate(), getchange |
124 yield 'iter', rev, None |
|
125 |
151 |
126 revrangesep = ':' |
152 revrangesep = ':' |
127 |
153 |
128 def revrange(ui, repo, revs, revlog=None): |
154 def revrange(ui, repo, revs, revlog=None): |
129 """Yield revision as strings from a list of revision specifications.""" |
155 """Yield revision as strings from a list of revision specifications.""" |
148 try: |
174 try: |
149 num = revlog.rev(revlog.lookup(val)) |
175 num = revlog.rev(revlog.lookup(val)) |
150 except KeyError: |
176 except KeyError: |
151 raise util.Abort('invalid revision identifier %s', val) |
177 raise util.Abort('invalid revision identifier %s', val) |
152 return num |
178 return num |
|
179 seen = {} |
153 for spec in revs: |
180 for spec in revs: |
154 if spec.find(revrangesep) >= 0: |
181 if spec.find(revrangesep) >= 0: |
155 start, end = spec.split(revrangesep, 1) |
182 start, end = spec.split(revrangesep, 1) |
156 start = fix(start, 0) |
183 start = fix(start, 0) |
157 end = fix(end, revcount - 1) |
184 end = fix(end, revcount - 1) |
158 step = start > end and -1 or 1 |
185 step = start > end and -1 or 1 |
159 for rev in xrange(start, end+step, step): |
186 for rev in xrange(start, end+step, step): |
|
187 if rev in seen: continue |
|
188 seen[rev] = 1 |
160 yield str(rev) |
189 yield str(rev) |
161 else: |
190 else: |
162 yield str(fix(spec, None)) |
191 rev = fix(spec, None) |
|
192 if rev in seen: continue |
|
193 seen[rev] = 1 |
|
194 yield str(rev) |
163 |
195 |
164 def make_filename(repo, r, pat, node=None, |
196 def make_filename(repo, r, pat, node=None, |
165 total=None, seqno=None, revwidth=None): |
197 total=None, seqno=None, revwidth=None): |
166 node_expander = { |
198 node_expander = { |
167 'H': lambda: hex(node), |
199 'H': lambda: hex(node), |
263 for f in d: |
295 for f in d: |
264 to = repo.file(f).read(mmap[f]) |
296 to = repo.file(f).read(mmap[f]) |
265 tn = None |
297 tn = None |
266 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) |
298 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) |
267 |
299 |
|
300 def trimuser(ui, rev, name, revcache): |
|
301 """trim the name of the user who committed a change""" |
|
302 try: |
|
303 return revcache[rev] |
|
304 except KeyError: |
|
305 if not ui.verbose: |
|
306 f = name.find('@') |
|
307 if f >= 0: |
|
308 name = name[:f] |
|
309 f = name.find('<') |
|
310 if f >= 0: |
|
311 name = name[f+1:] |
|
312 revcache[rev] = name |
|
313 return name |
|
314 |
268 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None): |
315 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None): |
269 """show a single changeset or file revision""" |
316 """show a single changeset or file revision""" |
270 log = repo.changelog |
317 log = repo.changelog |
271 if changenode is None: |
318 if changenode is None: |
272 changenode = log.node(rev) |
319 changenode = log.node(rev) |
465 def annotate(ui, repo, *pats, **opts): |
512 def annotate(ui, repo, *pats, **opts): |
466 """show changeset information per file line""" |
513 """show changeset information per file line""" |
467 def getnode(rev): |
514 def getnode(rev): |
468 return short(repo.changelog.node(rev)) |
515 return short(repo.changelog.node(rev)) |
469 |
516 |
|
517 ucache = {} |
470 def getname(rev): |
518 def getname(rev): |
471 try: |
519 cl = repo.changelog.read(repo.changelog.node(rev)) |
472 return bcache[rev] |
520 return trimuser(ui, rev, cl[1], ucache) |
473 except KeyError: |
|
474 cl = repo.changelog.read(repo.changelog.node(rev)) |
|
475 name = cl[1] |
|
476 f = name.find('@') |
|
477 if f >= 0: |
|
478 name = name[:f] |
|
479 f = name.find('<') |
|
480 if f >= 0: |
|
481 name = name[f+1:] |
|
482 bcache[rev] = name |
|
483 return name |
|
484 |
521 |
485 if not pats: |
522 if not pats: |
486 raise util.Abort('at least one file name or pattern required') |
523 raise util.Abort('at least one file name or pattern required') |
487 |
524 |
488 bcache = {} |
|
489 opmap = [['user', getname], ['number', str], ['changeset', getnode]] |
525 opmap = [['user', getname], ['number', str], ['changeset', getnode]] |
490 if not opts['user'] and not opts['changeset']: |
526 if not opts['user'] and not opts['changeset']: |
491 opts['number'] = 1 |
527 opts['number'] = 1 |
492 |
528 |
493 if opts['rev']: |
529 if opts['rev']: |
868 for lnum, cstart, cend, line in matchlines(body): |
904 for lnum, cstart, cend, line in matchlines(body): |
869 s = linestate(line, lnum, cstart, cend) |
905 s = linestate(line, lnum, cstart, cend) |
870 m[s] = s |
906 m[s] = s |
871 |
907 |
872 prev = {} |
908 prev = {} |
|
909 ucache = {} |
873 def display(fn, rev, states, prevstates): |
910 def display(fn, rev, states, prevstates): |
874 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates))) |
911 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates))) |
875 diff.sort(lambda x, y: cmp(x.linenum, y.linenum)) |
912 diff.sort(lambda x, y: cmp(x.linenum, y.linenum)) |
876 counts = {'-': 0, '+': 0} |
913 counts = {'-': 0, '+': 0} |
|
914 filerevmatches = {} |
877 for l in diff: |
915 for l in diff: |
878 if incrementing or not opts['every_match']: |
916 if incrementing or not opts['every_match']: |
879 change = ((l in prevstates) and '-') or '+' |
917 change = ((l in prevstates) and '-') or '+' |
880 r = rev |
918 r = rev |
881 else: |
919 else: |
882 change = ((l in states) and '-') or '+' |
920 change = ((l in states) and '-') or '+' |
883 r = prev[fn] |
921 r = prev[fn] |
884 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line)) |
922 cols = [fn, str(rev)] |
|
923 if opts['line_number']: cols.append(str(l.linenum)) |
|
924 if opts['every_match']: cols.append(change) |
|
925 if opts['user']: cols.append(trimuser(ui, rev, getchange(rev)[1], |
|
926 ucache)) |
|
927 if opts['files_with_matches']: |
|
928 c = (fn, rev) |
|
929 if c in filerevmatches: continue |
|
930 filerevmatches[c] = 1 |
|
931 else: |
|
932 cols.append(l.line) |
|
933 ui.write(sep.join(cols), eol) |
885 counts[change] += 1 |
934 counts[change] += 1 |
886 return counts['+'], counts['-'] |
935 return counts['+'], counts['-'] |
887 |
936 |
888 fstate = {} |
937 fstate = {} |
889 skip = {} |
938 skip = {} |
890 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts): |
939 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts) |
|
940 count = 0 |
|
941 for st, rev, fns in changeiter: |
891 if st == 'window': |
942 if st == 'window': |
892 incrementing = rev |
943 incrementing = rev |
893 matches.clear() |
944 matches.clear() |
894 elif st == 'add': |
945 elif st == 'add': |
895 change = repo.changelog.read(repo.lookup(str(rev))) |
946 change = repo.changelog.read(repo.lookup(str(rev))) |
918 fstate = fstate.items() |
970 fstate = fstate.items() |
919 fstate.sort() |
971 fstate.sort() |
920 for fn, state in fstate: |
972 for fn, state in fstate: |
921 if fn in skip: continue |
973 if fn in skip: continue |
922 display(fn, rev, {}, state) |
974 display(fn, rev, {}, state) |
|
975 return (count == 0 and 1) or 0 |
923 |
976 |
924 def heads(ui, repo, **opts): |
977 def heads(ui, repo, **opts): |
925 """show current repository heads""" |
978 """show current repository heads""" |
926 heads = repo.changelog.heads() |
979 heads = repo.changelog.heads() |
927 br = None |
980 br = None |
1071 return getattr(self.ui, key) |
1124 return getattr(self.ui, key) |
1072 cwd = repo.getcwd() |
1125 cwd = repo.getcwd() |
1073 if not pats and cwd: |
1126 if not pats and cwd: |
1074 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
1127 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
1075 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
1128 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
1076 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats, |
1129 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '', |
1077 opts): |
1130 pats, opts) |
|
1131 for st, rev, fns in changeiter: |
1078 if st == 'window': |
1132 if st == 'window': |
1079 du = dui(ui) |
1133 du = dui(ui) |
1080 elif st == 'add': |
1134 elif st == 'add': |
1081 du.bump(rev) |
1135 du.bump(rev) |
1082 show_changeset(du, repo, rev) |
1136 show_changeset(du, repo, rev) |
1569 [('I', 'include', [], 'include path in search'), |
1623 [('I', 'include', [], 'include path in search'), |
1570 ('X', 'exclude', [], 'exclude path from search')], |
1624 ('X', 'exclude', [], 'exclude path from search')], |
1571 "hg forget [OPTION]... FILE..."), |
1625 "hg forget [OPTION]... FILE..."), |
1572 "grep": |
1626 "grep": |
1573 (grep, |
1627 (grep, |
1574 [('0', 'print0', None, 'end filenames with NUL'), |
1628 [('0', 'print0', None, 'end fields with NUL'), |
1575 ('I', 'include', [], 'include path in search'), |
1629 ('I', 'include', [], 'include path in search'), |
1576 ('X', 'exclude', [], 'include path in search'), |
1630 ('X', 'exclude', [], 'include path in search'), |
1577 ('e', 'every-match', None, 'print every match in file history'), |
1631 ('e', 'every-match', None, 'print every rev with matches'), |
1578 ('i', 'ignore-case', None, 'ignore case when matching'), |
1632 ('i', 'ignore-case', None, 'ignore case when matching'), |
1579 ('l', 'files-with-matches', None, 'print names of files with matches'), |
1633 ('l', 'files-with-matches', None, 'print names of files and revs with matches'), |
1580 ('n', 'line-number', '', 'print line numbers'), |
1634 ('n', 'line-number', None, 'print line numbers'), |
1581 ('r', 'rev', [], 'search in revision rev')], |
1635 ('r', 'rev', [], 'search in revision rev'), |
|
1636 ('u', 'user', None, 'print user who made change')], |
1582 "hg grep [OPTION]... PATTERN [FILE]..."), |
1637 "hg grep [OPTION]... PATTERN [FILE]..."), |
1583 "heads": |
1638 "heads": |
1584 (heads, |
1639 (heads, |
1585 [('b', 'branches', None, 'find branch info')], |
1640 [('b', 'branches', None, 'find branch info')], |
1586 'hg heads [-b]'), |
1641 'hg heads [-b]'), |