##// END OF EJS Templates
Avoid some false positives for addremove -s...
Erling Ellingsen -
r4135:6cb6cfe4 default
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1 #!/bin/sh
2
3 hg init rep; cd rep
4
5 touch empty-file
6 python -c 'for x in range(10000): print x' > large-file
7
8 hg addremove
9
10 hg commit -m A
11
12 rm large-file empty-file
13 python -c 'for x in range(10,10000): print x' > another-file
14
15 hg addremove -s50
16
17 hg commit -m B
18
19 cd ..
20
21 hg init rep2; cd rep2
22
23 python -c 'for x in range(10000): print x' > large-file
24 python -c 'for x in range(50): print x' > tiny-file
25
26 hg addremove
27
28 hg commit -m A
29
30 python -c 'for x in range(70): print x' > small-file
31 rm tiny-file
32 rm large-file
33
34 hg addremove -s50
35
36 hg commit -m B
37
@@ -0,0 +1,12 b''
1 adding empty-file
2 adding large-file
3 adding another-file
4 removing empty-file
5 removing large-file
6 recording removal of large-file as rename to another-file (99% similar)
7 adding large-file
8 adding tiny-file
9 adding small-file
10 removing large-file
11 removing tiny-file
12 recording removal of tiny-file as rename to small-file (82% similar)
@@ -1,755 +1,764 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, sys, mdiff, util, templater, patch
10 import os, sys, mdiff, bdiff, util, templater, patch
11
11
12 revrangesep = ':'
12 revrangesep = ':'
13
13
14 def revpair(repo, revs):
14 def revpair(repo, revs):
15 '''return pair of nodes, given list of revisions. second item can
15 '''return pair of nodes, given list of revisions. second item can
16 be None, meaning use working dir.'''
16 be None, meaning use working dir.'''
17
17
18 def revfix(repo, val, defval):
18 def revfix(repo, val, defval):
19 if not val and val != 0 and defval is not None:
19 if not val and val != 0 and defval is not None:
20 val = defval
20 val = defval
21 return repo.lookup(val)
21 return repo.lookup(val)
22
22
23 if not revs:
23 if not revs:
24 return repo.dirstate.parents()[0], None
24 return repo.dirstate.parents()[0], None
25 end = None
25 end = None
26 if len(revs) == 1:
26 if len(revs) == 1:
27 if revrangesep in revs[0]:
27 if revrangesep in revs[0]:
28 start, end = revs[0].split(revrangesep, 1)
28 start, end = revs[0].split(revrangesep, 1)
29 start = revfix(repo, start, 0)
29 start = revfix(repo, start, 0)
30 end = revfix(repo, end, repo.changelog.count() - 1)
30 end = revfix(repo, end, repo.changelog.count() - 1)
31 else:
31 else:
32 start = revfix(repo, revs[0], None)
32 start = revfix(repo, revs[0], None)
33 elif len(revs) == 2:
33 elif len(revs) == 2:
34 if revrangesep in revs[0] or revrangesep in revs[1]:
34 if revrangesep in revs[0] or revrangesep in revs[1]:
35 raise util.Abort(_('too many revisions specified'))
35 raise util.Abort(_('too many revisions specified'))
36 start = revfix(repo, revs[0], None)
36 start = revfix(repo, revs[0], None)
37 end = revfix(repo, revs[1], None)
37 end = revfix(repo, revs[1], None)
38 else:
38 else:
39 raise util.Abort(_('too many revisions specified'))
39 raise util.Abort(_('too many revisions specified'))
40 return start, end
40 return start, end
41
41
42 def revrange(repo, revs):
42 def revrange(repo, revs):
43 """Yield revision as strings from a list of revision specifications."""
43 """Yield revision as strings from a list of revision specifications."""
44
44
45 def revfix(repo, val, defval):
45 def revfix(repo, val, defval):
46 if not val and val != 0 and defval is not None:
46 if not val and val != 0 and defval is not None:
47 return defval
47 return defval
48 return repo.changelog.rev(repo.lookup(val))
48 return repo.changelog.rev(repo.lookup(val))
49
49
50 seen, l = {}, []
50 seen, l = {}, []
51 for spec in revs:
51 for spec in revs:
52 if revrangesep in spec:
52 if revrangesep in spec:
53 start, end = spec.split(revrangesep, 1)
53 start, end = spec.split(revrangesep, 1)
54 start = revfix(repo, start, 0)
54 start = revfix(repo, start, 0)
55 end = revfix(repo, end, repo.changelog.count() - 1)
55 end = revfix(repo, end, repo.changelog.count() - 1)
56 step = start > end and -1 or 1
56 step = start > end and -1 or 1
57 for rev in xrange(start, end+step, step):
57 for rev in xrange(start, end+step, step):
58 if rev in seen:
58 if rev in seen:
59 continue
59 continue
60 seen[rev] = 1
60 seen[rev] = 1
61 l.append(rev)
61 l.append(rev)
62 else:
62 else:
63 rev = revfix(repo, spec, None)
63 rev = revfix(repo, spec, None)
64 if rev in seen:
64 if rev in seen:
65 continue
65 continue
66 seen[rev] = 1
66 seen[rev] = 1
67 l.append(rev)
67 l.append(rev)
68
68
69 return l
69 return l
70
70
71 def make_filename(repo, pat, node,
71 def make_filename(repo, pat, node,
72 total=None, seqno=None, revwidth=None, pathname=None):
72 total=None, seqno=None, revwidth=None, pathname=None):
73 node_expander = {
73 node_expander = {
74 'H': lambda: hex(node),
74 'H': lambda: hex(node),
75 'R': lambda: str(repo.changelog.rev(node)),
75 'R': lambda: str(repo.changelog.rev(node)),
76 'h': lambda: short(node),
76 'h': lambda: short(node),
77 }
77 }
78 expander = {
78 expander = {
79 '%': lambda: '%',
79 '%': lambda: '%',
80 'b': lambda: os.path.basename(repo.root),
80 'b': lambda: os.path.basename(repo.root),
81 }
81 }
82
82
83 try:
83 try:
84 if node:
84 if node:
85 expander.update(node_expander)
85 expander.update(node_expander)
86 if node and revwidth is not None:
86 if node and revwidth is not None:
87 expander['r'] = (lambda:
87 expander['r'] = (lambda:
88 str(repo.changelog.rev(node)).zfill(revwidth))
88 str(repo.changelog.rev(node)).zfill(revwidth))
89 if total is not None:
89 if total is not None:
90 expander['N'] = lambda: str(total)
90 expander['N'] = lambda: str(total)
91 if seqno is not None:
91 if seqno is not None:
92 expander['n'] = lambda: str(seqno)
92 expander['n'] = lambda: str(seqno)
93 if total is not None and seqno is not None:
93 if total is not None and seqno is not None:
94 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
94 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
95 if pathname is not None:
95 if pathname is not None:
96 expander['s'] = lambda: os.path.basename(pathname)
96 expander['s'] = lambda: os.path.basename(pathname)
97 expander['d'] = lambda: os.path.dirname(pathname) or '.'
97 expander['d'] = lambda: os.path.dirname(pathname) or '.'
98 expander['p'] = lambda: pathname
98 expander['p'] = lambda: pathname
99
99
100 newname = []
100 newname = []
101 patlen = len(pat)
101 patlen = len(pat)
102 i = 0
102 i = 0
103 while i < patlen:
103 while i < patlen:
104 c = pat[i]
104 c = pat[i]
105 if c == '%':
105 if c == '%':
106 i += 1
106 i += 1
107 c = pat[i]
107 c = pat[i]
108 c = expander[c]()
108 c = expander[c]()
109 newname.append(c)
109 newname.append(c)
110 i += 1
110 i += 1
111 return ''.join(newname)
111 return ''.join(newname)
112 except KeyError, inst:
112 except KeyError, inst:
113 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
113 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
114 inst.args[0])
114 inst.args[0])
115
115
116 def make_file(repo, pat, node=None,
116 def make_file(repo, pat, node=None,
117 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
117 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
118 if not pat or pat == '-':
118 if not pat or pat == '-':
119 return 'w' in mode and sys.stdout or sys.stdin
119 return 'w' in mode and sys.stdout or sys.stdin
120 if hasattr(pat, 'write') and 'w' in mode:
120 if hasattr(pat, 'write') and 'w' in mode:
121 return pat
121 return pat
122 if hasattr(pat, 'read') and 'r' in mode:
122 if hasattr(pat, 'read') and 'r' in mode:
123 return pat
123 return pat
124 return open(make_filename(repo, pat, node, total, seqno, revwidth,
124 return open(make_filename(repo, pat, node, total, seqno, revwidth,
125 pathname),
125 pathname),
126 mode)
126 mode)
127
127
128 def matchpats(repo, pats=[], opts={}, head='', globbed=False):
128 def matchpats(repo, pats=[], opts={}, head='', globbed=False):
129 cwd = repo.getcwd()
129 cwd = repo.getcwd()
130 if not pats and cwd:
130 if not pats and cwd:
131 opts['include'] = [os.path.join(cwd, i)
131 opts['include'] = [os.path.join(cwd, i)
132 for i in opts.get('include', [])]
132 for i in opts.get('include', [])]
133 opts['exclude'] = [os.path.join(cwd, x)
133 opts['exclude'] = [os.path.join(cwd, x)
134 for x in opts.get('exclude', [])]
134 for x in opts.get('exclude', [])]
135 cwd = ''
135 cwd = ''
136 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
136 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
137 opts.get('exclude'), head, globbed=globbed)
137 opts.get('exclude'), head, globbed=globbed)
138
138
139 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
139 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
140 globbed=False):
140 globbed=False):
141 files, matchfn, anypats = matchpats(repo, pats, opts, head,
141 files, matchfn, anypats = matchpats(repo, pats, opts, head,
142 globbed=globbed)
142 globbed=globbed)
143 exact = dict.fromkeys(files)
143 exact = dict.fromkeys(files)
144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
145 badmatch=badmatch):
145 badmatch=badmatch):
146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
147
147
148 def findrenames(repo, added=None, removed=None, threshold=0.5):
148 def findrenames(repo, added=None, removed=None, threshold=0.5):
149 '''find renamed files -- yields (before, after, score) tuples'''
149 if added is None or removed is None:
150 if added is None or removed is None:
150 added, removed = repo.status()[1:3]
151 added, removed = repo.status()[1:3]
151 ctx = repo.changectx()
152 ctx = repo.changectx()
152 for a in added:
153 for a in added:
153 aa = repo.wread(a)
154 aa = repo.wread(a)
154 bestscore, bestname = None, None
155 bestname, bestscore = None, threshold
155 for r in removed:
156 for r in removed:
156 rr = ctx.filectx(r).data()
157 rr = ctx.filectx(r).data()
157 delta = mdiff.textdiff(aa, rr)
158
158 if len(delta) < len(aa):
159 # bdiff.blocks() returns blocks of matching lines
159 myscore = 1.0 - (float(len(delta)) / len(aa))
160 # count the number of bytes in each
160 if bestscore is None or myscore > bestscore:
161 equal = 0
161 bestscore, bestname = myscore, r
162 alines = mdiff.splitnewlines(aa)
162 if bestname and bestscore >= threshold:
163 matches = bdiff.blocks(aa, rr)
164 for x1,x2,y1,y2 in matches:
165 for line in alines[x1:x2]:
166 equal += len(line)
167
168 myscore = equal*2.0 / (len(aa)+len(rr))
169 if myscore >= bestscore:
170 bestname, bestscore = r, myscore
171 if bestname:
163 yield bestname, a, bestscore
172 yield bestname, a, bestscore
164
173
165 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
174 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
166 similarity=None):
175 similarity=None):
167 if dry_run is None:
176 if dry_run is None:
168 dry_run = opts.get('dry_run')
177 dry_run = opts.get('dry_run')
169 if similarity is None:
178 if similarity is None:
170 similarity = float(opts.get('similarity') or 0)
179 similarity = float(opts.get('similarity') or 0)
171 add, remove = [], []
180 add, remove = [], []
172 mapping = {}
181 mapping = {}
173 for src, abs, rel, exact in walk(repo, pats, opts):
182 for src, abs, rel, exact in walk(repo, pats, opts):
174 if src == 'f' and repo.dirstate.state(abs) == '?':
183 if src == 'f' and repo.dirstate.state(abs) == '?':
175 add.append(abs)
184 add.append(abs)
176 mapping[abs] = rel, exact
185 mapping[abs] = rel, exact
177 if repo.ui.verbose or not exact:
186 if repo.ui.verbose or not exact:
178 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
187 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
179 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
188 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
180 remove.append(abs)
189 remove.append(abs)
181 mapping[abs] = rel, exact
190 mapping[abs] = rel, exact
182 if repo.ui.verbose or not exact:
191 if repo.ui.verbose or not exact:
183 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
192 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
184 if not dry_run:
193 if not dry_run:
185 repo.add(add, wlock=wlock)
194 repo.add(add, wlock=wlock)
186 repo.remove(remove, wlock=wlock)
195 repo.remove(remove, wlock=wlock)
187 if similarity > 0:
196 if similarity > 0:
188 for old, new, score in findrenames(repo, add, remove, similarity):
197 for old, new, score in findrenames(repo, add, remove, similarity):
189 oldrel, oldexact = mapping[old]
198 oldrel, oldexact = mapping[old]
190 newrel, newexact = mapping[new]
199 newrel, newexact = mapping[new]
191 if repo.ui.verbose or not oldexact or not newexact:
200 if repo.ui.verbose or not oldexact or not newexact:
192 repo.ui.status(_('recording removal of %s as rename to %s '
201 repo.ui.status(_('recording removal of %s as rename to %s '
193 '(%d%% similar)\n') %
202 '(%d%% similar)\n') %
194 (oldrel, newrel, score * 100))
203 (oldrel, newrel, score * 100))
195 if not dry_run:
204 if not dry_run:
196 repo.copy(old, new, wlock=wlock)
205 repo.copy(old, new, wlock=wlock)
197
206
198 class changeset_printer(object):
207 class changeset_printer(object):
199 '''show changeset information when templating not requested.'''
208 '''show changeset information when templating not requested.'''
200
209
201 def __init__(self, ui, repo, patch, buffered):
210 def __init__(self, ui, repo, patch, buffered):
202 self.ui = ui
211 self.ui = ui
203 self.repo = repo
212 self.repo = repo
204 self.buffered = buffered
213 self.buffered = buffered
205 self.patch = patch
214 self.patch = patch
206 self.header = {}
215 self.header = {}
207 self.hunk = {}
216 self.hunk = {}
208 self.lastheader = None
217 self.lastheader = None
209
218
210 def flush(self, rev):
219 def flush(self, rev):
211 if rev in self.header:
220 if rev in self.header:
212 h = self.header[rev]
221 h = self.header[rev]
213 if h != self.lastheader:
222 if h != self.lastheader:
214 self.lastheader = h
223 self.lastheader = h
215 self.ui.write(h)
224 self.ui.write(h)
216 del self.header[rev]
225 del self.header[rev]
217 if rev in self.hunk:
226 if rev in self.hunk:
218 self.ui.write(self.hunk[rev])
227 self.ui.write(self.hunk[rev])
219 del self.hunk[rev]
228 del self.hunk[rev]
220 return 1
229 return 1
221 return 0
230 return 0
222
231
223 def show(self, rev=0, changenode=None, copies=None, **props):
232 def show(self, rev=0, changenode=None, copies=None, **props):
224 if self.buffered:
233 if self.buffered:
225 self.ui.pushbuffer()
234 self.ui.pushbuffer()
226 self._show(rev, changenode, copies, props)
235 self._show(rev, changenode, copies, props)
227 self.hunk[rev] = self.ui.popbuffer()
236 self.hunk[rev] = self.ui.popbuffer()
228 else:
237 else:
229 self._show(rev, changenode, copies, props)
238 self._show(rev, changenode, copies, props)
230
239
231 def _show(self, rev, changenode, copies, props):
240 def _show(self, rev, changenode, copies, props):
232 '''show a single changeset or file revision'''
241 '''show a single changeset or file revision'''
233 log = self.repo.changelog
242 log = self.repo.changelog
234 if changenode is None:
243 if changenode is None:
235 changenode = log.node(rev)
244 changenode = log.node(rev)
236 elif not rev:
245 elif not rev:
237 rev = log.rev(changenode)
246 rev = log.rev(changenode)
238
247
239 if self.ui.quiet:
248 if self.ui.quiet:
240 self.ui.write("%d:%s\n" % (rev, short(changenode)))
249 self.ui.write("%d:%s\n" % (rev, short(changenode)))
241 return
250 return
242
251
243 changes = log.read(changenode)
252 changes = log.read(changenode)
244 date = util.datestr(changes[2])
253 date = util.datestr(changes[2])
245 extra = changes[5]
254 extra = changes[5]
246 branch = extra.get("branch")
255 branch = extra.get("branch")
247
256
248 hexfunc = self.ui.debugflag and hex or short
257 hexfunc = self.ui.debugflag and hex or short
249
258
250 parents = log.parentrevs(rev)
259 parents = log.parentrevs(rev)
251 if not self.ui.debugflag:
260 if not self.ui.debugflag:
252 if parents[1] == nullrev:
261 if parents[1] == nullrev:
253 if parents[0] >= rev - 1:
262 if parents[0] >= rev - 1:
254 parents = []
263 parents = []
255 else:
264 else:
256 parents = [parents[0]]
265 parents = [parents[0]]
257 parents = [(p, hexfunc(log.node(p))) for p in parents]
266 parents = [(p, hexfunc(log.node(p))) for p in parents]
258
267
259 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
268 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
260
269
261 if branch:
270 if branch:
262 branch = util.tolocal(branch)
271 branch = util.tolocal(branch)
263 self.ui.write(_("branch: %s\n") % branch)
272 self.ui.write(_("branch: %s\n") % branch)
264 for tag in self.repo.nodetags(changenode):
273 for tag in self.repo.nodetags(changenode):
265 self.ui.write(_("tag: %s\n") % tag)
274 self.ui.write(_("tag: %s\n") % tag)
266 for parent in parents:
275 for parent in parents:
267 self.ui.write(_("parent: %d:%s\n") % parent)
276 self.ui.write(_("parent: %d:%s\n") % parent)
268
277
269 if self.ui.debugflag:
278 if self.ui.debugflag:
270 self.ui.write(_("manifest: %d:%s\n") %
279 self.ui.write(_("manifest: %d:%s\n") %
271 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
280 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
272 self.ui.write(_("user: %s\n") % changes[1])
281 self.ui.write(_("user: %s\n") % changes[1])
273 self.ui.write(_("date: %s\n") % date)
282 self.ui.write(_("date: %s\n") % date)
274
283
275 if self.ui.debugflag:
284 if self.ui.debugflag:
276 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
285 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
277 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
286 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
278 files):
287 files):
279 if value:
288 if value:
280 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
289 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
281 elif changes[3] and self.ui.verbose:
290 elif changes[3] and self.ui.verbose:
282 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
291 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
283 if copies and self.ui.verbose:
292 if copies and self.ui.verbose:
284 copies = ['%s (%s)' % c for c in copies]
293 copies = ['%s (%s)' % c for c in copies]
285 self.ui.write(_("copies: %s\n") % ' '.join(copies))
294 self.ui.write(_("copies: %s\n") % ' '.join(copies))
286
295
287 if extra and self.ui.debugflag:
296 if extra and self.ui.debugflag:
288 extraitems = extra.items()
297 extraitems = extra.items()
289 extraitems.sort()
298 extraitems.sort()
290 for key, value in extraitems:
299 for key, value in extraitems:
291 self.ui.write(_("extra: %s=%s\n")
300 self.ui.write(_("extra: %s=%s\n")
292 % (key, value.encode('string_escape')))
301 % (key, value.encode('string_escape')))
293
302
294 description = changes[4].strip()
303 description = changes[4].strip()
295 if description:
304 if description:
296 if self.ui.verbose:
305 if self.ui.verbose:
297 self.ui.write(_("description:\n"))
306 self.ui.write(_("description:\n"))
298 self.ui.write(description)
307 self.ui.write(description)
299 self.ui.write("\n\n")
308 self.ui.write("\n\n")
300 else:
309 else:
301 self.ui.write(_("summary: %s\n") %
310 self.ui.write(_("summary: %s\n") %
302 description.splitlines()[0])
311 description.splitlines()[0])
303 self.ui.write("\n")
312 self.ui.write("\n")
304
313
305 self.showpatch(changenode)
314 self.showpatch(changenode)
306
315
307 def showpatch(self, node):
316 def showpatch(self, node):
308 if self.patch:
317 if self.patch:
309 prev = self.repo.changelog.parents(node)[0]
318 prev = self.repo.changelog.parents(node)[0]
310 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
319 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
311 self.ui.write("\n")
320 self.ui.write("\n")
312
321
313 class changeset_templater(changeset_printer):
322 class changeset_templater(changeset_printer):
314 '''format changeset information.'''
323 '''format changeset information.'''
315
324
316 def __init__(self, ui, repo, patch, mapfile, buffered):
325 def __init__(self, ui, repo, patch, mapfile, buffered):
317 changeset_printer.__init__(self, ui, repo, patch, buffered)
326 changeset_printer.__init__(self, ui, repo, patch, buffered)
318 self.t = templater.templater(mapfile, templater.common_filters,
327 self.t = templater.templater(mapfile, templater.common_filters,
319 cache={'parent': '{rev}:{node|short} ',
328 cache={'parent': '{rev}:{node|short} ',
320 'manifest': '{rev}:{node|short}',
329 'manifest': '{rev}:{node|short}',
321 'filecopy': '{name} ({source})'})
330 'filecopy': '{name} ({source})'})
322
331
323 def use_template(self, t):
332 def use_template(self, t):
324 '''set template string to use'''
333 '''set template string to use'''
325 self.t.cache['changeset'] = t
334 self.t.cache['changeset'] = t
326
335
327 def _show(self, rev, changenode, copies, props):
336 def _show(self, rev, changenode, copies, props):
328 '''show a single changeset or file revision'''
337 '''show a single changeset or file revision'''
329 log = self.repo.changelog
338 log = self.repo.changelog
330 if changenode is None:
339 if changenode is None:
331 changenode = log.node(rev)
340 changenode = log.node(rev)
332 elif not rev:
341 elif not rev:
333 rev = log.rev(changenode)
342 rev = log.rev(changenode)
334
343
335 changes = log.read(changenode)
344 changes = log.read(changenode)
336
345
337 def showlist(name, values, plural=None, **args):
346 def showlist(name, values, plural=None, **args):
338 '''expand set of values.
347 '''expand set of values.
339 name is name of key in template map.
348 name is name of key in template map.
340 values is list of strings or dicts.
349 values is list of strings or dicts.
341 plural is plural of name, if not simply name + 's'.
350 plural is plural of name, if not simply name + 's'.
342
351
343 expansion works like this, given name 'foo'.
352 expansion works like this, given name 'foo'.
344
353
345 if values is empty, expand 'no_foos'.
354 if values is empty, expand 'no_foos'.
346
355
347 if 'foo' not in template map, return values as a string,
356 if 'foo' not in template map, return values as a string,
348 joined by space.
357 joined by space.
349
358
350 expand 'start_foos'.
359 expand 'start_foos'.
351
360
352 for each value, expand 'foo'. if 'last_foo' in template
361 for each value, expand 'foo'. if 'last_foo' in template
353 map, expand it instead of 'foo' for last key.
362 map, expand it instead of 'foo' for last key.
354
363
355 expand 'end_foos'.
364 expand 'end_foos'.
356 '''
365 '''
357 if plural: names = plural
366 if plural: names = plural
358 else: names = name + 's'
367 else: names = name + 's'
359 if not values:
368 if not values:
360 noname = 'no_' + names
369 noname = 'no_' + names
361 if noname in self.t:
370 if noname in self.t:
362 yield self.t(noname, **args)
371 yield self.t(noname, **args)
363 return
372 return
364 if name not in self.t:
373 if name not in self.t:
365 if isinstance(values[0], str):
374 if isinstance(values[0], str):
366 yield ' '.join(values)
375 yield ' '.join(values)
367 else:
376 else:
368 for v in values:
377 for v in values:
369 yield dict(v, **args)
378 yield dict(v, **args)
370 return
379 return
371 startname = 'start_' + names
380 startname = 'start_' + names
372 if startname in self.t:
381 if startname in self.t:
373 yield self.t(startname, **args)
382 yield self.t(startname, **args)
374 vargs = args.copy()
383 vargs = args.copy()
375 def one(v, tag=name):
384 def one(v, tag=name):
376 try:
385 try:
377 vargs.update(v)
386 vargs.update(v)
378 except (AttributeError, ValueError):
387 except (AttributeError, ValueError):
379 try:
388 try:
380 for a, b in v:
389 for a, b in v:
381 vargs[a] = b
390 vargs[a] = b
382 except ValueError:
391 except ValueError:
383 vargs[name] = v
392 vargs[name] = v
384 return self.t(tag, **vargs)
393 return self.t(tag, **vargs)
385 lastname = 'last_' + name
394 lastname = 'last_' + name
386 if lastname in self.t:
395 if lastname in self.t:
387 last = values.pop()
396 last = values.pop()
388 else:
397 else:
389 last = None
398 last = None
390 for v in values:
399 for v in values:
391 yield one(v)
400 yield one(v)
392 if last is not None:
401 if last is not None:
393 yield one(last, tag=lastname)
402 yield one(last, tag=lastname)
394 endname = 'end_' + names
403 endname = 'end_' + names
395 if endname in self.t:
404 if endname in self.t:
396 yield self.t(endname, **args)
405 yield self.t(endname, **args)
397
406
398 def showbranches(**args):
407 def showbranches(**args):
399 branch = changes[5].get("branch")
408 branch = changes[5].get("branch")
400 if branch:
409 if branch:
401 branch = util.tolocal(branch)
410 branch = util.tolocal(branch)
402 return showlist('branch', [branch], plural='branches', **args)
411 return showlist('branch', [branch], plural='branches', **args)
403
412
404 def showparents(**args):
413 def showparents(**args):
405 parents = [[('rev', log.rev(p)), ('node', hex(p))]
414 parents = [[('rev', log.rev(p)), ('node', hex(p))]
406 for p in log.parents(changenode)
415 for p in log.parents(changenode)
407 if self.ui.debugflag or p != nullid]
416 if self.ui.debugflag or p != nullid]
408 if (not self.ui.debugflag and len(parents) == 1 and
417 if (not self.ui.debugflag and len(parents) == 1 and
409 parents[0][0][1] == rev - 1):
418 parents[0][0][1] == rev - 1):
410 return
419 return
411 return showlist('parent', parents, **args)
420 return showlist('parent', parents, **args)
412
421
413 def showtags(**args):
422 def showtags(**args):
414 return showlist('tag', self.repo.nodetags(changenode), **args)
423 return showlist('tag', self.repo.nodetags(changenode), **args)
415
424
416 def showextras(**args):
425 def showextras(**args):
417 extras = changes[5].items()
426 extras = changes[5].items()
418 extras.sort()
427 extras.sort()
419 for key, value in extras:
428 for key, value in extras:
420 args = args.copy()
429 args = args.copy()
421 args.update(dict(key=key, value=value))
430 args.update(dict(key=key, value=value))
422 yield self.t('extra', **args)
431 yield self.t('extra', **args)
423
432
424 def showcopies(**args):
433 def showcopies(**args):
425 c = [{'name': x[0], 'source': x[1]} for x in copies]
434 c = [{'name': x[0], 'source': x[1]} for x in copies]
426 return showlist('file_copy', c, plural='file_copies', **args)
435 return showlist('file_copy', c, plural='file_copies', **args)
427
436
428 if self.ui.debugflag:
437 if self.ui.debugflag:
429 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
438 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
430 def showfiles(**args):
439 def showfiles(**args):
431 return showlist('file', files[0], **args)
440 return showlist('file', files[0], **args)
432 def showadds(**args):
441 def showadds(**args):
433 return showlist('file_add', files[1], **args)
442 return showlist('file_add', files[1], **args)
434 def showdels(**args):
443 def showdels(**args):
435 return showlist('file_del', files[2], **args)
444 return showlist('file_del', files[2], **args)
436 def showmanifest(**args):
445 def showmanifest(**args):
437 args = args.copy()
446 args = args.copy()
438 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
447 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
439 node=hex(changes[0])))
448 node=hex(changes[0])))
440 return self.t('manifest', **args)
449 return self.t('manifest', **args)
441 else:
450 else:
442 def showfiles(**args):
451 def showfiles(**args):
443 return showlist('file', changes[3], **args)
452 return showlist('file', changes[3], **args)
444 showadds = ''
453 showadds = ''
445 showdels = ''
454 showdels = ''
446 showmanifest = ''
455 showmanifest = ''
447
456
448 defprops = {
457 defprops = {
449 'author': changes[1],
458 'author': changes[1],
450 'branches': showbranches,
459 'branches': showbranches,
451 'date': changes[2],
460 'date': changes[2],
452 'desc': changes[4],
461 'desc': changes[4],
453 'file_adds': showadds,
462 'file_adds': showadds,
454 'file_dels': showdels,
463 'file_dels': showdels,
455 'files': showfiles,
464 'files': showfiles,
456 'file_copies': showcopies,
465 'file_copies': showcopies,
457 'manifest': showmanifest,
466 'manifest': showmanifest,
458 'node': hex(changenode),
467 'node': hex(changenode),
459 'parents': showparents,
468 'parents': showparents,
460 'rev': rev,
469 'rev': rev,
461 'tags': showtags,
470 'tags': showtags,
462 'extras': showextras,
471 'extras': showextras,
463 }
472 }
464 props = props.copy()
473 props = props.copy()
465 props.update(defprops)
474 props.update(defprops)
466
475
467 try:
476 try:
468 if self.ui.debugflag and 'header_debug' in self.t:
477 if self.ui.debugflag and 'header_debug' in self.t:
469 key = 'header_debug'
478 key = 'header_debug'
470 elif self.ui.quiet and 'header_quiet' in self.t:
479 elif self.ui.quiet and 'header_quiet' in self.t:
471 key = 'header_quiet'
480 key = 'header_quiet'
472 elif self.ui.verbose and 'header_verbose' in self.t:
481 elif self.ui.verbose and 'header_verbose' in self.t:
473 key = 'header_verbose'
482 key = 'header_verbose'
474 elif 'header' in self.t:
483 elif 'header' in self.t:
475 key = 'header'
484 key = 'header'
476 else:
485 else:
477 key = ''
486 key = ''
478 if key:
487 if key:
479 h = templater.stringify(self.t(key, **props))
488 h = templater.stringify(self.t(key, **props))
480 if self.buffered:
489 if self.buffered:
481 self.header[rev] = h
490 self.header[rev] = h
482 else:
491 else:
483 self.ui.write(h)
492 self.ui.write(h)
484 if self.ui.debugflag and 'changeset_debug' in self.t:
493 if self.ui.debugflag and 'changeset_debug' in self.t:
485 key = 'changeset_debug'
494 key = 'changeset_debug'
486 elif self.ui.quiet and 'changeset_quiet' in self.t:
495 elif self.ui.quiet and 'changeset_quiet' in self.t:
487 key = 'changeset_quiet'
496 key = 'changeset_quiet'
488 elif self.ui.verbose and 'changeset_verbose' in self.t:
497 elif self.ui.verbose and 'changeset_verbose' in self.t:
489 key = 'changeset_verbose'
498 key = 'changeset_verbose'
490 else:
499 else:
491 key = 'changeset'
500 key = 'changeset'
492 self.ui.write(templater.stringify(self.t(key, **props)))
501 self.ui.write(templater.stringify(self.t(key, **props)))
493 self.showpatch(changenode)
502 self.showpatch(changenode)
494 except KeyError, inst:
503 except KeyError, inst:
495 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
504 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
496 inst.args[0]))
505 inst.args[0]))
497 except SyntaxError, inst:
506 except SyntaxError, inst:
498 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
507 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
499
508
500 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
509 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
501 """show one changeset using template or regular display.
510 """show one changeset using template or regular display.
502
511
503 Display format will be the first non-empty hit of:
512 Display format will be the first non-empty hit of:
504 1. option 'template'
513 1. option 'template'
505 2. option 'style'
514 2. option 'style'
506 3. [ui] setting 'logtemplate'
515 3. [ui] setting 'logtemplate'
507 4. [ui] setting 'style'
516 4. [ui] setting 'style'
508 If all of these values are either the unset or the empty string,
517 If all of these values are either the unset or the empty string,
509 regular display via changeset_printer() is done.
518 regular display via changeset_printer() is done.
510 """
519 """
511 # options
520 # options
512 patch = False
521 patch = False
513 if opts.get('patch'):
522 if opts.get('patch'):
514 patch = matchfn or util.always
523 patch = matchfn or util.always
515
524
516 tmpl = opts.get('template')
525 tmpl = opts.get('template')
517 mapfile = None
526 mapfile = None
518 if tmpl:
527 if tmpl:
519 tmpl = templater.parsestring(tmpl, quoted=False)
528 tmpl = templater.parsestring(tmpl, quoted=False)
520 else:
529 else:
521 mapfile = opts.get('style')
530 mapfile = opts.get('style')
522 # ui settings
531 # ui settings
523 if not mapfile:
532 if not mapfile:
524 tmpl = ui.config('ui', 'logtemplate')
533 tmpl = ui.config('ui', 'logtemplate')
525 if tmpl:
534 if tmpl:
526 tmpl = templater.parsestring(tmpl)
535 tmpl = templater.parsestring(tmpl)
527 else:
536 else:
528 mapfile = ui.config('ui', 'style')
537 mapfile = ui.config('ui', 'style')
529
538
530 if tmpl or mapfile:
539 if tmpl or mapfile:
531 if mapfile:
540 if mapfile:
532 if not os.path.split(mapfile)[0]:
541 if not os.path.split(mapfile)[0]:
533 mapname = (templater.templatepath('map-cmdline.' + mapfile)
542 mapname = (templater.templatepath('map-cmdline.' + mapfile)
534 or templater.templatepath(mapfile))
543 or templater.templatepath(mapfile))
535 if mapname: mapfile = mapname
544 if mapname: mapfile = mapname
536 try:
545 try:
537 t = changeset_templater(ui, repo, patch, mapfile, buffered)
546 t = changeset_templater(ui, repo, patch, mapfile, buffered)
538 except SyntaxError, inst:
547 except SyntaxError, inst:
539 raise util.Abort(inst.args[0])
548 raise util.Abort(inst.args[0])
540 if tmpl: t.use_template(tmpl)
549 if tmpl: t.use_template(tmpl)
541 return t
550 return t
542 return changeset_printer(ui, repo, patch, buffered)
551 return changeset_printer(ui, repo, patch, buffered)
543
552
544 def finddate(ui, repo, date):
553 def finddate(ui, repo, date):
545 """Find the tipmost changeset that matches the given date spec"""
554 """Find the tipmost changeset that matches the given date spec"""
546 df = util.matchdate(date + " to " + date)
555 df = util.matchdate(date + " to " + date)
547 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
556 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
548 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
557 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
549 results = {}
558 results = {}
550 for st, rev, fns in changeiter:
559 for st, rev, fns in changeiter:
551 if st == 'add':
560 if st == 'add':
552 d = get(rev)[2]
561 d = get(rev)[2]
553 if df(d[0]):
562 if df(d[0]):
554 results[rev] = d
563 results[rev] = d
555 elif st == 'iter':
564 elif st == 'iter':
556 if rev in results:
565 if rev in results:
557 ui.status("Found revision %s from %s\n" %
566 ui.status("Found revision %s from %s\n" %
558 (rev, util.datestr(results[rev])))
567 (rev, util.datestr(results[rev])))
559 return str(rev)
568 return str(rev)
560
569
561 raise util.Abort(_("revision matching date not found"))
570 raise util.Abort(_("revision matching date not found"))
562
571
563 def walkchangerevs(ui, repo, pats, change, opts):
572 def walkchangerevs(ui, repo, pats, change, opts):
564 '''Iterate over files and the revs they changed in.
573 '''Iterate over files and the revs they changed in.
565
574
566 Callers most commonly need to iterate backwards over the history
575 Callers most commonly need to iterate backwards over the history
567 it is interested in. Doing so has awful (quadratic-looking)
576 it is interested in. Doing so has awful (quadratic-looking)
568 performance, so we use iterators in a "windowed" way.
577 performance, so we use iterators in a "windowed" way.
569
578
570 We walk a window of revisions in the desired order. Within the
579 We walk a window of revisions in the desired order. Within the
571 window, we first walk forwards to gather data, then in the desired
580 window, we first walk forwards to gather data, then in the desired
572 order (usually backwards) to display it.
581 order (usually backwards) to display it.
573
582
574 This function returns an (iterator, matchfn) tuple. The iterator
583 This function returns an (iterator, matchfn) tuple. The iterator
575 yields 3-tuples. They will be of one of the following forms:
584 yields 3-tuples. They will be of one of the following forms:
576
585
577 "window", incrementing, lastrev: stepping through a window,
586 "window", incrementing, lastrev: stepping through a window,
578 positive if walking forwards through revs, last rev in the
587 positive if walking forwards through revs, last rev in the
579 sequence iterated over - use to reset state for the current window
588 sequence iterated over - use to reset state for the current window
580
589
581 "add", rev, fns: out-of-order traversal of the given file names
590 "add", rev, fns: out-of-order traversal of the given file names
582 fns, which changed during revision rev - use to gather data for
591 fns, which changed during revision rev - use to gather data for
583 possible display
592 possible display
584
593
585 "iter", rev, None: in-order traversal of the revs earlier iterated
594 "iter", rev, None: in-order traversal of the revs earlier iterated
586 over with "add" - use to display data'''
595 over with "add" - use to display data'''
587
596
588 def increasing_windows(start, end, windowsize=8, sizelimit=512):
597 def increasing_windows(start, end, windowsize=8, sizelimit=512):
589 if start < end:
598 if start < end:
590 while start < end:
599 while start < end:
591 yield start, min(windowsize, end-start)
600 yield start, min(windowsize, end-start)
592 start += windowsize
601 start += windowsize
593 if windowsize < sizelimit:
602 if windowsize < sizelimit:
594 windowsize *= 2
603 windowsize *= 2
595 else:
604 else:
596 while start > end:
605 while start > end:
597 yield start, min(windowsize, start-end-1)
606 yield start, min(windowsize, start-end-1)
598 start -= windowsize
607 start -= windowsize
599 if windowsize < sizelimit:
608 if windowsize < sizelimit:
600 windowsize *= 2
609 windowsize *= 2
601
610
602 files, matchfn, anypats = matchpats(repo, pats, opts)
611 files, matchfn, anypats = matchpats(repo, pats, opts)
603 follow = opts.get('follow') or opts.get('follow_first')
612 follow = opts.get('follow') or opts.get('follow_first')
604
613
605 if repo.changelog.count() == 0:
614 if repo.changelog.count() == 0:
606 return [], matchfn
615 return [], matchfn
607
616
608 if follow:
617 if follow:
609 defrange = '%s:0' % repo.changectx().rev()
618 defrange = '%s:0' % repo.changectx().rev()
610 else:
619 else:
611 defrange = 'tip:0'
620 defrange = 'tip:0'
612 revs = revrange(repo, opts['rev'] or [defrange])
621 revs = revrange(repo, opts['rev'] or [defrange])
613 wanted = {}
622 wanted = {}
614 slowpath = anypats or opts.get('removed')
623 slowpath = anypats or opts.get('removed')
615 fncache = {}
624 fncache = {}
616
625
617 if not slowpath and not files:
626 if not slowpath and not files:
618 # No files, no patterns. Display all revs.
627 # No files, no patterns. Display all revs.
619 wanted = dict.fromkeys(revs)
628 wanted = dict.fromkeys(revs)
620 copies = []
629 copies = []
621 if not slowpath:
630 if not slowpath:
622 # Only files, no patterns. Check the history of each file.
631 # Only files, no patterns. Check the history of each file.
623 def filerevgen(filelog, node):
632 def filerevgen(filelog, node):
624 cl_count = repo.changelog.count()
633 cl_count = repo.changelog.count()
625 if node is None:
634 if node is None:
626 last = filelog.count() - 1
635 last = filelog.count() - 1
627 else:
636 else:
628 last = filelog.rev(node)
637 last = filelog.rev(node)
629 for i, window in increasing_windows(last, nullrev):
638 for i, window in increasing_windows(last, nullrev):
630 revs = []
639 revs = []
631 for j in xrange(i - window, i + 1):
640 for j in xrange(i - window, i + 1):
632 n = filelog.node(j)
641 n = filelog.node(j)
633 revs.append((filelog.linkrev(n),
642 revs.append((filelog.linkrev(n),
634 follow and filelog.renamed(n)))
643 follow and filelog.renamed(n)))
635 revs.reverse()
644 revs.reverse()
636 for rev in revs:
645 for rev in revs:
637 # only yield rev for which we have the changelog, it can
646 # only yield rev for which we have the changelog, it can
638 # happen while doing "hg log" during a pull or commit
647 # happen while doing "hg log" during a pull or commit
639 if rev[0] < cl_count:
648 if rev[0] < cl_count:
640 yield rev
649 yield rev
641 def iterfiles():
650 def iterfiles():
642 for filename in files:
651 for filename in files:
643 yield filename, None
652 yield filename, None
644 for filename_node in copies:
653 for filename_node in copies:
645 yield filename_node
654 yield filename_node
646 minrev, maxrev = min(revs), max(revs)
655 minrev, maxrev = min(revs), max(revs)
647 for file_, node in iterfiles():
656 for file_, node in iterfiles():
648 filelog = repo.file(file_)
657 filelog = repo.file(file_)
649 # A zero count may be a directory or deleted file, so
658 # A zero count may be a directory or deleted file, so
650 # try to find matching entries on the slow path.
659 # try to find matching entries on the slow path.
651 if filelog.count() == 0:
660 if filelog.count() == 0:
652 slowpath = True
661 slowpath = True
653 break
662 break
654 for rev, copied in filerevgen(filelog, node):
663 for rev, copied in filerevgen(filelog, node):
655 if rev <= maxrev:
664 if rev <= maxrev:
656 if rev < minrev:
665 if rev < minrev:
657 break
666 break
658 fncache.setdefault(rev, [])
667 fncache.setdefault(rev, [])
659 fncache[rev].append(file_)
668 fncache[rev].append(file_)
660 wanted[rev] = 1
669 wanted[rev] = 1
661 if follow and copied:
670 if follow and copied:
662 copies.append(copied)
671 copies.append(copied)
663 if slowpath:
672 if slowpath:
664 if follow:
673 if follow:
665 raise util.Abort(_('can only follow copies/renames for explicit '
674 raise util.Abort(_('can only follow copies/renames for explicit '
666 'file names'))
675 'file names'))
667
676
668 # The slow path checks files modified in every changeset.
677 # The slow path checks files modified in every changeset.
669 def changerevgen():
678 def changerevgen():
670 for i, window in increasing_windows(repo.changelog.count()-1,
679 for i, window in increasing_windows(repo.changelog.count()-1,
671 nullrev):
680 nullrev):
672 for j in xrange(i - window, i + 1):
681 for j in xrange(i - window, i + 1):
673 yield j, change(j)[3]
682 yield j, change(j)[3]
674
683
675 for rev, changefiles in changerevgen():
684 for rev, changefiles in changerevgen():
676 matches = filter(matchfn, changefiles)
685 matches = filter(matchfn, changefiles)
677 if matches:
686 if matches:
678 fncache[rev] = matches
687 fncache[rev] = matches
679 wanted[rev] = 1
688 wanted[rev] = 1
680
689
681 class followfilter:
690 class followfilter:
682 def __init__(self, onlyfirst=False):
691 def __init__(self, onlyfirst=False):
683 self.startrev = nullrev
692 self.startrev = nullrev
684 self.roots = []
693 self.roots = []
685 self.onlyfirst = onlyfirst
694 self.onlyfirst = onlyfirst
686
695
687 def match(self, rev):
696 def match(self, rev):
688 def realparents(rev):
697 def realparents(rev):
689 if self.onlyfirst:
698 if self.onlyfirst:
690 return repo.changelog.parentrevs(rev)[0:1]
699 return repo.changelog.parentrevs(rev)[0:1]
691 else:
700 else:
692 return filter(lambda x: x != nullrev,
701 return filter(lambda x: x != nullrev,
693 repo.changelog.parentrevs(rev))
702 repo.changelog.parentrevs(rev))
694
703
695 if self.startrev == nullrev:
704 if self.startrev == nullrev:
696 self.startrev = rev
705 self.startrev = rev
697 return True
706 return True
698
707
699 if rev > self.startrev:
708 if rev > self.startrev:
700 # forward: all descendants
709 # forward: all descendants
701 if not self.roots:
710 if not self.roots:
702 self.roots.append(self.startrev)
711 self.roots.append(self.startrev)
703 for parent in realparents(rev):
712 for parent in realparents(rev):
704 if parent in self.roots:
713 if parent in self.roots:
705 self.roots.append(rev)
714 self.roots.append(rev)
706 return True
715 return True
707 else:
716 else:
708 # backwards: all parents
717 # backwards: all parents
709 if not self.roots:
718 if not self.roots:
710 self.roots.extend(realparents(self.startrev))
719 self.roots.extend(realparents(self.startrev))
711 if rev in self.roots:
720 if rev in self.roots:
712 self.roots.remove(rev)
721 self.roots.remove(rev)
713 self.roots.extend(realparents(rev))
722 self.roots.extend(realparents(rev))
714 return True
723 return True
715
724
716 return False
725 return False
717
726
718 # it might be worthwhile to do this in the iterator if the rev range
727 # it might be worthwhile to do this in the iterator if the rev range
719 # is descending and the prune args are all within that range
728 # is descending and the prune args are all within that range
720 for rev in opts.get('prune', ()):
729 for rev in opts.get('prune', ()):
721 rev = repo.changelog.rev(repo.lookup(rev))
730 rev = repo.changelog.rev(repo.lookup(rev))
722 ff = followfilter()
731 ff = followfilter()
723 stop = min(revs[0], revs[-1])
732 stop = min(revs[0], revs[-1])
724 for x in xrange(rev, stop-1, -1):
733 for x in xrange(rev, stop-1, -1):
725 if ff.match(x) and x in wanted:
734 if ff.match(x) and x in wanted:
726 del wanted[x]
735 del wanted[x]
727
736
728 def iterate():
737 def iterate():
729 if follow and not files:
738 if follow and not files:
730 ff = followfilter(onlyfirst=opts.get('follow_first'))
739 ff = followfilter(onlyfirst=opts.get('follow_first'))
731 def want(rev):
740 def want(rev):
732 if ff.match(rev) and rev in wanted:
741 if ff.match(rev) and rev in wanted:
733 return True
742 return True
734 return False
743 return False
735 else:
744 else:
736 def want(rev):
745 def want(rev):
737 return rev in wanted
746 return rev in wanted
738
747
739 for i, window in increasing_windows(0, len(revs)):
748 for i, window in increasing_windows(0, len(revs)):
740 yield 'window', revs[0] < revs[-1], revs[-1]
749 yield 'window', revs[0] < revs[-1], revs[-1]
741 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
750 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
742 srevs = list(nrevs)
751 srevs = list(nrevs)
743 srevs.sort()
752 srevs.sort()
744 for rev in srevs:
753 for rev in srevs:
745 fns = fncache.get(rev)
754 fns = fncache.get(rev)
746 if not fns:
755 if not fns:
747 def fns_generator():
756 def fns_generator():
748 for f in change(rev)[3]:
757 for f in change(rev)[3]:
749 if matchfn(f):
758 if matchfn(f):
750 yield f
759 yield f
751 fns = fns_generator()
760 fns = fns_generator()
752 yield 'add', rev, fns
761 yield 'add', rev, fns
753 for rev in nrevs:
762 for rev in nrevs:
754 yield 'iter', rev, None
763 yield 'iter', rev, None
755 return iterate(), matchfn
764 return iterate(), matchfn
General Comments 0
You need to be logged in to leave comments. Login now