diff --git a/contrib/perf.py b/contrib/perf.py --- a/contrib/perf.py +++ b/contrib/perf.py @@ -1031,6 +1031,71 @@ with ready: ready.notify_all() +@command('perfunidiff', revlogopts + formatteropts + [ + ('', 'count', 1, 'number of revisions to test (when using --startrev)'), + ('', 'alldata', False, 'test unidiffs for all associated revisions'), + ], '-c|-m|FILE REV') +def perfunidiff(ui, repo, file_, rev=None, count=None, **opts): + """benchmark a unified diff between revisions + + This doesn't include any copy tracing - it's just a unified diff + of the texts. + + By default, benchmark a diff between its delta parent and itself. + + With ``--count``, benchmark diffs between delta parents and self for N + revisions starting at the specified revision. + + With ``--alldata``, assume the requested revision is a changeset and + measure diffs for all changes related to that changeset (manifest + and filelogs). + """ + if opts['alldata']: + opts['changelog'] = True + + if opts.get('changelog') or opts.get('manifest'): + file_, rev = None, file_ + elif rev is None: + raise error.CommandError('perfunidiff', 'invalid arguments') + + textpairs = [] + + r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts) + + startrev = r.rev(r.lookup(rev)) + for rev in range(startrev, min(startrev + count, len(r) - 1)): + if opts['alldata']: + # Load revisions associated with changeset. + ctx = repo[rev] + mtext = repo.manifestlog._revlog.revision(ctx.manifestnode()) + for pctx in ctx.parents(): + pman = repo.manifestlog._revlog.revision(pctx.manifestnode()) + textpairs.append((pman, mtext)) + + # Load filelog revisions by iterating manifest delta. + man = ctx.manifest() + pman = ctx.p1().manifest() + for filename, change in pman.diff(man).items(): + fctx = repo.file(filename) + f1 = fctx.revision(change[0][0] or -1) + f2 = fctx.revision(change[1][0] or -1) + textpairs.append((f1, f2)) + else: + dp = r.deltaparent(rev) + textpairs.append((r.revision(dp), r.revision(rev))) + + def d(): + for left, right in textpairs: + # The date strings don't matter, so we pass empty strings. + headerlines, hunks = mdiff.unidiff( + left, '', right, '', 'left', 'right') + # consume iterators in roughly the way patch.py does + b'\n'.join(headerlines) + b''.join(sum((list(hlines) for hrange, hlines in hunks), [])) + timer, fm = gettimer(ui, opts) + timer(d) + fm.end() + @command('perfdiffwd', formatteropts) def perfdiffwd(ui, repo, **opts): """Profile diff of working directory changes""" diff --git a/tests/test-contrib-perf.t b/tests/test-contrib-perf.t --- a/tests/test-contrib-perf.t +++ b/tests/test-contrib-perf.t @@ -114,6 +114,7 @@ perftags (no help text available) perftemplating (no help text available) + perfunidiff benchmark a unified diff between revisions perfvolatilesets benchmark the computation of various volatile set perfwalk (no help text available) @@ -126,6 +127,8 @@ $ hg perfannotate a $ hg perfbdiff -c 1 $ hg perfbdiff --alldata 1 + $ hg perfunidiff -c 1 + $ hg perfunidiff --alldata 1 $ hg perfbookmarks $ hg perfbranchmap $ hg perfcca