TL; DR
Реальный ключ к приведенному ниже материалу заключается в понимании того, что ветви - или ветви имена - не там, где возникают отношения. Отношения между коммитами происходят из совершает . Имена ветвей просто помогут вам начать на графике!
Long
Помимо того, что VonC сказал , есть более общее описание из теории графов. Коммиты в Git-репозитории образуют направленный ациклический граф или DAG.
A граф G представляет собой набор узлов или вершин V вместе с набором ребер E , которые соединяются между вершинами, поэтому формулировка G = (V, E) . Граф направленный представляет собой график, в котором ребра заменены дугами , то есть стрелками. Вот два изображения из Википедии. Первый слева показывает регулярный (ненаправленный) график. Второй показывает ориентированный граф:
![image](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/Directed.svg/200px-Directed.svg.png)
When the graph is directed, you can only move from one node to another in the direction given by the arrows. The directed graph above is cyclic because starting at any node, you can move around and wind up back at that node.
An acyclic graph has no cycles, i.e., no matter which node you start from, you can never get back to where you started. The kind of graph that Git commits form is directed and acyclic, hence it is a DAG.
In the Git commit graph, each node or vertex is a commit, uniquely identified by its hash ID. Each arc, along with its direction, is a result of the parent commit hash ID(s) associated with the node. Hence a natural way to draw a Git graph is like this:
...
The name master
lets you start at the end, i.e., at commit H
, and work backwards. The connecting arcs / arrows come out of the node as parent pointers.
An ancestor of some commit is then any commit you can reach by following the arrows. Suppose we have a more complex graph, like this one. I can't draw the internal arrows properly here, but because I put newer nodes toward the right, all the arrows point leftwards—maybe left and up, or left and down, but definitely leftwards. Our graph looks like this:
D--E
/ \
A--B--C H--I--J
Commit A
is a node with no outgoing arcs. In a normal (non-Git) graph, this would make it a leaf node, but Git does everything backwards, so in Git, A
is a root node. Commit B
points back to A
, C
points to B
, and so on, until we get to H
. Commit H
is a merge commit: it has two outgoing arcs, pointing to commits E
and G
both. From H
, then, we can walk either the top line or the bottom line—or along both lines—and get back all the way to the root A
.
These names, as you might guess, are branch names. They act as entry points to tip commits. From tip commit L
we can work back along the bottom row, but we cannot reach commit H
—we get to G
by going from L
to K
to G
, but G
points only to F
—so we cannot work our way up to H
. All the arrows point backwards. From commit J
, though, we can work backwards to H
and hence to either row.
This means that commits K
and L
are only on develop
, while commits D
, E
, H
, I
, and J
are only on master. All other commits are on both branches. That's the reachability notion that VonC is describing.
Mathematically, in a DAG, we define predecessor and successor notions. For predecessor or precedes, we use the bent-less-than ≺ symbol: A ≺ B if you can go from A to B. We use the bent-greater-than ≻ ("successor" or "succeeds") symbol for the opposite. These define a частичный порядок в узлах на графике. Конечно, узел равен самому себе, и мы можем сказать, что для данного узла A = A, но также A ≼ A и A ≽ A.
Поскольку Git работает в обратном направлении, это меняется на противоположное: A ≺ B (A является предшественником B), если вы можете перейти от B к A! Чтобы избежать путаницы, Git использует is-ancestor вместо перед . В общем, Git также определяет is-ancestor как true, если вы сравниваете узел с самим собой, так что все узлы являются их собственными предками.
Поскольку внутренние стрелки Git работают исключительно в обратном направлении, Git не предлагает оператора is-потомок . Вы должны отменить тест is-ancestor (но см. Ниже). Если вы хотите знать, «является ли узел X потомком узла Y», вы можете начать с вопроса: «Y является предком X». Помните, что is-ancestor говорит «да», если два хеш-идентификатора совпадают!
Начиная с Git версии 1.8.0, простой способ проверить, связаны ли коммиты, обозначенные хешами $H1
и $H2
, с этим понятием предков:
if git merge-base --is-ancestor $H1 $H2; then
echo commit $H1 is an ancestor of $H2
else
echo commit $H1 is not an ancestor of $H2
fi
Обратите внимание, что поскольку ≼ определяет только частичный порядок, тот факт, что $H1
является не предком $H2
, не означает , что $H2
обязательно является предком $H1
. Например, в нашем примере выше коммиты J
и L
являются просто братьями и сестрами: J
не является родителем L
, но L
также не является родителем J
. Это означает, что если is-ancestor говорит «да», то вы сделали, но если он говорит «нет», вам все равно придется проверить обратный случай, чтобы выбрать между «является потомком» и «является родным братом».
Вы также можете иметь непересекающихся подграфов в хранилище. Здесь у вас есть как минимум два корня и как минимум два кончика ветви, но между узлами нет связи:
A--B--C <-- branch1
D--E--F <-- branch2
Нет никакой связи между коммитами верхнего ряда и коммитами нижнего ряда. Однако, если мы добавим слияние , это сделает их связанными в некотором смысле:
A--B--C
\
G <-- branch1
/
D--E--F <-- branch2
Теперь коммиты A-B-C
и D-E-F
как бы "связаны браком" с слиянием в G
, потому что теперь мы можем начинать с G
и работать в обратном направлении либо к A
или D
. Ни A
, ни D
не являются предками друг друга, но оба они являются предками G
.