I will try to describe the secuence of git operations that leads to the misunderstood behaviour.
Starting from this scenario with a local (LM) and remote (RM) branch master with commits m1 and m2:
RM --- m1 --- m2
LM --- m1 --- m2
A local feature branch is created (LF) and a commit f1 is made
RM --- m1 --- m2
LM --- m1 --- m2
LF --- m1 --- m2 --- f1
This branch is pushed to track a remote branch (RF) which is then merged into remote master (RM):
RM --- m1 --- m2 --- f1
LM --- m1 --- m2
RF --- m1 --- m2 --- f1
LF --- m1 --- m2 --- f1
The remote feature branch (RF) is deleted and a new commit x is added to remote master (RM) as a result of a merge:
RM --- m1 --- m2 --- f1 --- x
LM --- m1 --- m2
LF --- m1 --- m2 --- f1
Local master branch (LM) is updated and a new commit is made to local feature branch (LF):
RM --- m1 --- m2 --- f1 --- x
LM --- m1 --- m2 --- f1 --- x
LF --- m1 --- m2 --- f1 --- f2
When the local feature branch (LF) is pushed, commits f1 and f2 are seen as commits to be pushed but only f2 was added since last push. What is the explanation for this behaviour? What is happening behind the scenes in git?
Note that if at this point a new branch is created from master and f2 is cherry-picked from LF, when the push operation is performed only f2 appears as a commit to be pushed.
CodePudding user response:
When you merged f1 into master, it no more remains f1 in the eyes of git. Git sees it as something like f1' (a new commit hash).
So you would have something like below:
This branch is pushed to track a remote branch (RF) which is then merged into remote master (RM):
RM --- m1 --- m2 --- f1'
LM --- m1 --- m2
RF --- m1 --- m2 --- f1
LF --- m1 --- m2 --- f1
So your next sequence would be :
The remote feature branch (RF) is deleted and a new commit x is added to remote master (RM) as a result of a merge:
RM --- m1 --- m2 --- f1' --- x
LM --- m1 --- m2
LF --- m1 --- m2 --- f1
Local master branch (LM) is updated and a new commit is made to local feature branch (LF):
RM --- m1 --- m2 --- f1' --- x
LM --- m1 --- m2 --- f1' --- x
LF --- m1 --- m2 --- f1 --- f2
This is why f1 and f2 of LF recognized as new commits.
CodePudding user response:
The crucial step is this one: "...which is then merged into remote master (RM)" Depending what kind of merge was used, the result will be very different.
- If a true fast-forward was used, the history would look as you've drawn it.
- If a rebase was used, the commit "f1" will have been re-created, so the history of RM will actually be
m1 --- m2 --- f1_new
. - A "squash merge" would do the same as a rebase, since there's only one commit.
- A "true merge" would create a merge commit, so the history would look something like this:
m1 --- m2 -- M
\ /
f1
The behaviour you're seeing is consistent with using a rebase or squash merge - you have a commit which looks the same as f1, but is not the same commit. It has a new parent, and a new commit date, and therefore a new commit hash.
You can see this for yourself by looking at the commit hashes in the history of the branches you have. The commit you're labelling "f1" will actually have different IDs on each.
Now, when you ask for the difference between branches, or to merge from one to the other, git doesn't care if two commits are "really very similar", only if they are actually exactly the same commit. The commit "f1" does not exist on "RM", so will be included in the comparison or merge.
The rule of thumb is never use a rebase or squash merge on a branch you're going to carry on using. Use a true merge, and then git will know the proper relationship between the two branches.