Как добавить дополнительных родителей в старые git коммиты?

У меня есть проект, содержащий две ветки: master и gh-pages. Это по существу два разных проекта, где проект gh-pages зависит от мастер-проекта (а не наоборот). Подумайте об этом как "master содержит исходный код, gh-pages содержит двоичный код, созданный из этих исходных файлов". Периодически я принимаю изменения, которые были накоплены в master, и совершает новую фиксацию ветки gh-страниц с сообщением фиксации "Ввод в строку с master commit xxxxxxxx".

how the network graph currently looks

Через некоторое время я понял, что было бы неплохо, если бы gh-страницы зафиксировали "Ввод в соответствие с master commit xxxxxxxx", на самом деле xxxxxxxx был его родителем в репозитории git. Как это (плохое искусство MSPaint):

how it would ideally look

Есть ли способ сделать репозиторий похожим на второе изображение выше? Я знаю, как делать новые коммиты, следуя этому шаблону: я могу сделать "git merge -s ours master" (который устанавливает родителей в противном случае пустым фиксацией), а затем "git commit -amend adv550.z8" ( где adv550.z8 - это бинарный файл, который фактически меняется). Но позволяет ли git вернуться во времени и добавить новых родителей в старые коммиты?

Я абсолютно согласен "git push -f" и сдуть текущую историю моего репозитория Github, как только я получу свое местное репо, выглядящее правильно. Вопрос в том, могу ли я получить локальное репо, правильно выглядящее?


СОЕДИНЕННЫЕ ЛЕТЫ ДОЛЖНЫ ДОБАВИТЬ: Я в конце концов отказался от попыток сделать историю git gh-pages похожей на это; Я решил, что это слишком большая работа для нулевого выигрыша. Моя новая практика заключается в том, чтобы агрессивно скворовать фиксации на gh-pages, потому что сохранение этих сообщений фиксации действительно не имеет значения в моем случае. (Это просто длинная строка "Ввести в линию с фиксацией мастер...", но ни один из них не является исторически интересным.) Однако, если мне нужно было сделать это снова, я бы выслушал ответы, которые сказали

git merge $intended_parent_1 $intended_parent_2
git checkout $original_commit -- .
git commit --amend -a -C $original_commit

Ответ 1

Вы не можете вернуться вовремя и изменить существующие коммиты. Даже когда вы делаете что-то вроде git commit --amend, вы фактически не меняете фиксацию; вы создаете новый в одном месте в дереве со всем содержимым из оригинала (плюс ваши изменения). Вы заметите, что хеш фиксации изменяется после --amend, а оригинал все еще присутствует в вашем репо - вы можете вернуться к нему с помощью git reflog.

С другой стороны, вы можете вернуться во времени и создать альтернативный юниверс. По существу, вы вернетесь в свою точку разворота gh-pages и воссоздаете все это (например, git cherry-pick или что-то еще) в качестве параллельной ветки. Хэши будут меняться, потому что коммиты - это разные объекты.

(Мне любопытно, почему ваш репо настроен как отдельные ветки, а не отдельные каталоги в одной ветки. Похоже, процесс сборки кода → стал бы утомительным.)

Ответ 2

git replace --graft $original_commit \
    $(git show --pretty=%P $original_commit) \
    $additional_parent

Объяснение: git replace --graft позволяет реагировать на роли, которые являются родителями данного фиксации. Это работает, записывая новую фиксацию с соответствующими изменениями и записывая замену ref, которая заставляет git искать новый комманд вместо исходного при каждом доступе.

git show --pretty=%P $original_commit просто перечисляет исходных родителей.

Ответ 3

Это ваши волшебные слова:

git merge origin/master --strategy ours

Выполните это в ветки gh-pages, чтобы записать отношение к master, не изменяя ничего. Это работало для меня, когда я создал ветвь gh-pages как --orphan, как в этом ответе.

Вы можете даже --amend выполнить это пустое объединение, чтобы внести изменения, которые вы действительно хотите сделать, на свои "страницы". См. https://github.com/krlmlr/pdlyr/network для фактического примера в GitHub.

Ответ 4

Вы можете переписать фиксации, но для этого требуется переписать всю ветку, начиная с первой фиксации, для которой требуется дополнительный родительский элемент до кончика ответвления. Is (например, с rebases) - новая ветвь, которая имеет только одно содержимое.

Фокус в том, чтобы использовать commit-tree, которое берет все (данные коммиттера/автора, отметки времени, сообщение фиксации, список родителей), хотя и довольно непрактично. Затем немного скриптов делает работу.

Ответ 5

@румпель дал правильный ответ. Позвольте мне поделиться фрагментами, которые я использую.

Во-первых, я создал дочернюю ветвь master:

git checkout --orphan master
git commit --allow-empty -m 'Init'

Мой пример использования немного отличается, сайт, который я хочу, находится в директории _site на ветке develop. Поэтому я:

git checkout master
git rm -rf .
git read-tree develop^{tree}:_site
git checkout -- .
git commit -m "Update from develop SHA1:$(git rev-parse --short develop) ($(git log -1 --format=%cd develop))"
git replace --graft @ $(git show --pretty=%P @ | head -n1) develop

Обратите внимание, что в последней команде мне пришлось фильтровать | head -n1, так как мой git выводит намного больше, чем просто родителей.