From c7b96a36948fadfd10078f821e986ec0cf6d3df6 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Sun, 24 Jun 2018 08:55:48 +0200 Subject: [PATCH] reparent: add a `--commit` option Add an option `git imerge reparent --commit=COMMIT`, which allows the parents of an arbitrary commit to be changed. Moreover, the descendants of COMMIT are also rewritten all the way to HEAD. This feature was suggested by Ke Ma , and the implementation is partly derived from PR #130 submitted by Ke Ma. --- git-imerge | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/git-imerge b/git-imerge index 87eac6b..d5a69cd 100755 --- a/git-imerge +++ b/git-imerge @@ -3628,19 +3628,63 @@ def cmd_diagram(parser, options): ) +def reparent_recursively(git, start_commit, parents, end_commit): + """Change the parents of start_commit and its descendants. + + Change start_commit to have the specified parents, and reparent + all commits on the ancestry path between start_commit and + end_commit accordingly. Return the replacement end_commit. + start_commit, parents, and end_commit must all be resolved OIDs. + + """ + + # A map {old_oid : new_oid} keeping track of which replacements + # have to be made: + replacements = {} + + # Reparent start_commit: + replacements[start_commit] = git.reparent(start_commit, parents) + + for (commit, parents) in git.rev_list_with_parents( + '--ancestry-path', '--topo-order', '--reverse', + '%s..%s' % (start_commit, end_commit) + ): + parents = [replacements.get(p, p) for p in parents] + replacements[commit] = git.reparent(commit, parents) + + try: + return replacements[end_commit] + except KeyError: + raise ValueError( + "%s is not an ancestor of %s" % (start_commit, end_commit), + ) + + def cmd_reparent(parser, options): git = GitRepository() try: - commit_sha1 = git.get_commit_sha1('HEAD') + commit = git.get_commit_sha1(options.commit) + except ValueError: + sys.exit('%s is not a valid commit', options.commit) + + try: + head = git.get_commit_sha1('HEAD') except ValueError: sys.exit('HEAD is not a valid commit') try: - parent_sha1s = [git.get_commit_sha1(p) for p in options.parents] + parents = [git.get_commit_sha1(p) for p in options.parents] + except ValueError as e: + sys.exit(e.message) + + sys.stderr.write('Reparenting %s..HEAD\n' % (options.commit,)) + + try: + new_head = reparent_recursively(git, commit, parents, head) except ValueError as e: sys.exit(e.message) - sys.stdout.write('%s\n' % (git.reparent(commit_sha1, parent_sha1s),)) + sys.stdout.write('%s\n' % (new_head,)) def main(args): @@ -3927,10 +3971,24 @@ def main(args): subparser = subparsers.add_parser( 'reparent', - help='change the parents of the HEAD commit', + help=( + 'change the parents of the specified commit and propagate the ' + 'change to HEAD' + ), ) subparser.add_argument( - 'parents', nargs='*', help='[PARENT...]', + '--commit', metavar='COMMIT', default='HEAD', + help=( + 'target commit to reparent. Create a new commit identical to ' + 'this one, but having the specified parents. Then create ' + 'new versions of all descendants of this commit all the way to ' + 'HEAD, incorporating the modified commit. Output the SHA-1 of ' + 'the replacement HEAD commit.' + ), + ) + subparser.add_argument( + 'parents', nargs='*', metavar='PARENT', + help='a list of commits', ) options = parser.parse_args(args)