Я случайно набрал команду git commit --amend. Это ошибка, потому что я понял, что фиксация на самом деле совершенно новая, и она должна быть совершена с новым сообщением. Я хочу сделать новую фиксацию. Как мне отменить это?
Как отменить git commit -amend
Ответ 1
Комментарий PetSerAl - это ключ. Здесь две последовательности команд делают именно то, что вы хотите:
git reset --soft @{1}
git commit -C @{1}
и объяснение того, как это работает.
Описание
Когда вы делаете новый коммит, Git обычно 1 использует эту последовательность событий:
-  Прочитайте идентификатор (хэш SHA-1, например a123456...) текущего фиксации (черезHEAD, который дает нам текущую ветку). Позвольте называть этот идентификатор C (для текущего). Обратите внимание, что этот текущий фиксатор имеет родительский фиксатор; позвольте назвать его идентификатор P (для родителя).
- Поверните указатель (aka staging-area) в дерево. Это создает другой идентификатор; позвольте этому идентификатору T (для дерева).
- Напишите новый фиксатор с родительским = C и tree = T. Этот новый коммит получает другой идентификатор. Позвольте называть это N (для нового).
- Обновите ветвь с новым идентификатором фиксации N.
При использовании --amend Git процесс немного изменится. Он по-прежнему записывает новый коммит как прежде, но на шаге 3 вместо записи нового commit с parent = C он записывает его с parent = P.
Фото
Изобразительно, что мы можем сделать то, что произошло таким образом. Начнем с графика фиксации, заканчивающегося на P--C, на который указывает branch:
...--P--C   <-- branch
Когда мы создаем новый commit N, получаем:
...--P--C--N   <-- branch
Когда мы используем --amend, вместо этого получаем это:
       C
      /
...--P--N   <-- branch
Обратите внимание, что commit C все еще находится в репозитории; его просто отодвинули в сторону, откинув назад, так что новый commit N может указывать на старый родительский P.
Цель
То, что вы поняли, что хотите, после git commit --amend, должно выглядеть так:
...--P--C--N   <-- branch
Мы не можем этого сделать - мы не можем изменить N; Git никогда не может изменять какой-либо фиксатор (или любой другой объект) после его сохранения в репо, но обратите внимание, что цепочка ...--P--C все еще находится там, полностью неповрежденная. Вы можете найти commit C через reflogs, и это то, что делает синтаксис @{1}. (В частности, это сокращение для [email protected]{1},  2 что означает "где currentbranch указал на один шаг назад", что было "commit C".)
Итак, теперь мы запускаем git reset --soft @{1}, который делает это:
       C   <-- branch
      /
...--P--N
Теперь branch указывает на C, который указывает на P.
Что происходит с N? То же самое, что произошло с C до: он некоторое время сохранялся через reflog.
Нам это действительно не нужно (хотя это может пригодиться), потому что флаг --soft для git reset не содержит индексацию/промежуточную область (вместе с деревом). Это означает, что мы можем сделать новую фиксацию снова, просто запустив еще один git commit. Он выполнит те же четыре шага (прочитайте ID из HEAD, создайте дерево, создайте новый фиксатор и обновите ветвь):
       C--N2   <-- branch
      /
...--P--N
 где N2 будет нашим новым новым (вторым новым?) фиксацией.
Мы даже можем сделать git commit повторно использовать сообщение commit из commit N. Команда git commit имеет аргумент --reuse-message, также пишется -C; все, что нам нужно сделать, это дать ему что-то, что позволяет найти исходный новый commit N, из которого нужно скопировать сообщение, чтобы сделать N2 с. Как мы это делаем? Ответ: он в рефлоге, так же, как C был, когда нам нужно было сделать git reset.
На самом деле это тот же @{1}!
Помните, @{1} означает "где это было всего лишь минуту назад", а git reset только что обновил его, переместив его с C на N. Мы еще не сделали новую фиксацию N2. (Как только мы это сделаем, N будет @{2}, но мы еще этого не сделали.)
Итак, положив все это вместе, получим:
git reset --soft @{1}
git commit -C @{1}
1 Места, в которых это описание разбивается, включают, когда вы изменяете слияние, когда вы находитесь на отдельной HEAD, и когда вы используете альтернативный индекс. Даже тогда, хотя, довольно очевидно, как изменить описание.
  2 Если HEAD отделяется, поэтому нет текущей ветки, значение становится [email protected]{1}. Обратите внимание, что @ сам по себе является коротким для HEAD, поэтому тот факт, что @{n} ссылается на текущую ветвь, а не на HEAD сам, немного несогласован.
Чтобы увидеть, как они отличаются, рассмотрим git checkout develop, за которым следует git checkout master (при условии существования обоих ветвей). Первый checkout изменяет HEAD на develop, а второй изменяет HEAD на master. Это означает, что [email protected]{1} указывает на то, что зафиксировано на master, перед последним обновлением до master; но [email protected]{1} - это фиксация develop точек на данный момент - возможно, некоторые другие фиксации.
(Recap: после этих двух команд git checkout @{1} теперь означает [email protected]{1}, [email protected]{1} означает ту же фиксацию, что и develop, а @ означает HEAD). Если вы запутались, ну, так и я, и, видимо, я не один: см. комментарии.)
