'Why does merging an indentical commit with a different lineage work?
Common best practices say to never merge a feature branch which after it has been rebased. But it looks like if the contents of the rebased branch match exactly commits on the target branch, rebase will be able to do a clean merge. Why will this sequence work without producing merge conflicts because the first and second merges are merging the same changes but with different commits?
git init --initial-branch=prod
git commit --allow-empty -m 'initial commit'
git checkout -B test_a prod
git checkout -B test_b prod
git checkout test_a
echo hello > testing_file
git add testing_file && git commit -m "add testing file"
echo goodbye > testing_file
git add testing_file && git commit -m "update testing file"
git checkout test_b
git merge test_a
git checkout test_a
git rebase -i prod # squash commits
git checkout test_b
git merge test_a
How does this
commit c65182aad4b07c272b527888f8e0780854dee39b
Merge: 070cb78 1bf92cd
Author: REDACTED
Date: Wed Apr 6 11:22:55 2022 -0400
Merge branch 'test_a' into test_b
commit 070cb7828cf2216ed5b0edaa62fb3815824397ae
Author: REDACTED
Date: Wed Apr 6 11:21:43 2022 -0400
Squash
diff --git a/testing_file b/testing_file
new file mode 100644
index 0000000..dd7e1c6
--- /dev/null
+++ b/testing_file
@@ -0,0 +1 @@
+goodbye
commit 1bf92cd210ac92b94a9b0b250e7705201117619d
Author: REDACTED
Date: Wed Apr 6 11:21:44 2022 -0400
update testing file
diff --git a/testing_file b/testing_file
index ce01362..dd7e1c6 100644
--- a/testing_file
+++ b/testing_file
@@ -1 +1 @@
-hello
+goodbye
commit 2aa3f8a3637550d4b5c48e408da00c519450762b
Author: REDACTED
Date: Wed Apr 6 11:21:43 2022 -0400
initial commit
commit 4388bd254b58b4be0e8e50293c78f489efb2eaaa
Author: REDACTED
Date: Wed Apr 6 11:21:43 2022 -0400
add testing file
diff --git a/testing_file b/testing_file
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/testing_file
@@ -0,0 +1 @@
+hello
Solution 1:[1]
Perhaps some common best practices are based on incomplete understanding, or oversimplified (maybe deliberately and maybe for good reasons—I wouldn't know) models, and/or simple cargo cult programming. The key to the Git part of this is to identify these three commits:
- merge base
- current commit
- other commit
and then to inspect (or in this case, imagine) the two diffs you would get by running:
git diff --find-renames <hash-of-base> HEAD
git diff --find-renames <hash-of-base> <hash-of-other>
it looks like if the contents of the rebased branch match exactly commits on the target branch ...
Technically, until you define the word branch at least, a "branch" does not have contents. A commit has contents (metadata and snapshot), and here we can assume that:
contents of the rebased branch
means "the snapshot of the other commit" ($other below) and
match exactly commits on the target branch
means "the snapshot at the tip of the current branch" (the HEAD commit), or vice versa. In that case, pick any merge base commit hash $B and run:
git diff --find-renames $B HEAD > /tmp/diff1
git diff --find-renames $B $other > /tmp/diff2
and inspect the diff hunks. Since git diff HEAD $other is empty (HEAD and $other have the same snapshot by our assumption here), these two diff outputs will match, regardless of which specific commit we choose as $B. Git will therefore be trivially able to combine the two diffs, by taking one copy of all of the changes shown, and apply those changes to the snapshot in $B, giving the snapshot that appears in both HEAD and $other.
This will be the snapshot of the resulting merge. The merge commit made by git merge, if any, will depend on the ancestry—the is-ancestor and hash equality relationship between $B and HEAD—and whether we used --no-ff and/or --squash in our git merge command, but the merge process will be trivial, no matter what.
The key insight
The key to understanding this lies in the fact that git merge depends only on those three commits. Any intermediate commits are irrelevant:
I1--I2--I3--I4--L <-- leftside (HEAD)
/
...--o--B
\
I5--I6--I7--I8--R <-- rightside
(assuming git switch leftside && git merge rightside here). Git will compare the snapshots in B, L, and R only. The snapshots in the eight I (irrelevant) commits take no part in the merge-as-a-verb work-combining action. Only the diffs from B to L and from B to R matter here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | torek |
