1. ホーム
  2. git

[解決済み] git mv` とは対照的に、git コピーファイル

2022-02-17 19:45:19

質問

gitはファイルの中身を差分することで機能すると理解しています。私はコピーしたいファイルがあります。gitが混乱するのを絶対に防ぐために、ファイルを別のディレクトリにコピーして(mvではなく、cp)、同様にファイルをステージするために使用できるgitコマンドはあるのでしょうか?

解決方法は?

簡単に言うと、「ノー」です。 しかし、もっと知るべきことがあり、それにはいくつかの背景が必要です。 (そして JDBがコメントで提案 の理由についても触れておきます。 git mv は便宜上存在します)。

少し長くなりますが、Git がファイルの diff を取るというのは正しいのですが、以下の点については間違っているかもしれません。 いつ Git はこのファイル差分を実行します。

Gitの内部ストレージ・モデルは、各コミットが すべて は、そのコミット内のファイルです。 新しいコミットに含まれる各ファイルのバージョン、つまりそのパスのスナップショット内のデータは、そのパスのインデックスで git commit . 1

実際の実装は、最初のレベルでは、各スナップショット・ファイルを圧縮した形で取り込み ブロブオブジェクト を Git データベースに保存します。 この blob オブジェクトは、そのファイルのすべての前バージョンおよび後バージョンから完全に独立しています。ただし、ひとつだけ特別なケースがあります。 いいえ のデータが変更された場合 古いブロブを再利用する . つまり、2つのコミットを連続して行い、それぞれが100のファイルを保持し、1つのファイルだけが変更された場合、2番目のコミットは99の以前のブロブを再利用し、実際のファイル1つを新しいブロブにスナップショットするだけでいいのです。 2

したがって、Gitがファイルの差分を取るという事実は、コミットを行う際には全く関係ありません。 前のコミットに依存するコミットはありません。前のコミットのハッシュIDを保存すること以外には(そしておそらく完全にマッチするブロブを再利用することですが、これは正確にマッチすることによる副作用であり、むしろ、あなたが git commit ).

さて、このように独立したブロブオブジェクトは、最終的には法外なスペースを占有することになります。 この時点で Gitは、オブジェクトを .pack ファイルを作成します。 各オブジェクトを、選択された他のオブジェクトのセットと比較します。それらは歴史的に前か後か、同じファイル名か異なるファイル名か、理論的にはGitはコミットオブジェクトをblobオブジェクトに対して圧縮することもでき、その逆もできます(実際にはそうしませんが)-そしてより少ないディスクスペースで多くのblobを表現する方法を見つけようと努力します。 しかしその結果は、少なくとも論理的にはまだ一連の独立したオブジェクトであり、ハッシュIDを使用して元の形式で完全にそのまま取得されます。 つまり、この時点で使用するディスク容量が減少しても(そうであってほしい!)、すべてのオブジェクトは以前とまったく同じものなのです。

では、いつ が行います。 Git はファイルを比較するのでしょうか? 答えはこうです。 あなたが頼んだときだけです。 を実行したときです。 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 が呼ぶところの 複合差分 をマージコミットに対して実行した場合、一方 git log -p 通常、マージコミットでは差分をスキップするだけですが、これらのケースやその他の重要なケースで Git は git diff .

それは Git が実行するとき git diff を使えば、コピーを見つけるか見つけないかを尋ねることができます (場合によっては)。 そのため -C というフラグもあります。 --find-copies=<number> は、Git にコピーを見つけるよう依頼します。 その --find-copies-harder フラグ (Git のドキュメントでは "computationally expensive" と呼んでいます) は、プレーンな -C フラグを使用します。 このフラグは -B (不適切なペアを解除する)オプションは -C . そのため -M 別名 --find-renames=<number> オプションは -C . また git merge コマンドはリネーム検出のレベルを調整することができますが、少なくとも現在のところ、コピーを見つけたり、不適切なペアを解除したりすることはできません。

(コマンドは1つです。 git blame はやや異なるコピーファインディングを行うので、上記は完全に当てはまりません)。


1 を実行すると git commit --include <paths> または git commit --only <paths> または git commit <paths> または git commit -a を実行する前にインデックスを変更することだと考えてください。 git commit . の特殊なケースでは --only の場合、Git は一時的なインデックスを使うので少し複雑ですが、それでもコミットは an インデックスの代わりに特別な一時的なものを使うだけです。 一時的なインデックスを作成するために、Gitはすべてのファイルを HEAD コミットし、その上に --only を追加しました。 その他のケースでは、Git はワークツリー・ファイルを通常のインデックスにコピーし、インデックスから通常通りコミットを実行します。

2 実際にスナップショットを行い、Blobをリポジトリに格納するのは、以下の手順で行います。 git add . このため、密かに git commit を実行するのにかかる余分な時間には通常気づかないので、より高速になります。 git add を起動する前に git commit .


なぜ git mv が存在する

git mv old new が行うのは 非常に 大まかには

mv old new
git add new
git add old

最初のステップは、ワークツリー版のファイルの名前を変更することです。 2番目のステップも同様で、インデックスバージョンのファイルを所定の位置に配置する必要があります。 しかし、3つ目は 奇妙な 削除したばかりのファイルをなぜ追加しなければならないのでしょうか? まあね。 git add は、常にファイルを追加するわけではありません。この場合、ファイル でした。 がインデックスに含まれていて、もう存在しない。

また、その3番目のステップを次のように綴ることもできます。

git rm --cached old

私たちが実際に行っているのは、インデックスから古い名前を取り除くことだけです。

しかし、ここで問題があって、"と言ったわけです。 非常に roughly"です。 インデックスには各ファイルのコピーがあり、次回実行時にコミットされます 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回行うわけでも、移動と追加と削除を行うわけでもない。 その代わり、ワークツリーファイルの名前を変更する。 はインデックス内コピーの名前を変更します。 元ファイルのインデックスコピーがワークツリーファイルと異なる場合、名前を変更したインデックスコピーは名前を変更したワークツリーコピーと依然として異なります。

のようなフロントエンドのコマンドがないと、とても難しいです。 git mv . 4 もちろん、もしあなたが git add のすべてが必要かというと、そもそもこんなものは必要ない。 そして、注目すべきは、もし git cp が存在する場合、それはおそらく また は、インデックスのコピー時に、ワークツリーのバージョンではなく、インデックスのバージョンをコピーします。 そこで git cp は本当に存在するはずです。 また git mv --after オプションは、Mercurial の hg mv --after . どちらも べきである が存在するが、現在は存在しない。 (しかし、これらのどちらかが必要とされることは、まっすぐな git mv 私の意見では、)


3 この例では、ちょっとバカバカしくて意味がないですね。 しかし、もしあなたが git add -p を使用して慎重に中間コミット用のパッチを準備し、そのパッチと一緒にファイル名を変更することにした場合、慎重にパッチした中間バージョンを混乱させることなくそれを実行できるのは間違いなく便利なことです。

4 不可能ではない git ls-index --stage は、今のインデックスから必要な情報を得ることができますし git update-index を使えば、インデックスを任意に変更することができます。 この二つと、複雑なシェルスクリプトやより優れた言語によるプログラミングを組み合わせることで、以下のような実装が可能になります。 git mv --aftergit cp .