gitの仕組み

このエッセイでは、Gitの仕組みについて説明します。 プロジェクトのバージョン管理にGitを使用するのに十分なGitに精通していることを前提としています。



エッセイは、Gitの基になっているグラフ構造と、このグラフのプロパティがGitの動作をどのように決定するかに焦点を当てています。 基本を学び、APIを使用した実験から得られた仮説ではなく、信頼できる情報に基づいてアイデアを構築します。 適切なモデルを使用すると、Gitが何をしたのか、Gitが何をし、何をしようとしているのかをよりよく理解できます。



テキストは、単一のプロジェクトで作業する一連のチームに分割されます。 Gitの基礎となるグラフのデータ構造についての観察があります。 観察結果は、グラフのプロパティとそれに基づく動作を示しています。



より深く没頭するために読んだ後、GitのJavaScript実装の豊富にコメントされたソースコードを参照できます。



プロジェクト作成



~ $ mkdir alpha ~ $ cd alpha
      
      





ユーザーはアルファディレクトリを作成します



 ~/alpha $ mkdir data ~/alpha $ printf 'a' > data/letter.txt
      
      





このディレクトリに移動し、データディレクトリを作成します。 内部で、彼は「a」の内容を含むletter.txtファイルを作成します。 alphaディレクトリは次のようになります。



 alpha └── data └── letter.txt
      
      





リポジトリの初期化



 ~/alpha $ git init Initialized empty Git repository
      
      





git initは、現在のディレクトリをGitリポジトリに移動します。 これを行うために、彼は.gitディレクトリを作成し、その中にいくつかのファイルを作成します。 Git構成全体とプロジェクト履歴を定義します。 これらは普通のファイルです-魔法ではありません。 ユーザーはそれらを読んで編集できます。 つまり、ユーザーはプロジェクト自体のファイルと同じくらい簡単にプロジェクトの履歴を読んで編集できます。



alphaディレクトリは次のようになります。



 alpha ├── data | └── letter.txt └── .git ├── objects etc...
      
      





.gitディレクトリとその内容はGitを参照しています。 他のすべてのファイルは作業コピーと呼ばれ、ユーザーに属します。



ファイルを追加する



 ~/alpha $ git add data/letter.txt
      
      





ユーザーはgit add on data / letter.txtを実行します。 2つのことが起こります。



最初に、新しいblobファイルが.git / objects /ディレクトリに作成されます。 data / letter.txtの圧縮されたコンテンツが含まれています。 その名前は、生成されたコンテンツベースのハッシュです。 たとえば、Gitはaからハッシュを作成し、2e65efe2a145dda7ee51d1741299f848e5bf752eを取得します。 ハッシュの最初の2文字は、オブジェクトデータベースのディレクトリ名に使用されます:.git / objects / 2e /。 ハッシュの残りは、追加されたファイルの内部を含むblobファイルの名前です:.git / objects / 2e / 65efe2a145dda7ee51d1741299f848e5bf752e。



ファイルをGitに追加するだけで、その内容がオブジェクトディレクトリに保存されることに注意してください。 ユーザーが作業コピーからdata / letter.txtを削除すると、そこに保存されます。



次に、git addはファイルをインデックスに追加します。 インデックスは、Gitが従うように指示されたすべてのファイルを含むリストです。 .git / indexに保存されます。 監視対象ファイルに応じた各行は、その内容と追加時間をハッシュします。 これは、git addコマンドの実行後にインデックスの内容を取得する方法です。



 data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
      
      





ユーザーは、1234を含むdata / number.txtファイルを作成します。



 ~/alpha $ printf '1234' > data/number.txt
      
      





作業コピーは次のようになります。



 alpha └── data └── letter.txt └── number.txt
      
      





ユーザーがファイルをGitに追加します。



 ~/alpha $ git add data
      
      





git addコマンドは、data / number.txtの内容を保存するblobファイルを作成します。 data / number.txtのインデックスにエントリを追加し、blobをポイントします。 git addを再度実行した後のインデックスの内容は次のとおりです。



 data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e data/number.txt 274c0052dd5408f8ae2bc8440029ff67d79bc5c3
      
      





ユーザーがgit add dataコマンドを指定したにもかかわらず、データディレクトリのファイルのみがインデックスにリストされることに注意してください。 データディレクトリ自体は個別に指定されていません。



 ~/alpha $ printf '1' > data/number.txt ~/alpha $ git add data
      
      





ユーザーがdata / number.txtを作成したとき、彼は1234ではなく1を書き込みたいと思っていました。彼は変更を加え、ファイルを再びインデックスに追加しました。 このコマンドは、新しいコンテンツを持つ新しいblobを作成します。 data / number.txtのインデックスのエントリを新しいblobで更新します。



コミットする



 ~/alpha $ git commit -m 'a1' [master (root-commit) 774b54a] a1
      
      





ユーザーはa1をコミットします。 Gitはそれに関するデータを表示します。 すぐに説明します。



commitコマンドには3つのステップがあります。 コミットするプロジェクトのバージョンの内容を表すグラフを作成します。 コミットオブジェクトを作成します。 現在のブランチを新しいコミットオブジェクトにリダイレクトします。



グラフ作成



Gitは、インデックスからツリーグラフを作成することにより、プロジェクトの現在の状態を記憶します。 このグラフは、プロジェクト内の各ファイルの場所と内容を記録します。



グラフは、ブロブとツリーの2種類のオブジェクトで構成されます。 BLOBはgit addを介して保存されます。 ファイルの内容を表します。 コミット時にツリーが保存されます。 ツリーは、作業コピー内のディレクトリを表します。 以下は、新しいコミットでデータディレクトリの内容を記録したツリーオブジェクトです。



 100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt 100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt
      
      





最初の行は、data / letter.txtの複製に必要なすべてを記録します。 最初の部分には、ファイルのアクセス許可が格納されます。 2番目は、ファイルの内容がツリーではなくblobに保存されることです。 3つ目はBLOBハッシュです。 4番目はファイル名です。



同様に、2行目はdata / number.txtに適用されます。



以下は、プロジェクトのルートディレクトリであるalphaのツリーオブジェクトです。



 040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data
      
      





1本の線はデータツリーを指します。











上のグラフでは、ルートツリーはデータツリーを指しています。 データツリーは、data / letter.txtおよびdata / number.txtのBLOBを指します。



コミットオブジェクトの作成



git commitは、グラフの作成後にコミットオブジェクトを作成します。 コミットオブジェクトは、.git / objects /:内の別のテキストファイルです。



 tree ffe298c3ce8bb07326f888907996eaa48d266db4 author Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500 committer Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500 a1
      
      





最初の行はグラフツリーを指します。 ハッシュ-作業コピーのルートを表すツリーオブジェクト。 つまり、アルファディレクトリです。 最後の行は、コミットのコメントです。











現在のブランチを新しいコミットにダイレクトする



最後に、commitコマンドは現在のブランチを新しいコミットオブジェクトに向けます。



現在のブランチは何ですか? Gitは.git / HEADのHEADファイルに移動し、以下を確認します。



 ref: refs/heads/master
      
      





これは、HEADがマスターを指していることを意味します。 masterは現在のブランチです。



HEADとmasterはリンクです。 リンクは、特定のコミットを識別するためにGitまたはユーザーが使用するラベルです。



これはリポジトリ内の最初のコミットであるため、マスターリンクを表すファイルは存在しません。 Gitは.git / refs / heads / masterにファイルを作成し、その内容(コミットオブジェクトのハッシュ)を設定します。



 74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd
      
      





(読み取り中にコマンドをGitに入力すると、ハッシュa1は私のものとは異なります。コンテンツオブジェクト(ブロブとツリー)のハッシュは常に同じになります。ただし、作成者の日付と名前を考慮するため、コミットはありません)。



グラフにGit HEADとmasterを追加します。











コミット前のように、HEADはマスターを指します。 しかし、今ではmasterが存在し、新しいコミットオブジェクトをポイントしています。



最初のコミットは行いません



以下は、コミットa1後のグラフです。 作業コピーとインデックスが含まれています。











作業コピー、インデックス、およびコミットの内容は、data / letter.txtおよびdata / number.txtと同じであることに注意してください。 インデックスとHEADはBLOBを示すためにハッシュを使用しますが、作業コピーの内容はテキストとして他の場所に保存されます。



 ~/alpha $ printf '2' > data/number.txt
      
      





ユーザーはdata / number.txtの内容を2に変更します。これにより、作業コピーが更新されますが、インデックスとHEADは変更されません。











 ~/alpha $ git add data/number.txt
      
      





ユーザーがファイルをGitに追加します。 これにより、2を含むblobがオブジェクトディレクトリに追加されます。 data / number.txtのインデックスエントリポインターは、新しいBLOBを指します。











 ~/alpha $ git commit -m 'a2' [master f0af7e6] a2
      
      





ユーザーがコミットします。 彼の手順は以前と同じです。



最初に、インデックスの内容を表す新しいツリーグラフが作成されます。



data / number.txtのインデックスエントリが変更されました。 古いデータツリーは、データディレクトリのインデックス付き状態を反映しなくなりました。 新しいデータツリーオブジェクトを作成する必要があります。



 100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt 100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt
      
      





新しいオブジェクトのハッシュは、古いデータツリーとは異なります。 このハッシュを書き込むには、新しいルートツリーを作成する必要があります。



 040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data
      
      





次に、新しいコミットオブジェクトが作成されます。



 tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556 parent 774b54a193d6cfdd081e581a007d2e11f784b9fe author Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500 committer Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500 a2
      
      





コミットオブジェクトの最初の行は、新しいルートオブジェクトを指します。 2行目はa1:親コミットを指します。 親コミットを検索するために、GitはHEADに進み、マスターに移動して、コミットa1のハッシュを見つけます。



第三に、マスターブランチファイルの内容が新しいコミットのハッシュに変更されます。









コミットa2









作業コピーとインデックスなしのGitグラフ



グラフのプロパティ:



•コンテンツはオブジェクトのツリーとして保存されます。 これは、変更のみがデータベースに保存されることを意味します。 上記のグラフをご覧ください。 コミットA2は、コミットA1の前に作成されたBLOBを再利用します。 同様に、コミットからコミットまでのディレクトリ全体が変更されない場合、そのツリーとすべてのブロブおよび基礎となるツリーを再利用できます。 通常、コミット間の変更はわずかです。 これは、Gitが大きなコミット履歴を保存でき、スペースをほとんど占有しないことを意味します。



•各コミットには祖先があります。 これは、プロジェクト履歴をリポジトリに保存できることを意味します。



•リンク-特定のコミット履歴のエントリポイント。 これは、コミットに意味のある名前を付けることができることを意味します。 ユーザーは、fix-for-bug-376などのリンクを使用して、自分のプロジェクトにとって意味のある血統の形で作業を整理します。 GitはHEAD、MERGE_HEAD、FETCH_HEADなどのシンボリックリンクを使用して、コミット履歴編集コマンドをサポートします。



•オブジェクト/ディレクトリ内のノードは変更されていません。 これは、コンテンツが編集されているが、削除されていないことを意味します。 これまでに追加されたすべてのコンテンツ、行われたすべてのコミットは、オブジェクトディレクトリのどこかに保存されます。



•リンクは変更可能です。 そのため、リンクの意味が変わる場合があります。 masterが指すコミットは、現時点ではプロジェクトの最良のバージョンかもしれませんが、すぐに新しいコミットに置き換えられる可能性があります。



•作業コピーと参照されたコミットはすぐに利用できます。 他のコミットはそうではありません。 つまり、最近の履歴はより簡単に呼び出すことができますが、より頻繁に変更されます。 Gitの記憶は絶えず消滅していると言えますが、Gitの記憶はますます硬直した突きで刺激される必要があります。



作業コピーは、リポジトリのルートにあるため、履歴から呼び出すのが最も簡単です。 彼女の呼び出しにはGitコマンドさえ必要ありません。 しかし、これは歴史上最も不安定なポイントでもあります。 ユーザーはファイルのダースバージョンを作成できますが、Gitはそれらが追加されるまでそれらのいずれも書き込みません。



HEADが指すコミットは非常に簡単に呼び出すことができます。 確認されたブランチの最後です。 その内容を表示するために、ユーザーは作業コピーを保存して調べることができます。 同時に、HEADは最も頻繁に変更されるリンクです。



特定のリンクによって参照されるコミットは簡単に呼び出すことができます。 ユーザーはこのスレッドを確認するだけです。 ブランチの終わりはHEADよりも頻繁に変更されませんが、ブランチ名の意味を変更するのに十分です。



参照されていないコミットを呼び出すことは困難です。 ユーザーがリンクを離れるほど、コミットの意味を再現するのが難しくなります。 しかし、過去になればなるほど、最後に視聴されてから誰かが物語を変えた可能性は低くなります。



コミットの検証(チェックアウト)



 ~/alpha $ git checkout 37888c2 You are in 'detached HEAD' state...
      
      





ユーザーは、ハッシュを使用してコミットa2を確認します。 (これらのコマンドを実行すると、このコマンドは機能しません。gitlogを使用して、コミットa2のハッシュを見つけます)。



確認は4つのステップで構成されます。



まず、Gitはコミットa2とそれが指すグラフを受け取ります。



第二に、彼は作業コピーのグラフにファイルの記録を作成します。 その結果、変更は発生しません。 HEADはすでにマスターを通じてa2をコミットするように指示しているため、作業コピーにはすでにグラフの内容が含まれています。



第三に、Gitはインデックス内のグラフにファイルを記録します。 変更もありません。 インデックスにはすでにコミットa2が含まれています。



4番目に、コミットA2のハッシュがHEADのコンテンツに割り当てられます。



 f0af7e62679e144bb28c627ee3e8f7bdb235eee9
      
      





HEADにハッシュを書き込むと、リポジトリはHEADが切り離された状態になります。 下のグラフで、HEADはマスターではなく、a2を直接コミットしていることに注意してください。











 ~/alpha $ printf '3' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'a3' [detached HEAD 3645a0e] a3
      
      





ユーザーは、値3をdata / number.txtの内容に書き込み、変更をコミットします。 GitはHEADに移動して、コミットa3の親を取得します。 ブランチへのリンクを見つけて追跡する代わりに、コミットa2のハッシュを見つけて返します。



Gitは新しいa3コミットのハッシュを指すようにHEADを更新します。 リポジトリは、HEADが切り離された状態のままです。 1つのコミットがa3またはその子孫をポイントしないため、ブランチ上にはありません。 失うのは簡単です。



次に、グラフ図からツリーとブロブを省略します。











ブランチを作成する



 ~/alpha $ git branch deputy
      
      





ユーザーは、代理と呼ばれる新しいブランチを作成します。 その結果、HEADが指すハッシュを含む新しいファイルが.git / refs / heads / deputyに表示されます:コミットコミットハッシュa3。



ブランチは単なるリンクであり、リンクは単なるファイルです。 これは、Gitブランチの重量が非常に少ないことを意味します。



代理ブランチを作成すると、新しいコミットa3がブランチに配置されます。 HEADはまだコミットを直接指しているため、まだ切り離されています。











ブランチを確認



 ~/alpha $ git checkout master Switched to branch 'master'
      
      





ユーザーはmasterブランチを確認します。



まず、Gitはマスターが指すコミットa2を取得し、コミットが指すグラフを取得します。



第二に、Gitは作業コピーファイルのグラフにファイルエントリを作成します。 これにより、data / number.txtの内容が2に変更されます。



第三に、Gitはインデックスグラフにファイルエントリを作成します。 これにより、data / number.txtのエントリがblob hash 2に更新されます。



4番目に、GitはHEADをmasterに指示し、その内容をハッシュから



 ref: refs/heads/master
      
      













作業コピーと互換性のないブランチを確認する



 ~/alpha $ printf '789' > data/number.txt ~/alpha $ git checkout deputy Your changes to these files would be overwritten by checkout: data/number.txt Commit your changes or stash them before you switch branches.
      
      





ユーザーが誤ってdata / number.txtの内容を789に設定しています。彼は代理を確認しようとしています。 Gitは確認を推奨しません。



HEADは、a2を指すmasterを指し、data / number.txtに書き込まれます。2. deputyは、a3を指し、data / number.txtに書き込まれます。3. data / number.txtの作業コピーバージョンでは、789が書き込まれます。違いがあり、その違いを何らかの形で排除する必要があります。



Gitは、作業コピーのバージョンを確認済みのコミットのバージョンに置き換えることができます。 しかし、彼は必ずデータの損失を避けます。



Gitは、作業コピーバージョンと検証済みバージョンを組み合わせることができます。 しかし、それは難しいです。



したがって、Gitは確認を拒否します。



 ~/alpha $ printf '2' > data/number.txt ~/alpha $ git checkout deputy Switched to branch 'deputy'
      
      





ユーザーは、誤ってdata / number.txtを編集し、それを2に割り当てていることに気付きます。その後、代理人を確認します。











先祖との関係



 ~/alpha $ git merge master Already up-to-date.
      
      





ユーザーはマスターに代理人を含めます。 2つのブランチをマージすると、2つのコミットがマージされます。 最初のコミットは、代理が指すもの、つまりアクセプターです。 2番目のコミットは、マスターによって指定されたコミット:提供者です。 Gitはマージするために何もしません。 彼はすでに「すでに最新」であると報告しています。



グラフ内の一連の結合は、リポジトリのコンテンツに対する一連の変更として解釈されます。 これは、マージ時に、ギバーのコミットがレシーバーのコミットの祖先によって取得された場合、Gitは何もしないことを意味します。 これらの変更はすでに行われています。



子ども会



 ~/alpha $ git checkout master Switched to branch 'master'
      
      





ユーザーがマスターを確認します。











 ~/alpha $ git merge deputy Fast-forward
      
      





彼はマスターに代理人を含めています。 Gitは、コミットを受け入れるa2がコミットを与えるa3の祖先であることを発見します。 彼は早送りができます。



彼は贈り主と彼が指し示す数を取る。 作業コピーとインデックスの列にファイルエントリを作成します。 次に、彼はマスターを「巻き戻し」、a3を指すようにします。











グラフに対する一連のコミットは、リポジトリのコンテンツに対する一連の変更として解釈されます。 これは、贈与者が受給者の子孫である場合、組み合わせたときにストーリーが変わらないことを意味します。 目的の変更を記述するコミットのシーケンスが既にあります。受信者と提供者の間のコミットのシーケンスです。 しかし、Gitの歴史は変わりませんが、Gitのグラフは変わります。 HEADが指す特定のリンクが更新され、贈与者のコミットを示します。



異なる血統の2つのコミットを結合する



 ~/alpha $ printf '4' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'a4' [master 7b7bd9a] a4
      
      





ユーザーは、number.txtの内容に4を書き込み、変更をマスターにコミットします。



 ~/alpha $ git checkout deputy Switched to branch 'deputy' ~/alpha $ printf 'b' > data/letter.txt ~/alpha $ git add data/letter.txt ~/alpha $ git commit -m 'b3' [deputy 982dffb] b3
      
      





ユーザーが代理を確認します。 data / letter.txtの内容に「b」を書き込み、変更を代理にコミットします。











コミットは共通の親を持つことができます。 これは、コミットの履歴に新しい家系図を作成できることを意味します。



コミットは複数の親を持つことができます。 これは、2つの親とのコミット(結合コミット)によって異なる血統を結合できることを意味します。



 ~/alpha $ git merge master -m 'b4' Merge made by the 'recursive' strategy.
      
      





ユーザーはマスターと代​​理を組み合わせます。



Gitは、アクセプターb3とギバーa4の血統が異なることを発見します。 マージコミットを実行します。 このプロセスには8つのステップがあります。



1. Gitは、与えるコミットのハッシュをalpha / .git / MERGE_HEADファイルに書き込みます。 このファイルの存在は、Gitにマージのプロセス中であることを伝えます。



2.次に、Gitは基本的なコミット、つまりコミットの提供と受信に共通する最新の祖先を見つけます。











コミットには親がいます。 これは、2つの系図が分離したポイントを見つけることができることを意味します。 Gitは、すべての祖先を見つけるためにb3からチェーンを追跡し、同じ目的でa4からチェーンを追跡します。 彼は彼らの共通の祖先の最新のa3を見つけます。 これは基本的なコミットです。



3. Gitはベースのインデックスを作成し、ツリーグラフからコミットを授受します。



4. Gitは、コミットとコミットがベースに対して行った変更を組み合わせて、差分を作成します。 このdiffは、変更(追加、削除、変更、または競合)を示すファイルパスのリストです。



Gitは、ベースコミット、受信、およびギブのインデックスにあるすべてのファイルのリストを取得します。 それぞれについて、インデックスのエントリを比較し、ファイルの変更方法を決定します。 彼は対応するエントリをdiffに書き込みます。 この場合、2つのエントリがdiffに分類されます。



最初のエントリは、data / letter.txt用です。 このファイルの内容は、ベースの「a」、レシーバーの「b」、ギバーの「a」です。 ベースと受信者のコンテンツは異なります。 しかし、基本と提供者は同じです。 Gitは、コンテンツが受信者ではなく受信者によって変更されていることを確認します。 data / letter.txtの差分エントリは変更であり、競合ではありません。



diffの2番目のエントリは、data / number.txt用です。 この場合、コンテンツはベースと受信者で同じであり、提供者では内容が異なります。 data / number.txtのdiffエントリも変更です。



アソシエーションのベースコミットを見つけることができます。 これは、ファイルが受信または提供においてのみベースと異なる場合、Gitはファイルを自動的にマージできることを意味します。 これにより、ユーザーが行う必要のある作業が削減されます。



5. diffのエントリによって記述された変更が作業コピーに適用されます。 data / letter.txtのコンテンツはbに設定され、data / number.txtのコンテンツは4に設定されます。



6. diffのエントリによって記述された変更がインデックスに適用されます。 data / letter.txtに関連するレコードはblob bを指し、data / number.txtのレコードはblob 4を指します。



7.更新されたインデックスが確認されます。



 tree 20294508aea3fb6f05fcc49adaecc2e6d60f7e7d parent 982dffb20f8d6a25a8554cc8d765fb9f3ff1333b parent 7b7bd9a5253f47360d5787095afc5ba56591bfe7 author Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500 committer Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500 b4
      
      





コミットには2つの親があることに注意してください。



8. Gitは、現在のブランチの代理を新しいコミットに向けます。











同じファイルを変更する異なる血統からの2つのコミットを結合する



 ~/alpha $ git checkout master Switched to branch 'master' ~/alpha $ git merge deputy Fast-forward
      
      





ユーザーがマスターを確認します。これは、代理とマスターを組み合わせたものです。これにより、マスターが巻き戻されてb4がコミットされます。マスターと代​​理が同じコミットを指すようになりました。











 ~/alpha $ git checkout deputy Switched to branch 'deputy' ~/alpha $ printf '5' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'b5' [deputy bd797c2] b5
      
      





ユーザーが代理を確認します。data / number.txtの内容を5に設定し、代理への変更を確認します。



 ~/alpha $ git checkout master Switched to branch 'master' ~/alpha $ printf '6' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'b6' [master 4c3ce18] b6
      
      





ユーザーがマスターを確認します。data / number.txtの内容を6に設定し、マスターへの変更を確認します。











 ~/alpha $ git merge deputy CONFLICT in data/number.txt Automatic merge failed; fix conflicts and commit the result.
      
      





ユーザーは代理とマスターを組み合わせます。競合が明らかになり、関連付けが中断されます。競合を伴う結合のプロセスは、競合を伴わない結合のプロセスと同じ6つの最初のステップを取ります:.git / MERGE_HEADの定義、ベースコミットの検索、ベースのインデックスの作成、コミットの受信と付与、差分の作成、作業コピーの更新、インデックスの更新競合のため、コミットのある7番目のステップと更新リンクのある8番目のステップは実行されません。もう一度調べて、何が起こるか見てみましょう。



1. Gitは、与えるコミットのハッシュを.git / MERGE_HEADファイルに書き込みます。











2. Gitはベースコミットb4を見つけます。



3. Gitは、ベースのインデックスを生成し、コミットを受け入れ、コミットします。



4. Gitは、コミットの受信と提供によって行われたベースコミットの変更を結合するdiffを生成します。この差分は、変更、追加、削除、変更、または競合を示すファイルパスのリストです。



この場合、diffにはdata / number.txtの1つのエントリのみが含まれます。 data / number.txtの内容は、付与、受信、および基本コミットで異なるため、競合としてマークされます。



5.差分エントリによって定義された変更は、作業コピーに適用されます。競合領域では、Gitは両方のバージョンを作業コピーファイルに出力します。 ata / number.txtファイルの内容は次のとおりです。



 <<<<<<< HEAD 6 ======= 5 >>>>>>> deputy
      
      





6.差分エントリによって定義された変更がインデックスに適用されます。インデックスのエントリには、ファイルパスとステージの組み合わせとして一意の識別子があります。競合のないファイルレコードでは、ステージは0です。結合する前、インデックスはゼロがステージ番号であるように見えました。



 0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748 0 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
      
      





diffをインデックスに書き込むと、次のようになります。



 0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748 1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767 2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb 3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61
      
      





ステップ0のdata / letter.txtエントリは、マージ前と同じです。ステップ0のdata / number.txtのエントリが消えました。代わりに、3つの新しいものが登場しました。ステップ1のレコードには、data / number.txtの基礎となるコンテンツからのハッシュがあります。ステップ3のレコードには、data / number.txtのコンテンツからのハッシュがあります。3つのエントリが存在することにより、Gitにdata / number.txtの競合があったことがわかります。



関連付けが一時停止します。



 ~/alpha $ printf '11' > data/number.txt ~/alpha $ git add data/number.txt
      
      





ユーザーは、競合する2つのバージョンのコンテンツを統合し、data / number.txtのコンテンツを11に設定します。ユーザーはファイルをインデックスに追加します。Gitは11を含むblobを追加します。競合ファイルを追加すると、Gitに競合が解決されたことが通知されます。Gitは、ステップ1、2、および3のデータ/ number.txtの出現をインデックスから削除します。彼は、新しいblobからのハッシュを使用して、ステップ0でdata / number.txtのエントリを追加します。現在、インデックスにはエントリが含まれています。



 0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748 0 data/number.txt 9d607966b721abde8931ddd052181fae905db503 ~/alpha $ git commit -m 'b11' [master 251a513] b11
      
      





7.ユーザーがコミットします。Gitはリポジトリ内の.git / MERGE_HEADを確認し、マージが進行中であることを彼に伝えます。インデックスをチェックし、競合を検出しません。新しいコミットb11を作成して、許可された結合の内容を記録します。.git / MERGE_HEADファイルを削除します。これで合併が完了します。



8. Gitは、現在のブランチmasterを新しいコミットに向けます。











ファイル削除



Gitグラフのチャートには、コミットの履歴、最後のコミットのツリーとブロブ、作業コピー、インデックスが含まれます。











 ~/alpha $ git rm data/letter.txt rm 'data/letter.txt'
      
      





ユーザーはGitにdata / letter.txtを削除するよう指示します。ファイルは作業コピーから削除されます。レコードがインデックスから削除されます。











 ~/alpha $ git commit -m '11' [master d14c7d2] 11
      
      





ユーザーがコミットします。通常、コミットするとき、Gitはインデックスの内容を表すグラフを作成します。data / letter.txtは、インデックスに含まれていないため、グラフに含まれていません。











リポジトリをコピー



 ~/alpha $ cd .. ~ $ cp -R alpha bravo
      
      





ユーザーはalpha / repositoryの内容をbravo /ディレクトリにコピーします。これにより、次の構造が得られます。



 ~ ├── alpha | └── data | └── number.txt └── bravo └── data └── number.txt
      
      





現在、bravoディレクトリに新しいGitグラフがあります。











リポジトリを別のリポジトリにリンクする



  ~ $ cd alpha ~/alpha $ git remote add bravo ../bravo
      
      





ユーザーはアルファリポジトリに戻ります。bravoをalphaのリモートリポジトリとして指定します。これにより、alpha / .git / configファイルに数行が追加されます。



 [remote "bravo"] url = ../bravo/
      
      





行は、.. / bravoディレクトリにリモートbravoリポジトリがあることを示しています。



リモートリポジトリからブランチを取得



 ~/alpha $ cd ../bravo ~/bravo $ printf '12' > data/number.txt ~/bravo $ git add data/number.txt ~/bravo $ git commit -m '12' [master 94cd04d] 12
      
      





ユーザーはbravoリポジトリに移動します。data / number.txtの内容を12に設定し、bravoのマスターに変更をコミットします。











 ~/bravo $ cd ../alpha ~/alpha $ git fetch bravo master Unpacking objects: 100% From ../bravo * branch master -> FETCH_HEAD
      
      





ユーザーはアルファリポジトリに移動します。マスターをブラボーからアルファにコピーします。このプロセスには4つのステップがあります。



1. Gitは、マスターがbravoで指すコミットのハッシュを受け取りますこれは、コミット12のハッシュです



。2. Gitは、コミット12が依存するすべてのオブジェクトのリストを作成しますコミットオブジェクト自体、そのグラフ内のオブジェクト、コミット12の祖先のコミット、およびグラフのオブジェクト。アルファデータベースに既に存在するすべてのオブジェクトをこのリストから削除します。彼は残りをalpha / .git / objects /にコピーします。



3. alpha / .git / refs / remotes / bravo / master内の特定のリンクファイルコンテンツには、コミット12のハッシュが割り当てられます



。4. alpha / .git / FETCH_HEAD コンテンツは次のようになります。



 94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo
      
      





これは、最新のフェッチコマンドがbravoを使用してマスターからコミット12を取得したことを意味します。











オブジェクトをコピーできます。これは、異なるリポジトリに共通の履歴があることを意味します。



リポジトリには、alpha / .git / refs / remotes / bravo / masterなどのリモートブランチへのリンクを保存できます。これは、リポジトリがリモートリポジトリのブランチ状態をローカルに記録できることを意味します。コピーされると正しいですが、リモートブランチが変更されると廃止されます。



Union FETCH_HEAD



 ~/alpha $ git merge FETCH_HEAD Updating d14c7d2..94cd04d Fast-forward
      
      





ユーザーはFETCH_HEADに参加します。FETCH_HEADは別のリンクです。彼女は12の寄付をコミットすることを指します。HEADは11の受け入れをコミットすることを指します。Gitは巻き戻しを行い、マスターにコミット12を指示します。











リモートリポジトリのブランチを取得



 ~/alpha $ git pull bravo master Already up-to-date.
      
      





ユーザーは、ブラボーからアルファにマスターを転送します。プルは「コピーおよびマージFETCH_HEAD」の略です。Gitは両方のコマンドを実行し、マスターが「既に最新」であることを報告します。



リポジトリのクローン



 ~/alpha $ cd .. ~ $ git clone alpha charlie Cloning into 'charlie'
      
      





ユーザーは最上位ディレクトリに移動します。彼はアルファをチャーリーにクローンします。クローニングは、ユーザーがbravoリポジトリを作成するために実行したcpコマンドと同様の結果を生成します。Gitは新しいcharlieディレクトリを作成します。これは、charlieをリポジトリとして初期化し、alphaをremoteという名前でremoteという名前で追加し、originを取得してFETCH_HEADを連結します。



リモートリポジトリの検証済みブランチにブランチを配置(プッシュ)する



  ~ $ cd alpha ~/alpha $ printf '13' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m '13' [master 3238468] 13
      
      





ユーザーはアルファリポジトリに戻ります。data / number.txtの内容を13に設定し、マスターへの変更をalphaにコミットします。



 ~/alpha $ git remote add charlie ../charlie
      
      





チャーリーをリモートアルファリポジトリとして指定します。



 ~/alpha $ git push charlie master Writing objects: 100% remote error: refusing to update checked out branch: refs/heads/master because it will make the index and work tree inconsistent
      
      





それはマスターをチャーリーに置きます。コミット13に必要なすべてのオブジェクトがチャーリーにコピーされます。



この時点で、配置プロセスは停止します。 Gitは、いつものように、何がうまくいかなかったかをユーザーに伝えます。彼はブランチへの投稿を拒否しますが、これはリモートで確認されます。それは理にかなっています。配置により、リモートインデックスとHEADが更新されます。他の誰かがリモートリポジトリの作業コピーを編集している場合、これは混乱を招きます。



この時点で、ユーザーは新しいブランチを作成し、コミット13をマージして、このブランチをチャーリーに配置できます。しかし、ユーザーは何かを投稿するリポジトリが必要です。ホストするホスト、およびそこから受信(プル)できる中央リポジトリが必要ですが、誰も直接コミットしません。リモートGitHubのようなものが必要です。きれいな(裸の)リポジトリが必要です。



きれいな(むき出しの)リポジトリーを複製する



 ~/alpha $ cd .. ~ $ git clone alpha delta --bare Cloning into bare repository 'delta'
      
      





ユーザーは最上位ディレクトリに移動します。クリーンなリポジトリとしてデルタクローンを作成します。これは、2つの機能を持つ通常のクローンです。構成ファイルには、リポジトリがクリーンであることが記載されています。通常、.gitディレクトリに保存されているファイルは、リポジトリのルートに保存されます。



 delta ├── HEAD ├── config ├── objects └── refs
      
      













きれいなリポジトリにブランチを配置する



  ~ $ cd alpha ~/alpha $ git remote add delta ../delta
      
      





ユーザーはアルファリポジトリに戻ります。デルタをアルファのリモートリポジトリとして指定します。



 ~/alpha $ printf '14' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m '14' [master cb51da8] 14
      
      





コンテンツを14に設定し、masterの変更をalphaにコミットします。











 ~/alpha $ git push delta master Writing objects: 100% To ../delta 3238468..cb51da8 master -> master
      
      





彼はマスターをデルタに置きます。宿泊は3段階で行われます。



1. masterブランチでコミット14に必要なすべてのオブジェクトがalpha / .git / objects /からdelta / objects /にコピーされます。



2.デルタ/参考文献/ヘッド/マスターは14コミットするように更新される



3アルファ/ .git /参考文献/リモコン/デルタ/マスタが実際の記録状態ではアルファ14をコミット送信デルタです。









まとめ



Gitはグラフに基づいています。ほとんどすべてのGitコマンドがこのグラフを操作します。Gitを理解するには、プロシージャやコマンドではなく、グラフのプロパティに注目してください。



Gitの詳細については、.gitディレクトリをご覧ください。これは怖くない。中を見てください。ファイルの内容を変更し、何が起こるかを確認します。コミットを手動で作成します。リポジトリをどれだけ破壊できるかを確認してください。それを修正します。



All Articles