Какой рабочий процесс может смягчить конфликты слияния git, когда файл переименовывается и создается новый файл со старым именем? - PullRequest
2 голосов
/ 25 января 2020

Проблема

  1. У меня root ветвь master и feature ветвление от мастера.
  2. master имеет файл A
  3. feature переместил файл A в B и создал новый файл A
  4. Работа в master с файлом A должна быть объединена в feature файл ветви B
  5. Слияние master в feature приводит к конфликту слияния. master file A пытается объединиться в feature file A, хотя master A и feature A больше не связаны. Как мне сказать git вместо этого объединить master A в feature B?

Шаги Воспроизвести

В консоли :

mkdir git_rename_demo
cd git_rename_demo

git init

echo "LineB1\nLineB2\nLineB3\nLineB4" > A.txt
git add A.txt
git commit -m "Add A"

git checkout -b rename_A_to_B
git mv A.txt B.txt
echo "LineA1\nLineA2\nLineA3" > A.txt
git add A.txt
git commit -m "Moved Old A to B and Added New A"

git checkout master
echo "LineB5\nLineB5" >> A.txt
git add A.txt
git commit -m "Added More LineBs to A"

git checkout rename_A_to_B
git merge master

Сценарий

У меня есть ветвь master и ветвь feature. Файл A на master, содержащий код, который выполняет логическую операцию, связанную с буквой "A" c.

В ветке feature было обнаружено, что файл A не имеет смысла в качестве имени файла, поскольку код больше относится к логике "B" c. Одновременно был написан новый код, который относится к логике "A" c в feature ветви. Чтобы исправить это, файл A был переименован в файл B, что фактически означает, что все исходные логики, связанные с буквой «B» c в файле A, были перемещены в новый файл B. Все новые логики c, связанные с буквой «А», были добавлены в новый файл с именем A, который фактически заменил старый файл A.

Работа над веткой master продолжилась, добавив еще одну букву «Б». logi c в файл A, который все еще существует там. Это файл, который в ветви feature был переименован в B.

Приходит время, когда работа из master должна быть объединена в feature, так как функция будет продолжать разработан отдельно от master до более поздней даты. Объединение master в feature приводит к конфликту, описанному выше. Мы должны продолжать позволять разработчикам, работающим над master, выполнять работу, связанную с буквой «B», с A и иметь возможность объединять этот файл A с работой feature branch B без необходимости разрешения конфликта вручную каждый раз.

Ответы [ 2 ]

1 голос
/ 25 января 2020

TL; DR

Не очень хороший способ справиться с этим, но вы можете сделать это несколько вручную. См. Ниже.

Длинные

Не беспокойтесь слишком о именах ветвей . Не беспокойтесь о коммитах ; Слияние основано на коммитах, а не на именах веток. Имена ветвей просто помогают вам, и Git, находят определенных коммитов. Имя ветви всегда содержит необработанный коммит ha sh ID last коммит в ветви, по определению. Добавление нового коммита состоит из:

  1. Извлечение ветки по имени, чтобы Git знал, какое имя ветки нужно обновить на шаге 3. Это делает коммит подсказки для ветки текущий коммит. Специальное имя HEAD теперь присоединено к имени ветви, поэтому HEAD выбирает current commit, который является последним (или tip ) коммитом в ветви.

  2. Сделайте новый коммит обычным способом. Git создаст коммит, установив его родителя в текущий коммит. Этот новый коммит получит свой уникальный идентификатор ha sh, отличный от идентификатора каждого предыдущего коммита, и отличающийся от каждого будущего коммита.

  3. На этом этапе, сделав новый commit, Git записывает идентификатор new commit ha sh в имя ветви: то, к которому присоединен HEAD. Так что теперь имя ветки указывает на последний (совет) коммит в ветке. Этот новый коммит указывает на то, что раньше было вершиной; ветвь теперь на один коммит длиннее.

Всякий раз, когда вы делаете реальное слияние (есть несколько фальшивых видов слияний), задействуются три коммита: * база слияния , которая является лучшим общим предком двух других коммитов;

ваш текущий коммит (или HEAD), обычно верхушка branch; 1 и коммит, который вы выбираете в командной строке: часто - кончик другой ветки.

Git находит базовый коммит слияния сам по себе. Вы просто называете другой коммит. Если вы хотите заранее узнать, какой коммит является базой слияния, вы можете выполнить:

git merge-base --all other

, где other - это все, что вы планируете предоставить команде git merge, из которой Git выбирает третий коммит.


1 Другой вариант заключается в том, что вы можете находиться в отсоединенном HEAD состоянии, в котором специальное имя HEAD просто содержит необработанный идентификатор ha sh самого коммита. В любом случае, HEAD называет текущий коммит . Он просто делает это , используя имя ветки , когда вы находитесь на ветке. Но обычно вы работаете на ветке, когда запускаете слияние.


Как слияние работает в двух словах, отсюда и ваша проблема

Представьте себе упрощенную временную шкалу фиксации с позже происходит фиксация вправо, в которой мы заменим хеши фиксации одиночными заглавными буквами, чтобы облегчить их обсуждение. Мы можем иметь:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

Здесь имя branch1 находит или указывает на , наш текущий коммит, чей идентификатор ha sh равен J. Имя branch2 указывает на коммит L. Мы хотели бы объединить ветку branch2 в branch1, что на самом деле означает коммит слияния L в коммит J, чтобы создать новый коммит слияния M. Git автоматически находит лучший общий коммит, который является коммитом H: последний коммит, который находится на обеих ветвях.

В Чтобы объединить наши изменения с их изменениями, Git должен выяснить, что мы изменили, и что они изменили. Поскольку каждый коммит содержит полный снимок всех файлов, это относительно просто: Git будет отличать снимок в коммите H от снимка в нашем HEAD коммите J, чтобы увидеть, что мы изменилось. Кроме того, Git будет также отличать снимок в H от снимка в L, чтобы увидеть, что они изменились.

Вы можете запустить эти две команды git diff на вашем собственный:

git diff --find-renames <hash-of-H> <hash-of-J>   # what we changed
git diff --find-renames <hash-of-H> <hash-of-L>   # what they changed

Вот в чем ваша проблема: --find-renames в первом git diff найдет этот файл A в базе слияния, который предположительно также называется A в их коммите, теперь называется B в вашем коммите HEAD. Таким образом, разница будет объединять «А в базе, В в нашем» и сравнивать содержимое. Это то, что мы изменили: изменилось содержимое, а имя файла изменилось.

Второй git diff объединит "A в базе, A в их" чтобы увидеть, что они изменили. Когда слияние идет к объединению изменений, все это будет работать. Объединенные изменения будут такими:

  • все, что мы изменили, если угодно, плюс переименование файла;
  • , что бы они ни изменили, если что.

Git применил бы эти объединенные изменения к снимку в коммите H, который переименовал бы файл.

Но для Git в сначала найдите переименование, Git не должен связывать файл A в коммите H с файлом с именем A в коммите --ours commit (J). Если существует файл с именем A, Git автоматически предполагает , что это «тот же файл».

Когда вы запускаете git diff вручную, как это, вы можете добавить дополнительную опцию Git не , чтобы предположить, что. Когда git merge запускает git diff самостоятельно, он никогда не предоставляет эту опцию.

Обратите внимание, что для всех других файлов все работает нормально. Предположим, что в файле F1 , который имеет одно и то же имя во всех трех коммитах, вы изменили некоторые ранние строки, а они изменили некоторые более поздние строки. Git применяется , оба изменяют копию F1 с H и переходят к следующему файлу.

В какой-то момент, однако, Git может попасть в какой-нибудь файл - может быть F2 - где вы и они изменили те же строки . Git теперь остановится и заставит вас навести порядок.

Git оставляет вам четыре файла, а не только один, с беспорядком в них:

  • Копия файла F2 из базы слияния в Git index .
  • Копия файла F2 из вашего коммита в индексе Git.
  • Копия файла F2 из их коммита в индексе Git.
  • Наконец, есть Лучшее усилие Git - объединение этих измененных файлов в рабочем дереве.

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

Копии, которые есть в индексе Git, трудно увидеть, но вы можете получить все три из них git show или git checkout. Для этого команда git mergetool извлекает файл с именами F в F.BASE, F.LOCAL и F.REMOTE ..., а затем пытается запустить инструмент слияния для этих трех файлов, чтобы создать объединенный файл F, а затем удаляя эти три файла. Так что с mergetool почти все в порядке, но он делает слишком много.

Вы можете прибегнуть к методу git show:

git show :1:F > F.LOCAL    # :1: means the merge base version
git show :2:F > F.OURS     # :2: means our version, like `git checkout --ours`
git show :3:F > F.THEIRS   # :3: means their version, like `git checkout --theirs`

Но в этом случае Git объединяет неправильные файлы , так что это не поможет напрямую. Тем не менее, важно знать.

Выше сказано о том, что идет не так. Вот что вы можете сделать

Вы выполнили:

git merge other

Git, обнаружив три фиксации: базовый, HEAD и другие. Он побежал два git diff с, от базы до HEAD, чтобы увидеть, что вы сделали, и базы для других, чтобы увидеть, что они сделали.

Один из этих двух различий должен был найти переименование, но не , Затем он попытался объединить A из базы с A из локального / --ours коммита и A из другого / --theirs / удаленного коммита.

Вы можете извлечь три версии A и затем объединить их "от руки", вроде как git mergetool делает. Но настоящая хитрость здесь в том, что не вообще хочет A.LOCAL, вы хотите B.LOCAL или B.OURS, в зависимости от того, как вам нравится это называть.

You уже B. МЕСТНЫЙ уже. Это просто называется B. Итак, что вы хотите:

  • извлечь версию файла A на основе слияния: использовать git show :1:A > A.base
  • извлечь версию файла A: использовать git show :3:A > A.other
  • объединить эти три файлы, записывающие результат слияния в файл B.

ОК, первые две точки обозначены просто. Но теперь нам еще нужно объединить три файла. Ну, если у вас есть инструмент слияния, который вы любите, вы можете использовать его напрямую! Если нет, мы можем Git сделать это, так же, как Git делает это автоматически, но мы контролируем входные файлы. Мы просто используем команду:

git merge-file B A.base A.other

, которая объединяет наши существующие B с их A и A.other. Как обычно, могут возникнуть конфликты слияний; теперь они должны быть исправлены обычным способом.

Конечно, Git испортил наш файл A, но мы можем это исправить также:

git checkout --ours A

, который извлекает :2:A из индекса в рабочее дерево, а затем:

git add A

, который удаляет три записи в слотах :1:, :2: и :3: и помещает извлеченный индекс- версия-2 из A в качестве файла для фиксации A.

Если вам приходится делать это часто, вы можете написать его часть (все, кроме разрешения любых реальных конфликтов).

0 голосов
/ 25 января 2020

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

...