In this example, git doesn't think it needs to rebase, but it clearly does.
Here's a quick script to create a mini test git repository
#!/bin/bash
rm -rf testgit
mkdir testgit
cd testgit
git init
echo -e "a\nb\nc" > file
git add file
git commit -am 'first'
git checkout -b lineA
echo -e "A\nb\nc" > file
git commit -am 'A'
git checkout -b lineB
echo -e "a\nB\nc" > file
git commit -am 'B'
git checkout -b lineC
echo -e "a\nb\nC" > file
git commit -am 'C'
After this runs, here's what file
looks like in each branch
master | lineA | lineB | lineC
------------------------------
a A a a
b b B b
c c c C
Now let's merge all the branches into master, after which each letter in file
should be capitalised
$ git checkout master
$ git merge lineA
Okay.
$ git checkout lineB
$ git rebase master
Current branch lineB is up to date.
What? No, master changed.
$ git checkout master
$ cat file
A
b
c
$ git checkout lineB
$ cat file
a
B
c
It seems to me that branch lineB needs to rebase to incorporate the change to master made by merging lineA.
Why doesn't git think this needs to be done?
Additionally, if you do this
$ git checkout master
$ git merge lineA
Already up-to-date.
$ git merge lineB
...
1 file changed, 2 insertions(+), 2 deletions(-)
$ cat file
a
B
c
$ git merge lineC
...
1 file changed, 2 insertions(+), 2 deletions(-)
$ cat file
a
b
C
Here the merges should conflict, but git is quietly clobbering them. I'm don't know if this is relevant, but it seems weird.
Result of the First Commands
After the script we have (abbreviating the line branch names...):
master lA lB lC
↓ ↓ ↓ ↓
first----A----B----C
These commands:
$ git checkout master
$ git merge lineA
results in a fast-forward merge, which just moves master
to point to the same commit as lineA
.
Giving:
master
lA lB lC
↓ ↓ ↓
first----A----B----C
No Work for the Rebase
So this sequence of commands:
$ git checkout lineB
$ git rebase master
is saying move the lineB
branch on top of master
.
...but it is already on top of master
.
master
would have to have some commits in its history that lineB
does not have for the rebase command to have any work to do.
No Merge Conflict
When Git creates a merge commit it considers the history of the parents that are going into the merge. In this case both branches are pointing to commits on a common chain of commits. Git can see that the A
commit came before the B
commit which came before the C
commit. As such Git considers the later commits to be additions on top of the earlier commits and as such they do not conflict.
If instead you had a history like:
master
↓
---A
/
| lB
| ↓
first----B
then Git would see them as parallel lines of development. In that case merging lB
into master
or vice-versa would conflict. This is because B
is not an addition on top of A
in the history and as such Git needs user input to understand which of the changes to keep.
I think your point of confusion is further back. When you executed
git commit -am 'first'
git checkout -b lineA
you did create a new branch called lineA
but the branching-off point was the commit you just did ("first
"). As Git puts it, first
is part of the history of both branches, and at that point in your script it is also the most recent commit in both branches. Git log with some options shows that both branch names point to the first commit, which in this example is commit id 58b7dcf:
git log --graph --oneline --decorate
* 58b7dcf (HEAD, master, lineA) first
Continuing:
echo -e "A\nb\nc" > file
git commit -am 'A'
git log --graph --oneline --decorate
* ed40ed2 (HEAD, lineA) A
* 58b7dcf (master) first
The log command shows that commit A
(ed40ed2) is now the most recent commit on lineA
, and its parent commit is first
, which is still the most recent commit on master
. If you were to merge lineA
back to master
at this time it would be a fast-forward (trivial) merge, because no commits were made to master
since lineA
branched off from it. Likewise, rebasing lineA
onto master
would do nothing except report
Current branch lineA is up to date.
because the most recent commit on branch master
(the tip of the master branch) is still part of the history of branch lineA
. Continuing:
git checkout -b lineB
echo -e "a\nB\nc" > file
git commit -am 'B'
git checkout -b lineC
echo -e "a\nb\nC" > file
git commit -am 'C'
git log --graph --oneline --decorate
* 4033067 (HEAD, lineC) C
* f742aed (lineB) B
* ed40ed2 (lineA) A
* 58b7dcf (master) first
Each new branch is built on the most recent commit of the previous one, with no parallel development going on. Continuing:
git checkout master
git merge lineA
Updating 58b7dcf..ed40ed2
Fast-forward
Notice this is a fast-forward merge, so all it did was move master
to ed40ed2:
git log --graph --oneline --decorate
* ed40ed2 (HEAD, master, lineA) A
* 58b7dcf first
Finally:
git checkout lineB
git rebase master
Current branch lineB is up to date.
git log --graph --oneline --decorate
* f742aed (HEAD, lineB) B
* ed40ed2 (master, lineA) A
* 58b7dcf first
You wrote:
What? No, master changed.
Yes, master
changed, but not in a way that affected the history of lineB
or lineC
. master
was fast-forward to commit ed40ed2, which was already the parent of lineB
. To be precise, it's the parent of f742aed, which is the commit id I got for commit B
.
The confusion, for people used to other version control systems, is that Git records a tree-like data structure (technically a DAG) for the commit records, not the branch names. The branches are ephemeral; they're just labels, like movable version tags. The parent-child relationships between the commits is what you see in git log --graph
and other visualization tools.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With