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