Skip to main content

Gitを簡単に:rebaseで履歴を変更する—パートII

著者
Headshot of Or Kamara

Or Kamara

2020年6月17日

0 分で読めます

「Gitを簡単に使う」シリーズの第2回へようこそ!

前回の記事では、Rebaseの基本と、Gitを使う際によく遭遇する2つのシナリオについて説明しました。パートIはこちらからご覧いただけます。今回は、よくある4つのシナリオと、まとめをご紹介します。

さっそく始めましょう!

よくあるシナリオ(とその対処法!)—パートII

前回の記事の続きとして、コードの履歴を変更したい場面と、Gitを使った変更方法を見ていきましょう。

1. 過去のコミットを2つに分割する(edit)

2つのコミットをまとめた際にミスをしたことに気づき(詳しくはパートIを参照)、17a73c1をe2952e7にsquashした状態を元に戻し、新しいファイルを別のコミットにしたいとします。この場合は、編集したい位置までコミットを適用して停止し、変更を加えてから続行します。つまり、editオプションを使います。

もう一度git rebase -i masterを実行します。今回は、分割したいコミット(2つ目のコミット、`6871683`)にeditオプションを指定します。

pick 682c27f feat: 1st commit
edit 6871683 feat: 2nd commit
pick 1b2f5b1 feat: 3rd commit

rebase画面を閉じると、次のように表示されます。

Stopped at 6871683...  feat: 2nd commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Gitは1つ目と2つ目のコミットを適用して停止しました。ここからは自分で変更を加えられます。

現時点のGitグラフは次のとおりです。

* 6871683 (HEAD) feat: 2nd commit* 682c27f feat: 1st commit
* 24aa86a (master) Init: Git History

先ほど説明したとおり、2つ目のコミットをファイルごとに2つのコミットへ分割します。まずgit reset --soft HEAD^を実行し、2つ目のコミットによる変更をリセットします。resetコマンドは、現在HEADが指しているブランチのコミットを変更します。--softオプションを指定すると、インデックスと作業ディレクトリはそのまま維持され、元のHEADからそのコミットまでに変更されたすべてのファイルがステージされます。

新しいGitのステータスは次のとおりです。両方のファイルがステージされています。

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt
 new file:   b2.txt

次に、git restore --staged b2.txtを実行して、ステージ済みファイルの一覧からb2.txtを外します。

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
 b2.txt

ステージ済みファイルがb.txtだけになったので、2つ目のコミットの前半をコミットします。次にb2.txtをステージして、2つ目のコミットの後半をコミットします。

❯ git commit -m "feat: 2nd commit - part 1"
❯ git add b2.txt
❯ git commit -m "feat: 2nd commit - part 2"

Gitグラフは次のようになります。

* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

最後にrebaseを続行して、残りのコミットを適用します。次を実行してください。

git rebase --continue。最終的なGitグラフは次のようになります。

* 09fe03e (HEAD -> feat/git-history) feat: 3rd commit
* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

2. 特定のコミット内のすべてのファイルを修正する(edit)

次のシナリオには覚えがある方も多いでしょう。コードを複数のコミットにきれいに分けてプッシュした後で、リンターを実行し忘れたことに気づいたとします。先ほど説明したように、fix lintingという名前のコミットを追加するのではなく、すべてのコミットでリンティングを修正するのが適切です。

小さな変更ならsquashオプションを使えますが、複数のコミットにまたがる変更がある場合は、各コミット(または修正したいコミット)に対してリンティングスクリプトを1つずつ実行するのが最善です。そのために、前の例と同様、editオプションを再度使います。

再びgit commit rebase -iを実行し、複数のファイルを含む1つ目のコミットにeditオプションを指定します。

edit 9882b2f feat: 1st commit
pick 93260e5 feat: 2nd commit - part 1
pick 96127c6 feat: 2nd commit - part 2
pick 09fe03e feat: 3rd commit

先ほどと同じく、Gitは1つ目のコミットを適用したところで停止します。

Stopped at 9882b2f...  feat: 1st commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

ここでリンター(例:npm run lint)を実行して、リポジトリの状態を確認します。

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
 modified:   a.txt
 modified:   a1.txt
 modified:   a2.txt
 modified:   a3.txt
 modified:   a4.txt
 modified:   a5.txt

ご覧のとおり、リンタースクリプトによって6つのファイル(a*.txt)の内容が変更されました。あとは、変更をステージしてコミットし、現在の変更を amend してからrebaseを続行するだけです。

❯ git add a*
❯ git commit --amend --no-edit
❯ git rebase --continue
Successfully rebased and updated refs/heads/feat/git-history.

Gitグラフは次のようになります。

* 8f404fe (HEAD -> feat/git-history) feat: 3rd commit
* 7e37b6c feat: 2nd commit - part 2
* f71ad8e feat: 2nd commit - part 1
* b50f18f feat: 1st commit
* 24aa86a (master) Init: Git History

3. すべてのコミットでテストが通ることを確認する(exec)

コードを複数のコミットに分ける場合は、それぞれのコミットが単独で成立することが大切です。つまり、複数のコミットをメインブランチのいずれかにマージした後、それぞれのコミットに戻っても、コードがコンパイルでき、すべてのテストが通る状態であるべきです。

では、コミットをマージする前に、どうやって確認すればよいでしょうか?

すべてのコミットでテストを手動実行しましょう!……もちろん冗談です。自動化すべきですね :) そのために、execオプションを使います。

このオプションを使うと、rebaseの処理中にシェルスクリプトを実行できます。実行したい箇所にexecコマンドの行を追加するだけです。たとえば、もう一度git rebase -i masterを実行してみましょう。

pick b50f18f feat: 1st commit
pick f71ad8e feat: 2nd commit - part 1
pick 7e37b6c feat: 2nd commit - part 2
pick 8f404fe feat: 3rd commit

では、楽しい作業に取りかかりましょう。各コミットの後にスクリプト./run_tests.shを実行して、テストを動かします。

pick b50f18f feat: 1st commit
exec ./run_tests.sh
pick f71ad8e feat: 2nd commit
exec ./run_tests.sh
pick 7e37b6c feat: 2nd commit - part 2
exec ./run_tests.sh
pick 8f404fe feat: 3rd commit
exec ./run_tests.sh

rebase画面を閉じると、次のように出力されます。

❯ git rebase -i master
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
5 tests failed :( 108 tests passed!
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
All tests passed!
Successfully rebased and updated refs/heads/feat/git-history.

ご覧のとおり、Gitは各コミットの適用後にテストスクリプトを実行しました。つまり、feat: 2nd commitにテストが失敗する原因があるとわかります。次のように実行すれば、各コミットの後にexecコマンドを1つずつ追加することもできます。

$ git rebase -i --exec "./run_tests.sh"

最後に(実は最初に)知っておきたいこと—中止する方法

これはシナリオというより、注意点です。rebaseコマンドは危険な場合があります!各コマンドの影響を理解せずに使うと、作業を台無しにする可能性があります。そこで、まず2つのことをおすすめします。

  • コミットを操作する前に、Gitリポジトリのバックアップを必ず作成してください。私が手軽に行っているのは、すべての変更をリモートブランチにプッシュすることです。何か問題が起きても、git resetを使って元のコードに戻せます。

  • 各コマンドの動作を理解しておきましょう。実際のコードで初めて使うのではなく、まず一時的なGitリポジトリで試してください。

rebaseの処理はいつでも中止して、元の状態に戻せることを覚えておきましょう。

  • 最初のrebase画面では、コミットの行をすべて削除するだけで、何も変更されません。

  • rebaseの途中で道に迷い、どうすればよいかわからなくなった場合は、いつでもgit rebase --abortを実行できます。このコマンドはrebaseを中止し、リポジトリを元の状態(git rebase -iの実行前の状態)に戻します。

まとめ

ここまでの例で見てきたように、git rebaseは強力なコマンドです。特にインタラクティブモードで使うと、その力を発揮します。活用できるケースは数え切れないほどありますが、各オプションがどのような場面で役立つのか、基本を理解する一助になれば幸いです。

この記事を通してお伝えしたいのは、git rebaseを賢く、そして臆せず使ってほしいということです。最初は少し怖く感じるでしょうし、Enterキーを押すときに誰かに見守ってほしいと思うかもしれません。でも、それで大丈夫です。練習を重ねれば、次第にいつもの作業になります。また、皆さんの具体的な用途に合わせた、ほかにもたくさんの活用シナリオがあるはずです。

それでは、Gitを楽しんでください!

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。

カテゴリー:

続きを読む

Blog

Stadium Summer:Snyk Connect Fan Zone Tour

SnykのFan Zone Tourでは、8都市と3回のオンラインセッションで、AIセキュリティのワークショップやネットワーキング、楽しい競技を開催しました。参加者はスキルを磨き、アイデアを共有し、共に成長しました。

Blog

Snyk VulnBench JS 1.0:LLMは同じバグを2回見つけられるか?

Snyk VulnBench JS 1.0:300回の反復スキャンで、LLMのセキュリティ検出結果は実行ごとに異なる一方、SASTとモデルはそれぞれ異なる脆弱性の見落としを検出することが明らかに。

feature insights context
Blog

お客様と築くAIセキュリティ:Evoのデザインパートナープログラムから得た5つの教訓

SnykのEvoデザインパートナープログラムから得た5つの重要な教訓をご紹介します。AIの検出、リスクインテリジェンス、ポリシーの自動化によって、チームが生成AIを保護し、拡大するAI利用を大規模に統制する方法をご覧ください。