Reputation: 679
This is a simplification from my real scenario. I've tested this reduced case, and I get the same result as in the real one.
line 1
line 2
line 3
line 4
line 1
line 2
special line
line 3
line 4
line 1
line 2
line 3
line 4
line 1
line 2
special line
line 3
line 4
git log --graph --oneline
:
* df62c6c (HEAD -> master) Merge branch 'hotfix'
|\
| * abc6ae8 (hotfix) Deleted special line
| * ad37421 Added special line in 'hotfix'
* | 7844837 Added special line in 'master' (cherry-pick ad37421)
|/
* 35be1f5 Initial commit
I was expecting to have a merge conflict in the "special line", since it was added in 'master', but deleted in 'hotfix'. But the "special line" was added to the file, without warnings, so I've lost the fix from branch 'hotfix', without any notice.
Is this behaviour correct? Why I don't get any conflicts? Is there a way to make git merge pickier, so it raises these posible conflicts?
Upvotes: 0
Views: 437
Reputation: 535057
Almost everything in the scenario described in the question is a red herring. We don't need the cherry pick or any of the commits in hotfix. We can do exactly the same thing much more simply:
themini:gittest mattneubelcap$ echo -e "line 1\nline 2\nline 3\nline 4" > test.txt
themini:gittest mattneubelcap$ git init
Initialized empty Git repository in /Users/mattneubelcap/gittest/.git/
themini:gittest mattneubelcap$ git add .
themini:gittest mattneubelcap$ git commit -m "initial"
[master (root-commit) c3cfeba] initial
1 file changed, 4 insertions(+)
create mode 100644 test.txt
themini:gittest mattneubelcap$ git branch hotfix
themini:gittest mattneubelcap$ echo -e "line 1\nline 2\nspecial line\nline 3\nline 4" > test.txt
themini:gittest mattneubelcap$ git add .
themini:gittest mattneubelcap$ git commit -m "added special line"
[master ed3ed8d] added special line
1 file changed, 1 insertion(+)
themini:gittest mattneubelcap$ git merge hotfix -m "merge hotfix"
Already up to date.
themini:gittest mattneubelcap$ cat test.txt
line 1
line 2
special line
line 3
line 4
themini:gittest mattneubelcap$ git checkout hotfix
Switched to branch 'hotfix'
themini:gittest mattneubelcap$ cat test.txt
line 1
line 2
line 3
line 4
themini:gittest mattneubelcap$
So, we have two branches, master and hotfix. Branch master's version of test.txt contains "special line"; branch hotfix's version of test.txt does not. We merge hotfix into master, and we get the version of test.txt that does contain "special line". So if we are to be mystified by something, that is what we should be mystified by.
To unmystify ourselves, we need to know what a merge is. It is an application of the diffs between the LCA of both branches and the branches themselves.
Very well, the LCA of both branches here is c3cfeba
, the initial root commit. (If this were not obvious, we could find out with the git merge-base
command.) What are the diffs?
themini:gittest mattneubelcap$ git diff c3cfeba ed3ed8d
diff --git a/test.txt b/test.txt
index 9c2a709..0ad8aae 100644
--- a/test.txt
+++ b/test.txt
@@ -1,4 +1,5 @@
line 1
line 2
+special line
line 3
line 4
themini:gittest mattneubelcap$ git diff c3cfeba hotfix
themini:gittest mattneubelcap$
The first diff is that "special line" was added. The second diff is that no change was made. So we apply both of those and we get that the "special line" was added.
Perhaps our intuition somehow is that when we merge hotfix into master, the "absence of change" in hotfix should somehow outweigh the change in master. But that is not what a merge is. A merge does not say that one diff is better than the other. It is an application of both diffs. As long as both diffs can be applied, there is no conflict (that's an oversimplification, but it will do). And they can be.
Look at it this way. Suppose branch hotfix added a line to the end of the file. Would we say that merging hotfix into master should include the line added to the end of the file by hotfix, but not the special line added to the middle of the file by master? No, we would not. We would expect both lines to appear in the resulting file, both branches being equal partners in the merge. And that is exactly what does happen.
So it is inconsistent of us to expect in the actual question that the absence of the special line should somehow "override" what master did. What master did is legitimate. It is not to be blithely overridden by some other branch being merged in. It would be terrible if that sort of thing could happen.
Upvotes: 1
Reputation: 45659
Just before the merge, you have
O -- A -- !A <--(hotfix)
\
A' <--(master)
A
adds the line. A'
is the result of cherry-picking A
- it also adds the line. !A
removes the line - i.e. it reverts A
, so its final content is the same as O
. (If there are other unrelated changes in either A
or !A
, you didn't mention them; but it wouldn't change anything even if there are.)
Now, when you do the merge, you say the line is added in master
, but deleted in hotfix
- but from git's point of view that isn't true. The key is that to do a merge, git determines a "merge base" - a single snapshot relative to which changes are calculated. In this case, that's O
.
So git calculates two patches:
1) the difference between O
and !A
- this is the patch for hotfix
2) the difference between O
and A'
- this is the patch for master
and it combines those patches.
On master
the line was added - that much is true.
To describe what happened on hotfix, you have to start at the merge base - so "the line was deleted" is, at best, incomplete. Actually, the line was added and then deleted, which is no change at all. There's no change with which to conflict.
Because there is always one merge base, there can never be a conflict where "the same line was added on one side but removed on the other" - because at the start, either the line was there or it wasn't.
Addressing some of your more specific points:
so I've lost the fix from branch 'hotfix', without any notice.
No, you haven't "lost a fix from the branch". What did the branch "fix'? Removing a line that was never there to begin with until you put it there (as part of the hotfix) and then copied it back to master (defeating the point of hotfix branching)?
That's not a real-world hotfix.
Yes the behavior is correct. You don't get conflicts for the reason explained above.
If by "make git pickier" you mean "make git aware that a change was made and unmade on one side of the merge, and treat that as a conflict"... no, that can't be done. For various reasons, git only looks at three commits during a merge:
ours
- the currently-checked-out commit
theirs
- the commit to be merged in
base
- a common ancestor of ours
and theirs
, from which patches are calculated and combined to create the merge result.
Anything that happens "in between" is invisible, so a commit that you revert (or a change that you revert within a commit) cannot be the source of a conflict.
In cases where a commit was cherry-picked, you can get a different result by rebasing. If you rebase master
onto hotfix
, git should notice that A'
is a patch-wise duplicate of A
and therefore not apply it. In that case, your end result would be master
pointing to !A
- i.e. having the content matching O
, without the added/deleted line. BUT there are some things to be aware of:
1) The rebase is a history rewrite. Rewriting the history of master
is almost always a bad idea. (A more precise rule of thumb is that if you share a repo, there's always some coordination and cost involved in rebasing anything that's been pushed - and master has surely been pushed.)
2) Because you describe the deletion of the line as a "fix", you might think rebase is giving you a better result. But that's more a result of how your scenario is contrived than any indication that one way or the other is "better" - it would be roughly as likely to contrive a case where the merge result is preferred. You still aren't going to get a conflict that would alert someone to review and decide what's right.
Realistically if you don't want a situation like this to be possible, then the workflow of "branch, make a change, copy the change back to the parent branch..." is the issue.
Upvotes: 2
Reputation: 30184
The thing is that you are cherry-picking.... that creates no relation between the revisions you are cherry-picking... when you merge hotfix into master, when checking how hotfix branch has changed since the branch diverged from master.... well, it hasn't (the added line was deleted) so to git there's no difference there and so the merge goes fine.
Upvotes: -1