Система контроля версий — неотъемлемый инструмент в рабочем процессе любого разработчика. С его помощью вы можете сохранять состояния вашего проекта, переключаться между ними и поддерживать разработку несколькими людьми. Самой популярной системой контроля версий на текущий момент является git.
В этом посте мы не будем изучать основы git — есть множество туториалов, в том числе интерактивных. Предполагается, что вы знаете как создавать коммиты, просматривать историю и выполнять другие базовые команды. Мы займемся не самой распространенной проблемой при использовании git. Бывают ситуации, когда по невнимательности или чьей-то некомпетентности в репозитории оказывается нежелательная информация. Возможно даже, что после этого она была удалена и в текущей версии проекта ее нет, но она осталась в истории git и, откатившись к определенной версии, ее можно будет восстановить. Изменять историю мы будем с помощью git-команды rebase в режиме interactive.
Для большей реалистичности мы решим эту задачу на примере реальных проблем, которые могут возникнуть в вашей программистской практике.
Перезапись истории — очень опасный прием в том случае, если вы изменяете коммиты, которые уже находятся в публичном репозитории. Если есть разработчики, которые пользуются вашим проектом, то обновление истории в удаленном репозитории может сильно осложнить им жизнь, когда они получат несогласованные изменения. Поэтому используйте интерактивный rebase и любые другие способы изменения истории на коммитах, которые существуют только в вашем локальном репозитории либо, если вы знаете всех людей, которые занимаются проектом и вы можете согласовать с ними этот процесс.
Для демонстрации возможностей interactive rebase я создал тестовый репозиторий, в котором буду моделировать описанные выше проблемы.
Начнем с ситуации, когда вы сделали коммит и сразу осознали, что допустили ошибку в его message:
В этом случае использование interactive rebase даже не потребуется. Все, что вам нужно — использовать команду git commit --amend, которая позволяет изменить предыдущий коммит. После вызова команды откроется дефолтный редактор, в котором нужно исправить текст сообщения, сохраниться и выйти.
Аналогично решаются и остальные проблемы в случае, когда неудачный коммит — последний (перед вызовом git commit --amend сделайте необходимые изменения и добавьте файлы в stage-область). Поэтому далее будем рассматривать только вариант, когда после проблемного коммита были и другие.
Итак, после коммита с опечаткой было сделано несколько других изменений:
Здесь в дело вступает интерактивный rebase. Команда rebase позволяет перемещать коммиты между ветками, в данном случае мы будем их перемещать на то же самое место. А благодаря интерактивному режиму (флаг --interactive/-i), каждый из перемещаемых коммитов можно редактировать, изменяя текст сообщения, используемые файлы и их содержимое.
Необходимо вызвать rebase на коммите, предшествующем тому, в котором нужно, что-то изменить. Для этого нужно либо передать хэш этого коммита (972078a), либо насколько он отстает от текущего (HEAD~3). Я предпочитаю первый вариант, поскольку история может быть гораздо длиннее, а считать коммиты не хочется.
Вновь откроется дефолтный редактор, в котором необходимо выбрать коммиты для изменения.
Как можно увидеть, вся необходимая информация описана в этом файле. Изначально все коммиты берутся без изменений (об этом говорит слово pick). Для тех коммитов, которые требуют исправлений, замените pick на соответствующий вариант. Так как мы хотим изменить сообщение коммита 0be0764, заменим pick на reword либо в сокращенном варианте – r:
После того, как вы сохранитесь и выйдете, откроется редактор с точно таким же содержимым, как в случае commit --amend. Остается только изменить текст сообщения и выйти. История отредактирована:
Допустим, в какой-то момент мы добавили в публичный доступ логин и пароль к нашему сервису. Так выглядит история:
Снова воспользуемся interactive rebase. Вызов на коммите bfe4560, откроет редактор со следующим содержимым:
У нас есть два варианта. Если коммит b94ffdb содержит и другие изменения помимо нежелательных, то следует заменить pick на edit, что означает исправление данного коммита, и переименовать коммит соответствующим образом. В случае, если это единственная правка в коммите, то его следует удалить (для этого используйте слово drop).
Если вы решили исправить коммит, то после закрытия редактора, репозиторий окажется в состоянии соответствующем этому коммиту. Сделайте изменения и выполните команду git commit --amend, тем самым переписав коммит. Далее выполните команду git rebase --continue, которая продолжает процесс rebase, перемещаясь вверх по истории. Если изменения, которые вы произвели пересекаются с изменениями в следующих коммитах, вам будет предложено исправить конфликты. Например:
Исправьте состояние файла на текущем коммите:
Далее добавьте файл в stage-область с помощью git add и продолжите процесс командой git rebase --continue. После завершения rebase получаем вывод:
В случае удаления коммита процесс будет аналогичным.
У нас имеется репозиторий, в который в определенный момент был добавлен файл с логами. После этого разработка продолжалась, причем этот файл тоже менялся. Потом мы наконец заметили, что тащим с собой логи, и удалили их. После этого мы все же решили, что это не лучшее решение и будет правильнее удалить логи из всего репозитория.
Для этого для начала найдем, в каких коммитах файл с логами менялся (в первую очередь нас интересует, когда он появился). Это можно сделать с помощью команды git log. Укажите путь к файлу, который ищем (используйте --follow, если файл мог быть переименован).
Результат:
Теперь, зная к какому коммиту откатываться, воспользуемся интерактивным rebase.
Заменим pick на edit для коммита 6618972 и укажем drop вместо pick для коммита e22c934. После этого мы перенесемся в состояние репозитория на коммите, в котором впервые добавили ненужный файл. Здесь нам необходимо физически удалить файл с диска и закоммитить это изменение с помощью git commit --amend. Тут не стоит бояться, что файл пропадет. То, что мы удалили этот файл в промежуточном состоянии никак не повлияет на то, что он останется у вас на диске в итоговом состоянии. Продолжаем rebase с помощью git rebase --continue.
Далее во всех коммитах, в которых этот файл изменялся, необходимо удалять его из stage-области (git reset HEAD path_to_file) и продолжать процесс. В итоге вы очистите историю от ненужного файла, а сам файл в своем конечном состоянии останется на диске.
С помощью интерактивного rebase можно существенно изменить историю git-репозитория. Из других наиболее полезных возможностей этой команды я бы выделил объединение и разделение коммитов. Благодаря этим приемам историю можно сделать более аккуратной. С полным списком возможностей можно ознакомиться в документации.
Written on November 3rd , 2019 by Alexey Kalina