In plain English, what does "git reset" do?

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

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

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

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

  • варианты --hard, --soft а также --merge;
  • странная запись, которую вы используете с HEAD такие как HEAD^ а также HEAD~1;
  • конкретные варианты использования и рабочие процессы;
  • последствия для рабочей копии, HEAD и ваш глобальный уровень стресса.

9 ответов

Решение

В общем, 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>).

аргументы

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

Например, git reset other-branch path/to/foo сбрасывает все в пути /to/foo в его состояние в другой ветке, 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,

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

Помните, что в 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 чтобы уничтожить все и начать новую работу над новым коммитом.

Пост Сброс Демистифицированного в блоге Pro Git дает очень легкое объяснение git reset а также git checkout,

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

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

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

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

Когда вы делаете что-то для 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, чтобы исправить проблему.

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

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

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

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


Для тех, кто использует Терминал с включенным цветом (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 в области "неподготовленные файлы", готовые для перемещения в промежуточную и затем зафиксированные

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

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

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

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

РЕДАКТИРОВАТЬ: окончательное утверждение неверно. Смотрите комментарии...:)

Есть действительно хорошая статья, чтобы объяснить Reset (включая проверку / реверс и их сравнение), надеюсь, это поможет. https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/summary

Я не всегда делаю сброс git, но когда я это делаю, я смотрю на это:

      * 444668f (HEAD -> main) C
|
* c3739b7 B
|
* 207e8a1 A
|
* 38fab46 Initial commit


git reset --hard 207e8


* 207e8a1 (HEAD -> main) A
|
* 38fab46 Initial commit


To retrieve the changes, use --soft instead

переехал в A(тоже потому что HEADуказывает на main). не "сбрасывает" и. Вы все еще можете видеть Bа также Cс --reflogвариантgit log:

      git log --graph --oneline --all --reflog

Предупреждение

Прежде чем ты это сделаешь,

  • Если у вас есть неустановленные изменения :

    • С , он будет отброшен
    • С ( по умолчанию ) он будет смешиваться с поэтапными изменениями, а полученные изменения коммитов
  • Если у вас есть поэтапные изменения :

    • С --hard, он будет сброшен
    • С --mixed, он будет перемешан с неустановленными изменениями и полученными изменениями фиксации.
    • С --soft, он будет смешиваться с полученными изменениями коммитов

Чтобы избавиться от них, вы можете использовать git stash, но я предпочитаю просто создать новую ветку и создать там отдельный коммит для поэтапных и неустановленных изменений. Затем используйтеgit rebase+ git resetкогда я нуждаюсь в них обратно.

Другие вопросы по тегам