Говоря простым языком, что делает «git reset»?

674

Я видел интересные посты, объясняющие тонкости о git reset.

К сожалению, чем больше я об этом читаю, тем больше мне кажется, что я не до конца понимаю. Я родом из SVN, а Git - совершенно новая парадигма. Я легко получил Mercurial, но Git гораздо более технический.

Я думаю, что git resetблизко hg revert, но, кажется, есть различия.

Так что же git resetделать? Пожалуйста, включите подробные объяснения о:

  • варианты --hard, --softи --merge;
  • странные обозначения, которые вы используете с HEADтакими как HEAD^и HEAD~1;
  • конкретные варианты использования и рабочие процессы;
  • последствия для рабочей копии, HEADи ваш глобальный уровень стресса.
е-удовлетворяться
источник
17
Я думаю, что Visual Git Reference дает хорошее представление о том, что происходит при использовании общих команд git.

Ответы:

993

В общем, git resetфункция заключается в том, чтобы взять текущую ветвь и сбросить ее так, чтобы она указывала куда-то еще, и, возможно, перенести индекс и рабочее дерево. Конкретнее, если ваша основная ветвь (в настоящее время проверенная) выглядит так:

- A - B - C (HEAD, master)

и вы понимаете, что хотите, чтобы мастер указывал на B, а не на C, вы будете git reset Bперемещать его туда:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

Отступление: это отличается от проверки. Если вы бежите git checkout B, вы получите это:

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

Вы оказались в отдельном состоянии HEAD. HEAD, рабочее дерево, индекс все совпадают B, но основная ветвь осталась в C. Если вы сделаете новый коммит Dв этот момент, вы получите это, что, вероятно, не то, что вы хотите:

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

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

Случаи использования

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

Вещи, чтобы быть осторожным

  • --hardможет заставить вас действительно потерять работу. Это изменяет ваше рабочее дерево.

  • git reset [options] commitможет привести к потере коммитов. В приведенном выше примере с игрушкой мы потеряли коммит C. Он все еще находится в репо, и вы можете найти его, посмотрев на git reflog show HEADили git reflog show master, но на самом деле он больше недоступен из любой ветки.

  • Git навсегда удаляет такие коммиты через 30 дней, но до тех пор вы можете восстановить C, снова указав на него ветку ( git checkout C; git branch <new branch name>).

аргументы

Перефразируя man-страницу, чаще всего используется форма git reset [<commit>] [paths...], которая сбрасывает указанные пути в их состояние из данного коммита. Если пути не указаны, все дерево сбрасывается, а если фиксация не указана, она считается HEAD (текущая фиксация). Это общий шаблон для команд git (например, checkout, diff, log, хотя точная семантика различается), поэтому это не должно вызывать удивления.

Например, git reset other-branch path/to/fooвсе в пути / to / foo сбрасывается в его состояние в other-branch, git reset -- .сбрасывает текущий каталог в его состояние в HEAD, а простой git resetсбрасывает все в его состояние в HEAD.

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

Существует четыре основных варианта управления тем, что происходит с вашим рабочим деревом и индексом во время сброса.

Помните, что индекс - это «область подготовки» git - это то, куда идут вещи, когда вы говорите, git addготовясь к фиксации.

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

  • --mixedпо умолчанию, т.е. git resetозначает git reset --mixed. Сбрасывает индекс, но не рабочее дерево. Это означает, что все ваши файлы не повреждены, но любые различия между исходным коммитом и исходным коммитом будут отображаться как локальные изменения (или файлы без отслеживания) со статусом git. Используйте это, когда вы понимаете, что сделали плохие коммиты, но хотите сохранить всю работу, которую вы сделали, чтобы вы могли исправить ее и подтвердить. Для фиксации вам необходимо снова добавить файлы в индекс ( git add ...).

  • --softне касается индекса или рабочего дерева. Все ваши файлы не повреждены, как при использовании --mixed, но все изменения отображаются как changes to be committedсо статусом git (т. Е. Проверены при подготовке к фиксации). Используйте это, когда вы поймете, что сделали плохие коммиты, но все хорошо - все, что вам нужно сделать, это подтвердить это по-другому. Индекс не тронут, поэтому вы можете зафиксировать его немедленно, если хотите - итоговый коммит будет иметь все то же содержимое, что и до сброса.

  • --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 одними, так как они не были частью попытки слияния.

Хотите узнать больше?

Я действительно думаю, что man git resetэто действительно хорошо для этого - возможно, вам нужно немного понять, как работает git, чтобы они действительно погрузились. В частности, если вы уделите время их тщательному прочтению, эти таблицы с подробным описанием состояний файлов в индексе и рабочем дереве для всех различных вариантов и вариантов очень полезны. (Но да, они очень плотные - они передают очень много вышеуказанной информации в очень сжатой форме.)

Странное обозначение

Упоминаемая вами «странная нотация» ( HEAD^и HEAD~1) - это просто сокращение для указания коммитов без необходимости использовать хеш-имя, например 3ebe3f6. Он полностью задокументирован в разделе «указание ревизий» справочной страницы для git-rev-parse, с множеством примеров и связанным синтаксисом. Каретка и тильда на самом деле означают разные вещи :

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

карет и тильда

Cascabel
источник
«Вы будете использовать git reset, чтобы переместить его туда». почему вы не используете git checkout для этого?
Э-Удовлетворение
5
@ e-satin: git checkout переместит HEAD, но оставит ветку там, где она была. Это для того, когда вы хотите переместить ветку.
Каскабель
Так что, если я хорошо понимаю, сброс B будет делать: - A - B - C - B (мастер), в то время как проверка B будет делать - A - B (мастер)?
e-удовлетворительно
32
Документы хороши, даже если их читать нужно вечно, и они очень плотные, и нужно всегда, чтобы убедиться, что они говорят, что это работает, как будто вы уже знаете, как это работает. Не похоже, что документы хороши для меня ...
Кирби
4
Этот простой ответ дает очень простое и понятное объяснение: stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard
Nitin Bansal
80

Помните, что у gitвас есть:

  • HEADуказатель , который говорит вам , что совершить вы работаете
  • работает дерево , которое представляет собой состояние файлов в вашей системе
  • плацдарм (также называемый индексом ), который «этапы» меняется так , что они в дальнейшем могут быть совершенны вместе

Пожалуйста, включите подробные объяснения о:

--hard, --softИ --merge;

В порядке возрастания степени опасности:

  • --softперемещается, HEADно не касается области подготовки или рабочего дерева.
  • --mixedперемещает HEADи обновляет область подготовки, но не рабочее дерево.
  • --mergeперемещает HEAD, сбрасывает промежуточную область и пытается переместить все изменения в вашем рабочем дереве в новое рабочее дерево.
  • --hardперемещает HEAD и настраивает вашу область подготовки и рабочее дерево в новое HEAD, выбрасывая все.

конкретные варианты использования и рабочие процессы;

  • Используется, --softкогда вы хотите перейти к другому коммиту и исправить все, не теряя своего места. Это довольно редко, что вам нужно это.

-

# git reset --soft example
touch foo                            // Add a file, make some changes.
git add foo                          // 
git commit -m "bad commit message"   // Commit... D'oh, that was a mistake!
git reset --soft HEAD^               // Go back one commit and fix things.
git commit -m "good commit"          // There, now it's right.

-

  • Используйте --mixed(по умолчанию), когда вы хотите увидеть, как выглядят другие коммиты, но вы не хотите потерять какие-либо изменения, которые у вас уже есть.

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

  • Используйте, --hardчтобы уничтожить все и начать новый лист на новом коммите.

Джон Феминелла
источник
2
Это не тот случай использования reset --merge. Он не выполняет трехстороннее слияние. Это действительно только для сброса конфликтующих слияний, как описано в документации. Вы хотите использовать, checkout --mergeчтобы делать то, о чем вы говорите. Если вы хотите переместить ветку, я думаю, что единственный способ - это выполнить проверку / сброс, чтобы тащить ее вперед.
Каскабель
@Jefromi »Да, я не очень хорошо это сформулировал. Под «новым местом» я понимал «новое место, где у вас нет конфликтующего слияния».
Джон Феминелла
1
Ах я вижу. Я думаю, что здесь важно то, что если вы действительно не знаете, что делаете, вы, вероятно, никогда не захотите использовать его reset --mergeс какой-либо целью, кроме (по умолчанию) HEAD, потому что в случаях, помимо прерывания конфликтующего слияния, он отбрасывает информация, которую вы могли бы сохранить.
Каскабель
2
Я нашел этот ответ самым простым и полезным
Джазепи
Пожалуйста, добавьте информацию об этих командах: git resetи git reset -- ..
Flimm
35

Пост Reset Demystified в блоге Pro Git дает очень легкое объяснение git resetи git checkout.

После всего полезного обсуждения в верхней части этого поста автор сводит правила к следующим простым трем шагам:

Это в основном это. Команда resetперезаписывает эти три дерева в определенном порядке, останавливаясь, когда вы указываете это.

  1. Переместите любую ветку, на которую указывает HEAD (остановите, если --soft)
  2. ТОГДА сделайте так, чтобы индекс выглядел так (остановитесь здесь, если не --hard)
  3. ТОГДА сделайте так, чтобы рабочий каталог выглядел так

Есть также --mergeи --keepварианты, но я бы предпочел пока упростить ситуацию - это будет для другой статьи.

Даниил Гершкович
источник
25

Когда вы делаете что-то для git, вам сначала нужно внести изменения (добавить в индекс). Это означает, что вам нужно добавить в git все файлы, которые вы хотите включить в этот коммит, прежде чем git считает их частью коммита. Давайте сначала посмотрим на изображение git-репо: введите описание изображения здесь

теперь все просто. Надо работать в рабочем каталоге, создавать файлы, каталоги и все. Эти изменения не отслеживаются. Чтобы их отслеживать, нам нужно добавить их в индекс git с помощью команды git add . После того, как они добавлены в Git Index. Теперь мы можем зафиксировать эти изменения, если мы хотим отправить их в репозиторий git.

Но неожиданно мы узнали, что при фиксации у нас есть один дополнительный файл, который мы добавили в индекс, не требуется для добавления в git-репозиторий. Это означает, что мы не хотим этот файл в индексе. Теперь вопрос в том, как удалить этот файл из индекса git. Поскольку мы использовали git add, чтобы поместить их в индекс, было бы логично использовать git rm ? Неправильно! git rm просто удалит файл и добавит удаление в индекс. Так что же делать сейчас:

Использование: -

git reset

Он очищает ваш индекс, оставляет ваш рабочий каталог нетронутым. (просто unstaging все).

Это может быть использовано с рядом опций. Существует три основных варианта использования git reset: --hard, --soft и --mixed . Они влияют на то, что получает сброс в дополнение к указателю HEAD при сбросе.

Во-первых, --hard сбрасывает все. Ваш текущий каталог будет точно таким же, как если бы вы следили за этой веткой все время. Рабочий каталог и индекс изменяются на этот коммит. Это версия, которую я использую чаще всего. git reset --hard это что-то вроде svn revert .

Далее, полная противоположность - софт , не сбрасывает ни рабочее дерево, ни индекс. Он только перемещает указатель HEAD. Это оставляет ваше текущее состояние с любыми изменениями, отличными от коммита, на который вы переключаетесь, на месте в вашем каталоге и «подготовленным» для фиксации. Если вы делаете коммит локально, но не отправляете коммит на git-сервер, вы можете сбросить предыдущий коммит и повторить с хорошим сообщением коммита.

Наконец, --mixed сбрасывает индекс, но не рабочее дерево. Таким образом, изменения все еще присутствуют, но они «неустановлены» и должны быть добавлены в git add или git commit -a . мы используем это иногда, если мы зафиксировали больше, чем намеревалось, с помощью git commit -a, мы можем отменить коммит с помощью git reset --mixed, добавить вещи, которые мы хотим зафиксировать, и просто зафиксировать их.

Разница между git revert и git reset : -


Проще говоря, git reset - это команда «исправить ошибки, не переданные», а git revert - команда «исправить ошибки» .

Это означает, что если мы допустили какую-то ошибку в каком-либо изменении, зафиксировали и отправили то же самое в git repo, то git revert является решением. И если в случае, если мы определили ту же ошибку до нажатия / подтверждения, мы можем использовать git reset, чтобы исправить проблему.

Я надеюсь, что это поможет вам избавиться от путаницы.

любовь
источник
2
Это хороший простой английский ответ на вопрос, заданный ОП.
Episodex
1
Хотя я мог пропустить это в вашем ответе. Что git reset HEADпо умолчанию? --hard, --softИли --mixed? Отличный ответ, кстати.
Яннис Кристофакис
1
Отличный ответ, но я бы пояснил, что git reset --hardвы потеряете некоторые данные. И есть пункт, который может быть неправильным (хотя я не уверен на 100% ... все еще учусь!): Говоря о --mixedвас, вы говорите, что "мы используем это иногда, если мы совершали больше, чем намеревались, с помощью git commit -a". Вы имели в виду: «если бы мы поставили больше, чем хотели git stage .»? Если вы действительно допустили это, я думаю, что уже слишком поздно (как вы говорите в конце, git reset - это команда для «исправления ошибок»),
говорит Фабио. Восстановите Монику
6

TL; DR

git resetсбрасывает Staging до последнего коммита. Используйте --hardтакже для сброса файлов в вашем рабочем каталоге до последнего коммита.

БОЛЬШАЯ ВЕРСИЯ

Но это, очевидно, упрощенно, поэтому многие довольно подробные ответы. Для меня было больше смысла читать git resetв контексте отмены изменений. Например, увидеть это:

Если git revert является «безопасным» способом отмены изменений, вы можете рассматривать git reset как опасный метод. Когда вы отменяете с помощью git reset (а на коммиты больше не ссылается ни один ref или reflog), нет способа восстановить исходную копию - это постоянная отмена. При использовании этого инструмента необходимо соблюдать осторожность, так как это одна из единственных команд Git, которая может потерять вашу работу.

С https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

и это

На уровне фиксации сброс - это способ перемещения кончика ветви в другой коммит. Это может быть использовано для удаления коммитов из текущей ветви.

С https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/commit-level-operations

Snowcrash
источник
2

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

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


Для тех, кто использует Терминал с включенным цветом (git config --global color.ui auto):

git reset --soft A и вы увидите вещи B и C в зеленом цвете (поставленные и готовые к фиксации)

git reset --mixed A(или git reset A), и вы увидите вещи B и C в красном (не подготовленные и готовые к постановке (зеленые), а затем зафиксированные)

git reset --hard A и вы больше нигде не увидите изменений B и C (как если бы они никогда не существовали)


Или для тех, кто использует программу с графическим интерфейсом, как «Башня» или «SourceTree»

git reset --soft A и вы увидите вещи B и C в области 'готовые файлы', готовые к фиксации

git reset --mixed A(или git reset A), и вы увидите вещи B и C в области 'unstaged files', готовые для перемещения в промежуточную, а затем зафиксированные

git reset --hard A и вы больше нигде не увидите изменений B и C (как если бы они никогда не существовали)

timhc22
источник
1

Оформить заказ указывает на конкретный коммит.

Сброс указывает ветку на конкретный коммит. (Ветвь - это указатель на коммит.)

Кстати, если ваша голова не указывает на коммит, на который также указывает ветка, то у вас оторванная голова. (оказалось не так. Смотрите комментарии ...)

Ян Уорбертон
источник
1
Не придираться, но (да, на самом деле это является придирки , но давайте добавим его завершения) ваше третье предложение технически неверно. Допустим, ваш HEAD указывает на ветку B, которая, в свою очередь, указывает на коммит abc123. Если вы сейчас извлекаете коммит abc123, ваш HEAD и ветвь B оба указывают на коммит abc123 И ваш HEAD отсоединен. Фиксация в этой точке не обновит позицию ветви B. Вы могли бы сказать: «Если ваша голова не указывает на ветвь, значит, у вас оторванная голова»
RomainValeri
@RomainValeri Что будет делать в таких обстоятельствах?
Ян Уорбертон
1
Фиксация создаст коммиты, на которые не ссылается ни одна ветвь, и ветка B будет продолжать указывать на один и тот же коммит abc123 даже после того, как вы зафиксировали несколько раз после этого. Это означает, что эти коммиты станут кандидатами на сборку мусора, когда HEAD перестанет указывать на последний коммит в этой «дикой» серии коммитов.
RomainValeri