Ответ 1
Во-первых, короткая заметка: вы всегда можете попробовать слить, а затем вернуть ее, чтобы посмотреть, что она делает:
$ git checkout master
Switched to branch 'master'
$ git status
(убедитесь, что он очищает резервную копию неудачного слияния, когда изменения не являются забавными)
$ git merge feature
Если слияние не выполняется:
$ git merge --abort
Если автоматическое слияние выполнено успешно, но вы еще не хотите его сохранять:
$ git reset --hard HEAD^
(Помните, что HEAD^
является первым родителем текущего коммита, а первым родителем слияния является "то, что было до слияния". Таким образом, если слияние сработало, HEAD^
является фиксацией непосредственно перед слияние.)
Здесь простейший рецепт для определения того, что переименовывает git merge
будет автоматически обнаружен.
-
Убедитесь, что
diff.renamelimit
1 -0
, аdiff.renames
-true
:$ git config --get diff.renamelimit 0 $ git config --get diff.renames true
Если они еще не установлены таким образом, установите их. (Это влияет на шаг
diff
ниже.) -
Выберите, с какой ветвью вы сливаетесь, и из которой вы сходите. То есть, вы скоро сделаете что-то вроде
git checkout master; git merge feature
; нам нужно знать эти два имени. Найдите базу слияния между ними:$ into=master from=feature $ base=$(git merge-base $into $from); echo $base
Вы должны увидеть около 40 символов SHA-1, например
ae47361...
или что-то еще. (Не стесняйтесь вводитьmaster
иfeature
вместо$into
и$from
всюду здесь. Я использую переменные так, что это "рецепт" вместо "пример".) -
Сравните базу слияния как с
$into
, так и с$from
, чтобы определить, какие файлы обнаружены как "переименовать":$ git diff --name-status $base $into R100 fileB fileB.renamed $ git diff --name-status $base $from R100 fileC fileD
(Возможно, вы захотите запустить эти различия с выходом, сохраненным в двух файлах, а затем ознакомиться с файлами позже. Примечание: вы можете получить эффект третьего diff с помощью специального синтаксиса master...feature
: три точки здесь означает "найти базу слияния".)
Два выходных раздела имеют список файлов A
dded, D
eleted, M
odified, R
enamed и т.д. (в этом примере есть только два переименования со 100% совпадениями).
Так как $into
есть master
, первый список - это то, что git думает, что уже произошло в master
. (Это изменения git "хочет сохранить", когда вы объединяетесь feature
.)
Между тем $from
есть feature
, поэтому второй список - это то, что git думает, что произошло в feature
. (Это изменения git хочет "теперь добавить к master
", когда вы выполните слияние.)
На этом этапе вам нужно выполнить кучу работы:
- Файлы, отмеченные
R
, git, будут обнаружены как переименованные. - Если два списка
R
одинаковы в обеих ветвях, вы, возможно, все хорошо (но читайте в любом случае). Если в первом списке естьR
, которые не находятся во втором... ну, см. Ниже. - Когда вы запустите
git checkout master; git merge feature
(илиgit checkout $into; git merge $from
), git выполнит переименования, показанные во втором списке, чтобы "добавить эти изменения" вmaster
. - В любом случае сравните это с файлами, которые вы хотите git обнаружить как переименованные. Найдите записи
D
иA
, которые вы хотите отобразить как записиR
: они возникают, когда в одной из ветвей вы не только переименовали этот файл, но также изменили содержимое настолько, что git больше не обнаруживает переименование.
Если во втором списке не отображается все, что вы хотите увидеть, вам придется помочь git выйти. См. Еще более подробное описание ниже.
Если первый список имеет переименование, а не второе, это может быть совершенно безобидным или может вызвать "ненужный" конфликт слияния и пропущенный шанс для реального слияния. git предполагает, что вы намереваетесь сохранить это переименование, а также посмотрите, что произошло в слиянии - из ветки ($from
или feature
в этом случае). Если исходный файл был изменен там, git попытается внести изменения оттуда в переименованный файл. Это, вероятно, то, что вы хотите. Если исходный файл там не был изменен, git не имеет ничего, чтобы вставить его и оставит файл в покое. Это также, вероятно, то, что вы хотите. "Плохой" случай - это снова необнаруженное переименование: git считает, что исходный файл был удален в ветке feature
, и был создан новый файл с другим именем.
В этом "плохом" случае git даст вам конфликт слияния. Например, он может сказать:
CONFLICT (rename/delete): newname deleted in feature and renamed in HEAD.
Version HEAD of newname left in tree.
Automatic merge failed; fix conflicts and then commit the result.
Проблема здесь не в том, что git сохранил файл под своим новым именем в master
(мы, вероятно, хотим этого); что git, возможно, упустил шанс объединить изменения, сделанные в ветке feature
.
Хуже - и это может быть классифицировано как ошибка - если новое имя встречается в слиянии - из ветки feature
, но git считает его новым файлом, git оставляет нас только слиянием - в версию файла в дереве работ. Выпущенное сообщение одинаково. Здесь я сделал еще несколько изменений в master
, чтобы переименовать fileB
в fileE
, а на feature
убедиться, что git не обнаружит изменения как переименование:
$ git diff --name-status $base master
R100 fileB fileE
$ git diff --name-status $base feature
D fileB
R100 fileC fileD
A fileE
$ git checkout master; git merge feature
CONFLICT (rename/delete): fileE deleted in feature and renamed in HEAD.
Version HEAD of fileE left in tree.
Automatic merge failed; fix conflicts and then commit the result.
Обратите внимание на потенциально вводящее в заблуждение сообщение, fileE deleted in feature
. git печатает новое имя (версия имени master
); что имя, которое, по его мнению, "хочет" увидеть. Но это файл fileB
, который был "удален" в feature
, заменен на совершенно новый fileE
.
(git-imerge
, упомянутый ниже, может справиться с этим конкретным случаем.)
1 Также существует merge.renameLimit
(записанный в нижнем регистре limit
в источнике, но эти переменные конфигурации не чувствительны к регистру), которые вы можете установить отдельно. Установка этих значений в 0 сообщает git использовать "подходящее значение по умолчанию", которое с годами изменилось по мере того, как процессоры стали быстрее. Если отдельный предел переименования слияния не установлен, git использует ограничение переименования diff и снова подходящее значение по умолчанию, если оно не установлено или равно 0. Если вы установите их по-другому, то слияние и diff будут определять переименования в разных случаях.
Вы также можете установить "порог переименования" в рекурсивном слиянии с -Xrename-threshold=
, например, -Xrename-threshold=50%
. Использование здесь такое же, как для опции git diff
-M
. Эта опция впервые появилась в git 1.7.4.
Скажем, вы находитесь на ветке master
, и вы делаете git merge 12345467
или git merge otherbranch
. Здесь git делает:
-
Найдите базу слияния:
git merge-base master 1234567
илиgit merge-base master otherbranch
.Это дает идентификатор commit. Позвольте называть этот ID
B
для "Base". git теперь имеет три конкретных идентификатора фиксации:B
, база слияния; идентификатор фиксации вершины текущей веткиmaster
; и идентификатор фиксации, который вы ему дали,1234567
или кончик веткиotherbranch
. Позвольте просто нарисовать их в терминах графа фиксации, для полноты; скажем, это выглядит так:A - B - C - D - E <-- master \ F - G - H - I <-- otherbranch
Если все пойдет хорошо, git создаст коммандную фиксацию с
E
иI
в качестве двух родителей, но мы хотим сконцентрироваться здесь на полученном дереве работы, а не на графике фиксации. -
Учитывая эти три коммиты (
B
E
иI
), git вычисляет два diff, a lagit diff
:git diff B E git diff B I
Первый - это набор изменений, сделанных на
branch
, а второй - набор изменений, сделанных наotherbranch
, в этом случае.Если вы запустите
git diff
вручную, вы можете установить "порог подобия" для обнаружения переименования с помощью-M
(см. выше, чтобы установить его во время слияния). git default merge устанавливает автоматическое определение переименования на 50%, это то, что вы получаете без опции-M
, аdiff.renames
-true
.
Если файлы "достаточно схожи" (и "точно такое же" всегда достаточно), git обнаружит переименования:
$ git diff B otherbranch # I tagged the merge-base `B`
diff --git a/fileB b/fileB.txt
similarity index 71%
rename from fileB
rename to fileB.txt
index cfe0655..478b6c5 100644
--- a/fileB
+++ b/fileB.txt
@@ -1,3 +1,4 @@
file B contains
several lines of
stuff.
+changeandrename
(В этом случае я просто переименован из fileB
в fileB.txt
, но обнаружение также работает в каталогах.) Обратите внимание, что это удобно представить в виде git diff --name-status
output:
$ git diff --name-status B otherbranch
R071 fileB fileB.txt
(Я должен также отметить здесь, что у меня diff.renames
установлено значение true
и diff.renamelimit = 0
в моей глобальной конфигурации git.)
- Git теперь пытается объединить изменения от
B
доI
(наotherbranch
) в изменения отB
доE
(наbranch
).
Если git может обнаружить, что lib/a.txt
переименован из a.txt
, он будет их подключать. (И вы можете просмотреть, будет ли это делать, выполнив git diff
.) В этом случае результат автоматического слияния, вероятно, будет тем, что вы хотите, или достаточно близко.
Если нет, это не будет.
Когда автоматическое обнаружение переименования выходит из строя, существует способ разбить коммиты (или, может быть, они уже достаточно разбиты) поэтапно. Предположим, например, что в последовательности F
G
H
I
один шаг (возможно, G
) просто переименовывает a.txt
в lib/a.txt
, а другие шаги (F
, H
и/или I
) делают так много других изменений в a.txt
(под любым именем), чтобы обмануть git, не осознавая, что файл был переименован. Здесь вы можете увеличить количество слияний, чтобы git мог "видеть" переименование. Предположим для простоты, что F
не изменяет a.txt
и G
переименовывает его, так что diff от B
до G
показывает переименование. Мы можем сделать первое слияние commit G
:
git checkout master; git merge otherbranch~2
Как только это слияние завершено и git переименовал из a.txt
в lib/a.txt
в дереве для нового коммита слияния в ветке branch
, мы делаем второе слияние для ввода commits H
и I
:
git merge otherbranch
Это двухступенчатое слияние заставляет git "делать правильную вещь".
В самом крайнем случае инкрементная последовательность слияния с фиксацией по фиксации (что было бы крайне болезненно для выполнения вручную) поднимет все, что можно было бы подобрать. К счастью, кто-то уже написал для вас эту программу "постепенного слияния": git-imerge
. Я не пробовал это, но это Очевидный ответ на трудные случаи.