comparison hgext/graphlog.py @ 16186:af3e67354beb

graphlog: apply file filters --patch/--stat output When passing --patch/--stat, file filters have to be applied to generate the correct diff or stat output: - Without --follow, the static match object can be reused - With --follow, the files displayed at revision X are the ancestors of selected files at parent revision. To do this, we reproduce the ancestry calculations done by --follow, lazily. test-glog.t changes show that --patch output is not satisfying because renames are reported as copies. This can probably be fixed by: - Without --follow: compute files to display, look for renames sources and extend the matcher to include them. - With --follow: detect .path() transitions between parent/child filectx, filter them using the linked changectx .removed() field and extend fcache with them.
author Patrick Mezard <patrick@mezard.eu>
date Sun, 26 Feb 2012 17:12:15 +0100
parents 6863caf01daa
children 16ec050490fc
comparison
equal deleted inserted replaced
16185:352053e6cd8e 16186:af3e67354beb
240 for op in ["newest_first"]: 240 for op in ["newest_first"]:
241 if op in opts and opts[op]: 241 if op in opts and opts[op]:
242 raise util.Abort(_("-G/--graph option is incompatible with --%s") 242 raise util.Abort(_("-G/--graph option is incompatible with --%s")
243 % op.replace("_", "-")) 243 % op.replace("_", "-"))
244 244
245 def makefilematcher(repo, pats, followfirst):
246 # When displaying a revision with --patch --follow FILE, we have
247 # to know which file of the revision must be diffed. With
248 # --follow, we want the names of the ancestors of FILE in the
249 # revision, stored in "fcache". "fcache" is populated by
250 # reproducing the graph traversal already done by --follow revset
251 # and relating linkrevs to file names (which is not "correct" but
252 # good enough).
253 fcache = {}
254 fcacheready = [False]
255 pctx = repo['.']
256 wctx = repo[None]
257
258 def populate():
259 for fn in pats:
260 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
261 for c in i:
262 fcache.setdefault(c.linkrev(), set()).add(c.path())
263
264 def filematcher(rev):
265 if not fcacheready[0]:
266 # Lazy initialization
267 fcacheready[0] = True
268 populate()
269 return scmutil.match(wctx, fcache.get(rev, []), default='path')
270
271 return filematcher
272
245 def revset(repo, pats, opts): 273 def revset(repo, pats, opts):
246 """Return revset str built of revisions, log options and file patterns. 274 """Return (expr, filematcher) where expr is a revset string built
275 of revisions, log options and file patterns. If --stat or --patch
276 are not passed filematcher is None. Otherwise it a a callable
277 taking a revision number and returning a match objects filtering
278 the files to be detailed when displaying the revision.
247 """ 279 """
248 opt2revset = { 280 opt2revset = {
249 'follow': ('follow()', None), 281 'follow': ('follow()', None),
250 'follow_first': ('_followfirst()', None), 282 'follow_first': ('_followfirst()', None),
251 'no_merges': ('not merge()', None), 283 'no_merges': ('not merge()', None),
327 else: 359 else:
328 opts['follow'] = True 360 opts['follow'] = True
329 else: 361 else:
330 opts['_patslog'] = list(pats) 362 opts['_patslog'] = list(pats)
331 363
364 filematcher = None
365 if opts.get('patch') or opts.get('stat'):
366 if follow:
367 filematcher = makefilematcher(repo, pats, followfirst)
368 else:
369 filematcher = lambda rev: match
370
332 revset = [] 371 revset = []
333 for op, val in opts.iteritems(): 372 for op, val in opts.iteritems():
334 if not val: 373 if not val:
335 continue 374 continue
336 if op not in opt2revset: 375 if op not in opt2revset:
347 386
348 if revset: 387 if revset:
349 revset = '(' + ' and '.join(revset) + ')' 388 revset = '(' + ' and '.join(revset) + ')'
350 else: 389 else:
351 revset = 'all()' 390 revset = 'all()'
352 return revset 391 return revset, filematcher
353 392
354 def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None): 393 def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None,
394 filematcher=None):
355 seen, state = [], asciistate() 395 seen, state = [], asciistate()
356 for rev, type, ctx, parents in dag: 396 for rev, type, ctx, parents in dag:
357 char = ctx.node() in showparents and '@' or 'o' 397 char = ctx.node() in showparents and '@' or 'o'
358 copies = None 398 copies = None
359 if getrenamed and ctx.rev(): 399 if getrenamed and ctx.rev():
360 copies = [] 400 copies = []
361 for fn in ctx.files(): 401 for fn in ctx.files():
362 rename = getrenamed(fn, ctx.rev()) 402 rename = getrenamed(fn, ctx.rev())
363 if rename: 403 if rename:
364 copies.append((fn, rename[0])) 404 copies.append((fn, rename[0]))
365 displayer.show(ctx, copies=copies) 405 revmatchfn = None
406 if filematcher is not None:
407 revmatchfn = filematcher(ctx.rev())
408 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
366 lines = displayer.hunk.pop(rev).split('\n')[:-1] 409 lines = displayer.hunk.pop(rev).split('\n')[:-1]
367 displayer.flush(rev) 410 displayer.flush(rev)
368 edges = edgefn(type, char, lines, seen, rev, parents) 411 edges = edgefn(type, char, lines, seen, rev, parents)
369 for type, char, lines, coldata in edges: 412 for type, char, lines, coldata in edges:
370 ascii(ui, state, type, char, lines, coldata) 413 ascii(ui, state, type, char, lines, coldata)
387 directory. 430 directory.
388 """ 431 """
389 432
390 check_unsupported_flags(pats, opts) 433 check_unsupported_flags(pats, opts)
391 434
392 revs = sorted(scmutil.revrange(repo, [revset(repo, pats, opts)]), reverse=1) 435 expr, filematcher = revset(repo, pats, opts)
436 revs = sorted(scmutil.revrange(repo, [expr]), reverse=1)
393 limit = cmdutil.loglimit(opts) 437 limit = cmdutil.loglimit(opts)
394 if limit is not None: 438 if limit is not None:
395 revs = revs[:limit] 439 revs = revs[:limit]
396 revdag = graphmod.dagwalker(repo, revs) 440 revdag = graphmod.dagwalker(repo, revs)
397 441
401 if opts.get('rev'): 445 if opts.get('rev'):
402 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1 446 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
403 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) 447 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
404 displayer = show_changeset(ui, repo, opts, buffered=True) 448 displayer = show_changeset(ui, repo, opts, buffered=True)
405 showparents = [ctx.node() for ctx in repo[None].parents()] 449 showparents = [ctx.node() for ctx in repo[None].parents()]
406 generate(ui, revdag, displayer, showparents, asciiedges, getrenamed) 450 generate(ui, revdag, displayer, showparents, asciiedges, getrenamed,
451 filematcher)
407 452
408 def graphrevs(repo, nodes, opts): 453 def graphrevs(repo, nodes, opts):
409 limit = cmdutil.loglimit(opts) 454 limit = cmdutil.loglimit(opts)
410 nodes.reverse() 455 nodes.reverse()
411 if limit is not None: 456 if limit is not None: