Mercurial > hg
comparison hgext/absorb.py @ 40187:dcda50856843
absorb: use a formatter to generate output
Change absorb to use a formatter to generate its output. This allows the use
of templates to customize the output.
Differential Revision: https://phab.mercurial-scm.org/D4997
author | Mark Thomas <mbthomas@fb.com> |
---|---|
date | Fri, 12 Oct 2018 13:35:58 +0000 |
parents | 1be1689d9ce9 |
children | 2c5316796f45 |
comparison
equal
deleted
inserted
replaced
40186:9cbc2579f5be | 40187:dcda50856843 |
---|---|
272 2. (optionally), present self.fixups to the user, or change it | 272 2. (optionally), present self.fixups to the user, or change it |
273 3. call apply, to apply changes | 273 3. call apply, to apply changes |
274 4. read results from "finalcontents", or call getfinalcontent | 274 4. read results from "finalcontents", or call getfinalcontent |
275 """ | 275 """ |
276 | 276 |
277 def __init__(self, fctxs, ui=None, opts=None): | 277 def __init__(self, fctxs, path, ui=None, opts=None): |
278 """([fctx], ui or None) -> None | 278 """([fctx], ui or None) -> None |
279 | 279 |
280 fctxs should be linear, and sorted by topo order - oldest first. | 280 fctxs should be linear, and sorted by topo order - oldest first. |
281 fctxs[0] will be considered as "immutable" and will not be changed. | 281 fctxs[0] will be considered as "immutable" and will not be changed. |
282 """ | 282 """ |
283 self.fctxs = fctxs | 283 self.fctxs = fctxs |
284 self.path = path | |
284 self.ui = ui or nullui() | 285 self.ui = ui or nullui() |
285 self.opts = opts or {} | 286 self.opts = opts or {} |
286 | 287 |
287 # following fields are built from fctxs. they exist for perf reason | 288 # following fields are built from fctxs. they exist for perf reason |
288 self.contents = [f.data() for f in fctxs] | 289 self.contents = [f.data() for f in fctxs] |
295 self.chunkstats = [0, 0] # [adopted, total : int] | 296 self.chunkstats = [0, 0] # [adopted, total : int] |
296 self.targetlines = [] # [str] | 297 self.targetlines = [] # [str] |
297 self.fixups = [] # [(linelog rev, a1, a2, b1, b2)] | 298 self.fixups = [] # [(linelog rev, a1, a2, b1, b2)] |
298 self.finalcontents = [] # [str] | 299 self.finalcontents = [] # [str] |
299 | 300 |
300 def diffwith(self, targetfctx, showchanges=False): | 301 def diffwith(self, targetfctx, fm=None): |
301 """calculate fixups needed by examining the differences between | 302 """calculate fixups needed by examining the differences between |
302 self.fctxs[-1] and targetfctx, chunk by chunk. | 303 self.fctxs[-1] and targetfctx, chunk by chunk. |
303 | 304 |
304 targetfctx is the target state we move towards. we may or may not be | 305 targetfctx is the target state we move towards. we may or may not be |
305 able to get there because not all modified chunks can be amended into | 306 able to get there because not all modified chunks can be amended into |
327 for chunk in self._alldiffchunks(a, b, alines, blines): | 328 for chunk in self._alldiffchunks(a, b, alines, blines): |
328 newfixups = self._analysediffchunk(chunk, annotated) | 329 newfixups = self._analysediffchunk(chunk, annotated) |
329 self.chunkstats[0] += bool(newfixups) # 1 or 0 | 330 self.chunkstats[0] += bool(newfixups) # 1 or 0 |
330 self.chunkstats[1] += 1 | 331 self.chunkstats[1] += 1 |
331 self.fixups += newfixups | 332 self.fixups += newfixups |
332 if showchanges: | 333 if fm is not None: |
333 self._showchanges(alines, blines, chunk, newfixups) | 334 self._showchanges(fm, alines, blines, chunk, newfixups) |
334 | 335 |
335 def apply(self): | 336 def apply(self): |
336 """apply self.fixups. update self.linelog, self.finalcontents. | 337 """apply self.fixups. update self.linelog, self.finalcontents. |
337 | 338 |
338 call this only once, before getfinalcontent(), after diffwith(). | 339 call this only once, before getfinalcontent(), after diffwith(). |
544 pushchunk() | 545 pushchunk() |
545 pcurrentchunk[0] = list(chunk) | 546 pcurrentchunk[0] = list(chunk) |
546 pushchunk() | 547 pushchunk() |
547 return result | 548 return result |
548 | 549 |
549 def _showchanges(self, alines, blines, chunk, fixups): | 550 def _showchanges(self, fm, alines, blines, chunk, fixups): |
550 ui = self.ui | 551 |
551 | 552 def trim(line): |
552 def label(line, label): | |
553 if line.endswith('\n'): | 553 if line.endswith('\n'): |
554 line = line[:-1] | 554 line = line[:-1] |
555 return ui.label(line, label) | 555 return line |
556 | 556 |
557 # this is not optimized for perf but _showchanges only gets executed | 557 # this is not optimized for perf but _showchanges only gets executed |
558 # with an extra command-line flag. | 558 # with an extra command-line flag. |
559 a1, a2, b1, b2 = chunk | 559 a1, a2, b1, b2 = chunk |
560 aidxs, bidxs = [0] * (a2 - a1), [0] * (b2 - b1) | 560 aidxs, bidxs = [0] * (a2 - a1), [0] * (b2 - b1) |
562 for i in pycompat.xrange(fa1, fa2): | 562 for i in pycompat.xrange(fa1, fa2): |
563 aidxs[i - a1] = (max(idx, 1) - 1) // 2 | 563 aidxs[i - a1] = (max(idx, 1) - 1) // 2 |
564 for i in pycompat.xrange(fb1, fb2): | 564 for i in pycompat.xrange(fb1, fb2): |
565 bidxs[i - b1] = (max(idx, 1) - 1) // 2 | 565 bidxs[i - b1] = (max(idx, 1) - 1) // 2 |
566 | 566 |
567 buf = [] # [(idx, content)] | 567 fm.startitem() |
568 buf.append((0, label('@@ -%d,%d +%d,%d @@' | 568 fm.write('hunk', ' %s\n', |
569 % (a1, a2 - a1, b1, b2 - b1), 'diff.hunk'))) | 569 '@@ -%d,%d +%d,%d @@' |
570 buf += [(aidxs[i - a1], label('-' + alines[i], 'diff.deleted')) | 570 % (a1, a2 - a1, b1, b2 - b1), label='diff.hunk') |
571 for i in pycompat.xrange(a1, a2)] | 571 fm.data(path=self.path, linetype='hunk') |
572 buf += [(bidxs[i - b1], label('+' + blines[i], 'diff.inserted')) | 572 |
573 for i in pycompat.xrange(b1, b2)] | 573 def writeline(idx, diffchar, line, linetype, linelabel): |
574 for idx, line in buf: | 574 fm.startitem() |
575 shortnode = idx and node.short(self.fctxs[idx].node()) or '' | 575 node = '' |
576 ui.write(ui.label(shortnode[0:7].ljust(8), 'absorb.node') + | 576 if idx: |
577 line + '\n') | 577 ctx = self.fctxs[idx] |
578 fm.context(fctx=ctx) | |
579 node = ctx.hex() | |
580 fm.write('node', '%-7.7s ', node, label='absorb.node') | |
581 fm.write('diffchar ' + linetype, '%s%s\n', diffchar, line, | |
582 label=linelabel) | |
583 fm.data(path=self.path, linetype=linetype) | |
584 | |
585 for i in pycompat.xrange(a1, a2): | |
586 writeline(aidxs[i - a1], '-', trim(alines[i]), 'deleted', | |
587 'diff.deleted') | |
588 for i in pycompat.xrange(b1, b2): | |
589 writeline(bidxs[i - b1], '+', trim(blines[i]), 'inserted', | |
590 'diff.inserted') | |
578 | 591 |
579 class fixupstate(object): | 592 class fixupstate(object): |
580 """state needed to run absorb | 593 """state needed to run absorb |
581 | 594 |
582 internally, it keeps paths and filefixupstates. | 595 internally, it keeps paths and filefixupstates. |
607 self.fctxmap = {} # {path: {ctx: fctx}} | 620 self.fctxmap = {} # {path: {ctx: fctx}} |
608 self.fixupmap = {} # {path: filefixupstate} | 621 self.fixupmap = {} # {path: filefixupstate} |
609 self.replacemap = {} # {oldnode: newnode or None} | 622 self.replacemap = {} # {oldnode: newnode or None} |
610 self.finalnode = None # head after all fixups | 623 self.finalnode = None # head after all fixups |
611 | 624 |
612 def diffwith(self, targetctx, match=None, showchanges=False): | 625 def diffwith(self, targetctx, match=None, fm=None): |
613 """diff and prepare fixups. update self.fixupmap, self.paths""" | 626 """diff and prepare fixups. update self.fixupmap, self.paths""" |
614 # only care about modified files | 627 # only care about modified files |
615 self.status = self.stack[-1].status(targetctx, match) | 628 self.status = self.stack[-1].status(targetctx, match) |
616 self.paths = [] | 629 self.paths = [] |
617 # but if --edit-lines is used, the user may want to edit files | 630 # but if --edit-lines is used, the user may want to edit files |
636 continue | 649 continue |
637 if targetfctx.data() == fctxs[-1].data() and not editopt: | 650 if targetfctx.data() == fctxs[-1].data() and not editopt: |
638 continue | 651 continue |
639 seenfctxs.update(fctxs[1:]) | 652 seenfctxs.update(fctxs[1:]) |
640 self.fctxmap[path] = ctx2fctx | 653 self.fctxmap[path] = ctx2fctx |
641 fstate = filefixupstate(fctxs, ui=self.ui, opts=self.opts) | 654 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts) |
642 if showchanges: | 655 if fm is not None: |
643 colorpath = self.ui.label(path, 'absorb.path') | 656 fm.startitem() |
644 header = 'showing changes for ' + colorpath | 657 fm.plain('showing changes for ') |
645 self.ui.write(header + '\n') | 658 fm.write('path', '%s\n', path, label='absorb.path') |
646 fstate.diffwith(targetfctx, showchanges=showchanges) | 659 fm.data(linetype='path') |
660 fstate.diffwith(targetfctx, fm) | |
647 self.fixupmap[path] = fstate | 661 self.fixupmap[path] = fstate |
648 self.paths.append(path) | 662 self.paths.append(path) |
649 | 663 |
650 def apply(self): | 664 def apply(self): |
651 """apply fixups to individual filefixupstates""" | 665 """apply fixups to individual filefixupstates""" |
929 if opts.get('interactive'): | 943 if opts.get('interactive'): |
930 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher) | 944 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher) |
931 origchunks = patch.parsepatch(diff) | 945 origchunks = patch.parsepatch(diff) |
932 chunks = cmdutil.recordfilter(ui, origchunks)[0] | 946 chunks = cmdutil.recordfilter(ui, origchunks)[0] |
933 targetctx = overlaydiffcontext(stack[-1], chunks) | 947 targetctx = overlaydiffcontext(stack[-1], chunks) |
934 state.diffwith(targetctx, matcher, showchanges=opts.get('print_changes')) | 948 fm = None |
949 if opts.get('print_changes'): | |
950 fm = ui.formatter('absorb', opts) | |
951 state.diffwith(targetctx, matcher, fm) | |
952 if fm is not None: | |
953 fm.end() | |
935 if not opts.get('dry_run'): | 954 if not opts.get('dry_run'): |
936 state.apply() | 955 state.apply() |
937 if state.commit(): | 956 if state.commit(): |
938 state.printchunkstats() | 957 state.printchunkstats() |
939 elif not ui.quiet: | 958 elif not ui.quiet: |
946 ('i', 'interactive', None, | 965 ('i', 'interactive', None, |
947 _('interactively select which chunks to apply (EXPERIMENTAL)')), | 966 _('interactively select which chunks to apply (EXPERIMENTAL)')), |
948 ('e', 'edit-lines', None, | 967 ('e', 'edit-lines', None, |
949 _('edit what lines belong to which changesets before commit ' | 968 _('edit what lines belong to which changesets before commit ' |
950 '(EXPERIMENTAL)')), | 969 '(EXPERIMENTAL)')), |
951 ] + commands.dryrunopts + commands.walkopts, | 970 ] + commands.dryrunopts + commands.templateopts + commands.walkopts, |
952 _('hg absorb [OPTION] [FILE]...')) | 971 _('hg absorb [OPTION] [FILE]...')) |
953 def absorbcmd(ui, repo, *pats, **opts): | 972 def absorbcmd(ui, repo, *pats, **opts): |
954 """incorporate corrections into the stack of draft changesets | 973 """incorporate corrections into the stack of draft changesets |
955 | 974 |
956 absorb analyzes each change in your working directory and attempts to | 975 absorb analyzes each change in your working directory and attempts to |