Help us understand the problem. What is going on with this article?

Gitを手探ってみた その2

一応これはGitを手探ってみた その1の続きです。

gitをいじる目的

素晴らしいgitそれ自体の仕組みを知りたかったので始めたことですが、せっかくなので何かしらの機能を追加したいと思います(授業の本来の目的はこちらだろうが)。

gitは素晴らしいのでこれ以上変更する場所はあまりないのですが、SmallProjectsIdeasに「amendをするときにwarningを出すようにする」というのがあったのでこれを解決出来ないか試してみたいと思います。

git amendは直前のコミットを変更するコマンドです。とても便利なコマンドですが、すでにリモートにpushしているコミットをamendするとコンフリクトを起こしてしまいます。そのような時は、一度pullしてmergeしないといけないので素晴らしいGitの履歴が汚くなってしまいす。
なので、すでにpushしたコミットはamend出来ないようにしたほうがいいのではと考えました。

git_amend.png

gitプログラムについて

では、早速gitのプログラムを見てみましょう。git/gitは20万行を超えるCUIツールとしてはとても大きなソフトなので全て読み解くことは至難の業です。

自分はこのようなことは初心者なので、基本的な攻め方はgdbで起動してgit loggit commitの様子を探っていきます。
後から気づいたのですが、大まかなところを探るためにgrepで文字列を検索して大体な所を予想してbreak pointを設定すると捗りました。
個人的な意見としてはgitはライブラリなどが使用されておらず読みやすいように感じました。

main関数

main関数はgit.cにありました。commitlogのようにbuilt-inコマンドはここから main->run_argv->handle_builtin->run_builtinの順に関数を踏み、途中で収得した各コマンドごとの関数ポインタでそれぞれの処理に飛びます。
built-inコマンドは基本的にbuiltディレクトリの中に同名のファイルがあると思います。例えばaddならadd.ccommitなら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.ccmd_log_walk()を参考にこのようにしてみました。

decoration->nameは"origin/master"のような文字列です。branch_getbranch_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の出力は以下のようになります。

  1. localとremoteが同じ -> amendダメ
  2. localが進んでいる -> amend ok
  3. remoteが進んでいる -> amend ダメ
  4. 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にメールを投稿してい見ました。
したところ、以下の様な指摘を貰いました。

  1. パッチの送り方が違う(ドキュメントを読もう。)
  2. remoteにブランチがないとバグる。
  3. テストも書かないといけない。
  4. 強制的に落とすのは使っている人もいると思うのであまり良くない。

送ったあと見つけたのですが、Git にパッチを送って取り込まれた話という記事が参考になりそうです。
2はすぐに解決することが出来ますが、dieするのではなく例えばwarningを出すことやremoteを使ったテストの書き方が分からず、まだ出せていません。
また時間があるときに挑戦してみたいと思います。

参考文献

vintersnow
情熱のない大人になってしまった
http://vintersnow.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした