I have two git branches that produce logs like so:
Branch A
commit xyz @ 11:00 am
commit yut @ 11:10 am
commit mot @ 11:30 am
Branch B
commit xyz @ 11::00 am
commit shu @ 11: 20 am
commit yam @ 11: 40 am
Merge Branch B to Branch A
Now when i check the logs of Branch A, it shows as follows:
Branch A
commit xyz @ 11:00 am
commit yut @ 11:10 am
commit shu @ 11: 20 am
commit mot @ 11:30 am
commit yam @ 11: 40 am
commit merge Branch B 11:50 am
but when i check the commit object of shu, its parent hash is xyz and not yut (despite yut preceding it in the logs).
is the git log function just reading the commit history of both branches (through the merge commit) and then displaying them in order of 'time' committed?
wouldnt differences in local time on each computer around the world mess this order up?
Besides LeGEC's answer, it's worth considering how git log
could show commits. This is important because git log
actually does implement many of these options.
Remember that each commit is itself numbered with a unique hash ID, and each commit stores the numbers—the hash IDs—of its parent (predecessor) commit or commits. Most commits have a single parent, which produces a nice, simple linear (but backwards-looking) chain of commits:
... <-F <-G <-H
where H
is the last commit in the chain. The uppercase H
here stands in for the actual hash ID of the commit (whatever that may be). The contents of commit H
come in two parts:
H
stores the actual hash ID of earlier commit G
.We say, then, that commit H
points to earlier commit G
. Git uses a hash ID to find the actual commit, in its big database of all the Git objects in the repository, so as long as we give Git hash ID H
, it can use that to read out the contents, including hash ID G
. Then it has a hash ID for G
, which it can use to read out G
's contents, which include hash ID F
, which it can use to read F
's contents, and so on.
The place that Git gets H
's hash ID is from the branch name:
... <-F <-G <-H <--master
for instance. We say that the branch name points to the last commit in the chain.
What's special about a merge commit is that instead of a single parent, it has two parents. That is, the merge commit points (backwards, as always) to two commits instead of just one:
...--I--J <-- branch1
\
M <-- merged
/
...--K--L <-- branch2
Here, we have made a branch name merged
that points to commit M
. From M
, we can go backwards to either J
or L
, when we work one commit at a time (as Git tends to do).
But what if, as git log
would like, we want to look at both commits J
and L
? If we're forced to work with one commit at a time—as we often are—how will we manage that?
The method that Git uses is to put commit hash IDs into a queue, or more specifically, a ]priority queue](https://en.wikipedia.org/wiki/Priority_queue). That is, if we're on branch merged
and we run git log
, Git:
Uses the name merged
to find M
's commit hash ID, and puts M
in the queue. The queue now has just one entry.
Takes the first commit off the queue (making the queue empty) and examines it. This commit is a merge commit, so git log
doesn't print much about it, at least by default—that is, if you ask for patches with git log -p
you don't get one—but it does put both parents into the queue.
Since the queue has an ordering property—higher priority commits get used before lower priority commits—the priority determines which of J
or L
will get shown next.
Takes the first commit off the queue (leaving one commit in the queue) and examines it. This commit, whether it is J
or L
, is an ordinary commit with a single parent, so Git prints it and includes a patch if you asked for one.1 Then Git puts the parent of this commit, whichever parent that is, into the queue (so that there are again two entries in the queue).
Repeats the above "take first commit from the queue and work with it" until the queue is eventually empty—this can take a long time!—or you tell Git to quit.
The order in which you see commits therefore depends on what you've set up about the priorities.
Besides the various ordering options, though, there's a very important option, --first-parent
, that affects how Git handles merge commits. In particular, we saw in step 2 above that Git put both parents of merge commit M
into the queue. If we use --first-parent
, Git doesn't put both parents into the queue. Instead, Git puts only the first parent of M
into the queue.
The first parent of a merge is therefore pretty important. But which parent is the first one? The answer to that depends on how whoever did the merge, did the merge.
When you run:
git checkout somebranch
git merge otherbranch
and Git actually makes a merge commit like M
, the first parent of the new merge commit is the commit you checked out in the git checkout
step: the last commit of somebranch
before you make the merge. The second parent—the one that git log --first-parent
will ignore—is the other branch's last commit (at the time you run git merge
, that is). So if you have:
...--G--H--I <-- somebranch (HEAD)
...--J--K--L <-- otherbranch
—the (HEAD)
here indicates that you did a git checkout somebranch
and got commit I
—and run git merge otherbranch
, the new merge commit will have commit I
as its first parent, and L
as its second:
...--G--H--I--M <-- somebranch (HEAD)
/
...--J--K--L <-- otherbranch
Note that M
is now the last commit on somebranch
, and commits J-K-L
are now on both branches. The git log
command will therefore show commits J-K-L
unless you use --first-parent
, which specifically excludes these commits because they came from a side branch.
Note that using git pull
can result in what some call foxtrot merges, where git log --first-parent
shows you the commits you didn't want to see, instead of the ones that you did. That's because git merge
treats your (local) branch as the main line, instead of treating the other Git's branch as the main line and your work as a feature-branch. On the other hand, this can be the order you do prefer. As the person in control—the one making the merge—it is up to you to decide whether to have a preference, and if so, what that preference is.