atkretsch
atkretsch

Reputation: 2397

Git: Merging in reverse direction after squashed merge

My team is using a Gitflow-like workflow, but a recent merge from develop to master for a release was unintentionally done as a squash merge. At the time, we didn't raise any alarms and figured we'd get it right next time, but then we had to do a hotfix off of that release. Now, we need to get that hotfix merged back into develop, which has since diverged from master. We're getting conflicts, some legitimate, but most of them noise due to git thinking that the squash-merge commit on master represents its own separate commit of the changes, rather than a merge from develop.

A graph may help illustrate the issue:

 A ---------- D -- G (master)
  \
   \- B -- C -- E -- F (develop)

In this case, D is the result of a squash-merge of B and C (instead of a regular merge, as we would have normally done). Note that git diff C D returns empty - i.e. they are identical in content.

Now, I want to merge master into develop; I expect some conflicts due to legitimate overlap between E, F, and G (hence I can't simply globally say "use ours" or "use theirs"). But I'm getting conflicts for basically everything in B, C, and D, presumably because git views D (understandably) as its own commit in a separate chain of commits on master starting from A.

Long story short, what I'm hoping for is something like "merge relative to these two identical commits (C and D)" rather than "merge relative to A". Sort of like rebase --onto, but for merging, I guess.

FWIW, I know that I can just resolve the conflicts by hand; I'm just curious if there's a way to skip all the garbage conflicts introduced by having done a squash-merge instead of a regular merge when D was created.

Upvotes: 1

Views: 374

Answers (1)

Mark Adelsberger
Mark Adelsberger

Reputation: 45659

UPDATE - I had forgotten important information about exactly how this works; fixed.


For now I'll assume that doing a history rewrite (and the subsequent clean-up) isn't really a practical solution...

In this specific case, the simplest hack would be to use git replace during the merge. This would not work if not for the fact that C and D have the same content.

In a clone you can say

git replace --graft <E> <D>

(where <E> and <D> are expressions that resolve to the corresponding commits; in the above example, this might be git replace --graft develop^ master^). This creates a new commit E' that "looks like" E, but its parent is D. It then uses E' as a replacement for E - so when an operation leads git to E, it still sees Es commit ID, but it gets the rest of its info about that commit from E'. Physically you have

                E'
               /
 A ---------- D -- G (master)
  \
   \- B -- C -- E (REPLACE:E')
                 \
                  F (develop)

So then you

git chekcout develop
git merge master

(or maybe git merge hotfix-branch but you didn't show that in your graph, so we'll go with the simplified example)... Now the merge command "sees"

A ---------- D -- G (master)
              \
               (E|E') -- F (develop)

so it uses D as the merge base. Because C and D have the same content, the calculated merge result is what it should be. The result "looks like"

A ---------- D ------------ G (master)
              \              \
               (E|E') -- F -- M (develop)

which is really

                E'
               /
 A ---------- D ------ G (master)
  \                     \
   \                F -- M (develop)
    \              /
     \- B -- C -- E (REPLACE:E')

Now the history around D..M "looks funny", and that's the down side of doing this (rather than a history correction to replace D with a true merge). M is a borderline "evil merge", but since the default merge would conflict anyway (and since the calculated result is the "natural" resolution of the conflict) it's not terrible.

Once this is done you no longer need the replacement. Future merges between master and develop will see G as the merge base anyway, so the messiness that precedes it doesn't matter.

Upvotes: 3

Related Questions