Когда что-то пошло не так: git reset

Андрей Коваленко

В этой статье мы рассмотрим команду git reset, которая позволит отменить неправильные/неудачные изменения, сделанные локально, а также «откатить» неудачный merge (слияние).

Содержание:
Постановка задачи
Основные сведения
Примеры использования
«Странная» нотация (~ и ^)
Заключение

Постановка задачи

Допустим, вы работаете над проектом, и сделали изменения, которые теперь хотите отменить. Можно выполнить git status и увидеть все сделанные изменения, после чего вручную отменить ненужные. Но что если изменений очень много (такое случается после неудачных «ручных» мерджей)? В таком случае вам поможет команда git reset. Эта команда подобна скальпелю — она очень острая/эффективная, и именно поэтому ее следует использовать с осторожностью.

Далее рассмотрим основные сценарии ее использования.

Основные сведения

Вообще говоря, назначение git reset — взять текущую ветку и «сбросить» (переназначить, англ. reset) ее так, чтобы она указывала на какой-либо другой коммит; в частности, засинхронизировать индекс и рабочее дерево репозитория. Более конкретно, если ваша ветка master (в настоящее время в статусе checkout) выглядит так:

- A - B - C (HEAD, master)

где A, B, C — последовательные коммиты ветки master. И вы хотите, чтобы master заканчивалась коммитом B, а не С, то вы используете git reset <B> (в данном случае <B> — это хэш коммита B), чтобы переместить ее туда:

- A - B (HEAD, master)     # - коммит C все еще здесь, но никакая ветка больше не указывает на этот коммит

Важный момент: это поведение существенно отличается от команды git checkout <B>, которая привела бы к такому результату:

- A - B (HEAD) - C (master)

В результате вы получите состояние с отсоединенным (detached) указателем HEAD. Итак, HEAD, ваше рабочее дерево, индекс — все соответствуют коммиту B, но ветка master была оставлена на коммите C. Если в данном состоянии репозитория вы сделаете новый коммит D, вы получите такую конфигурацию, которая, скорее всего, отличается от того, чего вы хотели достичь:

Запомните главное: reset не делает коммитов, он всего лишь меняет ветку (которая указывает на коммит), чтобы она указывала на другой коммит. Другими словами, что бы ни значило само слово reset, логически оно определяет, что происходит с вашим индексом и рабочим деревом.

Примеры использования

Рассмотрим некоторые основные варианты использования git reset.

Команду можно использовать для самых разных целей; общая идея состоит в том, что все они включают сброс ветки, индекса и/или рабочего дерева с тем, чтобы они указывали/соответствовали заданному коммиту.

Будьте внимательны

  • Ключ –hard действительно может привести к потере результатов работы. Он модифицирует рабочее дерево репозитория.
  • git reset [options] <commit> может привести (если опустить технические детали) к потере коммитов. В условном примере, приведенном выше, мы потеряли коммит C. Он по-прежнему находится в репозитории, и вы можете найти его командамиgit reflog show HEAD или git reflog show master, но на самом деле к нему нельзя получить доступ из какой-либо ветки.
  • git окончательно и автоматически удаляет такие коммиты по истечении 30 дней, но до этого момента вы можете восстановить коммит C путем указания (checkout) ветки на этот коммит (git checkout C; git branch <new branch name>).

Аргументы

Если сократить документацию git reset до одного абзаца, наиболее частый путь использования команды — git reset [<коммит>] [пути...], что сбросит данные пути до их состояния из данного коммита. Если пути не указаны, все дерево будет сброшено («ресетнуто»), а если коммит не указан, подразумевается коммит HEAD (текущий коммит).

Это стандартный паттерн команд git (включая checkout, diff, log, хотя семантика может несколько отличаться), так что сюрпризов здесь не предвидится.

Например, git reset <другая-ветка> <путь/к/директории> сбрасывает все в пути <путь/к/директории> к его состоянию в ветке <другая-ветка>; git reset —  сбрасывает текущую директорию к ее состоянию в HEAD, а простой git reset сбрасывает все дерево к его состоянию в HEAD.

Основные опции для работы с деревом и индексом

Всего есть четыре опции для управления тем, что происходит с вашим рабочим деревом и индексом во время исполнения команды reset.

Напомним, «индекс» — это «сцена» git’а, условное место, где происходят изменения, когда вы, например, выполняете команду git add, подготавливая свой коммит.

  • –hard — заставляет весь контент соответствовать коммиту, к которому вы сбрасываете (ресетите). Наверное, это самый простой для понимания ключ (опция). Все ваши локальные изменения исчезают. Основной случай применения — удалить вашу последнюю работу без переключения коммитов: git reset --hard означает git reset --hard HEAD, то есть, не изменяя ветку, избавиться от всех локальных изменений. Другой случай применения — перенести ветку из одного места в другое и держать при этом индекс/рабочее дерево в синхронизированном состоянии.

Напоминаем, эта опция действительно может привести к потере вашей работы, поскольку она модифицирует ваше рабочее дерево. Будьте на 100% уверены, что действительно хотите отказаться от локальных изменений, перед тем как выполнять reset --hard.

  • –mixed — это опция по умолчанию, то есть git reset означает git reset --mixed. Эта команда сбрасывает индекс, но не рабочее дерево. Это означает, что все ваши файлы остаются неизменными, но любые изменения между текущим коммитом и коммитом, к которому вы сбрасываете, будут показаны как локальные изменения (или неотслеживаемые/untracked файлы) во время выполнения команды git status. Используйте эту опцию, когда вы вдруг поняли, что сделали неправильные коммиты, но все же хотите оставить некоторые изменения, которые сделали, чтобы их исправить и закоммитить снова. Чтобы закоммитить, вам придется добавить файлы в индекс снова командой git add.
  • --soft не изменяет индекс или рабочее дерево. Все ваши файлы остаются нетронутыми, как если бы вы использовали --mixed, но все изменения показываются как готовые к коммиту во время выполнения git status (то есть, зачекиненные и готовые к коммиту).

Используйте эту опцию, если осознали, что сделали несколько плохих коммитов, но сейчас состояние проекта правильное, и все что вам нужно — перекоммитить его еще раз. Индекс не тронут, поэтому вы можете закоммитить немедленно — результирующий коммит будет иметь то же содержание, которое было до выполнения reset.

  • –merge — эта опция добавлена в git относительно недавно, ее предназначение — прервать неудачное (failed) слияние (merge). Это бывает необходимо, потому что git merge в принципе позволяет осуществить попытку слияния с «грязным» рабочим деревом (грязным — в смысле «с локальными изменениями»), если эти изменения содержатся в файлах, не затронутых слиянием.

git reset --merge сбрасывает индекс (подобно опции --mixed — все изменения показываются как локальные изменения) и сбрасывает все файлы, затронутые слиянием, но не трогает остальные файлы. Это может позволить восстановить все до состояния, которое было до «плохого» слияния. Скорее всего, вы будете использовать это как git reset --merge (что означает git reset --merge HEAD), потому что хотите сбросить только изменения, вызванные попыткой слияния, а не переместить ветку (указатель HEAD не изменился, поскольку слияние было неудачным).

Если говорить более конкретно, представим, что мы поменяли файлы A и B и пытаемся «смерджиться» в ветку с измененными файлами C и D. Слияние «фейлится» по какой-либо причине, и мы решаем прервать его. Используем git reset --merge. Это приводит C и D обратно к состоянию, которое у них было в HEAD, но не затрагивает изменения в A и B, поскольку они не участвовали в попытке слияния.

Странная нотация (~ и ^)

При использовании команды git reset «странная нотация» бывает особенно полезной, поскольку позволяет указывать коммиты, близкие к HEAD, без необходимости написания их хэшей:

  • HEAD~ — это сокращенная запись HEAD~1 и означает первого родителя коммита. HEAD~2 означает первого родителя у первого родителя коммита. HEAD~n можно понимать как «n коммитов перед HEAD» или «n-ый предок HEAD».
  • HEAD^ (или HEAD^1) тоже означает первого родителя коммита. Но вот HEAD^2 означает второго родителя коммита. Помните, коммит, представляющий собой нормальное слияние (merge), имеет двух родителей: первый родитель — это коммит, в который осуществляется слияние, а второй родитель — коммит, который был слит. Вообще говоря, слияния могут иметь произвольно много родителей (octopus merges).
  • Операторы ^ и ~ могут выстраиваться в линию, например, HEAD~3^2 будет означать второго родителя предка HEAD третьего уровня; HEAD^^2 — второго родителя первого родителя HEAD; HEAD^^^ — это просто эквивалент HEAD~3.

Заключение

В этой статье мы рассмотрели команду git, которая позволяет отменить/исправить локальные изменения — git reset. Изучили различные условия, в которых приходится вносить исправления: отмена всего, частичная отмена, отмена в случае слияния. Освежили полезную «странную» нотацию для указания коммита без хэша — «~ и ^» нотацию.

Более подробно про нашу тему в этом видео: как различить soft, mixed и hard Git Reset:

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

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

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023