git merge:別のファイルに移動したコードに変更を適用します


132

私は今かなりぎこちないgitマージ操作を試みています。私が遭遇している1つの問題は、ブランチのコードにいくつかの変更を加えたが、同僚がそのコードをブランチの新しいファイルに移動したことです。そのため、私がを実行したときgit merge my_branch his_branch、Gitは新しいファイルのコードが古いファイルと同じであることを認識しなかったため、変更はありません。

新しいファイルのコードに変更を再度適用する最も簡単な方法は何ですか。どのコミットを再適用する必要があるかを見つけるのにそれほど多くの問題は発生しません(私はを使用できますgit log --stat)。しかし、私が知る限り、gitに変更を新しいファイルに再適用させる方法はありません。私が今見ている最も簡単なことは、手動で変更を再適用することですが、これは良いアイデアのようには見えません。

私はgitがファイルではなくblobを認識することを知っているので、「このコミットからのこの正確なコード変更を適用します。ただし、この新しいファイルのどこにあるかではなく、現在の場所を除きます」。


2
ここではまったく同じではなく、適用される可能性が良いのanwsersと同様の質問がある:stackoverflow.com/questions/2701790/...
マリアーノDesanze

4
なぜ gitがこのようなマージを自動的に実行できないのかについての回答はありません。名前の変更を検出して適切なマージを自動的に実行するのに十分スマートであるはずだと思いましたか?
ジョン

質問をしたとき、おそらくこれは真実ではなかったかもしれませんが、gitの最新バージョン(私は1.9.5を使用しています)は、名前を変更して移動したファイル間で変更をマージできます。--rename-threshold必要な類似性の量を調整するオプションさえあります。
トッドオーウェン

1
@ToddOwenは、上流のブランチから基本的なリベースを行う場合に機能しますが、ファイルの名前を変更するコミットが含まれていない可能性のある一連の変更をチェリーピッキングまたはバックポーティングしている場合でも問題が発生します。
GuyPaddock

最初にリベースを行うと、これはまったく問題ですか?
hbogert

回答:


132

同様の問題がありましたが、対象のファイル編成に一致するように作業をリベースすることで解決しました。

あなたが変更と言うoriginal.txtあなたのブランチ(上local分岐)が、masterブランチに、original.txt別のものにコピーされている、と言いますcopy.txt。このコピーは、commitという名前のコミットで行われましたCP

あなたは、すべてのローカルの変更、コミット適用するAと、B上で行われたの下に、original.txt新しいファイルに、copy.txt

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

moveを使用して、変更の開始点に使い捨てブランチを作成しますgit branch move X。つまり、マージするコミットの前のmoveブランチであるcommit Xにブランチを置きます。ほとんどの場合、これは変更を実装するために分岐したコミットです。ユーザー@digory dooが以下に書いたように、あなたはgit merge-base master localを見つけるために行うことができますX

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

このブランチで、次の名前変更コマンドを発行します。

git mv original.txt copy.txt

これにより、ファイルの名前が変更されます。copy.txtこの時点ではまだツリーに存在していないことに注意してください。
変更をコミットします(このコミットに名前を付けますMV)。

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

これで、上に作業をリベースできますmove

git rebase move local

これは問題なく動作するはずで、変更はcopy.txtローカルブランチに適用されます。

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

ここでMV、移動操作はCPメインブランチでのコミット時のコピー操作と競合する可能性があるため、メインブランチの履歴で必ずしもコミットする必要はありません。

次のように、移動操作を破棄して、作業を再度リベースするだけです。

git rebase move local --onto CP

... 他のブランチで導入されCPたコミットcopy.txtです。これにより、すべての変更copy.txtCPコミットに基づいてリベースされます。さて、あなたのlocalブランチは、正確に、あなたは常に変更されているかのようcopy.txtではなくoriginal.txt、あなたが他の人との合併を続行することができます。

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

変更が適用されるCPか、そうでなければcopy.txt存在せず、変更が再び適用されることが重要ですoriginal.txt

これが明確であることを願っています。この答えは遅くなりますが、これは他の人に役立つかもしれません。


2
それは大変な作業ですが、原則的にはうまくいくと思います。ただし、リベースよりもマージの方がうまくいくと思います。
asmeurer 2013

7
このソリューションには、パッチの編集や使用patch(多くの作業を伴う可能性もあります)とは異なり、基本的なgitコマンドのみが含まれるため、それを表示するのは興味深いと思いました。また、私の場合、変更を実際に適用するよりも、答えを書くのに時間がかかったことにも注意してください。マージを使用してどのステップを推奨しますか?なぜ?私が目にする唯一の違いは、リベースすることで、後で破棄される一時的なコミット(commit MV)を作成することです。これは、マージのみでは不可能です。
コアダンプ2013年

1
リベースを使用すると、各コミットを処理するため、マージの競合を処理する可能性が高くなります。一方、マージを使用すると、すべてを一度に処理します。つまり、リベースで発生した可能性のあるいくつかの変更は、マージでは存在しません。私がやろうとしていることは、マージして、マージされたファイルを手動で移動することです。多分私はあなたの答えの考えを誤解しました。
asmeurer 2013年

4
アプローチを明確にするために、ASCIIツリーをいくつか追加しました。その場合のマージとリベースの比較:私がしたいことは、私のブランチの「original.txt」に対するすべての変更を取得し、何らかの理由で「オリジナル」であるため、それらをマスターブランチの「copy.txt」に適用することです。 txtは、ある時点で「copy.txt」にコピーされました(移動されていません)。そのコピーの後、 'original.txt'もmasterブランチで進化した可能性があります。直接マージした場合、original.txtに対するローカルの変更がマスターブランチの変更されたoriginal.txtに適用され、マージが困難になります。よろしく。
コアダンプ2013年

1
どちらにしても、私はこの解決策がうまくいくと信じています(幸いなことに、今はそれを試す状況はありません)。今のところ、答えとしてマークします。
asmeurer 2013年

31

常にgit diff(またはgit format-patch)を使用してパッチを生成し、パッチのファイル名を手動で編集して、git apply(またはgit am)で適用できます。

これがなければ、それが自動的に機能する唯一の方法は、gitの名前変更検出が古いファイルと新しいファイルが同じものであることを理解できる場合です-それは実際にはあなたの場合ではなく、それらのチャンクにすぎないようです。gitがファイルではなくblobを使用することは事実ですが、blobはファイル全体のコンテンツであり、ファイル名とメタデータは添付されていません。したがって、2つのファイル間でコードのチャンクを移動した場合、それらは実際には同じblobではありません。残りのblobのコンテンツは異なり、共通のチャンクだけです。


1
まあ、それは今のところ最良の答えです。git format-patchコミットのためにどのように機能するかわかりませんでした。私がそうするならgit format-patch SHA1、それは歴史全体のためのパッチファイルの全体の束を生成します。しかし、私はgit show SHA1 > diff.patch同様にうまくいくと思います。
asmeurer

1
@asmeurer:-1オプションを使用します。format-patchの通常の操作モードは、のようなリビジョン範囲なorigin/master..masterので、パッチシリーズを簡単に準備できます。
Cascabel 2010

1
実際には、別のメモ。 git applyそしてgit am、彼らが同じ行番号をしたいので、あまりにもうるさいです。しかし、私は現在UNIX patchコマンドで成功しています。
asmeurer 2010

24

これは、名前の変更と編集でマージの競合が発生し、mergetoolが正しい3つのマージソースファイルを認識して解決するマージソリューションです。

  • 名前が変更され、編集されたことがわかった「削除されたファイル」のためにマージが失敗した後:

    1. マージを中止します。
    2. ブランチで名前を変更したファイルをコミットします。
    3. そして再びマージします。

ウォークスルー:

file.txtを作成します。

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

後で編集するブランチを作成します。

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

マスターで名前変更と編集を作成します。

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

スワップしてブランチし、そこで編集します。

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

マスターをマージしようとしました:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

競合を解決するのは難しいことに注意してください-そして、ファイルの名前が変更されました。中止し、名前変更を模倣します。

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

もう一度マージしてみてください:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

すごい!マージの結果、「通常の」競合が発生しますが、これはmergetoolで解決できます。

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits

面白い。次回この問題に遭遇したときに、これを試さなければなりません。
asmeurer 2013

1
その解決策では、最初のステップである中止されたマージは、名前が変更されてリモートで編集されたファイルを見つける場合にのみ役立つようです。事前に知っている場合。この手順を省略できます。基本的に、解決策は、ファイルをローカルで手動で名前を変更してから、通常どおり競合をマージして解決することです。

1
ありがとうございました。私の状況は、私が移動B.txt -> C.txtA.txt -> B.txtてgit mvを使用していて、gitがマージの競合を自動的に正しく一致させることができなかった(古いものB.txtと新しいものの間でマージの競合が発生していたB.txt)です。この方法を使用すると、正しいファイル間でマージの競合が発生します。
cib

これはファイル全体が移動された場合にのみ機能しますが、通常gitはその状況を自動的に検出するはずです。トリッキーな状況は、ファイルの一部のみが移動される場合です。
Robin Green、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.