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