В этом вопросе используется Git 2.7.0.windows.1, поэтому некоторые команды git могут быть устаревшими.
Если моя команда git merge не обнаруживает переименованный файл, как я могу указать git вручную объединить изменения в двух файлах, которые должны быть одним файлом, не запуская слияние заново и используя более низкий порог переименования?
Этапы воспроизведения:
git init
echo "//Hello world!" > hw.h
git add . && git commit -m "Initial commit"
git checkout -b someBranch
mv hw.h hw.hpp
echo "//Foobar" > hw.hpp
git add . && git commit -m "Change hw to HPP & change content"
git checkout master
echo "//Boofar" > hw.h
git add . && git commit -m "Change content of hw"
git merge -X rename-threshold=100% someBranch
Вы получите конфликты слияния, но не конфликтующие фрагменты. То есть единственный конфликт/ошибка, которую вы должны получить, это:
CONFLICT (modify/delete): hw.h deleted in branchB and modified in HEAD. Version HEAD of hw.h left in tree.
Automatic merge failed; fix conflicts and then commit the result.
И git status --porcelain покажет:
UD hw.h
A hw.hpp
Обычно при слиянии идеально установить порог обнаружения переименований, достаточно низкий, чтобы переименования обнаруживались. Некоторые люди рекомендуют 5%, например . В моем случае я выполняю массовое слияние (> 1000 файлов и около 9 млн LOC), и мне пришлось поднять порог переименования достаточно высоко, чтобы избежать любых "ложных срабатываний"; буквально на один процент ниже, и я получил огромное количество ложно обнаруженных переименований (я знаю, дублированный код - отстой). При значении, которое я использовал, я получаю лишь небольшое количество пропущенных переименований, что кажется лучшим вариантом.
TL;DR снижение порога переименования для меня не вариант; как я могу, не начиная слияние, сказать git считать hw.h и hw.hpp одним файлом (с конфликтами), а не двумя файлами, как показано выше?
Ответ 1 · рейтинг 4 · torek
Инструменты для этого немного неуклюжи, но они есть.
Вы должны быть уверены, что само слияние остановится перед фиксацией. В вашем случае это происходит автоматически. Для более сложных слияний, когда Git думает, что делает это правильно, но это не так, вы должны добавить --no-commit, но это повлияет на следующие несколько шагов. Мы пока проигнорируем эту проблему.
Далее вам нужно получить все три версии рассматриваемого файла. Поскольку Git остановился из-за конфликта, мы в хорошей форме: все три версии доступны через индекс. Помните, что нас интересуют три версии: merge base, --ours и --theirs.
Если Git обнаружил правильное переименование, все три версии будут в индексе под одним именем. Раз этого не было, то их нет: нам нужно два имени. (В случае "Git считает, что слияние выполнено правильно", базовая версия слияния файла вообще отсутствует в индексе, и нам нужно получить ее каким-то другим способом.) Два имени в вашем случае здесь: hw.h и hw.hpp, так что теперь мы делаем это:
$ git show :1:hw.h > hw.h.base # extract base version
$ git show :2:hw.h > hw.h # extract ours
$ mv hw.hpp hw.h.theirs # move theirs into place
(Переименование не является строго необходимым, оно просто поможет сохранить все правильно и красиво проиллюстрировано.)
Теперь мы хотим объединить один файл с git merge-file:
$ git merge-file hw.h hw.h.base hw.h.theirs
Это использует ваш настроенный merge.conflictStyle, так что то, что находится в объединенном файле, выглядит так, как вы ожидаете, за исключением того, что метки на конфликтующих строках немного отличаются. У меня установлено diff3, поэтому я получаю:
$ cat hw.h
<<<<<<< hw.h
//Boofar
||||||| hw.h.base
//Hello world!
=======
//Foobar
>>>>>>> hw.h.theirs
Теперь вы можете разрешить это как обычно: rm дополнительные файлы .base и .theirs, git add окончательный результат, git rm --cached hw.hpp и git commit. (Это зависит от вас, когда git rm --cached hw.hpp: это безопасно сделать в любой момент времени до фиксации, но после этого вы больше не сможете получить "их" из индекса; см. ниже.)
Обратите внимание, что "наша" и "их" версии также доступны через git show HEAD:path и git show MERGE_HEAD:path. Чтобы получить базовую версию без индекса, нам нужно будет запустить git merge-base HEAD MERGE_HEAD, чтобы найти его хэш-идентификатор (а затем предположить, что существует также одна база слияния1) и git show <hash>:path. Это то, что мы должны сделать, если Git считает, что выполнил слияние правильно.
Также обратите внимание, что если вы действительно хотите - я полагаю, что это будет верно только в том случае, если вы хотите использовать какие-то другие инструменты, которые у вас есть, которые требуют этого - вы можете использовать git update-index для перетасовки записей в указателе, перемещая hw.hpp в slot-3 hw.h, чтобы он действительно отображался как "их" и отображался таким образом в git status. Для этого конкретного примера:
$ printf '100644 bbda177a6ecfe285153467ff8fd332de5ecfb2f8 3\thw.h' |
git update-index --index-info
Хэш здесь получен от git ls-files --stage и является хешем для hw.hpp. (Вам нужен второй шаг, чтобы удалить запись индекса hw.hpp.)
1Используйте git merge-base --all, чтобы найти все базы слияния. Если их несколько, вы можете выбрать одну из них произвольно (это делает -s resolve) или попытаться объединить все базы слияния в виртуальную базу слияния. Чтобы объединить две базы слияния, вы находите их собственную базу слияния и объединяете две базы, как если бы они были ответвлениями, используя эту базу слияния. Рекурсивно и повторяйте по мере необходимости - это то, что Git делает со стратегией -s recursive по умолчанию - до тех пор, пока у вас не будет единой базовой версии файла слияния.