CREATIVE X 第2弾
CREATIVE X 第2弾
2018.04.06
#34
バックエンドへの道

Gitをもっと使いこなそう! ちょっと上級な便利コマンド32選

Kaz

バックエンドエンジニアのKazです。

昨今では、エンジニアにとってほぼ必須ツールとなった、ソースコードのバージョン管理ツール「Git」。今回はGitについて、ちょっと上級ですが、使いこなせばとても便利なコマンドを集めてみました。

なお、記事中のコマンドはすべて最新版のGitを想定しています。一部古いバージョンでは動作しないものも含まれていますので、バージョンの差異で非対応の場合はご容赦ください。

目次

  1. 用例
  2. 検索
  3. 差分
  4. ステージ操作
  5. コミットログ
  6. コミット操作
  7. ブランチ
  8. 参照ログ
  9. 掃除
  10. 初期化
  11. サブモジュール
  12. 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 branchgit 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 fetchgit 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が楽しみでなりません。