Я думаю, что это более объяснимо с помощью картинок, хотя моя способность к ASCII-искусству заканчивается через несколько --amends
.Хитрость заключается в том, чтобы понять, что git commit --amend
вместо git commit
без --amend
изменяет родительский хеш , сохраненный в новом коммите.
Нормальный git commit
замораживает содержимое индекса в дереве и делает новый коммит, используя новое дерево, вас как автора и коммиттера, ваше сообщение журнала и текущий коммит в качестве родителя нового коммита:
...--F--G <-- [you were here]
\
H <-- branch (HEAD) [you are here now]
Затем мы делаем новый коммит I
, после выпрямления чертежа:
...--F--G--H--I <-- branch (HEAD)
и нового коммита J:
...--F--G--H--I--J <-- branch (HEAD)
и т. Д.
Используя git commit --amend
, мы делаем новый коммит H
как обычно , за исключением , в котором родительский из H
равен F
вместо G
:
G [you were here]
/
...--F--H <-- branch (HEAD)
Затем, делая I
, мы делаем как обычно за исключением , что родительский из I
равен F
вместо H
:
G [you were here]
/
...--F--H [you were here too]
\
I <-- branch (HEAD)
и т. Д.
Если представить, что в этот момент выполняется git log
с коммитом I
, указывающим назад на коммит F
- вы увидите коммит I
, затем коммит F
, затем E
и так далее.Коммиты G
и H
будут невидимы для gitk
или git log
(но будут отображаться в выводе git reflog
, поскольку это не следует родительским цепочкам).
После 100 таких операцийУ меня закончились письма о коммитах, и я не могу привлечь безумного поклонника коммитов, указывающих на F
, но вы можете их себе представить;или я могу нарисовать всего 9 таких коммитов, от G
до O
:
GH
| I
|/ J
...--F==K
|\ L
| M
ON
Каждый из этих коммитов имеет дерево и сообщение , котороеты хочешь.Что не так с каждым из этих коммитов, за исключением самого G
, так это то, что у него неправильный parent: вы хотите, чтобы G
имел F
в качестве родителя (что он и делает), но тогда вы бы хотели, чтобы H
имел G
в качестве родителя (чего у него нет).
Это означает, что вы должны скопировать неправильных фиксаций.Давайте начнем с копирования H
в H'
, у которого G
в качестве родителя, но в противном случае используется то же дерево и , сообщение и другие метаданные как H
:
H'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
Теперь нам нужно скопировать I
в новый коммит I'
.Родитель I'
не G
, а H'
:
H'-I'
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
Мы повторяем для J
до J'
, используя I'
в качестве родителя для J'
, ии так до тех пор, пока мы не скопируем каждый «неправильный» коммит в «правильный».Затем мы можем установить имя ветви, указывающее на последний такой скопированный коммит:
H'-I'-J'-K'-L'-M'-N'-O' <-- repaired
/
GH
| I
|/ J
...--F==K
|\ L
| M
ON
Запуск git log
при repaired
или gitk --all
теперь будет показывать коммит N'
, ведущий кM'
возвращение к L'
и так далее.Помните, что git log
(и gitk
) следуют за родительскими связями в обратном направлении, вообще не просматривая reflog.
Если вы хотите разрешить использование некоторых метаданных (имя автора и коммиттера, электронная почта,и метка времени), каждый из этих коммитов легко сделать с помощью цикла сценария оболочки, используя git commit-tree
.если вы хотите сохранить эти метаданные, это сложнее: вам нужно установить серию переменных среды Git перед каждым вызовом git commit-tree
, установив:
GIT_AUTHOR_NAME
, GIT_AUTHOR_EMAIL
, GIT_AUTHOR_DATE
: для автора GIT_COMMITTER_NAME
, GIT_COMMITTER_EMAIL
, GIT_COMMITTER_DATE
: аналогично, но для коммиттера
Сообщение журнала можно скопировать непосредственно из некорректных фиксаций, используяsed
или аналогичный, чтобы отрубить все до первой пустой строки включительно (обратите внимание, что это отбрасывает любые данные кодирования i18n, и sed
может плохо работать с неопределенными окончательными строками текста в сообщениях фиксации, но это может быть допустимо, еслинет, извлеките соответствующий код из git filter-branch
).
Используйте git reflog
для получения хэш-идентификаторов всех коммитов, которые должны быть скопированы, в правильном порядке (сначала самый старый для копирования, последний для того, чтобыкопия = последняя последняя)Поместите их в файл, по одной записи в строке.Тогда, вероятно, будет достаточно следующего непроверенного сценария оболочки:
parent=$hash # hash ID of commit G, onto which new chain will be built
while read tocopy; do
tree=$(git rev-parse $tocopy^{tree})
parent=$(git cat-file -p $tocopy | sed '1,/^$/d' |
git commit-tree -p $parent -t $tree)
done < $file_of_commits
git branch repaired $parent
При этом создается новое имя ветви repaired
для хранения только что созданной цепочки.