Skip to content

Instantly share code, notes, and snippets.

@Glutexo
Last active August 29, 2015 14:14
Show Gist options
  • Save Glutexo/1c8b839f5c06603bb22d to your computer and use it in GitHub Desktop.
Save Glutexo/1c8b839f5c06603bb22d to your computer and use it in GitHub Desktop.
GIT: Incorporate changes made in an already merged branch to its merge commit.
#!/bin/sh
# Takes changes done in a branch that was already merged into another and
# incorporates them into the already existing merge commit. Leaves in a
# detached HEAD state, the current HEAD being the patched original commit.
# If the patch cannot be applied cleanly, it is saved into ./patch.
if [ -z $1 -o -z $2 ]
then
echo "Usage: remerge.sh <original merge commit> <commit to merge>"
exit 1
fi
git rev-parse --verify --quiet "$1" > /dev/null
if [ $? -ne 0 ]
then
echo "$1 is not a valid commit."
exit 2
fi
git rev-parse --verify --quiet "$1^2" > /dev/null
if [ $? -ne 0 ]
then
echo "$1 is not a merge commit."
exit 2
fi
git rev-parse --verify --quiet "$1^3" > /dev/null
if [ $? -eq 0 ]
then
echo "$1 is an octopus merge commit."
exit 2
fi
git rev-parse --verify --quiet "$2" > /dev/null
if [ $? -ne 0 ]
then
echo "$1 is not a valid commit."
exit 2
fi
REBASE_DIR=.git/rebase-apply
if [ -e "$REBASE_DIR" ]
then
echo "$REBASE_DIR exists."
exit 3
fi
TMP_BRANCH=_remerge_tmp_branch
git rev-parse --verify --quiet "$TMP_BRANCH" > /dev/null
if [ $? -eq 0 ]
then
echo "A branch _remerge_tmp_branch exists."
exit 3
fi
# Rev-parse so that checkouting results in a detached HEAD state.
ORIGINAL_MERGE=`git rev-parse "$1"`
MERGED_PARENT="$ORIGINAL_MERGE^2"
BRANCH_TO_MERGE=$2
# Always use the default commit message.
GIT_EDITOR=true
export GIT_EDITOR
amend_previous_head() {
# Turn the previous head into the current state.
git reset --soft HEAD@{1}
git commit --amend
}
# Format patch that turns the state of the merged branch in the time when merged
# into its current state. A throwaway branch is used for that.
git checkout "$MERGED_PARENT"
git checkout -b "$TMP_BRANCH"
git merge --squash "$BRANCH_TO_MERGE"
git commit --allow-empty
PATCH=`git format-patch --stdout HEAD^`
git checkout "$ORIGINAL_MERGE"
git branch -D "$TMP_BRANCH"
# Create a new commit whose main parent is the same as of the original merge,
# but the merged parent is the provided new one from the changed branch.
git checkout "$ORIGINAL_MERGE^1"
# The branches are merged using the “ours” strategy to ensure there are no
# conflicts. The merge is done just for the commit to have the correct parents,
# but the changes are applied as a patch.
git merge --strategy=ours "$BRANCH_TO_MERGE"
git reset --hard "$ORIGINAL_MERGE"
amend_previous_head
echo "$PATCH" | git am
if [ $? -eq 0 ]; then
# The patch applied cleanly, make it a part of the new merge.
amend_previous_head
echo "The patch has been applied cleanly."
else
echo "The patch could not be applied cleanly. Stored in \c"
PATCH_FILE_NAME="patch"
SRC_PATCH_PATH="$REBASE_DIR/$PATCH_FILE_NAME"
TARGET_PATCH_PATH="./$PATCH_FILE_NAME"
if [ -e "$TARGET_PATCH_PATH" ]
then
echo "$SRC_PATCH_PATH. Run git am --abort after applying."
else
cp "$SRC_PATCH_PATH" "$TARGET_PATCH_PATH"
git am --abort
echo "$TARGET_PATCH_PATH."
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment