Gitを使用して最後のXコミットを押しつぶす
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. コミットを潰す方法は?
現在、IntelliJやEclipseなどの一部の最新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 が示すように、このブランチにはいくつかのコミットがあります。
ここで、すべてのコミットを破棄し、コミット 29976c5にBugFix#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でコミットを潰す方法も学びました。