web-dev-qa-db-ja.com

`git mv`ではなくgit copy file

Gitはファイルの内容を比較することで機能することを理解しています。コピーしたいファイルがいくつかあります。 gitが混乱するのを完全に防ぐために、ファイルを別のディレクトリ(mvではなくcp)にコピーし、ファイルをステージングするために使用できるgitコマンドはありますか?

17
Alexander Mills

短い答えは「いいえ」です。しかし、知っておくべきことがあります。それはいくつかの背景が必要です。 (そして 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に移動するとしますが、何らかの奇妙な理由によりますインデックスの内容を変更せずに保持したい。実行すると:

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に対する呼び出しよりも少ないです。)


この例では、それはちょっとばかげて無意味です。しかし、git add -pを使用して中間コミット用のパッチを慎重に準備し、そのパッチと一緒にファイルの名前を変更することを決定した場合、綿密にパッチを適用してまとめることなく、それを行うことができるのは間違いなく便利です。中間バージョン。

4不可能ではありません。git ls-index --stageは、現在必要な情報をインデックスから取得します。git update-indexを使用すると、インデックスに任意の変更を加えることができます。これら2つと、より良い言語での複雑なシェルスクリプトまたはプログラミングを組み合わせて、git mv --aftergit cpを実装するものを構築できます。

23
torek