hatena

<body>
*1323766254*gitをテキトーに使って生産性を向上したユースケース
バージョン管理とかgitとかが「おおげさでめんどくさいもの」だと思う人は多い。でも、それは生産性向上のチャンスを逃していると思う。特に業務として多人数で開発している人たちの「変更前にはまずトピックブランチ」というやり方が、それはそれでよい方法なんだけど、いかにもめんどくさそうで尻込みさせてしまうのではないか。

先日の日曜日に、テキトーなgitの使い方をして、とても役に立ったのでユースケースとして報告しておこう。ただし、若干特殊な環境なのでここでやった方法が直接そのまま皆さんの所で使えるとは限らないが。

まず環境の説明。プロジェクトは「<a href='http://d.hatena.ne.jp/nishiohirokazu/20111211/1323533985'>次の日曜日、新感覚シューティングゲームを展示します</a>」で紹介している、テーブル型ディスプレイで動くシューティングゲーム。メインは @tokoroten で、ソースコードをバリバリ変更している。土曜日にとりあえず動くところまでは行った。改善点は山積みだ。使える時間は7時間。僕のMUSTな作業は終わったので、僕もゲームの実装を手伝いたい。テーブル型ディスプレイはWindowsマシンにつながっていて、ファイル共有でLAN内のマシンからファイルをいじれる。僕のマシンはMacBook Air。実機テストが必要かつ実機がでかいので、相手の作業が一段落ついたかどうかが振る舞いで感じ取れるのも重要なポイントだったかも。

<iframe width="560" height="315" src="http://www.youtube.com/embed/OF0gX2JNIDM" frameborder="0" allowfullscreen></iframe>


いちばん初期コストが安い方法は「ソースツリーを自分用にまるごとコピーしてそれをいじる」だよね。でもそれをやった場合、あとでどうやってマージするか考えると辛い。「同じソースを同時にいじらないように相手が今何をいじろうとしているか把握しないといけない」「あるファイルをいじりたいけども、相手も同じファイルをいじっているので待機」「それでもやっぱり同じファイルをいじってしまって、仕方がないのでdiff取ったりして頑張ってマージ」「そしてマージ失敗して修正したバグが復活」という地獄が待ってることが予想できてしまう。それを気合で何とかするとかやりたくないなーというのが僕の心情。

そして僕が到着した時tokorotenはまだ来ていなかった。うむ、そうだ、閃いた。git化してしまおう。

** 相手のソースツリーを勝手にgitリポジトリ化

tokorotenのソースツリーの中で git init すると、そこに.gitというフォルダが作られてリポジトリになる。 git status してどういうファイルがあるか確認。.svnがあったから.gitignoreで無視することにする。

>||
pattern: glob
*.pyc
.svn
||<

プロジェクトに必要そうなファイルを全部 git add してインデックスに登録し、git commit する。これでgitリポジトリを介してソースコードを共有する準備は完了。大して時間は掛からない。

なお、この作業は僕のMacBookAirからsamba経由で行ったので、gitのインストールとか設定とかは何もしていない。それが楽チンな理由の一つかもしれない。

コマンドを書いて置く。t$と書いてある場合はtokorotenのワークツリーの中で作業していることを表現している。
>||
t$ git init
t$ git status
t$ emacs .gitignore
t$ git add .
t$ git commit
||<

** 自分用のリポジトリを作成

次は僕がソースコードをいじって機能追加をするために自分用のリポジトリを作る。というと大仰だがshared/tokoroten/TreFesってのが先ほどgitリポジトリ化したソースツリーなので僕は自分のshared/nishioディレクトリの中で git clone ../tokoroten/TreFes とかやるだけ。これで nishio/TreFes の中にはあとはそこのソースコードを好きにいじるだけ。自分のワークツリーの中にいることをn$で表現する。

>||
$ git clone ../tokoroten/TreFes
$ cd TreFes
n$ ...
||<

で、ソースコードをガリガリいじって、動作テストして、うん、うまく動いた、一段落ついた、という時にコミットをする。まずは git status で何が変更されてるかを見る。 git add (filename) で変更されたファイルを丸ごとインデックスに登録してもいいし、個人的には git add -p して変更箇所ごとに登録するのが好き。うっかり消し忘れのデバッグ出力とかをコミットしたくないし。

>||
n$ git add -p
n$ git commit
||<

勝手に機能追加を始めていたところtokoroten登場。

** 相手の切りの良いタイミングを見極めてコミット

tokorotenにgitの操作を教えるかそのままやってもらうか考えた結果、時間も限られているから僕がやればいいやという結論になった。tokorotenが切りの良いタイミングで、彼の行った変更を僕がcommitする。っていってもsambaでファイル共有されているので、彼のワークツリーにcdして、細かいことは気にせずに git status で表示される変更されたり追加されたりしたファイルを git add して git commit するだけ。

>||
t$ git status
t$ git add <files>
t$ git commit
||<

** 相手の変更を自分のリポジトリに取り込む
次に自分のリポジトリでtokorotenの行った変更を git pull する。これで相手のリポジトリにコミットされている変更箇所のデータが自分のリポジトリに運ばれて、かつワークツリーのファイルとマージされる。自分の未コミットの変更とかがあると怒られる気がする。(あー、そうか、自分がトピックブランチ切って作業しているなら気にせずマージできるのかな、気づかなかった) 現場では最初に1回それで怒られて、コミットしてからpullしなおしたけど、それ以降は問題にならなかった。相手の作業完了に気づく時って、自分の作業も一段落ついるんだろうな。

pullすると自動でmergeも行われる。その結果「自動で問題なくマージできたよ!」って言われる場合もあるし「このファイルはできなかったから手動でマージしてね」って言われる(コンフリクト)場合もある。1日で31件のコミットがあって、コンフリクトは6回だった。うち1件だけ「ちょっとこれどういう意図?」って自然言語でのコミュニケーションが必要になったけど、残りはパッと見てすぐ直せるものだった。直した場合はそのファイルを git add して git commit する必要がある。

さて、これでtokorotenの行った変更を僕のリポジトリに反映させる作業が終わった。

>||
n$ git pull
コンフリクトしたファイルを編集
n$ git add <コンフリクトしたファイル>
n$ git commit
||<

** こちらの変更を相手のリポジトリにつっこむ
大体において前の章の作業はあっという間に終わるので、相手がソースをいじっていないすきにこちらの変更を相手に送り込む。今回は間にbareリポジトリがないのでpushはできない。相手のリポジトリで git pull することになる。

>||
t$ git pull ../../nishio/TreFes
||<

これで終了。

ただし、相手がすでにコードをいじっているとこのpullの時に、未コミット変更のせいでマージ出来なかったりする可能性があるね。現場では問題にならなかった。多分作業が一段落ついたら作業の内容を話したりトイレに行ったりお茶を飲んだりしているからだろう。一回だけコンフリクト修正に手間取っている間にtokorotenがソースをいじってたことがあって、その時は「どこいじってる?」と聞いて、いじっていたファイルがこれから送り込む内容とかぶってないのでGOした。

あと、これを書いていて気づいたんだけども、相手が「開いているファイルを書きこみロックするエディタ」とか「開いているファイルが更新されても気づかないエディタ」だと問題が起きそうね。特に後者とか pull で変更されていることに気づかずに上書き保存したりして…。そんなエディタ使うなよ、という結論でいいかな?

** まとめ

こんな感じで日曜日の午後を使ってシューティングゲームを作ったのだけど、14時から21時の7時間で31コミットが行われるくらいの速度感で、コンフリクトは6回。うち直すのに頭を使うコンフリクトは1回だけ。ちょうど同時にプレイヤーが玉を打つ部分のコードをいじったのでな。二人が同一のファイルをいじっていたケースは数えきれず…っていうかファイルがtrefes.pyとかplayer.pyとかの粒度なので当然そうなるよね。

もし「同一のファイルをいじったら後でマージ大変だな」とか「同じファイルをいじらないようにしよう」という気持ちがあったら、どれだけ機能追加のモチベーションを損ねたことだろうか。多分僕はダラダラしながら「こうしたらいいんじゃない?このコードいじればできるんじゃない?」なんて口を挟むだけで自分でコードを書こうとしなかったんじゃないだろうか?

ちなみにその7時間で僕が行った作業をコミットログから掘り起こしてみると:
- キャラによって弾の色を変更
- シナリオをデータファイルだけでなくスクリプトで指定できるように変更
- 弾が壁で反射するようにした
- 起動用のbatファイル作成
- 画面端との衝突判定
- enemyを複数種類にできるようリファクタリング
- まどかの弾を散弾銃にした
- 前方からの弾を反射する敵の追加
- 反射周りのリファクタリング
- ほむらの弾種変更

弾の反射みたいな結構大きな変更が安心して実装できたってのもメリットだけども、弾の色の変更とかそういう些細なタスクをメインプログラマに積まずに周りがさくっと片付けることで、メインプログラマにはもっと重要な作業に専念してもらうってところがチームとしての生産性を考えた場合に重要だったのではないか。僕がこういう細かいことをやっている間にtokorotenはゲームの状態遷移とかボスの出現とか効果音とか、ゲームの全体に関わることをしていたので。

そういうわけで自分の生産性を高めるために、チームの他のメンバがgitを使っているかどうかとは無関係に、自分がgitを使えることにはメリットがある。ブランチを切りすらしていないテキトーな使い方でも、使わないよりは明らかに優れている。
</body>
<comments>
<comment>
<username>o_show</username>
<body>「こちらの変更を相手のリポジトリにつっこむ」の節で、<br><br>>今回は間にbareリポジトリがないのでpushはできない。相手のリポジトリで git pull することになる。<br><br>とありますが、shared/tokoroten/TreFes の方がclone元なので、<br>そのままでは shared/tokoroten/TreFesの方からは<br>git pull はできないはずだから、remoteを設定したんですよね?<br>せっかくの良いチュートリアルなのでそのコマンドも補完してもらえないでしょうか。<br><br>t$ git remote add nishio ../../nishio/TreFes<br><br>↑多分、こんな感じでやったのではと思うのですが。</body>
<timestamp>1323773028</timestamp>
</comment>
<comment>
<username>nishiohirokazu</username>
<body>まさにそのとおりのことをしていました!<br>でもまあ、remote addはこの場合必須ではないので、説明することを増やして混乱させないように、<br>リポジトリを指定してpullする方法に修正しておきました。<br>- git pull<br>+ git pull ../../nishio/TreFes</body>
<timestamp>1323773487</timestamp>
</comment>
<comment>
<username>o_show</username>
<body>おお、なるほどその場でリポジトリを指定して pull も可能ですね。<br>remoteを説明しなくていいというのは大きいかも。<br><br>こういう「頑張り過ぎない」例でもGitは有効ってのが<br>よくわかるいい記事だと思います。ユースケースの公開ありがとうございました。</body>
<timestamp>1323774061</timestamp>
</comment>
</comments>

はてなダイアリー 2011-12-13