Git 業務で役に立ったコマンド達

業務でGitを使うようになって完全に理解した*1ので、役に立ったコマンド達をここに記しておく。

Git編

その前に

Gitコマンドは割と雰囲気で使っていたが、特に$ git checkout --help$ git reset --helpを見たら思ってたのと違う概念だったので先におさらいしておく。

checkout

$ git checkout branchで特定のブランチに移動する、あるいは特定のブランチの状態をワーキングツリーに再現する。

前者の概念はみんな知っているけど、後者の概念は最初触った時にはあまり意識できていなかった。

特定のブランチの状態をワーキングツリーに再現するので、現在のブランチで何らかの変更をしてしまっても$ git checkout branchnameあるいは$ git checkout .でそのブランチの最新の状態(つまり、変更を行う前)に戻すことができる。

reset

$ git reset は、何かをリセットするもの...ではない。 $ git resetの定義は、「現在のHEADの指している位置を変更する」。*2

一番よく使うのは$ git reset --hard HEAD^だろうか。意味は「HEADが指す位置をHEAD^、つまり一つ前のコミットに変更する」となる。

HEAD, HEAD^

  • HEAD: 現在チェックアウトしているブランチの一番最新のコミット
  • HEAD^: 現在チェックアウトしているブランチの一番最新のコミットから、一つ前のコミット

ここから、役に立ったコマンド達

PRを作るまでもないけど、作業ブランチとmasterとの差分を知りたい

実装前の設計のために、素振りでコードを変更している時によく使う。

$ git diff branch_name master

最後のコミットとの差分を知りたい

出社して朝一発目はこれを打つことが多い。

$ git diff HEAD^

特定のファイルだけ、特定のコミットの状態にしたい

$ git checkout commit_hash filepath

例えば、Gemfileだけmasterの状態にしたかったら$git checkout mastser Gemfileとか。

マージをなかった事にする

$ git reset --hard ORIG_HEAD

masterブランチにいると思い込んで、作業ブランチで$ git merge master をしてしまった時にお世話になった。

作業ブランチにmasterの変更を取り込むこと自体はそんなに致命的ではないが、あるPRからさらにブランチを派生させて作業していたため、$ git diff topic_branch1とのdiffが汚染されてしまったために使った。

$ git checkout topic_branch1 && git merge master && git pushとかでも良いかも。

ちなみにORIG_HEADは以下のような概念。

labs.timedia.co.jp

実は git merge のような「危険」な(= 現在のブランチの内容を大幅に変える可能性のある)コマンドの場合、 実行前の状態を ORIG_HEAD という名前で参照できるようになっています。 つまり、わざわざコミットログを確認しなくても以下のコマンドで マージ前の状態に巻き戻すことができます:

あるブランチでの変更の一部を、別のブランチに取り込む

デザイナーさんの実装待ちの時に、先にロジックだけ自分のブランチで実装してた時に使った。 みんな大好きcherry-pickの出番です。

$ git cherry-pick commit_id

やる事自体は普通のマージコマンドと同じ。 当然コンフリクトも有り得る。

内容としては、そのコミットで修正されているファイルがあったら、そのファイルの状態をマージしようとする。

コンフリクトしたら解決するかcherry-pickを中止するかを選べる。

解決するなら、そのファイルを修正して$ git add .した後$ git cherry-pick --continueでマージを実行。 中止するなら、$ git cherry-pick --abortcherry-pickを発行する前の状態に戻せる。

マージする時のコミットメッセージはデフォルトでcherry-pickでマージしたコミットのコミットメッセージを引っ張ってくる。 cherry-picked from hogehoge-commitとかそういう気の利いたコミットメッセージにはならないので注意。

マージした後コメントだけを直す場合は普通に$ git commit --amendで直せばいい。

リモートにpushしてしまったけど、コミットを直したい

ローカルで変更を加えて$ git commit --amend && git push -f-fをつけないとpushが弾かれる。特に複数人で触っているリポジトリのmasterブランチにはやってはいけない。*3

masterではないブランチから、さらにブランチを切って作業する時に、masterの変更を取り込みたい

派生元にしたいブランチ、例えばhoge-buranchがあったとして、それをチェックアウトしてから$ git checkout -b fuga-branchと発行して、ブランチからブランチを派生させた時の話。

よくあるのが、大元のまとめブランチをPRのベースとして作っておいて、そこから各機能をさらにブランチを切って作業をするパターン。

少しややこしいので以下に状態をまとめる

master <- hoge-branch <- fuga-branch

fuga-buranch上で$ git merge masterをしたが、hoge-branchは手をつけていない場合、PRのdiffにマージした内容が表示されてしまう。 この場合は、一度hoge-branchでmasterをマージしてから、fuga-branchからhoge-branchをマージする必要がある。 コマンド的にはこう。

$ git checkout master
$ git pull origin master
$ git checkout hoge-branch
$ git merge master
$ git checkout fuga-branch
$ git merge hoge-branch

masterに作業ブランチをマージしてしまった

これはfuga-branchhoge-branchにマージしようとして、間違えてmasterブランチ上で$ git merge fuga-branchと打ってしまった時。

マージも一つのコミットなので、resetで戻すことができる。

$ git reset --hard HEAD^

--hardはインデックス(ステージ)とワーキングツリーをリセットする。

おまけ:tig編

gitコマンドでできることを、CLI上でグラフィカルに表示してくれるツール。 地味にvimキーバインドが違う(Gとかdとか)ので割と脳がバグるが、gitで微妙に手が届かないところを強力にサポートしてくれる。

簡単な使い方

MacOSなら、$ brew install tigでインストールできる。$ tigで起動。コミット一覧が簡単に見られる。

gyazo.com
https://github.com/rails/railsに対して$ tigを実行した図

操作は以下のような感じで。*4

  • メインビュー(↑のコミット一覧画面)でEnterを押すとコミットの詳細が画面分割で表示される。
  • jkで上下移動、<C-u>,<C-d>でページ移動。普通のdは画面分割なしでコミット詳細が表示できる。
  • diff中に<C-n>,<C-p>でコミットの親、子を移動できる。いちいちコミットを選びなおす必要がないので楽。
  • tを押すと、変更されたファイルがツリーで表示される。
  • メインビューでsを押すと現在のワーキングツリーのgit status 表示。変更ファイル一覧が出るので、Enterでdiffを見れる。
  • どのビューでも、mでメインビューに戻ることができる。
  • メインビューででhを押すと、ブランチ一覧表示、ブランチ選択中にCでそのブランチをチェックアウトすることができる。
  • qで前の画面に戻る。
  • $ tig --allでリモート含めた全ブランチが見える。
  • $ tig [filename, dirname]でファイルorディレクトリが関わる履歴のみが見える
  • $ tig [branchname1, branchname2]で対象ブランチに関わる履歴のみが見える

tigでBlameがしたい

tで変更ファイルの一覧ツリーを表示してる画面で、ファイルを選択した状態でbを押すと、現在選択している行のファイルに対してのBlameが見られる。 この機能の素晴らしい点として、,を押すと、Blameを再帰的に遡ることができる。

gyazo.com
$ tig blame activerecord/lib/rails/generators/active_record.rbを実行した図

例えば、あるメソッドをblameで見ると、定義自体はだいぶ古いけど、中身の処理は頻繁に変更されている場合に「どういう経緯があって、今この状態にあるんだろう?」という歴史を見たいケースがある。

そういう時にtigでBlameビューを表示し、,を押すと、そのファイルに対して再帰的にBlameを実行することができ、最新の変更をする前にはどのような処理がなされていたのかを知ることができる。

本家のgitコマンドで実現しようとしたら、$ git log filenameでそのファイルのコミット一覧を見て、1個ずつblameしていく感じになると思う。*5

tigでGrepがしたい

$ git grepの代わりに、$tig grep some_method_namegrepできる。grep画面でEnterを押すとそのファイルに、bを押すとそのファイルに対してBlameができる。 メインビューでgを押してもgrepができる。

gyazo.com
$ tig grep eager_loadを実行した図

おわりに

色々調べたけど書ききれないぐらい色んなことができる。$ git config --help$ man tigで調べてGitライフを捗らせていきたい。

*1:全然わからない 俺たちは雰囲気でGitを触っている

*2:何でresetって名前がついているんだろう...

*3:masterへのpush -fはGitHub上の設定で制限できるかも

*4:割と自分向けのメモ

*5:もっと楽な方法があったらすみません