週刊 git GUIクライアントを作る [1] stage/unstage基礎知識編

  • 4
    いいね
  • 0
    コメント

始めに

何をどれだけ作るかは未定です。
まだGUIの話はしません。

動機

  • I love git.
  • 各種GUIクライアントたちは(機能とプラットフォームと好みで)一長一短
  • 「ぼくのかんがえたさいきょうのGUIクライアント」を作れるようになりたい

行単位のstage/unstage

gitのGUIクライアントにとって最も大事な機能は

  • 行単位でstageおよびunstageが気軽にできる
  • wokrespaceとindexの状況(diff)が把握しやすい

だと私は考えています。そういうことにしましょう。
だいたいdiffを見ながらstage/unstageするので、後者は前者の必要条件ですね。

なお、ここで言うstageとはindexへの登録、unstageとはindexから解除のことです。
indexとはパスとデータベース上のファイル実体(blob)の対応表みたいなもので、これに登録されているものがcommitの対象となります。

コマンドラインの場合

GUIを作る前に、gitの勉強をしましょう。
コマンドラインでは行単位のstage/unstageをする際に、以下のコマンドを使います

  • git add -p
  • git reset -p

上記コマンドでインタラクティブモードに入り、対象となるhunkに対してeを選んで編集することで、行単位でのstage/unstageが可能になります。
(hunkはdiffの断片で、1ファイル内の離れた場所にあるdiffは別のhunkとして切り出されます)

git add -e [ファイル名]でもだいたい同じですが、resetには-eオプションがありません。
また、-pオプション経由での編集時には、以下のようなコメントがついていて便利です。

# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.

この編集用hunkは.git/addp-hunk-edit.diffとして一時ファイルが作成されます。
git add -eの場合は.git/ADD_EDIT.patchで、
 1ファイルについての1つ以上のhunkを含むpatch全体です)

ここで編集されたhunkをもとにpatchが作成されて、自動でapply --cachedされます。

  • git apply --cached <patch>
  • git apply -R --cached <patch>

このインタラクティブモードでは、
(おそらく)patchはファイルとして作成されずにapplyコマンドに渡されていますが、
git diffおよびgit diff --cachedの出力と同様の形式です。
この出力をapplyすればgit addgit resetしたのと同じことが起こります。

git addは以下と同じ。

git diff | git apply --cached

git resetは以下と同じ。

git diff --cached | git apply -R --cached

patchおよびdiffのフォーマット

詳細はこちら https://git-scm.com/docs/diff-format#_combined_diff_format
ヘッダー部分と、1つ以上のhunkの繰り返しからできています。
(複数ファイルが対象となっている場合は、それがさらに繰り返されます。)

  • 1ファイルのdiff全体に対するヘッダー(4~5行)
    • hunkのヘッダー(1行)
    • hunkのボディ(1行~)
    • hunkのヘッダー
    • hunkのボディ
  • 1ファイルのdiff全体に対するヘッダー
    • hunkのヘッダー
    • hunkのボディ
    • hunkのヘッダー
    • hunkのボディ

ヘッダー部分はこんな感じです。ファイルごとに存在します。

diff --git a/names.txt b/names.txt
index 61b59be..2d94d6d 100644
--- a/names.txt
+++ b/names.txt

hunkはこんな感じです。@@から始まる行がhunk自身のヘッダーです。

@@ -1,2 +1 @@
-test.txt
-names.txt
\ No newline at end of file

インタラクティブモードではhunkのヘッダー部分は編集する必要がありませんが、apply --cachedされる前に自動で再計算されてpatchに組み込まれているようです。

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

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

GUIクライアントがGUIとして「見せる」のは1と2ですが、もちろん3~5も必要です。

  • 3は、インタラクティブモードでのhunk編集でやっていることで、 +-で始まる行を丸ごと消したり、スペース始まりにしたりします。
  • 4は、hunk内の対応行の開始位置とオフセットを差分の適用前後それぞれについて表しています。
  • 5は、applyするだけで、コマンドも分かっているので簡単なはずです。(でも、例えば、JGITはgit apply --cachedに相当するコマンドが無いように思います)

あとは作るだけ!

参考になるかもしれないコード

add/resetのインタラクティブモード

https://github.com/git/git/blob/master/git-add--interactive.perl
ただし、このコードが実際に使われているのか、よく分かっていません。
同内容のファイルは、windowsだと下記のような場所にあります。
C:\Program Files\Git\mingw64\libexec\git-core\git-add--interactive

git-gui.exeのStage Lines For Commit

https://github.com/git/git/blob/6867272d5b5615bd74ec97bf35b4c4a8d9fe3a51/git-gui/lib/diff.tcl#L643
tclのコードです。
テキストウィジェットの機能を駆使して文字列検索しているため、文法を知らずに読むのは無理でした。https://www.tcl.tk/man/tcl8.4/TkCmd/text.htm などを参照すれば読めると思います。