comparison mercurial/commands.py @ 3657:731e739b8659

move walkchangerevs to cmdutils
author Matt Mackall <mpm@selenic.com>
date Wed, 15 Nov 2006 15:51:58 -0600
parents e50891e461e4
children 6389205291c6
comparison
equal deleted inserted replaced
3656:e50891e461e4 3657:731e739b8659
46 message = open(logfile).read() 46 message = open(logfile).read()
47 except IOError, inst: 47 except IOError, inst:
48 raise util.Abort(_("can't read commit message '%s': %s") % 48 raise util.Abort(_("can't read commit message '%s': %s") %
49 (logfile, inst.strerror)) 49 (logfile, inst.strerror))
50 return message 50 return message
51
52 def walkchangerevs(ui, repo, pats, change, opts):
53 '''Iterate over files and the revs they changed in.
54
55 Callers most commonly need to iterate backwards over the history
56 it is interested in. Doing so has awful (quadratic-looking)
57 performance, so we use iterators in a "windowed" way.
58
59 We walk a window of revisions in the desired order. Within the
60 window, we first walk forwards to gather data, then in the desired
61 order (usually backwards) to display it.
62
63 This function returns an (iterator, matchfn) tuple. The iterator
64 yields 3-tuples. They will be of one of the following forms:
65
66 "window", incrementing, lastrev: stepping through a window,
67 positive if walking forwards through revs, last rev in the
68 sequence iterated over - use to reset state for the current window
69
70 "add", rev, fns: out-of-order traversal of the given file names
71 fns, which changed during revision rev - use to gather data for
72 possible display
73
74 "iter", rev, None: in-order traversal of the revs earlier iterated
75 over with "add" - use to display data'''
76
77 def increasing_windows(start, end, windowsize=8, sizelimit=512):
78 if start < end:
79 while start < end:
80 yield start, min(windowsize, end-start)
81 start += windowsize
82 if windowsize < sizelimit:
83 windowsize *= 2
84 else:
85 while start > end:
86 yield start, min(windowsize, start-end-1)
87 start -= windowsize
88 if windowsize < sizelimit:
89 windowsize *= 2
90
91 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
92 follow = opts.get('follow') or opts.get('follow_first')
93
94 if repo.changelog.count() == 0:
95 return [], matchfn
96
97 if follow:
98 defrange = '%s:0' % repo.changectx().rev()
99 else:
100 defrange = 'tip:0'
101 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
102 wanted = {}
103 slowpath = anypats
104 fncache = {}
105
106 if not slowpath and not files:
107 # No files, no patterns. Display all revs.
108 wanted = dict.fromkeys(revs)
109 copies = []
110 if not slowpath:
111 # Only files, no patterns. Check the history of each file.
112 def filerevgen(filelog, node):
113 cl_count = repo.changelog.count()
114 if node is None:
115 last = filelog.count() - 1
116 else:
117 last = filelog.rev(node)
118 for i, window in increasing_windows(last, nullrev):
119 revs = []
120 for j in xrange(i - window, i + 1):
121 n = filelog.node(j)
122 revs.append((filelog.linkrev(n),
123 follow and filelog.renamed(n)))
124 revs.reverse()
125 for rev in revs:
126 # only yield rev for which we have the changelog, it can
127 # happen while doing "hg log" during a pull or commit
128 if rev[0] < cl_count:
129 yield rev
130 def iterfiles():
131 for filename in files:
132 yield filename, None
133 for filename_node in copies:
134 yield filename_node
135 minrev, maxrev = min(revs), max(revs)
136 for file_, node in iterfiles():
137 filelog = repo.file(file_)
138 # A zero count may be a directory or deleted file, so
139 # try to find matching entries on the slow path.
140 if filelog.count() == 0:
141 slowpath = True
142 break
143 for rev, copied in filerevgen(filelog, node):
144 if rev <= maxrev:
145 if rev < minrev:
146 break
147 fncache.setdefault(rev, [])
148 fncache[rev].append(file_)
149 wanted[rev] = 1
150 if follow and copied:
151 copies.append(copied)
152 if slowpath:
153 if follow:
154 raise util.Abort(_('can only follow copies/renames for explicit '
155 'file names'))
156
157 # The slow path checks files modified in every changeset.
158 def changerevgen():
159 for i, window in increasing_windows(repo.changelog.count()-1,
160 nullrev):
161 for j in xrange(i - window, i + 1):
162 yield j, change(j)[3]
163
164 for rev, changefiles in changerevgen():
165 matches = filter(matchfn, changefiles)
166 if matches:
167 fncache[rev] = matches
168 wanted[rev] = 1
169
170 class followfilter:
171 def __init__(self, onlyfirst=False):
172 self.startrev = nullrev
173 self.roots = []
174 self.onlyfirst = onlyfirst
175
176 def match(self, rev):
177 def realparents(rev):
178 if self.onlyfirst:
179 return repo.changelog.parentrevs(rev)[0:1]
180 else:
181 return filter(lambda x: x != nullrev,
182 repo.changelog.parentrevs(rev))
183
184 if self.startrev == nullrev:
185 self.startrev = rev
186 return True
187
188 if rev > self.startrev:
189 # forward: all descendants
190 if not self.roots:
191 self.roots.append(self.startrev)
192 for parent in realparents(rev):
193 if parent in self.roots:
194 self.roots.append(rev)
195 return True
196 else:
197 # backwards: all parents
198 if not self.roots:
199 self.roots.extend(realparents(self.startrev))
200 if rev in self.roots:
201 self.roots.remove(rev)
202 self.roots.extend(realparents(rev))
203 return True
204
205 return False
206
207 # it might be worthwhile to do this in the iterator if the rev range
208 # is descending and the prune args are all within that range
209 for rev in opts.get('prune', ()):
210 rev = repo.changelog.rev(repo.lookup(rev))
211 ff = followfilter()
212 stop = min(revs[0], revs[-1])
213 for x in xrange(rev, stop-1, -1):
214 if ff.match(x) and x in wanted:
215 del wanted[x]
216
217 def iterate():
218 if follow and not files:
219 ff = followfilter(onlyfirst=opts.get('follow_first'))
220 def want(rev):
221 if ff.match(rev) and rev in wanted:
222 return True
223 return False
224 else:
225 def want(rev):
226 return rev in wanted
227
228 for i, window in increasing_windows(0, len(revs)):
229 yield 'window', revs[0] < revs[-1], revs[-1]
230 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
231 srevs = list(nrevs)
232 srevs.sort()
233 for rev in srevs:
234 fns = fncache.get(rev)
235 if not fns:
236 def fns_generator():
237 for f in change(rev)[3]:
238 if matchfn(f):
239 yield f
240 fns = fns_generator()
241 yield 'add', rev, fns
242 for rev in nrevs:
243 yield 'iter', rev, None
244 return iterate(), matchfn
245 51
246 def write_bundle(cg, filename=None, compress=True): 52 def write_bundle(cg, filename=None, compress=True):
247 """Write a bundle file and return its filename. 53 """Write a bundle file and return its filename.
248 54
249 Existing files will not be overwritten. 55 Existing files will not be overwritten.
1350 if opts['line_number']: 1156 if opts['line_number']:
1351 cols.append(str(l.linenum)) 1157 cols.append(str(l.linenum))
1352 if opts['all']: 1158 if opts['all']:
1353 cols.append(change) 1159 cols.append(change)
1354 if opts['user']: 1160 if opts['user']:
1355 cols.append(ui.shortuser(getchange(r)[1])) 1161 cols.append(ui.shortuser(get(r)[1]))
1356 if opts['files_with_matches']: 1162 if opts['files_with_matches']:
1357 c = (fn, r) 1163 c = (fn, r)
1358 if c in filerevmatches: 1164 if c in filerevmatches:
1359 continue 1165 continue
1360 filerevmatches[c] = 1 1166 filerevmatches[c] = 1
1364 counts[change] += 1 1170 counts[change] += 1
1365 return counts['+'], counts['-'] 1171 return counts['+'], counts['-']
1366 1172
1367 fstate = {} 1173 fstate = {}
1368 skip = {} 1174 skip = {}
1369 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset()) 1175 get = util.cachefunc(lambda r:repo.changectx(r).changeset())
1370 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts) 1176 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1371 count = 0 1177 count = 0
1372 incrementing = False 1178 incrementing = False
1373 follow = opts.get('follow') 1179 follow = opts.get('follow')
1374 for st, rev, fns in changeiter: 1180 for st, rev, fns in changeiter:
1375 if st == 'window': 1181 if st == 'window':
1662 non-trivial parents, user, date and time, and a summary for each 1468 non-trivial parents, user, date and time, and a summary for each
1663 commit. When the -v/--verbose switch is used, the list of changed 1469 commit. When the -v/--verbose switch is used, the list of changed
1664 files and full commit message is shown. 1470 files and full commit message is shown.
1665 """ 1471 """
1666 1472
1667 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset()) 1473 get = util.cachefunc(lambda r:repo.changectx(r).changeset())
1668 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts) 1474 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1669 1475
1670 if opts['limit']: 1476 if opts['limit']:
1671 try: 1477 try:
1672 limit = int(opts['limit']) 1478 limit = int(opts['limit'])
1673 except ValueError: 1479 except ValueError:
1723 continue 1529 continue
1724 if opts['only_merges'] and len(parents) != 2: 1530 if opts['only_merges'] and len(parents) != 2:
1725 continue 1531 continue
1726 1532
1727 if opts['keyword']: 1533 if opts['keyword']:
1728 changes = getchange(rev) 1534 changes = get(rev)
1729 miss = 0 1535 miss = 0
1730 for k in [kw.lower() for kw in opts['keyword']]: 1536 for k in [kw.lower() for kw in opts['keyword']]:
1731 if not (k in changes[1].lower() or 1537 if not (k in changes[1].lower() or
1732 k in changes[4].lower() or 1538 k in changes[4].lower() or
1733 k in " ".join(changes[3][:20]).lower()): 1539 k in " ".join(changes[3][:20]).lower()):
1736 if miss: 1542 if miss:
1737 continue 1543 continue
1738 1544
1739 copies = [] 1545 copies = []
1740 if opts.get('copies') and rev: 1546 if opts.get('copies') and rev:
1741 mf = getchange(rev)[0] 1547 mf = get(rev)[0]
1742 for fn in getchange(rev)[3]: 1548 for fn in get(rev)[3]:
1743 rename = getrenamed(fn, rev, mf) 1549 rename = getrenamed(fn, rev, mf)
1744 if rename: 1550 if rename:
1745 copies.append((fn, rename[0])) 1551 copies.append((fn, rename[0]))
1746 displayer.show(rev, changenode, copies=copies) 1552 displayer.show(rev, changenode, copies=copies)
1747 elif st == 'iter': 1553 elif st == 'iter':