LoginSignup
3
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-04

前号までのまとめ

[1] stage/unstage基礎知識編

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

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

[2] stage/unstage不完全攻略編

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

[3] stage/unstageパッチ編集編

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

[4] diff et al.

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

[5] 初めてのデスクトップアプリ

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

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

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 -isequence.editorが呼ばれるのは一度だけのはずです。

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

例えば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クライアントの代表格(?)であるSourcetree3では、以下の図のようになっています。

git_rebase_i_gui.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!がついてあるコミットを、メッセージを基準に移動させてsquashfixupの指示を出した状態が、TODOファイルの初期値となるようです4。が、今回の本筋ではありません。

大事なのは、以下の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クライアントを試してみる機会を作りたいなぁ。


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

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

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

  4. 例えば次の記事が参考になります。
    https://qiita.com/kyanro@github/items/818012c1b1827ed48277 

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1