I threw together a small utility called "git-reparent", available on GitHub at: https://github.com/MarkLodato/git-reparent I welcome any comments or suggestions. To make discussion easier, I've copied the README and code below. --- 8< --- NAME ==== git-reparent - Recommit HEAD with a new set of parents. SYNOPSIS ======== ``git reparent [OPTIONS] ((-p <parent>)... | --no-parent)`` DESCRIPTION =========== Create a new commit object that has the same tree and commit message as HEAD but with a different set of parents. If ``--no-reset`` is given, the full object id of this commit is printed and the program exits; otherwise, ``git reset`` is used to update HEAD and the current branch to this new commit. This command can be used to manually shape the history of a repository. Whereas ``git rebase`` moves around *diffs*, ``git reparent`` moves around *snapshots*. See EXAMPLES for a reason why you might want to use this command. OPTIONS ======= -h, --help show the help -e, --edit edit the commit message in an editor first -m, --message <message> use the given message instead of that of HEAD -p, --parent <commit> new parent to use; may be given multiple times --no-parent create a parentless commit -q, --quiet be quiet; only report errors --no-reset print the new object id instead of updating HEAD INSTALLATION ============ Make executable and place somewhere in your $PATH. EXAMPLES ======== Reparenting the tip of a branch ------------------------------- Suppose we create some commit *B* and then accidentally pass the ``--amend`` flag when creating new commit *C*, resulting in the following history:: B / ...---A---C (HEAD) What we really wanted was one linear history, ``...---A--B--C``. If we were to use ``git rebase`` or ``git cherry-pick`` to reconstruct the history, this would try to apply the *diff* of *A..C* onto *B*, which might fail. Instead, what we really want to do is use the exact message and tree from *C* but with parent *B* instead of *A*. To do this, we run :: $ git reparent -p B where the name *B* can be found by running ``git reflog`` and looking for the first "commit (amend)". The resulting history is now just what we wanted:: C / ...---A---B---C' (HEAD) Reparenting an inner commit --------------------------- We can also update the parents of a commit other than the most recent. Suppose that we want to perform a rebase-like operation, moving *master* onto *origin/master*, but we want to completely ignore any changes made in the remote branch. That is, our history currently looks like this:: B---C (master, HEAD) / ...---A---D---E (origin/master) and we want to make it look like this:: B---C (origin/master) / / ...---A---D---E---B'---C' (master, HEAD) We can accomplish this by using ``git rebase --interactive`` along with ``git reparent``:: $ git rebase -i A # select the "edit" command for commit B # git rebase will dump us out at commit B $ git reparent -p origin/master $ git rebase --continue Now the history will look as desired, and the trees, commit messages, and authors of *B'* and *C'* will be identical to those of *B* and *C*, respectively. SEE ALSO ======== git-filter-branch(1) combined with either git grafts or git-replace(1) can be used to achieve the same effect git-rebase(1) can be used to re-apply the *diffs* of the current branch to another AUTHOR ====== Mark Lodato <lodatom@xxxxxxxxx> --- 8< --- #!/bin/sh # Copyright (c) Mark Lodato, 2013 OPTIONS_SPEC="\ git reparent [OPTIONS] ((-p <parent>)... | --no-parent) Recommit HEAD with a new set of parents. -- h,help show the help e,edit edit the commit message in an editor first m,message= use the given message instead of that of HEAD p,parent=! new parent to use; may be given multiple times no-parent! create a parentless commit q,quiet be quiet; only report errors reset* default behavior no-reset! print the new object id instead of updating HEAD " SUBDIRECTORY_OK=Yes . "$(git --exec-path)/git-sh-setup" || exit $? require_clean_work_tree reparent "Please commit or stash them first." # Location of the temporary message. msg_file="$GIT_DIR/reparent-msg" die_with_usage() { echo "error: $1" >&2 usage } edit= message= no_parent= no_reset= parent_flags= quiet= while [ $# -gt 0 ]; do case "$1" in -p) [ $# -eq 0 ] && die_with_usage "-p requires an argument" shift parent_flags="$parent_flags$(git rev-parse --sq-quote -p "$1")" ;; --no-parent) no_parent=1 ;; -e) edit=1 ;; --no-edit) edit= ;; -m) message="$2"; shift ;; --no-message)message= ;; -q) quiet=-q ;; --no-quiet) quiet= ;; --reset) no_reset= ;; --no-reset) no_reset=1 ;; --) shift; break ;; *) die "internal error: unknown flag $1" ;; esac shift done [ $# -gt 0 ] && \ die_with_usage "no positional arguments expected" [ -z "$no_parent" -a -z "$parent_flags" ] && \ die_with_usage "either -p or --no-parent is required" [ -n "$no_parent" -a -n "$parent_flags" ] && \ die_with_usage "-p and --no-parent are mutually exclusive" # Create the commit. if [ -n "$message" ]; then echo "$message" > "$msg_file" else git cat-file commit HEAD | sed "1,/^$/d" > "$msg_file" fi if [ -n "$edit" ]; then # TODO: use the normal `git commit` comment stripping stuff git_editor "$msg_file" || die "no editor configured" [ -s "$msg_file" ] || die "aborting due to empty commit message" fi eval "$(get_author_ident_from_commit HEAD)" old_head="$(git rev-parse --short HEAD)" || exit $? new_head="$(eval "git commit-tree HEAD: $parent_flags" '< $msg_file')" || \ exit $? rm "$msg_file" # Print out the commit if --no-reset; otherwise update HEAD. if [ -n "$no_reset" ]; then echo "$new_head" else set_reflog_action reparent git reset $quiet "$new_head" || exit $? new_abbrev="$(git rev-parse --short HEAD)" || exit $? [ -z "$quiet" ] && echo "Moved HEAD to $new_abbrev (was $old_head)" fi -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html