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