Как переименовать сообщения фиксации в Git?

Я понимаю, что это плохая идея для множества сценариев. Я изучаю Git и экспериментирую. Никакой код в этом упражнении не пострадает.

Я создал такую ​​структуру:

* [cf0149e] (HEAD, branch_2) more editing
* [8fcc106] some edit
|
|  * [59e643e] (branch_2b) branch 2b
| /
|/
|  * [0f4c880] (branch_2_a) branch 2a
| /
|/
*  [a74eb2a] checkout 1
*  [9a8dd6a] added branch_2 line
|
|
| * [bb903de] (branch_3) branch 3
|/
|
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

Теперь я хочу пройти этот график и переименовать различные коммиты, чтобы они имели смысл.

Например:

    * | [a74eb2a] checkout 1
    * | [9a8dd6a] added branch_2 line

renamed to:

    * | [a74eb2a] branch 2 commit 2
    * | [9a8dd6a] branch 2 commit 1

Обратите внимание, что:

[cf0149e] (HEAD, branch_2) more editing
[59e643e] (branch_2b) branch 2b
[0f4c880] (branch_2_a) branch 2a

все разветвлены:

[a74eb2a] checkout 1

Я экспериментировал с

git rebase -i 328454f

затем сменив "pick" на "edit" на коммиты, которые я хотел изменить, а затем запустив

git commit --amend -m "the new message" 

по мере продолжения процесса обновления.

Проблема с этим подходом заключается в том, что после последнего git rebase --continue я заканчиваю двумя новыми коммитами (дубликаты двух, которые я хотел переименовать) на ветке, в которой я оказался включен. Например, если я запустил rebase, в то время как HEAD находился в "branch_2", граф мог выглядеть примерно так:

* [cf0149e] (HEAD, branch_2) more editing
* [8fcc106] some edit
* [3ff23f0] branch 2 commit 2
* [2f287a1] branch 2 commit 1
|  
|    * [59e643e] (branch_2b) branch 2b
|   /
|  /
| |  * [0f4c880] (branch_2_a) branch 2a
| | /
| |/
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
|
| * [bb903de] (branch_3) branch 3
|/
|
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

Другими словами, теперь у меня есть два набора коммитов, которые представляют точно такое же состояние кода.

Все, что я хотел сделать, это изменить сообщения фиксации.

Я также хочу переименовать исходное сообщение из "test" в нечто вроде "Initial version". Я не могу сделать это с помощью git commit --amend -m "Initial version", потому что я заканчиваю в безголовом режиме, если я проверяю, что зафиксировать.

Что я делаю неправильно? Конечно, это не может быть так сложно.

EDIT:
Здесь подход, который я только что пробовал, работает. Конечно, он переписывает историю. Таким образом, за пределами особых случаев это плохая идея. Вот шаги:

Оформить заявку на изменение ветки. Создайте файлы патчей:

git format-patch HEAD~x   // Where x is how far back from HEAD you need to patch

Отредактируйте файлы исправлений, чтобы изменить сообщение фиксации. Теперь reset голова.

git reset --hard HEAD~x   // Same x as before

Применить патчи:

git am 000*

Новые коммиты будут созданы с новыми SHA1. Если какие-либо ветки теперь должны ссылаться на новые коммиты с исправленными сообщениями, вы должны использовать git rebase, чтобы переместить их.

Чтобы использовать мой собственный пример, после применения процедуры исправления я закончил с этим:

* [7761415] (HEAD, branch_2) branch 2 commit 4
* [286e1b5] branch 2 commit 3
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [59e643e] (branch_2b) branch 2b
| | * [0f4c880] (branch_2_a) branch 2a
| |/
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

Итак, у меня есть мои метки branch_2, которые хорошо маркируются. Теперь я хочу переместить branch_2a так, чтобы он отделился от [53d638c] branch 2 commit 2

Оформить заказ branch_2a

git checkout branch_2a

Восстановите его

git rebase 53d638c

Теперь у меня есть:

* [fb4d1c5] (HEAD, branch_2a) branch 2a
| * [7761415] (branch_2) branch 2 commit 4
| * [286e1b5] branch 2 commit 3
|/
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [59e643e] (branch_2b) branch 2b
| * [a74eb2a] checkout 1
| * [9a8dd6a] added branch_2 line
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

Такая же процедура с branch_2b приводит к следующему:

* [ca9ff6c] (HEAD, branch_2b) branch 2b
| * [fb4d1c5] (branch_2a) branch 2a
|/
| * [7761415] (branch_2) branch 2 commit 4
| * [286e1b5] branch 2 commit 3
|/
* [53d638c] branch 2 commit 2
* [52f82f7] branch 2 commit 1
| * [bb903de] (branch_3) branch 3
|/
| * [674e08c] (branch_1) commit 1
| * [7d3db01] added branch_1 line
|/
* [328454f] (0.0.0) test

Именно это я и искал. Не слишком грязно. Опять же, не то, что вы хотели бы сделать за пределами особых случаев. В моем случае я просто играю, чтобы узнать Git, поэтому вышеупомянутое не влияет на реальный репозиторий кода. Приятно знать, что вы можете это сделать, если вам нужно.

Теперь переименуем первую фиксацию.

Ответ 1

На самом деле вы никогда не можете изменить фиксацию. Если вы сделаете минимальное однобитовое изменение где угодно, от написания имени в линии электронной почты до точной секунды временной метки фиксации, вы получите новую, другую фиксацию с новым, другим SHA1 (SHA1 - это "true" имя ", как бы, каждого" объекта "в базе данных git, с объявлением одного из четырех типов объекта).

Одна из неизменяемых частей коммита - это "родительский коммит", который создает цепочку назад от самого последнего - совершает к самому старым-фиксации.

Следовательно, то, что git rebase -i делает, - это сделать новую цепочку фиксации, причем каждая фиксация в цепочке имеет одинаковое содержимое/эффекты как исходное коммит плюс или минус любые изменения, которые вы делаете во время взаимодействия. Когда все будет сделано, он удаляет ярлык (как бы липкую заметку) с конца старой цепочки фиксации и вставляет ее в конец новой цепочки фиксации. Он начинается с того, что копия самой старой фиксации будет изменена/изменена. У этого есть переустановленный родитель (который может быть одним и тем же родительским фиксатором, как в исходной цепочке, или с другим родителем: в любом случае это нормально, потому что это новая фиксация). Затем он делает копию следующего в старой цепочке, но указывает на новую цепочку. Он повторяется до конца старой цепи.

Вот почему все ваши другие ветки теперь не зависят от вашей переустановленной ветки. Они должны быть, потому что они используют старые идентификаторы фиксации. Если вы хотите, чтобы они отделили вашу новую дочернюю ветку, вы должны войти в каждую из них и переустановить их.

Существует мощная команда Swiss-Army-chainaw-ish, git filter-branch, которую вы можете использовать, чтобы сделать очень большую серию "повторных множеств коммитов", делая все новые коммиты, которые имеют (в основном) то же содержимое, что и оригиналы ", вроде git rebase на стероидах, которые вы можете использовать для этой цели. (Запустите его с --all, чтобы воздействовать на все ветки.) Конечно, поскольку он действительно переделывает все коммиты, вы завершаете репо, которое в основном не связано с исходным репо.

Жестко переписать начальную фиксацию (не невозможно), потому что у нее нет родителя, поэтому регулярный rebase просто не сделает этого. 1 (filter-branch can.) Поскольку эти изменения Идентификаторы SHA1, и каждый, кто клонировал ваше репо, использует/зависит от тех, что обычно является функцией, которую вы не можете переваривать с такими фиксациями. Когда вы знаете, что никто не зависит от определенного набора коммитов, вы можете rebase все, что вам нравится, но вы не вернетесь к первоначальной фиксации, потому что это будет в общих частях репо. Это довольно редко (хотя, конечно, не "никогда" ), что все это на пути к "первоначальной фиксации" - это все ваши личные вещи.


1 Так как я написал это, git rebase научился копировать корневую фиксацию как начальную фиксацию. 2 Используя обычный синтаксис git rebase, вам придется назовите родительскую фиксацию корня, и, конечно же, нет родителя (что делает его "корнем" на графике). Поэтому rebase использует --root как аргумент для покрытия этого случая.

2 Возможно иметь несколько корней; используйте git checkout --orphan, например, за которым следует git commit, чтобы создать новый комманд root. Это немного необычно для них, хотя сам источник git хранится в репозитории git с несколькими корнями.