バックエンドエンジニアのKazです。
昨今では、エンジニアにとってほぼ必須ツールとなった、ソースコードのバージョン管理ツール「Git」。今回はGitについて、ちょっと上級ですが、使いこなせばとても便利なコマンドを集めてみました。
なお、記事中のコマンドはすべて最新版のGitを想定しています。一部古いバージョンでは動作しないものも含まれていますので、バージョンの差異で非対応の場合はご容赦ください。
用例
任意指定オプションについて
コマンド例の角カッコ ([]
)で囲まれたオプションは任意指定となります。
git log [-p]
↑この角カッコ内は任意指定
プレースホルダについて
コマンド例の山カッコ(<>
)で囲まれた値はプレースホルダとなります。下記に沿って適宜置き換えてください。
<branch>
: ブランチ名<path>
: ファイルのパス<pattern>
: 検索したい文字列やパターン<tree-ish>
,<commit>
,<upstream>
,<start_point>
: コミットなどを参照するあらゆる識別子(ブランチ名やコミットハッシュ、HEAD
など)
検索
ファイルを高速検索する
git grep [-i] [-P] <pattern> [--and|--or|--not <pattern>...]
git grep
を使うと、リポジトリ内のカレントディレクトリ下にあるファイルを高速かつ高機能に検索できます。
-i
オプションは大文字・小文字の区別を無視し、-P
オプションを使うとPerl互換の正規表現を利用できます。
--and
, --or
, --not
オプションを用いて複数の条件設定もできます。
Git管理外のファイルを高速検索する
git grep --no-index
--no-index
オプションを使うと、Gitリポジトリ外のディレクトリでもgit grep
を使うことができます。
差分
変更のみ確認
git diff --diff-filter=M
ファイルの削除や追加を行った場合、Diffには当該ファイル全体が差分となって表示されます。
これらの操作と併せて通常の変更も行った場合Diff結果が汚染されて見づらくなりますが、 --diff-filter
オプションで変更されたファイルのみDiffを表示するようにすれば結果が見やすくなります。
リネームやコピーを検出
git diff -C
リネームやコピーが行われたファイルを検出し、ファイル全体の差分を表示するかわりにその旨を表示します。リネームによってDiff結果が汚染されて見づらい場合に利用できます。
コミットやブランチの差分を確認
git diff <commit> <commit>
コミットやブランチ間の差分を表示します。
差分のあるファイル一覧を出す
git diff --name-only <commit> <commit>
コミットやブランチ間で差分のあるファイル名を参照します。差分ファイルのリリースや提出などを行う際に活用できます。
Git管理外のファイルを比較
git diff --no-index -- <path> <path>
git grep
と同じく、Git管理外のファイルを処理できる--no-index
オプションが用意されています。リポジトリ外のファイルに対しても高機能なgit grep
を利用することができます。
ステージ操作
変更内容を一部だけステージに追加・リセットする
git add -p
git reset -p
ファイルの変更内容の一部の行のみをステージに追加したり、反対に一部の行のみステージから除外したいときは-p
オプションを付与します。
このオプションを付与すると、対象となる差分ブロック(ハンク)をインタラクティブに選択できるモードが立ち上がるので、指示に従って対象となるハンクを決定していきます。
変更内容を一時保存・退避する
git stash
git stash show
git stash pop
作業内容はコミットしたくないが、ブランチ切り替えなどで一時的に変更内容を保存・退避させておきたい場合はgit stash
を利用します。
スタッシュした変更はshow
で確認、pop
で復帰できます。
特定のファイルを別ブランチやコミットから取得する
git checkout [-p] <tree-ish> -- <path>
このコマンドを使うと、別ブランチや別コミットから特定ファイルやコードを引っ張ってくることができます。
-p
オプションを付与すると、対象となるファイルの一部のみを取得することができます。
変更内容をすべて破棄(破壊的)
git reset --hard
未コミットの変更をすべて破棄します。追跡されているファイルはすべて編集前の状態 (HEAD
)に戻り、削除したファイルも復帰されます。
追跡されていないファイル(新規ファイル)はそのまま残ります。
追跡されていないファイルをすべて削除(破壊的)
git clean -df
このコマンドは追跡されていない新規ファイルと空のディレクトリをすべて削除します。
git reset --hard
と組み合わせることでクリーンなHEAD
の状態に戻すことができます。
.gitignore
で無視指定されたファイルには影響しません。
コミットログ
犯人探し(行ごとにコミッターを表示)
git blame <path>
ソースコードにはときどき悪魔のような実装が残されていることもあります。バグのあるコードを見つけたときはすかさずgit blame
を使いましょう。そのコードを最後に触ったのは誰なのか、すぐに明らかにすることができます。
犯人探しというと荒事のように聞こえますが、実際は不具合が見つかったときに急いで担当の開発者に実装仕様を問い、理由の確認が必要なときもあります。そういった場合、担当者を探り当てるのにも便利となるコマンドがgit blame
です。
ちなみにblameは責め立てる、責任を問う といった意味で、なかなかアグレッシブなコマンド名です。
特定ファイルのコミットの歴史を見る
git log -- <path>
git blame
では「最後にその行を編集した人」を見ることができますが、場合によっては編集履歴を事細かにチェックして何があったのかを追う必要があるときもあります。
その場合はファイルパス付きでgit log
を実行しましょう。当該ファイルに変更が加えられたコミットだけを抽出して歴史を俯瞰することができます。
コミットログに差分の内容を表示する
git log -p [-- <path>]
コミットメッセージと一緒に、各コミットの変更内容の歴史も確認したいときはgit log
に-p
(patch)オプションを加えましょう。すべてのファイルの変更履歴を詳らかにすることができます。
引数に-- <path>
も加えれば、特定ファイルの変更の歴史をすべて追いかけることができます。
コミットログにファイルと変更量を表示する
git log --stat
git log -p
だとファイルの変更内容が膨大すぎて見づらい、差分の概要だけ見たいといった場合は、かわりに--stat
オプションを使うことができます。
このオプションは、変更されたファイルのパスと差分量を表示してくれます。
コミットログツリーを可視化する
git log --graph
数多のマージコミットによって枝分かれと合流を繰り返したコミットログから、枝の流れを読み取りたいときは--graph
オプションを使うことで枝の流れを可視化することができます。
コミット操作
別ブランチから特定のコミットを取り込む
git cherry-pick <commit>
訳あって別ブランチ内の特定のコミットだけ先にリリースしたいというときには、チェリーピック(枝に生えたコミットの 収穫)が使えます。
直前のコミットに変更内容を追加する
git commit --amend --no-edit
コミットに含め忘れた変更があっても慌てることはありません。コミットに含めたい変更をgit add
し、このコマンドを叩くことで直前のコミットを編集することができます。
--no-edit
オプションを省くとコミットメッセージを編集できます。
ベースブランチの付け替え
git rebase <upstream>
マージは枝分かれしたコミットの歴史を合流させる のに対し、リベースは枝の根を付け替えて一つの枝にまとめます。
枝分かれを解消できるため歴史はキレイになりますが、リモートなどにプッシュ済みのコミットまでリベースしてしまうと双方の歴史が衝突してプッシュできなくなることもあります(強制プッシュにより上書きを行うことで対処可能)。
状況に応じてマージとリベースを使い分けましょう。
過去のコミットの編集・統合・削除
git rebase -i <upstream>
リベースコマンドは枝を付け替えるだけでなく、インタラクティブモードを使えば各コミットに対して下記の操作を行えます。
reword
: コミットメッセージの編集edit
: コミット内容の編集squash
,fixup
: 複数のコミットを融合し1つのコミットにするexec
: シェルコマンドの実行drop
: コミットの削除
ブランチ
ブランチの改名
git branch -m [<oldbranch>] <newbranch>
間違った名前でブランチを建ててしまっても焦ることはありません。-m
オプションを使えばブランチを改名することができます。
マージ済み・未マージのブランチ一覧
git branch --merged
git branch --no-merged
マージ済みのブランチを整理したり、逆にまだマージしていないブランチを確認したいときは、これらのオプションを利用できます。
特定のコミットやブランチを起点に新しくブランチを作成してチェックアウト
git checkout -b <new_branch> [<start_point>]
git branch
とgit checkout
を1コマンドで実行できるショートカットです。
git checkout -b new_branch origin/master
などと実行すれば即座に origin/master
をベースとしたクリーンな新規ブランチを作成・チェックアウトし、すぐに作業に移ることができます。
リモートのブランチを削除
git push --delete <branch>
リモートのブランチを消したくなったとき、GitHubやBitBucketのサイトにアクセスしてWeb UIから削除を行うのは少々面倒です。
このコマンドを使えば、ローカルからリモートのブランチを削除することができます。
参照ログ
参照ログの確認
git reflog
Gitはコミットだけでなく、ブランチ移動(チェックアウト)やブランチの削除・改名、マージ、スタッシュなどGitの「参照」に対する操作を記録しています。
これらすべての参照は内部的には実はコミットになっており(HEADが指し示すツリーから外れるため git log
などには表示されない)、このコミットIDや参照が分かれば reset
で戻したり checkout
することで任意のタイミングを復元することが可能です。
reflog
ではまさにこれらの参照の遷移履歴を表示することができ、これを用いれば「rebase
する前の状態」「Detatched HEAD
への復帰」などを行えます。
操作の取り消し
マージを間違えた場合や誤ってコミットを上書きした場合、ブランチを誤って削除してしまった場合、前述のReflogと git reset
を組み合わせて使えば操作前後の情報を参照・復活することができます。
git reset HEAD@{1} # ← ここの参照インデックスはreflogの結果を見て適宜変更する
困ったときの最後の助け舟です。
掃除
リモートで削除されたブランチのローカル参照を削除
git fetch --prune
Gitはgit fetch
やgit pull
の際にリモートのブランチをすべてローカルに取得してきますが、リモートで削除されたブランチの参照は消さない限りローカルに残り続けます(git branch -r
で確認できます)。
--prune
オプションを付与してフェッチを行うと、リモートで削除されたブランチのローカル参照を削除することができます。
この操作はリモートブランチ参照のみ(remotes/origin/foobar
など)を削除し、ローカルのリモートトラッキングブランチ(foobar
)を削除することはありません。
差分の圧縮と不要なオブジェクトの削除
git gc [--prune=all] [--aggressive]
git gc
は「ゴミ掃除」を行うコマンドで、未参照のオブジェクト(ゴーストとなったファイルなど)を削除し、かつファイルの差分をデルタ圧縮することでリポジトリの最適化を行います。
GitではReflogの項目で紹介したように、過去の操作をすべて参照ログとして記録しています。
このためコミットやブランチの削除や上書きによって参照されなくなったオブジェクトも実体だけ残ってしまうことになり、リポジトリの肥大化やパフォーマンスの低下を招いてしまいます。
--prune=all
オプションを付与すると、未参照のオブジェクトすべてを削除できます。デフォルトでは直近2週間以上参照されなかったオブジェクトのみが対象となります。
--aggressive
オプションを付与するとより強力な最適化を図ります。実行にはより多くの時間を要しますが、この結果は恒久的な効果をもたらすので数百回の変更(コミット等)に対し一度の周期で実行すれば十分です。
初期化
コミット数を限定してクローンする
git clone --depth=<n>
長大な歴史を持つリポジトリや、巨大なリポジトリを一部の最新コミットだけ選択的にクローンしたい場合は --depth
オプションを利用します。
このオプションで指定した数のコミット(最新順)のみがフェッチされ、クローンを高速化・軽量化できます。
サブモジュール
サブモジュールの一括更新
git submodule update --recursive --remote
Gitのサブモジュールを使っているのなら、上記のコマンドを使うと一括ですべてのサブモジュールを最新に更新できます。
Gitは便利
いかがでしたか? Gitはさまざまなことができる反面、本当にコマンドが多く、こんなことができたのかと気付かされることも多々あります。
本記事を書くにあたって、改めて思い付くさまざまなコマンドを挙げながら、つくづく便利だなということを実感しました。
Gitは、今も日々進化を続けていて、新しい機能や改善などが出ています。さらに便利になっていくGitが楽しみでなりません。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。