• База знаний
  • /
  • Блог
  • /
  • Wiki
  • /
  • ONLINE CHAT
+380 (44) 364 05 71

Статья также доступна на украинском (перейти к просмотру).

Пояснення, як скасувати зміни в Git за допомогою reset, revert і restore у різних сценаріях без втрати даних

Содержание:

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

Что означает «отменить коммит» в Git

Работа Git построена на основе постоянного контроля и фиксации состояний файлов проекта, например, при работе пайплайна CI/CD, опираясь на жизненный цикл состояний

(см. Рисунок 1).

Жизненный цикл состояных файлов

Рисунок 1. Жизненный цикл состояных файлов

Согласно этому, все файлы проекта могут быть либо под версионным контролем (отслеживаемые), либо нет (состояние Untracked).

К отслеживаемым относятся те объекты файловой системы, которые присутствовали при фиксации последнего состояния проекта. Они могут быть:

      • Немодифицированные (Unmodified);

      • Модифицированными (Modified);      

      • Подготовленными к индексации (Staged).  

До Untracked-файлов относятся любые файлы рабочего каталога, не присутствовавшие при фиксации последнего состояния проекта.     

Цикл начинается с создания / клонирования Git репозитория. Все файлы будут отслеживаться и находиться в состоянии Unmodified.

После редактирования (Edit the file) файл(ы) переходит(ят) в состояние Modified.

После передачи изменений в индекс (Stage the file) объекты переходят в состояние Stage the file и считаются подготовленными к индексации для передачи изменений в репозиторий.

После фиксации изменений в локальном репозитории (Commit) происходит переход объектов к состоянию Unmodified, после чего цикл повторяется.  

Таким образом, мы пришли к пониманию понятия коммита, представляющего собой действие пользователя по передаче подготовленных изменений в репозиторий (команда Commit), создавая точку фиксации текущего состояния проекта. Соответственно, отмена коммита означает откат изменений на предыдущее или другое заранее определенное состояние. Однако нередко случаются ситуации, когда изменения еще не дошли до фиксации в локальном репозитории или, наоборот, были уже отправлены в удаленный репозиторий, то есть «запушены». Для каждого случая применяются соответствующие команды со своим набором опций.  

Системой Git предусмотрено наличие команды для отслеживания состояний всех файлов проекта. Ее название – git status. 

В случае отсутствия отредактированных и/или неотслеживаемых файлов проекта указанная команда выдаст следующий результат:   

On branch master
nothing to commit, working tree clean

Команда сообщает, что в ветке master нет изменений для фиксации и рабочий каталог чист. Таким образом, Git включается в работу только в том случае, когда появляются отличия между рабочей версией проекта и репозиторием.    

Здесь мы пришли к понятию ветви. Ветвь – это направление развития проекта, которому может быть присвоено произвольное имя для удобного управления. Стандартное имя –  – master или main. Для главной ветви удаленного репозитория часто используется имя origin.   

Еще одно важное понятие – HEAD. Это указатель на текущий коммит. Стандартно он указывает на последний коммит текущей ветви. Но может быть перенаправлен, например, с помощью команды git reset, что будет рассмотрено ниже.  

Быстрая шпаргалка: какую команду и когда применять

В Таблице 1 приведены распространенные варианты команд для осуществления «откатов» разных уровней и показано их влияние на область индексации (Staged) и рабочий каталог проекта (Work dir). Столбик Безопасность показывает уровень безопасности выполнения команды.   

Таблица 1. Распространенные команды для отката изменений и их влияние на Git среду.

КомандаНазначениеStagedWork dirБезопасность
reset <commit> Переключение HEAD на указанный коммит без сохранения изменений в индексе + - +
reset --soft <commit> Переключение HEAD на указанный коммит с сохранением изменений в индексе - - +
reset --hard <commit> Отмена всех изменений указанного коммита в индексе и рабочем каталоге + + -
restore --staged <file> Отмена индексации файла + - +
restore <file> Откат изменений в файле + + -
commit --amend Исправление последнего коммита + - +
revert <commit> Безопасная отмена уже отправленного коммита новым - - +
reflog Восстановление после неудачных действий - - +

Как исправить последний коммит – git commit --amend

Для демонстрации возможностей этой и других команд нам понадобится Git repository и новый веб-проект развернут на VPS-хостинге. Создадим их.  

Создание репозитория и его инициализация

Создадим каталог с именем testgit_project и перейдем к нему. Он будет нашим рабочим каталогом проекта (Work di):

$ mkdir testgit_project
$ cd testgit_project

Инициализируем в testgit_project пустой Git репозиторий:

$ git init
 
Initialized empty Git repository in /home/testing_git/testgit_project/.git/

Тут testing_git – логин пользователя с правами sudo; .git – репозиторий.

Зарегистрируемся в Git:

$ git config --global user.email "alexander@meta.ua"
$ git config --global user.name "Alexander"

Создание веб-проекта и начало работы с ним

Веб-проект будет состоять из двух html-файлов:: test1.html и test2.html.

Создадим файл test1.html:

$ nano test1.html
 
<html>
  <head>
  </head>
  <body>
    <h1>Testing Git</h1>
     <p> Learning the basics of Git version control starts with examples.</p>
  </body>
</html>

Создадим файл test1.html

Создадим файл test2.html:

$ nano test2.html
 
<html>
  <head>
  </head>
  <body>
    <h1>Examples</h1>
     <p>Here are some practical examples of using Git.  
     </p>
  </body>
</html>

$ tree 

Просмотрим дерево рабочего каталога:

Дерево рабочего каталога

Добавим файлы к индексу:

$ git add *

Просмотрим содержимое индекса с помощью команды:

$ git ls-files -s
100644 3c474c3b0c61a0e527c36376844d358c054e6001 0       test1.html
100644 fe39821a787582e6a91030f8554a7f9bf7bd93b2 0       test2.html

Можно убедиться, что файлы уже там.

Создадим коммит:

$ git commit -m 'Adding test1.html and test2.html files'
[master (root-commit) 81efc13] Adding test1.html and test2.html files
 2 files changed, 18 insertions(+)
 create mode 100644 test1.html
 create mode 100644 test2.html

Просмотрим историю:

$ git log
commit 81efc131946c225904650ea92923a8ae40289186 (HEAD -> master)
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 18:03:29 2026 +0200
Adding test1.html and test2.html files

Здесь указатель HEAD указывает на ветку владелец, которая была создана автоматически.

HEAD всегда хешируется, и потому иногда полезно его просматривать. Это можно сделать с помощью команды:

$ git cat-file -p HEAD
tree 5d52e869cf737ccbf38965f1ee81a361f73cf358
author Alexander <alexander@meta.ua> 1774022609 +0200
committer Alexander <alexander@meta.ua> 1774022609 +0200
        Adding test1.html and test2.html files

Проверим состояния или статус всех имеющихся файлов проекта:

$ git status

On branch master nothing to commit, working tree clean

On branch master
nothing to commit, working tree clean

Мы видим, что разница между рабочим каталогом и репозиторием отсутствует. 

Импортируется в файл test1.html изменения, добавив новый абзац с текстом:

$ nano test1.html

Новый абзац: <p> First of all, let's discuss the capabilities of the system.</p>

$ git add test1.html

Добавим файл в индекс с созданием соответствующего коммита:

$ git add test1.html
$ git commit -m 'Added new <p> tag to test1.html file'
 
[master c16c606] Added new <p> tag to test1.html file
 1 file changed, 2 insertions(+), 2 deletions(-)

Проверим статус:

$ git status

Добавим файл в индекс с созданием соответствующего коммита:

$ git add test1.html
$ git commit -m 'Added new <p> tag to test1.html file'
 
[master c16c606] Added new <p> tag to test1.html file
 1 file changed, 2 insertions(+), 2 deletions(-)

Проверим статус:

$ git status

On branch master

On branch master
nothing to commit, working tree clean

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

Внесем еще одно изменение, добавив тег <h2>:

$ nano test1.html

Новый тег:    <h2>General overview of the system</h2>

$ git status

Проверим статус:

$ git status

On branch master

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   test1.html
no changes added to commit (use "git add" and/or "git commit -a")

Команда информирует, что индекс пуст и что тест1.html имеет статус модифицированный. Кроме того, команда дает подсказки: как можно добавить файл в индексную область (git add); как отменить изменения в рабочем каталоге (git restore).

Добавим файл в индекс и снова проверим его статус:

$ git add test1.html
$ git status

On branch master

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test1.html

Теперь команда информирует о том, что изменения подготовлены к отправке в репозиторий, а также предоставляет команду для возврата файла из индексной зоны (git restore --staged).

Создадим коммит:

$ git commit -m 'Added <h2> tag to test1.html file'

[master 27e9cd9] Added <h2> tag to test1.html file

[master 27e9cd9] Added <h2> tag to test1.html file
 1 file changed, 1 insertion(+)

Снова проверим статус:

$ git status

On branch master

On branch master
nothing to commit, working tree clean

Мы видим, что снова наступило равновесие между рабочим каталогом и репозиторием. И потому Git сейчас бездейственна.  

Использование команды git commit --amend

Нередко возникает потребность в исправлении сообщения последнего коммита. Здесь поможет следующая команда:

$ git commit --amend

При ее выполнении загружается редактор со старым сообщением, которое легко исправимо:

$ git commit --amend

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

      1. Вносим изменения в файл рабочего каталога:

$ nano test1.html

Добавим текст: «Git version control»)

Git version control2. Отправляем файл в индексную область:

$ git add test1.ht

3. Создаем соответствующий коммит:

$ git commit --amend --no-edit

Здесь мы использовали опцию --no-edit для того чтобы оставить сообщение коммита неизменным. 

--no-edit

[master 616622c] Added one <h2> tag to test1.html file
Date: Fri Mar 20 19:39:10 2026 +0200
1 file changed, 1 insertion(+)

Таким же образом мы можем добавить и новый файл, а не только редактируемый.

Проверим историю:

$ git lo

$ git log

commit 616622cb955d8a34b4a8f7c6b2cba610c1e90550 (HEAD -> master)
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 19:39:10 2026 +0200
 
    Added one <h2> tag to test1.html file
 
commit c16c606bf3ad3ea01f5bd06f2140e21f6b41f821
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 19:06:48 2026 +0200
 
    Added new <p> tag to test1.html file
 
commit 81efc131946c225904650ea92923a8ae40289186
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 18:03:29 2026 +0200
    Adding test1.html and test2.html files

Можно убедиться, что наши действия на историю коммитов не повлияли, поскольку рассматриваемая команда не перемещает HEAD и не изменяет ветки. Единственное ее «несовершенство» – она переписывает только последний коммит. Иногда ее применение может потребовать использования force push, однако для общих ветвей это следует делать осторожно. 

Как убрать последний локальный коммит – git reset

Для отмены локальных коммитов очень часто используется команда git reset с разными параметрами. Она влияет на историю, рабочий каталог и индексную зону в зависимости от используемых параметров. Продемонстрируем его использование на конкретных примерах.  

Ранее мы вывели нашу историю коммитов, где последний из них был связан с добавлением тега. <h2>. Его хеш: 616622cb955d8a34b4a8f7c6b2cba610c1e90550. Наша задача состоит в удалении тега из индекса, оставив его при этом в файле рабочего каталога. В таком случае в нашей ветке разработки HEAD нужно переместить на один шаг назад.

Команда может выглядеть так:

$ git reset --mixed HEAD~1

Или так:

$ git reset HEAD~1

Параметр --mixed применяется в стандартном режиме и потому его можно специально не указывать.

Выход команды:

Unstaged changes after reset:
M       test1.html

Проверим историю:

$ git log

$ git log

commit c16c606bf3ad3ea01f5bd06f2140e21f6b41f821 (HEAD -> master)
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 19:06:48 2026 +0200
    Added new <p> tag to test1.html file
commit 81efc131946c225904650ea92923a8ae40289186
Author: Alexander <alexander@meta.ua>
Date:   Fri Mar 20 18:03:29 2026 +0200
    Adding test1.html and test2.html files

Можно убедиться, что коммит с хэшем 616622cb955d8a34b4a8f7c6b2cba610c1e90550 теперь недоступен. Указатель HEAD теперь находится на предыдущем коммите.

Проверим, сохранились ли изменения в рабочем каталоге:

$ nano test1.html

$ nano test1.html

Изменения сохранились (тег <h2> остался на своем месте).

Теперь вернем HEAD назад, указав в команде хеш удалённого коммита:

$ git reset 616622cb955d8a34b4a8f7c6b2cba610c1e90550

Проверим историю:

$ git log

$ git log

Все хорошо, коммит возвращен.

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

Для этого существует следующий вариант команды

$ git reset --soft HEAD~

Проверим историю:

$ git log

$ git log

Проверим статус:

$ git status

$ git status

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   test1.html

Снова вернем состояние индекса назад:

$ git reset 616622cb955d8a34b4a8f7c6b2cba610c1e90550

Следующей нашей задачей будет отмена всех изменений последнего коммита в индексе и рабочем каталоге, то есть фактическое удаление их из репозитория. 

Для этого воспользуемся командой:

$ git reset --hard HEAD~

$ git reset --hard HEAD~1

HEAD is now at c16c606 Added new <p> tag to test1.html file

Проверим историю:

$ git log

$ git log

Проверим рабочий каталог:

$ nano test1.html

$ nano test1.html

Можно убедиться, что тег <h2> удален из файла

По результатам использования команды можно заключить, что команда с параметром   --hard опасна, поскольку способна изменять файлы рабочего каталога, а также влияет на историю, требующую осторожности при ее применении.

Как убрать файл из staged – git restore --staged

Начиная с версии Git 2.23.0, появилась команда git restore как альтернатива git reset для большинства операций отмены изменений. Рассмотрим его использование для нашего проекта.

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

Создадим новый файл и отправим его в индекс:

$ touch test3.html
$ git add test3.htm

Проверим статус:

$ git status

On branch master

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   test3.html

Команда подсказывает, как можно вернуть файл из индексной зоны, и поэтому воспользуемся ею:

$ git restore --staged test3.html

Проверим статус

$ git status

On branch master

On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test3.html
nothing added to commit but untracked files present (use "git add" to track)

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

Как вернуть только один файл – git restore

Команду git restore можно применять и в других случаях, например, для отката файла в состояние последнего или любого другого коммита. Рассмотрим эти случаи

Внесем изменения в файл test1.html, добавив к нему еще один тэг <p>:

$ nano test1.htm

Новый тег: <p> Now we can move on to the first example.</p>

$ git status

Сохраним внесенные изменения и закроем файл.

Проверим статус:

 

$ git status

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   test1.html
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        test3.html

Команда подсказывает, как можно вернуть файл в предыдущее состояние. Воспользуемся ею

$ git restore test1.html

Проверим файл в рабочем каталоге:

$ nano test1.htm

$ nano test1.html

Вы можете убедиться, что последние изменения были удалены из файла.

Команду можно использовать для возврата файла в определенное состояние:

$ git restore --source=HEAD~1 test1.html

Проверка:

$ nano test1.html

Testing git

Можно убедиться, что файл вернулся в состояние предыдущего коммита за счет перемещения. HEAD на одну позицию вспять. Конечно, мы могли бы переместить HEAD и на другие позиции – на два, три или более коммитов назад, но это в данном случае несущественно. 

Как отменить уже отправленный коммит - git revert

В Git существует команда, позволяющая аннулировать уже «запущенные» изменения, то есть, отправленные в главную ветвь удаленного репозитория. Продемонстрируем ее в работе.  

Добавим к индексу обновленный нами файл test1.html и сформируем соответствующий коммит:

$ git add test1.html
$ git commit -m 'Latest changes in file test1.html'

[master 58a0a79] Latest changes in file test1.html

[master 58a0a79] Latest changes in file test1.html
 1 file changed, 2 insertions(+), 2 deletions(-)

Отправим все наши локальные изменения в удаленный репозиторий с помощью следующей команды: 

$ git push origin mai

Тут origin – имя удаленного репозитория; main – имя ветви

Просмотрим историю для определения хэша коммита, который нужно аннулировать

$ git log

commit 58a0a79834d1ae04d7fb463063faa561365e4360 (HEAD -> master)

commit 58a0a79834d1ae04d7fb463063faa561365e4360 (HEAD -> master)
Author: Alexander <alexander@meta.ua>
Date:   Sat Mar 21 18:15:06 2026 +0200
      Latest changes in file test1.html   commit c16c606bf3ad3ea01f5bd06f2140e21f6b41f821 Author: Alexander <alexander@meta.ua> Date:   Fri Mar 20 19:06:48 2026 +0200       Added new <p> tag to test1.html file   commit 81efc131946c225904650ea92923a8ae40289186 Author: Alexander <alexander@meta.ua> Date:   Fri Mar 20 18:03:29 2026 +0200       Adding test1.html and test2.html files  

Аннулировать проблемный коммит можно с помощью команды:

$ git revert c16c606bf3ad3ea01f5bd06f2140e21f6b41f821

Тут c16c606bf3ad3ea01f5bd06f2140e21f6b41f821 – хеш коммита, который аннулируется.

При этом автоматически будет создан новый коммит (реверт-коммит), отменяющий действия указанного в команде коммита. При этом реверт-коммит останется в истории. 

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

Как восстановиться после ошибки – git reflog

Иногда могут возникать конфликтные ситуации или ошибки, требующие восстановления определенных коммитов, например, после неудачного применения опасной команды git reset --hard. В таком случае лучше воспользоваться командой reflog. Продемонстрируем это. 

Просмотрим журнал для поиска хэша коммита:

$ git reflog

$ git reflog

58a0a79 (HEAD -> master) HEAD@{0}: commit: Latest changes in file test1.html
c16c606 HEAD@{1}: reset: moving to HEAD~1
616622c HEAD@{2}: reset: moving to 616622cb955d8a34b4a8f7c6b2cba610c1e90550
c16c606 HEAD@{3}: reset: moving to HEAD~1
616622c HEAD@{4}: reset: moving to 616622cb955d8a34b4a8f7c6b2cba610c1e90550
c16c606 HEAD@{5}: reset: moving to HEAD~1
616622c HEAD@{6}: commit (amend): Added one
tag to test1.html file 9f0dfee HEAD@{7}: commit (amend): Added one
tag to test1.html file 27e9cd9 HEAD@{8}: commit: Added
tag to test1.html file c16c606 HEAD@{9}: commit: Added new
tag to test1.html file 81efc13 HEAD@{10}: commit (initial): Adding test1.html and test2.html files

Например, мы нуждаемся в восстановлении коммита с хэшем c16c606. Для этого переключимся на него с помощью команды checkout:

$ git checkout c16c606

 Note: switching to 'c16c606'

Note: switching to 'c16c606'.
HEAD is now at c16c606 Added new <p> tag to test1.html file

В текущем состоянии HEAD отключен и указывает на c16c606. Сейчас мы можем создать отдельную ветвь для сохранения восстановленных изменений. Это можно сделать с помощью команды:

$ git switch -c new_branch

Switched to a new branch 'new_branch'

Switched to a new branch 'new_branch'

В результате указатель HEAD будет указывать на новую ветку. Проверим это:

$ git lo

commit c16c606bf3ad3ea01f5bd06f2140e21f6b41f821 (HEAD -> new_branch)

Результат:

commit c16c606bf3ad3ea01f5bd06f2140e21f6b41f821 (HEAD -> new_branch)

Если же мы не хотим создавать новую ветвь, то отменить операцию можно с помощью команды

$ git switch -

По результатам применения команды можно заключить, что reflog перемещает HEAD и позволяет найти потерянный коммит после неудачного reset --hard.

Типичные ошибки и рекомендации

Укажем типичные ошибки и некоторые рекомендации при использовании команд для отмены внесенных изменений:

      • Не использовать reset --hard, если не уверен;

      • Не делать push --force в общую ветвь без осознания последствий;

      • Для уже созданных коммитов обычно лучше выбирать revert;

      • Перед опасными действиями полезно просмотреть git status и git log --oneline.

В заключение

      • Для безопасной работы важно не запоминать все команды, а понимать сценарии их использования;

      • В большинстве случаев выбор происходит между reset, revert и restore;

      • Команда reflog - страхование на случай ошибок.

Если вы хотите глубже разобраться в автоматизации процессов в Git, рекомендуем прочитать статью "Что такое Git hooks и зачем они нужны".

Подписывайтесь на наш Telegram-канал https://t.me/freehostua, чтобы быть в курсе новых полезных материалов

Смотрите наш канал YouTube на https://www.youtube.com/freehostua.

Мы в чем ошиблись, или что-то пропустили?

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

Дата: 31.03.2026
Голосование

Авторам статьи важно Ваше мнение. Будем рады его обсудить с Вами:

comments powered by Disqus
navigate
go
exit
Спасибо, что выбираете FREEhost.UA