In order:
Doesn't the third line,
git rebase master somechanges
, put commits fromchgs
after the last commit frommaster
but without merging them ...
Yes, assuming somechanges
is a typo for chgs
(the referenced page uses yet more spellings).
and leaves
HEAD
pointing to the latest commit fromchgs
Yes but only indirectly: HEAD
literally contains ref: refs/heads/chgs
, i.e., you are left with branch chgs
checked-out. It's chgs
itself that points to the latest commit.
Does that leave me with
chgs
checked out?
Yes.
Specifically, the "current branch", the one printed as on branch ...
when you use git status
, is just whatever refs/heads/branch
is in the file HEAD
, when HEAD
contains refs: refs/heads/branch
. Running git checkout branch
first makes sure it's OK to update the work tree, then does that and puts you on that branch, by rewriting HEAD
.
Internally, git rebase upstream branch
invokes git checkout branch
, so the whole process starts by getting you on the branch.
And why do we need to merge the changes into master? Doesn't that make a graph like [snipped]?
You need1 the merge
to move the label.
I prefer to draw the commit graph with letters, or sometimes just little o
nodes, with either single dashes or short arrows between them to denote the parenting relationships. And, stealing (but modifying) a leaf from git log --graph --oneline --decorate
, I add the branch name on the right with a longer arrow. If HEAD
names the branch, I add HEAD=
in front.
What you have right after the rebase can thus be drawn as:
... - E - F <-- master
\
G - H - I <-- HEAD=chgs
Here the commit labeled F
is the tip of branch master
, so master
points to F
(and F
points back to E
and so on). The tip of branch chgs
is commit I
; I
points back to H
; H
points back to G
; and G
points back to F
. And of course HEAD=chgs
so you're on that branch.
Once you git checkout master
, that updates your work tree as usual, and makes HEAD
point to master
which points to F
. Then if you run git merge chgs
, git looks to see if it's possible to do a "fast forward" merge:
... - E - F <-- HEAD=master
\
G - H - I <-- chgs
A fast forward merge is possible when the current commit (F
, in this case) is already an ancestor of the target commit (I
). If so, the branch label is peeled off the current commit and simply pasted onto the target commit:
... - E - F
\
G - H - I <-- chgs, HEAD=master
(There's no longer any ASCII-art-reason to keep the kink in the drawing [from F
down-and-right to G
], but I kept it for visual symmetry.)
As Cupcake noted in his faster answer, you can force a real merge with --no-ff
. I would draw this as:
... - E - F ------------- M <-- HEAD=master
\ /
G - H - I <-- chgs
In either case (fast forward, or "real merge"), once this is done, you can safely delete the branch label chgs
, as all of its commits are find-able by starting at master
and working backwards (along both branches if a "real merge"). Or, you can keep it and add more commits to it, if you prefer.
An interesting note here is that in this particular case the resulting work tree associated with commit M
is exactly the same as the one for commit I
. The key difference is that this creates an actual merge commit (a commit with multiple parents). The first parent is whatever commit was on the branch before—in this case, commit F
—and the remaining commits (just the one commit I
, here) are the branches being merged-in.
(You can override this—the work-tree being the same, I mean—with yet more merge flags, but in this case you would not want to. That sort of thing, overriding the work-tree, is mainly meant for "killing off" a branch while retaining the history: sort of a "look, we tried this and it didn't work" message to someone looking at the code next year.)
1Or don't need, if you don't want to move the labels. However, if you intend to push
your work back to someone, or let them pull
it from you, they will look at your labels. It's nice for them if you arrange your labels nicely for them. But it's always up to you.