git merge: застосуйте зміни до коду, який перемістився до іншого файлу


132

Я зараз намагаюся зробити досить приємний маневр злиття git. Одна з проблем, з якою я стикаюся, полягає в тому, що я вніс деякі зміни в код у своїй гілці, але мій колега перемістив цей код у новий файл у своїй гілці. Тож коли я це зробив git merge my_branch his_branch, git не помітив, що код у новому файлі такий самий, як у старому, і тому жодна з моїх змін там не спостерігається.

Який найпростіший спосіб знову застосувати свої зміни до коду в нових файлах. У мене не буде занадто багато проблем з з’ясуванням, які коміти потрібно повторно застосувати (я можу просто використовувати git log --stat). Наскільки я можу сказати, немає можливості отримати git, щоб повторно застосувати зміни до нових файлів. Найпростіше, що я зараз бачу, - це вручну повторно застосувати зміни, що не виглядає як гарна ідея.

Я знаю, що git розпізнає краплі, а не файли, тому, безумовно, повинен бути спосіб сказати це, "застосувати цю точну зміну коду з цього комітету, за винятком не того місця, де він був, але де він знаходиться зараз у цьому новому файлі".


2
Не зовсім те ж саме, але ось таке запитання з хорошою anwsers , які можуть застосовуватися: stackoverflow.com/questions/2701790 / ...
Маріано Desanze

4
Жодна з відповідей не описує, чому git не може автоматично виконувати злиття, як це. Я думав, що це повинно бути досить розумним, щоб виявити перейменування та виконати відповідне злиття автоматично?
Джон

Можливо, це було не так, коли було задано питання, але сучасні версії git (я використовую 1.9.5) можуть об'єднати зміни в перейменованих та переміщених файлах. Існує навіть --rename-thresholdваріант підправити необхідну кількість подібності.
Тодд Оуен

1
@ToddOwen, який спрацює, якщо ви робите реконструкцію ванілі поза верхньою гілкою, але все одно у вас виникнуть проблеми, якщо ви вибираєте вишню або переносите ряд змін, які можуть не включати комісію, яка перейменовує файли .
GuyPaddock

Чи / це взагалі була проблемою, якщо спочатку зробити ребауз?
hbogert

Відповіді:


132

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

Скажіть, що ви змінили 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.

Сподіваюся, це зрозуміло. Ця відповідь приходить пізно, але це може бути корисним комусь іншому.


2
Це дуже багато роботи, але я думаю, що це має працювати в принципі. Я думаю, що вам буде більше удачі з об'єднанням, ніж перезавантаження.
asmeurer

7
Це рішення передбачає лише основні команди git, на відміну від редагування патчів або використання patch(що передбачає також багато роботи), і саме тому я вважав, що це може бути цікаво показати. Також зауважте, що мені було потрібно більше часу, щоб написати відповідь, ніж реально застосувати зміни, в моєму випадку. На якому кроці ви б рекомендували використовувати об'єднання? і чому? Єдина відмінність, яку я бачу, полягає в тому, що відміняючи, я приймаю тимчасове зобов’язання, яке пізніше відкидається (фіксується MV), що неможливо лише злиттями.
coredump

1
З допомогою rebase ви маєте більший шанс вирішити конфлікти злиття, тому що ви вирішуєте кожну комісію, тоді як при злитті ви маєте справу зі всім одразу, тобто деякі зміни, які могли статися з ребазою, не мали б злиття. Що б я зробив - це злити, а потім перемістити об'єднаний файл вручну. Можливо, я неправильно зрозумів ідею вашої відповіді.
asmeurer

4
Я додав кілька дерев ASCII, щоб уточнити підхід. У такому випадку реєструючи злиття та ребазування: я хочу зробити всі зміни на "original.txt" у своїй гілці та застосувати їх до "copy.txt" у головній гілці, оскільки чомусь "оригінал". txt 'було скопійовано (а не переміщено) у' copy.txt 'у якийсь момент. Після цієї копії "original.txt" також може перетворитися на головну гілку. Якщо я об'єднав безпосередньо, мої локальні зміни в original.txt будуть застосовані до модифікованого original.txt в головній гілці, що було б важко об'єднати. З повагою
coredump

1
Так чи інакше, я вважаю, що це рішення спрацює (хоча, на щастя, у мене немає ситуації спробувати його зараз), тому поки що я позначу це як відповідь.
асмеурер

31

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

За винятком цього, єдиний спосіб, коли він буде працювати автоматично, це якщо виявлення перейменування git може зрозуміти, що старі та нові файли - це одне і те ж - це здається, що вони насправді не є у вашому випадку, а лише частинка їх. Це правда, що git використовує краплі, а не файли, але крапка - це лише вміст цілого файлу без додавання файлу та метаданих. Отже, якщо у вас є фрагмент коду, переміщений між двома файлами, вони насправді не є однією крапкою - решта вмісту блобу відрізняється, лише шматок спільного.


1
Ну, це найкраща відповідь поки що. Я не міг розібратися, як змусити git format-patchпрацювати на вчинку. Якщо я це зробити git format-patch SHA1, він генерує цілу купу файлів патчів за всю історію. Але я здогадуюсь, git show SHA1 > diff.patchбуде так само добре.
асмеурер

1
@asmeurer: скористайтеся -1опцією. Нормальний режим роботи для формату-патча - це, наприклад origin/master..master, діапазон редагування , тому ви можете легко підготувати серію патчів.
Каскабель

1
Власне, ще одна примітка. git applyі git amзанадто вибагливі, тому що вони хочуть однакових номерів рядків. Але в даний час я маю успіх з командою UNIX patch.
асмеурер

24

Ось злиття рішення зустрічаючи злиття конфлікту з перейменуванням і редагувати і дозволяти його 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.

Створіть перейменування та відредагуйте головний:

$ 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

1
У цьому рішенні мені здається, що перший крок, злиття, яке переривається, корисний лише для з'ясування, які файли були перейменовані та відредаговані віддалено. Якщо ви їх знаєте заздалегідь. Ви можете пропустити цей крок, і в основному рішенням є просто перейменувати файли вручну локально, а потім об'єднати та вирішити конфлікти як завжди.

1
Дякую. Моя ситуація полягала в тому, що я перемістився B.txt -> C.txtі A.txt -> B.txtз git mv, і git не міг автоматично правильно зіставити конфлікти злиття (отримував конфлікти злиття між старим B.txtі новим B.txt). За допомогою цього методу тепер конфлікти злиття між правильними файлами.
cib

Це працює лише тоді, коли весь файл переміщений, але git, як правило, визначає цю ситуацію автоматично. Складна ситуація, коли переміщується лише частина файлу.
Робін Грін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.