TDD Boot Camp 福岡 2 日目
朝ごはんをぎりぎりで食べて、直接会場に。
当日の発表資料はこちらです。
秋猫さん (id:Akineko) の発表の中で SQL で FizzBuzz やるとか SQL で bf 処理系作るとかそんなの絶対おかしいよ!
て言われた気がしますけど全然おかしくないよ普通だよ!
お昼ご飯を食べて、午後からは新しい仕様を言い渡しました。
- Web サービスからつぶやきを取ってきて、それを判別
- 非公式 RT も判別
- 現在の時刻前後 30 分の時間帯のつぶやきの判別
- これに伴い入力形式を変更
- 「yyyy/MM/dd HH:mm:ss\tScreenName\tBody」
この変更は結構厳しかったみたいです。いろんなところから悲鳴の声が。
三番目のお題のために和田さん (id:t-wada) にも Web サービス側の仕様変更をお願いしたのですが、さくっと対応してくれました。
それと、今日は Git を使うということで、全てのペアを見て回っていました。
ただ、AsakusaSatellite チームが入っている 3 ペアは Git の使い方まで見なくても大丈夫だろうと思って油断してたら、みずぴーさん (id:mzp) branch 切ってなかったという・・・
Git-Hooks を使って id/チケット番号で branch 切ると、コミットメッセージにチケット番号が追加されるので、トピックブランチを rebase した後からでも「ここからここまでがこのトピックブランチでの作業」というのがわかりやすくていい感じなのです。
branch 切りませう!
Git-Hooks についてはみずぴーさんのブログエントリをどうぞ。
Git+Redmineな人におすすめのフックスクリプト集 - みずぴー日記
結構進み具合の早いペアがいたので、急遽チケットを増やしました。
- URL も判別
- 短縮 URL の展開
この仕様追加は、どちらも秋猫さん (id:Akineko) 発案です。無慈悲な感じがとても素敵です。
全てのペアをまわって見て印象的だったのが、Java ペアと、OCaml ペアです。
Tweet クラスの導入
入力形式が変更になったタイミングで Tweet クラスを導入した Java のペアがいました。
その設計判断をしたときのことをレビューで
フィールドが文字列 2 つならまだ耐えることができたけど、日付が入ってきたので導入した。
という風に言っていました。
文字列ではなく何かほかの形式を使う、というペアはここ以外にもいました。
OCaml チームはかなり最初のほうからレコードを導入していましたし、文字列ではなくデータ構造を用意する、というペアはいました。
でも Java (や C#、VB) はクラスを作るのが面倒なんですよね。
その「面倒さ」を乗り越えてでもクラスを作るタイミングとして、2 つではなく 3 つ目というのは非常に素晴らしかったと思います。
また、このペアはコミットの数もおそらく一番多かったです。
ペア交代のタイミングでコミットというのは、なかなかに面白いやり方だと思いました。
アナログな手法
OCaml ペアでノートを活用しているペアがありました。
チケットよりも細かい粒度のタスクを TODO リストとして紙に書く、というのは自分もよくするのですが、今回見た限りではこのペアが一番アナログな手法を活用していました。
bleis 賞はこのペアに贈りましたが、決め手はそのあたりです。
以上で TDD Boot Camp 福岡は終了しましたが、それから打ち上げがあったので終電とか考えない感じで参加してきました。
席の並びはこんな感じ。
壁 mzp suer 壁机机机机机机机机 壁 bleis ukstudio 壁壁壁壁
OCaml チームに囲まれている・・・!
全体を通して、疲れたけどとても楽しく、また色々な気づきもあって有意義な時間を過ごせました。
なかやんさん (id:pocketberserker) をはじめ、TDD Boot Camp 福岡にかかわったすべての人に感謝です!
分散バージョン管理勉強会で話してきた
12/17 に開催された、分散バージョン管理勉強会に参加して、会社で運用している Git と Hudson を連携させた構成の紹介をしてきました。
会場に行くまでの話とか、他の人の発表の話とか、その他諸々は別エントリにまとめるとして*1、このエントリでは自分の発表に関連するところを書きます。
分かり難いことこそ美徳 (Git は玄人向け?)
という話があったんですけど、あー、うー。なかなか反論難しい感じですよねw
もちろん分かりにくくしようと思って分かり難くなっているわけではないです。
要は何を取って何を捨てるか、という話で、Git では速度をとって他のものを捨てているイメージ。
ハッシュわけわからん、って人はそれなりにいて、自分も「Mercurial のリビジョン番号とハッシュ併用スタイルいいよなー」とか思っていたんですけど、
@bleis リビジョン番号でどこそことか言われても相手と自分で指すchangesetが違うことがよくあるから困るんだよね #shibutra
2010-12-17 21:31:33 via YoruFukurou to @bleis
というのは考えていませんでした。
なるほど確かにそれは困る。
それに「どこそこのリビジョン」とリビジョン番号で指定できることは、自分にとってはあまり重要ではないのですよね。
コミットの粒度を細かくしているため、そのコミット自体にももちろん意味はあるんですけど、自分がほしい情報というのは「チケット番号」の方なのです。
それ以上細かく指定したい場合は「チケット番号ほにゃららの何番目のコミット」と指定できればうれしいかな、とは思う・・・の、で・・・これは後で何か作ろうかな*2。
central リポジトリに push する前に rebase -i してコミットメッセージに同じチケット内の何番目のコミットなのかを含める感じ?
うーん、メリットに対してデメリットもでかそう・・・もうちょっと軽くして、チケット番号と開発者と連番でそれなりに一意になるような情報を埋め込むとか?
で、Git 難しそうとか、Git は玄人向けとかってイメージですが、確かにこういうイメージありますよね。
でも、これはあくまでイメージで、慣れてしまえばそんなことないです。
どうやって慣れるの?という話は・・・うーん、うーん。
まず Git を会社で使いたい!と思う人は、とりあえず Git を入れて、使いまくってみてください。
どこかで「あぁこういうことか」とわかる時が来るはずです*3。
そうなれば次はチームに広めてみましょう。
チームのみんなには clone と add と commit と push と pull と gitk くらいを教えておけばいいでしょう。
で、「わからなかったり困ったことあったらどんどん聞いてね」と定期的に言うようにします。
そして、何か質問とかが来たら、それを実現するためのコマンドだけじゃなくて、「Git ではこうなっていて、だからこういうコマンドを使えばいいよ」とコミットグラフなどを図示してあげるといい感じです。
同じことを何度か聞かれることもあると思います。そういう時は、説明の方法をちょっと変えてみましょう。
こうすれば、チームのみんなも Git が分かるようになっていくし、あなたも知識がどんどん深くなっていきます。
チームのみんなが Git を十分に使えるようになったら、今度はチームのみんなも教える側に回ってもらって、もっと広く広めていけばいいでしょう。
って、多分これは Git に限った話じゃないですね。
コマンド難しそうというイメージ
これも慣れの話ですが・・・
これに関しては別にコマンドにこだわる必要はないですよね。
GUI 使えばいいと思う。Windows で Eclipse じゃないなら GitExtensions が個人的にはお勧めです。
ただ、「最初の一人」になろうと思ったらコマンドも使えないと色々とまずいかもしれません。GUI ではできないこととかあるかもしれませんし。
それに、コマンドと言っても補完機能とかありますし、思っているほど難しくないですよ。
構築とか運用が難しそうという話
という反応が多いようです。参考:分散バージョン管理勉強会の Togetter
ここらへんに関してフォローする目的で作りかけの資料とかあるのですが、さっさと完成させろよ > 自分
どうにかしたいところではありますね。
Groovy
Hudson の Groovy Plugin で色々できるらしいとの情報をもらったので、来年は Groovy の勉強しようかなー。
大規模
懇親会で、分散バージョン管理システムは大規模向けとか思われてることも多いけど、小規模の方が導入も楽だし色々できるよねー、という話があったんですけど、なるほどその通りですね。
Git と Hudson を連携させて「きれいなリポジトリ」を作ったとしても、誰も master に更新できなさそう、とかそういう。
まぁ、そういう人たちをふるいにかける、という意味ではいいのかもしれませんけど、それやると誰も残らなかったりして・・・とか。
人数が少なければフォロー可能でしょうけど、多いとそうもいきませんしね。
DVCS?うちは小規模だからいいや、って人は多いと思うんですけど、小規模の方がむしろやりやすいし便利ですよ!と。
Git と GitHub を体験しながら身につける勉強会行ってきた
9/18(土) 15:30~ GitとGitHubを体験しながら身につける勉強会(名古屋) : ATND
行ってきました。
なんかいろいろと話すことになったんですけど、あの場で言いそびれたこととか、もっとこう説明してればよかったなぁ、って部分の補足も兼ねたエントリです。
長文注意。
ショートカット
- git add の話
- git add -p/git reset -p の話
- リビジョン番号がない話
- ブランチの話
- git-completion の話、__git_ps1 の話
- コミットの指定の話
- reset の話
- rebase と merge の話
- 公開したものの rebase の話
- stash の話
- TortoiseGit、HG、SVNのはなし
- 全体を通して
git add の話
Git と SVN では、add に限らず、同じ名前のサブコマンドでも意味が異なるものがいくつかあります。
その中でも add は一番使うであろうサブコマンドなので、言及される場面も多いのだと思います。
また、add の違いは Git 特有の index (もしくは staging area) にもかかわってくるため、難しく考えてしまう人も多いかもしれません。
しかし、「コミットの粒度を小さく保ち」、「commit -a を使えば」、SVN の add と似た、「add は最初の一回だけ」という操作感を得ることも可能です。
ただしこれではもちろん index の恩恵は受けることができませんし、SVN と違い commit 時にファイルを指定することができませんをしませんので、全く同じというわけでもありません。
それでも、
- Git では細かく分割したコミットを後から簡単にひとつのコミットにまとめることができる
- 複数の修正を一度に行わなければ、index はほとんどの場合不要で、常に working tree 全体を commit すればいい
ということを考えれば、この方法は十分「アリ」だと思います。
git add -p/git reset -p の話
「hunk レベルで add」ができる -p オプションには言及しましたが、同様に「hunk レベルで reset」も可能です。
これも、reset に -p を付けるだけです。
- p を付けると hunk ごとに y,n,q,a,d,/,K,g,e,? のどれを選ぶのか聞かれますが、それぞれの意味は次の通りです (add の場合 stage、reset の場合 unstage)。
文字 | 意味 |
---|---|
y | この hunk を stage/unstage する |
n | この hunk を stage/unstage しない |
q | この hunk を stage/unstage せず、残った hunk もすべて stage/unstage しない |
a | この hunk と、同じファイルの残りの hunk を stage/unstage する |
d | この hunk と、同じファイルの残りの hunk を stage/unstage しない |
g | hunk を選択して移動する |
/ | 正規表現で hunk を検索して移動する |
j | この hunk の stage/unstage の決定をせず、次の未決定の hunk に移動する |
J | この hunk の stage/unstage の決定をせず、次の hunk に移動する |
k | この hunk の stage/unstage の決定をせず、前の未決定の hunk に移動する |
K | この hunk の stage/unstage の決定をせず、前の hunk に移動する |
s | この hunk を小さい hunk に分割 (split) する |
e | この hunk を手動で編集する |
? | ヘルプを表示する |
g や / は、単独で入力すると移動先の hunk や検索する正規表現の入力を求められます。
そうではなく、「g2」や「/hoge」のように直接指定することも可能です。
このうち一番使うのは y と n だと思うので、とりあえず y と n だけ覚えておけばいいです。
vim 使いならほかのもいくつかすぐに覚えてしまうと思います。
-p オプションを試すには、以下の手順でどうぞ。
- 何かファイルを用意する。ここでは hoge.txt を作ったとする。
- 4行程度何かを書く。ここでは hoge, piyo, foo, bar と書いたことにする (, で改行のつもり)。
- git add hoge.txt して git commit -m "add hoge.txt" する。
- hoge.txt の各行の次に、何か追加する。ここでは、この操作で各行が hoge, aaa, piyo, bbb, foo, ccc, bar, ddd となったことにする。
- git add -p hoge.txt して、最初のプロンプトで s を選択する。
これで色々と試せます。
戻したければ、git reset で git add -p hoge.txt する前まで戻すことも可能です。
リビジョン番号がない話
これは全体では話してないのですが、ちょっと話題に上がったのでついでに。
Git では Subversion と違い、リビジョン番号のような連番は振られません。
もし連番を振るにしても、リポジトリが複数になるため、振った連番は一個のリポジトリに対してのみ一意となるので、あまり役に立ちません。
そのため、Git ではわかりやすい連番を各コミットに割り振るのではなく、各コミットの内容から計算されるハッシュ値をコミットの識別に使っています。
ハッシュ値はコミットだけでなく、tree オブジェクト (ディレクトリに対応) や blob オブジェクト (ファイルに対応)、tag オブジェクト (注釈付きのタグに対応) にも用いられています。
ここでは詳しくは説明しませんが、これら Git のオブジェクトがハッシュ値により識別可能であることにより、
- ハッシュ値はそのオブジェクトの内容からのみ決定されるため、Git のオブジェクトはすべてイミュータブル
- オブジェクトの同一性の比較が非常に高速
- ハッシュ値が同じであれば、リポジトリをまたいですらそのオブジェクトが同一のものであるとわかる (たとえそれがインターネット越しであっても、ハッシュ値のみの比較で OK)
などという利点があります。
commit オブジェクトは内容に日時も含むため、同じに見えるコミットでも別のハッシュ値となりますが、tree や blob オブジェクトのハッシュ値を見るとわかりやすいでしょう。
オブジェクトのハッシュ値を見るには、git cat-file -p を使うのが便利です。
例えば、適当なリポジトリで git log -1 --oneline した結果が
bf58771 add hoge.txt
だったとしましょう。ここで、先頭の bf・・・がハッシュ値の先頭 7 桁になっています。
ハッシュ値は対象としているリポジトリ内で一意に決定することができるのであれば、最短で先頭からの 4 桁のみの指定で大丈夫です。
7 桁もあれば大抵の状況では一意に決定できるので、このように短い形式のハッシュ値はよく使用されます。
ここで、このハッシュ値が直近のコミットのハッシュ値 (正確には commit オブジェクトのハッシュ値) となります。
このオブジェクトの詳細を見るために、git cat-file -p bf58771 を実行すると、
tree ae59f12bf77f9eeb74aad6ee3d883970a22b8a45 parent e05a728bb9f2e7d33e05316777bc926eb26200a2 ・・・
となります。ここで、tree の行にあるハッシュ値が、このコミットが含むコンテンツの最上位の tree オブジェクトのハッシュ値、parent の行にあるハッシュ値が、このコミットの前の commit オブジェクトのハッシュ値となります。
図にすると、こんな感じです。
丸が commit オブジェクトで、三角が tree オブジェクトのつもりです。
commit オブジェクトをたどっても面白くないので、tree オブジェクトをたどってみます。
git cat-file -p ae59f12 を実行すると、今度は
100644 blob 6f405dfa9459fb06055ed0a189e388a3dbff968a .gitignore 040000 tree 82e3a754b6a0fcb238b03c0e47d05219fbf9cf89 a 100644 blob c1711f47ec4b3d14afe4659859fae8207aeb9c3a hoge 100644 blob 0372cbe1ca0f901202f3de163ebcb9f593b4f6d3 hoge.txt 100644 blob acdc2b8d1e5e3c92d72c6fa52a47086129af72a5 piyo
のような出力が得られます。
各行は、その tree オブジェクトに含まれる tree オブジェクトや blob オブジェクトです。
この出力の一列目は、ファイルモードを表す数値です。今は関係ないので放置します。
二列目は、オブジェクトの種類を表します。
三列目はハッシュ値で、四列目がオブジェクトの名前です。
ここまで追ったものを図にすると、こんな感じです。
四角が blob オブジェクトのつもりです。
さらに git cat-file -p 82e3a を実行すると、
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 .gitignore
となります。ここで、ハッシュ値が同じであることによる利点を試してみましょう。
あなたの Git リポジトリで、空のファイルをなんでもいいので作り、コミットしてみてください。
そして、今まで説明した方法で中をたどってみてください。
あなたが作った空のファイルを含む tree オブジェクトを cat-file したとき、そのハッシュ値と上の .gitignore のハッシュ値 (e69de...) を比べてみてください。同じになるはずです。
Subversion のような、内容を考慮しない、時系列のみによってつけられた連番の ID ではこうはいきません。
また、ファイル名が tree オブジェクトに格納されているため、ファイル名の変更によって blob オブジェクトのハッシュ値を再計算する必要はありません。
ブランチの話
Git のブランチに関しては、裏でどう動いているのかに関して流した程度だったので、もうちょっと詳しく書いておきます。
Git でのブランチは、実装上は commit オブジェクトのハッシュ値を保持するファイルに過ぎません。
ファイル自体は、ローカルブランチなら .git/refs/heads/ 以下に、リモートブランチなら .git/refs/remotes/リモートの名前/ 以下に格納されています。
試しに、壊れてもいいリポジトリを作って、適当に何回かコミットしてみてください。
例えばこんな感じです。
mkdir hoge cd hoge git init . echo hoge > hoge.txt git add . git commit -m "add hoge.txt" echo piyo > piyo.txt git add . git commit -m "add piyo.txt"
この状態で、git log -1 --oneline で最新のコミットのハッシュ値を確認すると、bc38b25 となりました。
このハッシュ値は設定や実行時間によって異なるので値自体に意味はありません。
ここで .git/refs/heads/master の中身は、bc38b25... となっており、最新のコミットのハッシュ値と一致しています。
とりあえず、このハッシュ値をクリップボードなどに控えておいてください。
では、何かファイルを修正するなり追加するなりして、新しいコミットを作ってからもう一度 git log -1 --oneline と、.git/refs/heads/master の中身の確認を行ってください。
ファイルの中身が更新され、最新のコミットのハッシュ値とやはり同じものになっているのが確認できます。
次に、控えておいたハッシュ値で .git/refs/heads/master の内容を書き換え、git log -1 --oneline を実行してみてください。
さっき行ったコミットではなく、ひとつ前のコミットのハッシュ値に戻ります。
このように、Git ではブランチは単なるハッシュ値を格納したファイルとして実装されているため、ブランチの作成や名前の変更、削除といった操作は非常に高速に実行できます。
ちなみに、タグは .git/refs/tags/ 以下に格納されているため、同じ名前のブランチとタグは同居可能です (紛らわしいのでやめた方がいいですが)。
git-completionの話、__git_ps1の話
git-completion が有効になっていると、git のサブコマンドやブランチ名やその他もろもろを、tab でいい感じに補完してくれます。
さらに、__git_ps1 という現在の working tree の状態が取得できる関数を PS1 に設定することで、現在チェックアウトしているブランチ名が常に確認できます。
__git_ps1 は現在チェックアウトしているブランチ名の確認だけでなく、たとえば rebase 時に conflict が発生した場合、(ブランチ名|REBASE) のような表記でそれを教えてくれるという機能もあります。
zsh の場合は id:clairvy (くらなんとかさん) がなんとかしてくれるので頼りにしましょう。
勉強会では Windows ユーザは TortoiseGit を使っていましたが、msysgit はデフォルト状態で git-completion が有効になっており、しかも __git_ps1 もいい感じに設定されているため、正直 TortoiseGit 程度なら使わない方がいいと思います。
TortoiseGit って、Tortoise って名前を関しているというだけでちょっと使われすぎじゃないですかね。
まぁ全員にコマンド使わせるのは無理としても、他にもましな選択肢はあると思うんですよね・・・
おっと、TortoiseGit 大嫌いなのでこの辺の話はかなりバイアスかかってるということでひとつ。
コミットの指定の話
リビジョン番号を持たない Git では、あるコミットを指定するためにいくつかの方法があります。
- ハッシュ値を直接指定する
- タグ名を指定する
- ブランチ名を指定する
- 相対的に指定する
などです。
この中で、相対的な指定は記号や数字が合わさって、はじめのうちは取っ付きにくいものですが、便利なのでぜひ覚えておきましょう。
相対的な指定でよく使うものに、^n (n 番目の親) と ~n (n 世代前の親) があります。
これはどちらもあるコミットの「前」のコミットを指すため、違いが分かりにくいですが、図にするとわかりやすいです。
この図でわかるように、^n による指定は親がいくつか存在する場合に親を選択するという状況で使用するため、merge ではなく rebase を多用するような場合、あまり使用することはないでしょう。
逆に、~n による指定は、簡単に前のコミットを選択できるため、使用する頻度は高くなります。
これらは組み合わせることもでき、たとえば master^2~3とすれば、master の二番目の親を選択し、そこからさらに三世代さかのぼる、ということも可能です。
また、n が 1 の場合省略することができるので、master^^^ と、master~~~、master~3 はすべて同じコミットを指します。
ちなみに、よく HEAD^ という記述を見かけますが、HEAD は現在チェックアウトしているブランチを指すので、現在のひとつ前、ということになります。
ここで ~ ではなく ^ を使うことが多いのは、Shift キーを押さなくても入力できるからだと勝手に思っています。US 配列とかでどうなってるのかは知らないです。
resetの話
reset コマンドはファイルを個別に指定する reset と、コミットを指定する reset の 2 つがあります。
コミットを作り上げるために使用する、ファイルを指定する reset コマンドは、add の逆操作と覚えておけばいいでしょう。
index に stage するために使用する add、index から unstage するために使用する reset です。
コミットを指定する reset は、主に歴史の修正やブランチを移動させるために使用します。
その際、soft/mixed/hard といったオプションが選べます (デフォルトは mixed) が、これらは以下のような違いがあります。
- soft
- リポジトリの状態のみを対象に reset する
- mixed
- リポジトリの状態と、index の状態を対象に reset する
- hard
- リポジトリの状態、index の状態、working tree のすべてを reset する
このような違いがあるため、各オプションは以下のように使い分けると良いでしょう。
- soft
- bare リポジトリでの操作のために使用する
- mixed
- 歴史の修正のために使用する
- hard
- ブランチを移動させるために使用する
ここで「ブランチを移動させる」と言っているのは、ブランチの話でやったような操作をコマンドで安全に行っているだけです。
そして「リセット」と言う語感に反して、そのブランチが指すコミットを書き換えるだけですので、現在のコミットよりも時系列的に後のコミットに reset することも可能です。
あくまで、re(再び) + set(設定する) のであって、リセットボタンのような融通の利かない操作ではない、という点には注意してください。
rebase と merge の話
なんか当日は「merge よりまずは rebase ですよ!」みたいなノリで話したんですけど、ここ完全に個人的な意見なので、その辺はよろしくお願いします。
rebase の欠点として「遅い」ってのは言ったのですが、もう一つ忘れてました。
rebase した結果、先頭のコミットに関しては正しく動いていても、途中のコミットが壊れてしまうという可能性があることです。
まぁ、その辺は tag あたりを利用して、「うまく動くという確証がある点」を明示しておけばそれほど問題ではないんじゃないかなぁ、とか思わなくもありません。
公開したものの rebase の話
公開したものを rebase してはいけないよ、という話はしたんですが、あんまりちゃんと説明できなかったので説明しようと思ったら、Pro Git にわかりやすくまとめられていたのでこちらをどうぞ。
ただし、開発メンバーが少数で、かつ声が十分に行き届くような開発現場の場合、このような混乱を生じさせずに済ませる方法もあります。
が、特殊なケースなのでその話はまた機会があれば。いやまぁ単純に bare リポジトリを reset してみんな取得し直す、というだけのものですが。
stash の話
最初に言っておくと、stash は好きじゃないです。
便利なのは認めるのですが、失敗した場合の復旧手段が全然覚えられない上に面倒という点で、あまり使いたくはない。
詳しい復旧方法は、
git stash save で一時退避した変更を、誤って git stash clear で消してしまったときの回復法 - t-wadaの日記
に丁寧でわかりやすい解説があるので、そちらを参考にしてください。
で、ですよ。stash です。stash を使う場面というのは、「とりあえずやった作業を置いておいて、違う作業がやりたい!」という場合がほとんどだと思います。
そしてここで stash を使うわけですが、ちょっと待ってください。それ、とりあえず commit しておきませんか?
commit しておけば、よほどのことがない限り作業を失うことはないですし、よほどのことが起こった場合は stash を使っていた場合でも作業は無に帰すでしょう (よほどのこと・・・リポジトリを間違って消しちゃうとか)。
まだ Git に慣れないうちは stash は手軽ですし使えばいいと思うのですが、rebase や reset を使いこなせるようになったら、「この作業消えると痛いなぁ」と思う場合は stash ではなく commit しておき、後で reset や commit --amend して一時的なコミットがあったという歴史を消してしまえばいいのです。
この方法だと、何か操作をミスした場合でも、いつものツールをいつものように使うだけで復旧が可能になります。
TortoiseGit、HG、SVN の話
大嫌いな TortoiseGit の話です。
懇親会であった話なんですが、TortoiseHG は TortoiseSVN の使い勝手からある程度離れ、独自の進化をたどっているそうです。素晴らしいですね。
今回久しぶりに TortoiseGit を触った (というほどではなく、見てただけですが) んですけど、TortoiseGit は TortoiseSVN の使い勝手をそのままにしようという方向性らしく、ダメなままですね。
Git をきちんと使いたいなら、TortoiseGit ではダメです。コマンドで使うか、GitExtensions などの別のフロントエンドを使いましょう。
全体を通して
今更と言えば今更なんですけど、非プログラマな方にはさっぱりな説明をしてしまったかな、というのが今回の最大の反省点です。ごめんなさい。
プログラマな人には、中身の話した方がたぶん分かりやすいと思って結構ディープな話とかもしてしまったんですけど、そうじゃない人を「何言ってんだこいつ」状態にしてしまったんじゃないかと思うと・・・
もうちょっとこう、中身の話しなくても説明できるようにならないとなぁ、と痛感しつつ、中身の話バリバリなエントリ書いてる時点で先はまだまだ見えない感じです。
それと、いろいろと深くまで突っ込んだせいで Github 部分全然やれなかったという点も反省ですね・・・
Git の中身に近い話
あるプロジェクトに新しく人が増えた時に参照する用にまとめておきます。
Git の基本的なコマンドを理解していることが前提条件です。
SHA-1
Git は扱うオブジェクトを全て 160bit の SHA-1 ハッシュ値により識別しています。
これにより、Git は各種操作を高速に行えるようになっています。
また、オブジェクトがハッシュ値により識別されるため、オブジェクトの内容が変ればこの値も新たに計算されるため、全てのオブジェクトをイミュータブルに扱うことが出来る、ということでもあります。
Git のオブジェクト
Git には 4 つのオブジェクトがあります。
- blob オブジェクト
- tree オブジェクト
- commit オブジェクト
- tag オブジェクト
この 4 つのオブジェクトが組み合わさって、Git のリポジトリが構成されています。
blob オブジェクト
blob オブジェクトは、ファイルの内容のみを保持するオブジェクトです。
git add によってリポジトリに作成されます。
blob オブジェクトはファイルの内容しか保持していないため、blob オブジェクトのハッシュ値はファイル名やモードなどに左右されません。
tree オブジェクト
tree オブジェクトは、ディレクトリを表し、格納している blob オブジェクトと tree オブジェクトの情報を保持しています。
また、blob オブジェクトに対応するファイル名やモードもこのオブジェクト内に格納されています。
ファイルモード、名前、blob/tree オブジェクトの SHA-1 ハッシュ値の順番で格納されています。
tree オブジェクトのハッシュ値は、含まれる blob/tree オブジェクトが変ったり、ファイル名やモードが変ったりすると変ります。
commit オブジェクト
commit オブジェクトは、コミット時の情報を保持するオブジェクトです。
主に、git commit コマンドによって作成されます。
commit オブジェクトには、作業ツリーの最上位の tree オブジェクトのハッシュ値と、作成者の情報、作成日時、コミッタの情報、コミット日時、コミットメッセージが格納されています。
また、一番最初の commit オブジェクト以外は親の commit オブジェクトのハッシュ値も保持しています。
commit オブジェクトはコミット日時を含んでいるため、内容が同じだったとしても毎回異なるハッシュ値となります。
作成者とコミッタ、作成日時とコミット日時のように一見同じに見える項目があり、またほとんどの場合で同じ情報が入るのですが、両者には違いがあります。
作成者、作成日時は、そのコミットに含まれる blob オブジェクトや tree オブジェクトを作成した人・日時が入り、コミッタ、コミット日時にはそれを元にリポジトリに commit オブジェクトを作成した人・日時が入ります。
例えば、送られてきたパッチから commit オブジェクトが作られる場合や、他人のリポジトリから持ってきたものを rebase する場合などで、これらの情報に違いが出てきます。
tag オブジェクト
tag オブジェクトは、アノテーション付のタグの情報を保持するオブジェクトです。
git tag -a や git tag -m によって作成されます。
通常の git tag コマンドでは「軽量タグ」と呼ばれるタグが作成され、tag オブジェクトは作成されません。
Git Extensions を一部日本語化しました
github に置いたので、お好きにどうぞ*1。
日本語化したのは、
- Browse 画面 (最初の画面)
- コミット画面
- ブランチ作成画面
- 簡易ブランチ作成画面
- ブランチ削除画面
- ブランチマージ画面
- pull 画面
- push 画面
- ブランチ移動画面 (ブランチの reset)
- 競合解決画面
- 検証画面 (git fsck)
と、
- ファイルビューア
- リビジョングリッド
です。
clone するなり、Download Source から zip とか tar で落とすなりして、ビルドして*2、GitCommands\GitExtensions の下に出来る .exe ファイルと .dll ファイルと ja フォルダを Git Extensions のインストールフォルダに上書きすれば日本語化されます。
とりあえず、翻訳は細々と続けるとして、他に
- Ctrl + Enter でコミットメッセージの入力を完了する機能
- rebase --onto などの実装されてない機能
を追加する予定です。あくまで予定ですが。
Hudson の Git Plugin を使うと文字化けする問題とその解決方法 (不完全)
こちらも仕事で Hudson と Git を使い始めた頃から気付いたんですが、ちょうどいい機会なので直してみます。
文字化けするのは、Hudson の Web 画面から確認できるコミットメッセージです。
始める前に
ここで紹介する方法は、プラグインのクラスファイルの一部を入れ替える方法です。
あくまでその場しのぎの解決方法であることを理解したうえで、この方法を実行する場合は自己責任でお願いします。
調査
まず、本来あるべきコードと、認識されているコードを調査しました。
上のページによると、UTF-8 なのに Shift_JIS として認識しているようです。
Git でのデフォルトのコミットメッセージは UTF-8 なので、これを読み込む際に間違ったエンコード方式を指定している可能性が濃厚です。
原因
文字化けの原因は、GitChangeLogParser クラスでファイルの読み込みに FileReader クラスを使っていることでした。
FileReader クラスは手軽にファイル読み込みができますが、読み込む際のエンコーディングは指定できず、常にシステムのデフォルトエンコーディングが使用されます。
このため、Windows で Hudson を動かし、さらに Git まで使っているという変態構成だと、ファイルのエンコーディングが UTF-8 であっても MS932 を使ってしまい、文字化けが発生していました。
これを回避するためには、i18n.commitencoding を Shift_JIS にすることも考えられるのですが、すでに utf-8 で運用しているため、できればこれは避けたいです。
GitChangeLogParser.java の修正
本来なら、リポジトリのコミットメッセージのエンコーディング方式を設定出来るように修正するのが一番なのですが、Hudson で Plugin を作ったことがないため、手っ取り早い方法を取りました。
自分が使っているリポジトリの i18n.commitencoding はすべてデフォルト状態、つまり utf-8 なので、FileReader を使わず、InputStreamReader と FileInputStream を組み合わせて utf-8 で読み込むようにしました。
BufferedReader rdr = null; try { rdr = new BufferedReader(new InputStreamReader(new FileInputStream(changelogFile), "utf-8")); ... } finally { if (rdr != null) rdr.close(); }
try の外側で BufferedReader と FileReader を new していた部分も、try の中で行うように変更しています。
また、import も変更しています。
後はこれをコンパイルして Hudson をインストールした場所にある plugins/git/WEB-INF/classes/hudson/plugins/git に生成された .class ファイルをコピーすればいい・・・のですが、NUnit Plugin の場合と違ってこちらは hudson.model.AbstractBuild と hudson.scm.ChangeLogParser というクラスを使用しているので、その辺もよろしくやってあげる必要があります。
使用している hudson.war から hudson-core-バージョン.jar を取り出し、src\main\java に置きます。
javac -cp hudson-core-1.355.jar hudson\plugins\git\GitChangeSet.java hudson\plugins\git\GitChangeSetList.java hudson\plugins\git\GitChangeLogParser.java
とするとコンパイルできました*3。
これで、コミットメッセージが文字化けせずに表示されるようにな・・・ると思ったのですが、微妙に化けてしまいます。
jobs フォルダの中の「プロジェクト名\builds\ビルド日時\changelog.xml」を utf-8 で開いてみると、どこかで変換にミスっているのか、? という文字が目立ちます。
この changelog.xml をどこで出力しているのかわからないので、これ以上は分かりませんでした*4。
コマンドの数
Git はコマンドが多すぎてどれから手を付けて良いか分からない!って理由で Mercurial や他の DVCS に流れてしまう人はある程度いるんじゃないかと思います。
実際、そういうエントリをいくつか見たことがあります。
確かに、git help -a とすると、楽勝で 100 個を越えるコマンドが表示されます。確か 138 個だったと思います。
でも、git help と打ってみてください。最新版の 1.7.0.1 でも 21 個しか出てきません。Git を普通に使う分にはその程度のコマンドを理解していればいいのです。
このように、オプションを付けなければ Git 開発者がよく使うコマンドと判断した 21 個が分かるのです。とりあえずはこの 21 個からはじめればいい、という指針になります。
では、他のバージョン管理システムはどうなのか?というのが気になって調べてみました。
VCS | help で表示されるコマンド数 |
---|---|
Git | 21 |
Mercurial | 87 |
Bazaar | 14 だけど実質 12 |
Monotone | 13 だけど実質 33 |
Darcs | 33 |
Subversion | 33 |
CVS | 32 |
Bazaar が実質の個数が少ないのは、bzr help の説明が 3 つに分かれているからで、
Monotone が実質の個数が多いのは、コマンドの前にオプションについての説明も入っているからです。
コマンドの実質の個数を並び替えると、
Bazaar < Git < CVS < Darcs/Subversion < Monotone < Mercurial
となります。ヘルプという点では Bazaar が非常に優秀です。短くまとめられているだけでなく、グループ化も行われており、非常に分かりやすいです*1。
それに対して Mercurial は、すべてのコマンドを表示してしまっており、どれからはじめればいいのかここからは全然分かりません。
Mercurial ユーザからすると、「最初に見るのはそこじゃない!」という反論があるかもしれませんが、それだったら Git だって「コマンド数全体じゃなくて最初にこの 21 個のコマンドをやればいい!」となりますよね。
なので、Git がコマンド数が多いことを理由に避けられるというのは非常に悲しいものがあります。
これからはこの 21 と言う数字を押し出していけば、こういう事態が避けられるような気がします。
また、コマンドが多いことは問題になるかというと、むしろ便利なことの方が多いと感じています。
例えば、あるコミットでの変更のみを他のブランチにも適用したい場合に、Git では cherry-pick というコマンドが使用できます。
$ git cherry-pick bug/123
とするだけで、bug/123 での修正を現在のブランチに対して適用することができるのです。
これを例えば Subversion でやろうと思ったら、
$ svn merge -c 2137 file:///path/to/trunk特定のリビジョンだけマージ(Cherry Picking)
このようになります。
さて、どちらが分かりやすいでしょうか?
Subversion との比較で言えば、ブランチの作成や切り替えでも同じようなことが言えます。
コマンドが多いことは、慣れてしまえばむしろ武器になりうるのです。
*1:ただし、pull や push と言った DVCS の特徴であるコマンドが一覧にありませんが・・・まぁ、Bazaar は DVCS としても集中管理型としても使えるので、こういう判断もアリだと思います