Как поменять порядок двух родителей на Git commit?
Согласование слияния - это фиксация с по меньшей мере двумя родителями. Эти родители находятся в определенном порядке.
Если я нахожусь в ветке master
, и я сливаюсь в ветке feature
, я создаю новую фиксацию с ее первым родителем, являющимся фиксацией из master
, а вторая фиксация является фиксацией из feature
. Этот порядок особенно очевиден, запустив git log --first-parent
.
* The merge commit
|\
| * The commit from `feature`
* | The commit from `master`
Скажем, теперь я понимаю, что порядок неправильный: я намеревался объединить ветвь master
в feature
, запустив git checkout feature; git merge master
. Я хочу поменять порядок родителей на фиксацию слияния, но я не хочу перебирать проблемы с повторением всех конфликтов слияния. Как я могу это сделать?
* The merge commit
|\
* | The commit from `feature`
| * The commit from `master`
Ответы
Ответ 1
Фактически, там действительно классная команда, которую я недавно узнал, будет делать именно то, что вы хотите:
git commit-tree -p HEAD^2 -p HEAD^1 -m "Commit message" "HEAD^{tree}"
Это создаст новую фиксацию, основанную на том, что в настоящее время HEAD, но притворится, что это родители HEAD ^ 2, HEAD ^ 1 (обратите внимание, что это обратный порядок).
git -commit-tree печатает новую ревизию как вывод, поэтому вы можете объединить ее с git - reset -hard:
git reset --hard $(git commit-tree -p HEAD^2 -p HEAD^1 -m "New commit message" "HEAD^{tree}")
Ответ 2
Один из способов заключается в моделировании слияния. Итак, как мы можем это сделать?
Предположим, что у вас есть что-то вроде следующего графика фиксации:
* (master) Merge branch 'feature'
|\
| * (feature) feature commit
* | master commit
. .
. .
. .
Сохраните изменения
Мы хотим объединить master
в feature
, но мы хотим сохранить изменения, поэтому сначала мы переключаемся на master
, из которого мы "вручную" обновляем нашу ссылку HEAD до точки feature
, а не изменение рабочего дерева.
git checkout master
git symbolic-ref HEAD refs/heads/feature
Команда symbolic-ref
похожа на git checkout feature
, но не касается рабочего дерева. Таким образом, все изменения от master
остаются.
Отменить старое слияние
Теперь мы имеем все изменения от слияния в рабочем дереве. Поэтому мы продолжаем "отменять" слияние путем сброса master
. Если вам не комфортно потерять ссылку на фиксацию слияния, вы можете создать временный тег или ветвь.
(Если вы хотите сохранить сообщение о фиксации, сейчас самое подходящее время для его копирования где-нибудь сохранить.)
# Optional
git tag tmp master
git branch -f master master^
Теперь ваше дерево фиксации должно выглядеть так же, как перед слиянием.
Подделка слияния
И вот идет хакерская часть. Мы хотим обмануть git, полагая, что мы сейчас сливаемся. Мы можем добиться этого, вручную создав файл MERGE_HEAD
в папке .git
, содержащий хэш коммита, который мы хотим объединить.
Итак, мы делаем это:
git rev-parse master > .git/MERGE_HEAD
Если вы используете git bash, git теперь скажет вам, что он в настоящее время находится в процессе слияния.
Чтобы завершить наше слияние, нам просто нужно commit
.
git commit
# Enter your commit message
И все. Мы воссоздали наше слияние, но с замененными родителями. Итак, вы фиксируете историю, теперь должны выглядеть так:
* (feature) Merge branch 'master'
|\
| * (master) master commit
* | feature commit
. .
. .
. .
Если вам нужна дополнительная информация, не стесняйтесь спрашивать.
Ответ 3
Обновить. Это никогда не бывает так просто, не так ли?
Я признал недостаток исходных инструкций, которые я предложил: при выполнении аргументов checkout
с аргументами пути вы можете ожидать, что файл будет удален из вашего рабочего пути, потому что он не находится в проверке, которую вы проверяете вне; но тогда вы можете быть удивлены...
Как и при любой перезаписи истории, стоит отметить, что вы, вероятно, не должны этого делать (используя какой-либо из этих методов, из любого из этих ответов, включая этот), на любое слияние, которое вы уже нажали. Тем не менее...
Предыдущие ответы в порядке, но если вы хотите избежать использования (или необходимости знать) команд сантехники или других внутренних действий git - и если это более важно для вас, чем однострочное решение, такое как решение Jared - тогда вот альтернатива:
Общая структура этого решения похожа на zeeker's, но где он использует команды сантехники для управления HEAD или "трюки" git для завершения слияния, мы просто используем фарфор.
Итак, как и раньше,
* (master) Merge Commit
| \
| * (feature) fixed something
* | older commit on master
Пусть начнется:
1) Пометьте старое слияние
Мы будем использовать этот тег. (Если вы хотите вместо этого записать сокращенный SHA1, это сработает, но дело здесь в том, чтобы сделать его удобным для пользователя, поэтому...)
git tag badmerge master
Теперь мы имеем
* (master) [badmerge] Merge Commit
| \
| * (feature) fixed something
* | older commit on master
2) Возьмите слияние из мастер-истории
git branch -f master master^
Достаточно просто:
* [badmerge] Merge Commit
| \
| * (feature) fixed something
* | (master) older commit on master
3) Начните новое слияние
git checkout feature
Если мы просто запустим git merge master
сейчас, мы знаем, что слияние не удастся; но мы не хотим повторять ручное разрешение конфликтов. Если бы у нас был способ наложить данные из badmerge на наш новый комманд слияния, мы бы все установили... и мы делаем!
Чтобы запустить слияние (a), не создавая состояние конфликта, которое должно быть очищено, но (b) оставив фиксацию слияния, чтобы мы могли "исправить" его:
git merge --no-commit --strategy=ours master
4) Наложение уже разрешенных/объединенных данных из badmerge
Удостоверившись, что мы находимся в корневом каталоге репо, мы могли бы сделать git checkout badmerge -- .
(обратите внимание, что мы предоставили путь (.) до git checkout
, поэтому он обновляет только рабочее дерево и индекс); но это может быть проблемой.
Если слияние привело к удалению файлов (но они находятся в нашем рабочем дереве прямо сейчас), то указанная выше команда не избавится от них. Поэтому, если мы не уверены, у нас есть пара вариантов, чтобы быть в безопасности от этой возможности:
Мы могли бы сначала очистить рабочее дерево... но нам нужно быть осторожным, чтобы не уничтожить папку .git, и может потребоваться специальная обработка для чего-либо в нашем файле ignore.
В простом случае -.git - единственный. файл, ничего не игнорируется - мы могли бы сделать
rm -rf *
git checkout badmerge -- .
Или, если это кажется слишком рискованным, другой подход состоит в том, чтобы пропустить rm
, сделать git checkout badmerge -- .
, а затем использовать diff от badmerge, чтобы увидеть, нужно ли что-то очищать.
5) Завершите слияние
git commit
git tag -d badmerge
Ответ 4
Вдохновленный этим ответом, я придумал это:
git replace -g HEAD HEAD^2 HEAD^1 &&
git commit --amend &&
git replace -d [email protected]{1}
Первые команды переключают двух родителей в нечто, называемое заменяющим реф, но сохраняют его только локально, и люди называют это хаком.
Вторая команда создает новый коммит.
Третья команда удаляет более старую заменяемую ссылку, поэтому она не портит другие коммиты в зависимости от этого коммита.
Ответ 5
Вы можете использовать git replace --graft
(например, git replace --graft HEAD HEAD^2 HEAD^1
) вместо переписывания истории. Он создает заменяющий коммит и сохраняет своих потомков без изменений.
Редактировать: созданные замены не являются общими по умолчанию. Вам необходимо настроить пульты, такие как push = refs/replace/*:refs/replace/*
а также указать своим участникам установить fetch = refs/replace/*:refs/replace/*
.
Смотрите также: