Gitはファイルの内容を比較することで機能することを理解しています。コピーしたいファイルがいくつかあります。 gitが混乱するのを完全に防ぐために、ファイルを別のディレクトリ(mvではなくcp)にコピーし、ファイルをステージングするために使用できるgitコマンドはありますか?
短い答えは「いいえ」です。しかし、知っておくべきことがあります。それはいくつかの背景が必要です。 (そして JDBがコメントで示唆しているように として、便宜上git mv
が存在する理由を述べます。)
少し長く:Gitがファイルを比較するのは正しいですが、whenGitがこれらのファイルの比較を行うのは間違っているかもしれません。
Gitの内部ストレージモデルでは、各コミットは、そのコミット内のファイルのallの独立したスナップショットであると提案されています。新しいコミットに入る各ファイルのバージョン、つまりそのパスのスナップショット内のデータは、git commit
を実行したときにそのパスの下のインデックスにあるものです。1
最初のレベルの実際の実装では、各スナップショットファイルは、Gitデータベースのblob objectとして圧縮形式でキャプチャされます。 blobオブジェクトは、1つ特別な場合を除いて、そのファイルの以前のバージョンと後続のバージョンから完全に独立しています。noデータが変更された新しいコミットを作成すると、古いblobを再利用します。したがって、連続して2つのコミットを作成し、それぞれが100個のファイルを保持し、1つのファイルのみが変更された場合、2番目のコミットは以前の99個のblobを再利用し、1つの実際のファイルを新しいblobにスナップショットするだけで済みます。2
したがって、Gitがファイルを比較するという事実は、コミットの作成にはまったく関与しません。コミットは、前のコミットのハッシュIDを格納すること(および完全に一致するblobを再利用することを除く)以外は、前のコミットに依存しませんが、これは、git commit
を実行する時点での複雑な計算ではなく、完全に一致するblobの副作用です。 )。
現在、これらすべての独立したblobオブジェクトは、最終的に法外な量のスペースを占有します。この時点で、Gitはオブジェクトを.pack
ファイルに「パック」できます。各オブジェクトを他のオブジェクトの選択されたセットと比較します。これらは、履歴の前または後で、同じファイル名または異なるファイル名を持つ可能性があります。理論的には、GitはBLOBオブジェクトに対してコミットオブジェクトを圧縮することも、その逆も可能です(実際にはそうではありませんが)—そして、より少ないディスク領域を使用して多くのblobを表現する方法を見つけようとします。しかし、結果として、少なくとも論理的には、一連の独立したオブジェクトがハッシュIDを使用して元の形式で完全にそのまま取得されます。したがって、この時点で使用されているディスク領域の量は少なくなりますが(願わくば!)、すべてのオブジェクトは以前とまったく同じです。
doesGitがファイルを比較するのはいつですか?答えは次のとおりです。要求されたときのみ。「問い合わせ時間」は、直接git diff
を実行するときです。
git diff commit1 commit2
または間接的に:
git show commit # roughly, `git diff commit^@ commmit`
git log -p # runs `git show commit`, more or less, on each commit
これについては多くの微妙な点があります。特に、git show
は、マージコミット時に実行されると、Gitがcombined diffsと呼ぶものを生成しますが、git log -p
は通常、マージコミット—これらは、他のいくつかの重要なケースとともに、Gitがgit diff
を実行するときです。
Gitがgit diff
を実行するときに、コピーを検索するかどうかを(場合によっては)要求することができます。 -C
フラグは、--find-copies=<number>
のスペルもあり、コピーを見つけるようGitに要求します。 --find-copies-harder
フラグ(Gitのドキュメントでは「計算コストが高い」と呼ばれています)は、単純な-C
フラグよりもコピーを探すのが難しく見えます。 -B
(不適切なペアリングを解除)オプションは-C
に影響します。 -M
別名--find-renames=<number>
オプションも-C
に影響します。 git merge
コマンドは、名前変更の検出レベルを調整するように指示できますが、少なくとも現在のところ、コピーを見つけるように指示したり、不適切なペアリングを解除したりすることはできません。
(1つのコマンドgit blame
は、多少異なるコピー検索を実行し、上記は完全には適用されません。)
1git commit --include <paths>
、git commit --only <paths>
、git commit <paths>
、git commit -a
を実行する場合は、git commit
を実行する前に、インデックスを変更すると考えてください。 --only
の特別なケースでは、Gitは一時的なインデックスを使用しますが、これは少し複雑ですが、anインデックスからコミットします—代わりに特別な一時的なインデックスを使用します通常のものの。一時インデックスを作成するために、GitはHEAD
コミットからすべてのファイルをコピーし、リストした--only
ファイルでそれらをオーバーレイします。その他の場合、Gitは作業ツリーファイルを通常のインデックスにコピーし、通常どおりインデックスからコミットします。
2実際、ブロブをリポジトリに保存する実際のスナップショットは、git add
の間に発生します。これにより、git commit
を起動する前にgit add
を実行するのに通常かかる余分な時間に気付かないため、git commit
が密かに高速になります。
git mv
が存在する理由git mv old new
が行うことは、veryです。
mv old new
git add new
git add old
最初のステップは十分明白です。ファイルの作業ツリーバージョンの名前を変更する必要があります。 2番目のステップも同様です。ファイルのインデックスバージョンを配置する必要があります。しかし、3番目はweird:削除したばかりのファイルを「追加」する必要があるのはなぜですか?まあ、git add
は常にファイルを追加するわけではありません。代わりに、この場合、インデックス内のファイルwasが検出され、もう存在しません。
次のように3番目のステップを綴ることもできます。
git rm --cached old
実際に行っているのは、古い名前をインデックスから削除することだけです。
しかし、ここに問題があります。そのため、「veryおおまかに」と言いました。インデックスには、次にgit commit
を実行したときにコミットされる各ファイルのコピーがあります。そのコピーは作業ツリーのコピーと一致しない場合があります。実際、HEAD
のコピーが存在する場合、HEAD
のコピーとまったく一致しない場合もあります。
たとえば、次のようになります。
echo I am a foo > foo
git add foo
ファイルfoo
は作業ツリーとインデックスに存在します。作業ツリーの内容とインデックスの内容が一致しています。しかし、今度はワークツリーのバージョンを変更しましょう:
echo I am a bar > foo
これで、インデックスと作業ツリーが異なります。基になるファイルをfoo
からbar
に移動するとしますが、何らかの奇妙な理由によります3—インデックスの内容を変更せずに保持したい。実行すると:
mv foo bar
git add bar
新しいインデックスファイル内にI am a bar
を取得します。次に、古いバージョンのfoo
をインデックスから削除すると、I am a foo
バージョンが完全に失われます。
したがって、git mv foo bar
は実際には移動と追加、2回、または移動と追加、および削除を行いません。代わりに、作業ツリーファイルの名前を変更しますandインデックス内コピーの名前を変更します。元のファイルのインデックスコピーが作業ツリーファイルと異なる場合でも、名前が変更されたインデックスコピーは、名前が変更された作業ツリーコピーとは異なります。
git mv
のようなフロントエンドコマンドなしでこれを行うのは非常に困難です。4 もちろん、git add
のすべてを計画しているのであれば、そもそものすべてを必要とするわけではありません。また、git cp
が存在する場合は、インデックスコピーを作成するときに、ワークツリーバージョンではなく、alsoインデックスバージョンをコピーする必要があることに注意してください。したがって、git cp
は実際に存在する必要があります。 Mercurialのgit mv --after
であるhg mv --after
オプションも必要です。shouldの両方が存在しますが、現在は存在しません。 (ただし、私の意見では、これらのいずれかに対する呼び出しは、単純なgit mv
に対する呼び出しよりも少ないです。)
3この例では、それはちょっとばかげて無意味です。しかし、git add -p
を使用して中間コミット用のパッチを慎重に準備し、そのパッチと一緒にファイルの名前を変更することを決定した場合、綿密にパッチを適用してまとめることなく、それを行うことができるのは間違いなく便利です。中間バージョン。
4不可能ではありません。git ls-index --stage
は、現在必要な情報をインデックスから取得します。git update-index
を使用すると、インデックスに任意の変更を加えることができます。これら2つと、より良い言語での複雑なシェルスクリプトまたはプログラミングを組み合わせて、git mv --after
とgit cp
を実装するものを構築できます。