Gitでプロジェクトサブツリーの名前を変更/移動したい
/project/xyz
に
/components/xyz
普通のgit mv project components
を使うと、xyz project
のコミット履歴はすべて失われます。歴史が維持されるようにこれを動かす方法はありますか?
Gitは操作をコミットで永続化するのではなく名前の変更を検出するので、git mv
とmv
のどちらを使用しても問題ありません。
log
コマンドは、名前変更操作の前に履歴を継続する--follow
引数を取ります。つまり、ヒューリスティックを使用して類似のコンテンツを検索します。
http://git-scm.com/docs/git-log
全履歴を検索するには、次のコマンドを使用します。
git log --follow ./path/to/file
ファイルの名前を変更して履歴をそのままにするのは 考えられる ですが、リポジトリの履歴全体を通してファイルの名前が変更されることになります。これはおそらく強迫観念のあるgit-log-loversのためだけであり、これらを含むいくつかの深刻な意味を持ちます。
今、あなたはまだ私と一緒にいるので、あなたはおそらく完全に分離されたファイルの名前を変更している単独の開発者です。 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 を指定すると、ファイルが存在するよりも前の履歴でこのフォルダが作成されなくなります。
簡単な答えは _ no _ です。Gitでファイルの名前を変更して履歴を記憶することはできません。そしてそれは痛みです。
git log --follow
--find-copies-harder
はうまくいくと噂されていますが、たとえファイルの内容に変更がなくても git mv
で移動したとしても、私にはうまくいきません。
(最初はEclipseを使って1回の操作でパッケージの名前変更と更新を行っていましたが、これはgitを混乱させる可能性がありました。しかし--follow
はmv
のみが実行され、commit
とmv
が実行されない場合はうまくいきます。遠すぎる。
Linus氏によると、個々のファイルを追跡する必要はなく、ソフトウェアプロジェクトの内容全体を総合的に理解することになっています。残念ながら、私の小さな頭脳ではそれができません。
非常に多くの人々がgitが自動的に動きを追跡するという文を不注意に繰り返しているのは 本当に厄介な です。彼らは私の時間を無駄にしました。 Gitはそのようなことはしません。 設計上(!)Gitは動きをまったく追跡しません。
私の解決策は、ファイルの名前を元の場所に戻すことです。ソース管理に合うようにソフトウェアを変更します。 gitを使えば、初めて正しくgitする必要があるようです。
残念ながら、これはEclipseを壊します。これは--follow
を使っているようです。git log --follow
git log
のように表示されていても、複雑な名前変更履歴のあるファイルの全履歴が表示されないことがあります。 (何故かはわからない。)
(昔の仕事をさかのぼってやり直すには、あまりにも巧妙なハックがいくつかありますが、どちらかといえば恐ろしいものです。GitHub-Gist: emiller/git-mv-with-history を参照してください。)
git log --follow [file]
名前の変更を通してあなたに歴史を見せるでしょう。
私がやります:
git mv {old} {new}
git add -u {new}
Gitのプロジェクトサブツリーの名前を変更/移動したい
/project/xyz
に
/ components/xyz
プレーンな
git mv project components
を使用すると、xyz
プロジェクトのすべてのコミット履歴が失われます。
いいえ(8年後、Git 2.19、2018年第3四半期)、Gitはディレクトリの名前変更を検出するため、これはより適切に文書化されるためです。
commit b00bf1c 、 commit 1634688 、 commit 0661e49 、 commit 4d34dff 、 commit 983f464 、 commit c840e1a 、 commit 99294 (2018年6月27日)、および commit d4e8062 、 commit 5dacd4a (2018年6月25日)by Elijah Newren(newren
) 。
( commit 0ce5a69 の Junio C Hamano-gitster
- に合併、2018年7月24日)
これは Documentation/technical/directory-rename-detection.txt
で説明されています:
例:
x/a
、x/b
、およびx/c
のすべてがz/a
、z/b
、およびz/c
に移動した場合、その間に追加されたx/d
も、ディレクトリ 'x
'が 'z
'に移動したというヒントを使用して、z/d
に移動する可能性があります。
しかし、それらは次のような他の多くの場合です:
履歴の一方は
x -> z
の名前を変更し、もう一方はx/e
に一部のファイルの名前を変更します。これにより、マージが推移的な名前変更を行う必要が生じます。
ディレクトリ名変更の検出を簡素化するために、これらのルールはGitによって実施されます。
ディレクトリ名の変更の検出が適用される場合、いくつかの基本的なルールが制限されます。
- 特定のディレクトリがマージの両側にまだ存在する場合、名前が変更されたとは見なされません。
- 名前を変更するファイルのサブセットにファイルまたはディレクトリが存在する場合(または互いに干渉する場合)、それらの特定のサブパスのディレクトリ名変更を「オフ」にし、競合をユーザーに報告する。
- 履歴の反対側がディレクトリの名前を変更してパスの名前を変更した場合、暗黙的なディレクトリの名前変更については、履歴の反対側からの特定の名前の変更を無視します(ただし、ユーザーに警告します)。
a /のテストを t/t6043-merge-rename-directories.sh
で見ることができます。
- a)名前の変更によりディレクトリが2つ以上に分割された場合、最も名前が変更されたディレクトリが「勝ちます」。
- b)パスがマージのいずれかの側で名前変更のソースである場合、パスのディレクトリ名変更検出を回避します。
- c)履歴の反対側が名前変更を行う側である場合にのみ、暗黙的なディレクトリ名変更をディレクトリに適用します。
git am
を使用 ( Smar からインスピレーションを受け、 Exherbo から借用)git log --pretty=email -p --reverse --full-index --binary
cat extracted-history | git am --committer-date-is-author-date
例:file3
、file4
および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ステップの変更を必要とすることに注意してください。
この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"' {} ';'
あなたの他のレポは:
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 '{.* => .*}'
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バージョンに対して正しかったかもしれません。