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)