Невозможно изменить любой коммит. Вместо этого вы копируете некоторый существующий набор коммитов в новый набор коммитов. Это действительно хорошая новость, потому что это означает, что оригинальные коммиты остаются доступными .
Когда мы создаем B
с помощью интерактивной перебазировки, комментарий A
также будет обновляться, чтобы показать # this is commit B
(при условии, что есть несколько других изменений, отличных от этого файла).
В конкретном примере, который вы показали, не должно быть: вы должны получить конфликт слияния.
Для других дел, конечно, вы правы.
Однако мы хотим оставить комментарий как есть. Примечание: A
будет иметь другой общий хеш из-за наличия другого родителя, но его хэш дерева должен оставаться идентичным предыдущему.
Помните, вы начали с:
... -- C -- A
который я бы нарисовал как:
...--C--A <-- branchname (HEAD)
указывает, что некоторая существующая ветка с именем branchname
указывает на фиксацию C
, а HEAD
присоединяется к A
.
Затем вы запустили git rebase -i <hash-of-C>
или подобное. Это дает вам список того, что нужно сделать, и вы решаете «редактировать» A
. Git сейчас:
отсоединяет ГОЛОВУ от цели перебазировки:
A <-- branchname
/
...--C <-- HEAD
Копирует коммит A
(в этом случае используется точное копирование / перемотка вперед, так что он сам использует A
; вы можете отключить его, если хотите, с помощью --no-ff
, хотя в конец не имеет значения):
A <-- HEAD, branchname
/
...--C
или
A <-- branchname
/
...--C--A' <-- HEAD
(используя --no-ff
для принудительного копирования).
На этом этапе вы должны внести некоторые изменения и запустить git add
и git commit --amend
, чтобы отодвинуть текущий коммит в сторону и заставить HEAD
указать на новый коммит B
, родитель которого - C
. Допустим, вы не использовали --no-ff
; результат тогда:
A <-- branchname
/
...--C--B <-- HEAD
(Если вы использовали --no-ff
, есть еще A'
, висящий без имени; он будет собирать мусор через месяц. Затем нам нужно будет вызвать следующую копию A"
, чтобы сообщить их отдельно, поэтому давайте предположим, что вы не использовали --no-ff
.)
Теперь вы хотите получить файлы из коммита A
и сообщение о коммите из коммита A
и сделать новый коммит. Поскольку branchname
по-прежнему указывает на исходный коммит A
, просто сделайте это:
$ git checkout branchname -- . # assumes you're at the top level of your repo
$ git commit -C branchname # or -c if you want to edit it again
Теперь у вас есть:
A <-- branchname
/
...--C--B--A' <-- HEAD
На этом этапе вы заканчиваете ребаз с помощью git rebase --continue
. Поскольку не осталось коммитов для копирования - вы завершили копирование последнего коммита, A
, что касается перебазирования, - это последний шаг перебазирования, который заключается в удалении имени ветви из исходной цепочки коммитов и переместите его так, чтобы он указывал на тот же коммит, что и HEAD
, при повторном присоединении HEAD
:
A <-- ORIG_HEAD
/
...--C--B--A' <-- branchname (HEAD)
В качестве побочного эффекта, rebase устанавливает ORIG_HEAD
, чтобы запомнить, где branchname
имел обыкновение указывать, поэтому легко убедиться, что все работает правильно, и вы оказались в желаемом состоянии:
git diff ORIG_HEAD HEAD
и если это не так, вы можете git reset --hard ORIG_HEAD
, в результате чего:
A <-- branchname (HEAD)
/
...--C--B--A' <-- ORIG_HEAD
Обратите внимание, что другие команды, включая git reset
, устанавливают ORIG_HEAD
(именно поэтому они поменялись местами здесь). В конце концов, один из этих двух коммитов будет полностью отменен, за исключением записей reflog, и когда срок их действия истечет, недостижимые коммиты действительно исчезнут. Срок действия таких коммитов по умолчанию - 30 дней.