RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

「tig」をつかってcommitをきれいに分割する

f:id:FM_Harmony:20180404022333p:plain

はじめに

2年目のエンジニアになりました、FM_Harmonyです。 Rakus Developers Blogでは4回目の投稿です。

↓前回の記事はこちら tech-blog.rakus.co.jp

さて、弊社ではビアバッシュというイベントを行っています。(ビアバッシュ・・・?という方はコチラ
今回はその際に私が発表したことについて、補足も踏まえつつまとめたいと思います。 テーマはtigでcommitをきれいに!です。

tigとは?

Tig is an ncurses-based text-mode interface for git.
--- https://github.com/jonas/tig より

tigとはターミナル上で動作するgitリポジトリのブラウザ・・・と言えます。 logの閲覧や、commit操作などを簡単に行うことができる便利なツールです。 動作も高速なので、慣れてしまえばストレスなくcommit操作を行うことができます。

準備

今回はwindowsで実演を行いますが、macでのインストール方法も記載しておきます。

tigのインストール(windowsの場合)

Git for Windows の v2.14.2から、tigが付属するようになりました。 なので、windowsではtigを特別にインストールする必要はありません。

tigのインストール(macOSの場合)

Homebrewを使うことで簡単にインストールすることができます*1。 tigをmacOSにインストールするためには、ターミナルから以下のコマンドを実行します。

$ brew install tig

commitをきれいにする理由

実演の前に、commitを分割する理由を説明します。 開発を行っていると、どうしても commit が大きくなったり、適切に commit が分けられていないという状況に出くわすと思います。
このcommitをそのままにすると何が良くないのかというと、

  • レビュワーがpushされたcommit一つ一つについて規模を把握しづらい。
  • 後から変更に問題があることが発覚した際に、問題が発生した commitを追いづらくなる.

といった事が挙げられます。

なので、上記の問題が発生しないようにcommitを適切な大きさに分割するとよいです。 その際に、tigを利用すると高速かつ簡単にcommitの分割を行うことができます。

使ってみる

実演の前準備

実際にtigを使って、commitの分割を行ってみましょう。

現在、sampleReposというリポジトリが以下のような構成になっているとします。

SampleRepos/
│
└ NewFile.java

NewFile.java の内容は以下の通りです。

public class NewFile {
    public static void main(String args[]){
        System.out.println("Hello World!");
    }
}

この状態で一度 commit したとします。

$ git add NewFile.java
$ git commit -m "first commit"

機能を追加して、まとめてcommitする

さて、この状態からNewFile.javamethodAmethodBというメソッドを追加することになりました。

public class NewFile {
    public static void main(String args[]){
        System.out.println("Hello World!");
        methodA();
        methodB();
    }

    public static void methodA(){
        System.out.println("methodA!");
    }

    public static void methodB(){
        System.out.println("methodB!");
    }
}

とりあえず今回の変更をまとめてcommitします。

$ git add NewFile.java  
$ git commit -m "add methodA and methodB to NewFile"

git rebase -i で、commitの分割を始める

では、早速commitを分割してみましょう。 今回は、先程のadd methodA and methodB to NewFile というcommitを

  • add methodA to NewFile
  • add methodB to NewFIle

という二つのcommitにします。

まず、以下のgitコマンドを実行します。

$ git rebase -i HEAD~1 // HEAD~の後の数字は適宜変更する.

すると、以下のような文章がテキストエディタで表示されるはずです*2

pick 1599a7b add methodA and methodB to NewFile

# Rebase 9276511..1599a7b onto 9276511 (1 command)
// 以下省略.

次に、add methodA and methodB to NewFileのcommitを分割したいので、その横にある pick を edit へ変更してエディタを保存して閉じます。
その後、以下のコマンドを実行します。

$ git reset HEAD~1

こうして問題のcommitによる変更がunstagedな状態になりました。 ( = 変更がaddされる前の状態)

tigを使ってcommitを分割する - その1

さて、この状態で以下のコマンドを実行します。

$ tig

すると、次のような画面が表示されるはずです。

yyyy-mm-dd Unknown o Unstaged changes
yyyy-mm-dd Committer I [HEAD] first commit

これがtigのMain Viewと呼ばれる画面です。 この状態で、Shift + Sキーを押すと、次のような画面へ遷移します。

Interactive rebase master
Changes to be committed:
  (no files)
Changes not staged for commit:
M NewFile.java
Untracked files:
  (no files)

これがtigのStatus Viewと呼ばれる画面です*3
この状態で、Changes not staged for commit: 以下の M NewFile.javaにカーソルを合わせてEnterキーを押すと、次のような画面が画面下(もしくは画面右)に分割されて表示されます。

 NewFile.java | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/NewFile.java b/NewFile.java
index 9fde6b2..01487f8 100644
--- a/NewFile.java
+++ b/NewFile.java
@@ -1,5 +1,15 @@
 public class NewFile {
        public static void main(String args[]){
                System.out.println("Hello World!");
+               methodA();
+               methodB();
+       }
+
+       public static void methodA(){
+               System.out.println("methodA!");
+       }
+
+       public static void methodB(){
+               System.out.println("methodB!");
        }
 }

これが、Diff Viewと呼ばれる画面です。
長くなりましたが、この画面上でstaged / unstagedする部分を指定できます。

tigを使ってcommitを分割する - その2

それでは、mainメソッド内の

               methodA();

と、methodAメソッドの定義である

       public static void methodA(){
               System.out.println("methodA!");
       }

という変更が含まれたcommitを作成しましょう。
今回は1行ずつstagedにしていきます。 Diff Viewで、commitに含める変更箇所にカーソルを合わせて1キーを押すことで1行づつ staged にすることができます。 ただし、変更内容が表示された画面でのカーソル移動には j, k キーを使うことに注意します。

完了したら、Status Viewが

Interactive rebase master
Changes to be committed:
M NewFile.java
Changes not staged for commit:
M NewFile.java
Untracked files:
  (no files)

となっているはずです。 つまり、同じファイルに対する変更をcommitに含むものと含まないものに分けることができたということです。
念のため、Changes to be committedの下にあるNewFile.javaのDiff Viewが以下のようになっていることを確認してください。

 NewFile.java | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/NewFile.java b/NewFile.java
index 9fde6b2..becff04 100644
--- a/NewFile.java
+++ b/NewFile.java
@@ -1,5 +1,10 @@
 public class NewFile {
        public static void main(String args[]){
                System.out.println("Hello World!");
+               methodA();
+       }
+
+       public static void methodA(){
+               System.out.println("methodA!");
        }
 }

qキーを押してDiff Viewを閉じてからStatus View上で Shift + C キーを押すと、git commitコマンド入力時と同じ画面が現れます。 そこで、 commit messageを add methodA to NewFile.java. としてcommitを行います。
こうして、methodAの追加に関する変更のみを含んだcommitが完成しました。

tigを使ってcommitを分割する - その3

その後、画面にはEnterキーを押すようにメッセージが出ているはずなので、それに従います。

すると、Main Viewに画面が戻るはずです。 ここで先程のadd methodA to NewFile.というcommitが表示されていない場合は、Shift + RキーでMain Viewを更新します。

では、同じようにmethodBの追加に関する変更のみを含んだcommitを作成しましょう。Status Viewから、unstagedなNewFile.javaに関するDiff Viewを開きます。

 NewFile.java | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/NewFile.java b/NewFile.java
index becff04..01487f8 100644
--- a/NewFile.java
+++ b/NewFile.java
@@ -2,9 +2,14 @@ public class NewFile {
        public static void main(String args[]){
                System.out.println("Hello World!");
                methodA();
+               methodB();
        }

        public static void methodA(){
                System.out.println("methodA!");
        }
+
+       public static void methodB(){
+               System.out.println("methodB!");
+       }
 }

また1行ずつstagedにしても良いのですが、今度は全ての変更をcommitに含みます。 なので、Uキー を押してファイルの変更全てを一括でstagedにします。
Status Viewを確認すると、以下のようになるはずです。

Changes to be committed:
M NewFile.java
Changes not staged for commit:
  (no files)
Untracked files:
  (no files)

そして、Diff Viewを閉じた状態でShift + Cキーを押してgit commitを行います。 commit messageはadd methodB to NewFile.java.とします。

commitが完了してMain Viewへ戻った後、qキー を押してtigを終了します。
その後、以下のコマンドを実行して、add methodA and methodB to NewFileというcommitへの編集を終了します。

$ git rebase --continue

その後、再びtigコマンドを実行すると、確かにcommitが分割されたことが分かります。

yyyy-mm-dd hh:ss Committer o [master] add methodB to NewFile.java
yyyy-mm-dd hh:ss Committer o add MethodA to NewFile.
yyyy-mm-dd hh:ss Committer I first commit

まとめ

いかがでしたでしょうか。 ちょっと伝わりづらい部分もあったと思いますが、実際にtigを使ってみるとその便利さが分かると思います。

commitを見やすく分割するのは、レビュアーの負担を減らすだけでなく、将来の自分が変更内容を理解するのに役立ちます。 なので、この記事をご覧いただいた方が、tig を使うことで少しでも楽に commit をきれいにできれば幸いです。

最後に、以下の言葉を引用してこの記事のまとめとします。

コードは他の人が最短時間で理解できるように書かなければならない
--- リーダブルコード - より良いコードを書くためのシンプルで実践的なテクニック P.3 より

(本来はコードの可読性に関する言葉ですが、commitをきれいにするということは上記の考え方と同じものが根底にあると感じました)

参考資料


*1:ソースコードをビルドする方法もあります。 詳しくはコチラ

*2:当然、commit id等は人によって異なります

*3:この状態でも、ファイルごとにstaged / unstagedを切り替えることは可能です。

Copyright © RAKUS Co., Ltd. All rights reserved.