Soft git reset: —soft, —mixed(по умолчанию), —hard / Хабр
—soft, —mixed(по умолчанию), —hard / Хабр
К моему удивлению на целом хабрахабре нет ни одного поста где бы было понятно написано про 3 вида git reset
. Например, во второй по релевантности статье по запросу «git reset» автор пишет что «данное действие может быть двух видов: мягкого(soft reset) и жесткого(hard reset)». Режим --mixed
, используемый по умолчанию, почему-то не удостоился упоминания.
Ничего удивительного, что часто видишь непонимание работы этой команды. Под катом коротко и ясно расскажу о всех трёх режимах git reset
, после прочтения топика неясностей остаться не должно.
Сделанные изменения в репозитории по умолчанию имеют статус unstaged. Для того чтобы их закоммитить сначала вы должны добавить изменения в индекс, выполнив git add
. Когда вы делаете git commit
, в репозиторий будет закоммичено только то, что было в индексе.
git reset —soft
Возьмем для примера ветку:- A - B - C (master)
HEAD указывает на C и индекс совпадает с C.
После выполнения
git reset --soft B
HEAD будет указывать на B и изменения из коммита C будут в индексе, как будто вы их добавили командой git add
. Если вы сейчас выполните git commit
вы получите коммит полностью идентичный C.
git reset —mixed (по умолчанию)
Режим --mixed
используется по умолчанию, т.е. git reset --mixed = git reset
Вернемся к тем же начальным условиям:- A - B - C (master)
Выполнив
git reset --mixed B
или
git reset B
HEAD опять же будет указывать на B, но на этот раз изменения из С не будут в индексе и если вы запустите здесь git commit
ничего не произойдет т.к. ничего нет в индексе. У нас есть все изменения из С, но если запустить git status
то вы увидите, что все изменения not staged. Чтобы их закоммитить нужно сначала добавить их в индекс командой git add
и только после этого git commit
.
git reset —hard
Те же самые начальные условия:- A - B - C (master)
Последний режим --hard
также как и --mixed
переместит HEAD на В и очистит индекс, но в отличие от --mixed
жесткий reset изменит файлы в вашей рабочей директории. Если выполнить
git reset --hard B
то изменения из С, равно как и незакоммиченные изменения, будут удалены и файлы в репозитории будут совпадать с B. Учитывая то, что этот режим подразумевает потерю изменений, вы всегда должны проверять git status
перед тем как выполнить жесткий reset чтобы убедиться что нет незакоммиченных изменений(или они не нужны).
Сравнительную таблицу режимов git reset
:
меняет индекс | меняет файлы в рабочей директории | нужно быть внимательным | |
---|---|---|---|
reset --soft | нет | нет | нет |
reset [--mixed] | да | нет | нет |
reset --hard | да | да | да |
Ну и напоследок картинкой: (thx to VBauer)
Готовим git reset правильно / Хабр
Очень короткая заметка из серии «Хозяйке на заметку».
Предисловие
У плохо организованных разработчиков, типа меня, часто так бывает, что накомитишь всякого, а потом оказывается что не то и не туда, но git помнит всё и весь этот разгул анархии остаётся в истории.
Мне всегда казалось что это можно исправить, но сколько раз я не начинал искать ответа на этот вопрос — как в git удалить камиты — столько раз мне это не удавалось.
А всё потому что мои поиски приводили меня к rebase, а надо было искать reset.
На мой вкус вопрос недостаточно освещён и моя заметка призвана закрыть этот пробел.
Алгоритм
- git status // проверяем что у нас актуальная версия
- git log // ищем камит к которому мы хотим откатиться
- sudo git reset —hard 7bcdf46b14b2dacc286b0ad469d5f9022d797f68 // указываем камит начиная с которого нам надо забыть наши изменения, при этом из локальной ветки все камиты с указанного будут забыты — удалены
- git push —force origin feature/draft // заливаем локальную ветку в оригинальную (ветку сервера) — из оригинальной ветки будут удалены все «лишние» камиты
- Победа !
Если есть более правильный способ, то прошу озвучить в коментах.
PS
На самом деле камиты из репозитория не удаляются, удаляется связь этих камитов с деревом изменений, таким образом эти камиты пропадают из ветки, но git помнит всё.
PS2
Более правильного способа не нашлось ( вариации с rebase не более чем вариации ).
Но у товарищей которые считают, что в жизни всё всегда происходит одинаково и поэтому всегда надо действовать единственным правильным образом крышу сорвало от такого вольго обращения с репозиторием.
Товарищи!
Для каждой задачи свои инструменты и свои методы, иногда и git reset — годный метод, особенно когда ты на проекте один разработчик и ты хочешь откатить камиты за последние два часа работы в своей feature ветке.
git reset | Atlassian Git Tutorial
Команда git reset
— это сложный универсальный инструмент для отмены изменений. Она имеет три основные формы вызова, соответствующие аргументам командной строки --soft, --mixed, --hard
. Каждый из этих трех аргументов соответствует трем внутренним механизмам управления состоянием Git: дереву коммитов (HEAD
), разделу проиндексированных файлов и рабочему каталогу.
Git reset и три дерева Git
Чтобы понять, как используется команда git reset
, необходимо разобраться с внутренними системами управления состоянием в Git. Иногда эти механизмы называют «тремя деревьями» Git. «Деревья» — возможно, не самое точное название, поскольку это не традиционные древовидные структуры данных в строгом смысле слова. Тем не менее это структуры данных на основе узлов и указателей, которые Git использует для отслеживания истории внесения правок. Лучший способ продемонстрировать эти механизмы — создать набор изменений в репозитории и проследить его по трем деревьям.
Начнем работу с создания нового репозитория с помощью приведенных ниже команд.
$ mkdir git_reset_test $ cd git_reset_test/ $ git init . Initialized empty Git repository in /git_reset_test/.git/ $ touch reset_lifecycle_file $ git add reset_lifecycle_file $ git commit -m"initial commit" [master (root-commit) d386d86] initial commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 reset_lifecycle_file
Приведенный выше пример кода создает новый репозиторий Git с одним пустым файлом reset_lifecycle_file
. На этом этапе репозиторий имеет один коммит (d386d86
), в котором отражено добавление файла reset_lifecycle_file
.
Рабочий каталог
Первое дерево, которое мы рассмотрим, — рабочий каталог. Это дерево синхронизировано с локальной файловой системой и отображает непосредственные изменения, внесенные в содержимое файлов и каталогов.
$ echo 'hello git reset' > reset_lifecycle_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: reset_lifecycle_file
В нашем демонстрационном репозитории изменим и добавим содержимое в файл reset_lifecycle_file
. Вызов команды git status
показывает, что Git знает об изменениях в этом файле. В настоящий момент эти изменения являются частью первого дерева, рабочего каталога. Команду git status
можно использовать для отображения изменений в рабочем каталоге. Измененные файлы будут отображаться красным цветом с префиксом «modified».
Раздел проиндексированных файлов
Следующее дерево — раздел проиндексированных файлов. Это дерево отслеживает изменения рабочего каталога, которые были добавлены с помощью команды git add
, для сохранения в следующем коммите. Это дерево представляет собой сложный внутренний механизм кэширования. В целом Git пытается скрыть от пользователя подробности реализации раздела проиндексированных файлов.
Для полного просмотра состояния раздела проиндексированных файлов необходимо использовать менее известную команду Git — git ls-files
. Команда git ls-files
по сути является утилитой отладки для проверки состояния дерева раздела проиндексированных файлов.
git ls-files -s 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 reset_lifecycle_file
Здесь мы выполнили команду git ls-files
с параметром -s
(или --stage
). Без параметра -s
вывод команды git ls-files
представляет собой просто список путей и имен файлов, которые в настоящее время являются частью индекса. Параметр -s
отображает дополнительные метаданные файлов, находящихся в разделе проиндексированных файлов. Эти метаданные — биты режима проиндексированного контента, имя объекта и номер в индексе. Здесь нас интересует второе значение, имя объекта (d7d77c1b04b5edd5acfc85de0b592449e5303770
). Это стандартный хеш SHA-1 объекта Git, представляющий собой хеш содержимого файлов. В истории коммитов хранятся собственные SHA объектов для идентификации указателей на коммиты и ссылки, а в разделе проиндексированных файлов есть свои SHA объектов для отслеживания версий файлов в индексе.
Далее мы добавим измененный файл reset_lifecycle_file
в раздел проиндексированных файлов.
$ git add reset_lifecycle_file
$ git status
On branch master Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
Здесь мы вызываем команду git add reset_lifecycle_file
, которая добавляет файл в раздел проиндексированных файлов. Теперь при вызове команды git status
файл reset_lifecycle_file
отображается зеленым цветом как изменение, подлежащее коммиту («Changes to be committed»). Важно отметить, что команда git status
не отображает истинное представление раздела проиндексированных файлов. Вывод git status
отображает различия между историей коммитов и разделом проиндексированных файлов. Давайте рассмотрим содержимое раздела проиндексированных файлов на данный момент.
$ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
Видно, что SHA объекта для файла reset_lifecycle_file
изменился с e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
на d7d77c1b04b5edd5acfc85de0b592449e5303770
.
История коммитов
Последнее дерево — история коммитов. Команда git commit
добавляет изменения в постоянный снимок, который находится в истории коммитов. Этот снимок также включает состояние раздела проиндексированных файлов на момент выполнения коммита.
$ git commit -am"update content of reset_lifecycle_file" [master dc67808] update content of reset_lifecycle_file 1 file changed, 1 insertion(+) $ git status On branch master nothing to commit, working tree clean
Здесь мы создали новый коммит с комментарием «update content of resetlifecyclefile
». В историю коммитов был добавлен набор изменений. Вызов git status
в этой точке показывает, что ни в одном дереве нет ожидающих изменений. Выполнение команды git log
отобразит дерево коммитов. Теперь, когда мы проследили за этим набором изменений во всех трех деревьях, можно приступать к использованию команды git reset
.
Порядок действий
На первый взгляд, поведение команды git reset
сходно с поведением команды git checkout
. Но команда 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
. В настоящий момент репозиторий находится в состоянии открепленного указателя HEAD
.
git reset b
Команда git reset
перемещает и указатель HEAD
, и указатель ветки на заданный коммит.
Помимо обновления указателей на коммит, команда git reset
изменяет состояние трех деревьев. Указатель ссылки меняется всегда, то есть происходит обновление третьего дерева, дерева коммитов. Аргументы командной строки --soft, --mixed
и --hard
определяют, каким образом необходимо изменить деревья раздела проиндексированных файлов и рабочего каталога.
Основные параметры
По умолчанию при вызове команды git reset
используются скрытые аргументы --mixed
и HEAD
. Таким образом, выполнение команды git reset
эквивалентно выполнению команды git reset --mixed HEAD
. В этом случае HEAD
является указанием на конкретный коммит. Вместо HEAD
можно использовать любой хеш SHA-1 коммита Git.
—hard
Это самый прямой, ОПАСНЫЙ и часто используемый параметр. При передаче параметра --hard
указатели в истории коммитов обновляются на указанный коммит. Затем происходит сброс раздела проиндексированных файлов и рабочего каталога до указанного коммита. Все предыдущие ожидающие изменения в разделе проиндексированных файлов и рабочем каталоге сбрасываются в соответствии с состоянием дерева коммитов. Это значит, что любая работа, находившаяся в состоянии ожидания в разделе проиндексированных файлов и рабочем каталоге, будет потеряна.
Чтобы продемонстрировать это, продолжим работать в репозитории, созданном ранее для примера с тремя деревьями. Сначала внесем в репозиторий некоторые изменения. Выполните следующие команды:
$ echo 'new file content' > new_file $ git add new_file $ echo 'changed content' >> reset_lifecycle_file
Эти команды создали новый файл с именем new_file
и добавили его в репозиторий. Кроме того, было изменено содержимое файла reset_lifecycle_file
. А теперь давайте выполним команду git status
и посмотрим, как эти изменения повлияли на состояние репозитория.
$ git status On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) new file: new_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: reset_lifecycle_file
Мы видим, что в репозитории появились ожидающие изменения: в дереве раздела проиндексированных файлов — добавление файла new_file
, а в рабочем каталоге — изменение файла reset_lifecycle_file
.
Прежде чем двигаться дальше, изучим состояние раздела проиндексированных файлов:
$ git ls-files -s 100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
Мы видим, что файл new_file
добавлен в индекс. Мы внесли изменения в файл reset_lifecycle_file
, но его SHA в разделе проиндексированных файлов (d7d77c1b04b5edd5acfc85de0b592449e5303770
) остался прежним. Это ожидаемый результат, поскольку мы не использовали команду git add
для добавления этих изменений в раздел проиндексированных файлов. Эти изменения присутствуют и в рабочем каталоге.
Теперь давайте выполним команду git reset --hard
и изучим новое состояние репозитория.
$ git reset --hard HEAD is now at dc67808 update content of reset_lifecycle_file $ git status On branch master nothing to commit, working tree clean $ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
Здесь мы выполнили «жесткий сброс» с помощью параметра --hard
. Вывод Git сообщает, что указатель HEAD
показывает на последний коммит, dc67808
. Далее проверяем состояние репозитория с помощью команды git status
. Git сообщает, что ожидающих изменений нет. Проверяем также состояние раздела проиндексированных файлов и видим, что он был сброшен к состоянию, предшествовавшему добавлению файла new_file
. Изменения, которые мы внесли в файл reset_lifecycle_file
, а также добавленный файл new_file
уничтожены. Важно понимать: восстановить эти потерянные данные невозможно.
—mixed
Это режим работы по умолчанию. Указатели ссылок обновляются. Раздел проиндексированных файлов сбрасывается до состояния указанного коммита. Любые изменения, которые были отменены в разделе проиндексированных файлов, перемещаются в рабочий каталог. Давайте продолжим.
$ echo 'new file content' > new_file $ git add new_file $ echo 'append content' >> reset_lifecycle_file $ git add reset_lifecycle_file $ git status On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) new file: new_file modified: reset_lifecycle_file $ git ls-files -s 100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file 100644 7ab362db063f9e9426901092c00a3394b4bec53d 0 reset_lifecycle_file
В приведенном выше примере мы внесли в репозиторий некоторые изменения: добавили файл new_file
и изменили содержимое файла reset_lifecycle_file
. Затем эти изменения были добавлены в раздел проиндексированных файлов с помощью команды git add
. Теперь выполним команду reset по отношению к репозиторию в данном состоянии.
$ 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: reset_lifecycle_file Untracked files: (use "git add ..." to include in what will be committed) new_file no changes added to commit (use "git add" and/or "git commit -a") $ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
В данном случае мы выполнили «смешанный сброс». Напоминаем, что --mixed
является режимом по умолчанию и выполнение команды git reset
приведет к аналогичному результату. При изучении вывода команд git status
и git ls-files
становится видно, что раздел проиндексированных файлов был сброшен до состояния, когда в этом разделе находился только файл reset_lifecycle_file
. SHA объекта для файла reset_lifecycle_file
был сброшен к предыдущей версии.
Обратите внимание: команда git status
показывает нам, что появились изменения в файле reset_lifecycle_file
и существует неотслеживаемый файл new_file
. Это результат явного указания параметра --mixed
. Раздел проиндексированных файлов был сброшен, а ожидающие изменения были перемещены в рабочий каталог. Для сравнения, при использовании параметра --hard
были сброшены и раздел проиндексированных файлов, и рабочий каталог, что привело к потере этих обновлений.
—soft
При передаче аргумента --soft
выполняется обновление указателей, и на этом операция сброса останавливается. Раздел проиндексированных файлов и рабочий каталог остаются неизменными. Четко продемонстрировать такое поведение довольно сложно. Давайте продолжим работать с нашим демонстрационным репозиторием и подготовим его к мягкому сбросу.
$ git add reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
Untracked files:
(use "git add ..." to include in what will be committed)
new_file
Здесь мы снова воспользовались командой git add
, чтобы добавить измененный файл reset_lifecycle_file
в раздел проиндексированных файлов. Чтобы убедиться, что индекс обновлен, посмотрим на вывод команды git ls-files
. Теперь в выводе команды git status
строка «Changes to be committed» окрашена в зеленый цвет. Файл new_file
из предыдущего примера находится в рабочем каталоге как неотслеживаемый. Удалим его с помощью простой команды rm new_file
, поскольку в следующих примерах он нам больше не понадобится.
Теперь давайте мягко сбросим текущее состояние репозитория.
$ git reset --soft $ git status On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) modified: reset_lifecycle_file $ git ls-files -s 100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
Мы выполнили «мягкий сброс». Изучение состояния репозитория с помощью команд git status
и git ls-files
показывает, что ничего не изменилось. Это ожидаемый результат. Мягкий сброс влияет только на историю коммитов. По умолчанию команда git reset
вызывается с использованием HEAD
в качестве целевого коммита. Поскольку наша история коммитов уже указывала на HEAD
и мы выполнили неявный сброс до HEAD
, в реальности ничего не произошло.
Для получения более полного представления о параметре --soft
и правильного его использования нам потребуется целевой коммит, отличный от HEAD
. У нас уже есть файл reset_lifecycle_file
, находящийся в разделе проиндексированных файлов. Давайте создадим новый коммит.
$ git commit -m"prepend content to reset_lifecycle_file"
На данный момент в нашем репозитории находится три коммита. Мы выполним возврат к первому коммиту. Для этого нам потребуется идентификатор первого коммита. Его можно найти, просмотрев вывод команды git log
.
$ git log commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df Author: bitbucket Date: Fri Dec 1 15:03:07 2017 -0800 prepend content to reset_lifecycle_file commit dc67808a6da9f0dec51ed16d3d8823f28e1a72a Author: bitbucket Date: Fri Dec 1 10:21:57 2017 -0800 update content of reset_lifecycle_file commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4 Author: bitbucket Date: Thu Nov 30 16:50:39 2017 -0800 initial commit
Помните, что идентификаторы в истории коммитов уникальны для каждой системы. Это значит, что идентификатор коммита в этом примере будет отличаться от идентификатора, который вы увидите на вашей машине. Идентификатор интересующего нас коммита для этого примера — 780411da3b47117270c0e3a8d5dcfd11d28d04a4
. Это идентификатор, соответствующий первичному коммиту «initial commit». Теперь укажем его в качестве целевого для нашей операции мягкого сброса.
Прежде чем вернуться назад во времени, давайте проверим текущее состояние репозитория.
$ git status && git ls-files -s On branch master nothing to commit, working tree clean 100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
Здесь мы выполнили составную команду, состоящую из команд git status и
git ls-files -s
. Она показывает, что в репозитории есть ожидающие изменения, а файл reset_lifecycle_file
в разделе проиндексированных файлов находится в версии 67cc52710639e5da6b515416fd779d0741e3762e
. Держа в уме эти сведения, давайте выполним мягкий сброс до нашего первого коммита.
$git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4 $ git status && git ls-files -s On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) modified: reset_lifecycle_file 100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
Приведенный выше код выполняет «мягкий сброс», а также вызывает составную команду, которая включает команды git status
и git ls-files
и выводит информацию о состоянии репозитория. Мы можем изучить эту информацию и сделать несколько интересных наблюдений. Во-первых, git status
указывает на наличие изменений в файле reset_lifecycle_file
и сообщает, что эти изменения проиндексированы для выполнения коммита. Во-вторых, входные данные git ls-files
указывают, что раздел проиндексированных файлов не изменился и SHA остался прежним: 67cc52710639e5da6b515416fd779d0741e3762e.
Чтобы выяснить, что произошло при этом сбросе, давайте выполним команду git log:
$ git log commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4 Author: bitbucket Date: Thu Nov 30 16:50:39 2017 -0800 initial commit
Теперь вывод команды log показывает, что в истории коммитов находится единственный коммит. Это четко иллюстрирует, что делает параметр --soft
. Как и при всех вызовах команды git reset
, сначала происходит сброс дерева коммитов. Наши предыдущие примеры с параметрами --hard
и --mixed
воздействовали на указатель HEAD
и не возвращали дерево коммитов в предыдущее состояние. Во время мягкого сброса происходит только сброс дерева коммитов.
Это может сбивать с толку: почему команда git status
сообщает о наличии измененных файлов? Параметр --soft
не затрагивает раздел проиндексированных файлов, поэтому изменения в этом разделе сохраняются в истории коммитов. В этом можно убедиться, просмотрев вывод команды git ls-files -s
, показывающей, что SHA для файла reset_lifecycle_file
остается неизменным. Напомним, что команда git status
не показывает состояние «трех деревьев», она показывает различия между ними. В этом случае она показывает, что изменения в разделе проиндексированных файлов опережают изменения в истории коммитов, как если бы мы уже добавили их в индекс.
Разница между командами git reset и git revert
Если git revert
— «безопасный» способ отмены изменений, то git reset
— опасный. При выполнении команды git reset
есть реальный риск потерять наработки. Git reset
никогда не удаляет коммиты, однако может оставить их без родителя, т. е. указатель потеряет прямой путь для доступа к ним. Такие коммиты без родителя обычно можно найти и восстановить с помощью команды git reflog
. После запуска внутреннего «сборщика мусора» Git навсегда удалит все коммиты без родителя. По умолчанию Git запускает этот «сборщик мусора» каждые 30 дней. История коммитов — одно из «трех деревьев Git»; два оставшихся — раздел проиндексированных файлов и рабочий каталог — невозможно восстановить как коммиты. При использовании этого инструмента необходимо соблюдать осторожность, поскольку это одна из немногих команд Git, которые могут привести к потере ваших наработок.
Команда revert предназначена для безопасной отмены публичных коммитов, а git reset
— для отмены локальных изменений в разделе проиндексированных файлов и рабочем каталоге. Поскольку они предназначены для разных целей, их реализация также различается: команда reset полностью удаляет набор изменений, тогда как команда revert оставляет исходный набор изменений и использует новый коммит для применения отмены.
Не используйте reset в публичной истории
Никогда не используйте команду git reset
, если после этого в публичный репозиторий были помещены какие-либо снимки состояния. После того как коммит опубликован, на него могут полагаться другие разработчики.
При удалении коммита, после которого другие члены команды продолжили работу, могут возникнуть серьезные проблемы. Когда коллеги попытаются синхронизироваться с вашим репозиторием, кусок истории проекта будет просто отсутствовать. На схеме внизу показано, что происходит при использовании команды reset для публичного коммита. Ветка origin/master
является версией вашей локальной ветки master
в центральном репозитории.
Как только вы добавите новые коммиты после выполнения команды reset, Git будет считать, что ваша локальная история отклонилась от ветки origin/master
, а коммит слияния, необходимый для синхронизации ваших репозиториев, скорее всего, собьет с толку вашу команду и помешает ей.
Команду git reset
можно использовать только для локальных экспериментов, в которых что-то пошло не так, а не для публичных изменений. Если вам необходимо исправить публичный коммит, воспользуйтесь специально предназначенной для этой цели командой git revert
.
Примеры
git reset
Удаляет указанный файл из раздела проиндексированных файлов, но оставляет рабочий каталог без изменений. Эта команда удаляет из индекса подготовленный файл, не перезаписывая все изменения.
git reset
Сбрасывает раздел проиндексированных файлов до состояния последнего коммита, но оставляет рабочий каталог без изменений. Эта команда удаляет из индекса все подготовленные файлы, не перезаписывая все изменения, что позволяет повторно собрать снимок состояния с нуля.
git reset --hard
Сбрасывает раздел проиндексированных файлов и рабочий каталог до состояния последнего коммита. Флаг --hard
говорит Git, что нужно не только отменить изменения в разделе проиндексированных файлов, но и перезаписать все изменения в рабочем каталоге. Другими словами, эта команда уничтожит все неотправленные изменения, поэтому перед ее использованием убедитесь, что вы действительно хотите удалить ваши локальные наработки.
git reset
Перемещает указатель текущей ветки на более ранний , сбрасывает раздел проиндексированных файлов до состояния этого коммита, но не затрагивает рабочий каталог. Все изменения, внесенные после
, останутся в рабочем каталоге, чтобы вы могли повторно сделать коммит истории проекта с использованием более мелких и упорядоченных снимков состояния.
git reset --hard
Перемещает указатель текущей ветки на более ранний
и сбрасывает как раздел проиндексированных файлов, так и рабочий каталог до состояния этого коммита. Эта команда уничтожит не только неотправленные изменения, но и все коммиты, которые были добавлены после указанного коммита.
Удаление файла из раздела проиндексированных файлов
Команда git reset
часто встречается при подготовке проиндексированного снимка состояния. В следующем примере предполагается, что у вас есть два файла с именами hello.py
и main.py
, которые вы уже добавили в репозиторий.
# Редактирование файлов hello.py и main.py # Добавление в индекс всех файлов из текущего каталога git add . # Осознание того, что изменения в файлах hello.py и main.py # должны были попасть в коммиты разных состояний кода # Удаление файла main.py из индекса git reset main.py # Коммит только файла hello.py git commit -m "Make some changes to hello.py" # Коммит файла main.py в отдельном снимке состояния git add main.py git commit -m "Edit main.py"
Как видите, команда git reset
помогает соблюдать согласованность коммитов, позволяя не вносить изменения, которые не связаны со следующим коммитом.
Удаление локальных коммитов
В следующем примере показан более продвинутый сценарий использования. Он демонстрирует, что происходит, когда вы некоторое время работали над новой экспериментальной функцией, но после добавления нескольких коммитов состояния решили полностью все удалить.
# Создание нового файла `foo.py` и добавление в него кода # Коммит файла в историю проекта git add foo.py git commit -m "Start developing a crazy feature" # Повторное редактирование файла `foo.py` и изменение некоторых других отслеживаемых файлов # Коммит нового состояния git commit -a -m "Continue my crazy feature" # Удаление функции и связанных с ней коммитов git reset --hard HEAD~2
Команда git reset HEAD~2
перемещает указатель текущей ветки на два коммита назад, по сути удаляя из истории проекта оба снимка состояния, которые мы только что создали. Помните, что этот вид команды reset можно использовать только для неопубликованных коммитов. Никогда не выполняйте эту операцию, если вы уже отправили свои коммиты в общий репозиторий.
Резюме
Итак, git reset
— это мощная команда, используемая для отмены локальных изменений в репозитории Git. Git reset
работает с «тремя деревьями Git»: историей коммитов (HEAD
), разделом проиндексированных файлов и рабочим каталогом. Существует три параметра командной строки, соответствующие этим трем деревьям. Эти параметры — --soft, --mixed
и --hard
— можно передавать команде git reset
.
В этой статье мы воспользовались несколькими другими командами Git для демонстрации работы команды reset. Подробнее об этих командах см. на соответствующих страницах: git status, git log, git add, git checkout, git reflog
и git revert
.
Машина времени в git / Хабр
В последнее время мои коллеги начинают знакомство с git’ом. И один из интересующих их вопросов — как откатиться до определённой ревизии. В интернете можно найти набор команд, но хочется, чтобы было понимание каждой из них. Баловство с комадами git’а без понимания может привести к потере истории разработки.
В этой статье я хочу рассказать о командах git checkout
и git reset
с ключами --soft
и --hard
.
Итак, начнём краткий ликбез по машине времени, предоставляемой git’ом. Сперва проиллюстрируем историю:
Здесь кружочками обозначены коммиты. Чем правее коммит, тем он новее. Коммит с хэшем 6e04e..-это самый первый коммит. Одно из основных понятий, которое стоит уяснить себе новичку, — это указатели на коммиты, а точнее некоторое «прозвище» того или иного коммита. Их тьма тьмущая, например: HEAD, master, FETCH_HEAD, ORIG_HEAD и т.д. Это я перечислил крупицу стандартных прозвищ. Их можно создавать и самим, но об этом впереди.
Заострим наше внимание на двух указателях: master и HEAD. master указывает на самый старший коммит в ветке под названием master (эта ветка создаётся при инициализации репозитория). HEAD указывает на указатель master (читай, текущее состояние файлов). После появления первого коммита в репозитории, HEAD и master указывают на один и тот же коммит. И так будет продолжать до тех пор, пока не переключимся на другую ветку, не откатимся по истории, либо не совершим ряд необдуманных действий. Итак, проиллюстрируем нашу историю с указателями:
Указатель HEAD в нашем случае указывает на master, а master — на коммит d79fb… Архиважно понять, что текущее состояние неизменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD. То есть, если HEAD будет указывать на коммит с хэшем 6e04e.., то файлы окажутся в первоначальном своём состоянии. Для «движения» указателя HEAD существует команда: git checkout . Те, кто знаком хоть чуть-чуть с git’ом, узнали в этой команде переключение на другую ветку. Всё совершенно верно — при переключении на другую ветку мы просто переносим указатель HEAD на последний коммит ветки.
Перенос указателя HEAD (git checkout
)
Откат по истории коммитов:
После завершения операции checkout мы будем находиться в состоянии, в котором были два коммита назад. Это всё прекрасно — мы сделали шажок в прошлое, что-то там подглядели, но как вернуться назад? Я вот, например, не обладаю сверхпамятью, и не помню хэш самого последнего коммита (тот, который самый правый — d79fb..). Если написать git log
, то увидим историю, состоящую из трёх коммитов:
[user@localhost project]$ git log --pretty=oneline
6741a69bd121c295413be95d7597cd7409e713a0 add unit test
b3e74f50c3cc48e6b335014b6dc7e301b382a903 add readme
6e04e39d0952a2d6022502d56aaa05d5a064bea Initial commit
Неужели мы потеряли всю историю? Как узнать самый «новый» коммит? Это не проблема — есть выход, и их несколько:
- Написать команду
git log --all
. Данная команда напечатает нам всю историю, вплоть до современности, т.е. в нашем случае историю из пяти коммитов:[user@localhost project]$ git log --pretty=oneline --all d79fb5688af71b4577f450919535e7177e9d74e8 fix bug 478927e3a088d3cec489ca8810eaaca97c6ce0ff documentation 6741a69bd121c295413be95d7597cd7409e713a0 add unit test b3e74f50c3cc48e6b335014b6dc7e301b382a903 add readme 6e04ee39d0952a2d6022502d56aaa05d5a064bea Initial commit
Далее остаётся скопировать нужный нам хэш и вновь запустить машину времени:git checkout
. Но данный способ не рекомендую, так как он требует слишком много действий. - Git позволяет отслеживать все изменения указателя HEAD. Это возможно командой
git reflog
, но это уже не для новичков и используется не для поставленных нами целей. Самое грамотное — это поступить следующим образом: - Вспомнить, что указатель master указывает на самый свеженький коммит. Таким образом, возврат в исходное состояние выполняется одной командой:
git checkout master
. Вуа-ля:
Для прояснения механизма git checkout
создадим новую ветку devel:
[user@localhost project]$ git checkout -b devel
*флаг -b означает, что необходимо создать ветку с указанным именем и сразу переключится на неё.
Проиллюстрируем совершённое нами действие:
Заметим, что указатель HEAD указывает на вершину ветки devel.
Породим в новой ветке несколько коммитов. История репозитория будет выглядеть следующим образом:
Возвращение в ветку master происходит также безболезненно:
[user@localhost project]$ git checkout master
Итак, запоминаем первый пункт:
- Комнда git checkout передвигает указатель HEAD
Перенос указателя на вершину ветки (git reset ...
)
Кроме того, git позволяет двигать не только HEAD, но и континеты указатели на вершины веток. Для этого существует команда git reset
с ключами либо --soft
, либо --hard
.
- Ключ
--hard
означает, что мы теряем текущее состояние файлов и приобретаем состояние того коммита, куда был сделан reset. - Ключ
--soft
означает, что мы НЕ теряем текущее состояние проекта, но указатель на текущую ветку уже передвинут, т.е. git status нам выдаст разницу между текущим состоянием проекта (от которого мы сделали reset) и тем, на который мы сделали reset.
В обоих случаях появляется «прозвище» для коммита, с которого был совершён reset — ORIG_HEAD.
git reset --hard HEAD~2
:
git reset --soft HEAD~2
:
ORIG_HEAD полезен для редактирования неверных коммитов на локальной машине (!). Предположим, что мы хотим объединить два последних коммита в единый. Для этого, сохраняя текущее состояние файлов, переводим указатель master на два коммита назад:
[user@localhost project]$ git reset --soft HEAD~2
Посмотрим на изменения:
[user@localhost project]$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# ЧТО-ТО ТАМ ДЛЯ КОММИТА
#
Ну а теперь сделаем трюк — объединяем коммиты
[user@localhost project]$ git commit -c ORIG_HEAD
Вводим сообщение, сохраняемся. Теперь наша история выглядит вот так:
Важное замечание — ORIG_HEAD по-прежнему указывает на коммит d79fb… Если мы сейчас выполним команду git checkout ORIG_HEAD, то мы получим так называемое состояние detach HEAD. Оно характеризуется тем, что HEAD указывает не на вершину ветки, а просто на коммит. HEAD всегда должен указывать только на вершину какой-либо ветки!
Чтобы «выйти» из состояния detach HEAD достаточно просто переключиться на какую-либо ветку или создать новую ветку командой git checkout -b new_branch_name
Итак, запоминаем второй пункт:
git reset
с ключами--soft
или--hard
двигает указатель на вершину ветки, а вместе с ним и указатель HEAD.
И самое главное! Самая частая операция из вышеперечисленных при работе с git`ом — это переключение между ветками. Все остальные рассмотренные случаи встречаются редко, но тем не менее необходимо понимать всё, что происходит при их использовании!
Удачных вам путешествий по истории своего репозитория!
При подготовке материала использовались следующие источники:
Самый лучший manual — книга: ProGit
Наглядная справка по git: A Visual Git Reference (Русская версия)
UPD:
В комментариях посоветовали ещё один полезный ресурс по git`у: githowto
P.S. Благодарю за инвайт и всем желаю приятных выходных!
основы рабочего процесса и базовые команды
Статья «Git для начинающих» направлена на усовершенствование навыка его использования и исправление распространенных ошибок.
Рассмотрим различные ситуации из жизни разработчика (и не только). К каждой будет прилагаться правильное оптимальное решение.
Git init
Чтобы создать в текущей директории поддиректорию .git
, где будут храниться все файлы репозитория, выполните git init
. Если поддиректория уже существует, при повторной аналогичной команде будет выведено сообщение Reinitialized existing Git repository in
.
Git add
Вам необходимо добавить в проект файлы с определенным расширением. Можно добавить все файлы по одному, но лучше использовать *.< имя_расширения>
, чтобы включить все файлы с этим расширением:
git add *.py
Если вы хотите добавить файлы с определенным расширением и следом указать имя каталога, то можно выполнить следующую команду. Она добавит все .py файлы из подкаталогов models/directory:
git add models/\*.py
Git clean
Допустим, вы создали несколько новых файлов или папок в ветке Git, а через время оказалось, что эти файлы больше не нужны. В этом случае нужно очистить свое рабочее дерево от лишних файлов (которые были добавлены с помощью git add) следующей командой:
git clean -df
Чтобы увидеть, какие untracked-файлы будут удалены, используйте команду:
git clean -dn
Git rm
Следующая команда в подборке «Git для начинающих» поможет удалить отслеживаемые файлы:
git rm <путь к файлу>
Если удаляемый файл находится в staging-области, нужно применить специальный флаг:
git rm <путь к файлу> -f
На случай если вы хотите удалить файлы из репозитория, а в своей ФС оставить, выполните эту команду:
git rm --cached <путь к файлу>
Git branch
Вы сделали опечатку в имени ветки или хотите изменить ее имя? Следующая команда вам в помощь:
git branch -m <старое имя ветки> <новое имя ветки>
Если нужно изменить имя текущей ветки:
git branch -m <новое имя ветки>
Для изменения имени запушиной ветки потребуется выполнить несколько дополнительных шагов:
git push <имя удаленной ветки> --delete <старое имя ветки> git push <имя удаленной ветки> <новое имя ветки>
Если имя локальной ветки не совпадает с именем ветки в репозитории, используйте команду:
git push <имя удаленной ветки> <локальное имя ветки>:<имя удаленной ветки>
Git log
Чтобы увидеть историю Git коммит, применяйте команду git log. Вам будет показано много информации, но среди этого вам нужен только commit id и сообщение. Для изменения вывода выполните такую команду:
git log --oneline
Она покажет вывод, как на картинке ниже.
Первые семь символов – это сокращенный commit id, а после него идет сообщение. Commit id представлен в сокращенном виде, т. к. «полная версия» состоит из сорока шестнадцатеричных символов, указывающих на 160-разрядный SHA1 хэш.
Head -> master – означает, что мы в ветке master.
Если вы хотите увидеть сообщение конкретного автора, можно выполнить это:
git log --author="John Doe"
Git stash
Допустим, вы решили проверить код ветки перед внесением изменений. Для этого есть команда stash, которая будет держать рабочее дерево в «чистоте»:
git stash
Для отмены изменений делайте так:
git stash pop
А если передумали, делайте откат:
git stash drop
Git checkout
Чтобы переключиться на другую ветку, выполните команду:
git checkout <имя ветки>
Не забывайте сохранять изменения или делать Git коммит в текущей ветке. Если этого не сделать, изменения могут отразиться на других ветках, а вам это не нужно.
Например, есть ветка development, и вы хотите сделать ее копию, чтобы переключаться в нее напрямую. Это можно сделать так:
(development)$ git checkout -b <новое имя ветки>
Git commit
Вы закоммитили изменения, позже поняли, что допустили ошибку, или просто необходимо сделать описание более понятным. Тогда эта команда – то, что нужно:
git commit --amend -m "сообщение"
Если вы добавили новые файлы или исправили ошибку, но не хотите добавлять еще одно commit-сообщение, используйте команду с флагом —no-edit:
git commit --amend --no-edit
Вы запушили свой код в удаленный репозиторий, а затем поняли, что нужно изменить commit-сообщение. Для этого после внесения изменений сделайте принудительный push. Используя имя удаленного репозитория в качестве источника, выполните:
git push origin <имя ветки> -f
Git reset
Если вам нужно отменить последний коммит, используйте git reset. Существуют три полезных флага сброса:
Предположим, вы хотите отменить изменения, до момента добавления файла two.txt, имеющего commit id 96b037c.
Теперь давайте выполним команду git reset с флагом —soft:
git reset --soft 96b037c
git reset —soft «потеряет» все коммиты после этого идентификатора (например, 96b037c), но файлы не будут удалены, они будут находиться в промежуточной области.
Потерянные коммиты не имеют прямой ссылки для доступа к ним. Такие коммиты обычно можно найти и восстановить с помощью git reflog. Git навсегда удалит «потеряшки», когда запустится сборщик мусора (запуск производится каждые 30 дней).
При запуске git status вы увидите:
Если вы запустите git log —oneline, то увидите, что предыдущие коммиты были удалены:
Если выполнить команду reset с флагом —mixed, коммиты станут «потерянными», файлы не будут находиться в промежуточной области, но их можно будет найти в ФС. Если вы вообще не укажете флаг в команде reset, флаг —mixed будет использоваться по умолчанию.
При запуске git status увидим следующее:
Если необходимо окончательно удалить файлы, запустите команду reset с флагом —hard.
Рекомендуется сначала выполнить reset —soft, чтобы проверить затрагиваемые файлы. Если все хорошо, запускайте reset —hard.
Никогда не используйте git reset <commit-id> после того, как все запушено в общедоступный репозиторий. Удаление коммита, с которым продолжают работать другие члены команды, создает серьезные проблемы.
Git revert
Еще один способ отменить Git коммит:
git revert <commit-id> --no-commit
Проверить затронутые файлы с помощью git status. Затем git commit -m «commit-message».
Revert отменяет не коммит, а изменения возвращенного commit-id.
Например, необходимо отменить последний коммит. После revert-а статус будет выглядеть так:
Перед последним коммитом файл six.txt не был добавлен, поэтому он удален, а five.txt возвращается в предыдущее состояние. Теперь история будет выглядеть так:
Если вы хотите отменить несколько коммитов в пределах диапазона, выполните следующую команду:
git revert <старый commit-id>..<недавний commit-id> --no-commit
Если нужно отменить несколько коммитов, вне диапазона, следует указывать каждый commit-id:
git revert <commit-id-1> <commit-id-2> --no-commit
Git cherry-pick
И последняя команда из рубрики Git для начинающих – cherry-pick. Чтобы сделать коммит (например, исправление ошибки) в ветке «А», работая в ветке «Б», используйте команду cherry-pick.
Сначала необходимо переключиться на ветку с коммитом, скопировать commit-id, а потом перейти обратно в свою ветку. Затем выполните следующую команду, чтобы получить коммит в рабочей ветке:
git cherry-pick <commit-id>
Git fetch
Команда для случаев, когда необходимо собрать все существующие коммиты из нужной ветки (удаленный репозиторий) и сохранить их в local repository. После выполнения git fetch
вы получаете ссылки на ветки удаленного проекта. Суть и главное преимущество команды в том, что с ее помощью вы не сливаете все коммиты в основную ветку.
Git merge
А вот git merge
как раз для этого и нужен.
Git pull
Данная команда – симбиоз git fetch
и git merge
. Шорткод хорош, но только в том случае, если вам действительно нужно слить коммиты сразу после их сбора.
Git push
Если git pull
предназначен для мержа изменений в локальный репозиторий из удаленного, то git push
действует с точностью до наоборот: локальные изменения пушатся в удаленный репозиторий.
Также можно использовать:
git push origin master
Что означает коммит из локальной ветки master
на удаленный репозиторий origin
.
А какие еще команды вы бы посоветовали изучить новичкам?
Оригинал
10 полезных Git команд, которые облегчат работу
Ведущие разработчики поделились топом Git команд, которые незаменимы в работе с самой популярной системой контроля версий.
За последние несколько лет популярность git возросла, что позволило этой системе стать наиболее распространенной. Она используется разными программистами и командами разработчиков, начиная небольшими опен-сорс проектами и заканчивая linux kernel. Существует большое количество Git команд, которые важно знать.
Давайте же разберем топ Git команд.
Эта команда показывает всю историю коммитов, но пропускает те, в которых произошли слияния двух веток, или где был решен конфликт фиксации. Она позволяет быстро просмотреть все изменения, сделанные в проекте без скопления слияний в истории git.
$git log --no-merges commit e75fe8bf2c5c46dbd9e1bc20d2f8b2ede81f2d93 Author: John Date: Mon Jul 10 18:04:50 2017 +0300 Add new branch. commit 080dfd342ab0dbdf69858e3b01e18584d4eade34 Author: John Date: Mon Jul 11 15:40:56 2017 +0300 Added index.php. commit 2965803c0deeac1f2427ec2f5394493ed4211655 Author: John Date: Mon Jul 13 12:14:50 2017 +0300 Added css files.
Git revert создает новый коммит с содержимым, полученным из всех существующих коммитов, которые были им отменены. Если вы хотите обратить названный комит и избежать автоматических, можете использовать ‐‐no-commit или сокращение -n.
Git diff
показывает изменения между двумя коммитами, рабочими деревьями или файлами на диске. Когда несколько человек работает над одним и тем же проектом, часто происходят изменения из-за tab текстового редактора. Чтобы игнорировать различия, вызванные пробелами при сравнении строк, можно использовать команду с -w.
Показывает, как каждый файл был изменен за определенное время. Вы можете добавить 3 параметра: width для определения ширины вывода по умолчанию, name-width для установки ширины имени файла и count для ограничения вывода на первое число строк.
$ git diff --stat index.php | 83 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 40 deletions(-)
$ git diff --stat-width=10 index.php | 83 +++--- 1 file changed, 43 insertions(+), 40 deletions(-)
Сбросьте head до определенного коммита, не касаясь индексного файла и рабочего дерева. Все изменения, сделанные после этой фиксации, переносятся на этап “поставлены для коммита”. Далее вам просто нужно запустить git commit, чтобы добавить их обратно.
Эта команда создает новую ветку с именем branch-name и проверяет ее, а затем применяет к ней изменения от заданного stash и сбрасывает его. Если ни один stash не указан, используется последний. Это позволяет применять любые спрятанные изменения в более безопасной среде, которая впоследствии может быть объединена с мастером.
Команда показывает список всех удаленных и локальных ветвей. Вы можете использовать флаг ‐‐merged, чтобы видеть только те ветки, которые полностью объединены с главной. Таким образом, вы можете отслеживать свои ветки и узнавать, какие из них больше не используются, и могут быть удалены.
$ git branch -a dev * master remotes/origin/HEAD -> origin/master remotes/origin/dev
С помощью git commit ‐‐amend вы можете изменить свой предыдущий коммит, вместо того чтобы создавать новый. Если вы не внесли свои изменения в удаленную ветку, можете использовать эту команду для внесения изменений в последний коммит, добавления последних изменений и даже изменения сообщения о коммите.
Git pull ‐‐rebase заставляет git сначала вытащить изменения, а затем переустановить разблокированные коммиты поверх последней версии удаленной ветки. Параметр ‐‐rebase может использоваться для создания линейной истории, избегая ненужных фиксаций.
Когда вы используете эту команду вместо немедленного добавления всех совершенных изменений, система спрашивает, что сделать с каждым из них. Таким образом, вы сможете сами выбрать, что именно хотите закоммитить.
diff --git a/package.json b/package.json index db78332..a814f7e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ }, "devDependencies": { "bootstrap-sass": "^3.3.7", - "gulp": "^3.9.1", "jquery": "^3.1.0", "laravel-elixir": "^6.0.0-11", "laravel-elixir-vue-2": "^0.2.0", Stage this hunk [y,n,q,a,d,/,e,?]?
Источник
Также вам могут понравиться:
Практическое применение git reset —soft? — git
Я работаю с git чуть больше месяца. Действительно, я впервые использовал reset только вчера, но мягкий сброс все еще не имеет для меня большого смысла.
Я понимаю, что могу использовать мягкий сброс для редактирования фиксации без изменения индекса или рабочего каталога, как это было бы с git commit --amend
.
Действительно ли эти две команды одинаковы ( reset --soft
vs commit --amend
)? Есть ли причины использовать то или иное в практическом плане? И что еще более важно, существуют ли какие-либо другие способы использования reset --soft
, кроме внесения изменений в коммит?
git
Поделиться
Источник
AJJ
05 марта 2011 в 11:31
11 Ответов
96
git reset
-это все о перемещении HEAD
, и вообще ветвь ref .
Вопрос: А как насчет рабочего дерева и индекса?
При использовании с --soft
, перемещается HEAD
, чаще всего обновляя ветвь ref, и только HEAD
.
Это отличается от commit --amend
как:
- это не создает новую фиксацию.
- он может фактически переместить HEAD в любую фиксацию (поскольку
commit --amend
— это только о том, чтобы не перемещать HEAD, позволяя при этом повторить текущую фиксацию)
Просто нашел такой пример объединения:
- классическое слияние
- слияние поддеревьев
все в один (осьминог, так как там больше двух ветвей слилось) совершают слияние.
Томас «wereHamster» Карнеки объясняет в своей статье «Subtree Octopus merge» :
- Стратегия слияния поддеревьев может быть использована, если вы хотите объединить один проект в подкаталог другого проекта, а затем поддерживать подпроект в актуальном состоянии. Это альтернатива подмодулям git.
- Стратегия слияния octopus может быть использована для объединения трех или более ветвей. Обычная стратегия может объединить только две ветви, и если вы попытаетесь объединить больше, то git автоматически вернется к стратегии осьминога.
Проблема в том, что вы можете выбрать только одну стратегию. Но я хотел совместить эти два варианта, чтобы получить чистую историю, в которой весь репозиторий атомарно обновляется до новой версии.
У меня есть суперпроект , назовем его
projectA
, и подпроектprojectB
, который я слил в подкаталогprojectA
.
(это часть слияния поддеревьев)
Я также поддерживаю несколько местных коммитов.
ProjectA
регулярно обновляется,projectB
имеет новую версию каждые пару дней или недель и обычно зависит от конкретной версииprojectA
.Когда я решаю обновить оба проекта, я не просто вытаскиваю из
projectA
иprojectB
, поскольку это создаст два коммита для того, что должно быть атомарным обновлением всего проекта .
Вместо этого я создаю один коммит слияния , который объединяетprojectA
,projectB
и мои локальные коммиты .
Хитрая часть здесь заключается в том, что это слияние осьминога (три головы), ноprojectB
нужно объединить со стратегией поддерева . Так вот чем я занимаюсь:
# Merge projectA with the default strategy:
git merge projectA/master
# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master
Здесь автор использовал reset --hard
, а затем read-tree
, чтобы восстановить то, что первые два слияния сделали с рабочим деревом и индексом, но именно здесь может помочь reset --soft
:
Как мне повторить эти два слияния, которые сработали, т. е. мое рабочее дерево и индекс в порядке, но без необходимости записывать эти два коммита?
# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}
Теперь мы можем возобновить решение Томаса:
# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD
# And finally do the commit:
git commit
Так что, каждый раз:
- вы удовлетворены тем, что у вас получается (в терминах рабочего дерева и индекса)
- вы не удовлетворены всеми обязательствами, которые потребовались вам, чтобы добраться туда:
git reset --soft
— вот ответ.
Поделиться
VonC
05 марта 2011 в 12:36
32
Вариант использования-объединение серии локальных коммитов
«Oops. Those three commits could be just one.»
Итак, отмените последние 3 (или что-то еще) коммита (не затрагивая ни индекс, ни рабочий каталог). Затем зафиксируйте все изменения как одно целое.
E.g.
> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate
> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.
> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate
> * e1e8042 Start here.
Теперь все ваши изменения сохранены и готовы быть зафиксированы как одно целое.
Короткие ответы на ваши вопросы
Действительно ли эти две команды одинаковы ( reset --soft
vs commit --amend
)?
Есть ли причины использовать то или иное в практическом плане?
commit --amend
для добавления файлов/rm с самого последнего коммита или изменения его сообщения.reset --soft <commit>
для объединения нескольких последовательных коммитов в один новый.
И что еще более важно, существуют ли какие-либо другие способы использования reset --soft
, кроме внесения изменений в коммит?
- Смотрите другие ответы 🙂
Поделиться
Shaun Luttin
03 октября 2014 в 01:22
16
Я использую его, чтобы изменить больше, чем просто последний коммит.
Допустим, я допустил ошибку в коммите A, а затем совершил коммит B. теперь я могу только исправить B.
Так что я git reset --soft HEAD^^
, я исправляю и заново совершить, а затем повторное совершение Б.
Конечно, это не очень удобно для больших коммитов… но вы все равно не должны делать больших коммитов 😉
Поделиться
Simon
06 марта 2011 в 02:29
9
Еще один потенциальный вариант использования-это альтернатива тайнику (который некоторым людям не нравится, см., например, https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/ ).
Например, если я работаю на ветке и мне нужно срочно что-то исправить на master, я могу просто сделать:
git commit -am "In progress."
затем проверьте Мастер и сделайте исправление. Когда я закончу, я вернусь в свою ветвь и сделаю это.
git reset --soft HEAD~1
чтобы продолжать работать там, где я остановился.
Поделиться
deltacrux
24 сентября 2015 в 06:25
6
Вы можете использовать git reset --soft
для изменения версии, которую вы хотите иметь в качестве родительской для изменений, внесенных в ваш индекс и рабочее дерево. Случаи, когда это полезно, редки. Иногда вы можете решить, что изменения, внесенные в ваше рабочее дерево, должны относиться к другой ветви. Или вы можете использовать это как простой способ свернуть несколько коммитов в один (подобно squash/fold).
Смотрите этот ответ на VonC для практического примера:
Сквош первых двух коммитов в Git?
Поделиться
Johannes Rudolph
05 марта 2011 в 11:40
6
Один из возможных вариантов использования-это когда вы хотите продолжить свою работу на другой машине. Это будет работать вот так:
Проверьте новую ветвь с именем, похожим на тайник,
git checkout -b <branchname>_stash
Толкни свою заначку веткой вверх,
git push -u origin <branchname>_stash
Переключитесь на другую машину.
Потяните вниз как ваш тайник, так и существующие ветви,
git checkout <branchname>_stash; git checkout <branchname>
Теперь вы должны быть на вашей существующей ветке. Слияние изменений из ветки тайника,
git merge <branchname>_stash
Мягкий сброс существующей ветви до 1 перед слиянием,
git reset --soft HEAD^
Уберите свою заначку ветку,
git branch -d <branchname>_stash
Также удалить свой заначку филиал от происхождения,
git push origin :<branchname>_stash
Продолжайте работать со своими изменениями, как если бы вы их обычно прятали.
Я думаю, что в будущем GitHub и ко. следует предложить эту функцию «remote stash» в меньшем количестве шагов.
Поделиться
llaughlin
01 июля 2014 в 17:46
5
Большая причина использовать ‘ 27 ‘ — это переместить HEAD
в голое РЕПО.
Если вы попытаетесь использовать опцию --mixed
или --hard
, вы получите ошибку, так как вы пытаетесь изменить и работать дерево и/или индекс, который не существует.
Примечание: вам нужно будет сделать это непосредственно из голого РЕПО.
Еще раз обратите внимание: вам нужно будет убедиться, что ветвь, которую вы хотите сбросить в голом РЕПО, является активной ветвью. Если нет, следуйте ответу VonC о том, как обновить активную ветвь в голом РЕПО, когда у вас есть прямой доступ к РЕПО.
Поделиться
Hazok
17 мая 2011 в 18:24
5
Одно практическое применение заключается в том, что вы уже взяли на себя обязательства по локальному РЕПО (т. е. git commit-m) затем вы можете отменить эту последнюю фиксацию, выполнив git reset —soft HEAD ~1
Также для вашего знания, если вы уже организовали свои изменения (т. е. с git add .) затем вы можете отменить постановку, сделав git reset —mixed HEAD или я обычно также просто использовал git reset
наконец, git reset —hard стирает все, включая ваши локальные изменения. Голова ~ after говорит вам, сколько коммитов нужно сделать сверху.
Поделиться
j2emanue
21 февраля 2016 в 16:39
1
SourceTree — это git GUI, который имеет довольно удобный интерфейс для размещения только тех битов, которые вы хотите. Он не имеет ничего отдаленно похожего для внесения поправок в надлежащую редакцию.
Так что git reset --soft HEAD~1
гораздо полезнее, чем commit --amend
в этом сценарии. Я могу отменить фиксацию, вернуть все изменения обратно в промежуточную область и возобновить настройку промежуточных битов с помощью SourceTree.
Действительно, мне кажется, что commit --amend
-это более избыточная команда из двух, но git — это git и не уклоняется от подобных команд, которые делают немного разные вещи.
Поделиться
Roman Starkov
15 июля 2014 в 00:33
0
Хотя мне очень нравятся ответы в этой теме, я использую git reset --soft
для немного другого, но очень практичного сценария, тем не менее.
Я использую IDE для разработки, которая имеет хороший инструмент diff для отображения изменений (поэтапных и нестагнированных) после моей последней фиксации. Теперь большинство моих задач включает в себя несколько коммитов. Например, допустим, я делаю 5 коммитов для выполнения определенной задачи. Я использую инструмент diff в IDE во время каждой инкрементной фиксации из 1-5, чтобы посмотреть на мои изменения с последней фиксации. Я считаю, что это очень полезный способ просмотреть мои изменения перед фиксацией.
Но в конце моей задачи, когда я хочу увидеть все мои изменения вместе (до 1-го коммита), чтобы сделать самостоятельный анализ кода перед выполнением запроса на вытягивание, я буду видеть только изменения из моего предыдущего коммита (после коммита 4), а не изменения из всех коммитов моей текущей задачи.
Поэтому я использую git reset --soft HEAD~4
, чтобы вернуться к 4 коммитам. Это позволяет мне увидеть все изменения вместе. Когда я уверен в своих изменениях,я могу тогда сделать git reset HEAD@{1}
и уверенно нажать его на пульт.
Поделиться
Swanky Coder
04 июля 2018 в 01:52
0
Другой пример использования — когда вы хотите заменить другую ветвь на свою в запросе pull, например, допустим, что у вас есть программное обеспечение с функциями A, B, C в Development.
Вы развиваетесь со следующей версией и вы:
В процессе разработки только что добавленные исправления для функции B.
Вы можете объединить Development в next, но это иногда может быть неприятно, но вы также можете использовать git reset --soft origin/develop
и создать коммит с вашими изменениями, и ветвь можно объединить без конфликтов и сохранить ваши изменения.
Оказывается, что git reset --soft
-это удобная команда. Я лично часто использую его, чтобы раздавить коммиты, которые не имеют «completed work», как «WIP», поэтому, когда я открываю запрос pull, все мои коммиты понятны.
Поделиться
cnexans
23 января 2019 в 12:54
Похожие вопросы:
Инверсия git reset —soft
git reset —soft сохранит ваши текущие изменения,но переместит HEAD в другую фиксацию. Я ищу обратную операцию, чтобы HEAD оставался неподвижным, но состояние всех файлов было бы таким же, как и в…
Когда вы используете git reset —soft?
Когда вы используете git reset —soft? Я использую git сброс —жесткий все время, но никогда не кажется, чтобы найти случай, чтобы использовать сброс git —мягкий.
Разница между git reset —soft и git reset
В чем же разница между ними git reset —soft origin/dev и git reset origin/dev Надеюсь, это не такая уж большая разница, потому что я мог просто случайно что-то испортить.
Изменяет ли git reset —soft только значения hash?
В этом посте автор хорошо объясняет 3 варианта сброса git (мягкий, смешанный, жесткий): https://www.atlassian.com/git/tutorials/undoing-changes/git-reset Он использует three trees в качестве…
Является ли’ git reset —soft ‘ командой no-op?
В документации говорится, что использование опции —soft …Не касается ни индексного файла, ни рабочего дерева вообще, но требует, чтобы они были в хорошем порядке. Это оставляет все ваши…
Зачем явно вызывать git reset —soft перед git reset (—mixed)
В документах git (и многочисленных потоках SO) рекомендуется этот подход сброса: $ git reset —soft HEAD^ ;# go back to WIP state <2> $ git reset <3> . 2. Это удаляет фиксацию WIP из…
Как отменить git reset —soft, чтобы вернуть мои изменения?
У меня были некоторые локальные изменения в моем каталоге, которые я зафиксировал с помощью git commit. Позже я понял, что по ошибке мои изменения сломали мою сборку. Поэтому я сделал git reset…
git rebase <tag> / git reset —soft <tag> не работает
Я ожидал, что git reset —soft или git rebase раздавят несколько коммитов в один, но в моем случае это вообще не работает. У меня есть этот сценарий bash. Он предназначен для извлечения ветви из…
Как быстро «exit» git soft reset?
Поэтому я решил вернуться на пару коммитов назад, чтобы проверить предыдущее состояние. Поэтому я делаю это: git reset —soft {commit_hash} Есть ли способ быстро вернуться к последним изменениям,…
Как `git pull —rebase ‘отличается от’ git reset —soft origin/b`
Я пытаюсь понять git pull —rebase …чтобы сделать это, как эта команда отличается от: git fetch origin git reset —soft remotes/origin/foo git add . git commit -am bar git merge remotes/origin/foo…
для начальной фиксации
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
.
Как отменить «git reset»?
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
.
Git Reset | Учебник Atlassian Git
Команда git reset
— это сложный и универсальный инструмент для отмены изменений. У него есть три основных формы призывания. Эти формы соответствуют аргументам командной строки --soft, --mixed, --hard
. Каждый из трех аргументов соответствует трем внутренним механизмам управления состоянием Git: дереву фиксации ( HEAD
), промежуточному индексу и рабочему каталогу.
Git Reset и три дерева Git
Чтобы правильно понять использование git reset
, мы должны сначала понять внутреннюю систему управления состоянием Git.Иногда эти механизмы называют «тремя деревьями» Git. Деревья могут использоваться неправильно, поскольку они не являются строго традиционными древовидными структурами данных. Однако они представляют собой структуры данных на основе узлов и указателей, которые Git использует для отслеживания временной шкалы изменений. Лучший способ продемонстрировать эти механизмы — создать набор изменений в репозитории и проследить за ним по трем деревьям.
Для начала создадим новый репозиторий с помощью следующих команд:
$ mkdir git_reset_test $ cd git_reset_test / $ git init.Инициализированный пустой репозиторий Git в /git_reset_test/.git/ $ touch reset_lifecycle_file $ git add reset_lifecycle_file $ git commit -m "initial commit" [master (root-commit) d386d86] начальная фиксация 1 файл изменен, 0 вставок (+), 0 удалений (-) режим создания 100644 reset_lifecycle_file
В приведенном выше примере кода создается новый репозиторий git с одним пустым файлом reset_lifecycle_file
. На этом этапе репозиторий примера имеет единственную фиксацию ( d386d86
) от добавления reset_lifecycle_file
.
Рабочий каталог
Первое дерево, которое мы рассмотрим, это «Рабочий каталог». Это дерево синхронизируется с локальной файловой системой и отражает немедленные изменения, вносимые в содержимое файлов и каталогов.
$ echo 'hello git reset'> reset_lifecycle_file
$ git status
На главном сервере ветки
Изменения, не поставленные для фиксации:
(используйте «git add ...», чтобы обновить то, что будет зафиксировано)
(используйте «git checkout - ... ", чтобы отменить изменения в рабочем каталоге)
изменено: reset_lifecycle_file
В нашем демонстрационном репозитории мы изменяем и добавляем некоторый контент в файл reset_lifecycle_file
. Вызов git status
показывает, что Git знает об изменениях в файле. Эти изменения в настоящее время являются частью первого дерева «Рабочий каталог». Git status
можно использовать для отображения изменений в рабочем каталоге. Они будут отображаться красным цветом с префиксом «измененный».
Промежуточный индекс
Далее идет дерево промежуточного индекса. Это дерево отслеживает изменения рабочего каталога, которые были продвинуты с помощью git add
, для сохранения в следующем коммите. Это дерево представляет собой сложный механизм внутреннего кэширования. Git обычно пытается скрыть детали реализации промежуточного индекса от пользователя.
Для точного просмотра состояния промежуточного индекса мы должны использовать менее известную команду Git git ls-files
. Команда git ls-files
по сути является утилитой отладки для проверки состояния дерева промежуточного индекса.
git ls-files -s 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 reset_lifecycle_file
Здесь мы выполнили git ls-files
с опцией -s
или --stage. Без опции
-s
вывод git ls-files
представляет собой просто список имен файлов и путей, которые в настоящее время являются частью индекса. Параметр -s
отображает дополнительные метаданные для файлов в промежуточном индексе. Эти метаданные представляют собой биты режима поэтапного содержимого, имя объекта и номер этапа.Здесь нас интересует имя объекта, второе значение ( d7d77c1b04b5edd5acfc85de0b592449e5303770
). Это стандартный хэш SHA-1 объекта Git. Это хеш содержимого файлов. История фиксации хранит свои собственные объектные SHA для определения указателей на фиксации и ссылки, а промежуточный индекс имеет свои собственные объектные SHA для отслеживания версий файлов в индексе.
Затем мы добавим модифицированный reset_lifecycle_file
в индекс промежуточного уровня.
$ git add reset_lifecycle_file
$ git status
На главной ветке Изменения, которые необходимо зафиксировать:
(используйте "git reset HEAD... "отключить сцену)
изменено: reset_lifecycle_file
Здесь мы вызвали git add reset_lifecycle_file
, который добавляет файл в промежуточный индекс. При вызове git status
теперь reset_lifecycle_file
отображается зеленым цветом в разделе «Изменения, которые необходимо зафиксировать». Важно отметить, что git status
не является истинным представлением промежуточного индекса. Выходные данные команды git status
отображают изменения между историей фиксации и промежуточным индексом.Давайте теперь исследуем содержание промежуточного индекса.
$ GIT LS-файлов -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
Мы можем видеть, что объект ША для reset_lifecycle_file
был обновлен с e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
до d7d77c1b04b5edd5acfc85de0b592449e5303770
.
История фиксации
Последнее дерево - это история фиксации. Команда git commit
добавляет изменения в постоянный снимок, который хранится в истории фиксации.Этот снимок также включает состояние промежуточного индекса на момент фиксации.
$ git commit -am "обновить содержимое reset_lifecycle_file" [master dc67808] обновить содержимое файла reset_lifecycle_file 1 изменен, 1 вставка (+) $ git status На мастере ветки нечего фиксировать, рабочее дерево очищено
Здесь у нас есть создал новый коммит с сообщением «обновить содержимое файла resetlifecyclefile»
. Набор изменений был добавлен в историю фиксации. Вызов git status
на этом этапе показывает, что нет ожидающих изменений ни в одном из деревьев.Выполнение git log
отобразит историю фиксации. Теперь, когда мы проследили этот набор изменений по трем деревьям, мы можем начать использовать git reset
.
Как это работает
На поверхностном уровне поведение git reset
похоже на поведение git checkout
. Если git checkout
работает исключительно с указателем HEAD
ref, git reset
перемещает указатель HEAD
ref и текущий указатель ссылки ветки.Чтобы лучше продемонстрировать это поведение, рассмотрим следующий пример:
Этот пример демонстрирует последовательность коммитов на ветви master
. HEAD
ref и master
branch ref в настоящее время указывают на фиксацию d. Теперь давайте выполним и сравним как git checkout b
, так и git reset b.
git checkout b
С git checkout
, master
ref все еще указывает на d
. HEAD
ref был перемещен и теперь указывает на фиксацию b
.Репо теперь находится в состоянии « HEAD
».
git сбросить b
Для сравнения: git reset
перемещает как HEAD
, так и ссылки ветки в указанную фиксацию.
Помимо обновления указателей ссылок на фиксацию, git reset
изменит состояние трех деревьев. Модификация указателя ref всегда происходит и является обновлением третьего дерева, дерева фиксации. Аргументы командной строки --soft, --mixed
и --hard
указывают, как изменить промежуточный индекс и деревья рабочего каталога.
Основные параметры
По умолчанию вызов git reset
имеет неявные аргументы - смешанные
и HEAD
. Это означает, что выполнение git reset
эквивалентно выполнению git reset --mixed HEAD
. В этой форме HEAD
- это указанная фиксация. Вместо HEAD
можно использовать любой хэш коммита Git SHA-1.
- твердый
Это самый прямой, ОПАСНЫЙ и часто используемый вариант.При передаче --hard
Указатели ссылки истории фиксации обновляются до указанной фиксации. Затем индекс промежуточного хранения и рабочий каталог сбрасываются в соответствии с указанным коммитом. Любые ранее ожидающие изменения в промежуточном индексе и рабочем каталоге сбрасываются, чтобы соответствовать состоянию дерева фиксации. Это означает, что любая незавершенная работа, которая висела в промежуточном индексе и рабочем каталоге, будет потеряна.
Чтобы продемонстрировать это, давайте продолжим работу с репо из трех деревьев, которое мы создали ранее.Сначала внесем некоторые изменения в репо. Выполните следующие команды в репозитории примера:
$ echo 'новое содержимое файла'> new_file $ git add new_file $ echo 'измененное содержимое' >> reset_lifecycle_file
Эти команды создали новый файл с именем new_file
и добавили это в репо. Кроме того, содержимое reset_lifecycle_file
будет изменено. С этими изменениями давайте теперь исследуем состояние репо, используя git status
.
$ git status В мастере ветки Изменения, которые необходимо зафиксировать: (используйте «git reset HEAD ...» для отмены постановки) новый файл: new_file Изменения, не поставленные для фиксации: (используйте «git add ...», чтобы обновить то, что будет совершено) (используйте "git checkout - ...", чтобы отменить изменения в рабочем каталоге) изменено: reset_lifecycle_file
Мы видим, что в репо есть отложенные изменения. В дереве промежуточного индекса есть ожидающие изменения для добавления new_file
, а в рабочем каталоге есть ожидающие изменения для модификаций reset_lifecycle_file
.
Перед тем, как двигаться дальше, давайте также проверим состояние индекса промежуточного уровня:
$ git ls-files -s 100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file 100644 d7d77c1b04b5edd5acfile2000 . Мы обновили reset_lifecycle_file
, но промежуточный индекс SHA ( d7d77c1b04b5edd5acfc85de0b592449e5303770
) остался прежним.Это ожидаемое поведение, потому что не использовалось git add
для продвижения этих изменений в промежуточный индекс. Эти изменения существуют в рабочем каталоге. Давайте теперь выполним git reset --hard
и проверим новое состояние репозитория.
$ git reset --hard HEAD теперь находится на dc67808 обновить содержимое reset_lifecycle_file $ git status На главном сервере нечего фиксировать, рабочее дерево очистить $ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file 9000
Здесь мы выполнили «жесткий сброс» с использованием параметра --hard
.Git отображает вывод, указывающий, что HEAD
указывает на последнюю фиксацию dc67808
. Затем мы проверяем состояние репо с помощью git status
. Git указывает, что ожидающих изменений нет. Мы также проверяем состояние индекса промежуточного уровня и видим, что он был сброшен до точки до добавления new_file
. Наши модификации reset_lifecycle_file
и добавление new_file
были уничтожены. Эту потерю данных нельзя отменить, это очень важно принять к сведению.
- смешанный
Это рабочий режим по умолчанию. Указатели ref обновлены. Индекс промежуточного хранения сбрасывается до состояния указанной фиксации. Любые изменения, которые были отменены из промежуточного индекса, перемещаются в рабочий каталог. Давайте продолжим.
$ echo 'новое содержимое файла'> new_file $ git add new_file $ echo 'append content' >> reset_lifecycle_file $ git add reset_lifecycle_file $ git status На главном сервере ветки Изменения, которые необходимо зафиксировать: (используйте "git reset HEAD... "для выключения) новый файл: новый_файл изменен: reset_lifecycle_file $ git ls-files -s 100644 8e66654a5477b1bf4765946147c49509a431f963 0 новый_файл 100644 7ab362db063f9e94262c00a3394b4be
В приведенном выше примере мы внесли некоторые изменения в репозиторий. Снова мы добавили new_file
и изменили содержимое reset_lifecycle_file
. Затем эти изменения применяются к промежуточному индексу с помощью git add
. Теперь, когда репо находится в этом состоянии, мы выполним сброс.
$ git reset --mixed $ git status В мастере ветки Изменения, не поставленные для фиксации: (используйте «git add ...», чтобы обновить то, что будет зафиксировано) (используйте «git checkout - ...», чтобы отменить изменения в рабочем каталоге) изменено: reset_lifecycle_file Не отслеживаемые файлы: (используйте "git add ...", чтобы включить в то, что будет зафиксировано) new_file изменения не добавлены в фиксацию (используйте "git add" и / или "git commit -a") $ git ls-files -s 100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file
Здесь мы выполнили «смешанный сброс».Повторюсь, --mixed
- это режим по умолчанию и тот же эффект, что и при выполнении git reset
. Изучение выходных данных git status
и git ls-files
показывает, что индекс промежуточного уровня был сброшен до состояния, в котором reset_lifecycle_file
является единственным файлом в индексе. Объект SHA для reset_lifecycle_file
был сброшен до предыдущей версии.
Здесь важно обратить внимание на то, что git status
показывает нам, что есть изменения в reset_lifecycle_file
и есть неотслеживаемый файл: new_file
.Это явное поведение - смешанное
. Промежуточный индекс был сброшен, а ожидающие изменения были перемещены в рабочий каталог. Сравните это со случаем сброса --hard
, когда индекс промежуточного уровня был сброшен, а рабочий каталог также был сброшен, и эти обновления были потеряны.
- мягкий
Когда передается аргумент --soft
, указатели ref обновляются, и на этом сброс останавливается. Промежуточный индекс и рабочий каталог остаются нетронутыми.Такое поведение трудно четко продемонстрировать. Давайте продолжим наше демонстрационное репо и подготовим его к мягкому сбросу.
$ git add reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
$ git status
"Сбросить
на
на главном сервере ветки
. unstage)
изменено: reset_lifecycle_file
Файлы без отслеживания:
(используйте "git add... "включить в то, что будет совершено)
new_file
Здесь мы снова использовали git add
для продвижения модифицированного reset_lifecycle_file
в индекс промежуточного уровня. Мы подтверждаем, что индекс был обновлен с выходными данными git ls-files
. В выходных данных git status
теперь отображается зеленый цвет «Изменения, которые необходимо зафиксировать». new_file
из наших предыдущих примеров находится в рабочем каталоге как неотслеживаемый файл.Давайте быстро выполним rm new_file
, чтобы удалить файл, так как он нам не понадобится в следующих примерах.
Теперь, когда репозиторий находится в этом состоянии, мы выполняем программный сброс.
$ git reset --soft $ git status На главном сервере ветки Изменения, которые необходимо зафиксировать: (используйте "git reset HEAD ..." для отключения) изменено: reset_lifecycle_file $ git ls-files -s 100644 67cc52710639e5da6b515416fd779d0741e37cycle_23e reset_life60006
Мы выполнили «мягкий сброс».Изучение состояния репо с помощью git status
и git ls-files
показывает, что ничего не изменилось. Это ожидаемое поведение. Мягкий сброс приведет к сбросу только истории фиксации. По умолчанию вызывается git reset
с HEAD
в качестве целевой фиксации. Поскольку наша история фиксации уже находилась на HEAD
, и мы неявно сбросили до HEAD
, на самом деле ничего не произошло.
Чтобы лучше понять и использовать --soft
, нам нужен целевой коммит, отличный от HEAD
.У нас есть reset_lifecycle_file
, ожидающих в индексе промежуточного хранения. Создадим новый коммит.
$ git commit -m "добавить содержимое в reset_lifecycle_file"
На этом этапе в нашем репо должно быть три коммита. Мы вернемся назад к первому коммиту. Для этого нам понадобится ID первого коммита. Это можно найти, просмотрев вывод git log
.
$ git log commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df Автор: bitbucket Дата: 1 декабря, пятница, 15:03:07 2017 -0800 добавить содержимое в файл reset_lifecycle_file commit dc67808a6da9f0dec51ed16d3dda_file_file_commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4 Автор: bitbucket Дата: Чт, 30 ноября, 16:50:39 2017 -0800 начальная фиксация
Имейте в виду, что идентификаторы истории фиксации будут уникальными для каждой системы.Это означает, что идентификаторы фиксации в этом примере будут отличаться от того, что вы видите на своем персональном компьютере. Идентификатор фиксации, который нас интересует в этом примере, - 780411da3b47117270c0e3a8d5dcfd11d28d04a4
. Это идентификатор, соответствующий «начальной фиксации». Как только мы найдем этот идентификатор, мы будем использовать его в качестве цели для нашего программного сброса.
Прежде чем отправиться в прошлое, давайте сначала проверим текущее состояние репо.
$ git status && git ls-files -s На мастере ветки нечего фиксировать, рабочее дерево очищено 100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file
Здесь мы выполняем комбинированную команду из файлов git status и . это показывает нам, что в репо есть незавершенные изменения, а reset_lifecycle_file
в промежуточном индексе имеет версию 67cc52710639e5da6b515416fd779d0741e3762e
.Имея это в виду, давайте выполним мягкий сброс до нашей первой фиксации.
$ git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4 $ git status && git ls-files -s В главном узле ветки Изменения, которые необходимо зафиксировать: (используйте команду «git reset HEAD ...» для отмены постановки) изменено: reset_lifecycle_file 9646396e3e3e07e08e06e6e6e6e6e7e07e6e07e6e6e6e07ec5e /
Приведенный выше код выполняет «мягкий сброс», а также вызывает комбинированную команду git status
и git ls-files
, которая выводит состояние репозитория.Мы можем изучить вывод состояния репо и отметить некоторые интересные наблюдения. Во-первых, git status
указывает на наличие изменений в reset_lifecycle_file
и выделяет их, указывая на то, что это изменения, подготовленные для следующей фиксации. Во-вторых, ввод git ls-files
указывает на то, что индекс промежуточного уровня не изменился и сохранил ранее использованный SHA 67cc52710639e5da6b515416fd779d0741e3762e.
Чтобы дополнительно прояснить, что произошло при этом сбросе, давайте исследуем журнал git :
$ git log commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4 Автор: bitbucket Дата: Чт, 30 ноября, 16:50:39,
, начальная фиксация
log теперь показывает, что в истории фиксаций есть одна фиксация.Это помогает наглядно проиллюстрировать, что сделал - soft
. Как и во всех вызовах git reset
, первое действие, которое выполняет сброс, - это сбросить дерево фиксации. Наши предыдущие примеры с --hard
и --mixed
были против HEAD
и не вернули дерево фиксации назад во времени. Это все, что происходит во время мягкого сброса. Тогда это может сбивать с толку, почему git status
указывает на наличие измененных файлов. --soft
не затрагивает промежуточный индекс, поэтому обновления нашего промежуточного индекса следовали за нами во времени через историю фиксации. Это может быть подтверждено выводом git ls-files -s
, показывающим, что SHA для reset_lifecycle_file
не изменился. Напоминаем, что git status
не показывает состояние «трех деревьев», он по существу показывает разницу между ними. В этом случае отображается, что промежуточный индекс опережает изменения в истории фиксации, как если бы мы их уже подготовили.
Сброс против возврата
Если git revert
является «безопасным» способом отмены изменений, вы можете рассматривать git reset
как опасный метод. Есть реальный риск потерять работу с git reset
. Git reset
никогда не удалит фиксацию, однако коммиты могут стать «осиротевшими», что означает отсутствие прямого пути от ссылки для доступа к ним. Эти потерянные коммиты обычно можно найти и восстановить с помощью git reflog
. Git навсегда удалит все потерянные коммиты после запуска внутреннего сборщика мусора.По умолчанию Git настроен на запуск сборщика мусора каждые 30 дней. История фиксации - одно из «трех деревьев git», два других, промежуточный индекс и рабочий каталог не так постоянны, как коммиты. Следует соблюдать осторожность при использовании этого инструмента, поскольку это одна из немногих команд Git, которая может привести к потере вашей работы.
В то время как возврат предназначен для безопасной отмены публичной фиксации, git reset
предназначен для отмены локальных изменений в промежуточном индексе и рабочем каталоге.Из-за различных целей эти две команды реализованы по-разному: при сбросе полностью удаляется набор изменений, а при возврате сохраняется исходный набор изменений и используется новая фиксация для применения отмены.
Не сбрасывать общедоступную историю
Никогда не следует использовать git reset
, если какие-либо снимки после того, как были отправлены в общедоступный репозиторий. После публикации фиксации вы должны предположить, что другие разработчики полагаются на нее.
Удаление фиксации, которую продолжали развивать другие члены команды, создает серьезные проблемы для совместной работы.Когда они попытаются синхронизироваться с вашим репозиторием, это будет выглядеть так, как будто часть истории проекта внезапно исчезла. Приведенная ниже последовательность демонстрирует, что происходит, когда вы пытаетесь сбросить публичную фиксацию. Ветвь origin / master
- это версия центрального репозитория вашей локальной ветки master
.
Как только вы добавите новые коммиты после сброса, Git подумает, что ваша локальная история отличается от origin / master
, и фиксация слияния, необходимая для синхронизации ваших репозиториев, скорее всего, запутает и расстроит вашу команду.
Дело в том, что вы используете git reset
для локального эксперимента, который не прошел, а не для опубликованных изменений. Если вам нужно исправить публичную фиксацию, команда git revert
была разработана специально для этой цели.
Примеры
git reset
Удалить указанный файл из промежуточной области, но оставить рабочий каталог без изменений. Это деактивирует файл без перезаписи изменений.
git reset
Сбросьте промежуточную область, чтобы она соответствовала самой последней фиксации, но оставьте рабочий каталог без изменений.Это деактивирует все файлы без перезаписи каких-либо изменений, что дает вам возможность воссоздать поэтапный снимок с нуля.
git reset --hard
Сбросить промежуточную область и рабочий каталог в соответствии с последней фиксацией. Помимо неустановленных изменений, флаг --hard
сообщает Git о необходимости перезаписать все изменения в рабочем каталоге. Другими словами: это стирает все незафиксированные изменения, поэтому убедитесь, что вы действительно хотите выбросить свои локальные разработки, прежде чем использовать его.
git reset
Переместите текущую ветку назад на commit
, сбросьте промежуточную область в соответствие, но оставьте рабочий каталог в покое. Все изменения, внесенные с
, будут находиться в рабочем каталоге, что позволит вам повторно зафиксировать историю проекта, используя более чистые и атомарные снимки.
git reset --hard
Переместите текущую ветку назад на
и сбросьте как промежуточную область, так и рабочий каталог, чтобы они совпадали.Это стирает не только незафиксированные изменения, но и все последующие коммиты.
Деактивация файла
Команда git reset
часто встречается при подготовке поэтапного снимка. В следующем примере предполагается, что у вас есть два файла с именами hello.py
и main.py
, которые вы уже добавили в репозиторий.
# Отредактируйте hello.py и main.py # Поместите все в текущий каталог git add. # Поймите, что изменения в привет.py и main.py # должны быть зафиксированы в разных снимках # Unstage main.py git reset main.py # Зафиксировать только hello.py git commit -m "Внести некоторые изменения в hello.py" # Зафиксировать main.py в отдельном снимке git add main.py git commit -m "Edit main.py"
Как видите, git reset
помогает сфокусироваться на ваших коммитах, позволяя отменять изменения, не связанные со следующим коммитом.
Удаление локальных коммитов
В следующем примере показан более сложный вариант использования.Он демонстрирует, что происходит, когда вы какое-то время работали над новым экспериментом, но решили полностью выбросить его после совершения нескольких снимков.
# Создайте новый файл с именем `foo.py` и добавьте в него код # Зафиксируйте его в истории проекта git add foo.py git commit -m" Начать разработку безумной функции "# Отредактируйте` foo.py` снова и изменить некоторые другие отслеживаемые файлы # Зафиксируйте еще один снимок git commit -a -m "Продолжить мою сумасшедшую функцию" # Решите отказаться от функции и удалить связанные коммиты git reset --hard HEAD ~ 2
git Команда reset HEAD ~ 2
перемещает текущую ветку назад на две фиксации, эффективно удаляя два только что созданных снимка из истории проекта.Помните, что такой сброс следует использовать только для неопубликованных коммитов. Никогда не выполняйте описанную выше операцию, если вы уже отправили свои коммиты в общий репозиторий.
Резюме
Напомним, что git reset
- мощная команда, которая используется для отмены локальных изменений состояния репозитория Git. Сброс Git
работает на «Трех деревьях Git». Этими деревьями являются история фиксации ( HEAD
), промежуточный индекс и рабочий каталог. Есть три параметра командной строки, соответствующие трем деревьям.Параметры --soft, --mixed
и --hard
могут быть переданы в git reset
.
В этой статье мы использовали несколько других команд Git, чтобы продемонстрировать процессы сброса. Узнайте больше об этих командах на их отдельных страницах: git status, git log, git add, git checkout, git reflog,
и git revert
.
.
git-сброс (1)
В таблицах ниже показано, что происходит при работе:
git reset --option target
для сброса HEAD
на другую фиксацию ( target
) с другим
сбросить параметры в зависимости от состояния файлов.
В этих таблицах A
, B
, C
и D
- это некоторые разные состояния
файл. Например, первая строка первой таблицы означает, что если
файл находится в состоянии A
в рабочем дереве, в состоянии B
в индексе, в
состояние C
в HEAD
и в состоянии D
в цели, затем git reset --soft
цель
оставит файл в рабочем дереве в состоянии A
и в
индекс в состоянии Б
.Он сбрасывает (т. Е. Перемещает) HEAD
(т. Е. Кончик
текущая ветка, если вы в одной) на цель
(в которой есть файл
в состоянии D
).
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
A B C D - мягкая A B D
- смешанный A D D
- жесткий D D D
--merge (запрещено)
--keep (запрещено)
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
A B C C - мягкая A B C
- смешанный A C C
- жесткий C C C
--merge (запрещено)
- сохранить A C C
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
B B C D - мягкий B B D
- смешанный B D D
- жесткий D D D
- объединить D D D
--keep (запрещено)
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
B B C C - мягкий B B C
- смешанный B C C
- жесткий C C C
- объединить C C C
- Хранить B C C
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
B C C D - мягкая B C D
- смешанный B D D
- жесткий D D D
--merge (запрещено)
--keep (запрещено)
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
B C C C - мягкая B C C
- смешанный B C C
- жесткий C C C
- объединить B C C
- содержание B C C
reset --merge
предназначен для использования при сбросе из конфликтующей
слить.Любая операция слияния гарантирует, что файл рабочего дерева
участвующий в слиянии не имеет локальных изменений по отношению к индексу
перед запуском и записывает результат в рабочее дерево. Так что если
мы видим некоторую разницу между индексом и целью, а также
между индексом и рабочим деревом, значит, мы не
сброс из состояния, которое осталось после неудачной операции слияния
с конфликтом. Поэтому в этом случае мы не используем опцию --merge
.
reset --keep
предназначен для использования при удалении некоторых из последних
фиксируется в текущей ветке, сохраняя изменения в рабочем
дерево. Если бы между изменениями в коммите могли быть конфликты, мы
хотим удалить, и изменения в рабочем дереве, которые мы хотим сохранить,
сброс запрещен. Вот почему это запрещено, если есть оба
изменения между рабочим деревом и HEAD
, а также между HEAD
и
цель.На всякий случай также запрещено, когда есть не объединенные
записи.
В следующих таблицах показано, что происходит, когда не объединены
записей:
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
X U A B - мягкий (запрещено)
- смешанный X B B
- жесткий B B B
- слияние B B B
--keep (запрещено)
рабочий индекс HEAD целевой рабочий индекс HEAD
-------------------------------------------------- -
X U A A - мягкое (запрещено)
- смешанный X A A
- жесткий А А А
- объединить A A A
--keep (запрещено)
X
означает любое состояние, а U
означает не объединенный индекс.
.