Git 初心者が詰まったところをメモ

この記事は KainokiKaede Advent Calendar 19日目の記事です(大嘘)

最近は Mercurial から Git に乗り換えて開発をしているのですが、この乗り換えの際に気になった点をメモ。Git の哲学を学ぶという意味でも。まぁ半分ぐらい愚痴エントリです。

個人的な印象だけど、Git は Mercurial と比較して「人間はミスらない」ことを前提として作られている気がする(破壊的な動作が多すぎる)。

merge と rebase はどう違うの(merge だけでよくない?)

  • merge: すべてのコミットがその時点のものとなる(動くようにコミットし続けていれば、動く)。
  • rebase: rebase される枝のコミットはコミットされた時点のものではなくなる(他の箇所が変更されていたりすると、動かなくなる)。

rebase には全く利点がないように思える。公式の解説 を参考にすると、要は「コミットグラフが綺麗になる(それ得かな? 複雑なほうが気持ちよくない?)」「fast-forward でコミットされた側を移動させることができるようになるので、受け取った側が楽(でもこれも merge すれば同じじゃねーの?)」「公開したコミットをリベースしてはいけない」に尽きるっぽい。

「リベースはあくまでもプッシュする前のコミットをきれいにするための方法であるととらえ、リベースするのはまだ公開していないコミットのみに限定するようにしている限りはすべてがうまく進みます。」だそうだ。

ここ を読むと、merge したときのコミットっていうのは基本的には「機械的に生成された、だれもテストしてないコミット」なので、動く保証はない。だから rebase をするのだ、と読めるが、rebase したときのコミットも基本的には機械生成されたもんだと思う。

ここ の図はわりとわかりやすく、要は「直接 merge するのではなく、rebase してから merge すれば、rebase した時点でもう1回テストできる」ということ。 でもそれも、一回 master を topic にマージした後に、そこでテストをして、OKなら topic を master にマージ、みたいにしたほうが(たしかにグラフは汚くなるかもしれないけど)過去の動いていたはずのコードを破棄する必要がなくて安心なのでは。

ここ によれば「書いた通り rebase じゃなくて merge で済む場面がほとんどです。」らしいので rebase は忘れよう。

なんでブランチを消すとその枝が全部消えるの(ログとしてとっておきたいのに)

ここ から読み取れることは、「ログとして残したいならブランチをつけておけ」ということである。でもブランチリストに表示されるのがイライラすんだよなぁ。Mercurial ならブランチがどこからも参照されていなくても残り続けるから安心なのだ。

タグをつければいいじゃんという意見もあるが、それは今度はタグリストに表示されるじゃん、無意味でしょ。

でも Mercurial を使うわけにはいかないので(デファクトスタンダードを使わなければならない)、ゴミを電気に変えなければならない(Mr. Fusion)。

とりあえず、ブランチを消しても30日間は reflog とかいうログに残るらしい(30日間ってなんだよ、永遠に残せよ)。だから消してすぐならブランチをつけ直すことで対処できる。

結局「ブランチリストには出てきてほしくないけど、過去の変更のログを残すためにそこにいてほしい」みたいな使い方をすることはできないんだね(やはりゴミかな?)。

ここ で紹介されている方法は一応2つ? refs/archive/branchname みたいなところに格納するか、 git tag archive/branchname みたいにタグ付けするか。で、後者のほうが正解っぽい、と。 SourceTree はブランチ名をディレクトリっぽく書けばそれを認識して折りたたんでくれるみたいだから、それでもいいのかなぁ。

ここ によれば「Mercurial の哲学は、“履歴は永久的で神聖である”ということです。」らしくて完全に同意である。

pull いらなくない?(fetch + merge でよくない?)

pull は基本的にはコンフリクトが起こらないと確信できる状態でしか使わない。

こんな記事 とか こんな記事 とかがあって、要は送りたいときは push, 貰いたいときは fetch, 繋げたいときは merge と覚えておけば予期せぬ動作をすることはあまりないようだ。

amend, cherry-pick, squash, rebase -i とかコミットログを混乱させるだけでは

そうだよ(便乗) とくに rebase -i とか問題を起こさずに実行する自信がまったくない。

  • amend: 1つ前のメッセージを変える。
  • revert: あるコミットを打ち消すコミットを作る。(間違えて revert した場合は reset する。)
  • reset: あるコミットまで戻る。(間違えて reset した場合は reflog を参照したり ORIG_HEAD を参照したりして対処する。)

このあたりはもしかしたら使うことがあるかもしれないけど、基本的に全ての commit は final であるという意識を持つべきだと思う。

origin/HEAD がズレる(更新したい)

もともと origin/HEAD は master に向いていた→Redmine を使うとかの関係で develop に symbolic-ref を張り替えた→でもいくらフェッチしても僕のコピーの origin/HEAD は master に向いたまま。

ここ によれば、

origin/HEAD is set automatically when you clone a repository, and that's about it. Bizarrely, that it's not set by commands like git remote update - I believe the only way it will change is if you manually change it.

だそうなので、つまりは「リモートの変更を同期する」みたいなオプションは与えられてないってことだ。たいがいクソだと思うが、ふつうに local で origin/HEAD に symbolic-ref を張ってやればいいのかな。

Mercurial が恋しい

むかし Mercurial を使っていたときは特に問題なく直感的に使えていたのに Git を使い始めてからこれだけ意味不明感が出てきたの、あきらかに Git の不親切を感じるしこれがデファクトスタンダードであることに違和感を感じるしやはり世界同時革命しかない。