Перейти к основному содержимому

Сократите последние X коммитов с помощью Git

· 10 мин. чтения

1. Обзор

Мы часто слышим слово «сквош», когда говорим о рабочих процессах Git .

В этом уроке мы кратко расскажем, что такое сжатие Git. Затем мы поговорим о том, когда нам нужно раздавить коммиты. Наконец, мы более подробно рассмотрим, как сквошить коммиты.

2. Что такое Git Squashing?

Когда мы говорим «сквош» в Git, это означает объединение нескольких непрерывных коммитов в один.

Давайте посмотрим на пример:

┌───┐      ┌───┐     ┌───┐      ┌───┐
... │ A │◄─────┤ B │◄────┤ C │◄─────┤ D │
└───┘ └───┘ └───┘ └───┘

After Squashing commits B, C, and D:

┌───┐ ┌───┐
... │ A │◄─────┤ E │
└───┘ └───┘

( The commit E includes the changes in B, C, and D.)

В этом примере мы объединили коммиты B, C и D в E.

Далее мы обсудим, когда нам следует подавлять коммиты.

3. Когда скрывать коммиты?

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

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

Однако, когда мы реализовали эту функцию, эти промежуточные коммиты выглядят излишними. Итак, мы можем захотеть объединить наши коммиты в один.

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

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

Итак, когда мы объединяем ветку feature с веткой master, мы хотим объединить 20 коммитов в один. Таким образом, мы сохраняем ветку master в чистоте.

4. Как раздавить коммиты?

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

Например, в IntelliJ мы можем выбрать коммиты, которые мы хотим раздавить, и выбрать «Squash Commits» в контекстном меню, вызываемом правой кнопкой мыши:

./15eeaf846ad0acef82ac97f289a918a8.png

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

Следует отметить, что squash не является командой Git, даже если это обычная операция Git. То есть « git squash… » — недопустимая команда Git.

Мы рассмотрим два разных подхода к раздавливанию коммитов:

  • Интерактивная перебазировка: git rebase -i …
  • Слияние с параметром –squash : git merge –squash ``

Далее давайте посмотрим на них в действии.

5. Сжатие с помощью Interactive Rebase

Прежде чем мы начнем, давайте создадим псевдоним Git slog (расшифровывается как короткий журнал), чтобы отображать журналы коммитов Git в компактном виде:

git config --global alias.slog = log --graph --all --topo-order --pretty='format:%h %ai %s%d (%an)'

В качестве примера мы подготовили репозиторий Git:

$ git slog
* ac7dd5f 2021-08-23 23:29:15 +0200 Commit D (HEAD -> master) (Kai Yuan)
* 5de0b6f 2021-08-23 23:29:08 +0200 Commit C (Kai Yuan)
* 54a204d 2021-08-23 23:29:02 +0200 Commit B (Kai Yuan)
* c407062 2021-08-23 23:28:56 +0200 Commit A (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Интерактивная перебазировка Git отобразит все соответствующие коммиты в редакторе по умолчанию. В данном случае это те коммиты, которые мы хотим раздавить.

Затем мы можем управлять каждым коммитом и сообщением коммита по своему усмотрению и сохранять изменения в редакторе.

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

Стоит отметить, что когда мы говорим «последние X коммитов», мы говорим о последних X коммитах из HEAD .

Итак, в данном случае это последние четыре коммита:

* ac7dd5f ... Commit D (HEAD -> master)
* 5de0b6f ... Commit C
* 54a204d ... Commit B
* c407062 ... Commit A

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

Стоит отметить, что принудительная отправка в общедоступный репозиторий может быть опасной операцией, поскольку она может перезаписать чужие коммиты.

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

Например, мы можем установить для свойства push.default значение current , чтобы только текущая ветвь была отправлена/принудительно отправлена в удаленный репозиторий.

В качестве альтернативы мы можем принудительно отправить только одну ветку, добавив «+» перед refspec для отправки. Например, git push origin +feature принудительно отправит ветку функций .

5.1. Раздавить последние X коммитов

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

git rebase -i HEAD~[X]

Итак, вот что мы должны запустить:

git rebase -i HEAD~4

После того, как мы выполним команду, Git запустит системный редактор по умолчанию (редактор Vim в этом примере) с коммитами, которые мы хотим удалить, и интерактивной справочной информацией по перебазированию:

./968e4a513767adc9c225cbaee22c5e6c.png

Как видно на снимке экрана выше, все четыре коммита, которые мы хотим сжать, перечислены в редакторе с помощью команды pick .

Подробное руководство о том, как управлять каждой фиксацией и сообщением о фиксации, приведено в последующих строках с комментариями.

Например, мы можем изменить команду выбора коммитов на s или squash , чтобы раздавить их:

./6359fa5dd9ce2b6fba9f6fa60c0b3b5e.png

Если мы сохраним изменения и выйдем из редактора, Git выполнит перебазирование, следуя нашим инструкциям:

$ git rebase -i HEAD~4
[detached HEAD f9a9cd5] Commit A
Date: Mon Aug 23 23:28:56 2021 +0200
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

Теперь вот что мы увидим, если еще раз проверим журнал коммитов Git:

$ git slog
* f9a9cd5 2021-08-23 23:28:56 +0200 Commit A (HEAD -> master) (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Как видно из вывода slog , мы сжали последние четыре коммита в один новый коммит, f9a9cd5 .

Теперь, если мы посмотрим на полный журнал фиксации, мы увидим, что сообщения всех раздавленных коммитов объединены:

$ git log -1
commit f9a9cd50a0d11b6312ba4e6308698bea46e10cf1 (HEAD -> master)
Author: Kai Yuan
Date: 2021-08-23 23:28:56 +0200

Commit A

Commit B

Commit C

Commit D

5.2. Когда X относительно большой

Мы узнали, что команда git rebase -i HEAD~X довольно проста для уничтожения последних X коммитов.

Однако подсчет большего числа X может быть проблемой, когда в нашей ветке довольно много коммитов. Более того, он подвержен ошибкам.

Когда X подсчитать непросто, мы можем найти хэш коммита, который мы хотим перебазировать «на», и запустить команду git rebase -i hash_onto .

Давайте посмотрим, как это работает:

$ git slog
e7cb693 2021-08-24 15:00:56 +0200 Commit F (HEAD -> master) (Kai Yuan)
2c1aa63 2021-08-24 15:00:45 +0200 Commit E (Kai Yuan)
ac7dd5f 2021-08-23 23:29:15 +0200 Commit D (Kai Yuan)
5de0b6f 2021-08-23 23:29:08 +0200 Commit C (Kai Yuan)
54a204d 2021-08-23 23:29:02 +0200 Commit B (Kai Yuan)
c407062 2021-08-23 23:28:56 +0200 Commit A (Kai Yuan)
29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Как видно из слога git , в этой ветке у нас есть несколько коммитов.

Теперь предположим, что мы хотели бы удалить все коммиты и выполнить ребазинг на коммит 29976c5 с сообщением: BugFix #1 .

Таким образом, нам не нужно считать, сколько коммитов нам нужно раздавить. Вместо этого мы можем просто выполнить команду git rebase -i 29976c5.

Мы узнали, что нам нужно изменить команды выбора на сквош в редакторе, и Git выполнит сжатие, как мы и ожидали:

$ git rebase -i 29976c5
[detached HEAD aabf37e] Commit A
Date: Mon Aug 23 23:28:56 2021 +0200
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.

$ git slog
* aabf37e 2021-08-23 23:28:56 +0200 Commit A (HEAD -> master) (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

6. Сквош путем слияния с параметром –squash

Мы увидели, как использовать интерактивную перебазировку Git для сглаживания коммитов. Это может эффективно очистить граф коммитов в ветке.

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

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

В этом случае мы можем использовать команду commit git merge –squash для достижения этой цели.

Давайте разберемся на примере:

$ git slog
* 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (HEAD -> feature) (Kai Yuan)
* cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
* 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
* e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
| * 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (master) (Kai Yuan)
| * 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
|/
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Как видно из приведенного выше вывода, в этом репозитории Git мы реализовали «Feature2» в ветке функций .

В нашей функциональной ветке мы сделали четыре коммита.

Теперь мы хотим объединить результат обратно в основную ветку с помощью одной единственной фиксации, чтобы сохранить основную ветку в чистоте:

$ git checkout master
Switched to branch 'master'

$ git merge --squash feature
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested

В отличие от обычного слияния, когда мы выполняем команду git merge с параметром –squash , Git не будет автоматически создавать фиксацию слияния.

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

$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: readme.md

В этом примере все изменения «Feature2» относятся к файлу readme.md .

Нам нужно зафиксировать изменения, чтобы завершить слияние:

$ git commit -am'Squashed and merged the Feature2 branch'
[master 565b254] Squashed and merged the Feature2 branch
1 file changed, 4 insertions(+)

Теперь давайте проверим граф ветвления:

$ git slog
* 565b254 2021-08-24 15:53:05 +0200 Squashed and merged the Feature2 branch (HEAD -> master) (Kai Yuan)
* 204b03f 2021-08-24 15:30:29 +0200 Urgent HotFix2 (Kai Yuan)
* 8a58dd4 2021-08-24 15:30:15 +0200 Urgent HotFix1 (Kai Yuan)
| * 0ff435a 2021-08-24 15:28:07 +0200 finally, it works. phew! (feature) (Kai Yuan)
| * cb5fc72 2021-08-24 15:27:47 +0200 fix a typo (Kai Yuan)
| * 251f01c 2021-08-24 15:27:38 +0200 fix a bug (Kai Yuan)
| * e8e53d7 2021-08-24 15:27:13 +0200 implement Feature2 (Kai Yuan)
|/
* 172d2ed 2021-08-23 23:28:56 +0200 BugFix #2 (Kai Yuan)
* 29976c5 2021-08-23 23:28:33 +0200 BugFix #1 (Kai Yuan)
* 34fbfeb 2021-08-23 23:28:19 +0200 Feature1 implemented (Kai Yuan)
* cbd350d 2021-08-23 23:26:19 +0200 Init commit (Kai Yuan)

Мы видим, что мы объединили все изменения в ветке feature с веткой master , и у нас есть один единственный коммит, 565b254 , в ветке master .

С другой стороны, в ветке feature у нас по-прежнему четыре коммита.

7. Заключение

В этой статье мы говорили о том, что такое сквоширование Git и когда нам следует подумать об его использовании.

Мы также узнали, как сквошить коммиты в Git.