297 2. (optionally), present self.fixups to the user, or change it |
297 2. (optionally), present self.fixups to the user, or change it |
298 3. call apply, to apply changes |
298 3. call apply, to apply changes |
299 4. read results from "finalcontents", or call getfinalcontent |
299 4. read results from "finalcontents", or call getfinalcontent |
300 """ |
300 """ |
301 |
301 |
302 def __init__(self, fctxs, path, ui=None, opts=None): |
302 def __init__(self, fctxs, path, ui=None, **opts): |
303 """([fctx], ui or None) -> None |
303 """([fctx], ui or None) -> None |
304 |
304 |
305 fctxs should be linear, and sorted by topo order - oldest first. |
305 fctxs should be linear, and sorted by topo order - oldest first. |
306 fctxs[0] will be considered as "immutable" and will not be changed. |
306 fctxs[0] will be considered as "immutable" and will not be changed. |
307 """ |
307 """ |
308 self.fctxs = fctxs |
308 self.fctxs = fctxs |
309 self.path = path |
309 self.path = path |
310 self.ui = ui or nullui() |
310 self.ui = ui or nullui() |
311 self.opts = opts or {} |
311 self.opts = opts |
312 |
312 |
313 # following fields are built from fctxs. they exist for perf reason |
313 # following fields are built from fctxs. they exist for perf reason |
314 self.contents = [f.data() for f in fctxs] |
314 self.contents = [f.data() for f in fctxs] |
315 self.contentlines = pycompat.maplist(mdiff.splitnewlines, self.contents) |
315 self.contentlines = pycompat.maplist(mdiff.splitnewlines, self.contents) |
316 self.linelog = self._buildlinelog() |
316 self.linelog = self._buildlinelog() |
373 self.ui.write( |
373 self.ui.write( |
374 _(b'%s: chunk %d:%d -> %d lines\n') |
374 _(b'%s: chunk %d:%d -> %d lines\n') |
375 % (short(self.fctxs[idx].node()), a1, a2, len(blines)) |
375 % (short(self.fctxs[idx].node()), a1, a2, len(blines)) |
376 ) |
376 ) |
377 self.linelog.replacelines(rev, a1, a2, b1, b2) |
377 self.linelog.replacelines(rev, a1, a2, b1, b2) |
378 if self.opts.get(b'edit_lines', False): |
378 if self.opts.get('edit_lines', False): |
379 self.finalcontents = self._checkoutlinelogwithedits() |
379 self.finalcontents = self._checkoutlinelogwithedits() |
380 else: |
380 else: |
381 self.finalcontents = self._checkoutlinelog() |
381 self.finalcontents = self._checkoutlinelog() |
382 |
382 |
383 def getfinalcontent(self, fctx): |
383 def getfinalcontent(self, fctx): |
666 2. (optionally), present fixups to the user, or edit fixups |
666 2. (optionally), present fixups to the user, or edit fixups |
667 3. call apply, to apply changes to memory |
667 3. call apply, to apply changes to memory |
668 4. call commit, to commit changes to hg database |
668 4. call commit, to commit changes to hg database |
669 """ |
669 """ |
670 |
670 |
671 def __init__(self, stack, ui=None, opts=None): |
671 def __init__(self, stack, ui=None, **opts): |
672 """([ctx], ui or None) -> None |
672 """([ctx], ui or None) -> None |
673 |
673 |
674 stack: should be linear, and sorted by topo order - oldest first. |
674 stack: should be linear, and sorted by topo order - oldest first. |
675 all commits in stack are considered mutable. |
675 all commits in stack are considered mutable. |
676 """ |
676 """ |
677 assert stack |
677 assert stack |
678 self.ui = ui or nullui() |
678 self.ui = ui or nullui() |
679 self.opts = opts or {} |
679 self.opts = opts |
680 self.stack = stack |
680 self.stack = stack |
681 self.repo = stack[-1].repo().unfiltered() |
681 self.repo = stack[-1].repo().unfiltered() |
682 |
682 |
683 # following fields will be filled later |
683 # following fields will be filled later |
684 self.paths = [] # [str] |
684 self.paths = [] # [str] |
694 # only care about modified files |
694 # only care about modified files |
695 self.status = self.stack[-1].status(targetctx, match) |
695 self.status = self.stack[-1].status(targetctx, match) |
696 self.paths = [] |
696 self.paths = [] |
697 # but if --edit-lines is used, the user may want to edit files |
697 # but if --edit-lines is used, the user may want to edit files |
698 # even if they are not modified |
698 # even if they are not modified |
699 editopt = self.opts.get(b'edit_lines') |
699 editopt = self.opts.get('edit_lines') |
700 if not self.status.modified and editopt and match: |
700 if not self.status.modified and editopt and match: |
701 interestingpaths = match.files() |
701 interestingpaths = match.files() |
702 else: |
702 else: |
703 interestingpaths = self.status.modified |
703 interestingpaths = self.status.modified |
704 # prepare the filefixupstate |
704 # prepare the filefixupstate |
718 continue |
718 continue |
719 if targetfctx.data() == fctxs[-1].data() and not editopt: |
719 if targetfctx.data() == fctxs[-1].data() and not editopt: |
720 continue |
720 continue |
721 seenfctxs.update(fctxs[1:]) |
721 seenfctxs.update(fctxs[1:]) |
722 self.fctxmap[path] = ctx2fctx |
722 self.fctxmap[path] = ctx2fctx |
723 fstate = filefixupstate(fctxs, path, ui=self.ui, opts=self.opts) |
723 fstate = filefixupstate(fctxs, path, ui=self.ui, **self.opts) |
724 if fm is not None: |
724 if fm is not None: |
725 fm.startitem() |
725 fm.startitem() |
726 fm.plain(b'showing changes for ') |
726 fm.plain(b'showing changes for ') |
727 fm.write(b'path', b'%s\n', path, label=b'absorb.path') |
727 fm.write(b'path', b'%s\n', path, label=b'absorb.path') |
728 fm.data(linetype=b'path') |
728 fm.data(linetype=b'path') |
1007 lines[a1:a2] = blines |
1007 lines[a1:a2] = blines |
1008 memworkingcopy[path] = b''.join(lines) |
1008 memworkingcopy[path] = b''.join(lines) |
1009 return overlaycontext(memworkingcopy, ctx) |
1009 return overlaycontext(memworkingcopy, ctx) |
1010 |
1010 |
1011 |
1011 |
1012 def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None): |
1012 def absorb(ui, repo, stack=None, targetctx=None, pats=None, **opts): |
1013 """pick fixup chunks from targetctx, apply them to stack. |
1013 """pick fixup chunks from targetctx, apply them to stack. |
1014 |
1014 |
1015 if targetctx is None, the working copy context will be used. |
1015 if targetctx is None, the working copy context will be used. |
1016 if stack is None, the current draft stack will be used. |
1016 if stack is None, the current draft stack will be used. |
1017 return fixupstate. |
1017 return fixupstate. |
1034 raise error.InputError(_(b'no mutable changeset to change')) |
1034 raise error.InputError(_(b'no mutable changeset to change')) |
1035 if targetctx is None: # default to working copy |
1035 if targetctx is None: # default to working copy |
1036 targetctx = repo[None] |
1036 targetctx = repo[None] |
1037 if pats is None: |
1037 if pats is None: |
1038 pats = () |
1038 pats = () |
1039 if opts is None: |
1039 |
1040 opts = {} |
1040 state = fixupstate(stack, ui=ui, **opts) |
1041 state = fixupstate(stack, ui=ui, opts=opts) |
1041 matcher = scmutil.match(targetctx, pats, pycompat.byteskwargs(opts)) |
1042 matcher = scmutil.match(targetctx, pats, opts) |
1042 if opts.get('interactive'): |
1043 if opts.get(b'interactive'): |
|
1044 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher) |
1043 diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher) |
1045 origchunks = patch.parsepatch(diff) |
1044 origchunks = patch.parsepatch(diff) |
1046 chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0] |
1045 chunks = cmdutil.recordfilter(ui, origchunks, matcher)[0] |
1047 targetctx = overlaydiffcontext(stack[-1], chunks) |
1046 targetctx = overlaydiffcontext(stack[-1], chunks) |
1048 if opts.get(b'edit_lines'): |
1047 if opts.get('edit_lines'): |
1049 # If we're going to open the editor, don't ask the user to confirm |
1048 # If we're going to open the editor, don't ask the user to confirm |
1050 # first |
1049 # first |
1051 opts[b'apply_changes'] = True |
1050 opts['apply_changes'] = True |
1052 fm = None |
1051 fm = None |
1053 if opts.get(b'print_changes') or not opts.get(b'apply_changes'): |
1052 if opts.get('print_changes') or not opts.get('apply_changes'): |
1054 fm = ui.formatter(b'absorb', opts) |
1053 fm = ui.formatter(b'absorb', pycompat.byteskwargs(opts)) |
1055 state.diffwith(targetctx, matcher, fm) |
1054 state.diffwith(targetctx, matcher, fm) |
1056 if fm is not None: |
1055 if fm is not None: |
1057 fm.startitem() |
1056 fm.startitem() |
1058 fm.write( |
1057 fm.write( |
1059 b"count", b"\n%d changesets affected\n", len(state.ctxaffected) |
1058 b"count", b"\n%d changesets affected\n", len(state.ctxaffected) |
1072 b'%s\n', |
1071 b'%s\n', |
1073 descfirstline, |
1072 descfirstline, |
1074 label=b'absorb.description', |
1073 label=b'absorb.description', |
1075 ) |
1074 ) |
1076 fm.end() |
1075 fm.end() |
1077 if not opts.get(b'dry_run'): |
1076 if not opts.get('dry_run'): |
1078 if ( |
1077 if ( |
1079 not opts.get(b'apply_changes') |
1078 not opts.get('apply_changes') |
1080 and state.ctxaffected |
1079 and state.ctxaffected |
1081 and ui.promptchoice( |
1080 and ui.promptchoice( |
1082 b"apply changes (y/N)? $$ &Yes $$ &No", default=1 |
1081 b"apply changes (y/N)? $$ &Yes $$ &No", default=1 |
1083 ) |
1082 ) |
1084 ): |
1083 ): |
1152 to the correct place, run :hg:`absorb -a` to apply the changes |
1151 to the correct place, run :hg:`absorb -a` to apply the changes |
1153 immediately. |
1152 immediately. |
1154 |
1153 |
1155 Returns 0 on success, 1 if all chunks were ignored and nothing amended. |
1154 Returns 0 on success, 1 if all chunks were ignored and nothing amended. |
1156 """ |
1155 """ |
1157 opts = pycompat.byteskwargs(opts) |
|
1158 |
|
1159 with repo.wlock(), repo.lock(): |
1156 with repo.wlock(), repo.lock(): |
1160 if not opts[b'dry_run']: |
1157 if not opts['dry_run']: |
1161 cmdutil.checkunfinished(repo) |
1158 cmdutil.checkunfinished(repo) |
1162 |
1159 |
1163 state = absorb(ui, repo, pats=pats, opts=opts) |
1160 state = absorb(ui, repo, pats=pats, **opts) |
1164 if sum(s[0] for s in state.chunkstats.values()) == 0: |
1161 if sum(s[0] for s in state.chunkstats.values()) == 0: |
1165 return 1 |
1162 return 1 |