一応これはGitを手探ってみた その1の続きです。
gitをいじる目的
素晴らしいgitそれ自体の仕組みを知りたかったので始めたことですが、せっかくなので何かしらの機能を追加したいと思います(授業の本来の目的はこちらだろうが)。
gitは素晴らしいのでこれ以上変更する場所はあまりないのですが、SmallProjectsIdeasに「amendをするときにwarningを出すようにする」というのがあったのでこれを解決出来ないか試してみたいと思います。
git amendは直前のコミットを変更するコマンドです。とても便利なコマンドですが、すでにリモートにpushしているコミットをamendするとコンフリクトを起こしてしまいます。そのような時は、一度pullしてmergeしないといけないので素晴らしいGitの履歴が汚くなってしまいす。
なので、すでにpushしたコミットはamend出来ないようにしたほうがいいのではと考えました。
gitプログラムについて
では、早速gitのプログラムを見てみましょう。git/gitは20万行を超えるCUIツールとしてはとても大きなソフトなので全て読み解くことは至難の業です。
自分はこのようなことは初心者なので、基本的な攻め方はgdbで起動してgit log
やgit commit
の様子を探っていきます。
後から気づいたのですが、大まかなところを探るためにgrepで文字列を検索して大体な所を予想してbreak pointを設定すると捗りました。
個人的な意見としてはgitはライブラリなどが使用されておらず読みやすいように感じました。
main関数
main関数はgit.c
にありました。commit
やlog
のようにbuilt-inコマンドはここから main->run_argv->handle_builtin->run_builtin
の順に関数を踏み、途中で収得した各コマンドごとの関数ポインタでそれぞれの処理に飛びます。
built-inコマンドは基本的にbuiltディレクトリの中に同名のファイルがあると思います。例えばadd
ならadd.c
、commit
ならcommit.c
のがあります。
git.cから最初に呼び出されるのはcmd_{コマンド名}
という関数のようです。commit
の場合はcommit.cの中のcmd_commit()
が呼び出されます。
commit --amendを手探る。
commit --amend
を渡してみてgdbで探ってみます。
オプションが正しいか解決するのはparse_and_validate_options
という関数のようです(commit.cのcmd_commit()内で呼び出されています)。
この関数の真ん中らへんに以下のようなコードがあります。
if (amend && whence != FROM_COMMIT) {
if (whence == FROM_MERGE)
die(_("You are in the middle of a merge -- cannot amend."));
else if (whence == FROM_CHERRY_PICK)
die(_("You are in the middle of a cherry-pick -- cannot amend."));
}
amend
の文字もあるし、もしkillするならココらへんにプログラムを入れると良さそうです。
どのようにpush済みか判断するか
当初、push済みかどうかを判断するのにlog
のコードを使えばいいと考えていました。
--decorate
を使えば、下記のようにremoteとlocalのheadが分かります。なのでlocalのHEADがremoteのHEADより進んでいる場合のみamend出来るようにしたら良さそうです。
$ git log --decorate --oneline
d7e7b6f (HEAD -> master) Merge remote-tracking branch 'origin/master'
6e85f70 (origin/master) test
log.c
のcmd_log_walk()
を参考にこのようにしてみました。
decoration->name
は"origin/master"のような文字列です。branch_get
とbranch_get_upstream
でリモート名(慣習的にorigin)を習得し現在のHEADのdecorationにリモート名が含まれていたら、dieしています。
struct branch *br = branch_get(NULL);
const char *upstream_name = branch_get_upstream(br,NULL);
decoration = get_name_decoration(&commit->object);
while(decoration){
if (strstr(decoration->name,upstream_name)){
die(_("This commit are already pushed to remotes -- cannot amend."));
break;
}
decoration = decoration->next;
}
これでもある場合では動くのですが、もしremoteのヘッドが先行している場合(進めていないbranchをfetch
した場合など)は期待通りには動きません。
ではリモートのHEADとローカルのHEAD双方から全探索をするのか?
それはあまりいい方では無いように思うし、何より素晴らしいGitとしてはスマートではありません。
といことでいろいろと探してみたところ、status
が使えるのではないかと思い当たりました。
以下の4パターンでのgit status
の出力は以下のようになります。
- localとremoteが同じ -> amendダメ
- localが進んでいる -> amend ok
- remoteが進んでいる -> amend ダメ
- localとremote両方が進んでいる。 -> amend ok
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working directory clean
On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working directory clean
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 12 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
nothing to commit, working directory clean
status
コマンドを探ってみたところremote.c
の中にちょうどいい関数がありました。
stat_tracking_info(branch, &ours, &theirs, &full_base)
にbranchとint型変数のアドレス2つとconst char*型のアドレスを渡すと、oursの中にlocalの進んでいる分、theirsの中にremoteの進んでいる分を教えてくれます。
これを使った最終的なプログラムは以下のようになりました。
struct branch *br = branch_get(NULL);
int num_ours,num_theirs;
const char *upstream_name;
stat_tracking_info(br,&num_ours,&num_theirs,&upstream_name);
if (amend && num_ours==0){
die(_("This commit are already pushed to remotes -- cannot amend."));
}
すっきり綺麗にまとまりましたね。素晴らしい。
Gitコミュニティにコミットしてみようとしてみた
せっかくの変更したので、gitのコミュニティにコミットしてみようと思いGit Mailing Listにメールを投稿してい見ました。
したところ、以下の様な指摘を貰いました。
- パッチの送り方が違う(ドキュメントを読もう。)
- remoteにブランチがないとバグる。
- テストも書かないといけない。
- 強制的に落とすのは使っている人もいると思うのであまり良くない。
送ったあと見つけたのですが、Git にパッチを送って取り込まれた話という記事が参考になりそうです。
2はすぐに解決することが出来ますが、dieするのではなく例えばwarningを出すことやremoteを使ったテストの書き方が分からず、まだ出せていません。
また時間があるときに挑戦してみたいと思います。