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