クフでダローバルな日記

タフでもグローバルもない

gitに新機能を追加してgit masterを目指す(初級編)

学科の実験「大規模ソフトウェアを手探る」でgitを弄って改良しようとしたので、その過程と結果を何回かにわたって書いてみようと思います。

「レポートとしてブログ記事が認められる」という変則的な実験なので、レポートとして書きますが、文体は敬語かつ口語っぽく書かせていただきます。
また、単にハウツーを書くだけではなく、チームや個人としての感想や想いについても書かせていただきます。

というのも、ブログ記事が推奨されている理由が、

今後同じく大規模ソフトウェアやOSS、特に自分の暑かったソフトについて手探ろうとしている人たちの参考となるため

であるためです。
従って、この記事は単なるレポートではなく、他の方に参考にして戴ける記事にしたいと思っているので、質問や意見などがあればコメント欄やはてブコメントなどで仰ってください。
twitter@showmeearなので、リプを飛ばしていただいても構いません。

ちなみにチームメンバーはtkkとvinterと僕です。
記事内でもこの名前で呼びますが、レポートの中でどう扱われるんでしょうか……?

なお、手探るために用いたgitのバージョンは2.6.0です。

実験「大規模ソフトウェアを手探る」とは

レポートとしては不要かもしれませんが、まずこの実験について簡単に紹介しておきます。

僕の所属する東京大学電子情報工学科(通称eeic)で3年後期に行われる実験の一つで、テーマは

全容を把握できるわけがない程大きなソフトウェアをいかに扱い,必要な動作を理解し,変更するか

だそうです。
サイトは一般に公開されているようなので、ご覧になってみてください。
配布されている資料のpdfがとても参考になるかと思います。

手探る対象ソフトの決定

手探る対象として、私達のチームはgitを選びました。

その主な理由を以下に述べます。

ブラックボックス化の解消

一つ目の理由が、「 普段良く使っているけど、中身がほとんどわからないまま使っているので、手探ることで実態を知りたい 」というものです。

僕自身もかねてから思っていたこととして、「便利なものをブラックボックス化して使っているだけだと、トップダウン的な学習しか出来ずに技術の進歩についていけなくなる」というものがあります。
gitは何も理解しなくても使えて便利なのですが、そのために完全にブラックボックス化して使っていたことの反省として弄ることにしました。

「もともとバージョン管理システムsvnなどがあったのに、どうしてgitがデファクトスタンダードとなったのか?」とか、「gitで何が出来て何が出来ないのか?」を真に理解しようと思ったら、エンジニアとしては「コードを読む」のが何よりも良いのではないかと思います。

コードの綺麗さ

ふたつめの理由は「gnuplotなどに比べると割と新しめのソフトなので、コードが綺麗そう」というものです。

有名な逸話ですが、創造主Linusは、1週間でgitの元となるものを創りだしたそうです。

安直な考えではありますが、元となるものが1週間で出来たのだったらコードもさほど煩雑ではないだろうし、歴史も長くないので破綻しているところも少ないだろうということで、コードが綺麗で弄りやすいだろうと期待したということです。

実際に見てみると、コードが上手く分割されていたり、変数名や関数名が分かりやすかったりと、かなり綺麗なものでした。
後述しますが、mailing listでmergeしてもらおうとする際には結構厳し目のレビューをされるようなので、そういった開発者コミュニティの努力によって維持された美しさなのかなと思います。

開発とは直接関係はありませんが、こういった綺麗なコードを読む機会は普段一人で開発している時には得難いものなので、そのためにもOSSのコードを読んでみることは良いのではないかと思います。

カッコよさ

gitにコミットしたんだぜ」って言うとカッコ良さそうですよね。それだけです。

最初の手探り git add

最初に私達のチームでは

  1. commitメッセージを勝手に変える
  2. git addとするとgit add .される

機能を実装することにしました。
あまり重くなさそうなタスクを探した結果、これらになりました。

1つ目はvinter君、2つ目は僕とtkk君が担当したので、僕は後者について解説します。

目的

現状git addとするとgit add .の間違いではないかと言われるが、当然その間違いであるので、そのような提案をするのではなく、実際にgit add .と入力したものとした動きをする。

gitの処理の流れ(怪しい関数の探し方)

まず、git add . した時の流れをgdbで追いました。

ただ、gdbで追っているだけだとどれが大事な関数なのかわかりづらいので、実際はソースをエディタで見て怪しい関数にめどを付け、その関数にbreakpointを貼るようにしました。
最初のうちは「怪しい関数」と言われてもよくわからなかったのですが、慣れてからの探し方は以下のような流れでした。

  1. gdbでどのファイルのどの関数に入ったのか見る
  2. そのファイルをエディタで見て、呼ばれる関数を探す
  3. その関数の中で、複数の処理をし、複数の関数が呼ばれていくはずなので、その中に怪しい物があるはず
    • 引数のない関数は多分準備とか設定に関わっているものなので怪しくない
    • 逆に、引数が多くてrunとかhundleとかcmdとかのいかにも実行しそうな名前のついてる関数は怪しい
      (gitはこのように命名がわかりやすいので非常に弄りやすい)
  4. 見つけた関数にbreakpointを設定し、1に戻る
  5. もし何らかの文章が表示された時は実効が完了してしまった可能性が高いので、飛ばした部分にある関数が怪しいとめどを付けてやり直す

こういった手探りを繰り返して分かったgitの大雑把な処理の流れは以下のとおりです。

  • 引数として与えられたコマンドやオプションをparseする
  • commandをbuiltinかexternalか判別
  • commandに対応した関数を呼ぶ

です。

コードの中での関数の流れ(先述の怪しい関数)は主に以下の通りとなっていました。

main(git.c)
→handle_options(git.c)
→ run_argv(git.c)
→ handle_builtin(git.c)
→ cmd_add(builtin/add.c)

parseについては当然のことだと思われるので省略します。

builtin,externalについて、僕は知らなかったのですが、gitはプラグインを用いて自前のコマンドを用意することが出来ます。
もちろんそれはソースとは別の所にあるので、それらを区別する必要があるわけです。
それがrun_argv()の中で行われて、builtinコマンドであればhandle_builtin()が実行されます。

このhandle_builtin()ではgit.c内で定義されているstruct cmd_struct commands[]の中から対応する関数を探してきます。
このcommands[]の定義はgit.cの中でもひときわ目を引くので、エディタでみてればすぐに怪しいなと分かります。

実際、"add"に対応しているのは cmd_add() という関数であることがわかり、テキストエディタでプロジェクト内でこの関数で検索をかけてみると builtin/add.c にこの関数を見つけることが出来ました。

更に、commands[] のテーブルとbuiltinフォルダを見比べることで、「builtinコマンドの多くはこの builtin/hoge.c のなかに cmd_hoge() として定義されてるっぽい」ということまでわかりました。

add.cの流れ

次に、add をした時の流れについて詳細に追ってみます。

先ほどの調査でbuiltin/add.ccmd_add() が怪しいということが明らかになりました。

今回の目的はgit addgit add .とすることだったので、まずはgit add とした時にどこで例外処理されて終了するのかを探してみることにします。

gdbb cmd_add とした上で r addとして、nを連打して飛ばし続けていると、add.c:357

if (require_pathspec && argc == 0) {
  fprintf(stderr, _("Nothing specified, nothing added.\n"));
  fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
  return 0;
}

の部分で終わっていることが明らかにわかりました。
エラーで吐く文章も一致していることからも明らかですね。

無理矢理addするための二つの方法

今回は Maybe you wanted to say 'git add .'? との忠告を無視して、無理矢理git add .であるように振る舞いたいので、そのための方法を探します。

といっても、僕が実装した方法はとても愚直で、
argc==0だったらargv[0]に"."を入れておく
ということで解決しました。
つまり、エラーで提案するのではなく、あたかも最初からgit add .されていたかのように引数を無理矢理修正したということです。

実際にこれでコンパイルしてみて実行すると、期待通りの挙動をしたので、目標は達成しました。

ただ、この方法は汎用性が低く、汚いという欠点が明らかだったので、どうにか関数を使って上手いことやりたいとも感じました。
それを実際にやってくれたのがtkk君で、彼はargcが0の時にはaddremoveを1にすることで解決していました。

このaddremoveという変数は、git add -Aの時に1になるフラグなので、git add -Aの挙動を示すことになりました。

gitはこのようにフラグでオプションを管理しているところが多いので、困ったら怪しそうなフラグを探すのも手段の一つとして有りうるのではないかと思います。

次回予告

以上、gitを手探った際のファーストステップ、簡単なコマンドを弄ってみた時の手順を解説しました。

次回は、もう少し実用的な修正をした時の記録を書いていこうと思います。