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