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


283

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

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

(転写はここにあります。)

あなたはに飛び込むときしかし、Gitの本、あなたが言われて最初にすることはGitリポジトリ内のファイルのいずれかすることができるということである追跡人跡未踏。さらに、Gitのエクスペリエンス全体がファイルのバージョン管理に向けられているように思えます。使用時git diffまたはgit status出力時は、ファイルごとに表示されます。使用git addする場合は、ファイルごとに選択することもできます。ファイルごとに履歴を確認することもでき、非常に高速です。

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


20
reddit.com/r/git/comments/5xmrkv/what_is_a_snapshot_in_git-「現時点では、Gitがユーザーにファイルを表示する方法と内部でファイルを処理する方法に違いがあることを理解することがより重要だと思います。ユーザーに提示されるように、スナップショットには差分だけでなく完全なファイルが含まれています。しかし内部的には、Gitは差分を使用して、リビジョンを効率的に保存するパックファイルを生成します。」(これは、例えばSubversionとは対照的です。)
user2864740

5
Gitはファイルを追跡せず、チェンジセットを追跡します。ほとんどのバージョン管理システムはファイルを追跡します。これがどのように/なぜ重要であるかの例として、空のディレクトリをgitにチェックインしてみてください(spolier:できません。これは「空の」チェンジセットだからです)。
Elliott Frisch

12
@ElliottFrischそれは正しく聞こえません。あなたの説明は、例えばdarcsがすることにより近いです。Gitは変更セットではなくスナップショットを保存します。
メルポメン

4
彼はGitがファイルを直接追跡しないことを意味すると思います。ファイルには、その名前と内容が含まれています。Gitはコンテンツをblobとして追跡します。BLOBのみが与えられている場合、対応するファイル名を特定することはできません。これは、異なるパスで異なる名前を持つ複数のファイルのコンテンツである可能性があります。パス名とblob間のバインディングは、ツリーオブジェクトで記述されます。
ElpieKay

3
関連:ランダルシュワルツによるLinusの講演フォローアップ(これもGoogle Techの講演です)-「... Gitは本当に何なのか... LinusはGitがそうではないものを言った」
Peter Mortensen

回答:


316

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

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

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


4
これがCVSとSubversionで$Id$ファイルのようにタグを使用できる理由であると追加できます。同じことがgitでは機能しません。デザインが異なるためです。
gerrit

58
そして、コンテンツは期待どおりにファイルにバインドされていません。1つのファイルのコードの80%を別のファイルに移動してみてください。既存のファイル内でコードを移動しただけでも、Gitはファイルの移動+ 20%の変更を自動的に検出します。
allo

13
@alloその副作用として、gitは他の人ができない1つのことを実行できます。2つのファイルがマージされ、「git blame -C」を使用すると、gitは両方の履歴を調べることができます。ファイルベースの追跡では、どのオリジナルファイルが実際のオリジナルであるかを選択する必要があり、他の行はすべて新品のように見えます。
イズカタ

1
@ allo、Izkata- コミットエンティティとそのユーザーが正確に指定または合成することを要求するのではなく、クエリ時にレポジトリコンテンツ(コミット履歴と参照されたツリーとBLOB間の差異)を分析することで、これらすべてを処理するクエリエンティティです。コミット時のこの情報-ツールがデプロイされる前に、この機能と対応するメタデータスキーマを設計および実装するためのリポジトリツール開発者も。トーバルズ氏は、そのような分析は時間の経過とともに改善するだけであり、初日からすべての Gitリポジトリのすべての履歴が恩恵を受けると主張しました。
ジェレミー

1
@alloはい、そしてgitがファイルレベルで機能しないという事実を理解するために、ファイル内のすべての変更を一度にコミットする必要さえありません。ファイルの他の変更をコミット外に残しながら、任意の範囲の行をコミットできます。もちろん、そのためのUIはそれほど単純ではないので、ほとんどの人はそれをしませんが、それが使用されることはほとんどありません。
Alvin Thompson

103

私はブライアンmに同意します。カールソンの答え:Linusは確かに、少なくとも部分的に、ファイル指向のバージョン管理システムとコミット指向のバージョン管理システムを区別しています。しかし、それだけではない。

、私の著書ストールされ、完成しない飽きないかもしれません、私が思い付くしようとした分類バージョン管理システムのため。私の分類では、ここで私たちが関心を持っている用語は、バージョン管理システムの原子性です。現在22ページを参照してください。VCSにファイルレベルの原子性がある場合、実際には各ファイルの履歴があります。VCSは、ファイルの名前と各ポイントで何が発生したかを記憶している必要があります。

Gitはそれを行いません。Gitにはコミットの履歴しかありません。コミットはその原子性の単位であり、履歴リポジトリ内のコミットのセットです。コミットが記憶するのは、データ(ファイル名とそれらの各ファイルに関連するコンテンツが含まれるツリー全体)に加えて、いくつかのメタデータです。たとえば、誰がコミットしたか、いつ、なぜか、内部のGitハッシュIDなどです。コミットのコミットの。(それは、この親であり、すべてのコミットとその親を読むことによって形成される監督acyclingグラフ、あるリポジトリ内の歴史。)

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

Gitにファイルの履歴を表示するように依頼すると、次のようになります。

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

Gitが実際に行っていることは、Gitが持っている唯一の履歴であるコミット履歴をたどることですが、以下の場合を除き、これらのコミットのいずれも表示しません

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

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


追加するもう1つのことは、Gitが浅いクローンのようなことを行えるようにすることです。ヘッドコミットとそれが参照するすべてのblobを取得する必要があるだけです。変更セットを適用してファイルを再作成する必要はありません。
Wes Toleman

@WesToleman:それは間違いなくそれを簡単にします。Mercurialは差分を時々リセットして格納し、Mercurialの人々は浅いクローンをそこに追加するつもりです(「リセット」の考えにより可能です)が、実際にはまだ実行していません(技術的な課題が多いため)。
torek

@torek私はファイル履歴要求に答えるのGitについてのご説明について疑問を持っていますが、私はそれはそれ自身の適切な質問に値すると思う: stackoverflow.com/questions/55616349/...
シモン・ラミレスアマヤ

@torekあなたの本へのリンクをありがとう、私はそれのような他のものを見たことがありません。
gnarledRoot

17

紛らわしいビットはここにあります:

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

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

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

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

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

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

ツリーをファイルシステムにシリアル化し、すべての.gitフォルダーを削除し、gitにツリーをデータベースに戻すように指示した場合、データベースには何も追加されず、要素は既に存在します。

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

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

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

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

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

gitリポジトリはそのようなものです。

つまり、特定のgitチェンジセットにはコミットメッセージ(ハッシュコードとして)が含まれ、ワークツリーが含まれ、親の変更が含まれます。

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

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

ファイルがどうなるかを調べるには、チェンジセット内のそのファイルから始めます。そのチェンジセットには歴史があります。多くの場合、その履歴では、同じ名前のファイルが存在し、内容が同じ場合もあります。内容が同じ場合、ファイルは変更されていません。それが異なる場合、変化があり、正確に何が起こるかを理解するために作業を行う必要があります。

時々、ファイルはなくなっています。しかし、「ディレクトリ」ツリーには同じコンテンツ(同じハッシュコード)を持つ別のファイルが含まれている可能性があるため、その方法で追跡できます(注:これは、commit-to-commitとは別にファイルを移動する必要がある理由です) -編集)。または、同じファイル名で、ファイルをチェックした後、十分に類似しています。

したがって、gitは「ファイル履歴」を一緒にパッチワークできます。

ただし、このファイル履歴は、「変更セット全体」を効率的に解析した結果であり、ファイルのバージョン間のリンクからのものではありません。


12

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

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

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

Gitはファイル履歴と名前の変更を追跡せず、その効率はそれらに依存しないため、Gitが重要でない履歴に関心のある履歴/差分/ブレードを生成するまで、さまざまなオプションを数回試さなければならない場合があります。

これは、履歴を再構築するのではなく記録する Subversionのようなシステムとは異なります。記録されていない場合は、それを聞くことはできません。

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


7

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

これを見る方法は次のとおりです。

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

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


5
私はあなたが言っていることを理解していると思いますが、「Gitでは、これを行う直接のコマンドはありません」とは、リンクしている質問への回答によって直接矛盾します。バージョン管理はリポジトリ全体のレベルで行われることは事実ですが、通常、Gitで何かを実行する方法はたくさんあります。そのため、ファイルの履歴を表示するための複数のコマンドがあることは、それほど多くの証拠にはなりません。
Joe Lee-Moyet

私はあなたがリンクした質問の最初のいくつかの答えをすくい取り、それらすべてが使用するgit logか、その上に構築されたいくつかのプログラム(または同じことをするいくつかのエイリアス)を使用しました。しかし、ジョーが言うように、さまざまな方法があったとしても、それはブランチの履歴を表示する場合にも当てはまります。(これもgit log -p <file>組み込まれていて、それを正確に行います)
Voo

SVNがファイルごとに変更を内部的に保存しますか?まだしばらくは使用していませんが、プロジェクトファイルの構造を反映するのではなく、バージョンIDのような名前のファイルがあることを漠然と覚えています。
Artur Biesiadowski

3

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

常にそうであるとは限らず、Git 1.4(2006年5月)のみが、コミット443f833で「コンテンツの追跡」ポリシーを実施しました

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

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

与える-u(または--untracked)は、この整理を無効にして、ユーザーがすべての追跡されていないファイルを取得できるようにします。

これは、数年後の2011年1月のcommit 8fe533、Git v1.7.4にも反映されました。

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

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

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

コンテンツの追跡は、Git Blameが非常に早い段階(Git 1.4.4、2006年10月、コミットcee7f24)でより高いパフォーマンスを発揮できるようにするものです。

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

これ(コンテンツの追跡)は、Git APIにgit addを追加したものでもあり、Git 1.5.0(2006年12月、コミット366bfcb

'git add'をインデックスへのファーストクラスのユーザーフレンドリーなインターフェースにする

これにより、インデックスについてまったく話さなくても、適切なメンタルモデルを使用してインデックスのパワーを前もって発揮できます。
たとえば、すべての技術的な議論がgit-addのmanページからどのように排除されたかを参照してください。

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

これがgit add --interactive、同じGit 1.5.0(commit 5cde71d)で可能になりました。

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

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

ファイル自体ではなくファイルの内容を表示することで、コミット1de70db(Git v2.18.0-rc0、2018年4月)で説明されているようなマージシナリオが可能になります。

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

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

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

コミット37b65ce、Git v2.21.0-rc0、2018年12月、衝突の競合解決を最近改善しました。
また、コミットbbafc9cは、rename / rename(2to1)競合の処理を改善することにより、ファイルコンテンツを考慮することの重要性をさらに示しています。

  • collide_path~HEADおよびcollide_path~MERGEでファイルを保存する代わりに、ファイルは双方向でマージされ、で記録されcollide_pathます。
  • 名前が変更された側に存在する名前が変更されたファイルのバージョンをインデックスに記録する代わりに(したがって、名前が変更されていない履歴側のファイルに加えられた変更は無視されます)、名前が変更されたファイルに対して3者間コンテンツマージを実行しますパス、ステージ2またはステージ3で保存します。
  • 名前を変更するたびにコンテンツをマージすると競合が発生する可能性があるため、名前を変更した2つのファイルをマージする必要があるため、ネストされた競合マーカーが発生する可能性があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.