Продвинутые операции с коммитами в Git

GitGitBeginner
Практиковаться сейчас

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

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

К концу этого практического занятия вы сможете исправлять коммиты, отменять изменения, выбирать определенные коммиты (cherry-pick), выполнять интерактивное перебазирование (interactive rebasing) и объединять коммиты (squash). Это мощные инструменты, которые профессиональные разработчики используют каждый день, чтобы поддерживать чистую и организованную историю проекта. Давайте приступим и повысим ваш уровень навыков работы с Git!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL git(("Git")) -.-> git/DataManagementGroup(["Data Management"]) git(("Git")) -.-> git/BranchManagementGroup(["Branch Management"]) git(("Git")) -.-> git/SetupandConfigGroup(["Setup and Config"]) git(("Git")) -.-> git/BasicOperationsGroup(["Basic Operations"]) git/SetupandConfigGroup -.-> git/init("Initialize Repo") git/BasicOperationsGroup -.-> git/add("Stage Files") git/BasicOperationsGroup -.-> git/commit("Create Commit") git/DataManagementGroup -.-> git/reset("Undo Changes") git/BranchManagementGroup -.-> git/branch("Handle Branches") git/BranchManagementGroup -.-> git/checkout("Switch Branches") git/BranchManagementGroup -.-> git/log("Show Commits") git/BranchManagementGroup -.-> git/cherry_pick("Cherry Pick") git/BranchManagementGroup -.-> git/rebase("Reapply Commits") subgraph Lab Skills git/init -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/add -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/commit -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/reset -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/branch -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/checkout -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/log -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/cherry_pick -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} git/rebase -.-> lab-387471{{"Продвинутые операции с коммитами в Git"}} end

Настройка рабочего пространства

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

Откройте терминал и введите следующие команды, нажимая Enter после каждой строки:

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

Разберем, что делают эти команды пошагово:

  1. cd ~/project: cd означает "change directory" (изменить директорию). ~/project - это путь, который обычно указывает на папку "project" в вашем домашнем каталоге (~). Эта команда переводит терминал в эту директорию. Если директория "project" не существует в вашем домашнем каталоге, вы можете столкнуться с ошибкой. В таком случае, создайте ее с помощью mkdir ~/project, а затем попробуйте снова cd ~/project.
  2. mkdir git-advanced-lab: mkdir означает "make directory" (создать директорию). Эта команда создает новую папку с именем "git-advanced-lab" в текущей директории (которая должна быть ~/project после предыдущей команды). Эта новая директория станет корнем нашего Git-репозитория для этого практического занятия.
  3. cd git-advanced-lab: Эта команда снова изменяет текущую директорию, перемещая вас в только что созданную директорию "git-advanced-lab". Теперь любые команды, которые вы запускаете, будут выполняться в этой директории.
  4. git init: Это важная команда Git для инициализации репозитория. git init настраивает все необходимые структуры Git в текущей директории (git-advanced-lab), превращая ее в Git-репозиторий. Вы увидите скрытую папку с именем .git, созданную внутри git-advanced-lab. Эта папка .git является сердцем вашего Git-репозитория и хранит всю историю версий и настройки.

Теперь, когда мы инициализировали Git-репозиторий, создадим простой файл для работы и сделаем первый коммит:

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

Вот что происходит в этих командах:

  1. echo "Hello, Advanced Git" > hello.txt: echo - это команда, которая выводит текст. "Hello, Advanced Git" - это текст, который мы хотим вывести. > - это оператор перенаправления. Он берет вывод команды echo и перенаправляет его в файл с именем "hello.txt". Если "hello.txt" не существует, он будет создан. Если он существует, он будет перезаписан новым содержимым. Таким образом, эта команда создает новый файл с именем "hello.txt" с содержимым "Hello, Advanced Git".
  2. git add hello.txt: Прежде чем Git может отслеживать изменения в файле, вы должны явно сказать Git начать отслеживать его. git add hello.txt добавляет файл "hello.txt" в область подготовки (staging area). Область подготовки - это место, где вы готовите файлы для включения в следующий коммит. Представьте себе область подготовки как зону подготовки для вашего коммита.
  3. git commit -m "Initial commit": git commit берет все изменения, которые в настоящее время находятся в области подготовки (в нашем случае, добавление файла "hello.txt") и сохраняет их как новый коммит в истории репозитория. -m "Initial commit" добавляет сообщение коммита. "Initial commit" - это само сообщение, которое должно быть кратким описанием изменений, которые вы коммитируете. Лучше писать ясные и краткие сообщения коммитов.

Отлично! Теперь у нас есть репозиторий с одним коммитом. Проверим статус, чтобы убедиться, что все настроено правильно:

git status

После выполнения команды git status в терминале вы должны увидеть сообщение, похожее на следующее:

On branch master
nothing to commit, working tree clean

Это сообщение означает, что ваше рабочее дерево (working tree) чисто. "Рабочее дерево" - это директория, где находятся файлы вашего проекта. "Чисто" означает, что в вашей рабочей директории нет изменений, которые еще не были закоммичены. Это подтверждает, что мы готовы приступить к продвинутым операциям с чистого листа!

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

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

Давайте попробуем. Сначала изменим наш файл hello.txt, добавив еще одну строку:

echo "This is an important file." >> hello.txt

Эта команда добавляет новую строку в наш файл "hello.txt". Разберём оператор >>:

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

Таким образом, после выполнения этой команды файл "hello.txt" будет содержать две строки:

Hello, Advanced Git
This is an important file.

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

git add hello.txt
git commit --amend -m "Initial commit with important note"

Вот разбор этих команд:

  1. git add hello.txt: Мы изменили hello.txt, поэтому нам нужно снова добавить изменения в область подготовки (staging area) с помощью git add. Это сообщает Git, что мы хотим включить текущее состояние файла "hello.txt" в наш коммит.
  2. git commit --amend -m "Initial commit with important note": Вот здесь происходит магия.
    • git commit --amend: Флаг --amend сообщает Git, что вместо создания нового коммита мы хотим изменить последний коммит.
    • -m "Initial commit with important note": Это предоставляет новое сообщение коммита. Если вы опустите опцию -m, Git откроет ваш текстовый редактор по умолчанию, позволив вам отредактировать исходное сообщение коммита.

Что произошло?

Вместо создания нового коммита в истории Git, Git фактически заменил предыдущий "Initial commit" новым, улучшенным вариантом. Старый коммит исчез, а новый коммит включает изменения, которые мы добавили в область подготовки, и обновленное сообщение коммита.

Давайте проверим это, посмотрев на журнал коммитов:

git log --oneline

В выводе вы должны увидеть только один коммит, но сообщение коммита теперь должно быть "Initial commit with important note".

<commit_hash> Initial commit with important note

Нажмите q, чтобы выйти из вида журнала, если он не закроется автоматически.

Важные соображения при исправлении коммитов:

  • Исправляйте только неподтвержденные коммиты: Вы должны только исправлять коммиты, которые вы еще не отправили в общий удаленный репозиторий (например, GitHub, GitLab и т.д.). Исправление уже отправленного коммита переписывает историю, от которой могут зависеть другие участники проекта, что может привести к серьезным проблемам и путанице в совместной работе.
  • Очистка локальной истории: Исправление коммитов в первую очередь полезно для очистки вашей локальной истории коммитов перед тем, как поделиться своей работой. Это отличный способ усовершенствовать свои коммиты, когда вы еще работаете над веткой функции, например.

Отмена коммита

Иногда вы делаете коммит, а потом понимаете, что он привел к появлению ошибки или вы просто хотите отменить эти изменения. Можно подумать об использовании git reset для возврата назад во времени и удаления коммита. Однако git reset может быть опасным, особенно если вы уже отправили свои изменения или если другие участники работают над той же веткой. Более безопасным и подходящим для совместной работы способом отмены изменений является использование git revert.

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

Давайте создадим новый коммит, который мы потом отменим:

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

Эти команды похожи на те, которые мы уже выполняли:

  1. echo "This line will be reverted" >> hello.txt: Добавляет новую строку в наш файл "hello.txt".
  2. git add hello.txt: Добавляет измененный файл в область подготовки (staging area).
  3. git commit -m "Add line to be reverted": Создает новый коммит с сообщением "Add line to be reverted", который включает добавление этой новой строки в файл "hello.txt".

Теперь предположим, что мы решили, что добавление строки "This line will be reverted" было ошибкой, и мы хотим удалить ее. Мы можем отменить последний коммит с помощью:

git revert HEAD

Разберем эту команду:

  • git revert: Это команда для отмены изменений.
  • HEAD: В Git HEAD - это указатель на текущий коммит вашей текущей ветки. В большинстве случаев HEAD ссылается на самый последний коммит. Таким образом, git revert HEAD означает "отменить самый последний коммит". Вы также можете указать конкретный хэш коммита, чтобы отменить более старый коммит.

Когда вы выполняете git revert HEAD, Git сделает следующее:

  1. Анализ изменений: Он анализирует изменения, внесенные коммитом HEAD (в нашем случае, "Add line to be reverted").
  2. Создание обратных изменений: Он определяет, как отменить эти изменения (в нашем случае, удалить строку "This line will be reverted" из файла "hello.txt").
  3. Создание нового коммита: Он автоматически создает новый коммит, который применяет эти обратные изменения.
  4. Открытие текстового редактора (по желанию): Git обычно откроет ваш текстовый редактор по умолчанию (например, Vim, Nano, редактор VS Code и т.д.) с предзаполненным сообщением коммита для коммита отмены. По умолчанию сообщение обычно выглядит как "Revert 'Add line to be reverted'". Вы можете принять сообщение по умолчанию или изменить его, чтобы добавить больше контекста.

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

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

  • Для принятия сообщения по умолчанию и завершения отмены:
    • Если это Vim: Нажмите клавишу Esc (чтобы войти в командный режим), затем введите :wq (записать и выйти) и нажмите Enter.
    • Если это Nano: Нажмите Ctrl + X (чтобы выйти), затем Y (чтобы сохранить изменения) и Enter (чтобы подтвердить имя файла).
    • Для других редакторов: Найдите опции "Сохранить" и "Закрыть" или "Выйти".

После того, как вы сохраните и закроете редактор (если он открылся), Git завершит создание коммита отмены.

Давайте теперь проверим нашу историю коммитов:

git log --oneline

В журнале вы должны увидеть три коммита в обратном хронологическом порядке (сначала новые):

  1. Коммит, начинающийся с "Revert" (коммит отмены, который мы только что создали).
  2. "Add line to be reverted" (коммит, который мы отменили).
  3. "Initial commit with important note" (наш исходный исправленный коммит).
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original> Add line to be reverted
<commit_hash_initial> Initial commit with important note

Если вы посмотрите на содержимое файла hello.txt сейчас, строка "This line will be reverted" должна быть удалена, фактически отменяя изменения из коммита "Add line to be reverted".

Почему git revert предпочтительнее git reset в общих репозиториях?

  • Неразрушающая история: git revert не переписывает историю. Он добавляет новый коммит для отмены изменений, что делает его безопасным для совместной работы. Все могут увидеть исходную ошибку и ее исправление в истории.
  • Избегание принудительного пуша: git reset часто требует принудительного отправления (git push --force) в удаленный репозиторий, если вы уже отправили коммиты. Принудительное отправление может быть очень разрушительным и перезаписать работу других участников. git revert избавляет от этой проблемы.
  • Четкая аудит-отчетность: git revert предоставляет четкую запись о том, когда и почему изменения были отменены. Это ценно для понимания эволюции проекта и отладки проблем.

Выбор отдельных коммитов (Cherry-picking)

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

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

Сначала нам нужно создать новую ветку и сделать коммит в ней:

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

Разберем эти команды:

  1. git checkout -b feature-branch:
    • git checkout: Эта команда используется для переключения между ветками или создания новых веток.
    • -b feature-branch: Флаг -b сообщает git checkout создать новую ветку с именем "feature-branch" и сразу переключиться на нее. Таким образом, эта команда создает новую ветку и делает ее активной.
  2. echo "This is a new feature" >> feature.txt: Создает новый файл с именем "feature.txt" с содержимым "This is a new feature" в нашей ветке "feature-branch".
  3. git add feature.txt: Добавляет только что созданный файл "feature.txt" в область подготовки (staging area).
  4. git commit -m "Add new feature": Создает коммит в ветке "feature-branch" с сообщением "Add new feature", включая добавление файла "feature.txt".

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

git checkout master

Эта команда просто переключает вашу активную ветку обратно на "master".

Теперь мы хотим применить коммит "Add new feature" из ветки "feature-branch" в нашу ветку master. Для этого мы используем git cherry-pick:

git cherry-pick feature-branch

Подождите, почему feature-branch, а не хэш коммита? В этом простом случае feature-branch в качестве "commit-ish" будет разрешено в последний коммит в ветке feature-branch. Поскольку мы сделали только один коммит в ветке feature-branch, это фактически выбирает этот коммит. Если бы у вас было несколько коммитов в ветке feature-branch и вы хотели выбрать определенный, вы бы использовали хэш этого конкретного коммита. Например, вы могли бы найти хэш коммита с помощью git log feature-branch --oneline и затем использовать git cherry-pick <commit_hash>.

Когда вы выполняете git cherry-pick feature-branch, Git делает следующее:

  1. Поиск коммита: Он определяет последний коммит в ветке "feature-branch" (или коммит, указанный по хэшу).
  2. Применение изменений: Он пытается применить изменения, внесенные этим коммитом, к текущей ветке (master).
  3. Создание нового коммита: Если все прошло успешно (нет конфликтов), Git создает новый коммит в вашей ветке master, который имеет те же изменения, что и выбранный коммит, и обычно то же самое сообщение коммита.

Давайте проверим журнал коммитов в ветке master:

git log --oneline

Вы должны увидеть новый коммит в вашей ветке master с сообщением "Add new feature". Важно, что это новый коммит с другим хэшем, чем коммит в ветке "feature-branch", даже если изменения одинаковы.

Также, если вы выведете список файлов в вашей ветке master (например, с помощью команды ls), вы должны увидеть новый файл "feature.txt" и там.

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

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

Интерактивное перебазирование (Interactive rebasing)

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

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

Давайте создадим серию коммитов, чтобы посмотреть, как работает интерактивное перебазирование:

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

Эти команды создают три новых коммита последовательно, каждый из которых добавляет строку в наш файл "hello.txt". Сокращение -am в git commit -am объединяет добавление всех измененных и удаленных файлов в область подготовки (-a) с созданием коммита (-m). Это удобный ярлык, когда вы уже отслеживаете файлы и просто хотите закоммитить изменения в них.

Теперь предположим, что мы хотим очистить эти три коммита. Возможно, "First change" и "Second change" на самом деле были частью одного логического изменения, и мы хотим объединить их в один коммит. И, возможно, мы хотим улучшить сообщение коммита для "Third change". Интерактивное перебазирование идеально подходит для этого.

Начните интерактивную сессию перебазирования с помощью:

git rebase -i HEAD~3

Понять эту команду можно так:

  • git rebase -i: git rebase - это команда для перебазирования. Флаг -i означает "интерактивный", который сообщает Git, что мы хотим выполнить интерактивное перебазирование.
  • HEAD~3: Это указывает диапазон коммитов, которые мы хотим перебазировать. HEAD ссылается на текущий коммит. ~3 означает "вернуться на три коммита назад от HEAD". Таким образом, HEAD~3 выбирает последние три коммита. Вы также можете указать хэш коммита вместо HEAD~3, чтобы начать перебазирование с определенной точки в истории.

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

1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 #
7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ##                    commit's log message, unless -C is used, in which case
14 ##                    keep only this commit's message; -c is same as -C but
15 ##                    opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 #.       create a merge commit using the original merge commit's
23 #.       message (or the oneline, if no original merge commit was
24 #.       specified); use -c <commit> to reword the commit message
25 #
26 ## These lines can be re-ordered; they are executed from top to bottom.
27 #
28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 #
30 ## However, if you remove everything, the rebase will be aborted.

Понимание интерактивного редактора перебазирования:

  • Список коммитов: Верхняя часть файла содержит список коммитов, которые входят в операцию перебазирования. По умолчанию они перечислены в обратном хронологическом порядке (самый старый сверху, самый новый снизу).
  • Команды: Под списком коммитов Git предоставляет список доступных команд, которые вы можете использовать для изменения каждого коммита. Самые распространенные из них:
    • pick (или p): Использовать коммит как есть. Это значение по умолчанию.
    • reword (или r): Использовать коммит, но позволить изменить сообщение коммита.
    • edit (или e): Использовать коммит, но остановить процесс перебазирования на этом коммите, чтобы вы могли внести дополнительные изменения (например, исправить файлы, добавить новые изменения).
    • squash (или s): Объединить этот коммит с предыдущим коммитом в списке. Сообщение коммита этого коммита будет добавлено к сообщению предыдущего коммита (позже вы сможете отредактировать объединенное сообщение).
    • fixup (или f): Аналогично squash, но отбрасывает сообщение коммита этого коммита и использует только сообщение предыдущего коммита.
    • drop (или d): Полностью удалить этот коммит из истории.

Наша план интерактивного перебазирования:

Мы хотим:

  1. Объединить "Second change" с "First change".
  2. Изменить сообщение для "Third change" на более информативное.

Для этого измените файл в редакторе так, чтобы он выглядел следующим образом:

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

Не изменяйте хэши коммитов в файле редактора.

Инструкции по редактированию в Vim (если вы используете этот редактор):

  1. Войти в режим вставки: Нажмите клавишу i. Это позволит вам вводить текст и редактировать файл.
  2. Внести изменения: Используйте стрелки на клавиатуре, чтобы перейти к нужным строкам и изменить "pick" на "squash" и "reword", как показано выше.
  3. Выйти из режима вставки: Нажмите клавишу Esc.
  4. Сохранить и выйти: Введите :wq и нажмите Enter.

После сохранения и закрытия файла редактора Git начнет выполнять операции перебазирования в соответствии с вашими инструкциями.

Сначала, объединение коммитов:

Git сначала обработает команду "squash". Он объединит изменения из "Second change" в "First change". Затем он снова откроет ваш текстовый редактор, на этот раз, чтобы вы могли отредактировать объединенное сообщение коммита для объединенного коммита. Вы увидите что-то вроде этого в редакторе:

## This is a combination of 2 commits.
## This is the 1st commit message:

First change

## This is the 2nd commit message:

Second change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:30:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (2 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
## Next command to do (1 remaining command):
##    reword ghi9101 Third change
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

В этом редакторе показаны исходные сообщения коммитов, которые объединяются. Теперь вы можете отредактировать этот текст, чтобы создать единое, связанное сообщение коммита для объединенного коммита. Например, вы можете изменить его на:

Combined first and second changes: Initial setup of hello.txt

После редактирования сообщения сохраните и закройте редактор.

Далее, изменение сообщения коммита:

Затем Git перейдёт к команде "reword" для "Third change". Он снова откроет редактор, на этот раз, чтобы вы могли отредактировать сообщение коммита для "Third change". Вы увидите исходное сообщение:

Third change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:35:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (3 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
##    reword ghi9101 Third change
## Next command to do (0 remaining commands):
##    (finish)
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

Измените сообщение на более описательное, например:

Improved third change: Added a more descriptive line to hello.txt

Сохраните и закройте редактор.

Завершение перебазирования:

После обработки всех команд в ваших инструкциях по интерактивному перебазированию Git завершит процесс перебазирования. Обычно вы увидите сообщение вроде "Successfully rebased and updated refs/heads/master."

Проверка перебазированной истории:

Теперь снова проверьте журнал коммитов:

git log --oneline

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

<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note

Обратите внимание, что:

  • Теперь у вас меньше коммитов, чем раньше (мы объединили два коммита в один).
  • Сообщение коммита для "First change" и "Second change" теперь объединено и более информативно.
  • Сообщение коммита для "Third change" было изменено.
  • Хэши коммитов перебазированных коммитов изменились, так как мы переписали историю.

Важные напоминания об интерактивном перебазировании:

  • Инструмент для локальной истории: Интерактивное перебазирование в первую очередь является инструментом для очистки вашей локальной истории коммитов.
  • Избегайте перебазирования общих веток: Не перебазируйте ветки, которые были отправлены в общий репозиторий, если вы не абсолютно уверены в своих действиях и не согласовали это с командой. Переписывание общей истории может привести к серьезным проблемам для коллег по проекту.
  • git rebase --abort: Если вы совершили ошибку во время интерактивного перебазирования, или если вы запутались или столкнулись с конфликтами, которые не можете разрешить, вы всегда можете использовать git rebase --abort, чтобы отменить операцию перебазирования и вернуть вашу ветку в состояние, в котором она была до начала перебазирования. Это безопасный способ выйти из перебазирования, если что-то пойдет не так.

Перебазирование с корневого коммита

В шаге 5 мы использовали git rebase -i HEAD~3 для перебазирования последних трех коммитов. Однако интерактивное перебазирование может быть еще мощнее. Команда git rebase -i --root позволяет вам интерактивно перебазировать все коммиты в истории вашего репозитория, начиная от самого первого (корневого) коммита. Это дает вам полный контроль над изменением всей истории проекта.

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

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

Давайте попробуем перебазирование с корневого коммита:

git rebase -i --root

Эта команда очень похожа на предыдущее перебазирование, но вместо HEAD~3 мы используем --root. Флаг --root сообщает Git, что нужно учитывать все коммиты с самого начала истории репозитория для интерактивного перебазирования.

При выполнении этой команды снова откроется ваш текстовый редактор по умолчанию, но на этот раз он выведет все коммиты в вашем репозитории, начиная от самого первого коммита ("Initial commit with important note" или то, как вы его изменили). Теперь список будет длиннее, чем в предыдущем шаге.

Вы, возможно, увидите что-то вроде этого (хэши и сообщения коммитов могут отличаться в зависимости от того, как вы прошли предыдущие шаги):

pick <hash_initial> Initial commit with important note
pick <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

Наша план перебазирования с корневого коммита:

В рамках этого упражнения давайте сделаем несколько вещей при перебазировании с корневого коммита:

  1. Изменить сообщение первого коммита. Предположим, что мы хотим еще больше уточнить сообщение нашего начального коммита.
  2. Объединить коммит "Revert" с коммитом, в котором была добавлена строка, которую мы отменили. Это немного надуманный пример, но он демонстрирует, как можно манипулировать историей. Можно утверждать, что коммит "Revert" на самом деле является частью исправления первоначальной ошибки, поэтому мы можем объединить его с коммитом, в котором эта ошибка была допущена. (В реальной ситуации вы не всегда захотите объединять коммиты отмены, так как они предоставляют четкую запись об отмене изменений).

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

reword <hash_initial> Initial commit with important note
squash <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

Мы изменили "pick" на "reword" для первого коммита и "pick" на "squash" для второго коммита ("Add line to be reverted"). Пока оставьте коммит "Revert" как "pick".

Сохраните и закройте редактор.

Сначала, изменение сообщения начального коммита:

Git сначала остановится, чтобы вы могли изменить сообщение начального коммита. В редакторе откроется текущее сообщение "Initial commit with important note" (или то, как вы его изменили на шаге 2). Измените его на что-то вроде: "Initial setup and file creation". Сохраните и закройте редактор.

Далее, объединение "Add line to be reverted" с начальным коммитом:

Затем Git обработает команду "squash". Он объединит изменения из "Add line to be reverted" в предыдущий коммит (который теперь - "Initial setup and file creation"). Редактор снова откроется, чтобы вы могли отредактировать объединенное сообщение коммита. Вы увидите исходные сообщения обоих коммитов. Вы можете составить новое сообщение, которое отражает объединенные изменения. Например, вы можете изменить сообщение на:

Initial setup and file creation with subsequent line removal

Сохраните и закройте редактор.

Завершение перебазирования:

Git продолжит обрабатывать остальные команды ("pick" для оставшихся коммитов) и завершит перебазирование с корневого коммита.

Проверка истории после перебазирования с корневого коммита:

Проверьте журнал коммитов последний раз:

git log --oneline

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

  • Первый коммит должен иметь новое сообщение "Initial setup and file creation with subsequent line removal" (или похожее).
  • Коммит "Add line to be reverted" и коммит "Revert" могут по-прежнему присутствовать, или они могут быть объединены, в зависимости от того, как именно вы указали перебазирование. (В приведенном выше примере инструкций мы оставили "Revert" как "pick", поэтому он должен остаться).
  • Хэши коммитов будут изменены на протяжении всей истории, начиная от корневого коммита.

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

Итоги

Поздравляем, мастер Git! Вы еще больше повысили свои навыки управления версиями. Давайте обобщим мощные техники, которые вы узнали в этом практическом занятии:

  1. Исправление коммитов: Теперь вы можете исправлять мелкие ошибки или добавлять небольшие изменения в самый последний коммит без создания нового коммита, сохраняя чистую историю для мелких корректировок.
  2. Отмена коммитов: Вы узнали, как безопасно отменить изменения, создав новый коммит, который отменяет предыдущий. Это неразрушающий способ исправить ошибки в совместной среде.
  3. Выбор отдельных коммитов (Cherry-picking): Теперь вы можете применять определенные коммиты из одной ветки в другую, получая точный контроль над тем, какие изменения вы включаете. Это идеально подходит для выборочного добавления функций или исправлений.
  4. Интерактивное перебазирование: Вы овладели искусством переписывания истории, что позволяет вам очищать и организовывать свои коммиты перед тем, как поделиться ими с другими. Это включает изменение порядка, объединение и редактирование сообщений коммитов для создания более качественной истории.
  5. Перебазирование с корневого коммита: Вы узнали, как изменить всю историю проекта, начиная от самого первого коммита, получая полный контроль над историей репозитория. Помните о силе и рисках, связанных с этим!

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