web-dev-qa-db-ja.com

Linus Torvaldsは、Gitが「決して」ファイルを追跡しないと言ったとき、何を意味しますか?

GitがGitで処理できるファイルの数を尋ねられたときにLinus Torvaldsを引用 2007年のGoogleでのTech Talk (43:09):

…Gitはコンテンツを追跡します。単一のファイルを追跡することはありません。 Gitでファイルを追跡することはできません。できることは、ファイルが1つしかないプロジェクトを追跡できることですが、プロジェクトにファイルが1つしかない場合は、それを確認して実行できますが、10,000個のファイルを追跡する場合、Gitはそれらを個別のファイルとして認識しません。 Gitはすべてを完全なコンテンツと考えています。 Gitのすべての履歴は、プロジェクト全体の履歴に基づいています…

(トランスクリプト ここ 。)

しかし、 Git book に飛び込むと、最初に言われるのは、Gitのファイルはtrackedまたはuntracked。さらに、Gitの経験全体がファイルのバージョン管理に向けられているように思えます。 git diffまたはgit statusを使用する場合、出力はファイルごとに表示されます。 git addを使用する場合、ファイルごとに選択することもできます。ファイル単位で履歴を確認することもでき、非常に高速です。

この声明はどのように解釈されるべきですか?ファイル追跡に関して、GitはCVSなどの他のソース管理システムとどのように違いますか?

280

CVSでは、履歴はファイルごとに追跡されていました。ブランチは、それぞれ独自のバージョン番号を持つ、独自のさまざまなリビジョンを持つさまざまなファイルで構成される場合があります。 CVSはRCS( Revision Control System )に基づいており、同様の方法で個々のファイルを追跡しました。

一方、Gitはプロジェクト全体の状態のスナップショットを取ります。ファイルは個別に追跡およびバージョン管理されません。リポジトリのリビジョンは、1つのファイルではなく、プロジェクト全体の状態を指します。

Gitがファイルの追跡を指す場合、それは単にプロジェクトの履歴に含まれることを意味します。 Linusの講演では、Gitコンテキストでのファイルの追跡について言及していませんでしたが、CVSおよびRCSモデルとGitで使用されるスナップショットベースのモデルとを対比しました。

315
bk2204

brian m。carlson's answer に同意します。Linusは、少なくとも部分的に、ファイル指向バージョン管理システムとコミット指向バージョン管理システムを区別しています。しかし、それ以上のことがあると思います。

my book は、停止していて、決して終わらないかもしれないので、バージョン管理システム用に taxonomy を見つけようとしました。私の分類では、ここで興味のある用語は、バージョン管理システムの原子性です。現在22ページの内容を参照してください。VCSにファイルレベルの原子性がある場合、実際には各ファイルの履歴があります。 VCSは、ファイルの名前と、各ポイントで何が発生したかを覚えておく必要があります。

Gitはそれを行いません。 Gitにはコミットの履歴しかありません。コミットは原子性の単位であり、履歴はリポジトリ内のコミットのセットです。コミットが覚えているのは、ファイル名と各ファイルに付随するコンテンツで構成されるツリー全体のデータと、いくつかのメタデータ(たとえば、誰がコミットしたか、いつ、なぜ、内部GitハッシュID)です。コミットのparentコミットの(これは、この親であり、すべてのコミットとその親を読み取ることによって形成される有向サイクリンググラフです。は、リポジトリ内の履歴です。)

VCSはコミット指向でも、ファイルごとにデータを保存できることに注意してください。これは実装の詳細ですが、重要な場合もありますが、Gitもそれを行いません。代わりに、各コミットはtreeを記録し、ツリーオブジェクトエンコーディングファイルnamesmodes(つまり、このファイルは実行可能かどうか)、および実際のファイル内容へのポインタ。コンテンツ自体は、blobオブジェクトに個別に保存されます。コミットオブジェクトと同様に、ブロブはそのコンテンツに固有のハッシュIDを取得しますが、一度だけ表示されるコミットとは異なり、ブロブは多くのコミットに表示されます。そのため、Gitの基になるファイルコンテンツはblobとして直接格納され、次にindirectlyにハッシュIDが(直接または間接的に)記録されるツリーオブジェクトに格納されますコミットオブジェクト。

Gitに以下を使用してファイルの履歴を表示するように依頼すると:

git log [--follow] [starting-point] [--] path/to/file

gitが実際に行っているのは、commit履歴を歩くことです。これは、Gitが持つ唯一の履歴ですが、showingこれらのコミットのいずれか

  • コミットが非マージコミットであり、かつ
  • そのコミットの親にもファイルがありますが、親のコンテンツが異なるか、コミットの親にファイルがまったくありません

(ただし、これらの条件の一部はgit logオプションを追加することで変更できます。また、履歴ウォークからGitに一部のコミットを完全に省略する履歴の単純化と呼ばれる副作用を記述するのは非常に困難です)。ここに表示されるファイル履歴は、ある意味ではリポジトリに正確には存在しません。代わりに、実際の履歴の単なる合成サブセットです。異なるgit logオプションを使用すると、異なる「ファイル履歴」が得られます!

102
torek

紛らわしい部分は次のとおりです。

Gitはこれらを個別のファイルと見なすことはありません。 Gitはすべてを完全なコンテンツと考えています。

Gitは多くの場合、独自のリポジトリのオブジェクトの代わりに160ビットのハッシュを使用します。ファイルのツリーは、基本的にそれぞれのコンテンツ(および一部のメタデータ)に関連付けられた名前とハッシュのリストです。

ただし、160ビットハッシュはコンテンツを一意に識別します(gitデータベースのユニバース内)。そのため、コンテンツとしてハッシュを持つツリーコンテンツを含む状態になります。

ファイルのコンテンツの状態を変更すると、そのハッシュが変更されます。ただし、ハッシュが変更されると、ファイル名のコンテンツに関連付けられたハッシュも変更されます。これにより、「ディレクトリツリー」のハッシュが変更されます。

Gitデータベースがディレクトリツリーを格納する場合、そのディレクトリツリーはのすべてのサブディレクトリとit 内のすべてのファイルのすべてのコンテンツを含意し、含みます。

これは、ブロブまたは他のツリーへの(不変、再利用可能な)ポインターを持つツリー構造で編成されますが、論理的には、ツリー全体のコンテンツ全体の単一のスナップショットです。 gitデータベースの representation はフラットなデータコンテンツではありませんが、論理的にはすべてのデータであり、他には何もありません。

ツリーをファイルシステムにシリアル化し、すべての.gitフォルダーを削除し、データベースにツリーを再び追加するようにgitに指示した場合、データベースに何も追加しないことになります-要素は既に存在します。

Gitのハッシュを、不変データへの参照カウントポインターと考えると役立つ場合があります。

その周りにアプリケーションを構築した場合、ドキュメントはページの集まりであり、ページにはレイヤーがあり、グループにはオブジェクトがあります。

オブジェクトを変更する場合は、そのオブジェクト用に完全に新しいグループを作成する必要があります。グループを変更する場合、新しいレイヤーを作成する必要があります。新しいレイヤーには新しいページが必要で、新しいドキュメントが必要です。

単一のオブジェクトを変更するたびに、新しいドキュメントが生成されます。古いドキュメントは引き続き存在します。新しいドキュメントと古いドキュメントは、ほとんどのコンテンツを共有します。同じページを持っています(1を除く)。その1つのページには同じレイヤーがあります(1を除く)。その層には同じグループがあります(1を除く)。そのグループには同じオブジェクトがあります(1を除く)。

そして、同じ意味で、論理的にはコピーを意味しますが、実装面では、同じ不変オブジェクトへの参照カウントポインターにすぎません。

Gitレポはそのようなものです。

つまり、特定のgit変更セットにはコミットメッセージ(ハッシュコード)が含まれ、作業ツリーが含まれ、親の変更が含まれます。

これらの親の変更には、親の変更が含まれます。

history を含むgitリポジトリの部分は、その変更のチェーンです。その変更の連鎖は、レベル above で「ディレクトリ」ツリー-「ディレクトリ」ツリーから、変更セットと変更のチェーンに一意に到達することはできません。

ファイルに何が起こるかを知るには、チェンジセットでそのファイルから始めます。そのチェンジセットには履歴があります。多くの場合、その履歴には同じ名前のファイルが存在し、同じコンテンツが含まれることもあります。内容が同じ場合、ファイルに変更はありません。異なる場合、変更があり、正確に何を実行するために作業を行う必要があります。

ファイルがなくなっている場合があります。ただし、「ディレクトリ」ツリーには同じ内容の別のファイル(同じハッシュコード)がある可能性があるため、そのように追跡できます(注:コミット先とは別にファイルを移動する必要があるのはこのためです) -編集)。または同じファイル名で、ファイルをチェックした後は十分に似ています。

そのため、gitは「ファイル履歴」をパッチワークできます。

しかし、このファイル履歴は、ファイルのあるバージョンから別のバージョンへのリンクからではなく、「変更セット全体」の効率的な解析から得られます。

「gitはファイルを追跡しない」とは、gitのコミットが、ツリー内のパスを「blob」に接続するファイルツリースナップショットと、commitsの履歴を追跡するコミットグラフで構成されることを意味します。それ以外はすべて、「git log」や「git blame」などのコマンドによってオンザフライで再構築されます。この再構築は、さまざまなオプションを使用して、ファイルベースの変更を探すのがどれだけ難しいかを伝えることができます。デフォルトのヒューリスティックは、BLOBがファイルツリー内の場所を変更せずに変更したとき、またはファイルが以前とは異なるBLOBに関連付けられたときを判断できます。 Gitが使用する圧縮メカニズムは、blob /ファイルの境界をあまり気にしません。コンテンツが既にどこかにある場合、これにより、さまざまなBLOBを関連付けずにリポジトリの成長を小さく保ちます。

これがリポジトリです。 Gitには作業ツリーもあり、この作業ツリーには、追跡されたファイルと追跡されていないファイルがあります。追跡されたファイルのみがインデックスに記録され(ステージング領域?キャッシュ?)、そこで追跡されたもののみがリポジトリに記録されます。

インデックスはファイル指向であり、それを操作するためのファイル指向のコマンドがいくつかあります。しかし、リポジトリで最終的に行われるのは、ファイルツリースナップショットの形式でのコミットと、関連するBLOBデータおよびコミットの祖先だけです。

Gitはファイルの履歴と名前の変更を追跡せず、その効率はそれらに依存しないため、Gitが重要な履歴ではない履歴/差分/非難を生成するまで、異なるオプションで数回試行する必要があります。

reconstruct履歴ではなくrecordであるSubversionのようなシステムでは異なります。記録されていない場合、あなたはそれについて聞くことができません。

実際には、リリースツリーをGitにチェックインし、その効果を複製するスクリプトを作成してリリースツリーを比較する差分インストーラーを実際に一度に作成しました。ツリー全体が移動することもあったため、これにより、すべてが上書き/削除されるよりもはるかに小さな差分インストーラーが生成されました。

12
user11341543

Gitはファイルを直接追跡しませんが、リポジトリのスナップショットを追跡します。これらのスナップショットはファイルで構成されています。

ここにそれを見る方法があります。

他のバージョン管理システム(SVN、Rational ClearCase)では、ファイルを右クリックして変更履歴を取得できます

Gitには、これを行う直接的なコマンドはありません。 この質問 を参照してください。さまざまな答えがあることに驚くでしょう。 Gitは単にファイルを追跡しないであり、SVNやClearCaseが行う方法ではないため、簡単な答えはありません。

ちなみに、「コンテンツ」の追跡は、空のディレクトリを追跡しないことにつながったものです。
だからこそ、フォルダーの最後のファイルをgit rmした場合、 フォルダー自体が削除されます

常にそうとは限らず、Git 1.4(2006年5月)のみが commit 443f8 を使用してその「コンテンツの追跡」ポリシーを実施しました。

git status:空のディレクトリをスキップし、-uを追加してすべての追跡されていないファイルを表示します

デフォルトでは、--others --directoryを使用して、(ユーザーの注意を引くために)興味のないディレクトリをコンテンツなしで表示します(出力を整理します)。
空のディレクトリを表示するのは意味がないため、--no-empty-directoryを渡します。

-u(または--untracked)を指定すると、この整頓が無効になり、ユーザーがすべての未追跡ファイルを取得できるようになります。

それは数年後の2011年1月に commit 8fe5 、Git v1.7.4でエコーされました:

これは、UIの一般的な哲学に沿っています:gitは空のディレクトリではなくコンテンツを追跡します。

それまでの間、Git 1.4.3(2006年9月)で、Gitは commit 2074cb を使用して、追跡されないコンテンツを空でないフォルダーに制限し始めます。

完全に追跡されていないディレクトリの内容をリストするのではなく、そのディレクトリの名前(および末尾の '/')のみをリストする必要があります。

トラッキングコンテンツは、Git 1.4.4、2006年10月、 commit cee7f24 )より早い段階でgit blameを許可したものです。

さらに重要なことに、その内部構造は、複数のパスを取得できるようにすることでcontent移動(カットアンドペースト)をより簡単にサポートするように設計されています同じコミット。

それ(コンテンツの追跡)は、Git 1.5.0でGit APIにgit addを追加したものでもあります(2006年12月、 commit 36​​6bfcb

「git add」をインデックスへの最初のクラスのユーザーフレンドリーなインターフェイスにする

これにより、インデックスについてまったく話さずに、適切なメンタルモデルを使用してインデックスのパワーを事前に引き出すことができます。
たとえば、git-addのマニュアルページからすべての技術的な議論がどのように排除されているかをご覧ください。

コミットするコンテンツはすべて一緒に追加する必要があります。
コンテンツが新しいファイルに由来するか、変更されたファイルに由来するかは関係ありません。
git-addを使用するか、-aを使用してgit-commitを提供することにより、「追加」するだけです(もちろん既知のファイルのみ)。

それが、同じGit 1.5.0で git add --interactive を可能にしたものです( commit 5cde71d

選択した後、空の行で答えて、インデックス内の選択されたパスの作業ツリーファイルのcontentsをステージングします。

また、ディレクトリからすべてのコンテンツを再帰的に削除するには、ディレクトリ名だけでなく-rとして<path>オプションを渡す必要があります(Git 1.5.0、 commit 9f95069 )。

commit 1de70db (Git v2.18.0-rc0、2018年4月)で説明されているようなマージシナリオを可能にするのは、ファイル自体の代わりにファイルコンテンツを表示することです。

名前変更/追加の競合を伴う次のマージを検討してください。

  • サイドA:fooの変更、無関係なbarの追加
  • サイドB:foo->barの名前を変更します(ただし、モードや内容は変更しないでください)

この場合、元のfoo、Aのfoo、およびBのbarの3者間マージは、Aがbarに対して持っていたのと同じモード/コンテンツを持つfooの望ましいパス名になります。
したがって、Aにはファイルの正しいモードと内容があり、正しいパス名がありました(つまり、bar)。

コミット37b65ce 、Git v2.21.0-rc0、2018年12月、衝突の競合解決が最近改善されました。
そして commit bbafc9c さらに、名前変更/名前変更の処理を改善することで、ファイルcontentを考慮することの重要性を示します。 (2対1)競合:

  • collide_path~HEADおよびcollide_path~MERGEにファイルを保存する代わりに、ファイルはcollide_pathに双方向でマージおよび記録されます。
  • インデックスの名前が変更された側に存在する名前が変更されたファイルのバージョンを記録する代わりに(名前が変更されていない履歴側のファイルに加えられた変更を無視する)、名前が変更されたファイルに対して3方向のコンテンツマージを行いますパス、次にステージ2またはステージ3のいずれかに保存します。
  • 名前の変更ごとのコンテンツのマージには競合が生じる可能性があるため、名前を変更した2つのファイルをマージする必要があるため、ネストされた競合マーカーになる可能性があることに注意してください。
3
VonC