Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git - remerging further down the line

Tags:

git

The main disadvantage of merging vs. rebasing is that merging results in a messy tree. If the master is frequently being updated, then merging in mastery every time there has been a significant update will create a whole host of unnecessary commits. Now, most of the time, there isn't actually any need for this. Consider the repository as follows:

Master A\--B--C\--D\---------E
Branch   l--m---n---o--p--q

Here n is a merge where we had to resolve a significant amount of conflicts which we want to avoid resolving again. We want to merge E back in without creating a new merge commit

So we go back to o, merge E and cherry-pick p and q on top:

Master A\--B--C\--D\--------E\
Branch   l--m---n\--o--p--q   \
Tmp               -------------o'-p-q

If this works without an error, we can then remove the old branch.

I would be surprised if I was the first person to think of this kind of workflow. Are there any git extensions that can automate this?

I've started working on a script to do this, but the main issue is selecting o. ":/^Merge" - will select o in most cases, but it would be surprised if there wasn't a better way that avoids relying on commit messages not starting with the word Merge.

like image 246
Casebash Avatar asked Feb 20 '14 01:02

Casebash


3 Answers

edit: To retroactively enable rerere, see here.. The script in this answer doesn't turn rerere on and will behave differently than rerere would in the presence of further changes, in E's history (see below), to n...D-conflicted hunks resolved in o. rerere does an n...E merge and applies any n...D resolutions that are still valid, while this script does an standard o...E merge (but replaces parent o with o)

Copying the graph for reference:

      before                                 desired after
Master A\--B--C\--D\....E             \  Master A\--B--C\-D...E\
Branch   l--m---n---o--p--q         /    Branch   l--m---n------o'--p'--q'

rerere will thus bypass E-reverted n...D conflicts, while the script will treat the reversion as a further change. Both scripts produce the "desired after" graph above, but they get there by different routes.

An hour's trying hasn't produced any more comprehensible further textual description of the differences than simply looking at the graphs and deducing.

It's not clear to me that either rerere's or the script's behavior is always better.


When I do

go back to o, merge E

with

git checkout o
git merge E

I get

Master A\--B--C\--D\---------E
Branch   l--m---n---o\-p--q   \
HEAD                  `--------o'

with o for o''s parent. You do want the o' content (to preserve any conflict resolutions you'd already applied in o), but you want it to have n and E as parents, not o and E. That's actually fairly easy.

So the complete procedure is,

#!/bin/sh

# 1. identify the most recent merge on $1
# 2. verify that it's from $2
# 3. update the merge to $2's current state, 
# 4. preserving any conflict resolutions that were already done
# 5. and cherry-pick everything since.

# most recent merge in $1
update=`git rev-list -1 --min-parents=2 $1`

# verify most recent merge is from correct merge base
test "`git merge-base $1 $2`" = "`git rev-parse $update^2`" \
|| { echo "most recent merge isn't of $1 from $2"; exit 1; }

set -e  # exit immediately on error
git checkout $update
git merge $2 
git checkout $(
      # this makes a new commit with HEAD's commit message and tree
      # but with $update^ and $2 as parents
      git cat-file -p HEAD \
      | sed 1,/^$/d \
      | git commit-tree -p $update^ -p $2 HEAD^{tree}
   )
test $update = `git rev-parse $1` || git cherry-pick $update..$1
git checkout -B $1

If you're willing to lose any conflict resolutions done in o you can instead do (from the set -e)

set -e
git checkout $update^
git merge $2
test $update = `git rev-parse $1` || git cherry-pick $update..$1
git checkout -B $1

If doing work in a subshell and exiting from that to continue isn't any trouble you can make this much less fragile by doing

git merge $2 \
|| { echo "Resolve conflicts and \`exit\`, or \`exit 1\` to not continue"; $SHELL; }

edit: update to handle updating the merge when there's nothing to cherry-pick.

like image 112
jthill Avatar answered Oct 03 '22 09:10

jthill


As I mentioned in the comments, a git rerere activated when created n would have helped avoiding to deal with the same conflicts when doing a:

git rebase --onto E n q

I've started working on a script to do this,

Speaking of script, the most sophisticated one to handle a complex rebase is called:

git-imerge -- incremental merge and rebase for git (by Michael Haggerty)

See a comparison matrix here.

That will keep the size of the conflicts to a minimum (and don't have to rely on git rerere)

like image 21
VonC Avatar answered Oct 03 '22 09:10

VonC


You're getting the same old conflicts because you should have been rebasing the branch to the trunk, rather than merging stuff from the trunk into the branch (which is a wrong-way merge).

If point "n" had been a rebase, then it would be done. l-m-n would have been replayed on top of C, and so the branch would start from C. The changes before C wouldn't have to be rebased any more.

When you're ready for the branch to go into the trunk, then you rebase it one more time, so that the branch starts at the HEAD revision of trunk. At that point, the branch can replace the trunk: you can check out trunk and reset to the branch.

like image 41
Kaz Avatar answered Oct 03 '22 08:10

Kaz