Mercurial > hg
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: |