context : I was trying to have a "clean" master branch, so I played with the options git merge --squash
and git rebase -i HEAD~x
, but for both I encountered problems (mainly because I don't really understand git) :
what I have :
C - D - E
/ \
A - B - - - - - F
and I would like to see a "clean" log history :
A - B - F
I finally discovered that I can manipulate the log output instead of changing the history, I think that is great :
using git log --first-parent master
gives me (simplified) :
F "merged commit"
B "second commit"
A "first commit"
but there is something that I don't understand : if I changed the commit message of F
with git commit amend -m "third commit"
, I will still get :
F "merged commit"
B "second commit"
A "first commit"
even though git log --graph
output :
* F "third commit"
|\
| |
| * E "feature third commit"
| |
| * D "feature second commit"
| |
| * C "feature first commit"
| |
|/
* B "second commit"
|
|
* A "first commit"
so, why isn't git log --first-parent master
showing me the amended message ?
CodePudding user response:
The git commit --amend
option does not change a commit. It makes a new and different commit. When Git does this, it updates one branch name, specifically the current one.
Suppose the original sequence of commits goes like this. Note: I tend to draw them horizontally with newer commits towards the right, rather than as git log --graph
draws them with newer commits towards the top:
C--D--E <-- your-branch
/ \
A--B---------F <-- master (HEAD)
That is, you're using the name master
as the current name, so that git log --graph --first-parent master
will show you commits F
, then B
, then A
, but so will git log --graph
without the branch name master
.
Now suppose that instead of the above, we have this:
C--D--E <-- your-branch
/ \
A--B---------F <-- dev (HEAD), master
This is the same graph, so git log --graph --first-parent master
will start at commit F
, then jump back to commit B
—skipping over the second-parent E
and its ancestors—and show you what you expect.
Now, using this same setup, let's run git commit --amend
and make note of the fact that it does not change commit F
at all. Instead, it makes a new (and supposedly improved) F'
that's like F
but has something different in it: in this case, we change the log message. So now we have:
C--D--E <-- your-branch
/ |\
A--B-------\-F <-- master
\ \
--------F' <-- dev (HEAD)
Running git log --graph --first-parent
, you'll see your new F'
, then B
, then A
, in that order. But the only name that git commit --amend
updated was dev
, not master
. So git log --graph --first-parent master
will show commit F
—the original, not-actually-amended-at-all commit—and then B
and then A
.
What this means for you is that you must be very careful with git commit --amend
. It needs the same care as git rebase
, which also works by copying existing commits to new-and-supposedly-improved replacement commits. The original commits always remain. No commit can ever be changed, not even by Git itself! If nobody ever sees the original commits any more, it looks as though the old commits have been changed into the new ones, provided you never look at the raw hash IDs.1 But they haven't: the original commits are still there, with their original hash IDs. We're just using the updated name to find the new commits instead of the old ones.
The care is required because some people—in this case, maybe even yourself—will still have names that refer to the old, un-improved commits. When using those names to find commits, they will see the old commits.
1And who does that, anyway?