Git merge: применить изменения к коду, перемещенному в другой файл

Я пытаюсь выполнить довольно мускулистый маневр git. Одна из проблем, с которыми я сталкиваюсь, заключается в том, что я внес некоторые изменения в какой-то код в своем филиале, но мой коллега переместил этот код в новый файл в своем филиале. Поэтому, когда я сделал git merge my_branch his_branch, git не заметил, что код в новом файле был таким же, как старый, и поэтому никаких моих изменений нет.

Какой самый простой способ применить мои изменения снова к коду в новых файлах. У меня не будет слишком много проблем, выясняя, какие коммиты должны быть повторно применены (я могу просто использовать git log --stat). Но, насколько я могу судить, нет способа получить git для повторного применения изменений в новых файлах. Самое простое, что я вижу сейчас, - это вручную повторно применить изменения, которые не выглядят как хорошая идея.

Я знаю, что git распознает blobs, а не файлы, поэтому, наверняка, должен быть способ рассказать об этом ", примените это точное изменение кода из этого коммита, кроме того, где он был, но где он сейчас находится в этом новом файле".

Ответ 1

У меня была аналогичная проблема, и я решил ее, перезарядив мою работу, чтобы соответствовать организации целевого файла.

Скажите, что вы изменили original.txt на своей ветке (ветвь local), но на главной ветке original.txt была скопирована на другую, скажем copy.txt. Эта копия была выполнена в фиксации, которую мы назовем commit CP.

Вы хотите применить все свои локальные изменения, зафиксировать A и B ниже, которые были сделаны на original.txt, в новый файл copy.txt.

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

Создайте отложенную ветвь move в начальной точке ваших изменений. То есть, положите ветвь 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 еще не существовало в вашем дереве в данный момент.
Зафиксируйте свое изменение (назовем это commit 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.txt поверх фиксации CP. Теперь ваш ветвь local точно так же, как если бы вы всегда изменяли copy.txt, а не original.txt, и вы можете продолжать слияние с другими.

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

Важно, чтобы изменения были применены к CP или иначе copy.txt не существовало бы и изменения будут применены обратно на original.txt.

Надеюсь, это понятно. Этот ответ приходит поздно, но это может быть полезно кому-то другому.

Ответ 2

Вы можете всегда использовать git diff (или git format-patch) для создания патча, затем вручную отредактировать имена файлов в патче и применить его с помощью git apply (или git am).

В этом случае единственный способ, которым он будет работать автоматически, - это если git определение переименования может определить, что старые и новые файлы - это то же самое, что звучит так, будто они на самом деле не в вашем случае, просто кусок их. Это правда, что git использует blobs, а не файлы, но blob - это всего лишь содержимое целого файла без прикрепленных файлов и метаданных. Поэтому, если у вас есть кусок кода, перемещаемый между двумя файлами, они на самом деле не являются тем же самым blob - остальная часть содержимого blob отличается, только общий фрагмент.

Ответ 3

Вот решение слияния, которое встречает конфликт слияния с переименованием и редактированием и разрешает его с помощью mergetool, распознающего правильные файлы с тремя слияниями.

  • После слияния слияния из-за "удаленного файла", который, как вы понимаете, был переименован и отредактирован:

    • Вы прекратите слияние.
    • Зафиксировать переименованные файлы в вашей ветке.
    • И снова объединить.

Walk-через:

Создайте файл .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