Иногда это фактически невозможно (за некоторыми исключениями, когда вам может повезти иметь дополнительные данные), и решения здесь не будут работать.
Git не сохраняет историю ссылок (которая включает ветки). Он сохраняет только текущую позицию для каждой ветви (главы). Это означает, что вы можете потерять некоторую историю веток в git со временем. Когда вы, например, разветвляетесь, сразу теряется, какая ветка была оригинальной. Все, что делает ветка, это:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Вы можете предположить, что первым подтверждено ветвь. Это имеет место, но это не всегда так. Ничто не мешает вам совершить коммит в первую ветку после вышеуказанной операции. Кроме того, временные метки git не гарантируют надежность. Только тогда, когда вы посвятите себя обоим, они действительно станут структурно ветвями.
В то время как в диаграммах мы склонны к нумерации коммитов концептуально, git не имеет реального стабильного понятия последовательности, когда ветки дерева коммитов. В этом случае вы можете предположить, что числа (указывающие порядок) определяются меткой времени (было бы интересно увидеть, как пользовательский интерфейс git обрабатывает вещи, когда вы устанавливаете все метки времени на одно и то же).
Это то, что человек ожидает концептуально:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Вот что вы на самом деле получаете:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Вы могли бы предположить, что B1 является исходной веткой, но она может просто заразиться мертвой веткой (кто-то сделал checkout -b, но никогда не выполнял ее). До тех пор, пока вы не подтвердите обе эти возможности, вы не получите легитимную структуру веток в git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Вы всегда знаете, что C1 предшествовал C2 и C3, но вы никогда не могли достоверно узнать, предшествовал ли C2 раньше C3 или C3 до C2 (потому что вы можете установить время на своей рабочей станции, например, на что угодно). B1 и B2 также вводят в заблуждение, так как вы не можете знать, какая ветвь появилась первой. Во многих случаях вы можете сделать очень хорошее и, как правило, точное предположение. Это немного похоже на гоночную трассу. При прочих равных с машинами, вы можете предположить, что машина, которая находится на круге позади, начала круг позади. У нас также есть соглашения, которые очень надежны, например, master почти всегда будет отображать ветки с наибольшим временем жизни, хотя, к сожалению, я видел случаи, когда даже это не так.
Пример, приведенный здесь, является примером сохранения истории:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
Реальное здесь также вводит в заблуждение, потому что мы, люди, читаем его слева направо, от корня к листу (ссылка). Git этого не делает. Где мы делаем (A-> B) в наших головах git делает (A <-B или B-> A). Это читает это от ссылки до корня. Реферы могут быть где угодно, но имеют тенденцию быть листами, по крайней мере, для активных веток. Ссылка указывает на коммит, а коммиты содержат только то же самое для своих родителей, а не для своих детей. Когда коммит является коммитом слияния, у него будет более одного родителя. Первый родитель всегда является исходным коммитом, который был объединен. Другие родители всегда коммиты, которые были объединены в первоначальный коммит.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Это не очень эффективное представление, скорее, выражение всех путей, которые git может взять из каждого ref (B1 и B2).
Внутренняя память Git выглядит примерно так (не то, что родительский объект A появляется дважды):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Если вы сбросите raw git commit, вы увидите ноль или более родительских полей. Если есть ноль, это означает, что родитель отсутствует, а фиксация является корнем (у вас может быть несколько корней). Если он есть, это означает, что слияния не было и это не корневой коммит. Если их несколько, это означает, что фиксация является результатом слияния, и все родители после первого являются коммитами слияния.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Когда оба поразят А, их цепь будет одинаковой, до этого их цепь будет совершенно другой. Первый коммит, который есть у двух других коммитов, является общим предком и откуда они расходятся. здесь может быть некоторая путаница между терминами commit, branch и ref. На самом деле вы можете объединить коммит. Это то, что действительно делает слияние. Ссылка просто указывает на фиксацию, а ветвь - это не что иное, как ссылка в папке .git / refs /head. Расположение папки определяет, что ссылка является ветвью, а не чем-то другим, например тегом.
Когда вы теряете историю, слияние будет выполнять одно из двух в зависимости от обстоятельств.
Рассматривать:
/ - B (B1)
- A
\ - C (B2)
В этом случае слияние в любом направлении создаст новый коммит с первым родителем в качестве коммита, на который указывает текущая извлеченная ветвь, и вторым родителем в качестве коммита в конце ветви, которую вы слили в текущую ветвь. Он должен создать новый коммит, поскольку обе ветви имеют изменения со времени их общего предка, которые должны быть объединены.
/ - B - D (B1)
- A /
\ --- C (B2)
На данный момент D (B1) теперь имеет оба набора изменений из обеих ветвей (себя и B2). Однако вторая ветвь не имеет изменений от B1. Если вы объединяете изменения из B1 в B2 так, чтобы они были синхронизированы, то вы можете ожидать что-то, похожее на это (вы можете заставить git merge сделать это так, однако с помощью --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Вы получите это, даже если у B1 есть дополнительные коммиты. Пока в B2 нет изменений, которых нет в B1, две ветви будут объединены. Он выполняет ускоренную перемотку вперед, которая похожа на перебазирование (перебазирование также использует или линеаризует историю), за исключением того, что в отличие от перебазирования, поскольку только одна ветвь имеет набор изменений, ей не нужно применять набор изменений из одной ветви поверх этой из другой.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Если вы прекратите работу над B1, то в целом все хорошо для сохранения истории в долгосрочной перспективе. Только B1 (который может быть главным) продвигается обычно, поэтому местоположение B2 в истории B2 успешно представляет точку, в которой оно было объединено с B1. Это то, что git ожидает от вас, чтобы ветвь B от A, затем вы можете объединить A в B столько, сколько захотите, по мере накопления изменений, однако при объединении B обратно в A не ожидается, что вы будете работать над B и далее. , Если вы продолжаете работать над своей веткой после быстрой перемотки вперед, объединяя ее с веткой, над которой вы работали, то каждый раз стираете предыдущую историю B. Вы действительно создаете новую ветку каждый раз после быстрой перемотки в исходный код, а затем в ответ.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
От 1 до 3 и от 5 до 8 являются структурными ветвями, которые появляются, если вы следите за историей в течение 4 или 9. В git нет способа узнать, к какой из этих неназванных и не связанных структурных ветвей принадлежит именованная и ссылочная ветви как конец структуры. Из этого рисунка можно предположить, что от 0 до 4 принадлежит B1, а от 4 до 9 принадлежит B2, но, кроме 4 и 9, я не мог знать, какая ветвь принадлежит какой ветви, я просто нарисовал его так, чтобы иллюзия этого. 0 может принадлежать B2, а 5 может принадлежать B1. В этом случае существует 16 различных вариантов, к которым именованной ветви может принадлежать каждая из структурных ветвей.
Есть много стратегий мерзавца, которые работают вокруг этого. Вы можете заставить git merge никогда не переходить вперед и всегда создавать ветку слияния. Ужасный способ сохранить историю ветвей - использовать теги и / или ветви (теги действительно рекомендуется) в соответствии с некоторыми соглашениями по вашему выбору. Я действительно не рекомендовал бы фиктивный пустой коммит в ветке, в которую вы сливаетесь. Очень распространенное соглашение - не объединяться в ветку интеграции, пока вы не захотите по-настоящему закрыть свою ветку. Это практика, которой люди должны придерживаться, иначе вы работаете над тем, чтобы иметь ветви. Однако в реальном мире идеал - это не всегда практическое значение, а правильные поступки не жизнеспособны для любой ситуации. Если что ты