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]'),