GrabDuck

Как вернуться (откатиться) к более раннему коммиту?

:

Этот вопрос можно понять по-разному:

  1. Что значит вернуться или откатиться: просто посмотреть, изменить содержимое рабочей области, изменить историю Git?
  2. Что именно откатить: рабочую область (worktree), индекс (область подготовки коммита, staging area), текущую ветку, удаленную ветку?
  3. К какой позиции откатить: к индексу, к последнему коммиту, к произвольному коммиту?

Обозначим начальную ситуацию на следующей схеме:

               (i) (wt)
A - B - C - D - ? - ?
            ↑
          master
          (HEAD)

A, B, C, D — коммиты в ветке master.
(HEAD) — местоположение указателя HEAD.
(i) — состояние индекса Git. Если совпадает c (HEAD) - пуст. Если нет - содержит изменения, подготовленные к следующему коммиту.
(wt) — состояние рабочей области проекта (working tree). Если совпадает с (i) — нет неиндексированных изменений, если не совпадает — есть изменения.
обозначает коммит, на который указывает определенная ветка или указатель.

Вот решения, в зависимости от задачи:

Если вам нужно просто переключиться на другой коммит, чтобы, например, посмотреть на его содержимое, достаточно команды git checkout:

git checkout aaaaaa

 (wt)
 (i)
  A - B - C - D
  ↑           ↑
(HEAD)    master

Сейчас репозиторий находится в состоянии «detached HEAD». Чтобы переключиться обратно, используйте имя ветки (например, master):

git checkout master

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

git checkout -b имя-новой-ветки aaaaaa

 (wt)
 (i)
  A - B - C - D
  ↑           ↑
 new       master
(HEAD)

Начальное состояние:

               (i) (wt)
A - B - C - D - ? - ?
            ↑
          master
          (HEAD)

3.1 Безопасно — с помощью кармана (stash)

3.1.1 Только неиндексированные

Можно удалить прикарманить только те изменения, которые еще не были индексированы (командой add):

git stash save --keep-index

Конечное состояние:

               (wt)
               (i)       
A - B - C - D - ?         ?
            ↑             ↑
          master      stash{0}
          (HEAD)

3.1.2 Индексированные и нет

Эта команда отменяет все индексированные и неиндексированные изменения в рабочей области, сохраняя их в карман (stash).

git stash save

Конечное состояние:

           (wt)
           (i)           
A - B - C - D             ?
            ↑             ↑
          master      stash{0}
          (HEAD)

Восстановление несохраненных изменений: легко и просто.

git stash apply

Если stash совсем не нужен, его можно удалить.

# удалить последнюю запись кармана
git stash drop

Подробнее про использование stash.

После этого восстановить изменения всё ещё можно, но сложнее: How to recover a dropped stash in Git?

3.2 Опасный способ

Осторожно! Эта команда безвозвратно удаляет несохраненные текущие изменения из рабочей области и из индекса Если они вам все-таки нужны, воспользуйтесь git stash.

Восстановление несохраненных изменений: неиндексированные потеряны полностью, но вы можете восстановить то, что было проиндексировано.

Здесь мы будем использовать git reset --hard

Выполняем:

git reset --hard HEAD

Конечное состояние:

           (wt)
           (i)
A - B - C - D - х - х
            ↑
          master
          (HEAD)

Осторожно! Эта команда переписывает историю Git-репозитория. Если вы уже опубликовали (git push) свои изменения, то этот способ использовать нельзя (см. почему). Используйте вариант дальше с git revert.

4.1 При этом сохранить изменения в индекс репозитория:

git reset --soft bbbbbb

После этого индекс репозитория будет содержать все изменения от cccccc до dddddd. Теперь вы можете сделать новый коммит (или несколько) на основе этих изменений.

           (wt)
           (i)
A - B - C - D 
    ↑
  master
  (HEAD)

4.2 Сохранить изменения в рабочей области, но не в индексе.

git reset bbbbbb

Эта команда просто перемещает указатель ветки, но не отражает изменения в индексе (он будет пустым).

   (i)     (wt)
A - B - C - D 
    ↑
  master
  (HEAD)

4.3 Просто выбросить изменения.

Осторожно! Эта команда безвозвратно удаляет несохраненные текущие изменения. Если удаляемые коммиты не принадлежат никакой другой ветке, то они тоже будут потеряны.

Восстановление коммитов: Используйте git reflog и этот вопрос чтобы найти и восстановить коммиты; иначе сборщик мусора удалит их безвозвратно через некоторое время.

Восстановление несохраненных изменений: неиндексированные потеряны полностью, но вы можете восстановить то, что было проиндексировано.

Начальное состояние:

               (i) (wt)
A - B - C - D - ? -  ?
            ↑
          master
          (HEAD)

Выполняем:

git reset --hard bbbbbb

Конечное состояние:

   (wt)
   (i)
A - B - C - D - х - х
    ↑
  master
  (HEAD)

Воспользуйтесь командой git revert. Она создает новые коммиты, по одному на каждый отменяемый коммит. Таким образом, если нужно отменить все коммиты после aaaaaa:

# можно перечислить отменяемые коммиты
git revert bbbbbb cccccc dddddd

# можно задать диапазон от более раннего к более позднему (новому)
git revert bbbbbb..dddddd

# либо в относительных ссылках
git revert HEAD~2..HEAD

# можно отменить коммит слияния, указывая явным образом номер предка (в нашем примере таких нет):
git revert -m 1 abcdef

# после этого подтвердите изменения:
git commit -m'детальное описание, что и почему сделано'

Восстановление: Если revert-коммит оказался ошибочным, используйте этот ответ.