場合によっては、事実上不可能であり(追加のデータがあると幸運な場合を除いて)、ここでのソリューションは機能しません。
Gitは(ブランチを含む)参照履歴を保持しません。各ブランチ(ヘッド)の現在の位置のみを保存します。つまり、時間の経過とともにgitの一部のブランチ履歴が失われる可能性があります。たとえば、ブランチを作成すると、どのブランチが元のブランチであったかはすぐに失われます。ブランチが行うことはすべて次のとおりです。
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
あなたは最初にコミットされたものがブランチであると仮定するかもしれません。これはそうなる傾向がありますが、常にそうとは限りません。上記の操作の後、最初にいずれかのブランチにコミットすることを妨げるものは何もありません。また、gitタイムスタンプの信頼性は保証されません。あなたが両方にコミットするまで、それらが本当に構造的に枝になることはありません。
ダイアグラムでは、概念的にコミットに番号を付ける傾向がありますが、gitには、コミットツリーが分岐するときのシーケンスの実際の安定した概念がありません。この場合、数値(表示順)はタイムスタンプによって決定されると想定できます(すべてのタイムスタンプを同じに設定した場合にgit UIがどのように処理するかを見るのは楽しいかもしれません)。
これは人間が概念的に期待することです:
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はどちらのブランチが最初に来たのかわからないため、誤解を招く可能性もあります。多くの場合、非常に適切で通常は正確な推測を行うことができます。レース場に少し似ています。すべてのものが一般的に車と同じである場合、1周遅れの車が1周遅れを始めたと想定できます。また、非常に信頼性の高い規則もあります。たとえば、マスターはほとんどの場合、最も長く存続するブランチを表しますが、悲しいことに、これが当てはまらない場合もあります。
ここに示す例は、履歴を保存する例です。
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)
私たち人間がそれを左から右に、根から葉に(ref)読んでいるので、ここの本当も誤解を招くものです。Gitはそれを行いません。頭の中で(A-> B)を実行する場所では、gitは(A <-BまたはB-> A)を実行します。refからrootにそれを読み込みます。参照はどこにあってもかまいませんが、少なくともアクティブなブランチでは、リーフになる傾向があります。refはコミットを指し、コミットには親への「いいね!」のみが含まれ、子は含まれません。コミットがマージコミットの場合、複数の親が存在します。最初の親は常に、マージされた元のコミットです。他の親は常に、元のコミットにマージされたコミットです。
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が各参照(B1およびB2)から取ることができるすべてのパスの表現です。
Gitの内部ストレージは次のようになります(親としてのAが2回表示されるわけではありません)。
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
raw git commitをダンプすると、0個以上の親フィールドが表示されます。ゼロがある場合、それは親がないことを意味し、コミットはルートです(実際には複数のルートを持つことができます)。存在する場合は、マージがなかったことを意味し、ルートコミットではありません。複数ある場合は、コミットはマージの結果であり、最初の親はすべてマージコミットであることを意味します。
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)
両方がAを押すと、チェーンは同じになりますが、その前にチェーンはまったく異なります。他の2つのコミットに共通する最初のコミットは、共通の祖先であり、そこから分岐しました。ここで、commit、branch、refの用語の間には多少の混乱があるかもしれません。実際にコミットをマージすることができます。これは実際にマージが行うことです。refは単にコミットを指し、ブランチは.git / refs / headsフォルダー内のrefにすぎません。フォルダーの場所は、refがタグなどではなくブランチであることを決定するものです。
履歴を失うところは、マージは状況に応じて2つのことのいずれかを行うということです。
検討してください:
/ - B (B1)
- A
\ - C (B2)
この場合、いずれかの方向のマージにより、現在のチェックアウトされたブランチによって指し示されるコミットとして最初の親があり、現在のブランチにマージしたブランチの先端で2番目の親がコミットとして新しいコミットが作成されます。両方のブランチには共通の祖先を組み合わせる必要があるため、両方のブランチに変更があるため、新しいコミットを作成する必要があります。
/ - B - D (B1)
- A /
\ --- C (B2)
この時点で、D(B1)には、両方のブランチ(それ自体とB2)からの両方の変更セットがあります。ただし、2番目のブランチにはB1からの変更はありません。B1からB2への変更をマージして同期化すると、次のような結果が期待できます(gitマージで--no-ffを使用してこのようにすることもできます)。
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
B1に追加のコミットがある場合でも、それを取得します。B1にない変更がB2にない限り、2つのブランチはマージされます。リベースと同様に早送りを行います(ただし、リベースとは異なり、1つのブランチにのみ変更セットがあるため、1つのブランチの変更セットを別のブランチの変更セットの上に適用する必要はありません。
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
B1での作業をやめれば、長期的には履歴を保存するのにほとんど問題ありません。通常はB1(マスターである可能性があります)のみが進むため、B2の履歴におけるB2の位置は、B1にマージされたポイントを正常に表します。これはgitがあなたに期待していることで、Aから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戦略があります。gitマージを強制的に早送りせず、常にマージブランチを作成することができます。ブランチの履歴を保存するための恐ろしい方法は、選択したいくつかの慣例に従って、タグやブランチ(タグは本当に推奨されます)を使用することです。マージするブランチでダミーの空のコミットを実行することはお勧めしません。非常に一般的な慣習は、ブランチを完全に閉じるまで統合ブランチにマージしないことです。これは、他の方法ではブランチがあるという点を回避しているのと同じように、人々が従おうとする習慣です。ただし、現実の世界では、理想は必ずしも実用的ではありません。つまり、正しいことを行うことがすべての状況で実行可能であるとは限りません。もしあなたが