При использовании git иногда возникает необходимость откатывать изменения. Причиной тому могут быть внезапно возникшие баги, которые не удалось выявить на этапе тестирования. А если речь идет о локальном репозитории, то причин может быть еще больше.
Переписывать публичную историю - сомнительная затея, мы же разработчики, а не политики. Но прежде чем оставить след в истории - подумай, насколько он ценен.
Создадим тестовый репозиторий, с которым будет проводить эксперименты и добавим туда несколько коммитов:
$ git init
Инициализирован пустой репозиторий Git в /home/byurrer/reps/revert/.git/
$ touch file.txt
$ git commit -am 'added file.txt'
[master (корневой коммит) b9d0814] added file.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file.txt
$ echo "1" >> file.txt
$ git commit -am 'writed 1'
[master 7c94686] writed 1
1 file changed, 1 insertion(+)
$ echo "2" >> file.txt
$ git commit -am 'writed 2'
[master 911d49a] writed 2
1 file changed, 1 insertion(+)
$ echo "3" >> file.txt
$ git commit -am 'writed 3'
[master 894f7ff] writed 3
1 file changed, 1 insertion(+)
Теперь имеем такую историю:
$ git log --oneline
894f7ff (HEAD -> master) writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
reset
- инструмент изменения истории по 3 направлениям, иначе их называют деревья (дополнительно про деревья можн опочитать здесь и здесь):
git reset
не должна применяться к публичной истории, так как при синхронизации между участниками разработки, их локальные репозитории будут отставать.
Команда git reset
имеет 3 опции, каждая из которых возвдейтвует на определенный набор деревьев:
--soft
- меняет дерево коммитов--mixed
- меняет дерево коммитов и раздел проиндексированных файлов--hard
- меняет дерево коммитов, раздел проиндексированных файлов и рабочий каталогДля дальнейшего рассмотрения сферы влияния каждой из перечисленных опций, внесем изменения в репозиторий, добавив в файл file.txt
еще одну строку, закоммитим и просмотрим состояние репозитория:
$ echo "4" >> file.txt
$ git commit -am 'writed 4'
[master 5fafd6c] writed 4
1 file changed, 2 insertions(+)
$ git status
На ветке master
нечего коммитить, нет изменений в рабочем каталоге
$ git log --oneline
5fafd6c (HEAD -> master) writed 4
894f7ff writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
Сделаем мягкий сброс к предыдущему коммиту и посмотрим состояние репозитория:
$ git reset --soft 894f7ff
$ git status
На ветке master
Изменения, которые будут включены в коммит:
(use "git restore --staged <file>..." to unstage)
изменено: file.txt
$ git log --oneline
894f7ff (HEAD -> master) writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
git reset --soft
сбрасывает только историю коммитов, не затрагивая рабочую директорию и раздел проиндексированных файлов. При помощи мягкого сброса можно удалять коммиты, без потери индекса данных и изменений файлов.
Сделаем смешанный сброс к предыдущему коммиту и посмотрим состояние репозитория:
$ git reset --mixed 894f7ff
Непроиндексированные изменения после сброса:
M file.txt
$ git status
На ветке master
Изменения, которые не в индексе для коммита:
(используйте «git add <файл>…», чтобы добавить файл в индекс)
(use "git restore <file>..." to discard changes in working directory)
изменено: file.txt
нет изменений добавленных для коммита
(используйте «git add» и/или «git commit -a»)
git reset --mixed
сбрасывает историю коммитов и раздел проидексированных файлов, не затрагивая рабочую директорию. При помощи смешанного сброса можно удалять коммиты и индексированные данные, но без потери изменений файлов.
Сделаем жесткий сброс к предыдущему коммиту и посмотрим состояние репозитория:
$ git reset --hard 894f7ff
HEAD сейчас на 894f7ff writed 3
$ git status
На ветке master
нечего коммитить, нет изменений в рабочем каталоге
$ git log --oneline
894f7ff (HEAD -> master) writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
git reset --hard
сбрасывает все. Без возможности восстановления. Стоит несколько раз подумать прежде чем жестко сбрасывать.
revert
- безопасное исключение изменений коммита с сохранением истории и добавлением нового коммита.
Пробуем отменить последний коммит:
$ git revert HEAD
Revert "writed 3"
This reverts commit d3e6055f6a8a55fb9f4c84c8c061edc7dbab2d93.
# Пожалуйста, введите сообщение коммита для ваших изменений. Строки,
# начинающиеся с «#» будут проигнорированы, а пустое сообщение
# отменяет процесс коммита.
#
# На ветке master
# Ваша ветка обновлена в соответствии с «origin/master».
#
# Изменения, которые будут включены в коммит:
# изменено: file.txt
#
Посмотрим историю репозитория:
$ git log --oneline
136b515 (HEAD -> master) Revert "writed 3"
894f7ff writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
Вернем все как было (для чистоты следующих примеров):
$ git reset --hard 894f7ff
HEAD сейчас на 894f7ff writed 3
Попробуем отменить коммит ниже текущего:
$ git revert 911d49a
Автослияние file.txt
КОНФЛИКТ (содержимое): Конфликт слияния в file.txt
error: не удалось обратить изменения коммита 911d49a… writed 2
подсказка: после разрешения конфликтов, пометьте исправленные пути
подсказка: с помощью «git add <пути>» или «git rm <пути>»
подсказка: и сделайте коммит с помощью «git commit»
Еще раз взглянем на историю репозитория:
$ git log --oneline
894f7ff (HEAD -> master) writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
Отменяя коммит
911d49a
остается коммит894f7ff
после него, который вносит изменения в тот же файл что и коммит911d49a
, что ведет к конфликту слияния.
В данном случае есть вариант решать конфликт вручную, либо произвести серию отмен (revert
) коммитов до нужного коммита:
$ git revert 7c94686..HEAD
[master 8f2ff6a] Revert "writed 3"
1 file changed, 1 deletion(-)
[master 97aaf82] Revert "writed 2"
1 file changed, 1 deletion(-)
Посмотрим историю:
$ git log --oneline
97aaf82 (HEAD -> master) Revert "writed 2"
8f2ff6a Revert "writed 3"
894f7ff writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
Серия отмен коммитов происходит в полуоткрытом промежутке (
7c94686
,HEAD
], то есть отменяется все начиная отHEAD
и до7c94686
, но не включая7c94686
.
Теперь добавим в репозиторий новый файл:
$ touch file2.txt
$ git add .
$ git commit -m 'added file2.txt'
[master ae1a44b] added file2.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file2.txt
$ git log --oneline
ae1a44b (HEAD -> master) added file2.txt
894f7ff writed 3
911d49a writed 2
7c94686 writed 1
b9d0814 added file.txt
Появился новый коммит не затрагивающий изменения файла file.txt
. Попробуем отменить коммит ниже текущего и отменить последние изменения в файле file.txt
:
$ git revert 894f7ff
[master 136b515] Revert "writed 3"
1 file changed, 1 deletion(-)
Очевидно, конфликтов нет.
revert
отменяет изменения коммита не удаляя его из истории, создавая при этом новый коммит.
git revert
предназначена для безопасной отмены публичных коммитов, а git reset
- для отмены локальных изменений.