1

Assume that I have a following tree:

$ git log --graph --oneline --decorate --all

* 7b261e3 (HEAD, master) Revert "A"
* 32f08ae A
| * f0b008f (b) A
|/
* 83c0052 init

$ git log -p --graph --decorate --all

* commit 7b261e3534e12446b75d286ef94556d077a9ee87 (HEAD, master)
| Author: Shin Kojima <shin@kojima.org>
| Date:   Tue Jun 10 03:13:29 2014 +0900
| 
|     Revert "A"
|     
|     This reverts commit 32f08ae6e6a8036a7ed0a72568ac41b3e0fe806a.
| 
| diff --git a/test b/test
| index f16344d..26604dc 100644
| --- a/test
| +++ b/test
| @@ -1,3 +1,3 @@
|  foo
| -hoge
| +bar
|  buz
|  
* commit 32f08ae6e6a8036a7ed0a72568ac41b3e0fe806a
| Author: Shin Kojima <shin@kojima.org>
| Date:   Tue Jun 10 03:12:18 2014 +0900
| 
|     A
| 
| diff --git a/test b/test
| index 26604dc..f16344d 100644
| --- a/test
| +++ b/test
| @@ -1,3 +1,3 @@
|  foo
| -bar
| +hoge
|  buz
|    
| * commit f0b008f3da2426611b40560ce4b64be6e32707e5 (b)
|/  Author: Shin Kojima <shin@kojima.org>
|   Date:   Tue Jun 10 03:12:18 2014 +0900
|   
|       A
|   
|   diff --git a/test b/test
|   index 26604dc..f16344d 100644
|   --- a/test
|   +++ b/test
|   @@ -1,3 +1,3 @@
|    foo
|   -bar
|   +hoge
|    buz
|  
* commit 83c00525a1d8168ae251cf33c00178d398ef4b54
  Author: Shin Kojima <shin@kojima.org>
  Date:   Tue Jun 10 03:11:38 2014 +0900

      init

  diff --git a/test b/test
  new file mode 100644
  index 0000000..26604dc
  --- /dev/null
  +++ b/test
  @@ -0,0 +1,3 @@
  +foo
  +bar
  +buz

7b261e3 is a revert commit of 32f08ae and f0b008f is a cherry-pick from 32f08ae.

When I merge branch b into master, I found that git ignores the revert commit(7b261e3) silently and the result in a different outcome from rebasing method. It seems like I still have to evaluate degradations and see for myself in this case.

Merge made by the 'recursive' strategy.
 test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

$ cat test

foo
hoge
buz

Are there any ways to detect conflict? What I have done wrong?

git version 2.0.0

ernix
  • 3,442
  • 1
  • 17
  • 23
  • 1
    tl;dr on @torek's answer: merge works on the cumulative change since the two branches diverged. Your master branch's cumulative content change is that nothing's different now. If you want git to know a commit's effect has been correctly applied, merge it. You've heard that git's very good at not duplicating cherry-picked commits, and it is, but that's because it's very good at identifying the merge base -- in complicated situations it will even derive an ersatz ideal base. – jthill Jun 09 '14 at 22:23

1 Answers1

3

This is a consequence of how git defines the merge action. Let me elaborate:

You're on branch master (i.e., HEAD is ref: refs/heads/master), with master referring to commit 7b261e3 and b referring to commit f0b008f. The commit graph looks like this:

I - A - R   <-- HEAD=master
  \
    A'      <-- b

where I is the initial commit (83c0052), A is your commit with message "A", R is the reversion commit of A, and A' (f0b008f) is the copy of A that you cherry-picked to branch b.

You now run:

$ git merge b

Git finds the "merge base" of HEAD and b, which is commit I.

Git now does, in effect, two diffs:

git diff 83c0052 HEAD  # or: git diff 83c0052 7b261e3
git diff 83c0052 b     # or: git diff 83c0052 f0b008f

(Try these yourself as actual commands, using the raw SHA-1s if you have not undone the merge.)

The changes from I to R are, well, none: you made the change in A, then you made a reversing change to get back to the state you had in I. So the first diff is empty.

The changes from I to A' are the changes you made in A.

You told git to combine those two (not three or four, just two) changes. The combination of "no change" and "change bar to hoge" is to change bar to hoge, so that's what you get:

I - A - R - M   <-- HEAD=master
  \   ____/
    A'          <-- b

where commit M's tree keeps the changes from A'. Hence:

Are there any ways to detect conflict? What I have done wrong?

there is no conflict; the only thing that you could say you did wrong is to expect more from git. :-)

Seriously, this sort of thing is one reason—just one of many—why you must always inspect the result of a merge in some way, whether it's by looking at the logs of items being merged, or running automated tests, or just eyeballing the results.

... a different outcome from rebasing method.

The "rebase" operation is fundamentally different from merge. Merge takes two (or more) development histories and combines them, by comparing the merge-base with each history-tip and combining all the changes. Rebase takes a set of commits (usually a single development history) and attempts to "replay" each one (as a cherry-pick) starting at some new point. If it all succeeds, the label is moved to the tip of the resulting series of commits.

In this case, however, if you were to replay "branch b" onto "branch master":

I - A - R    <-- master
  \
    A'       <-- b

that would tell git to cherry-pick A' atop R, giving:

I - A - R        <-- master
  \       ` A''  [proposed new b]
    A'           <-- b

which results in the same tree as merging b into master. (And then once the cherry-pick worked, git would move branch b to the proposed new place.)

On the other hand, replaying master atop b (by checking out b and doing a series of cherry-picks) is different: this means to cherry-pick (copy) A, then R. Since A is already copied, the attempt to bring A in again is skipped as a duplicate:

I - A - R      <-- master
  \
    A' - A''?  [proposed, but we discover A'' is duplicate, discard]

I - A - R    <-- old master
  \
    A' - R'  <-- new master, after proposed cherry pick sequence accepted

That is, cherry picking (and thus rebasing as well) works one commit at a time; but merge tries to take the result as a whole, without considering all the intermediate steps it may have taken to get there.

(To take intermediate steps into consideration, do an "incremental merge". Note that this is not built into git.)

torek
  • 448,244
  • 59
  • 642
  • 775