Mercurial > hg > mercurial-crew-with-dirclash
comparison mercurial/commands.py @ 1031:503aaf19a040
Rewrite log command. New version is faster and more featureful.
The original implementation of log walked backwards through history,
which had terrible behaviour. It took several minutes to view
complete kernel change history on a fast machine, for example.
The rewrite uses a windowed approach to walk hunks of history
forwards, while still giving results in reverse order. This reduces
run time from five minutes to five seconds on my system.
In addition, the rewrite uses our normal name handling mechanisms, so
you can run a command like "hg log net/ipv4/**.c" and get a useful
answer. It optimises for three different cases (no arguments, only
files, and anything goes), so it performs well in all circumstances
I've tested.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Wed, 24 Aug 2005 12:39:10 -0700 |
parents | 28e2f13ca7c4 |
children | 8dbbea5bc844 |
comparison
equal
deleted
inserted
replaced
1030:28e2f13ca7c4 | 1031:503aaf19a040 |
---|---|
33 return util.matcher(repo, cwd, pats or ['.'], opts.get('include'), | 33 return util.matcher(repo, cwd, pats or ['.'], opts.get('include'), |
34 opts.get('exclude'), head) | 34 opts.get('exclude'), head) |
35 | 35 |
36 def makewalk(repo, pats, opts, head = ''): | 36 def makewalk(repo, pats, opts, head = ''): |
37 cwd = repo.getcwd() | 37 cwd = repo.getcwd() |
38 files, matchfn = matchpats(repo, cwd, pats, opts, head) | 38 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head) |
39 exact = dict(zip(files, files)) | 39 exact = dict(zip(files, files)) |
40 def walk(): | 40 def walk(): |
41 for src, fn in repo.walk(files = files, match = matchfn): | 41 for src, fn in repo.walk(files = files, match = matchfn): |
42 yield src, fn, util.pathto(cwd, fn), fn in exact | 42 yield src, fn, util.pathto(cwd, fn), fn in exact |
43 return files, matchfn, walk() | 43 return files, matchfn, walk() |
84 end -= 1 | 84 end -= 1 |
85 step = -1 | 85 step = -1 |
86 for rev in xrange(start, end, step): | 86 for rev in xrange(start, end, step): |
87 yield str(rev) | 87 yield str(rev) |
88 else: | 88 else: |
89 yield spec | 89 yield str(fix(spec, None)) |
90 | 90 |
91 def make_filename(repo, r, pat, node=None, | 91 def make_filename(repo, r, pat, node=None, |
92 total=None, seqno=None, revwidth=None): | 92 total=None, seqno=None, revwidth=None): |
93 node_expander = { | 93 node_expander = { |
94 'H': lambda: hg.hex(node), | 94 'H': lambda: hg.hex(node), |
191 for f in d: | 191 for f in d: |
192 to = repo.file(f).read(mmap[f]) | 192 to = repo.file(f).read(mmap[f]) |
193 tn = None | 193 tn = None |
194 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) | 194 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) |
195 | 195 |
196 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None, brinfo=None): | 196 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None): |
197 """show a single changeset or file revision""" | 197 """show a single changeset or file revision""" |
198 changelog = repo.changelog | 198 log = repo.changelog |
199 if filelog: | 199 if changenode is None: |
200 log = filelog | 200 changenode = log.node(rev) |
201 filerev = rev | 201 elif not rev: |
202 node = filenode = filelog.node(filerev) | 202 rev = log.rev(changenode) |
203 changerev = filelog.linkrev(filenode) | |
204 changenode = changenode or changelog.node(changerev) | |
205 else: | |
206 log = changelog | |
207 changerev = rev | |
208 if changenode is None: | |
209 changenode = changelog.node(changerev) | |
210 elif not changerev: | |
211 rev = changerev = changelog.rev(changenode) | |
212 node = changenode | |
213 | 203 |
214 if ui.quiet: | 204 if ui.quiet: |
215 ui.write("%d:%s\n" % (rev, hg.short(node))) | 205 ui.write("%d:%s\n" % (rev, hg.short(changenode))) |
216 return | 206 return |
217 | 207 |
218 changes = changelog.read(changenode) | 208 changes = log.read(changenode) |
219 | 209 |
220 t, tz = changes[2].split(' ') | 210 t, tz = changes[2].split(' ') |
221 # a conversion tool was sticking non-integer offsets into repos | 211 # a conversion tool was sticking non-integer offsets into repos |
222 try: | 212 try: |
223 tz = int(tz) | 213 tz = int(tz) |
224 except ValueError: | 214 except ValueError: |
225 tz = 0 | 215 tz = 0 |
226 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36) | 216 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36) |
227 | 217 |
228 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p)) | 218 parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p)) |
229 for p in log.parents(node) | 219 for p in log.parents(changenode) |
230 if ui.debugflag or p != hg.nullid] | 220 if ui.debugflag or p != hg.nullid] |
231 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1: | 221 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1: |
232 parents = [] | 222 parents = [] |
233 | 223 |
234 if ui.verbose: | 224 if ui.verbose: |
235 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode))) | 225 ui.write("changeset: %d:%s\n" % (rev, hg.hex(changenode))) |
236 else: | 226 else: |
237 ui.write("changeset: %d:%s\n" % (changerev, hg.short(changenode))) | 227 ui.write("changeset: %d:%s\n" % (rev, hg.short(changenode))) |
238 | 228 |
239 for tag in repo.nodetags(changenode): | 229 for tag in repo.nodetags(changenode): |
240 ui.status("tag: %s\n" % tag) | 230 ui.status("tag: %s\n" % tag) |
241 for parent in parents: | 231 for parent in parents: |
242 ui.write("parent: %d:%s\n" % parent) | 232 ui.write("parent: %d:%s\n" % parent) |
243 if filelog: | |
244 ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode))) | |
245 | 233 |
246 if brinfo and changenode in brinfo: | 234 if brinfo and changenode in brinfo: |
247 br = brinfo[changenode] | 235 br = brinfo[changenode] |
248 ui.write("branch: %s\n" % " ".join(br)) | 236 ui.write("branch: %s\n" % " ".join(br)) |
249 | 237 |
251 hg.hex(changes[0]))) | 239 hg.hex(changes[0]))) |
252 ui.status("user: %s\n" % changes[1]) | 240 ui.status("user: %s\n" % changes[1]) |
253 ui.status("date: %s\n" % date) | 241 ui.status("date: %s\n" % date) |
254 | 242 |
255 if ui.debugflag: | 243 if ui.debugflag: |
256 files = repo.changes(changelog.parents(changenode)[0], changenode) | 244 files = repo.changes(log.parents(changenode)[0], changenode) |
257 for key, value in zip(["files:", "files+:", "files-:"], files): | 245 for key, value in zip(["files:", "files+:", "files-:"], files): |
258 if value: | 246 if value: |
259 ui.note("%-12s %s\n" % (key, " ".join(value))) | 247 ui.note("%-12s %s\n" % (key, " ".join(value))) |
260 else: | 248 else: |
261 ui.note("files: %s\n" % " ".join(changes[3])) | 249 ui.note("files: %s\n" % " ".join(changes[3])) |
558 addremove(ui, repo, *pats, **opts) | 546 addremove(ui, repo, *pats, **opts) |
559 cwd = repo.getcwd() | 547 cwd = repo.getcwd() |
560 if not pats and cwd: | 548 if not pats and cwd: |
561 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] | 549 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
562 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] | 550 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
563 fns, match = matchpats(repo, (pats and repo.getcwd()) or '', pats, opts) | 551 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '', |
552 pats, opts) | |
564 if pats: | 553 if pats: |
565 c, a, d, u = repo.changes(files = fns, match = match) | 554 c, a, d, u = repo.changes(files = fns, match = match) |
566 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r'] | 555 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r'] |
567 else: | 556 else: |
568 files = [] | 557 files = [] |
847 if opts['fullpath']: | 836 if opts['fullpath']: |
848 ui.write(os.path.join(repo.root, abs), end) | 837 ui.write(os.path.join(repo.root, abs), end) |
849 else: | 838 else: |
850 ui.write(rel, end) | 839 ui.write(rel, end) |
851 | 840 |
852 def log(ui, repo, f=None, **opts): | 841 def log(ui, repo, *pats, **opts): |
853 """show the revision history of the repository or a single file""" | 842 """show revision history of entire repository or files""" |
854 if f: | 843 # This code most commonly needs to iterate backwards over the |
855 files = relpath(repo, [f]) | 844 # history it is interested in. This has awful (quadratic-looking) |
856 filelog = repo.file(files[0]) | 845 # performance, so we use iterators that walk forwards through |
857 log = filelog | 846 # windows of revisions, yielding revisions in reverse order, while |
858 lookup = filelog.lookup | 847 # walking the windows backwards. |
859 else: | 848 files, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts) |
860 files = None | 849 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) |
861 filelog = None | 850 wanted = {} |
862 log = repo.changelog | 851 slowpath = anypats |
863 lookup = repo.lookup | 852 window = 300 |
864 revlist = [] | 853 if not slowpath and not files: |
865 revs = [log.rev(lookup(rev)) for rev in opts['rev']] | 854 # No files, no patterns. Display all revs. |
866 while revs: | 855 wanted = dict(zip(revs, revs)) |
867 if len(revs) == 1: | 856 if not slowpath: |
868 revlist.append(revs.pop(0)) | 857 # Only files, no patterns. Check the history of each file. |
869 else: | 858 def filerevgen(filelog): |
870 a = revs.pop(0) | 859 for i in xrange(filelog.count() - 1, 0, -window): |
871 b = revs.pop(0) | 860 revs = [] |
872 off = a > b and -1 or 1 | 861 for j in xrange(max(0, i - window), i): |
873 revlist.extend(range(a, b + off, off)) | 862 revs.append(filelog.linkrev(filelog.node(j))) |
874 | 863 revs.reverse() |
875 for i in revlist or range(log.count() - 1, -1, -1): | 864 for rev in revs: |
876 show_changeset(ui, repo, filelog=filelog, rev=i) | 865 yield rev |
866 | |
867 minrev, maxrev = min(revs), max(revs) | |
868 for filelog in map(repo.file, files): | |
869 # A zero count may be a directory or deleted file, so | |
870 # try to find matching entries on the slow path. | |
871 if filelog.count() == 0: | |
872 slowpath = True | |
873 break | |
874 for rev in filerevgen(filelog): | |
875 if rev <= maxrev: | |
876 if rev < minrev: break | |
877 wanted[rev] = 1 | |
878 if slowpath: | |
879 # The slow path checks files modified in every changeset. | |
880 def mfrevgen(): | |
881 for i in xrange(repo.changelog.count() - 1, 0, -window): | |
882 for j in xrange(max(0, i - window), i): | |
883 yield j, repo.changelog.read(repo.lookup(str(j)))[3] | |
884 | |
885 for rev, mf in mfrevgen(): | |
886 if filter(matchfn, mf): | |
887 wanted[rev] = 1 | |
888 | |
889 def changerevgen(): | |
890 class dui: | |
891 # Implement and delegate some ui protocol. Save hunks of | |
892 # output for later display in the desired order. | |
893 def __init__(self, ui): | |
894 self.ui = ui | |
895 self.hunk = {} | |
896 def bump(self, rev): | |
897 self.rev = rev | |
898 self.hunk[rev] = [] | |
899 def status(self, *args): | |
900 if not self.quiet: self.write(*args) | |
901 def write(self, *args): | |
902 self.hunk[self.rev].append(args) | |
903 def __getattr__(self, key): | |
904 return getattr(self.ui, key) | |
905 for i in xrange(0, len(revs), window): | |
906 nrevs = [rev for rev in revs[i : min(i + window, len(revs))] | |
907 if rev in wanted] | |
908 srevs = list(nrevs) | |
909 srevs.sort() | |
910 du = dui(ui) | |
911 for rev in srevs: | |
912 du.bump(rev) | |
913 yield rev, du | |
914 for rev in nrevs: | |
915 for args in du.hunk[rev]: | |
916 ui.write(*args) | |
917 | |
918 for rev, dui in changerevgen(): | |
919 show_changeset(dui, repo, rev) | |
877 if opts['patch']: | 920 if opts['patch']: |
878 if filelog: | 921 changenode = repo.changelog.node(rev) |
879 filenode = filelog.node(i) | |
880 i = filelog.linkrev(filenode) | |
881 changenode = repo.changelog.node(i) | |
882 prev, other = repo.changelog.parents(changenode) | 922 prev, other = repo.changelog.parents(changenode) |
883 dodiff(sys.stdout, ui, repo, prev, changenode, files) | 923 dodiff(dui, dui, repo, prev, changenode, files) |
884 ui.write("\n\n") | 924 du.write("\n\n") |
885 | 925 |
886 def manifest(ui, repo, rev=None): | 926 def manifest(ui, repo, rev=None): |
887 """output the latest or given revision of the project manifest""" | 927 """output the latest or given revision of the project manifest""" |
888 if rev: | 928 if rev: |
889 try: | 929 try: |
1160 R = removed | 1200 R = removed |
1161 ? = not tracked | 1201 ? = not tracked |
1162 ''' | 1202 ''' |
1163 | 1203 |
1164 cwd = repo.getcwd() | 1204 cwd = repo.getcwd() |
1165 files, matchfn = matchpats(repo, cwd, pats, opts) | 1205 files, matchfn, anypats = matchpats(repo, cwd, pats, opts) |
1166 (c, a, d, u) = [[util.pathto(cwd, x) for x in n] | 1206 (c, a, d, u) = [[util.pathto(cwd, x) for x in n] |
1167 for n in repo.changes(files=files, match=matchfn)] | 1207 for n in repo.changes(files=files, match=matchfn)] |
1168 | 1208 |
1169 changetypes = [('modified', 'M', c), | 1209 changetypes = [('modified', 'M', c), |
1170 ('added', 'A', a), | 1210 ('added', 'A', a), |
1376 ('I', 'include', [], 'include path in search'), | 1416 ('I', 'include', [], 'include path in search'), |
1377 ('X', 'exclude', [], 'exclude path from search')], | 1417 ('X', 'exclude', [], 'exclude path from search')], |
1378 'hg locate [OPTION]... [PATTERN]...'), | 1418 'hg locate [OPTION]... [PATTERN]...'), |
1379 "^log|history": | 1419 "^log|history": |
1380 (log, | 1420 (log, |
1381 [('r', 'rev', [], 'revision'), | 1421 [('I', 'include', [], 'include path in search'), |
1422 ('X', 'exclude', [], 'exclude path from search'), | |
1423 ('r', 'rev', [], 'revision'), | |
1382 ('p', 'patch', None, 'show patch')], | 1424 ('p', 'patch', None, 'show patch')], |
1383 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'), | 1425 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'), |
1384 "manifest": (manifest, [], 'hg manifest [REV]'), | 1426 "manifest": (manifest, [], 'hg manifest [REV]'), |
1385 "outgoing|out": (outgoing, [], 'hg outgoing [DEST]'), | 1427 "outgoing|out": (outgoing, [], 'hg outgoing [DEST]'), |
1386 "parents": (parents, [], 'hg parents [REV]'), | 1428 "parents": (parents, [], 'hg parents [REV]'), |