1. 概要

Git ワークフローについて話すとき、「スカッシュ」という言葉をよく耳にします。

このチュートリアルでは、Gitスカッシングとは何かを簡単に紹介します。 次に、コミットを潰す必要がある場合について説明します。 最後に、コミットを潰す方法を詳しく見ていきます。

2. Git Squashingとは何ですか?

Gitで「スカッシュ」と言うときは、複数の連続したコミットを1つに結合することを意味します。

例を見てみましょう:


          ┌───┐      ┌───┐     ┌───┐      ┌───┐
    ...   │ 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. コミットを潰すのはいつですか?

簡単に言えば、ブランチグラフをクリーンに保つためにスカッシングを使用します。

新しい機能を実装する方法を想像してみましょう。 通常、いくつかの修正やテストなど、満足のいく結果が得られるまでに複数回コミットします。

ただし、この機能を実装すると、これらの中間コミットは冗長に見えます。 したがって、コミットを1つにまとめたい場合があります。

コミットを潰したいもう1つの一般的なシナリオは、ブランチをマージすることです。

多くの場合、新しい機能の作業を開始すると、機能ブランチが開始されます。 機能ブランチで20のコミットを使用して作業を行ったとしましょう。

したがって、機能ブランチをマスターブランチにマージするときは、20個のコミットを1つに結合するためにスカッシュを実行する必要があります。 このようにして、マスターブランチをクリーンに保ちます。

4. コミットを潰す方法は?

現在、IntelliJEclipseなどの一部の最新IDEには、一般的なGit操作のサポートが統合されています。 これにより、GUIからコミットを潰すことができます。

たとえば、IntelliJでは、スカッシュするコミットを選択し、右クリックのコンテキストメニューで[スカッシュコミット]を選択できます。

ただし、このチュートリアルでは、Gitコマンドを使用した押しつぶしに焦点を当てます。

一般的なGit操作であっても、squashはGitコマンドではないことに注意してください。つまり、「gitsquash…」は無効なGitコマンドです。

コミットを潰すための2つの異なるアプローチについて説明します。

  • インタラクティブリベース: gitrebase-i…
  • –squash オプションでマージ: git merge –squash

次に、それらの動作を見てみましょう。

5. インタラクティブリベースによる押しつぶし

始める前に、Git alias 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のインタラクティブなリベースでは、関連するすべてのコミットがデフォルトのエディターに一覧表示されます。 この場合、それらは私たちが押しつぶしたいコミットです。

次に、各コミットおよびコミットメッセージを必要に応じて制御し、変更をエディターに保存できます。

次に、最後の4つのコミットを押しつぶしましょう。

「最後のXコミット」と言うときは、HEADからの最後のXコミットについて話していることに注意してください。

したがって、この場合、これらは最後の4つのコミットです。

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

さらに、すでにプッシュされたコミットを押しつぶしていて、押しつぶされた結果を公開したい場合は、強制的にプッシュする必要があります。

パブリックリポジトリへの強制プッシュは、他の人のコミットを上書きする可能性があるため、危険な操作になる可能性があることに注意してください。

さらに、本当に強制的にプッシュしたい場合は、必要なブランチのみを強制的にプッシュするようにしてください。

たとえば、push.defaultプロパティをcurrent に設定して、現在のブランチのみがリモートリポジトリにプッシュ/強制プッシュされるようにすることができます

または、 refspecの前に「+」を追加してプッシュすることで、1つのブランチのみにプッシュを強制することもできます。たとえば、 git push origin + feature は、プッシュを強制します。 機能ブランチ。

5.1. 最後のXコミットを押しつぶす

インタラクティブリベースを使用して最後のXコミットを押しつぶす構文は次のとおりです。

git rebase -i HEAD~[X]

だから、これは私たちが実行する必要があるものです:

git rebase -i HEAD~4

コマンドを実行した後、Gitは、押しつぶしたいコミットとインタラクティブなリベースヘルプ情報を使用して、システムのデフォルトエディター(この例ではVimエディター)を起動します。

上のスクリーンショットでわかるように、押しつぶしたい4つのコミットはすべて、pickコマンドを使用してエディターに一覧表示されます。

次のコメント行には、各コミットおよびコミットメッセージを制御する方法に関する詳細なガイドラインがあります。

たとえば、コミットのpickコマンドをsまたはsquashに変更して、それらを押しつぶすことができます。

変更を保存してエディターを終了すると、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 の出力が示すように、最後の4つのコミットを1つの新しいコミット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が比較的大きい場合

コマンドgitrebase -i HEAD〜X は、最後のXコミットを潰すのが非常に簡単であることを学びました。

ただし、ブランチに非常に多くのコミットがある場合、Xの数値を大きくするのは面倒な場合があります。 さらに、エラーが発生しやすくなります。

X を数えるのが簡単でない場合は、「onto」にリベースするコミットハッシュを見つけて、コマンド git rebase -ihash_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 slog が示すように、このブランチにはいくつかのコミットがあります。

ここで、すべてのコミットを破棄し、コミット 29976c5BugFix#1というメッセージを付けてリベースしたいとします。

したがって、押しつぶす必要のあるコミットの数を数える必要はありません。 代わりに、コマンド git rebase-i29976c5。を実行するだけです。

エディターでpickコマンドをsquashに変更する必要があることを学びました。そうすれば、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インタラクティブリベースを使用してコミットを潰す方法を見てきました。 これにより、ブランチ内のコミットグラフを効果的にクリーンアップできます。

ただし、機能ブランチで作業しているときに、多くのコミットを行うことがあります。 機能を開発した後、通常、機能ブランチをメインブランチ(「マスター」など)にマージします。

マスターブランチグラフをクリーンに保ちたい(たとえば、1つの機能、1つのコミット)。ただし、機能ブランチにコミットがいくつあるかは関係ありません。

この場合、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リポジトリでは、featureブランチに「Feature2」を実装しました。

feature ブランチでは、4つのコミットを行いました。

次に、結果を master ブランチにマージし、 master ブランチをクリーンに保つために、1つのコミットを行います。

$ 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に1つのコミットがあることがわかります。 X165X]masterブランチ。

一方、 feature ブランチでは、まだ4つのコミットがあります。

7. 結論

この記事では、Gitの押しつぶしとは何か、いつ使用を検討すべきかについて説明しました。

また、Gitでコミットを潰す方法も学びました。