週刊 git GUIクライアントを作る [6] interactive rebase


# 前号までのまとめ

## [ \[1\] stage/unstage基礎知識編](https://qiita.com/guignol/items/f19d05b8db5390b9b647)

行単位のstage/unstageのためにやるべきこと5つ

`1. 元になるpatchとしてdiffを出力する`
`2. stage/unstageしたい行を選ぶ`
`3. hunkのボディを編集する`
`4. hunkのヘッダーを再計算する`
`5. 最終的なpatchをapplyする`

##  [ \[2\] stage/unstage不完全攻略編](https://qiita.com/guignol/items/3463d8f569c390be6801)

JGitを使ってみたら無理だったので、git.exeを使おうというお話でした。

## [ \[3\] stage/unstageパッチ編集編](https://qiita.com/guignol/items/c8d0cea9f6833942eaf2)

`3. hunkのボディを編集する`
`4. hunkのヘッダーを再計算する`

## [ \[4\] diff et al.](https://qiita.com/guignol/items/b59dce0137c4922553eb)

`1. 元になるpatchとしてdiffを出力する`

## [ \[5\] 初めてのデスクトップアプリ](https://qiita.com/guignol/items/1f40fbae971606cbbcb8)

`2. stage/unstageしたい行を選ぶ`ための「表示」まで

# 今号は

## `git rebase -i`

続きをやるなら`2. stage/unstageしたい行を選ぶ`の「選択」のはずですが、
何を書こうとしたか覚えてないし、
Swingでの苦労話を書いても（未来の自分含め）誰の役にも立たなそうなので、
interactive rebaseつまり`git rebase -i`をやります。

## コミットメッセージの編集とコミットの並び替え

interactive rebaseはいろいろできますが、今回は以下の2つのみを対象とします。

+ コミットメッセージの編集
+ コミットの並び替え

たぶん他の機能もほぼ同じはずですが、まだ試していません。

## そもそもなんで作るの？

gitおよびSourcetreeのUIが気に入らなかったり文字化けしたりするからですね。
Javaで作ったら文字化けしなかったのでJava最高！ってなりました。

# コマンドラインで基本を確認

## 図 for CLI

CLIで行う`git rebase -i`は以下の図のようになっています。

![git_rebase_i_cli.png](https://qiita-image-store.s3.amazonaws.com/0/202327/3a35d871-09a0-4cf1-746e-c70052648ea7.png)

`sequence.editor`および`core.editor`として設定されているエディタ[^1]に、テキストファイルのパスが渡されるので、そのファイルを編集してgitに制御を返します。

[^1]: 設定されていない場合はデフォルトのエディタが起動します。

## `sequence.editor`とは

> Text editor used by git rebase -i for editing the rebase instruction file. The value is meant to be interpreted by the shell when it is used. It can be overridden by the GIT_SEQUENCE_EDITOR environment variable. When not configured the default commit message editor is used instead.
https://git-scm.com/docs/git-config#git-config-sequenceeditor

`git rebase -i`でどのような作業を行うかを指示するファイルのためのエディタです。一度の`git rebase -i`で`sequence.editor`が呼ばれるのは一度だけのはずです。

このファイルは`git rebase -i`コマンドで最初に編集することになるファイルで、`.git/rebase-merge/git-rebase-todo`というパス[^2]なので、以下では「TODOファイル」と呼びます。

[^2]: このファイル名が仕様として決められているのか、現在の実装がそうなっているだけなのか、環境によるのかは不明です。

例えば`git rebase -i HEAD~3`とかコマンドを打つと、
以下のようなTODOファイルの編集となります。

```
pick 2ec15d6 dev
pick ff9c541 copy image
pick 62f4691 dev dev

# Rebase 6d702ce..62f4691 onto 6d702ce (3 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
```

## `core.editor`とは

> Commands such as commit and tag that let you edit messages by launching an editor use the value of this variable when it is set, and the environment variable GIT_EDITOR is not set.
https://git-scm.com/docs/git-config#git-config-coreeditor

コミットメッセージを編集するためのエディタです。

上記のファイルの3行目
`pick 62f4691 dev dev`を
`r 62f4691 dev dev`に変更すると、
コミットメッセージの編集となります。

```
dev dev

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Dec 28 19:43:35 2017 +0900
#
# interactive rebase in progress; onto 6d702ce
# Last commands done (3 commands done):
#    pick ff9c541 copy image
#    r 62f4691 dev dev
# No commands remaining.
# You are currently editing a commit while rebasing branch 'dev2' on '6d702ce'.
#
# Changes to be committed:
#       modified:   新しいテキスト ドキュメント.txt
#

```

# Sourcetreeを参考にする

## 図 for Sourcetree

interactive rebaseに対応したGUIクライアントの代表格（？）であるSourcetree[^3]では、以下の図のようになっています。

[^3]: さっき気づいたんですが、SourceTreeじゃなくてSourcetreeなんですね。

![git_rebase_i_gui.png](https://qiita-image-store.s3.amazonaws.com/0/202327/c7550793-226e-15b8-28bb-4a92adf92d31.png)

エディタとしてstree_gri.exeを指定して、そこからSourcetreeを呼び出し、テキストファイルの編集内容をGUIで決定している、という感じになっているようです。

Sourcetreeとstree_gri.exe間のやりとりについては想像になりますが、stree_gri.exeからSourcetreeにファイルパスを渡して、正常に編集が終わったらstree_gri.exeに0を返す、というようなことをやっていると思います。

Sourcetreeが叩いているコマンドを見てみると、上記の見当および想像がつきます。

## Sourcetreeが叩くgitコマンド

Sourcetreeでコミットを右クリックして「～の子とインタラクティブなリベースを行う...」を押してしばらくすると、操作するためのUIが出ます。そのUIでキャンセルボタンを押すと、エラーの詳細とコマンドが表示されます。

git -c diff.mnemonicprefix=false -c core.quotepath=false -c sequence.editor='C:\Users\<ユーザ名>\AppData\Local\SourceTree\app-2.3.5\tools\stree_gri' -c core.editor='C:\Users\<ユーザ名>\AppData\Local\SourceTree\app-2.3.5\tools\stree_gri' rebase -i --autosquash 115a2e7a4d5a44c8b1cabebbc66a6322c6141578

こんな感じですが、長いので区切ります。

git
-c diff.mnemonicprefix=false
-c core.quotepath=false
-c sequence.editor='C:\Users\<ユーザ名>\AppData\Local\SourceTree\app-2.3.5\tools\stree_gri'
-c core.editor='C:\Users\<ユーザ名>\AppData\Local\SourceTree\app-2.3.5\tools\stree_gri'
 rebase -i --autosquash 115a2e7a4d5a44c8b1cabebbc66a6322c6141578

 `-c diff.mnemonicprefix=false` と
  `-c core.quotepath=false` は
 ここでは本質的でないので、忘れるかググりましょう。
  `--autosquash`はコミットメッセージの先頭に`squash!`や`fixup!`がついてあるコミットを、メッセージを基準に移動させて`squash`や`fixup`の指示を出した状態が、TODOファイルの初期値となるようです[^4]。が、今回の本筋ではありません。
  
  [^4]: 例えば次の記事が参考になります。
  https://qiita.com/kyanro@github/items/818012c1b1827ed48277

大事なのは、以下の2つです。
 `-c sequence.editor='(省略)\stree_gri'`
 `-c core.editor='(省略)\stree_gri'`
sequence.editorおよびcore.editorとして同じものを指定していることが分かります。

各エディタにはファイルパスしか渡されないため、おそらくファイルパスから必要な処理を判断しているのだろうと思われます。

私が確認した限りでは、
sequence.editorには
`(レポジトリトップ)/.git/rebase-merge/git-rebase-todo`
core.editorには
`(レポジトリトップ)/.git/COMMIT_EDITMSG`
というファイルのパスがそれぞれ渡されていました。

## ユーザ操作させるタイミング

エディタ（中継用）と GUIアプリケーションは、だいたい下記のような役割で連携することになると思います。

+ エディタ（中継用）の役割

1. 唯一の引数としてファイルパスを受け取る
2. UIアプリケーションにファイルパスを渡す
3. GUIアプリケーションからステータスを受け取る
4. GUIアプリケーションから受け取ったステータスでプログラムを終了する

+ GUIアプリケーションの役割

1. `git rebase -i`にエディタの実行パスを渡して、呼び出しを待つ
2. エディタから呼ばれたら、ファイルパスおよび内容から判断して（UIを表示してユーザに操作させ、その結果に基づいて）ファイル編集を行う
3. ファイル編集を終えたら、エディタにステータスを渡す
4. 2～3を必要なだけ繰り返す

GUIアプリケーションとしてのSourcetreeは、sequence.editorとしての呼び出しの際の3～4で専用のUIを表示してユーザに操作させ、コミットの並び替えや、メッセージの内容を確定させているように見えます。このUIでの入力を元に、core.editorとしての呼び出し時にはユーザ操作させないままファイル編集を行っているようです。（gitの挙動に合わせてメッセージ編集を後回しにするのではなく）まとめて操作させるのは、UIとして優れていると思います。

また、 `git rebase -i`を呼び出す前にユーザ操作させて、コミットの並び順や編集後のメッセージ内容を確定させておくことも可能です。先に`git rebase -i`を呼ぶことで、外部から他のgit操作を行えないようにロックしたほうが無難ということでしょう。しかし、 `git rebase -i`からエディタの起動はCLIでさえ遅いので、先に編集内容を確定させておくのもアリだと私は思います。

# 罠っぽいもの

## コミットメッセージの編集

コミットメッセージの編集は、対象となっているコミットが分かりづらいです。

上述と同じ例ですが、以下のようなファイルを編集するとき、対象となるコミットのハッシュ値（62f4691）はあまり分かりやすい位置にありません。ファイルの真ん中あたりにあります。`#    r 62f4691 dev dev`の行がそれで、TODOファイルの処理済み一覧の一部として記載されています。

```
dev dev

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Dec 28 19:43:35 2017 +0900
#
# interactive rebase in progress; onto 6d702ce
# Last commands done (3 commands done):
#    pick ff9c541 copy image
#    r 62f4691 dev dev
# No commands remaining.
# You are currently editing a commit while rebasing branch 'dev2' on '6d702ce'.
#
# Changes to be committed:
#       modified:   新しいテキスト ドキュメント.txt
#
```

rebaseとはコミットを改めて積み上げていくものですから、その起点となるコミット（6d702ce）は大事なので、分かりやすく二度も登場します。しかし、コミットメッセージが変わればハッシュ値は変更されるわけですから、今から作成されようとしているコミットと編集前のハッシュ値は無関係で、忘れていいはずのものです。です、が、もうちょっと分かりやすいと嬉しいですね。

ちなみに私は、`#    r 62f4691 dev dev`の直後にある
`# No commands remaining.`を目印にしています。
 この位置には、他に、  
`# Next command to do`
 `# Next commands to do`
 があります。

また、sequence.editorが呼ばれてTODOファイルの編集が終われば、core.editorの呼ばれるコミットとその順序は確定します。その順序に基づいて対象コミットを判断することもできます。

## `#`から始まる行

~~`#`から始まる行はコメントに見えるんですが、
`#`から始まる行をすべて削除すると、rebaseに失敗する場合があります。
が、どういう場合に失敗するかは、再確認できませんでした。~~

# 編集後記

そろそろ月刊を名乗ったほうが良さそう……。
mac版のSourcetreeは良いですね。
Windows版と挙動が違ったりするのが辛いけど。
改めて各GUIクライアントを試してみる機会を作りたいなぁ。