ラベル git の投稿を表示しています。 すべての投稿を表示
ラベル git の投稿を表示しています。 すべての投稿を表示

2014年8月8日金曜日

gitコミットとRedmineチケットは1対1にする

ソフトウェア開発のプロジェクトでgit(あるいはSubversion)を使っていない方が珍しいと思います。と同時に、極めてよく使われるツールがRedmineなどのプロジェクト管理ツールですね。

昔はバグトラッキングシステム(BTS)と呼ばれていましたが、バグの管理はもちろんのこと、プロジェクトのタスク管理につかうシステムとしても非常に優れており、広く使われるようになったことから最近では "プロジェクト管理ソフトウェア" などと呼ばれているようです。BTSに変わる決まった呼び方があるのかどうかはよく知りません。

ともあれ、git と redmine を連携させて使っているプロジェクトも多いことでしょう。Redmine 上のプロジェクトには git のリポジトリを関連付けることができ、gitのコミットメッセージにチケット番号を特定の書式で書くと、自動的にRedmine上のチケットとgitのコミットが関連付けされます。チケットの表示画面にgitコミットの概要とリンクが表示され、またコミットの表示画面ではチケットへのリンクが表示されます。これは後からソースコードの変更を確認したいときに、タスクの記録と一緒に追うことができて大変便利です。

ところで、1つのチケットに複数のコミットを関連付けることもできます。また逆に1つのコミットに複数のチケットを関連付けることもできます。ただし、僕の意見としては、極力チケットとコミットを1対1に対応させると、後から過去の変更を調べる際にすごく楽になります。(当然完全には無理でしょうから、出来る限りということです。)

例えば、1つのコミットに複数のチケットに関する変更を入れてしまうと、それらの変更がそれぞれどのチケットに関するものか判別するのは、変更した本人でない限り結構手間がかかるものです。あるいは、1つのチケットの解決に要した変更が複数のコミットにまたがっていると、これまたどんな変更がそのチケットに関連してなされたのかの結論を確認するのに骨が折れます。

だから、チケットとコミットを1対1に対応させるのがよいのです。もちろん、自分の手元でコミットを1回きりで済ませるのは大変だと思いますので、例えばローカル専用ブランチを作ってそこにコミットし、1つのチケットの分がまとまったら提出用ブランチにmerge --squash でまとめるといった方法をとればよいと思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年7月25日金曜日

git diffって多用するよね

git diffは非常によく使うコマンドの1つで、僕の仕事のクオリティを支えてくれているツールでもあります。また、difftoolにvimdiffを用いたgit difftoolも同じくらいよく使います。

僕はかなりこまめにgit diffを使います。プログラミングをしていると、ちょっと書いたらgit diffで差分を見て、動作確認し、またちょっと書く、みたいな感じです。差分を確認せずに一気に書くことはほとんどないです。

あとコミット前にはインデックスとHEADの差分をgit diffとgit difftoolの両方で確認します。ここで原則として差分については全て余すところなく確認します。

あと、ローカルブランチのコミットをgit merge --squashでまとめて提出用ブランチにした際も、提出用ブランチの直前との差分を確認しますが、この際はざっと目を通す感じですね。

こんな感じです。実に利用シーンは多いです。ちなみに差分アルゴリズムはhistogramがおすすめです。difftoolにvimdiffを用いる場合に、差分作成アルゴリズムを変更する方法については過去の記事を参照。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年7月13日日曜日

gitのpush前にmerge --squashとpullとrebaseを

gitでどうブランチを切ってコミット・プッシュするかの運用方法については様々な意見というか流派(?)があるように思います。僕自身も複数人数のチームで開発を実際に行う中で、こうしたらいいんじゃないかって思っている方法を書いてみようと思います。

まず開発のタスク管理にはredmineを使っている、という背景があります。gitの共有リポジトリはredmineと連携させていて、共有リポジトリにプッシュしたコミットのメッセージにチケット番号を記載しておくと、自動的にredmineのチケットからリポジトリビューアにリンクされるようになっています。そのような背景から、チケットごとにコミットを1つにまとめています。

さらに、共有リポジトリへプッシュする際は、自分の名前が付けられたブランチにプッシュすることにしています。(masterへのマージはmasterブランチ管理者以外は行わない。)例えば僕はkasaiという名前のブランチを作成して、そこへ自分が担当するチケットを解決するコミットを行い、それを共有リポジトリへプッシュします。

masterブランチ管理者は、共有リポジトリへプッシュされた他の担当者のブランチをローカルリポジトリへプルしてmasterへのマージを行い、改めて共有リポジトリへプッシュします。(つまり、共有リポジトリのmasterへのプッシュを行えるのはmasterブランチ管理者だけです。)

この辺までは結構よくある運用方法でしょうね。ここまで全体的な流れの話を書きましたが、次に、各担当者が個人的に行う運用方法についてです。

僕の場合、ローカルリポジトリにはローカル用ブランチを作って作業しています。ローカル用ブランチは自分のためだけのブランチで、一度行った変更を後のコミットで元に戻したり、試行錯誤の汚い履歴が混在していて、そのまま共有リポジトリにプッシュするなんて考えられない代物です。ちなみに僕はローカル用ブランチには local/yy.mm.dd という名前を付けています(yy.mm.ddは作成時の日付、1日に複数作成した場合はyy.mm.dd-2などとする)。

とういことで、ローカル用ブランチで試行錯誤して、完成したら提出用ブランチ(前述した例ではkasai)にまとめて取り込むという方法を採っています。その際にはmerge --squashを使います。

なお、提出用ブランチにmerge --squashされた後のローカル用ブランチは消してもいいのですが、バックアップの意味もあるし、コードが動かなくなった際に履歴が残っていると原因究明にも役立ったり、元に戻したりできるので残しておくことにしています。

では、提出用ブランチをこのままpushすればいいかというと、ちょっと考えるべき問題があります。pushする前に、共有リポジトリのmasterに、自分が元にした時点から変更がいくつも加わっている場合、コミット内容がmasterと競合しないにもかかわらず、仕様変更によりコードが動かなくなってしまう可能性があります。例えば、他人の担当範囲で定義されていた関数やメソッドを呼び出してコードを書いたが、自分がローカルで作業している間に、その関数やメソッドの挙動を変えるコミットがmasterに既にマージされていた、といった場合です。(この問題に関してはGitユーザマニュアルの「マージコミットの分割が1本線の履歴の分割よりも困難となる理由」の解説が分かりやすいです。)

だから、push直前に、最新のmasterを改めてpullで取得し、その最新のmasterにこれからpushしようとする変更を統合した状態で動作することを確認しておきたいんですね。そのためにrebaseを行ます。つまりpush直前に、自分のブランチをpullで取得した最新のmaster上にrebaseするのです。

こうすることで、上記のようにmaster側に知らないうちに仕様変更が加わっていて、競合が発生しないにもかかわらずコードが動かなくなる、という事態に陥る危険性を(ゼロではないにしても)かなり減らすことができます。また、もしそのような事態になったとしても、rebaseしておくことで原因の究明はぐっとやりやすくなります(当然ながら、これはpush前のコミットだからrebaseしてもいいのであって、たとえ提出用ブランチでも、一旦pushした後のコミットは決してrebaseしてはいけません)。

まとめると、こういう流れになります。
  1. ローカル用ブランチを作成し開発を行う。コミットは適宜好きなように行う。
  2. 提出用ブランチにてmerge --squash でローカル用ブランチの変更をまとめて取り込む。まとめる単位はredmineのチケット単位とする。
  3. masterブランチにてpullで共有リポジトリの最新の状態を取り込む。
  4. 提出用ブランチにてrebaseで変更を最新のmaster上に移し替える。
  5. rebaseされた提出用ブランチにて共有リポジトリへpushする。
  6. (masterブランチ管理者のみ) 各担当者の提出用ブランチブランチをpullし、ローカルでmasterへのマージを行い、共有リポジトリへpushする。
これが今のところいいんじゃないかと思っている方法です。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年7月8日火曜日

vimdiffでより賢いアルゴリズム(patience,histogram)を使う

vimdiff使ってますか?差分を取る際には非常に便利ですよね。git difftoolに設定して使っている人も多いと思います。しかしgit diffは差分計算のアルゴリズムを選択できますが、vimdiffはデフォルトでは差分計算のアルゴリズムを選択できません。

git diffの差分アルゴリズムには標準のもの以外にpatienceやhistogramがあり、より人間に読みやすい差分を表示してくれる「賢い」アルゴリズムになっています。それぞれgit diffコマンドのオプション--patienceや--histogramを付けるか、または~/.gitconfig設定ファイルにアルゴリズムを指定することで利用できます。

具体的なアルゴリズムの詳細は僕も詳しくないですが、patienceアルゴリズムは「ファイル内でユニークかつ比較ファイル同士で一致する行をなるべく"変化していない行"と認識する」よう働きます。また、histogramアルゴリズムは「patienceアルゴリズムの基本的ルールを継承しつつ高速化を図ったアルゴリズム」で、多くのファイルでpatienceアルゴリズムと同一の結果が得られるようです。また、histogramアルゴリズムはEclipseに統合されたGitクライアントEGit(が使うJGit)の標準アルゴリズムです。僕はコマンドラインツールのgitにおいてもhistogramアルゴリズムを使ってます。

さて、vimdiffにおいてもhistogramアルゴリズムを使いたいところです。vimの設定ではvimdiffの差分計算に使用するコマンドをdiffexprという設定値で指定できます。そして、git diffコマンドも--no-indexオプションを指定することでdiffコマンドと同じようにスタンドアロンツールとして利用できます。じゃあ、diffexprにgit diff --no-indexを指定すればいいのか・・・というと実はそれだけでは失敗します。

理由は、vimがdiffexprに設定したコマンドから受け付ける差分形式が、diffコマンドのnormal形式かed形式でなければならないからです(vimのヘルプにはed形式とだけ書いてあるのですが、normal形式も受け付けます)。そして、git diffコマンドでは直接diffコマンドのnormal形式やed形式で出力することができずunified形式のみに対応しています。

そこで、unified形式をnormal形式に変換するrubyスクリプトを書いて使うことにしました。以下がその変換スクリプトです。利用にはrubyが必要です。

#!/usr/bin/env ruby
iwhite = ''
if (ARGV[0] == '-b')
  iwhite = ARGV.shift.dup << ' '
end

diffout = `git diff --no-index --no-color -U0 #{iwhite}#{ARGV[0]} #{ARGV[1]}`
diffout.sub!(/\A.*?@@/m, '@@')
diffout.gsub!(/^\+/, '> ')
diffout.gsub!(/^-/, '< ')
diffout.gsub!(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*/) do
  before_start = $1
  before_size = $2
  after_start = $3
  after_size = $4

  action = 'c'
  if (before_size == '0')
    action = 'a'
  elsif (after_size == '0')
    action = 'd'
  end

  before_end = ''
  if (before_size && before_size != '0')
    before_end = ",#{before_start.to_i + before_size.to_i - 1}"
  end

  after_end = ''
  if (after_size && after_size != '0')
    after_end = ",#{after_start.to_i + after_size.to_i - 1}"
  end

  "#{before_start}#{before_end}#{action}#{after_start}#{after_end}"
end
diffout.gsub!(/^(<.*)(\r|\n|\r\n)(>)/, '\1\2---\2\3')
print diffout

上記スクリプトを~/bin/git-diff-normal-formatとして保存し、~/binにPATHを通しておきます。そしたら、.vim設定ファイルに次の記述を追加します。

" diffのコマンド
set diffexpr=MyDiff()
function MyDiff()
  let opt = ""
  if &diffopt =~ "iwhite"
    let opt = opt . "-b "
  endif
  silent execute "!git-diff-normal-format " . opt . v:fname_in . " " . v:fname_new . " > " . v:fname_out
  redraw!
endfunction

最後に、git diffで使うアルゴリズムを指定します。下記記述ではgit difftoolで起動するコマンドにvimdiffを指定する設定も一緒に行っています。

[diff]
  tool = vimdiff
  algorithm = histogram

これでvimdiffやgit difftoolを実行した時にhistogramアルゴリズムが使えるようになります(patienceを使いたい場合は、上記設定のalgorithmの値をpatienceに変えてください)。

では具体的な効果を見てみましょう。サンプルとして用意した2つの比較対象のファイルをvimでウィンドウを分割して並べたものが下の画像です(クリックで拡大できます)。


まずは、今回の設定をする前、デフォルトのアルゴリズムでvimdiffによる差分をとった結果が以下です(クリックで拡大できます)。


そして、今回の設定をした後の、histogramアルゴリズムでvimdiffによる差分をとった結果が以下です(クリックで拡大できます)。


こちらのほうが、より差分内容も分かりやすく、かつ実際に手で行った変更の操作に近いものになっていますね。
※ちなみにこのvimdiffの色設定は過去に書いたこの記事を参照。

※2014/07/10更新:diffupdateを実行した際に画面の描画がおかしくなることがあったので、回避のためMydiff()にredraw!を追加

※参考記事
Making your Git Diff/Merge More Useful | The Interim Developer
Alfedenzo - Patience Diff, a brief summary
Bram Cohen's Journal - Patience Diff Advantages
HistogramDiff (JGit - Core 2.0.0.201206130900-r API)

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年7月4日金曜日

gitでマージの事前に衝突するかどうか確認する

gitを使っていて、特定のブランチとマージしたら衝突が起きないかどうか知りたい場合があります。例えば、プロジェクトチームで共有しているリポジトリにコミットをプッシュする前に、現在作業中のブランチがmasterブランチと衝突を起こさないか確認しておきたい、なんて場合です。

そんな時は、mergeコマンドの —no-commit オプションで master をマージしてコミットせずに止めてみればいいです。あとで簡単に元に戻せます。(本来確かめたい向きとは逆向きのマージですが、衝突の確認をするだけなら結果は同じですから問題ありません。)

[user@host]% git merge master --no-commit
Auto-merging /some/file/path
Automatic merge went well; stopped before committing as requested

同一ファイルに編集があったが、自動的にマージされた場合には上記のような表示になります。衝突を起きて自動的に解決できない場合はその旨がここで表示されるはずです。

確認は終わりましたが、そのままではワーキングツリーとインデックスが変更されたままですので、元に戻します。mergeコマンドの --abort オプションを使えばいいです。

[user@host]% git merge --abort

これで元通り。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月26日土曜日

一人プロジェクトでもGitは役に立つ

ちょいと先日ある人が、「一人でやってるとGitもあまりありがたみがない」なんてことを言ってましたけれども、そんなことは無いと思いますよ。

そういう僕も昔はSubversionしか知らなくて、Gitを知った時もメリットが理解できず、移行したのは単に「将来性がありそうだから」くらいの理由だったんですがね。使ううちに、「これは明らかにGitに優位性がある」と実感できるようになりました。さらに、「たとえ一人のプロジェクトだとしてもやはりGitは有効活用できる」と思ってます。

ポイントは、リモートリポジトリとローカルリポジトリの使い分けにあります。開発中は色々と試行錯誤をしながら進めていくものですが、それらをコミットしていくと、結構変更が入り乱れてきます。一段落したところで変更をまとめてスッキリさせたいと思うわけですよ。

そういう時に、ローカルリポジトリに試行錯誤のためのブランチを作ってそこでにコミットしていき、一段落したところで、それらの変更をまとめて改めて別のブランチにコミットします。具体的には、以前この記事に書いたような merge --squash を使う方法が有用です。リモートリポジトリにプッシュするのは変更をまとめたブランチのみとしておけば、リモートリポジトリのコミットログもスッキリし、分かりやすくなります。

そういえば、ブランチの概念もSubversionとGitでは全然違いますよね。Subversionだとブランチはあくまで派生であって、trunkを更新するのが基本。Gitの方はmasterとは別のブランチにコミットして、masterにマージして取り込む、というのがよくあるパターンです。ですので一人プロジェクトでも気軽にブランチを切っていきます。

というわけで、一人プロジェクトでもGitは相当役に立ちますよ、という話でした。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月10日木曜日

Vim使いにもお勧めするEclipseの併用方法

もともと昔から開発はEclipseで行っていましたが、今はRubyを書いていくのがVimの方がやりやすいため、コードはVimで書いています。さらに、gitのコミットなどの操作もEclipseからではなく、ターミナル上のgitコマンドで行うようにしています(こちらは、Eclipseはシンボリックリンクを含むgitリポジトリの扱いにバグがあるため)。

逆に、Eclipseを使っていない人もいると思うんですよね。僕の周囲にはそういう人も居ます。確かに、Vimとターミナル上の各種コマンドで全てのことはできるっちゃできるんですが、Eclipseを併用すると色々な場面で効率を上げることができます。

僕がEclipseで活用している機能のうち、特に役立つと思うものをいくつか紹介します。

・ファイル検索

grepコマンドやfindコマンドでも同様のことができますが、Eclipseは結果表示の一覧性が良く、結果からエディターのヒット箇所へジャンプしつつ、結果を次々と見ていけたりできます。検索範囲をファイルツリーから選ぶのも直感的にできますし、使える正規表現もgrepより多いです。
操作方法:プロジェクト・エクスプローラ上で、検索したい範囲を選択した上でメニューから「検索>検索...」を選びます。

・gitヒストリー(コミットグラフ)の閲覧

これがあるからEclipseを併用していると言っても過言ではないです。gitのコミットグラフを可視化するツールはいくつも試しましたが、Eclipse(の標準プラグインEGit)が一番綺麗で見やすいと思います。
操作方法:プロジェクト・エクスプローラ上でプロジェクトのトップディレクトリを右クリックして「チーム>ヒストリーに表示」を選びます。

・gitコミット直前の確認ダイアログ

コミットする際に変更のあったファイル一覧は、ターミナル上でgitコマンドによるコミットを行う場合も出てきますけれども、Eclipseの場合ダイアログが表示されて変更のあったファイルと変更の種類を一覧で出すとともに、その一覧のファイルをダブルクリックすると、コミットしようとしている差分を表示してくれます。僕はここで差分だけ確認したらキャンセルして、実際のコミット操作はターミナル上のgitコマンドで行っています。
操作方法:プロジェクト・エクスプローラ上でプロジェクトのトップディレクトリを右クリックして「チーム>コミット...」を選びます。

・ディレクトリ単位での比較

比較結果がディレクトリ構造にのっとってツリー状に表示されるため、変更箇所が分かりやすいです。
操作方法:プロジェクト・エクスプローラ上で比較したい2つのディレクトリを選択した状態で右クリックして「比較>相互」を選びます。

他にも色々機能はありますが、上で述べた機能が最もよく使いますし、これらだけでも使いこなせば、Vimとターミナルだけで開発を進めるよりも一段高い生産性を発揮できることでしょう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月2日水曜日

Eclipse EGitでサブモジュールを含むリポジトリが常に変更ありになる件

Eclipse の EGit プラグインでは、サブモジュールを含むリポジトリをチェックアウトすると、まだ何も変更していないはずなのに、変更ありのマークが付くという不可思議な状況になります。

最初はいきなり変更ありのマークが付いて何かミスったのかと思いましたが、やり直しても変わらないのでEGitがおかしいってことですね。

ちなみにシンボリックリンクも同じように変更していないはずなのに変更ありのマークが就いてしまいます。

調べてみたら、どうやらEgitが使うgitのJava上の実装であるJGitのバグのようです。シンボリックリンクの扱いが原因みたいですね。

https://bugs.eclipse.org/bugs/show_bug.cgi?id=391280

Eclipseはクロスプラットフォームである以上、シンボリックリンクの扱いをどうするのかは難しい問題なのかもしれません。上記リンク先を見ると、まだバグの対処は手付かずのようです。

仕方ないのでとりあえず、僕は端末上から git コマンドを併用することにしています。ステータスを見る時やgit コマンドによる各種操作はEclipseでなく端末上の git から行うようにしています。Eclipseはヒストリー(コミットグラフ)が見やすいのと、コミット時に出る、変更一覧を選択して差分を見るダイアログが見やすいので、実際のコミットは行わずコミット時のダイアログだけ見るといった使い方をしています。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年3月20日木曜日

[Ruby]開発環境をEclipse(Aptana)からVimに変更した件

開発環境にはEclipseを愛用してきた僕ですけれども、Rubyでの開発プロジェクトを本格的に進めるにあたり、色々試行錯誤の結果Vimに乗り換えることにしました。いえ、乗り換えるというと語弊がありますね。正確に言えば、コーディングすなわちソースコードをガリガリ書く部分にはVimを使いますが、EclipseはGitリポジトリの状態を見るのに使ったりするので併用していく方式にした、ということです。適材適所。

PHPの時にはEclipseのPDTプラグインが大変良く働いて、例えば文脈に応じての補完機能などは非常に便利でした。これは動的にコードから補完ができる優れ物で、自分で書いたクラス定義を参照してインスタンスのメソッド名を補完できたりしたんですね。さらに、フレームワークのソースコードをライブラリを読み込ませることで、フレームワークに用意されたクラスやクラス内のメソッドまで補完が効きました。あとクラスやメソッドにカーソルを合わせてショートカットで定義元にジャンプできたりとか。

一方、EclipseでRuby開発に使えるプラグインとしてはAptana Studio3がありますが、動的にコードから補完するところまではできないようでした。また、フレームワークのクラス名やメソッド名も補完が働きませんでした。もしかしたら僕の設定が間違っていた可能性もありますが、ある程度時間を掛けたところで諦めました。

そもそも、純粋なテキストエディタとしての使い勝手としてはVimが好きで、EclipseにVim風のキーバインドを実現するプラグインのvrapperを入れて愛用していたくらいです。PHPで開発していた時には、文脈を理解した動的な補完とライブラリからの補完がよく出来ていたので書きやすさを少し犠牲にしてEclipseを使っていましたが、それがイマイチならEclipseを使う優位性は薄れてしまいます。

調べてみると、Vimで一定レベルの補完や自動文法チェックもプラグインでできるので、それならRubyを書くのにはVimの方がいいかと思ったんですね。ということでコーディングはVimで行うことにしました。

ちなみに、Gitリポジトリの状態を見るためにEclipseは併用します。コミットグラフが見やすいので。ヒストリービューはほぼリアルタイム(長くても5秒くらいの遅延)でコミットグラフを更新してくれるので、状態を把握しつつブランチ間を移動したりコミットしたりできます。Eclipseは検索なんかも結果が見やすいですし。タスク・タグの一覧とかも便利です。普段はVimでコードを書きつつ、裏でEclipseを起動させておいて必要な時だけ見る、という使い方になると思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年3月13日木曜日

gitでmasterブランチへのマージを --no-ff で行う理由(コミットグラフ例つき)

gitを用いてローカルリポジトリで開発する際の1つの手法として、ローカル専用ブランチを切って、変更を自分の名前のブランチにmerge --squashで反映する方法を以前紹介しました。

そして、自分の名前のブランチを中央リポジトリへとブッシュし、中央リポジトリへの管理者が各開発者の名前のブランチからmasterブランチにマージするわけですけれども、その際、masterブランチに各開発者の名前のブランチの変更を反映する時には、常にfast-forwardのマージを避けて--no-ffでマージしています。

これはなぜかというと、各開発者の行った変更・修正がどのタイミングでmasterブランチに統合されたのかを明確に記録するためです。

また、コミットグラフを見た際にも、開発者の名前がついたブランチが1直線につながるので綺麗に見えるという利点もあります。

例をお見せしましょう。EclipseのEgitプラグインでコミットグラフを見てみるとこんな感じに見えます。


上の図で、一番左の線がローカル専用ブランチ、真ん中の線が開発者の名前のブランチ(tkasai)、一番右の線がmasterブランチです。
※上から2番目のコミットだけmasterのグラフが左に行ってますが、脳内で右に位置を替えてみてるとすっきりします。

真ん中の線のtkasaiブランチへのコミットは、左の線のローカル専用ブランチ(local/日付)をmerge --squashでまとめたものです。例えば、真ん中あたりのコミットID:689b51aはコミットID:6e1b3b7〜60f3d8e(つまりlocal/14.02.18ブランチにおけるコミット)をまとめたものになっています。そして、tkasaiブランチを任意のタイミングでmasterブランチへと--no-ffでマージしています。ここではローカル専用ブランチをmerge --squashした直後が多いですね。

こうしてみると、いつmasterにどのブランチをマージしたのかが明確にわかります。

ちなみに、Egitのコミットグラフは現在のブランチに直接つながりのないブランチを隠すことができます。するとこうなります。今度は左の線がmaster、右の線がtkasaiで、さっきの図と位置が逆になっているので注意。


スッキリして見えますよね。最新以外のローカル専用ブランチが省略されてこうなるのです。まだ開発者ブランチが僕一人の分しか無いからこれだけですが、もっと開発者が増えたら、コミットグラフが整理されて見える恩恵はもっと大きくなると思います。

関連記事:gitでローカルリポジトリにローカル専用ブランチを作って開発する

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年3月8日土曜日

Ruby開発にBundler使ってGitで管理する際のポイント

Rubyを使って開発を進めてて、必要なgemパッケージをBundlerで管理することにしました。Bundlerは開発環境と同じgemパッケージを本番環境に揃えることに関して管理を楽にしてくれるので安心便利ですよね。

Bundlerの使い方そのものについては詳しく解説された良いサイトが多数存在しますので省略させて頂いて、ポイントだけ書いておこうと思います。プロジェクトをGitで管理していることも考慮に入れてます。

bundle install の際は --path vendor/bundle オプションをつける


--pathオプションをつけないとシステムデフォルトの位置にgemパッケージがインストールされます。しかしせっかくBundlerを使うのであれば、プロジェクトごとに個別にgemを管理したほうがよいですね。プロジェクトごとに違うバージョンを使うこともありえますし。ということでインストール先を指定するため--path vendor/bundleオプションをつけます。vendor/bundleという名前にするのは、後で出てくる--deployment オプションと統一するためです。

Gemfile と Gemfile.lock だけをGit管理対象とし、.bundle ディレクトリと vendor/bundle ディレクトリは .gitignore で無視する


上記の --path 指定をして bundle install を実行すると、.bundle/configという設定ファイルも自動的に生成されます。このファイルにはインストール先が記録してあって、次回から--path指定をしなくても済むようになります。しかし.bundleには環境固有の設定も入るようなので、Git管理対象にはしないほうが良さそうです。もちろん、gemパッケージのインストール先であるvendor/bundleディレクトリも管理対象から外します。

そのため、Gitをクローンした各開発者には個別にbundle install --path vendor/bundleを実行してもらう必要があります。(gemをアップデートした際には bundle update)

デプロイする際は --deployment オプションをつけて bundle install しておく


bundle install コマンドにはデプロイ用のオプション --deployment があります。これは、vendor/bundle 内に必要な依存gemパッケージを確実に揃えるとともに、その後Gemfileを変更してバージョンを変えられないようにします。デプロイをスクリプトで自動化している際には、どこかのタイミングでbundle install --deployment を行うようにしておくといいでしょう。

基本的に--path vendor/bundle を指定して bundle install を実行するとvendor/bundle 内に必要な依存gemパッケージは揃うと思うんですが、システム共通でインストールしたgemが既にあるとインストールしてくれないみたいbundle install --disable-shared-gems vendor/bundle とやればいいみたいですが、だったら固定操作まで一気にやってくれる--deployment使ったほうが楽でしょうとうことで。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年3月1日土曜日

ドキュメント管理のためSphinxを導入してGitで管理

ソフトウェア開発においてはドキュメントの変更は頻繁に起こるものです。といいますか、「起きるようにすべき」ってことなんですがね。

ドキュメントの更新は得てして(非常に高い確率で)放置されがちなタスクです。古くなってカビとキノコが生えたドキュメントは役に立たず、誰からも参照されなくなります。そして、リリース直前になって慌てて開発チームが更新作業に追われるのですね。

昨今ではすっかり市民権を得た(と少なくとも見える)アジャイル開発においても、「ドキュメントよりも動くソフトウェアを重視する」という方針のおかげか、ドキュメントは開発中は手を付けず、最後にまとめて作るやりかたも実際に行われてるという話も聞いたことがあります。

僕自身、アジャイル開発を本格的に行った以前のプロジェクトにおいては、開発中にドキュメントは作りませんでした。しかし考えてみれば、そのプロジェクトはWebサービスの開発であって、誰かに仕様を1つにまとめて伝える必要性がそこまで高くないものだったのです。しかし今取り組んでいるプロジェクトは製品として外部に出荷することを想定しており、その意味では仕様をまとめる必要性があります。

しかし、最後にまとめて変更するやり方ではそもそも大変ですし、ミスも生まれやすくなります。何より、ひたすらドキュメントの更新をするのはあまり楽しい作業ではありません。やはり、ドキュメントを開発にともなって随時変更していきたいところです。しかし、それは放置されがちになることは既に述べました。

プログラムのソースコードがなぜアジャイル開発のような頻繁な変更にも耐えられるのかといえば、SubversionやGitのようなバージョン管理システムの貢献が大きいです。コードのどの部分がいつ誰によってどう変更されたのか明確に把握できてこそ、一度書き上げたコードの一部をかいつまんで変更し続けることが容易になるのですね。そうでなければチームは大混乱に陥ります。その意味で、バージョン管理システムが発達していなかった時代、ウォーターフォールしか選択肢がなかったのも頷ける話です。

つまり、ドキュメントの変更を開発に伴って頻繁に行うには、どの部分がいつ誰によってどう変更されたのか把握できるようにしなければなりません。残念ながらExcelやPowerPointではこのことは実現が非常に困難です。

だったら、もしバージョン管理システムによってドキュメントを管理できれば、開発しながらドキュメントも書くということが実現できそうです。そのためには、ソースコード管理システムによって変更を把握できる形式、すなわち「プレーンテキスト」である必要があります。

そこでプレーンテキストからドキュメントを生成する仕組みを導入すればいいと気づくわけですね。有名なものに科学論文向けのTeXがありますが、ソフトウェアのドキュメント向けに開発されたツールの1つとしてSphinxがあります。SphinxはPythonの公式ドキュメントに使われている他、多くのマニュアルやサイトに使われています。

なんか前振りだけで長くなりましたが、まあこういったツールの導入には考え方が重要ですからよしとしましょう。手順自体は大したこと無いんで、簡潔にまとめます。環境はDebian Linuxです。

まず、Sphinxをインストールします。Pythonは既にインストールされているものとします。Debianのパッケージは古いので、公式リポジトリからソースをダウンロードして使います。

$ wget https://bitbucket.org/birkenfeld/sphinx/get/1.2.1.tar.bz2
$ tar jxvf 1.2.1.tar.bz2
$ cd birkenfeld-sphinx-3238dc3d4528/
$ python setup.py build
$ su -
# python setup.py install
# exit

これで/usr/local/bin以下に実行ファイルがインストールされます。次に、ドキュメント管理用のGitリポジトリを用意し、その中でsphinxプロジェクトを初期化します。

$ mkdir foo-doc
$ cd foo-doc
$ git init
$ sphinx-quickstart

sphinx-quickstartを実行すると、幾つか質問されます。その際、プロジェクトの名前と著者名(複数可)だけ答えてあとはデフォルトでいいという説明をよく見かけますが、こんかいはGitで管理する都合上、ソースと生成したドキュメントを分離することにします。以下の質問をされた時に y と答えればいいです。

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]: y

これでSphinxでドキュメントを作成するための基本のファイルが生成されます。 .gitignoreでbuildディレクトリを除外しておきましょう。ドキュメントのソースコードはsource ディレクトリ内にあります。ドキュメントを生成するには、Makefileのあるディレクトリで

make html

などと打てば生成されます。html以外にもpdfなど様々な形式を指定できます。具体的なドキュメントの書き方の詳細については日本ユーザ会のマニュアルなどを参照ください。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月27日木曜日

gitブランチ名とステータスをzshの右プロンプトで表示

ターミナルからgitコマンドでリポジトリを操作するときに、マージしてから自分が現在いるブランチを間違えていた事に気づいてやりなおす・・・なんてミスは僕もやったことがある。それに現在いるブランチを確認するのにgit branchをそのたびごとに打つかわりに、常に表示できていたら便利じゃないだろうか。

実は、gitの配布ファイルの中には、bashとzsh向けに、gitリポジトリの中にいるときにだけ、プロンプトにブランチ名とステータスを表示するためのスクリプトが含まれている。これを使えば、上で上げたようなミスも防げるし、現在の変更状態も把握できて作業しやすくなる。ここではzshについてやり方を書こうと思う。

まず、gitの配布ファイルを解凍してできたディレクトリの contrib/completion/git-prompt.sh を適当な場所にコピーする。(gitの配布ファイルは例えばここからダウンロードできるし、githubから直接落としてきてもいい。)ここでは ~/.zsh/git-prompt.sh にコピーしたとしよう。

そうしたらまず、~/.zshrcに次の行を追加して読み込ませる。

source ~/.zsh/git-prompt.sh

このスクリプトにはブランチ名を表示する __git_ps1という関数が定義されており、gitリポジトリ内にいるかどうかを自動で判別して、いるときだけ動作する。

zshの場合、プロンプトを書き換えるにはシェル変数PS1を変更するのだが、通常のプロンプトに加えてブランチ名まで表示すると、ちょっと長過ぎる。そういう時、zshでは「右プロンプト」が利用できる。これは、入力中の行の画面右端に表示されるプロンプトだ。なお、入力文字列が長くなった時は自動的に消えてくれる優れものだ。

右プロンプトを設定する時は、シェル変数RPROMPTを設定する。また、zshにはプロンプト表示直前に実行されるprecmd()を定義でき、git-prompt.sh内のコメントによるとそこで実行したほうが若干速いらしい。ということで、以下の定義を ~/.zshrc に追加する。

setopt PROMPT_SUBST
setopt TRANSIENT_RPROMPT
precmd () {
    RPROMPT='$(__git_ps1 "[%s]")'
}

ここで setopt PROMPT_SUBST は、プロンプトに指定した文字列で変数展開を有効にするという意味。こうすると$(...)内部をスクリプトとして展開してくれる。setopt TRANSIENT_RPROMPT は、右プロンプトをコマンド実行後に消すという意味。また、__git_ps1 の引数には%sを含む文字列を指定できて、%sの部分がブランチ名とステータスを表示してくれる。

ここまでの設定だけだと、実はブランチ名しか表示されない。それだけでも便利だが、さらにオプションのシェル変数を設定することで、各種ステータスを記号で表示できる。設定できるシェル変数には次のようなものがある。
  • GIT_PS1_SHOWDIRTYSTATE
    空の値以外を指定すると、未ステージの変更がある時は「*」を、ステージ済の変更がある時は「+」をブランチ名の後に表示する。
  • GIT_PS1_SHOWSTASHSTATE
    空の値以外を指定すると、スタッシュが存在する時は「$」をブランチ名の後に表示する。
  • GIT_PS1_SHOWUNTRACKEDFILES
    空の値以外を指定すると、追跡されていないファイルが存在する時は「%」をブランチ名の後に表示する。この設定はリポジトリごとの設定 bash.showUntrackedFilesにより上書きされる。
  • GIT_PS1_SHOWUPSTREAM
    アップストリーム・リポジトリで対応するブランチとのコミットの差分状態を表示する設定で、次のものを指定できる。
    "auto" : アップストリーム側に新しいコミットがある場合は「<」を、ローカル側に新しいコミットがある場合は「>」を表示する。
    "verbose" : アップストリーム側の新しいコミット数を「u-数字」で、ローカル側の新しいコミット数を「u+数字」で表示する。
    他にも"name" "legacy" "git" "svn"があるようだけど説明は省略。詳しくはgit-prompt.sh内のコメント参照。この設定はリポジトリごとの設定 bash.showUpstream により上書きされる。
  • GIT_PS1_DESCRIBE_STYLE
    特定のコミットやタグをチェックアウトしている時の表示方法を指定できる。"contains" "branch" "describe" "default" から選べる。詳しくはgit-prompt.sh内のコメント参照。
  • GIT_PS1_SHOWCOLORHINTS
    空の値以外を指定すると、GIT_PS1_SHOWDIRTYSTATE を有効にしている時に色付きで表示してくれる。ただし、__git_ps1 を PROMPT_COMMAND または precmd 経由で実行している時に限る。
今回は次のように設定した。

GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM="auto"
GIT_PS1_DESCRIBE_STYLE="default"
GIT_PS1_SHOWCOLORHINTS=1

以上まとめると、設定は以下のようになる。

source ~/.zsh/git-prompt.sh
setopt PROMPT_SUBST
setopt TRANSIENT_RPROMPT
precmd () {
    RPROMPT='$(__git_ps1 "[%s]")'
}
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM="auto"
GIT_PS1_DESCRIBE_STYLE="default"
GIT_PS1_SHOWCOLORHINTS=1


これで、gitリポジトリ内にいる時は右プロンプトでブランチ名とステータスが表示されるようになった。便利便利。

※2014/03/27追記(2014/08/29一部修正)

なんか時々表示がおかしくなるなーと思ってたら、どうやら追跡されていないファイルが存在する時の「%」が、本来ならZSHだと「%%」とエスケープされるはずのところ、単独の「%」のまま出力されることがあるらしい。しかも、ログイン直後は大丈夫だけど、しばらくgitコマンドを使っているとそのうちおかしくなるという意味不明な状況。いろいろ調べたけど原因はわからず、仕方ないので下記のように強制的に置き換えて対処してます。

precmd () {
    RPROMPT=`echo $(__git_ps1 "[%s]")|sed -e s/%/%%/|sed -e s/%%%/%%/|sed -e 's/\\$/\\\\$/'`
}

最初の「%」を「%%」に置き換えますが、もし最初からエスケープされてる場合は「%%%」になってしまうので、その場合は「%%」に戻してます。sedだと正規表現に先読み・戻り読みが使えないのでこうしてます。さらにスタッシュの存在を示す「$」が変数展開と解釈されないようエスケープしてます。スマートじゃないな・・・。もっと良いやり方知っている人いたら教えてください。

その他の設定も若干変更したので該当部分の設定を再度載せておきます。

source ~/.zsh/git-prompt.sh
setopt TRANSIENT_RPROMPT
precmd () {
    RPROMPT=`echo $(__git_ps1 "[%s]")|sed -e s/%/%%/|sed -e s/%%%/%%/|sed -e 's/\\$/\\\\$/'`
}
GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
GIT_PS1_SHOWUPSTREAM="auto"
GIT_PS1_DESCRIBE_STYLE="branch"
GIT_PS1_SHOWCOLORHINTS=0

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月21日金曜日

gitでローカルブランチをリモートブランチの内容で上書きする

gitでサブモジュールを使っていて、そのサブモジュールはリモートリポジトリからコードを引っ張ってくるためだけの目的で使っている場合、そのサブモジュールの内容は自分ではいじることはなくて、単にリモートリポジトリのコピーであればいい。

で、サブモジュールの内容を最新に更新しなきゃな―、って思って pullしたときに、衝突がワンサカ出た。リモートリポジトリ側でブランチがきちんと管理されていればこういう衝突は起こらないはずだが、まあそこは何か事情もあるのだろうし、完璧を求めても仕方ない。

ってことで、今回はmasterブランチを使っているので、ローカルのmasterブランチをリモートのmasterブランチの内容で強制的に上書きしてやることにする。masterブランチをチェックアウトした状態で、

git fetch origin
git reset --hard origin/master

でOK。すでに"git pull origin"してconflict警告が出た状態なら1行目は不要。

余談だけれど、"git pull origin"は結局"git fetch origin"と"git merge origin/ブランチ名"を一緒にやっているだけだから、pullは使わなくて良かったりする。今回のようなこともあるし、次回からはpullは使うのやめようかな。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月19日水曜日

gitでローカルリポジトリにローカル専用ブランチを作って開発する

ソースコード管理はgitがもはや標準になっているけれど、ブランチの管理方法は人やプロジェクトによって様々だと思う。今回は、僕が現在採用してる方法を紹介したい。

想定として、中央リポジトリに各開発者がプッシュしていく、ハブ型の構成としよう。中央リポジトリにプッシュする際のブランチは、各開発者の名前(もしくは開発者を区別できる文字列)をつけたブランチとし、中央リポジトリの管理者が、各開発者の名前のついたブランチをmasterブランチにマージすることにする(だから各開発者はmasterにマージしない)。

※2014/03/13追記:ちなみに、masterにマージする際は--no-ffでしたようがよいのだが、それについては別の記事を書いた→ gitでmasterブランチへのマージを --no-ff で行う理由(コミットグラフ例つき)

さて、ここで各開発者は自分の名前のついたブランチ上で開発を行うわけだけれども、ここでコミットの頻度・タイミングをどうするかで悩むことになる。要するにこういうことだ。

  • コミットを細かく行ったほうが開発者としては状態を細かく保存でき、保存した状態からソースコードを比較できて開発を進めやすいし、作業失敗した時の保険にもなって安心だ。しかし、一度行った変更を更に変更したりあるいは戻したりすることも日常茶飯事であり、要するに細かく行ったコミットは「汚い」。中央リポジトリにプッシュする際にそのコミットは見せたくないし、masterブランチにマージする中央リポジトリ管理者も作業しにくいだろう。そしてmasterブランチのコミットログが大変見づらくなる。
  • コミットをまとまった機能ができたタイミングなど、ある程度大きい変更単位にすれば、コミットでの変更目的が分かりやすいし、開発途中での紆余曲折や変遷をいちいち記録しないのでコミットもすっきりする。中央リポジトリの管理者も楽だろう。masterブランチのコミットログも見やすい。ただし、開発者としては作業しづらいし何かあった際に作業内容が吹っ飛ぶ危険性もある。

じゃあどうするのかという話だけれど、僕が採用している方法は、
  • ローカルリポジトリにローカル専用のブランチを作る。
  • 中央リポジトリへプッシュするブランチは、ローカル専用ブランチからmarge --squashで変更を反映する。
  • marge --squashを行った後のローカル専用ブランチはそのまま残し、新たに別のローカル専用ブランチを切る。
というものだ。
ローカルリポジトリには、master、自分の名前、local/作成日付という名前で3種類のブランチを用意する。例えば、master、kasai、local/14.02.19という名前でブランチを作成したとしよう。


作業はlocal/14.02.19ブランチ上で行う。ここでのコミットは汚くなることを気にせず行っていい。例えば何回かコミットした状態としよう。


ここで、ある程度まとまった機能ができたとしよう。中央リポジトリへここまでの変更を1つのコミットとして提出することにする。まず、自分の名前のブランチ(kasai)へ移動する(git checkout kasai)。なお、EclipseのEgitだと太字が現在のブランチを表すので、移動しているのが分かる。


そして、先程まで作業していたローカル専用リポジトリの変更をmerge --squashで取り込む。(git merge --squash local/14.02.19)
変更を取り込んだら、git diff local/14.02.19を実行し、local/14.02.19とワーキングツリーが同じ内容になったことを確認し、git status で変更がインデックスに記録されたことを確認する。その後 git commit でコミットする。
コミットの際は自動的にそのコミットをまとめたコミットなのかコメントがデフォルトで入っているが、さらにコミットの要約文と、どのブランチのコミットをまとめたのかを書いておくといいだろう(この場合 local/14.02.19)。


これで自分の名前のブランチ(kasai)にローカル専用ブランチの変更を取り込めた。git diff kasai local/14.02.19 を実行し、同じ内容であることを確認しておこう。


開発を継続する時は新たにローカル専用ブランチを作成してそちらで作業する。(例えば git checkout -b local/14.02.20)


これを繰り返していくわけだ。新しく作ったローカル専用ブランチに何回かコミットするとこんな感じ。


ある程度まとまったら先ほどと同様にmerge --squashで自分の名前のブランチ(kasai)へ反映する。


さて、いかがだろうか。これで中央リポジトリのコミットログを汚なくせずに、なおかつ手元で細かくコミットを行って開発できる。ローカル専用ブランチ(local/...)が沢山できるけれど、気にならないならそのままにしておいてもいいし、ある程度時間が経ったら古いものは消してもいいだろう。

関連記事:gitでmasterブランチへのマージを --no-ff で行う理由(コミットグラフ例つき)

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月8日土曜日

gitでサブモジュールを削除する(バージョンごとの方法まとめ)

gitのサブモジュールってバージョンによって削除の方法が違う。
サブモジュールのリポジトリのルートからの相対パスが path/to/submodule の場合、リポジトリのルートで次のようなコマンドを実行すればいいと思う。

v1.8.5以上
$ git submodule deinit path/to/submodule
$ git rm path/to/submodule
$ rm -rf .git/modules/path/to/submodule

v1.8.3以上v1.8.5未満
$ git submodule deinit path/to/submodule
$ git config -f .gitmodules --remove-section submodule.path/to/submodule
$ git rm path/to/submodule
$ rm -rf .git/modules/path/to/submodule

v1.8.3未満
$ git config -f .git/config --remove-section submodule.path/to/submodule
$ git config -f .gitmodules --remove-section submodule.path/to/submodule
$ git rm --cached path/to/submodule
$ rm -rf .git/modules/path/to/submodule
$ rm -rf /path/to/submodule

ちなみに、これまで使ってたgitはDebian7のパッケージ管理システム(apt-get)でインストールしたやつだったけど、バージョンは1.7.10.4と古かったので、サブモジュールの削除も面倒。この際バージョンアップすることにした。他にも表示が親切になるらしいし。

まずコンパイルに必要なパッケージを入れる。
$ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
 

git最新版のDebian公式パッケージはないので、ソースからコンパイルする。
https://code.google.com/p/git-core/downloads/list
から最新版をダウンロードする。この記事を書いている時点では1.8.5.4が最新だ。ダウンロードしたら解凍して、コンパイル&ルート権限でインストール。
% wget https://git-core.googlecode.com/files/git-1.8.5.4.tar.gz
% tar zxvf git-1.8.5.4.tar.gz
% cd git-1.8.5.4
% ./configure
% make
% su -
# make install

特に迷うこともなくあっさり完了。

ちなみに僕はzsh使いだけれど、git submodule deinitが補完で出てこない。gitのソースにはzsh用の補完関数スクリプトが添付されている(解凍したソースのcontrib/completion/git-completion.zsh)けど、これを使っても補完候補に出てこないので注意。

余談だけれどgitのバージョン上げたらgitコマンドの出力がモノクロからカラーになってて超便利。中でもgit diffで差分を色分けしてくれるのは素晴らしいね。

参考サイト:
git submoduleを今風な感じで削除する - Qiita
git submoduleについてのメモ - rcmdnk's blog
How do I remove a Git submodule? - Stack Overflow

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。