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 исходных файла слияния.
После сбоя слияния из-за "удаленного файла", который вы понимаете, был переименован и отредактирован:
- Вы прерываете слияние.
- Зафиксируйте переименованные файлы в вашей ветке.
- И снова слить.
Прохождение:
Создайте файл.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
и т.д. будут по-прежнему работать с этими файлами, поскольку коммиты, которые переместили файлы, не изменили их. Так что я не вижу недостатков в этом методе, и его очень легко понять.