1

Ok, so I've run into a bit of a trouble by squashing a bunch of commits, and want to know if there is a way to recover from this. Here is what happened,

Here's where we started from:

  D (mybranch)
 /
A--B--C (master)

Work was done on both the branches:

  D--E--F (mybranch)
 /
A--B--C--G--H (master)

Merged the changes from master to mybranch (squashed here unfortunately so B, C, G and H were combined into a single commit H' on mybranch):

  D--E--F--H' (mybranch)
 /         \   
A--B--C--G--H--L--M (master)

Work was done both the branches:

  D--E--F--H'--I--J--K (mybranch)
 /         \   
A--B--C--G--H--L--M--N--O (master)

And now I want to merge back the changes from mybranch to master and am getting a ton of conflicts saying same files were modified by both.

Any suggestions on what I can try to do now to have a clean merge from mybranch to master?

Update 1

Here is the output of git log

>git log --pretty=raw -n 1 d25ae411 (this is H')
commit d25ae411ffff9c59570437b50bfb89df7720af90
tree 3ffbb07a722fe70a66c64b257bb4a8c81235a027
parent aed6b526524db5c05bdb2518db18cf5ecfd8ef9d

>git log --pretty=raw -n 1 69c01f42 (this is H)
commit 69c01f42e73dad9f504c084de58b6a57f0d8506d
tree 323d506718f99d523fdd9529d8b6114a01657025
parent 7065134c416f7ecc6cf66c30ea5e019c625c9c19
parent 8170ca5ce1ce46df6b9f04d80cfad63b6f082b66
Techtwaddle
  • 1,643
  • 1
  • 15
  • 11
  • are you sure about `H`-`H'` link? Does it really exist and in what conmit? – user3159253 May 02 '16 at 15:16
  • Could you perform `git log --pretty=raw -n 1 H'` and `git log --pretty=raw -n 1 H` and attach output to the question? – user3159253 May 02 '16 at 15:19
  • I think in your diagram, the `H-H'` link should point from `H` to `H'` ( `/`). Right now you have it going the other way. – Mort May 02 '16 at 15:20
  • you may remove any sensible information ftom the output, I just need `commit` and `parent` fields – user3159253 May 02 '16 at 15:22
  • @user3159253 I've updated the question with the output of git log – Techtwaddle May 02 '16 at 15:38
  • @Techtwaddle. Those logs don't make sense to me. You may want do do a `git log --oneline --graph origin/master origin/mybranch` to see the graph for yourself. – Mort May 02 '16 at 15:51
  • `H'` isn't a merge, it has only one parent. Are you sure that you have merged anything into `H'`. – user3159253 May 02 '16 at 16:29
  • What's more interesting is that `H` isn't a descendant of `H'`. So you have merged something completely different into `H`. I'd suggest that you should run a commit visualizing application, like `gitk --all` and try to understand what's going on in your repo. Alternatively you can mark current branches states with temporary tags or branches, jump back to a last good known state, and then retry the merge. – user3159253 May 02 '16 at 18:05

2 Answers2

1

A "squash merge" is not a merge at all, so this drawing is wrong in several important ways:

  D--E--F--H'--I--J--K (mybranch)
 /         \
A--B--C--G--H--L--M--N--O (master)

... but luckily it gives me everything we need to know, to know how to handle this as easily as possible.

First, you said that H' was a squash merge from master into mybranch. The \ should probably have been / (we want "later" commits to be to the right of "earlier" commits), but it really should not indicate a merge at all, and probably should be named differently.

So, here is how I would re-draw the graph fragment (as correctly as I can manage anyway—you will want to re-check all of this carefully on your end before using this):

  D--E--F----BCGH'--I--J--K   <-- mybranch
 /
A--B--C--G--H----L--M--N--O   <-- master

The commit named BCGH' here is the squash "merge"1 of commits B--C--G--H, which you had called H'. It is a copy of all the work from the original four commits.

Using rebase, omitting the squash

You might now want to rebase mybranch on the tip of master. This is probably the easiest and best approach, though it does mean "rewriting history" (pretending that you started your work after commit O). To do that, use an interactive rebase:

$ git checkout mybranch
$ git rebase -i master

and then specifically delete commit BCGH' from the pick list. This will ask Git to cherry-pick each of commits D, E, F, I, J, and K (in that order unless you also re-order the pick lines):

  D--E--F----BCGH'--I--J--K   [will be abandoned]
 /
A--B--C--G--H----L--M--N--O   <-- master
                           \
                            D'-E'-F'-I'-J'-K'   <-- mybranch

Using merge, getting Git to do the work

Alternatively, if you really want to merge, make a new (but temporary) branch, starting from commit A, onto which you cherry-pick just those same six commits:

$ git checkout -b tempbranch mybranch~7  # this should start from A
$ git cherry-pick mybranch~7..mybranch~4 # pick D, E, and F
$ git cherry-pick mybranch~3..mybranch   # pick I, J, and K

  D--E--F----BCGH'--I--J--K   <-- mybranch
 /
A--B--C--G--H----L--M--N--O   <-- master
 \
  D'--E'--F'--I'--J'--K'      <-- tempbranch

Note that tempbranch has no BCGH' copy of the commits from master, and is otherwise basically the same as what we would get if we rebased onto the tip of master.2 The point of making this particular copy is to obtain the source tree associated with this new commit K', because now we can run:

$ git merge master

to make a new (albeit temporary) merge commit T, made without interference from BCGH':

  D--E--F----BCGH'--I--J--K   <-- mybranch
 /
A--B--C--G--H----L--M--N--O   <-- master
 \                         \
  D'--E'--F'--I'--J'--K'----T   <-- tempbranch

This new merge commit T has the source tree we would like to obtain if we make a real merge from master into mybranch. So now let's start that merge, which fails with merge conflicts because of commit BCGH':

$ git checkout mybranch
$ git merge master

This fails with conflicts, so let's now resolve all the conflicts by discarding the entire merge result and replacing it with the tree from temporary merge T. Note that you must be at the top level of your git tree here so that . refers to everything:

$ git rm -rf .
$ git checkout tempbranch -- .

The first step discards the entire work tree (and index/staging-area contents), and the second step creates an all-new work tree (and index contents) from the tipmost commit of tempbranch, which is commit T.

Now you can commit the result (though it's wise to check it carefully first—of course, you can do that on branch tempbranch before you even get this far). Once you commit, you will have this:

  D--E--F----BCGH'--I--J--K-P   <-- mybranch
 /                         /
A--B--C--G--H----L--M--N--O     <-- master
 \                         \
  D'--E'--F'--I'--J'--K'----T   <-- tempbranch

where P is a proper merge, using the source tree from T. It is now safe to delete tempbranch entirely:

$ git branch -D tempbranch

and you are left with a merge that Git did using a temporary branch that omitted commit BCGH'.

More options, and notes on how and why to do whatever you do

There are additional ways to work this, such as making a new branch starting from commit F, doing a real merge from master into this new branch (instead of a squash "merge"), then cherry-picking I through K and doing another real merge from master.

The only good reasons to use any one method, compared to any other method, are:

  • if you like that method better;
  • if it is easier for you; and/or
  • if other people have some of those commits and you don't want to make the other people do more work.

In the end, commits give you two things: a state (a work-tree, plus metadata like commit author, date, and log message), and a history (what commits come before this commit?). The idea is to make exactly as many commits as needed to provide enough state and history to make sense and enable cooperative development, without providing so much state and history as to obscure sense and disable cooperative development.


1The quotes around "merge" are because, again, a squash merge is not a merge. A merge is a commit with two or more parents, and these squash "merge" commits do not record a second parent: they discard key information, which is why they become so much trouble later if you are not very careful with them.

2What I mean here is it's the same in terms of commits as what we would want if we were to rebase. It is, of course, different in terms of the associated source trees, since it does not contain any of the work done in B, C, G, H, L, M, N, or O. A rebase starting from O starts from a source tree that has all that work done.

torek
  • 448,244
  • 59
  • 642
  • 775
0

I suspect that somehow the history is not what you think it is and the merge of changes from master into mybranch was somehow not done properly.

Was that initial merge painful with lots of conflicts and fixing up? If not, I would just get rid of it and start from scratch

git checkout -b mybranch_test F
git rebase --onto F H' mybranch_test

No wait, you could just do a git checkout myrbanch; git rebase --interactive F and delete the H' commit.

This should be pretty easy to test, and with git trying stuff like this out is free, so give it a try.

This is all predicated on the assumption that the H' merge did not introduce major conflict-inducing changes, which may or may not be true.

Mort
  • 3,379
  • 1
  • 25
  • 40
  • The initial merge wasn't too painful, there were a few minor conflicts. Starting from scratch would be my last option :-) But I've started on that already to see if it'll work.. – Techtwaddle May 02 '16 at 15:40