web-dev-qa-db-ja.com

GITコピーファイルの保存履歴

GITにはやや紛らわしい質問があります。たとえば、ファイルdir1/A.txtがコミットされており、gitはコミットの履歴を保持しています

(何らかの理由で)ファイルをdir2/A.txtにコピーする必要があります(移動ではなくコピー)。 git mvコマンドがあることは知っていますが、dir2/A.txtと同じコミット履歴を持つにはdir1/A.txtが必要であり、dir1/A.txtがまだ残っている必要があります。

コピーが作成され、今後のすべての作業がA.txtで行われると、dir2/A.txtを更新する予定はありません。

紛らわしいように聞こえますが、この状況はJavaベースのモジュール(Maven化されたプロジェクト)にあり、顧客が2つの異なる機能を持つことができるように、新しいバージョンのコードを作成する必要がありますランタイムのバージョンでは、最初のバージョンは、アライメントが完了すると最終的に削除されます。もちろん、Mavenのバージョン管理を使用できます。私はGITの初心者であり、ここでgitが提供できるものに興味があります。

147
Mark Bramnik

Subversionとは異なり、gitにはファイルごとの履歴はありません。コミットデータ構造を見ると、それは以前のコミットとこのコミットの新しいツリーオブジェクトのみを指します。コミットによってファイルが変更される明示的な情報は、コミットオブジェクトに保存されません。これらの変更の性質も。

変更を検査するツールは、ヒューリスティックに基づいて名前の変更を検出できます。例えば。 「git diff」には、名前変更検出をオンにするオプション-Mがあります。そのため、名前変更の場合、「git diff」は1つのファイルが削除され、別のファイルが作成されたことを示しますが、「git diff -M」は実際に移動を検出し、それに応じて変更を表示します(「man git diff」を参照)詳細)。

したがって、gitでは、これは変更をどのようにコミットするかではなく、コミットされた変更を後で見る方法の問題です。

71
CliffordVienna

必要なのは、2つの異なる場所に並行して移動し、マージしてから1つのコピーを元の場所に戻すだけです。

その後、特別なオプションなしで、どちらかのコピーでgit blameを実行し、両方の最適な履歴属性を確認できます。オリジナルでgit logを実行して、完全な履歴を表示することもできます。

具体的には、リポジトリのfoo /をbar /にコピーするとします。これを行います(フックによる書き換えなどを避けるためにコミットするために-nを使用していることに注意してください):

git mv foo bar
git commit -n
SAVED=`git rev-parse HEAD`
git reset --hard HEAD^
git mv foo foo-magic
git commit -n
git merge $SAVED # This will generate conflicts
git commit -a -n # Trivially resolved like this
git mv foo-magic foo
git commit -n

注:Linuxではgit 2.1.4を使用しました

なぜこれが機能するのか

次のような改訂履歴になります。

  ORIG_HEAD
    /    \
SAVED  ALTERNATE
    \    /
    MERGED
      |
   RESTORED

Gitに「foo」の履歴について尋ねると、(1)MERGEDとRESTOREDの間で「foo-magic」から名前変更を検出し、(2)「foo-magic」がMERGEDのALTERNATE親から来たことを検出し、 (3)ORIG_HEADとALTERNATEの間で 'foo'から名前の変更を検出します。そこから「foo」の歴史を掘り下げます。

Gitに「bar」の履歴について尋ねると、(1)MERGEDとRESTOREDの間に変化がないこと、(2)「bar」がMERGEDのSAVED親から来たことを検出し、(3)「 ORIG_HEADとSAVEDの間のfoo '。そこから「foo」の歴史を掘り下げます。

とても簡単です。ファイルの2つの追跡可能なコピーを受け入れることができるマージ状態にgitを強制する必要があり、これを元のファイルの並行移動で行います(すぐに元に戻します)。

100
Peter Dillinger

ファイルをコピーし、追加してコミットするだけです:

cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"

その後、次のコマンドは完全なコピー前の履歴を表示します:

git log --follow dir2/A.txt

元のファイルから継承された行ごとの注釈を表示するには、次を使用します。

git blame -C -C -C dir2/A.txt

Gitはコミット時にコピーを追跡せず、代わりに履歴を検査する際にdetectsgit blameおよびgit log

この情報のほとんどは、次の回答から得られます。 Gitを使用したファイルコピー操作の記録

20
Jakob Buron

完全を期すために、制御されたファイルと制御されていないファイルでいっぱいのディレクトリ全体をコピーする場合は、次を使用できます。

git mv old new
git checkout HEAD old

管理されていないファイルはコピーされるため、クリーンアップする必要があります。

git clean -fdx new
10
Hervé

Peterの回答 を少し変更して、git-split.shという再利用可能な非対話型のシェルスクリプトを作成しました。

#!/bin/sh

if [[ $# -ne 2 ]] ; then
  echo "Usage: git-split.sh original copy"
  exit 0
fi

git mv "$1" "$2"
git commit -n -m "Split history $1 to $2 - rename file to target-name"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv "$1" temp
git commit -n -m "Split history $1 to $2 - rename source-file to temp"
git merge $REV
git commit -a -n -m "Split history $1 to $2 - resolve conflict and keep both files"
git mv temp "$1"
git commit -n -m "Split history $1 to $2 - restore name of source-file"
7
Lukas Eder

私の場合、ハードドライブで変更を行い(作業コピーのあるパスから作業コピーの別のパスに約200個のフォルダー/ファイルをカット/ペースト)、SourceTree(2.0.20.1)を使用して、検出された両方をステージングしました変更(1回の追加、1回の削除)、追加と削除の両方を一緒にステージングしている限り、ピンクのRアイコン(名前の変更を想定)で自動的に1つの変更に結合されます。

一度に非常に多くの変更があったため、SourceTreeはすべての変更を検出するのが少し遅かったので、ステージングファイルの一部は追加(緑プラス)または削除(赤マイナス)のように見えますが、ファイルのステータスを更新し続け、最終的にポップアップが表示されたときに新しい変更をステージングし続けました。数分後、リスト全体が完璧になり、コミットの準備が整いました。

履歴が存在することを確認しました。履歴を検索するときは、[名前の変更されたファイルに従う]オプションをオンにします。

2
darbvin