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

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

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

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

3 ответа

Решение

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

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

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

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

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

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

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

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

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

Если не считать этого, единственный способ, которым он будет работать автоматически, - это если git обнаружение переименования может выяснить, что старые и новые файлы - это одно и то же - похоже, что они на самом деле не в вашем случае, а всего лишь их часть. Это правда, что git использует BLOB-объекты, а не файлы, но BLOB-объект - это просто содержимое всего файла без имени файла и метаданных. Таким образом, если у вас есть кусок кода, перемещенный между двумя файлами, они на самом деле не являются одним и тем же BLOB-объектом - остальная часть содержимого BLOB-объекта отличается, только общая часть.

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

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

    1. Вы прерываете слияние.
    2. Зафиксируйте переименованные файлы в вашей ветке.
    3. И снова слить.

Прохождение:

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

Создайте переименование и отредактируйте на 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

Мое быстрое решение этой проблемы (в моем случае это был не один файл, а целая структура каталогов) было:

  • переместите файл (ы) в "my_branch" в то место, где они находятся в "his_branch" (git rm / git add)
  • git commit -m "moved to original location for merging"
  • git merge his_branch (на этот раз никаких конфликтов!)
  • переместите файл (ы) в желаемое место (git rm / git add)
  • git commit -m "moved back to final location after merge"

У вас будет два дополнительных коммита в истории.

Но поскольку git отслеживает перемещение файлов, git blame, git logи т.д. будут по-прежнему работать с этими файлами, поскольку коммиты, которые переместили файлы, не изменили их. Так что я не вижу недостатков в этом методе, и его очень легко понять.

Другие вопросы по тегам