web-dev-qa-db-ja.com

Gitでファイルを移動/名前変更してその履歴を維持することは可能ですか?

Gitでプロジェクトサブツリーの名前を変更/移動したい

/project/xyz

/components/xyz

普通のgit mv project componentsを使うと、xyz projectのコミット履歴はすべて失われます。歴史が維持されるようにこれを動かす方法はありますか?

597
sgargan

Gitは操作をコミットで永続化するのではなく名前の変更を検出するので、git mvmvのどちらを使用しても問題ありません。

logコマンドは、名前変更操作の前に履歴を継続する--follow引数を取ります。つまり、ヒューリスティックを使用して類似のコンテンツを検索します。

http://git-scm.com/docs/git-log

全履歴を検索するには、次のコマンドを使用します。

git log --follow ./path/to/file
598
Troels Thomsen

ファイルの名前を変更して履歴をそのままにするのは 考えられる ですが、リポジトリの履歴全体を通してファイルの名前が変更されることになります。これはおそらく強迫観念のあるgit-log-loversのためだけであり、これらを含むいくつかの深刻な意味を持ちます。

  • Gitを使用している間は最も重要なことではない共有履歴を書き換えることができます。他の誰かがリポジトリを複製した場合は、これを実行して壊します。頭痛を避けるために彼らは再クローン化しなければならないでしょう。名前の変更が十分に重要であれば、これで問題ないかもしれませんが、慎重に検討する必要があります。オープンソースコミュニティ全体が混乱する可能性があります。
  • リポジトリの履歴の中でファイルを古い名前で参照したことがある場合は、事実上以前のバージョンを壊しています。これを直すには、もう少しフープジャンプをする必要があります。それは不可能ではなく、単調でそしておそらくそれだけの価値がありません。

今、あなたはまだ私と一緒にいるので、あなたはおそらく完全に分離されたファイルの名前を変更している単独の開発者です。 filter-treeを使ってファイルを移動しましょう!

ファイルoldをフォルダdirに移動し、名前をnewにするとします。

これはgit mv old dir/new && git add -u dir/newで行うことができますが、それは歴史を壊します。

代わりに:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

will redo ブランチをコミットするたびに、繰り返しごとにティックでコマンドを実行します。あなたがこれを行うとたくさんのものがうまくいかないことがあります。私は通常、ファイルが存在するかどうかを確かめるためにテストし(そうでなければまだそこに移動していない)、それから必要なステップを実行して私の好みに合わせてツリーを強化します。ここでは、ファイルへの参照を変更するためにファイルを参照することができます。自分をノックアウト! :)

完了すると、ファイルは移動され、ログはそのまま残ります。あなたは忍者海賊のようです。

また。 mkdirディレクトリは、もちろんファイルを新しいフォルダに移動する場合にのみ必要です。 if を指定すると、ファイルが存在するよりも前の履歴でこのフォルダが作成されなくなります。

86

いいえ.

簡単な答えは _ no _ です。Gitでファイルの名前を変更して履歴を記憶することはできません。そしてそれは痛みです。

git log --follow--find-copies-harder はうまくいくと噂されていますが、たとえファイルの内容に変更がなくても git mv で移動したとしても、私にはうまくいきません。

(最初はEclipseを使って1回の操作でパッケージの名前変更と更新を行っていましたが、これはgitを混乱させる可能性がありました。しかし--followmvのみが実行され、commitmvが実行されない場合はうまくいきます。遠すぎる。

Linus氏によると、個々のファイルを追跡する必要はなく、ソフトウェアプロジェクトの内容全体を総合的に理解することになっています。残念ながら、私の小さな頭脳ではそれができません。

非常に多くの人々がgitが自動的に動きを追跡するという文を不注意に繰り返しているのは 本当に厄介な です。彼らは私の時間を無駄にしました。 Gitはそのようなことはしません。 設計上(!)Gitは動きをまったく追跡しません。

私の解決策は、ファイルの名前を元の場所に戻すことです。ソース管理に合うようにソフトウェアを変更します。 gitを使えば、初めて正しくgitする必要があるようです。

残念ながら、これはEclipseを壊します。これは--followを使っているようです。
git log --followgit logのように表示されていても、複雑な名前変更履歴のあるファイルの全履歴が表示されないことがあります。 (何故かはわからない。)

(昔の仕事をさかのぼってやり直すには、あまりにも巧妙なハックがいくつかありますが、どちらかといえば恐ろしいものです。GitHub-Gist: emiller/git-mv-with-history を参照してください。)

77
Tuntable
git log --follow [file]

名前の変更を通してあなたに歴史を見せるでしょう。

40
Erik Hesselink

私がやります:

git mv {old} {new}
git add -u {new}
17
James M. Greene

Gitのプロジェクトサブツリーの名前を変更/移動したい

/project/xyz

/ components/xyz

プレーンなgit mv project componentsを使用すると、xyzプロジェクトのすべてのコミット履歴が失われます。

いいえ(8年後、Git 2.19、2018年第3四半期)、Gitはディレクトリの名前変更を検出するため、これはより適切に文書化されるためです。

commit b00bf1ccommit 1634688commit 0661e49commit 4d34dffcommit 983f464commit c840e1acommit 99294 (2018年6月27日)、および commit d4e8062commit 5dacd4a (2018年6月25日)by Elijah Newren(newren
commit 0ce5a69Junio C Hamano-gitster- に合併、2018年7月24日)

これは Documentation/technical/directory-rename-detection.txt で説明されています:

例:

x/ax/b、およびx/cのすべてがz/az/b、およびz/cに移動した場合、その間に追加されたx/dも、ディレクトリ 'x'が 'z'に移動したというヒントを使用して、z/dに移動する可能性があります。

しかし、それらは次のような他の多くの場合です:

履歴の一方はx -> zの名前を変更し、もう一方はx/eに一部のファイルの名前を変更します。これにより、マージが推移的な名前変更を行う必要が生じます。

ディレクトリ名変更の検出を簡素化するために、これらのルールはGitによって実施されます。

ディレクトリ名の変更の検出が適用される場合、いくつかの基本的なルールが制限されます。

  1. 特定のディレクトリがマージの両側にまだ存在する場合、名前が変更されたとは見なされません。
  2. 名前を変更するファイルのサブセットにファイルまたはディレクトリが存在する場合(または互いに干渉する場合)、それらの特定のサブパスのディレクトリ名変更を「オフ」にし、競合をユーザーに報告する。
  3. 履歴の反対側がディレクトリの名前を変更してパスの名前を変更した場合、暗黙的なディレクトリの名前変更については、履歴の反対側からの特定の名前の変更を無視します(ただし、ユーザーに警告します)。

a /のテストを t/t6043-merge-rename-directories.sh で見ることができます。

  • a)名前の変更によりディレクトリが2つ以上に分割された場合、最も名前が変更されたディレクトリが「勝ちます」。
  • b)パスがマージのいずれかの側で名前変更のソースである場合、パスのディレクトリ名変更検出を回避します。
  • c)履歴の反対側が名前変更を行う側である場合にのみ、暗黙的なディレクトリ名変更をディレクトリに適用します。
16
VonC

目的

  • git am を使用 Smar からインスピレーションを受け、 Exherbo から借用)
  • コピー/移動したファイルのコミット履歴を追加
  • あるディレクトリから別のディレクトリへ
  • あるリポジトリから別のリポジトリへ

制限

  • タグとブランチは保持されません
  • パスファイルの名前変更(ディレクトリ名変更)で履歴が切れる

概要

  1. を使用して電子メール形式で履歴を抽出する
    git log --pretty=email -p --reverse --full-index --binary
  2. ファイルツリーを再編成してファイル名を更新する
  3. を使って新しい履歴を追加する
    cat extracted-history | git am --committer-date-is-author-date

1.電子メール形式で履歴を抽出する

例:file3file4およびfile5の履歴の抽出

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

目的地を設定/清掃する

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder

各ファイルの履歴を電子メール形式で抽出する

cd my_repo/dirB
find -name .git -Prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

残念ながら、オプション--followまたは--find-copies-harder--reverseと組み合わせることはできません。これが、ファイルの名前が変更されたとき(または親ディレクトリの名前が変更されたとき)に履歴が切り捨てられる理由です。

電子メール形式の一時的な履歴

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

Dan Bonachea この最初のステップでgit log generationコマンドのループを逆にすることをお勧めします。ファイルごとに1回git logを実行するのではなく、コマンドラインでファイルのリストを使用して1回だけ実行します。 。この方法では、複数のファイルを変更するコミットは結果として単一のコミットのままになり、すべての新しいコミットは元の相対的な順序を維持します。これはまた(今統一された)ログのファイル名を書き換えるとき下記の第2ステップの変更を必要とすることに注意してください。


2.ファイルツリーを再編成してファイル名を更新する

この3つのファイルをこの別のリポジトリに移動するとします(同じリポジトリにすることができます)。

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # from subdir
│   │   ├── file33    # from file3
│   │   └── file44    # from file4
│   └── dirB2         # new dir
│        └── file5    # from file5
└── dirH
    └── file77

したがって、ファイルを再編成してください。

cd /tmp/mail/dir
mkdir -p dirB/dirB1
mv subdir/file3 dirB/dirB1/file33
mv subdir/file4 dirB/dirB1/file44
mkdir -p dirB/dirB2
mv file5 dirB/dirB2

あなたの一時的な歴史は今です:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

履歴内のファイル名も変更します。

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

3.新しい履歴を適用する

あなたの他のレポは:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

一時履歴ファイルからコミットを適用します。

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-dateはオリジナルのコミットタイムスタンプを保持します( Dan Bonachea のコメント)。

あなたの他のレポは今です:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

コミットされたコミットの量を確認するにはgit statusを使用してください:-)


特別なトリック:あなたのリポジトリの中で名前を変更された/移動されたファイルをチェックする

名前が変更されたファイルを一覧表示するには

find -name .git -Prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

その他のカスタマイズ:オプションgit logまたは--find-copies-harderを使ってコマンド--reverseを完成させることができます。 cut -f3-と完全パターン '{。* =>。*}'を使用して最初の2つの列を削除することもできます。

find -name .git -Prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
14
olibre

Gitの中核をなすものですが、git配管は名前の変更を追跡しませんが、gitログ "porcelain"を使って表示された履歴はそれらを検出することができます。

与えられたgit logに対して、-Mオプションを使用してください。

git log -p -M

現在のバージョンのgitを使って。

これはgit diffのような他のコマンドにも同様に働きます。

比較を多少厳密にするためのオプションがあります。同時にファイルに大きな変更を加えずにファイルの名前を変更すると、git logや友人が名前の変更を検出しやすくなります。このため、あるコミットでファイルの名前を変更し、別のコミットでファイルを変更する人がいます。

Gitにファイルのリネーム場所の確認を依頼するたびに、cpuの使用にコストがかかります。そのため、使用するかどうか、および使用するかどうかはお客様次第です。

特定のリポジトリで名前の変更の検出とともに履歴を常に報告したい場合は、次のようにします。

git config diff.renames 1

あるディレクトリから別のディレクトリに移動するファイルが検出されました。これが例です:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <[email protected]>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <[email protected]>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

git logだけでなくdiffを使っているときはいつでもこれが働くことに注意してください。例えば:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

試行として、機能ブランチの1つのファイルに小さな変更を加えてコミットし、次にマスターブランチでそのファイルの名前を変更してコミットし、次にファイルの別の部分に小さな変更を加えてコミットしました。私が機能ブランチに行き、masterからマージしたとき、マージはファイルの名前を変更して変更をマージしました。これがマージからの出力です。

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

その結果、ファイルの名前が変更され、両方のテキストが変更された作業ディレクトリが作成されました。そのため、名前の変更を明示的に追跡しないという事実にもかかわらず、gitは正しいことを実行できます。

これは古い質問に対する遅い回答なので、その時点で他の回答はgitバージョンに対して正しかったかもしれません。

2
John S Gruber