こんにちは、バックエンドコースのShunsukeです。Advent Calendar 9日目ということでGit初心者に向けてGitで開発体験を向上させるためのTipsを、様々なユースケースごとに紹介したいと思います。
対象読者
本記事の対象読者はGitをコマンドラインで使ったことがあり、 add , commit , push , pull などの基本的な操作ができる人を想定しています。
Case 1. 別ブランチで開発をしている間に main ブランチが進んでしまったが、ログをきれいに残したい。
これは普段の開発でもよく出てくるシチュエーションだと思います。新しく切ったブランチ(これを仮に feature-A とします)で作業をしている間に main に変更が取り込まれてしまった場合、それをただ git merge main などとしてはマージによってログが一部ごちゃごちゃになってしまうことがあります。
こんな時は''もし作業ブランチをリモートに push する前であれば''Gitの rebase の機能を利用することができます。これにより実際にどのようなことが起こるのかを下の図に示します。

ここでただ単に merge してしまうと、

git merge main としたときこうなります。しかしここで git rebase main とすると 、

git rebase main としたときこうなります。上と見比べると rebase したときの方がログがきれいに直列に並んでいることが分かると思います。
ただしこの機能の利用には注意点があります。それは上で「''もし作業ブランチを リモートに push する前であれば'' 」と注釈をつけた通り、すでにリモートに push したブランチに対して行ってはいけないということです。ここで rebase したときの図を見るとD, EのコミットがそれぞれD', E' と少し変わっています。これは rebase を行うとコミットのIDとでも呼ぶべきもの(ハッシュ)が変化してしまうことを表しています。そのため、すでにリモートに push したものに対して rebase をしてしまうとリモートとコミットIDが一致せず、衝突が起きてしまいます。よって、既にリモートに push してしまったブランチに関してはおとなしく mergeを行った方がいいでしょう。
今回は簡単のために rebase コマンドを紹介しましたが、実際に main ブランチの変更を取り込むときには pull コマンドに --rebase のオプションがあり同様の動作をするため、こちらを使用するといいでしょう。 ( git pull --rebase origin main など)
またコンフリクトが起こることもありますが、その場合にはコンフリクトを解消した後に git rebase --continue とすれば大丈夫です。もしコンフリクトによって rebase が止まり、その rebase を中止したいときには git rebase --abort で中止することができます。
Case 2. すでにコミットしてしまったが、ソースコードの小さなミスやコミットメッセージをなおしたい。
Gitで作業していると、すでにコミットはしてしまったがソースコードに小さなミスやタイポを見つけてしまったり、コミットメッセージを間違えてしまったりしていることに気づくときがあると思います。ただ、新しくコミットをつくるには小さすぎて無駄なコミットを増やしてしまいます。
このようなときに使えるのは git commit の --amend オプションです。これは直前のコミットを修正することができます。コミットメッセージのみ修正したい場合にはそのまま、ソースコードを変更する場合には通常のコミットを作成するようにソースコードを add でインデックスに追加してから、 git commit --amend を実行します。すると直前のコミットメッセージの編集画面が現れ、コミットメッセージを修正ののち保存してエディタを閉じると、直前のコミットメッセージを修正することができます。コミットメッセージを修正する必要が無い場合にはそのまま保存してエディタを閉じれば大丈夫です。
ただしこの場合にもCase 1の時のように、すでに push したコミットに対してこれを行ってしまうとリモートと競合状態になってしまうため、注意が必要です。
Case 3. 途中まで変更したファイルを残したままブランチを移って作業したい。
これも普段の開発でよく出てくるシチュエーションだと思います。Gitで作業していると、途中でブランチを移って作業したいのだけれども変更があるためにブランチを移動することができない、ただ変更は残しておきたいしコミットするには中途半端すぎるという場面が出てくるでしょう。このような時には stash という機能を利用することができます。この stash はワーキングディレクトリとインデックスの内容を記録し、変更前のクリーンな状態に戻してくれます。
では実際に使用するコマンドの例を説明します。今 feature-A ブランチでコミット前の作業の状態があり、 feature-B ブランチに移って作業したいとします。こんな時には、
$ git status
On branch feature-A
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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git stash
Saved working directory and index state WIP on feature-A: 3cfe4a2 initial commit
# ワーキングディレクトリとindexがクリーンな状態に戻る
$ git checkout feature-B
# 何かしらの作業
$ git checkout feature-A
$ git stash pop
On branch feature-A
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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (31ea34970820eb8f9c4ace4c896c6cba0400c571)
# 元の状態に戻るとすると別ブランチで作業後、元の状態を復旧することができます。ちなみに git stash list とすることで stash したものの一覧を見ることができます。また stash したものを分かりやすくするためには git stash save -m "<message>" としてメッセージを付与するといいでしょう。
Case 4. 別ブランチからあるコミットの変更だけ取り込みたい。
これは、例えばDjangoで開発をしていてオートマイグレーションの機能を使用しているときなどに、別ブランチでモデルの変更とマイグレーションファイルのみ取り込みたいという場合などが考えられます。このような時にはGitの cherry-pick という機能を利用することができます。使い方は至って簡単で、
-
git status --onelineなどとして取り込みたいコミットのハッシュ値を得る。 -
git cherry-pick <ハッシュ値>として変更を取り込む。
以上です。
ちなみに cherry-pick というのは「いいとこ取り」を意味するようです。
まとめ
今回はGitの便利だけどあまり使われていなさそうな機能である rebase , --amend, stash , cherry-pick についてユースケースに基づいて簡単に紹介しました。ただ利用には様々な注意点もあったりするので、詳しくはリファレンスを参照してください。またGitにはこの他にも便利な機能がたくさんあるので、興味を持った方は下の参考リンクを参照してみてください。
Gitを使いこなして快適な開発体験を実現しましょう。
参考リンク
- Git SCM Book : https://git-scm.com/book/ja/v2
- Git SCM Reference : https://git-scm.com/docs