2

In a Udacity lesson covering relative commit references, it says:

^ indicates the parent commit, ~ indicates the first parent commit

The main difference between the ^ and the ~ is when a commit is created from a merge. A merge commit has two parents. With a merge commit, the ^ reference is used to indicate the first parent of the commit while ^2 indicates the second parent. The first parent is the branch you were on when you ran git merge while the second parent is the branch that was merged in.

Based on the following output of running git log --oneline --graph on the command line:

graph

The commit with SHA f69811c is HEAD~4^2 relative to the (topmost, with the head pointer) commit 9ec05ca. The merge commit 1a56a81's second parent is f69811c, and the lesson explains this "HEAD~4 references the fourth parent commit of the current one and then the ^2 tells us that it’s the second parent of the merge commit (the one that got merged in)."

Based on the lesson text quoted in yellow, it seems e014d91 is 1a56a81's first parent, because it is the parent with the same branch as the merge commit.

Based on that information, what would e6c65a6's relation to 1a56a81 be?

I don't think it's also a second parent (if there can be multiple first and second parents...), even though it was also the branch that was merged into master, because that answer was rejected in a lesson quiz.

Also, if I'm right that e014d91 is 1a56a81's first parent, what is the relation of 2097521 and 3772ab1 to 1a56a81 (it would seem they're also first parents, again, if a merge commit can actually have multiple parents)?

nCardot
  • 5,992
  • 6
  • 47
  • 83

2 Answers2

7

I think it might help to view some alternative presentations of a graph.

Consider this very simple diamond-shaped graph, with later commits drawn higher and earlier commits drawn lower:

  D
 / \
B   C
 \ /
  A

Here, D could be the extremely-shortened hash ID of a merge commit, with B and C being its two inputs (two parents, in Git's terminology). A is the (one, single) parent of both B and C. As a result, B and C are siblings, or would be if Git used that concept directly: both have the same parent, so they must be brother and sister (or two brothers or two sisters or whatever). But Git doesn't normally talk about commits this way—it's normally only interested in immediate parent/child relationships.

We could—and git log --graph does—also draw this as:

*    d...... fourth message
|\
| *  c...... third message
* |  b...... another message
|/
*  a...... some commit message

Like human children, Git's "children" can have more than one parent. However, the most typical case is to have just one parent, in which case that parent is the first, last, and only parent. You can number it—C^1 is A, for instance—but there's no real need. There is no C^2 and asking for it will just get you an error.

In StackOverflow postings, I like to draw my graphs with earlier commits at the left and later ones at the right, like this:

  B
 / \
A   D   <-- master (HEAD)
 \ /
  C   <-- develop

This gives me room to insert the branch names, and attach the word HEAD to one of them, as Git normally does. However, this makes it difficult to tell which parent is the first, and which is the second. Note that the first vertical diagram above has the same problem.

Any commit with at least two parents is called a merge commit. This uses the word merge as an adjective, modifying commit. We'll also see it often as just a merge, using the word merge as a noun. As ElpieKay notes, a merge commit can have more than two parents, but these octopus merges (as Git calls them) don't do anything you can't do with just pairwise merges, so they are mostly just for showing off. :-) When you do have a merge with three or more parents, you can number all of them. The only special distinction that Git itself makes is for the first parent, though, using the --first-parent flag to various Git commands such as git log.

Confusingly, the git merge command does not have to make a merge commit. It has two parts, which I like to refer to merge as a verb—the act of combining work—and then subsequently making a commit. The commit it makes is a merge commit unless you tell it not to. And, as if this weren't confusing enough already, several additional Git commands do the merge as a verb part without producing a merge commit. So it's important to keep in mind the distinction between the verb form, to merge, and the noun or adjective form.

It's worth noting a few more items:

  • All commits—all Git objects, really—are read-only. Once a commit is made, it can never be changed, because its hash ID—its true name, as it were—is computed by running all of its underlying data through a hash function. If you were to somehow change even a single bit inside the commit, you'd get a new and different hash, and hence a new and different commit.

  • Since the parent or parents exist when the child is made, the child can record the parents.

  • But since its child or children do not exist yet when the parent is made, the parent cannot record its children.

  • It's these backwards parent <- child linkages that form the commit graph.1

This means that Git's internal linkages are all, always, backwards. Git must start at the last, child-most, commit and work backwards. That's why branch names like master always point to the tip commit of the branch. As Git works backwards, one commit at a time, merges—which have two or more parents—present a problem: Git can only move back from the child to one of the parents. Git's usual solution to this problem is to place all of the parents into a queue, then work on the first commit in the queue.

The --first-parent flag tells Git to put only the first parent into the queue, ignoring the second parent (and any additional parents if this is an octopus merge). That allows Git to walk the commit graph without ever having more than one commit at a time to deal with.


1Mathematically, any graph G is defined by a collection of vertices V and edges E, which we write as G = (V, E). A graph can be directed, and Git's is: the links from vertex to vertex go only one way. Such edges are called arcs. In our case I prefer to call the vertices nodes; these are the actual commits themselves, and each node contains a list of all its outgoing arcs, i.e., the hash IDs of the parent commits.

Git's commit graph is not only directed but also acyclic, meaning that if we start at any one commit, and walk through the graph, we'll never return to that same commit. No parent can be its own child, in other words. This is a useful and important property for the various graph transformations that Git does, so we sometimes talk about the Git commit graph as a DAG, which is short for Directed Acyclic Graph. The commit graph, or commit DAG, represents all the snapshots you have ever made.

Note that each source snapshot is simply attached to one commit. The graph operations don't have to care what is in the corresponding snapshots: they look only at the graph itself!

torek
  • 448,244
  • 59
  • 642
  • 775
  • is there a way to `revspec` the second (or nth) parent. ie like `HEAD^` for the first parent if `HEAD` is a merge – CervEd Nov 24 '22 at 08:54
  • nvm, it's `HEAD^n`. I always thought `HEAD~` and `HEAD^` were the same – CervEd Nov 24 '22 at 09:00
  • @CervEd: right: since `n` defaults to 1, and `HEAD~` means *first parent*, `HEAD~1` and `HEAD^1` are synonymous: both mean the first parent of the current commit. But the second parent of the current commit is `HEAD^2`, while the first parent of the first parent of the current commit is `HEAD^1^1` or `HEAD~2`. So once the number isn't just 1, they become different. – torek Nov 24 '22 at 09:40
2

e6c65a6 is 1a56a81's 2nd parent(f69811c)'s 1st parent.

3772ab1 is 1a56a81's 1st parent(e014d91)'s 1st parent(2097521)'s 1st parent.

A commit can have zero parent. It's a root commit.

A commit can have 2 parents, which is the result of merging one branch to another in the way of "true merge".

A commit can have more than 2 parents, which is the result of merging 2 or more branches to one branch at the same time, in the way of "octopus merge".

To find out the parent commits of a commit X, you could use:

git log -1 X --pretty=%P

or

git log -1 X --pretty=raw

or

git cat-file -p X

etc.

ElpieKay
  • 27,194
  • 6
  • 32
  • 53
  • Are e6c65a6 and f69811c both 1a56a81's second parents since they are both on a different branch (sidebar) than the one merged into (master)? Also, are e014d91 and 209752a both first parents of 1a56a81 since they are on the same branch as the one merged into? – nCardot May 21 '18 at 02:42
  • 1
    e6c65a6 is the parent's parent, not a second parent. If a commit has a second parent, it has only one second parent. – ElpieKay May 21 '18 at 03:08