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