Одно из основных правил Git заключается в том, что, так как большую часть работы вы делаете в своем локальном репозитории, вы можете переписывать свою историю локально так, как вам хочется.
Однако, как только вы отправите свои наработки на удаленный сервер, то с этого момента их нужно рассматривать как финальные до тех пор, пока у вас не появится весомая причина что-то изменить.
Если коротко, то вы должны воздержаться от отправки своих изменений до тех пор, пока не будете полностью довольны и готовы поделиться ими со всем миром.
Изменение вашего последнего коммита, наверное, наиболее частое исправление истории, которое вы будете выполнять. С последним коммитом можно сделать две основные операции: изменить сообщение коммита или изменить только что сделанный снимок, добавив, изменив или удалив файлы. Это очень просто.
Вместо последнего коммита будет создан новый коммит (с новым Id), который содержит прежние изменения и новое сообщение коммита.
Если нужно добавить изменения:
Вместо последнего коммита будет создан новый коммит (с новым Id), который содержит прежние и новые изменения и новое сообщение коммита.
Пример: Дополнить последний коммит.
Для изменения коммита, не являющегося последним, а расположенного где-то раньше в истории, нужно обратиться к более сложным инструментам. В Git отсутствуют инструменты для переписывания истории, но вы можете использовать перебазирование, чтобы перебазировать группу коммитов туда же на HEAD, где они были изначально, вместо перемещения их в другое место.
С помощью интерактивного перебазирования можно останавливаться после каждого нужного вам коммита и изменять сообщения, добавлять файлы или делать что-то другое, что вам нужно.
Для этого можно воспользоваться расширением Git Graph.
Чтобы вам было удобнее наблюдать за происходящим, откройте настройки расширения — (Repository Settings).
Установите флажок Include commits only mentioned by reflogs. Благодаря этому флажку, вы сможете видеть больше коммитов, чем обычно, например, коммиты, которые уже не принадлежат существующим веткам.
Итак, у вас есть история коммитов: первый коммит, второй коммит и третий коммит.В некоторый момент вы понимаете, что второй и третий коммиты оказались «недоделаны». То ли вы забыли дописать номер задачи в сообщение коммита, то ли забыли сделать изменения, а может быть и то и другое вместе.
Чтобы исправить эти коммиты, вы выделяете предшествующий им коммит, и нажимаете Rebase current branch on this Commit....
Другими словами, вы хотите перебазировать эти два коммита но в эту же ветку и после того же коммита, за которым они идут сейчас. В процессе перебазирования вы внесете в них нужные исправления.
Visual Studio Code предложит вам выполнить интерактивное перебазирование во встроенном терминале. Установите флажок Launch Interactive Rebase in new Terminal и нажмите Yes, rebase.
Visual Studio Code откроет терминал и покажет в нем план перебазирования с подсказками.
В данном случае план перебазирования включает в себя два коммита, которые перечислены в начале в обратной последовательности: сначала будет перебазирован второй коммит, после этого — третий коммит.
Для каждого из них указано действие. Сейчас, стандартно, это pick. Это действие означает, что коммит будет перебазирован без изменений.
Вы не уверены точно, что именно нужно изменить в этих двух коммитах поэтому выбираете действие edit.
Сейчас встроенный терминал находится в режиме команд, об этом говорит последняя строка. Чтобы отредактировать план перебазирования, нужно перевести терминал в режим редактирования. Терминал использует команды популярного в Linux текстового редактора Vim.
Чтобы перейти в режим редактирования, можно нажать Insert. Также это можно сделать, нажав клавиши «i» или «R».
О том, что редактор перешел в режим редактирования, вы поймете по последней строке. В ней будет написано «-- РЕЖИМ ВСТАВКИ --» (если вы нажали Insert или «i») или «-- РЕЖИМ ЗАМЕНЫ --» (если вы нажали Insert два раза или нажали «R»).
Отредактируйте план перебазирования.
Теперь изменения нужно сохранить и закрыть редактор.
Сначала вам нужно снова вернуться в режим команд. Для этого нажмите Esc. Режим редактирования исчезнет из последней строки.
После этого в английской раскладке введите символ «:» — фокус ввода переместится в последнюю строку, — а затем «wq». «w» — это команда сохранения файла, а «q» — это команда выхода из редактора.
Нажмите Ввод.
Visual Studio Code начнет перебазирование и остановится на первом коммите из плана перебазирования — второй коммит.
Второй
коммит извлечен в рабочий каталог, а от вас требуется выполнить одно из двух
действий. Либо заняться изменением этого коммита (git commit
--amend
), либо перейти к следующему (git rebase
--continue
).
Вы хотите изменить сообщение этого коммита, поэтому выполняете команду:
> git commit --amend
Visual Studio Code открывает сообщение коммита в редакторе.
Как и в случае с планом перебазирования, вы входите в режим редактирования (Insert), изменяете сообщение коммита, выходите из режима редактирования (Esc) и сохраняете и закрываете редактор («:wq»).
Visual Studio Code фиксирует ваши изменения в новом коммите и ожидает дальнейших указаний. Если все хорошо (в данном случае это так), то вы продолжаете перебазирование. Для этого вы выполняете команду:
git rebase --continue
В соответствии с вашим планом перебазирования Visual Studio Code останавливается на следующем, третьем коммите, и сразу же копирует его в новое место, за новым вторым коммитом.
В этом коммите, для простоты, вы хотите тоже только изменить сообщение. Поэтому поступаете с ним точно так же, как и с предыдущим.
После того, как вы изменили сообщение и закрыли текстовый редактор, Visual Studio Code зафиксирует ваши изменения в новом третьем коммите и будет ожидать дальнейших указаний.
Если все хорошо (в данном случае это так), то вы продолжаете перебазирование и выполняете команду:
git rebase --continue
Поскольку в вашем плане перебазирования больше ничего нет, операция успешно заканчивается, о чем сообщает Visual Studio Code.
В расширении Git Graph вы включили отображение дополнительных коммитов и видите историю в «честном» виде. В таком виде она удобна, например, для исправления ошибочных действий. Но для обычной работы удобнее видеть только те ветки, которые имеют непосредственное отношение к выполняемой вами задачи. Например, в разделе Граф системы управления версиями (Source Control Graph).
Тут хорошо видно, что вы сделали изменения, которые хотели, но теперь ваша локальная ветка main разошлась с веткой main, которая находится в удаленном репозитории.
Вспомните, что вы читали в предыдущих разделах про опасности перебазирования изменений, уже отправленных в удаленный репозиторий. Здесь в очередной раз это видно.
Если вы попробуете сейчас отправить свои изменения в удаленный репозиторий, то получите следующее сообщение:
Действительно, ваша ветка не является потомком серверной ветки, а значит сначала нужно влить в нее серверную ветку. Сделать это можно только локально.
Но что, если вы точно уверены в своих действиях и хотите именно заменить содержимое ветки в удаленном репозитории тем, которое есть у вас локально, не смотря ни на что?
Например, вы работаете с этими репозиториями один. Или вы точно уверены, что никто из ваших коллег не использовал ту удаленную ветку, которую вы хотите сейчас перезаписать.
В этом случае вы можете воспользоваться командной строкой и командой git push с параметром --force.
> git push --force origin main
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 12 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 821 bytes | 821.00 KiB/s, done.
Total 6 (delta 2), reused 4 (delta 2), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To https://github.com/1C-EDT-Developer/coldevex
+ ed25041...b4ce5b4 main -> main (forced update)
В результате удаленная ветка станет такой же, как ваша локальная ветка main, а коммиты второй коммит и третий коммит, существовавшие в удаленной ветке, как бы «перестанут существовать».
Вы можете использовать интерактивное перебазирование для изменения порядка или полного удаления коммитов. Если вы хотите удалить коммит Добавил README-файл и изменить порядок, в котором были внесены два оставшихся, то вы можете изменить план перебазирования с такого:
pick 398ba5d Исправил сообщения, выводимые в консоль
pick b4ce5b4 Добавил определение разрядности операционной системы
pick a5f4a0d Добавил README-файл
на такой:
pick b4ce5b4 Добавил определение разрядности операционной системы
pick 398ba5d Исправил сообщения, выводимые в консоль
Когда вы сохраните план перебазирования и выйдете из редактора, Git переместит вашу ветку на родителя этих коммитов, применит Добавил определение разрядности операционной системы, затем Исправил сообщения, выводимые в консоль и после этого остановится. Вы, фактически, изменили порядок этих коммитов и полностью удалили коммит Добавил README-файл.
С помощью интерактивного режима команды git rebase также можно объединить несколько коммитов в один.
Если вместо pick или edit вы укажете squash, Git применит изменения из текущего и предыдущего коммитов и предложит вам объединить их сообщения. Таким образом, если вы хотите из этих трех коммитов сделать один, ...
pick ce1cad6 Исправил сообщения, выводимые в консоль
pick d3b18bb Добавил определение разрядности операционной системы
pick c1a9da6 Добавил README-файл
... вы должны изменить план перебазирования следующим образом:
pick ce1cad6 Исправил сообщения, выводимые в консоль
squash d3b18bb Добавил определение разрядности операционной системы
squash c1a9da6 Добавил README-файл
Когда вы сохраните план и выйдете из редактора, Git применит изменения всех трех коммитов и затем вернет вас обратно в редактор, чтобы вы могли объединить сообщения коммитов:
# This is a combination of 3 commits.
# This is the 1st commit message:
Исправил сообщения, выводимые в консоль
# This is the commit message #2:
Добавил определение разрядности операционной системы
# This is the commit message #3:
Добавил README-файл
После сохранения сообщения, вы получите один коммит, содержащий изменения всех трех коммитов, существовавших ранее.
Разбиение коммита отменяет его и позволяет затем по частям индексировать и фиксировать изменения, создавая таким образом столько коммитов, сколько вам нужно.
pick ce1cad6 Исправил сообщения, выводимые в консоль
edit d3b18bb Добавил определение разрядности операционной системы
pick c1a9da6 Добавил README-файл
Затем, когда план вернет вас в командную строку, вам нужно будет отменить индексацию изменений этого коммита, и создать несколько коммитов на основе этих изменений.
Когда вы сохраните план перебазирования и выйдете из редактора, Git переместится на родителя первого коммита в вашем списке, применит первый коммит (Исправил сообщения, выводимые в консоль), применит второй (Добавил определение разрядности операционной системы), и вернет вас в консоль.
Здесь вы можете отменить коммит с помощью команды git reset HEAD^
,
которая, фактически, отменит этот коммит и удалит из индекса измененные файлы.
Теперь вы можете добавлять в индекс и фиксировать файлы, пока не создадите требуемые коммиты:
> git add bitdepthlin.sbsl
> git commit -m 'Добавил определение разрядности Linux'
[detached HEAD e9daf82] Добавил определение разрядности Linux
1 file changed, 11 insertions(+)
create mode 100644 bitdepthlin.sbsl
> git add bitdepthwin.sbsl
> git commit -m 'Добавил определение разрядности Windows'
[detached HEAD 93d86aa] Добавил определение разрядности Windows
1 file changed, 11 insertions(+)
create mode 100644 bitdepthwin.sbsl
После этого выполните команду git rebase --continue
.
Git применит последний коммит (Добавил README-файл) из плана перебазирования, и ваша история примет следующий вид.
И снова, при этом изменились SHA-1 хеши коммитов в вашем списке, поэтому убедитесь, что ни один коммит из этого списка ранее не был отправлен в удаленный репозиторий.
Обратите внимание, что первый коммит, с которого начиналось перебазирование, Исправил сообщения, выводимые в консоль, не изменился. Несмотря на то, что коммит был в списке перебазирования, он был отмечен как pick и применен до применения перебазирования, поэтому Git оставил его нетронутым.
Если вы хотите избавиться от какого-либо коммита, то удалить его можно во время интерактивного перебазирования. Выберите действие drop для коммита, который хотите удалить, или просто удалите его из плана перебазирования:
pick ce1cad6 Исправил сообщения, выводимые в консоль pick d3b18bb Добавил определение разрядности операционной системы drop c1a9da6 Добавил README-файл
Коммит Добавил README-файл будет удален.
Из-за того, как Git создает объекты коммитов, удаление или изменение коммита влечет за собой перезапись всех последующих коммитов. Чем дальше вы вернетесь в историю ваших коммитов, тем больше коммитов потребуется переделать. Это может вызвать множество конфликтов слияния, особенно если у вас много последующих коммитов, которые зависят от удаленного.
Если во время подобного перебазирования вы поняли, что это была не очень хорошая
идея, то всегда можно остановиться. Просто выполните команду git rebase
--abort
и ваш репозиторий вернется в то состояние, в котором он был до
начала перебазирования.
Вы можете полностью отменить операцию перебазирования и вернуть все в то состояние, которое было «до этого».
Например, у вас была такая история.
Затем вы решили третий коммит разделить на две части. В результате у вас появилось три новых коммита, а старые «исчезли».
После раздумий вы решили, что это была не очень хорошая идея и хотите вернуть все обратно.
Это можно сделать, так как старые коммиты ветки все еще находятся в базе данных объектов, но больше не видны, поскольку они больше не доступны ни из одной ветки. Вы можете увидеть эти коммиты и использовать их для отмены перебазирования.
По материалам книги Pro Git (авторы Scott Chacon и Ben Straub, издательство Apress). Книга распространяется по лицензии Creative Commons Attribution Non Commercial Share Alike 3.0 license.