Skip to content

Instantly share code, notes, and snippets.

@milki
Created March 10, 2012 04:16
Show Gist options
  • Save milki/2010094 to your computer and use it in GitHub Desktop.
Save milki/2010094 to your computer and use it in GitHub Desktop.
Replace old ff-merges with non-ff merges
#!/bin/sh
# xargs -tpa /home/milki/tmp/redo-merges/merges -L 3 /home/milki/tmp/redo-merges/remerge
# plan
# HEAD points to either the last redone merge or not a merge
# if last redone merge, NEW merge base is base of last merge
# git rev-parse HEAD^2 last "branch"
# else head is NEW merge base
# reset to NEW merge base of $2
# branch, cherry-pick from HOLD merge baes to $2~1, return to newmaster
# merge branch
# rebase to base of $3
# if same OLD merge base, no rebase
# else, rebase
# $1 previous merge
# $2 current merge
# $3 next merge
# assuming HEAD is result from remerging $1
# origin/master is HEAD for original tree
echo "args: $1 $2 $3"
REDOFFMERGE=0
git checkout newmaster
git branch -f oldmaster
#### STEP 1: DETERMINE NEW MERGE BASE ####
LASTBASE=`git rev-parse --short $1^2~1`
OLDBASE=`git rev-parse --short $2^2~1`
if [ $OLDBASE = $LASTBASE ] # same related branch
then
echo '==Related merge'
NEWBASE=`git rev-parse --short oldmaster^2~1` # use the same base as last merge
else
# Check if bad ff merge at OLDBASE
MSG=`git log -n 1 --format=%s $2~1`
if echo $MSG | grep --silent split
then
echo '**FF MERGE DETECTED**'
REDOFFMERGE=1
fi
echo '==New merge'
NEWBASE=`git rev-parse --short oldmaster` # new branch base
fi
### END STEP 1
### STEP 2 PART 0: RE-MERGE FF BRANCH ####
if [ $REDOFFMERGE = 1 ]
then
echo "Fix ff merge $2~1 onto $NEWBASE"
BRANCH=`git log -n 1 $2~1 --stat --name-only | tail -n 2 | sed '/^$/d' | head -n 1 | sed "s/#\(.*\)/\1/"`
MSG="Merge branch '$BRANCH'"
git checkout -B temp $NEWBASE
git cherry-pick $2~1
git checkout newmaster
git merge --no-ff -m "$MSG" temp # no-ff merge
fi
#### STEP 2 PART 1: RE-MERGE BRANCH ####
echo "==Fix regular merge $2 onto $NEWBASE"
MSG=`git show --format=%s $2` # use same merge message
git checkout -B temp $NEWBASE
git cherry-pick $2^2
if [[ $? -ne 0 ]]
then
echo "conflict in cherry-pick"
echo "Recover $2"
git read-tree $2
git clean -fd
git checkout-index -fa
git status
echo "Using date from $2^2"
#CDATE=`git show -s --format=%ci $2^2`
#ADATE=`git show -s --format=%ai $2^2`
GIT_COMMITTER_DATE="$CDATE" GIT_AUTHOR_DATE="$ADATE" git commit -m "$MSG"
fi
git checkout newmaster
git merge --no-ff -m "$MSG" temp # no-ff merge
# Possible conflict
if [[ $? -ne 0 ]]
then
echo "conflict in remerge"
echo "Recover $2"
git read-tree $2
git clean -fd
git checkout-index -fa
git status
echo "Using date from $2"
GIT_COMMITTER_DATE="$CDATE" GIT_AUTHOR_DATE="$ADATE" git commit -m "$MSG"
fi
### END STEP 2 ###
### STEP 3: Rebase to base next merge if unrelated to current merge###
if [ -n "$3" ]
then
NEXTOLDBASE=`git rev-parse --short $3^2~1`
NEWMASTER=`git rev-parse --short newmaster`
if [ $NEXTOLDBASE != $OLDBASE ] # If next isn't related, rebase
then
echo "Rebase to next merge-base $NEXTOLDBASE onto $NEWMASTER"
git rebase --committer-date-is-author-date -p --onto newmaster $2 $NEXTOLDBASE
git branch -f newmaster
fi
fi
### END STEP 3 ###
#git log --stat oldmaster..HEAD
@milki
Copy link
Author

milki commented Mar 10, 2012

All new non-ff merges and old merges from rebase end up with new author/commit dates. This messes up the original history and git log --graph is ugly because branches that were only 2 commits long are now span from original date to new merge date.

@milki
Copy link
Author

milki commented Mar 29, 2012

The real problem with the messed up history is that the rebased non-ff merges retain their original branch HEAD instead of the new HEAD.

@milki
Copy link
Author

milki commented Apr 8, 2012

New approach. Re-merge EVERY merge. This way, the merges have the right parents. Its looking good so far. But step 3 seems to lead to a noop.

When run in the script:
Successfully rebased and updated refs/heads/newmaster

When run manually:
Successfully rebased and updated detached HEAD

I expect to be in a detached HEAD when doing rebase --onto, so I'm not sure why the script is behaving differently at this point.

@milki
Copy link
Author

milki commented Apr 8, 2012

Replace git show with git rev-parse. Everything looks ok for a while until I start missing some merges. My merge file is probably missing some merge commits.

@milki
Copy link
Author

milki commented May 3, 2012

I lost some merges from a single bad merge commit which resulted in non-linear history. When I rebased into this commit, it skipped a couple merges as well as pulled in references to the old repo tree. Manually handling just this part was all that was needed. This script works just fine otherwise. I am done with this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment