Переместить текущий указатель на указанную фиксацию без изменения файлов в рабочем каталоге

Обновлено: 28.06.2024

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

Три дерева

Легче всего думать о сбросе и извлечении через представление о том, что Git является менеджером контента трех разных деревьев. Под «деревом» здесь мы действительно подразумеваем «набор файлов», а не конкретно структуру данных. (Есть несколько случаев, когда индекс не совсем похож на дерево, но для наших целей пока проще думать об этом таким образом.)

Git как система управляет тремя деревьями в своей обычной работе:

Последний снимок фиксации, следующий родительский элемент

Предлагаемый снимок следующего коммита

ГОЛОВА

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

На самом деле довольно легко увидеть, как выглядит этот снимок. Вот пример получения фактического списка каталогов и контрольных сумм SHA-1 для каждого файла в моментальном снимке HEAD:

Команды cat-file и ls-tree — это вспомогательные команды, которые используются для вещей более низкого уровня и не используются в повседневной работе, но они помогают нам увидеть, что здесь происходит.

Индекс

Индекс — это предлагаемая вами следующая фиксация. Мы также называли эту концепцию «Промежуточной областью» Git, так как это то, на что Git смотрит, когда вы запускаете git commit .

Git заполняет этот индекс списком всего содержимого файлов, которое в последний раз было извлечено в ваш рабочий каталог, и тем, как они выглядели при первоначальном извлечении. Затем вы заменяете некоторые из этих файлов их новыми версиями, а git commit преобразует их в дерево для новой фиксации.

Опять же, здесь мы используем команду ls-files , которая больше похожа на скрытую команду, показывающую, как выглядит ваш индекс в данный момент.

Технически индекс не является древовидной структурой — фактически он реализован в виде плоского манифеста, но для наших целей он достаточно близок.

Рабочий каталог

Наконец у вас есть рабочий каталог. Два других дерева хранят свое содержимое эффективным, но неудобным способом в папке .git. Рабочий каталог распаковывает их в настоящие файлы, что значительно упрощает их редактирование. Думайте о рабочем каталоге как о песочнице, где вы можете опробовать изменения перед тем, как зафиксировать их в своей промежуточной области (индексе), а затем в истории.

Рабочий процесс

Основная цель Git — записывать снимки вашего проекта в последовательно улучшающихся состояниях, манипулируя этими тремя деревьями.

сбросить рабочий процесс

Давайте визуализируем этот процесс: допустим, вы заходите в новый каталог с одним файлом. Мы назовем это v1 файла и обозначим его синим цветом. Теперь мы запускаем git init , который создаст репозиторий Git со ссылкой HEAD, которая указывает на нерожденную ветку (мастер еще не существует).

reset ex1

На данный момент содержимое есть только в дереве рабочего каталога.

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

reset ex2

Затем мы запускаем git commit , который берет содержимое индекса и сохраняет его как постоянный снимок, создает объект фиксации, который указывает на этот снимок, и обновляет master, чтобы он указывал на эту фиксацию.

reset ex3

Если мы запустим git status , мы не увидим никаких изменений, потому что все три дерева одинаковы.

Теперь мы хотим внести изменения в этот файл и зафиксировать его. Мы пройдем тот же процесс; сначала мы меняем файл в нашем рабочем каталоге. Давайте назовем это v2 файла и обозначим его красным цветом.

reset ex4

Если мы запустим git status прямо сейчас, мы увидим файл, выделенный красным как «Изменения, не подготовленные для фиксации», потому что эта запись различается между индексом и рабочим каталогом. Затем мы запускаем git add, чтобы добавить его в наш индекс.

сбросить ex5

На этом этапе, если мы запустим git status, мы увидим файл зеленым цветом в разделе «Изменения, которые необходимо зафиксировать», потому что индекс и HEAD различаются, то есть предлагаемый нами следующий коммит теперь отличается от нашего последнего коммита. Наконец, мы запускаем git commit, чтобы завершить фиксацию.

reset ex6

Теперь git status ничего не выводит, потому что все три дерева снова одинаковы.

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

Роль сброса

Команда сброса имеет больше смысла, если рассматривать ее в этом контексте.

Для целей этих примеров предположим, что мы снова изменили файл.txt и зафиксировали его в третий раз. Итак, теперь наша история выглядит так:

сбросить запуск

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

Шаг 1. Переместите HEAD

Первое, что сделает сброс, это переместит то, на что указывает HEAD. Это не то же самое, что изменение самого HEAD (что и делает проверка); reset перемещает ветку, на которую указывает HEAD. Это означает, что если HEAD настроен на ветку master (т. е. вы в настоящее время находитесь в ветке master), запуск git reset 9e5e6a4 начнется с того, что master укажет на 9e5e6a4 .

reset soft

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

Теперь взгляните на эту диаграмму и поймите, что произошло: она фактически отменила последнюю команду git commit. Когда вы запускаете git commit , Git создает новый коммит и перемещает в него ветку, на которую указывает HEAD. Когда вы возвращаетесь к HEAD~ (родителю HEAD), вы перемещаете ветку туда, где она была, без изменения индекса или рабочего каталога. Теперь вы можете обновить индекс и снова запустить git commit, чтобы выполнить то, что сделал бы git commit --amend (см. Изменение последней фиксации).

Шаг 2. Обновление индекса (--mixed)

Обратите внимание: если вы сейчас запустите git status, вы увидите зеленым цветом разницу между индексом и новым HEAD.

Следующее, что сделает сброс, — это обновит индекс содержимым любого моментального снимка, на который сейчас указывает HEAD.

сброс смешанный

Если вы укажете параметр --mixed, сброс будет остановлен на этом этапе. Это также значение по умолчанию, поэтому, если вы вообще не укажете никаких параметров (в данном случае просто git reset HEAD~), на этом команда остановится.

Теперь уделите еще одну секунду, чтобы посмотреть на эту диаграмму и понять, что произошло: она по-прежнему отменяет ваш последний коммит , но при этом все не помещается в стадию. Вы откатились до того, как выполнили все свои команды git add и git commit.

Шаг 3. Обновление рабочего каталога (--hard)

Третье, что сделает сброс, — это сделает рабочий каталог похожим на индекс. Если вы используете параметр --hard, он будет продолжен до этого этапа.

reset hard

Давайте подумаем о том, что только что произошло. Вы отменили свой последний коммит, команды git add и git commit и всю работу, которую вы проделали в своем рабочем каталоге.

Важно отметить, что этот флаг ( --hard ) — единственный способ сделать команду сброса опасной и один из очень немногих случаев, когда Git действительно уничтожает данные. Любой другой вызов сброса может быть довольно легко отменен, но опция --hard не может, так как она принудительно перезаписывает файлы в рабочем каталоге. В этом конкретном случае у нас все еще есть версия нашего файла v3 в фиксации в нашей базе данных Git, и мы могли бы получить ее обратно, просмотрев наш reflog, но если бы мы ее не зафиксировали, Git все равно перезаписал бы файл и это было бы невозможно восстановить.

Подведение итогов

Команда сброса перезаписывает эти три дерева в определенном порядке и останавливается, когда вы указываете:

Переместите ветку HEAD, указывающую на (остановитесь здесь, если --soft )

Сделайте индекс похожим на HEAD (остановитесь здесь, если не --hard )

Сделайте рабочий каталог похожим на индекс

Сбросить с помощью пути

Это относится к поведению сброса в его базовой форме, но вы также можете предоставить ему путь, по которому нужно действовать.Если вы укажете путь, сброс пропустит шаг 1 и ограничит остальные действия определенным файлом или набором файлов. На самом деле в этом есть смысл — HEAD — это просто указатель, и вы не можете указать на часть одного коммита и часть другого. Но индекс и рабочий каталог могут быть частично обновлены, поэтому сброс осуществляется с помощью шагов 2 и 3.

Итак, предположим, мы запускаем git reset file.txt . Эта форма (поскольку вы не указали коммит SHA-1 или ветку и не указали --soft или --hard ) является сокращением для git reset --mixed HEAD file.txt , который будет:

Переместить ветку HEAD, указывающую на (пропущено)

Сделайте указатель похожим на HEAD (остановитесь здесь)

По сути, он просто копирует файл file.txt из HEAD в индекс.

reset path1

На практике это приводит к декомпозиции файла. Если мы посмотрим на диаграмму этой команды и подумаем о том, что делает git add, они будут полной противоположностью.

reset path2

Вот почему выходные данные команды git status предполагают, что вы должны запустить ее, чтобы удалить файл из стадии. (Подробнее об этом см. в Odstranění souboru z oblasti připravených změn.)

С таким же успехом мы могли бы не позволить Git предположить, что мы имели в виду «извлечь данные из HEAD», указав конкретную фиксацию, из которой нужно извлечь эту версию файла. Мы просто запускаем что-то вроде git reset eb43bf file.txt .

reset path3

Это фактически делает то же самое, как если бы мы вернули содержимое файла к версии 1 в рабочем каталоге, запустили git add для него, а затем снова вернули его обратно к версии 3 (фактически не выполняя все эти шаги). Если мы запустим git commit сейчас, он запишет изменение, которое вернет этот файл обратно к версии 1, даже если он никогда больше не был в нашем рабочем каталоге.

Также интересно отметить, что, как и git add , команда reset будет принимать параметр --patch для удаления контента по частям. Таким образом, вы можете выборочно отключить или вернуть содержимое.

Сжатие

Давайте посмотрим, как сделать кое-что интересное с помощью этой новой возможности — подавления коммитов.

Допустим, у вас есть серия коммитов с такими сообщениями, как "упс", "незавершенное выполнение" и "забыли этот файл". Вы можете использовать сброс, чтобы быстро и легко сжать их в один коммит, который заставит вас выглядеть очень умным. (Squashing Commits показывает другой способ сделать это, но в этом примере проще использовать reset .)

Допустим, у вас есть проект, в котором первая фиксация содержит один файл, вторая фиксация добавляет новый файл и изменяет первый, а третья фиксация снова изменяет первый файл. Работа над вторым коммитом еще не завершена, и вы хотите его сократить.

reset squash r1

Вы можете запустить git reset --soft HEAD~2, чтобы переместить ветку HEAD обратно к более старой фиксации (первой фиксации, которую вы хотите сохранить):

reset squash r2

А затем просто снова запустите git commit:

reset squash r3

Теперь вы можете видеть, что ваша доступная история, история, которую вы бы отправили, теперь выглядит так, как будто вы сделали один коммит с файлом-a.txt v1, а затем второй, который изменил файл-a.txt на v3 и добавил файл- б.текст . Коммит с версией файла v2 больше не находится в истории.

Проверить

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

Без путей

Запуск git checkout [branch] очень похож на запуск git reset --hard [branch] тем, что он обновляет все три дерева, чтобы они выглядели как [branch] , но есть два важных отличия.

Во-первых, в отличие от reset --hard , checkout безопасен для рабочего каталога; он проверит, не удаляются ли файлы, в которых есть изменения. На самом деле, это немного умнее — он пытается выполнить тривиальное слияние в рабочем каталоге, поэтому все файлы, которые вы не изменили, будут обновлены. reset --hard , с другой стороны, просто заменит все без проверки.

Второе важное отличие заключается в том, как он обновляет HEAD. Если reset переместит ветку, на которую указывает HEAD, то checkout переместит сам HEAD, чтобы он указывал на другую ветку.

Например, предположим, что у нас есть ветки master и development, которые указывают на разные коммиты, и в данный момент мы находимся на стадии разработки (поэтому HEAD указывает на нее). Если мы запустим git reset master , сама разработка теперь будет указывать на ту же фиксацию, что и master. Если мы вместо этого запустим git checkout master , то develop не сдвинется, а сам HEAD. HEAD теперь будет указывать на master .

Итак, в обоих случаях мы перемещаем HEAD в точку для фиксации A, но как мы это делаем очень по-разному. reset переместит ветку, на которую указывает HEAD, checkout переместит сам HEAD.

сбросить проверку

С путями

Другой способ выполнить проверку – указать путь к файлу, который, как и reset , не перемещает HEAD. Это похоже на файл git reset [branch] в том смысле, что он обновляет индекс этим файлом при этой фиксации, но также перезаписывает файл в рабочем каталоге. Это было бы точно так же, как git reset --hard [branch] file (если бы сброс позволил вам запустить это) — это не безопасно для рабочего каталога и не перемещает HEAD.

Кроме того, как и git reset и git add , checkout примет опцию --patch, позволяющую вам выборочно возвращать содержимое файла по частям.

Шрнути

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

Вот шпаргалка, по которой команды влияют на какие деревья. Столбец «HEAD» читается как «REF», если эта команда перемещает ссылку (ветвь), на которую указывает HEAD, и «HEAD», если она перемещает сам HEAD. Обратите особое внимание на столбец WD Safe? — если там написано НЕТ, подумайте секунду, прежде чем запускать эту команду.

Чтобы переместить указатель извлеченной ветки, можно использовать команду git reset --hard. Но как переместить указатель ветки непроверенной ветки, чтобы он указывал на другую фиксацию (сохранив все остальное, например, отслеживаемую удаленную ветку)?

Похоже, все, что вы хотели сделать, это создать ветку из коммита, отличного от того, из которого она создана. Если я правильно понимаю, то почему бы вам просто не создать новую ветку из коммита, который вы хотите создать, используя ветку git
и сбросить старую ветку?

11 ответов 11

Если new-tip-commit опущен, по умолчанию используется текущая фиксация.

new-tip-commit может быть именем ветки (например, master, origin/master).

Или для произвольных ссылок, git update-ref -m "reset: сбросить
до "
. (Вы можете придраться к сообщению reflog, если хотите - я считаю, что ветвь -f one отличается от reset --hard, и это не совсем ни то, ни другое.)

Это лучший ответ, так как он подходит для случая 99 % и фактически соответствует документации. ветка справки git говорит: "-f, --force Reset
to if
уже существует. Без -f ветвь git отказывается изменять существующую ветку."

Я выполняю команду git branch -f master, и мне выдается фатальное сообщение: невозможно принудительно обновить текущую ветку. Эммм, что я должен сделать сейчас, проверить какую-нибудь другую случайную ветку, прежде чем мне будет разрешено использовать эту команду?

Вы можете сделать это для произвольных ссылок. Вот как переместить указатель ветвления:

где -m добавляет сообщение в журнал ссылок для ветки.

Общая форма

Вы можете придраться к сообщению reflog, если хотите - я считаю, что ветка -f one отличается от reset --hard, и это не совсем ни то, ни другое.

ПРИМЕЧАНИЕ. Это не работает с голыми репозиториями. В голых репозиториях вы должны использовать 'git branch -f master' для обновления ветки (см. ответ ниже).

Если, как и я, вы случайно используете
вместо refs/heads/
, вы получите новый файл в каталоге .git по адресу .git/
и вы будете получать сообщения типа "refname 'master' is ambiguous" при попытке работать с ним. Вы можете удалить файл из каталога .git, чтобы исправить ситуацию.

Не было удовлетворительно объяснено, почему это лучше, чем git branch -f . Если быть точным, этот метод кажется: (А) более сложным в использовании (Б) трудным для запоминания и (В) более опасным

«что именно подразумевается под произвольными ссылками» — ветки — не единственный вид ссылок, указывающих на фиксацию. Есть теги, и вы также можете сами создавать произвольные ссылки в стиле refs/whatevs/myref, которые не являются ни ветвями, ни тегами. Я считаю, что это также отвечает на вопрос Стивена Лу о том, что это может быть «лучше». Я согласен, что branch -f проще всего, если вы работаете с ветвями.

Вы также можете передать git reset --hard ссылку на фиксацию.

Я обнаружил, что иногда делаю что-то подобное:

Предполагая эту историю

Это нормально, если ваше рабочее дерево чистое. Если у вас есть много поэтапных или неустановленных изменений, вероятно, лучше выполнить git update-ref, как обсуждалось выше.

Вы заметили, что ваш «ответ» не добавляет ничего, что уже не является частью вопроса?? - ОП сказал: если это проверено. вы можете использовать git reset --hard . Нет необходимости повторять это здесь! :-(

@Robert: Я не согласен. В вопросе не говорилось как его использовать, а это говорит. Было приятно не искать это как.

@WilsonF, возможно, вам было приятно найти это здесь, но это вообще не ответ на вопрос. Может быть, это ответ на какой-то другой вопрос, но здесь он неверный.

Я думаю, что это должен был быть комментарий к вопросу, а не ответ. (Или, может быть, это был ответ на более раннюю версию вопроса?)

Просто чтобы обогатить обсуждение, если вы хотите переместить ветку myBranch в текущий коммит, просто опустите второй аргумент после -f

ветвь git -f моя ветка

Обычно я делаю это, когда перебазирую в состоянии Detached HEAD :)

В gitk --all :

  • щелкните правой кнопкой мыши нужный коммит
  • ->создать новую ветку
  • введите название существующей ветки
  • нажмите клавишу возврата в диалоговом окне, подтверждающем замену старой ветки с таким именем.

Учтите, что при повторном создании вместо изменения существующей ветки информация об отслеживаемой ветке будет потеряна. (Как правило, это не проблема для простых случаев использования, когда есть только один удаленный и ваша локальная ветвь имеет то же имя, что и соответствующая ветвь на удаленном компьютере. Дополнительные сведения см. в комментариях, спасибо @mbdevpl за указание на этот недостаток.)< /p>

Было бы круто, если бы в gitk была функция, позволяющая диалоговому окну иметь 3 варианта: перезаписать, изменить существующие или отменить.

Даже если вы, как и я, привыкли работать с командной строкой, git gui и gitk прекрасно спроектированы для той части использования git, которую они допускают. Я настоятельно рекомендую использовать их для того, в чем они хороши (т. е. выборочно размещать куски в / из индекса в git gui, а также просто фиксировать. (ctrl-s, чтобы добавить подпись: строка, ctrl-enter, чтобы зафиксировать .)

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

У меня даже нет открытого графического файлового браузера, но я люблю gitk/git gui.

Получите полный доступ к Git Pocket Guide и более чем 60 000 других наименований с бесплатной 10-дневной пробной версией O'Reilly.

Есть также прямые онлайн-мероприятия, интерактивный контент, материалы для подготовки к сертификации и многое другое.

Глава 4. Отмена и редактирование коммитов

В главе 3 мы обсуждали промежуточные изменения в индексе для включения в следующую фиксацию. Эта глава посвящена отмене или исправлению изменений после их фиксации.

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

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

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

Обратите внимание, что большинство методов, обсуждаемых в этой главе, имеют смысл только тогда, когда задействованная часть истории является линейной; то есть не содержит коммитов слияния. Мы обсудим методы редактирования разветвленной истории в главе 10.

Технически вы не можете «изменить» фиксацию. Из-за адресации на основе содержимого, если вы что-то меняете в фиксации, она становится другой фиксацией, поскольку теперь у нее другой идентификатор объекта. Поэтому, когда мы говорим об изменении коммита, мы на самом деле имеем в виду замену коммита с исправленными атрибутами или содержимым. Но так как намерение состоит в том, чтобы изменить историю, удобно использовать эту формулировку, и мы так и сделаем.

Изменение последней фиксации

Самое распространенное исправление — это предыдущая фиксация: вы запускаете git commit , а затем понимаете, что допустили ошибку — возможно, вы забыли включить новый файл или пропустили некоторые комментарии. Эта распространенная ситуация также является самой простой для решения. Нет никакого подготовительного шага; просто внесите необходимые исправления, добавив их в индекс, как обычно. Затем используйте эту команду:

Git предложит вам отредактировать предыдущее сообщение коммита, если хотите; затем он просто отбрасывает предыдущую фиксацию и помещает новую на ее место с вашими исправлениями. Вы можете добавить -C HEAD, если хотите повторно использовать предыдущее сообщение фиксации как есть.

Функция --amend — хороший пример того, как внутренняя организация Git упрощает многие операции как для реализации, так и для понимания. Верхний коммит в текущей ветке имеет указатель на предыдущий коммит, его родителя; это все, что связывает его с остальной частью ветки. В частности, никакие коммиты не указывают на этот, поэтому никакие другие коммиты не затрагиваются (напомним, что коммиты указывают на своих родителей, но не на их потомков). Таким образом, отмена коммита подсказки состоит только в перемещении указателя ветки назад к предыдущему; больше ничего не нужно делать. В конце концов, если отброшенная фиксация остается отключенной от графика коммитов, Git удалит ее из базы данных объектов в рамках периодической сборки мусора.

После удаления предыдущей фиксации состояние репозитория, которое вы хотите изменить и повторно зафиксировать, будет казаться потерянным… но нет: в индексе есть еще одна его копия, поскольку эта фиксация была сделана из текущего индекса. Таким образом, вы просто изменяете индекс по своему усмотрению и снова фиксируете его.

Хотя мы описали это с точки зрения линейной истории, git commit --amend также работает с коммитами слияния; тогда задействовано несколько родителей и ветвей вместо одной, но это работает аналогично.

Исправление сообщения фиксации

Если вы используете git commit --amend без внесения каких-либо изменений в индекс, Git по-прежнему позволяет вам редактировать сообщение фиксации, если хотите, или вы можете указать новое сообщение с параметром -m. Это по-прежнему требует замены последнего коммита, так как текст сообщения является частью коммита; новый коммит будет иметь то же содержимое (указывать на то же дерево), что и предыдущий.

Упс!

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

Команда git log, которую мы обсудим в главе 9, обычно показывает историю вашего проекта в виде фрагментов графа коммитов. Однако параметр -g показывает совершенно другое. Для каждой ветки Git ведет журнал операций, выполненных в этой ветке, который называется «reflog». Напомним, что ветка — это просто ссылка, указывающая на вершину фиксации ветки; у каждого реферала может быть журнал регистрации его референтов с течением времени. git log -g отображает составной журнал ссылок, начиная с текущей ветки и заканчивая командами, которые переключают ветки, такими как git checkout . Например:

Коммит 965dfda4 отсутствует, удален из истории, но запись о нем сохраняется в журнале ссылок. Вы можете использовать git show 965dfda4, чтобы просмотреть различия для этой фиксации и восстановить недостающую информацию, или git checkout 965dfda4, чтобы переместить ваше рабочее дерево в это состояние, если это более удобно.

Дополнительную информацию о журнале ссылок см. в разделе Имена относительно журнала ссылок.

Отмена последней фиксации

Предположим, вы делаете коммит, но потом решаете, что не были готовы это сделать. У вас нет конкретного исправления, как в случае с git commit --amend ; вы просто хотите «отменить коммит» и продолжить работу. Это просто; просто сделай :

git reset — это универсальная команда с несколькими режимами и действиями. Он всегда перемещает голову текущей ветки в данную фиксацию, но отличается тем, как он обрабатывает рабочее дерево и индекс; в этом случае он обновляет индекс, но оставляет рабочее дерево в покое. Ссылка HEAD, как всегда, указывает на конец текущей ветки, а завершающая тильда называет коммит, предшествующий этому (см. главу 8). Таким образом, эффект этой команды состоит в том, чтобы переместить ветку назад на один коммит, отбросив последний (но вы все еще можете восстановить его через reflog, как и раньше). Поскольку он также сбрасывает индекс для соответствия, любые соответствующие изменения в вашем рабочем дереве теперь снова не проиндексированы, о чем Git сообщает, как показано, вместе с любыми другими незавершенными изменениями (M означает «изменено»; также может отображаться A для «добавлено, "D" для "удалено" и т. д.).

Отмена любого количества коммитов

В приведенном выше описании единственное, что ограничивает действие «последней фиксацией», — это выражение HEAD~ ; он работает так же хорошо, чтобы отбросить любое количество последовательных коммитов в конце ветки. Это действие иногда называют «перемоткой ветки». Например, чтобы отменить три коммита, сбрасывая кончик ветки на четвертый коммит, выполните:

HEAD~3 относится к четвертой обратной фиксации, потому что этот синтаксис нумерации начинается с нуля; HEAD и HEAD~0 эквивалентны.

При отмене нескольких коммитов становятся полезными некоторые дополнительные параметры сброса git:

--mixed По умолчанию: делает индекс соответствующим данной фиксации, но не изменяет рабочие файлы. Изменения, сделанные с момента последней фиксации, отображаются неустановленными. --soft Сбрасывает только кончик ветки и не изменяет индекс; изменения отброшенного коммита остаются поэтапными. Вы можете использовать это для подготовки всех изменений из нескольких предыдущих коммитов, а затем повторно применить их как один коммит. --merge Пытается сохранить незавершенные изменения файла при перемотке ветки, где это имеет смысл: файлы с неустановленными изменениями сохраняются, а файлы, отличающиеся между HEAD и данным коммитом, обновляются. Если между этими наборами есть перекрытие, сброс невозможен. --hard Сбрасывает ваши рабочие файлы, чтобы они соответствовали данному коммиту, а также индексу. Любые изменения, которые вы сделали после отмены коммита, будут безвозвратно утеряны, поэтому будьте осторожны с этой опцией! Не поддавайтесь желанию создать псевдоним или ярлык для использования git reset --hard ; вы, вероятно, пожалеете об этом.

Отмена фиксации

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

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

Частичная отмена

Опция -n для git revert указывает Git применить и подготовить отмененные изменения, но не делать фиксации. Затем вы отменяете все изменения с помощью git reset и переустанавливаете только те, которые хотите, используя интерактивный git add -p . Наконец, после фиксации подмножества изменений, которые вы хотите, вы отбрасываете остальные, проверяя содержимое индекса, перезаписывая оставшиеся примененные изменения из git revert .

Обычный git revert будет жаловаться, если вы внесли поэтапные изменения в индекс (то есть индекс не соответствует фиксации HEAD), поскольку его цель — сделать новую фиксацию на основе той, которую необходимо отменить, и она будет потеряете свои изменения, если он сбросит индекс, чтобы сделать это. git revert -n , однако, не будет жаловаться на это, поскольку не делает фиксацию.

Обратите внимание, что если коммит, который вы отменяете, удалил файл, он будет добавлен обратно. Однако после сброса git восстановленный файл будет отображаться в Git как «неотслеживаемый», и git add -p не увидит его; вам придется добавить его снова отдельно, если это одно из изменений, которые вы хотите внести ( git add --interactive (-i) может помочь с этим; это более общая команда, а git add -p на самом деле является часто используемой подкомандой из него). Точно так же при окончательной проверке не будет удален восстановленный файл, который вы решили не добавлять; вам придется удалить его самостоятельно. Вы можете использовать git reset --hard или git clean , но будьте осторожны, чтобы случайно не удалить другие неотслеживаемые файлы или отменить другие изменения рабочего дерева, которые у вас могут быть.

Редактирование серии коммитов

git commit --amend — это хорошо, но что, если вы хотите изменить фиксацию, которая находится на несколько шагов раньше в вашей истории? Поскольку каждый коммит ссылается на предыдущий, его изменение означает, что все последующие коммиты должны быть заменены, даже если вам не нужно вносить в них какие-либо другие изменения. Функция --amend работает так же просто, как и прежде, потому что нет необходимости учитывать следующие коммиты.

На самом деле, Git позволяет вам редактировать любую линейную последовательность коммитов, ведущую к подсказке ветки, — не только в отношении их сообщений и содержимого, но и переупорядочивать их, удалять некоторые из них, сворачивать некоторые вместе или разделять некоторые на дополнительные части. совершает. Используемая функция — git rebase. Перебазирование — это общий метод, предназначенный для перемещения ветки из одного места в другое, и мы рассмотрим его более подробно в разделе «Перебазирование». Однако при перемещении ветки он также позволяет вам использовать очень общий «редактор последовательности» для одновременного преобразования ветки (с опцией --interactive (-i) ), и это функция, которую мы здесь хотим. Эта команда:

перезаписывает последние n коммитов в текущей ветке. На самом деле он просит Git «переместить» ветку, но место назначения такое же, как и начальная точка, поэтому местоположение ветки на самом деле не меняется, и вы можете использовать редактор последовательности для изменения коммитов по своему усмотрению в процессе. .

В ответ на эту команду Git запускает ваш редактор и представляет однострочное описание каждой фиксации в указанном диапазоне, например:

Обратите внимание: порядок здесь тот, в котором были сделаны коммиты (и в котором они будут переделаны), что обычно противоположно тому, что вы увидите в git log , который использует в обратном хронологическом порядке (сначала самая последняя фиксация).

pick Использовать фиксацию как есть. Git не остановится для этой фиксации, если нет конфликта. reword Измените только сообщение коммита. Git позволяет вам отредактировать сообщение перед повторным применением этой фиксации. edit Измените содержимое коммита (и сообщение, если хотите). Здесь Git останавливается после переделки этого коммита и позволяет вам делать все, что вы хотите. Обычно используется git commit --amend для замены фиксации, затем git rebase --continue, чтобы позволить Git продолжить операцию перебазирования. Однако вы также можете вставить дополнительные коммиты, возможно, разделив исходные изменения на несколько меньших коммитов. Git просто продолжает с того места, где вы остановились, со следующим изменением, которое вы попросили его сделать. squash Сделайте изменения этого коммита частью предыдущего. Чтобы объединить несколько последовательных коммитов в один, оставьте первый помеченным pick, а остальные пометьте squash. Git объединяет все сообщения фиксации для редактирования. fixup То же, что и squash , но отбрасывает сообщение этой фиксации при составлении составного сообщения.

Вы можете сократить действие до его начальной буквы, например, r для переименования. Вы также можете изменить порядок строк, чтобы сделать новые коммиты в другом порядке, или полностью удалить коммит, удалив его строку. Если вы хотите отменить перебазирование, просто сохраните файл без строк действий; Git прервется, если не найдет, что делать. Он не прервется, если вы просто оставите указания, как вы их нашли, но в этом простом случае результат будет таким же, поскольку Git обнаружит, что ему не нужно переделывать какие-либо коммиты, чтобы следовать указания (которые говорят использовать каждый коммит как есть с pick ). В любой момент, когда Git остановится, вы можете прервать весь процесс и вернуться к предыдущему состоянию с помощью git rebase --abort .

Конфликты

Можно запросить изменения, которые сделают существующие коммиты недействительными. Например: если одна фиксация добавляет файл, а более поздняя фиксация изменяет этот файл, и вы меняете порядок этих коммитов, то Git не может применить новый первый патч, так как он предлагает изменить файл, которого еще не существует. Кроме того, исправления для существующих файлов зависят от контекста, который может измениться, если вы отредактируете содержимое более ранних коммитов. В этом случае Git остановится, укажет на проблему и попросит вас разрешить конфликт, прежде чем продолжить. Например:

Здесь Git использует тот же механизм для обозначения конфликтов, что и при выполнении слияния; см. Конфликты слияния для получения подробной информации о том, как их исследовать и разрешать. Когда вы закончите, как указано выше, просто запустите git rebase --continue, чтобы сделать исправленную фиксацию, и перейдите к следующему редактированию.

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

Действие exec

На самом деле есть еще одно действие, exec , но вы не будете редактировать существующую строку в инструкциях по перебазированию, чтобы использовать ее как с другими действиями, так как оно не говорит ничего делать с фиксацией; скорее, остальная часть строки — это просто команда оболочки для запуска Git. Обычно это используется для проверки предыдущего коммита, чтобы убедиться, что вы случайно не испортили содержимое; вы можете добавить exec make test , например, для запуска автоматизированного тестирования программного обеспечения. Git остановится, если команда exec завершается с ненулевым статусом. Вы также можете дать одну команду с опцией git rebase --exec, которая будет выполняться после каждой фиксации; это ярлык для вставки одной и той же строки exec после каждой фиксации в направлениях секвенсора.

Получите карманное руководство по Git прямо сейчас с онлайн-обучением O’Reilly.

Члены O’Reilly проходят онлайн-обучение в режиме реального времени, а также получают книги, видео и цифровой контент от более чем 200 издателей.


Если я чему-то и научился за свои 15 с лишним лет программирования, так это тому, что ошибки распространены, и я делаю их много. Это в равной степени относится и к инструментам контроля версий. Если вы случайно зафиксировали изменения или просто поняли, что ваш предыдущий зафиксированный код — это не то, что вам нужно, часто вам нужно отменить предыдущую фиксацию в Git.

В этой статье я покажу несколько способов отмены ваших коммитов в зависимости от вашего варианта использования. Это сложная тема (что справедливо для многих тем Git в целом), поэтому убедитесь, что вы следуете инструкциям, которые лучше всего соответствуют вашим потребностям.

Удалить неопубликованные коммиты

Если вы еще не опубликовали свои коммиты в удаленном репозитории, например на GitHub, вы можете удалить предыдущие коммиты с помощью команды сброса.

Несмотря на то, что это эффективное решение, оно опасно, поскольку вы переписываете историю и оставляете «удаленные» коммиты без ссылок или «осиротевшими». Единственный способ найти и восстановить эти коммиты без ссылок — использовать git reflog .

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

Это означает, что, используя только эту команду, вы не только вернетесь к предыдущей фиксации, но и потеряете все рабочие изменения в процессе. Чтобы не потерять рабочие изменения, вы можете использовать команды stash и stash pop:

Команда stash сохраняет ваши рабочие изменения (без каких-либо коммитов или изменений в дереве), а затем stash pop возвращает их обратно.

Другой вариант, который вы можете рассмотреть, — это параметр --soft. Эта опция работает почти так же, как git reset --hard , но влияет только на историю коммитов, а не на ваш рабочий каталог или промежуточный индекс.

Поэтому, если у вас есть незафиксированные изменения, которые вы хотите сохранить, то, скорее всего, вам подойдет именно этот вариант.

Удаление опубликованных коммитов

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

Вместо этого рекомендуется использовать команду возврата. Эта команда отменяет изменения, внесенные в указанную фиксацию, создавая новую фиксацию и фактически не удаляя предыдущие фиксации. Это идеально подходит для опубликованных изменений, потому что тогда сохраняется истинная история репо. Вот команда:

Допустим, у вас в репозитории есть текстовый файл со следующим содержимым:

А затем вы меняете его на:

История коммитов может выглядеть примерно так:

Если мы решим, что больше не хотим использовать слово "круто" в нашем тексте, но не хотим удалять фиксацию 676ec, мы можем использовать revert, чтобы отменить это изменение:

После того, как нас попросят ввести сообщение о коммите, мы теперь можем видеть в нашей истории коммитов, что на самом деле есть новый коммит:

В результате этого первая и третья фиксации представляют одно и то же состояние проекта. Коммит был отменен, и история не была потеряна.

Бесплатная электронная книга: Git Essentials

Ознакомьтесь с нашим практическим руководством по изучению Git, включающим передовые практики, общепринятые стандарты и памятку. Перестаньте гуглить команды Git и на самом деле изучите их!

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

Или, если вы хотите отменить множество непостоянных коммитов, укажите их по отдельности:

Временно проверить предыдущую фиксацию

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

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

Читайте также: