Перейти к основному содержанию
Рецепты Linux

Main navigation

  • Основы
  • Система
  • Команды
  • Программы
  • Дистро
  • Интерфейсы
  • Устройства
  • Доки
User account menu
  • Войти

Строка навигации

  1. Главная
  2. Pro Git
  3. Инструменты Git

Перезапись истории

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

Изменение последнего коммита

Изменение последнего коммита это, вероятно, наиболее типичный случай переписывания истории, который вы будете делать. Как правило, вам от вашего последнего коммита понадобятся две основные вещи: изменить сообщение коммита, или изменить только что записанный снимок состояния, добавив, изменив или удалив из него файлы. Если вы всего лишь хотите изменить сообщение последнего коммита — это очень просто: $ git commit --amend Выполнив это, вы попадёте в свой текстовый редактор, в котором будет находиться сообщение последнего коммита, готовое к тому, чтобы его отредактировали. Когда вы сохраните текст и закроете редактор, Git создаст новый коммит с вашим сообщением и сделает его новым последним коммитом. Если вы сделали коммит и затем хотите изменить снимок состояния в коммите, добавив или изменив файлы, допустим, потому что вы забыли добавить только что созданный файл, когда делали коммит, то процесс выглядит в основном так же. Вы добавляете в индекс изменения, которые хотите, редактируя файл и выполняя для него git add или выполняя git rm для отслеживаемого файла, и затем git commit --amend возьмёт текущий индекс и сделает его снимком состояния нового коммита. Будьте осторожны используя этот приём, потому что git commit --amend меняет SHA-1 коммита. Тут как с маленьким перемещением (rebase) — не правьте последний коммит, если вы его уже куда-то отправили.

Изменение сообщений нескольких коммитов

Чтобы изменить коммит, находящийся глубоко в истории, вам придётся перейти к использованию более сложных инструментов. В Git нет специального инструмента для редактирования истории, но вы можете использовать rebase для перемещения ряда коммитов на то же самое место, где они были изначально, а не куда-то в другое место. Используя инструмент для интерактивного перемещения, вы можете останавливаться на каждом коммите, который хотите изменить, и редактировать сообщение, добавлять файлы или делать что-то ещё. Интерактивное перемещение можно запустить добавив опцию -i к git rebase. Необходимо указать насколько далекие в истории коммиты вы хотите переписать, сообщив команде на какой коммит выполняется перемещение. Например, если вы хотите изменить сообщения последних трёх коммитов, или сообщения для только некоторых коммитов в этой группе, вам надо передать в git rebase-i в качестве аргумента родителя последнего коммита, который вы хотите изменить, то есть HEAD~2^ или HEAD~3. Наверное проще запомнить ~3, потому что вы пытаетесь отредактировать три последних коммита, но имейте в виду, что на самом деле вы обозначили четвёртый сверху коммит — родительский коммит, для того, который хотите отредактировать: $ git rebase -i HEAD~3 Снова напомним, что эта команда для перемещения, то есть все коммиты в диапазоне HEAD~3..HEAD будут переписаны, вне зависимости от того меняли ли вы в них сообщение или нет. Не трогайте те коммиты, которые вы уже отправили на центральный сервер — сделав так, вы запутаете других разработчиков дав им разные версии одних и тех же изменений. Запуск этой команды выдаст вам в текстовом редакторе список коммитов, который будет выглядеть как-нибудь так: pick f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file # Rebase 710f0f8..a5f4a0d onto 710f0f8 # # Commands: # p, pick = use commit # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # Важно отметить, что эти коммиты выведены в обратном порядке по сравнению с тем, как вы их обычно видите используя команду log. Запустив log, вы получите что-то типа следующего: $ git log --pretty=format:"%h %s" HEAD~3..HEAD a5f4a0d added cat-file 310154e updated README formatting and added blame f7f3f6d changed my name a bit Обратите внимание на обратный порядок. Интерактивное перемещение выдаёт сценарий, который будет выполнен. Он начнётся с коммита, который вы указали в командной строке (HEAD~3), и воспроизведёт изменения сделанные каждым из этих коммитов сверху вниз. Наверху указан самый старый коммит, а не самый новый, потому что он будет воспроизведён первым. Вам надо отредактировать сценарий так, чтобы он останавливался на коммитах, которые вы хотите отредактировать. Чтобы сделать это, замените слово pick на слово edit для каждого коммита, на котором сценарий должен остановиться. Например, чтобы изменить сообщение только для третьего коммита, отредактируйте файл так, чтобы он выглядел следующим образом: edit f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file Когда вы сохраните и выйдете из редактора, Git откатит вас назад к последнему коммиту в списке и выкинет вас в командную строку выдав следующее сообщение: $ git rebase -i HEAD~3 Stopped at 7482e0d... updated the gemspec to hopefully work better You can amend the commit now, with git commit --amend Once you’re satisfied with your changes, run git rebase --continue В этой инструкции в точности сказано что надо сделать. Наберите $ git commit --amend Измените сообщение коммита и выйдите из редактора. Теперь выполните $ git rebase --continue Эта команда применит оставшиеся два коммита автоматически, и тогда всё. Если вы измените pick на edit для большего количества строк, то вы повторите эти шаги для каждого коммита, где вы напишите edit. Каждый раз Git будет останавливаться, давая вам исправить коммит, а потом, когда вы закончите, будет продолжать.

Переупорядочение коммитов

Интерактивное перемещение можно также использовать для изменения порядка следования и для полного удаления коммитов. Если вы хотите удалить коммит “added cat-file” и поменять порядок, в котором идут два других коммита, измените сценарий для rebase с такого pick f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file на такой: pick 310154e updated README formatting and added blame pick f7f3f6d changed my name a bit Когда вы сохраните и выйдите из редактора, Git откатит вашу ветку к родительскому для этих трёх коммиту, применит 310154e, затем f7f3f6d, а потом остановится. Вы фактически поменяли порядок следования коммитов и полностью удалили коммит “added cat-file”.

Уплотнение коммитов

С помощью интерактивного перемещения также возможно взять несколько коммитов и сплющить их в один коммит. Сценарий выдаёт полезное сообщение с инструкциями для перемещения: # # Commands: # p, pick = use commit # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # Если вместо pick или edit указать squash, Git применит изменения и из этого коммита, и из предыдущего, а затем даст вам объединить сообщения для коммитов. Итак, чтобы сделать один коммит из трёх наших коммитов, надо сделать так, чтобы сценарий выглядел следующим образом: pick f7f3f6d changed my name a bit squash 310154e updated README formatting and added blame squash a5f4a0d added cat-file Когда вы сохраните и выйдите из редактора, Git применит все три изменения, а затем опять выдаст вам редактор для того, чтобы объединить сообщения трёх коммитов: # This is a combination of 3 commits. # The first commit's message is: changed my name a bit # This is the 2nd commit message: updated README formatting and added blame # This is the 3rd commit message: added cat-file Когда вы это сохраните, у вас будет один коммит, который вносит изменения такие же как три бывших коммита.

Разбиение коммита

Разбиение коммита — это отмена коммита, а затем индексирование изменений частями и добавление коммитов столько раз, сколько коммитов вы хотите получить. Например, предположим, что вы хотите разбить средний из наших трёх коммитов. Вместо updated README formatting and added blame, вы хотите получить два отдельных коммита: updated README formatting в качестве первого и added blame в качестве второго. Вы можете сделать это в сценарии rebase -i поставив edit в инструкции для коммита, который хотите разбить: pick f7f3f6d changed my name a bit edit 310154e updated README formatting and added blame pick a5f4a0d added cat-file Теперь, когда сценарий выбросит вас в командную строку, отмените этот коммит с помощью reset, возьмите изменения, которые были сброшены и создайте из них несколько коммитов. Когда вы сохраните и выйдите из редактора, Git откатится к родителю первого коммита в списке, применит первый коммит (f7f3f6d), применит второй (310154e) и выбросит вас в консоль. Здесь вы можете сбросить этот коммит в смешанном режиме с помощью git reset HEAD^ — это эффективно отменит этот коммит и оставит изменённые файлы непроиндексированными. Теперь вы можете добавлять файлы в индекс и делать коммиты, пока не получите несколько штук. Затем, когда закончите, выполните git rebase --continue: $ git reset HEAD^ $ git add README $ git commit -m 'updated README formatting' $ git add lib/simplegit.rb $ git commit -m 'added blame' $ git rebase --continue Когда Git применит последний коммит (a5f4a0d) в сценарии, история будет выглядеть так: $ git log -4 --pretty=format:"%h %s" 1c002dd added cat-file 9b29157 added blame 35cfb2b updated README formatting f3cc40e changed my name a bit Повторимся ещё раз, что эта операция меняет SHA всех коммитов в списке, так что убедитесь, что ни один из коммитов в этом списке вы не успели уже отправить в общий репозиторий.

Крайнее средство: filter-branch

Есть ещё один вариант переписывания истории, который можно использовать если надо переписать большое количество коммитов в автоматизируемой форме — например, везде поменять свой e-mail адрес или удалить файл из каждого коммита — это команда filter-branch. Она может переписать огромные периоды вашей истории, так что, возможно, вообще не стоит использовать её, если только ваш проект не успел ещё стать публичным и другие люди не успели ещё проделать работу на основе коммитов, которые вы собрались переписать. Однако, она может быть весьма полезной. Мы посмотрим на некоторые типичные варианты использования команды так, чтобы вы получили представление о тех вещах, на которые она способна.

Удаление файла изо всех коммитов

Такое случается довольно часто. Кто-нибудь случайно добавляет в коммит огромный бинарный файл необдуманно выполнив git add ., и вы хотите удалить его отовсюду. Или, может быть, вы нечаянно добавили в коммит файл содержащий пароль, а теперь хотите сделать код этого проекта открытым. filter-branch — это тот инструмент, который вы наверняка захотите использовать, чтобы прочесать всю историю. Чтобы удалить файл с именем passwords.txt изо всей истории, используйте опцию --tree-filter для filter-branch: $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21) Ref 'refs/heads/master' was rewritten Опция --tree-filter выполняет указанную команду после выгрузки каждой версии проекта и затем заново делает коммит из результата. В нашем случае, мы удалили файл с именем passwords.txt из каждого снимка состояния независимо от того существовал ли он там или нет. Если вы хотите удалить все случайно добавленные резервные копии сделанные вашим текстовым редактором, выполните что-то типа git filter-branch --tree-filter 'rm -f *~' HEAD. Вы увидите как Git переписывает деревья и коммиты, а в конце переставляет указатель ветки. Как правило, хороший вариант — делать это в тестовой ветке, а затем жёстко сбрасывать ветку master с помощью reset --hard, когда вы поймёте, что результат это то, чего вы действительно добивались. Чтобы запустить filter-branch для всех веток, можно передать команде параметр --all.

Сделать подкаталог новым корнем

Предположим, вы импортировали репозиторий из другой системы управления версиями и в нём есть бессмысленные каталоги (trunk, tags, и др.). Если вы хотите сделать trunk новым корнем проекта, команда filter-branch может помочь вам сделать и это: $ git filter-branch --subdirectory-filter trunk HEAD Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12) Ref 'refs/heads/master' was rewritten Теперь всюду корневой каталог проекта будет в подкаталоге trunk. Git также автоматически удалит все коммиты, которые не затрагивают данный подкаталог.

Глобальное именение e-mail адреса

Ещё один типичный случай это, когда вы забыли выполнить git config, чтобы задать своё имя и e-mail адрес перед тем как начать работать. Или, возможно, вы хотите открыть код своего проекта с работы и поменять все свои рабочие e-mail’ы на свой личный адрес. В любом случае, с помощью filter-branch вы с таким же успехом можете поменять адреса почты в нескольких коммитах за один раз. Вам надо быть аккуратным, чтобы не поменять и чужие адреса, поэтому используйте --commit-filter: $ git filter-branch --commit-filter ' if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ]; then GIT_AUTHOR_NAME="Scott Chacon"; GIT_AUTHOR_EMAIL="schacon@example.com"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD Эта команда проходит по всем коммитам и переписывает их так, чтобы там был указан новый адрес. Так как коммиты содержат значения SHA-1 своих родителей, эта команда поменяет все SHA в вашей истории, а не только те, в которых есть указанный e-mail адрес. Pro Git

Перекрёстные ссылки книги для Перезапись истории

  • Прятанье
  • Вверх
  • Отладка с помощью Git

Book navigation

  • Введение
  • Основы Git
  • Ветвление в Git
  • Git на сервере
  • Распределённый Git
  • Инструменты Git
    • Выбор ревизии
    • Интерактивное индексирование
    • Прятанье
    • Перезапись истории
    • Отладка с помощью Git
    • Подмодули
    • Слияние поддеревьев
    • Итоги

Последние материалы

  • Утилита xrandr
    3 weeks 1 day ago
  • Sane в Linux
    1 month 1 week ago
  • Приложение Zoom
    2 months 3 weeks ago
  • Команда restore
    3 months 1 week ago
  • Файл sudoers
    3 months 2 weeks ago
RSS feed

Secondary menu

  • О проекте

© 2008–2025 Олег Меньшенин mensh@yandex.ru