git reset

Git reset и три дерева
Команда git reset — это инструмент, используемый для отмены изменений. У нее есть три формы вызова, соответствующие трем внутренним системам управления состоянием Git, называемым тремя деревьями Git. Эти системы включают HEAD (историю коммитов), индекс staging и рабочий каталог. Мы рассмотрим каждую из этих систем.
Рабочий каталог
Первое дерево — это рабочий каталог. Оно представляет файлы в файловой системе вашего компьютера, доступные редактору кода для внесения изменений. Рабочий каталог считается определенным коммитом проверенного проекта. Когда проект извлечен, это означает, что его файлы распакованы из репозитория Git.
git reset command
echo 'hello git reset' > edited_file
git status
#On branch master
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_fileИндекс staging
Следующее дерево — это индекс staging, который отслеживает изменения, подготовленные в рабочем каталоге. В целом детали работы области staging скрыты от пользователей Git. Иногда при разговоре об области staging используются разные выражения, например cache, directory cache, staged files, staging area и т. д.
Здесь нам нужна команда git ls-files, которая считается отладочным инструментом и проверяет состояние индекса staging.
git ls-files -s
git ls-files -s
#543644 a32de29bb3c1d643328b29ae775ad8c2e48c3256 0 edited_fileИстория коммитов
Последнее дерево — это история коммитов. Команда git commit фиксирует изменения в постоянном снимке, помещенном в индекс staging.
git reset, commit history
git commit -am "edit content of test_file"
#[master ab23324] edit the content of edited_file
#1 file changed, 1 insertion(+)
git status
#On branch master
#nothing to commit, working tree cleanВ примере выше вы можете видеть новый коммит с сообщением "edit content of test_file". Изменения привязаны к истории коммитов. На этом этапе запуск git status показывает, что никаких предстоящих изменений в деревьях нет. При вызове git log вы увидите историю коммитов. После того как изменения проходят через три дерева, можно использовать git reset.
Как это работает
На первый взгляд команда git reset имеет некоторое сходство с git checkout, поскольку обе работают с HEAD. Команда git checkout работает исключительно с указателем ссылки HEAD, тогда как команда git reset перемещает указатель ссылки HEAD и указатель ссылки текущей ветки. Лучше понять ее поведение поможет иллюстрация ниже:

На этой иллюстрации показана последовательность коммитов в ветке master. Как видите, ссылка HEAD и ссылка ветки master в данный момент указывают на коммит d. Посмотрим, как изменится изображение в случае git checkout b и git reset b.
git checkout b
При выполнении команды git checkout ссылка master по-прежнему указывает на коммит d. Что касается ссылки HEAD, она была перемещена и теперь указывает на коммит b. В результате репозиторий находится в состоянии 'detached HEAD'.

git reset b
Команда git reset переключает ссылки HEAD и ветки на указанный коммит. Кроме того, она изменяет состояние трех деревьев. Есть три аргумента командной строки --soft, --mixed и --hard, которые определяют изменение деревьев индекса staging и рабочего каталога.

Основные параметры
По умолчанию команда git reset имеет постоянные аргументы --mixed и HEAD. Поэтому вызов git reset эквивалентен вызову git reset --mixed HEAD. Здесь HEAD — это указанный коммит. Вместо него можно использовать любой SHA-1 хеш коммита Git.

--hard
Наиболее часто используемый параметр — --hard. Однако его использование связано с некоторыми рисками. При --hard указатели ссылки истории коммитов начинают указывать на указанный коммит. После этого индекс staging и рабочий каталог сбрасываются так, чтобы соответствовать указанному коммиту. Изменения, которые ранее ожидали в индексе staging и рабочем каталоге, сбрасываются до состояния дерева коммита. Любой ожидающий коммит в индексе staging и рабочем каталоге будет потерян. Пример ниже демонстрирует сказанное выше. Сначала выполните следующие команды:
git reset usage
echo 'test content' > test_file
git add test_file
echo 'modified content' >> edited_fileНовый файл с именем test_file был создан и добавлен в репозиторий. Кроме того, содержимое edited_file будет изменено. Теперь проверим состояние репозитория с этими изменениями с помощью команды git status.
git status
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_fileКак видите, теперь есть некоторые ожидающие изменения. Ожидающее изменение для дерева индекса staging — это добавление test_file, а для рабочего каталога — изменения в edited_file. Теперь посмотрим на состояние индекса staging:
git ls-files -s
git ls-files -s
#123126 7a32454a5477b1bf4765946147c49509a431f963 0 test_file
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_filetest_file был добавлен в индекс. edited_file был обновлен, но SHA индекса staging (d7d77c1b04b5edd5acfc85de0b592449e5303770) остается прежним. Эти изменения находятся в рабочем каталоге. Они не были перенесены в индекс staging, поскольку мы не использовали команду git add. На этом этапе мы можем выполнить git reset --hard и увидеть новое состояние репозитория:
git reset --hard
git reset --hard
#HEAD is now at ab23324 update content of edited_file
git status
#On branch master
#nothing to commit, working tree clean
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_fileПараметр --hard выполнил "hard reset". Git указывает, что HEAD теперь указывает на недавний коммит ab23324. Затем состояние репозитория проверяется с помощью git status. Git сообщает, что ожидающих изменений нет. Что касается состояния индекса staging, он был сброшен к моменту до добавления test_file. Изменения edited_file и добавление test_file были удалены. Эта потеря не может быть отменена.
--mixed
Режим работы по умолчанию — --mixed. Он обновляет указатели ссылок. Индекс staging сбрасывается до указанного коммита. Отмененные изменения из индекса staging помещаются в рабочий каталог.
the git reset command
echo 'new file content' > test_file
git add test_file
echo 'append content' >> edited_file
git add edited_file
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#modified: edited_file
git ls-files -s
#123126 6a32154a5477b1bf4765946147c49509a4323d32 0 test_file
#123126 3c3262db063f9e9426901092c00a3394b4bd3445 0 edited_fileВ примере выше был добавлен test_file, а содержимое edited_file было изменено. Затем эти изменения были применены к индексу staging с помощью git add. При таком состоянии репозитория теперь пора вызвать git reset.
git reset --mixed
git reset --mixed
git status
#On branch master
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file
#Untracked files:
#(use "git add ..." to include in what will be committed)
#test_file
#no changes added to commit (use "git add" and/or "git commit -a")
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file--mixed — это режим по умолчанию. Он имеет тот же эффект, что и git reset. git status показывает, что есть изменения в edited_file и что test_file — это неотслеживаемый файл. Это и есть точное поведение --mixed. Индекс staging был сброшен, а ожидающие изменения перемещены в рабочий каталог.
--soft
Аргумент --soft обновляет указатели ссылок и останавливает сброс. Однако он не влияет на индекс staging и рабочий каталог.
git reset --soft
git reset --soft
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
git ls-files -s
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_fileМягкий сброс (soft reset) сбрасывает только историю коммитов. По умолчанию он вызывается с HEAD в качестве целевого коммита. Теперь создадим новый коммит, чтобы попробовать --soft с целевым коммитом, который не является HEAD:
git reset and commit
git commit -m "add changes to edited_file"Теперь в нашем репозитории три коммита. Чтобы найти первый из них, нам нужно проверить его ID, что можно сделать, посмотрев вывод git log.
git reset and git log
git log
#commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
#Author: w3docs
#Date: Fri Nov 1 14:02:07 2019 -0800
#add changes to edited_file
#commit ab23324a6da9f0dec51ed16d3d8823f28e1a72a
#Author: w3docs
#Date: Fri Nov 1 11:31:58 2019 -0800
#change content of edited_file
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commitЭто ID начального коммита. Теперь он будет использоваться как цель для мягкого сброса. Сначала нам нужно проверить текущее состояние репозитория:
git status && git ls-files -s
git status && git ls-files -s
#On branch master
#nothing to commit, working tree clean
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_fileТеперь мы можем выполнить мягкий сброс к первому коммиту:
git reset --soft
git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
git status && git ls-files -s
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_fileВ примере выше мы выполнили мягкий сброс и вызвали комбинированную команду git status и git ls-files, которая выводит состояние репозитория. Команда git status показывает, что есть некоторые изменения в edited_file, выделяя их как изменения, подготовленные для следующего коммита. Вывод git ls-files показывает, что индекс staging остался без изменений и сохраняет SHA 32a252710639e5da6b515416fd779d0741e4561a. Давайте дополнительно рассмотрим состояние репозитория после мягкого сброса с помощью git log:
git reset
git log
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commitКак видим, вывод выше показывает, что в истории коммитов остался один коммит. Как и при всех вызовах git reset, сначала --soft сбрасывает дерево коммитов.
В отличие от --hard и --mixed, которые воздействуют на HEAD, мягкий сброс откатил дерево коммитов назад во времени.
Разница между командами reset и revert
Git revert считается более безопасным способом отмены изменений, чем git reset. При использовании reset существует высокая вероятность потерять работу. git reset не удаляет коммит, но может сделать коммит “orphaned”. Это означает, что прямого способа доступа к нему нет. В результате Git удалит все orphaned-коммиты, когда запустит внутренний сборщик мусора. По умолчанию Git запускает внутренний сборщик мусора каждые 30 дней. Orphaned-коммиты обычно находят с помощью команды git reflog.
Еще одно различие между этими двумя командами заключается в том, что git revert предназначен для отмены публичных коммитов, а git reset — для отмены локальных изменений в рабочем каталоге и индексе staging.
Недопустимость сброса публичной истории
Не используйте git reset <commit>, когда после <commit> есть снимки, которые уже перенесены в публичный репозиторий. Когда вы публикуете коммит, учитывайте, что на него полагаются и другие разработчики. Удаление коммитов, над которыми работают и другие участники команды, вызовет множество проблем. Используйте git reset <commit> только для локальных изменений. Чтобы исправить публичные изменения, используйте команду git revert.
Примеры
Используйте следующее, чтобы удалить указанный файл из области staging, не изменяя рабочий каталог. Это снимет файл с подготовки, не перезаписывая изменения:
git reset file
git reset fileИспользуйте следующее, чтобы сбросить область staging так, чтобы она соответствовала последнему коммиту, но оставить рабочий каталог без изменений. Это снимет подготовку со всех файлов, не перезаписывая изменения, и даст вам возможность заново собрать подготовленный снимок с нуля:
git reset staging area
git resetИспользуйте следующее, чтобы сбросить область staging и рабочий каталог так, чтобы они соответствовали последнему коммиту. Это снимет подготовку и перезапишет все изменения в рабочем каталоге:
git reset --hard command
git reset --hardИспользуйте следующее, чтобы переместить указатель ветки назад во времени к коммиту, сбросив область staging так, чтобы она ему соответствовала, но не затрагивая рабочий каталог:
git reset commit
git reset commitИспользуйте следующее, чтобы переместить указатель текущей ветки назад к коммиту и сбросить область staging и рабочий каталог так, чтобы они ему соответствовали:
git reset --hard commit
git reset --hard commitУдаление локальных коммитов
Как упоминалось выше, вы можете использовать команду git reset для удаления коммитов в локальном репозитории. Пример ниже демонстрирует такое использование git reset. Команда git reset --hard HEAD~2 сдвигает текущую ветку назад на два коммита и удаляет два недавно созданных снимка из истории проекта.
git add command
# Create a new file called `yourname.txt` and add some code to it
# Commit it to the project history
git add yourname.txt
git commit -m "Start to develop a project"
# Edit `yourname.txt` again and change some other tracked files, too
# Commit another snapshot
git commit -a -m "Continue developing"
# Scrap the project and remove the related commits
git reset --hard HEAD~2Снятие файлов с подготовки
Команда git reset обычно используется для создания подготовленных снимков. В примере ниже у нас есть 2 файла с именами task.txt и index.txt, которые были добавлены в репозиторий. git reset позволяет снять с подготовки изменения, не связанные со следующим коммитом.
git reset unstaging files
# Edit task.txt and index.txt
# Stage everything in the current directory
git add .
# Realize that the changes in task.txt and index.txt
# should be committed in different snapshots
# Unstage index.txt
git reset index.txt
# Commit only task.txt
git commit -m "Edit task.txt"
# Commit index.txt in a separate snapshot
git add index.txt
git commit -m "Edit index.txt"Practice
What are the features and options of the 'git reset' command in Git?