読者です 読者をやめる 読者になる 読者になる

A Way of Code

興味の赴くままに書き綴っていきます。

MacでOracle JDK 7 (Java 7) を試す

Oracle JDKのMac版がリリースされたので、早速試してみました。

インストール

AppleJDKがインストールされている場合は、Oracle JDKと共存できるのでそのままで結構です。

ダウンロード先はこちら:

ダウンロードトップページ
http://www.oracle.com/technetwork/java/javase/downloads/index.html
Oracle Java SE 7u4
http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u4-downloads-1591156.html


上記ページにある"jdk-7u4-macosx-x64.dmg"をダウンロードします。
f:id:toggtc:20120428104300j:plain


"jdk-7u4-macosx-x64.dmg"というファイルをマウントすると、"JDK 7 Update 04.pkg"というインストーラーパッケージが出てきます。
f:id:toggtc:20120428104508j:plain

このpkgファイルをダブルクリックすればインストーラーが起動するので、そのままインストールします。
f:id:toggtc:20120428104610j:plain


f:id:toggtc:20120428104845j:plain
「次のステップ」というリンクをクリックすると、後述するセットアップの作業手順を示したページが表示されます。


インストールが終わったので、ここで一旦、パスが通っているJavaのバージョンを確認しておきます。

$java -version
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04-415-11M3646)
Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01-415, mixed mode)

バージョンが1.6(AppleのJDK)になっているので、勝手にパスが書き換わる、ということは無いようです。

セットアップ

インストールが完了したら、次は以下のページに従ってセットアップを行います。
http://www.oracle.com/technetwork/java/javase/downloads/jdk-for-mac-readme-1564562.html

まず、/Applications/Utilities/にある"Java Preferences"を開きます。
次に、"Java 7"の項目を一番上にドラッグ&ドロップして移動します。
f:id:toggtc:20120428105029j:plain

そして、Javaのバージョンを確認します。

$ java -version
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

これでJava 7が使えるようになりました。

セットアップ(Eclipse編)

Eclipse側もJDK 7にしてしまいましょう。
注意点は、「MacOS X VM」を選択することです。

1)
[Eclipse] → [環境設定]から"Preferences"ウィンドウを開く
Java→Installed JREs を選択して、Addボタンを押す。
f:id:toggtc:20120428105553j:plain

2)
MacOSX VM を選択して、次へ
f:id:toggtc:20120428105849j:plain

3)
JRE home: /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home
JRE name: 任意の名前(ここではJava SE 7)
を設定して、Finish。
f:id:toggtc:20120428110217j:plain

サンプルコード

サンプルを動かしてみましょう。

javaのバージョン確認

// 1.  Javaのバージョンを確認する
System.out.println(System.getProperty("java.version")); // => 1.7.0_04

実行環境のJavaバージョンは、"java.version"プロパティで確認できます。
ちゃんと1.7.0_04になりました。

Pathsクラスの確認

Macだと、Pathsクラスの実装クラスは"sun.nio.fs.UnixPath"でした。

// 2. Pathsクラスの実装クラスを確認する
String className = Paths.get("").getClass().toString();
System.out.println("java.ni.file.Path=" + className); // =>java.ni.file.Path=class sun.nio.fs.UnixPath

WatchServiceでディレクトリをモニタリングしてみる

Java 7で追加されたNIO2のWatchServiceを使ってみます。
モニタリング先のディレクトリはテキトーに/private/tmp/にしています。

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class Java7Test {
	public static void main(String[] args) {
		// 1.  Javaのバージョンを確認する
		System.out.println(System.getProperty("java.version")); // => 1.7.0_04

		// 2. Pathsクラスの実装クラスを確認する
		String className = Paths.get("").getClass().toString();
		System.out.println("java.ni.file.Path=" + className); // =>java.ni.file.Path=class.sun.nio.fs.UnixPath

		// 3. WatchServiceを使ってディレクトリをモニタリングしてみる
		try (WatchService service = FileSystems.getDefault().newWatchService()) {
			final Path dir = Paths.get("/private/tmp/");

			dir.register(service, 
					StandardWatchEventKinds.ENTRY_CREATE,
					StandardWatchEventKinds.ENTRY_DELETE,
					StandardWatchEventKinds.ENTRY_MODIFY
					);
			
			WatchKey key = service.take();
			for (final WatchEvent<?> event : key.pollEvents()) {
				System.out.println(event.kind().toString() + ":" + event.context().toString());
			}
		} catch (IOException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

/private/tmp/ディレクトリにファイルを追加したり編集したりすると、コンソールに結果が表示されます。
サンプルを実行して、

$ touch /private/tmp/hello.txt

とすると、"ENTRY_CREATE:hello.txt"と表示されます。

いやぁ、便利ですね。システム間をファイルの受け渡しで連携するときに使えそう。
ちなみに、ファイルのイベント通知は基本ネイティブサポートされているようですが、サポートされていない場合、ポーリングのような方法でイベントを拾うようです。

はてなブログがGoogle Analyticsに対応しました

管理ページの「管理」→「詳細設定」の箇所にトラックIDを設定する箇所があります。
http://blog.hatena.ne.jp/my/config/detail

開発スタッフからの告知はこちら
http://staff.hatenablog.com/entry/2012/04/05/161422

というわけで、以前投稿した方法は非推奨です。

Lion向けJavaセキュリティアップデート Java 1.6.0_31

Java 1.6.0_31のMac版がセキュリティアップデートとしてリリースされていました。
Oracle版が2/15にリリースされてから約2ヶ月、日々を戦々恐々と過ごしていたMacユーザもようやく安心できるのではないでしょうか。

Developer Package版はいつもの場所からダウンロードできます。
"Java for OS X 2012-001 Developer Package"
https://developer.apple.com/downloads/index.action#
※要Apple-ID

てっきりJava for Mac OS X 10.7 Update 2という名前なのかと思ったら、Java for OS X 2012-001 Developer Packageという名前で登録されてしました。
どうやらAppleセキュリティアップデート2012-001というアップデート群の中の1つという位置づけのようですね。


コンパイラもアップデートされます。
アップデート前:

$ javac -version
javac 1.6.0_29

アップデート後:

$ javac --version
javac 1.6.0_31

『パターン認識と機械学習』上下巻ともに再出版

入手困難になっていた『パターン認識と機械学習』(Christopher M. Bishop著)が、丸善出版から再出版されます。

『パターン認識と機械学習 上』
http://www.amazon.co.jp/dp/4621061224/
→確率から線形回帰、ベイズニューラルネットワークまで

『パターン認識と機械学習 下』
http://www.amazon.co.jp/dp/4621061240/
カーネル法サポートベクターマシン、ブースティング等

これまでシュプリンガー・ジャパン社が販売していた同書ですが、丸善出版に事業譲渡され、何故か下巻だけが先に販売されていました。
どうやら明日(2012/4/5)、丸善出版から上巻が発売されるようです。

売り切れる前に、買いに急げ!

はてなブックマークがmicroadへの情報提供をやめた

素晴らしい判断です、社長。
http://hatena.g.hatena.ne.jp/hatena/20120313/1331629384

はてなブックマークは表示するだけで、情報が送られていたみたいなんですよね。
個人の端末や回線だったらトラッキングされたって別に気にしないのですけど、会社の端末となると話は別。
特にお客様の事務所に出向いて、お客様の端末と回線を使わせていただいているような場面で、勝手にトラッキングされるのはいただけない。
トラッキングクッキーが埋め込まれると、ウィルススキャンに引っかかって、いちいち報告しなきゃいけないし。と思ったけど、microadに情報が提供されないだけで、はてなブックマーク以外にも他のトラッキングクッキーがあるから、スキャン警告の問題ははてブだけをどうこう言ってもしょうがないですね。これはインターネットを業務で利用・提供する側が考えなきゃいけない話。よくよく考えたら社内フィルタ張ってあるでしょうし、zenbackを使っている人が言えた話じゃぁない。(2012/03/13追記)

はてなさんは好きな会社なので、あまり言いたくはないけど、収入源を増やすためとはいえ、不特定多数の企業を敵に回さないほうがいいと思う。
CFOを募集するのもいいけど、はてなさんをよく理解した上でCSR対策をやれる人材を募集したほうがいいんじゃないかな。

って、内情を知らないのに好き勝手なことを言ってはいけませんね。すみません。

まぁなんにせよ、良かったです。
めでたしめでたし

実践Git&GitHub - homebrewをフォークするためのGit&GitHub入門 後編(2/2)

homebrewをフォークするためのGit&GitHub入門 後編(1/2)の続きです。
※文字数制限に引っかかりました(汗

A.ブランチの削除

Formulaが無事に本家に取り込まれて、作業用ブランチが用済みになったからブランチを削除したい、という場合は以下のようにします。

$ git branch -D ghotscript-spike
$ git push origin :ghostscript-spike

"-D"オプションは、ブランチを強制的に削除するオプションです。
pushするときに、:ブランチ名 とすると、リモート側のブランチを削除できます。

なお、ブランチを削除しても、そのブランチに紐づいていたGitオブジェクトが消えるわけではありません。
単にブランチポインタが消えるだけで、リポジトリ内にはオブジェクトが残っています。

B. Forkしたリポジトリの削除

いろいろと間違ってしまい、Forkしたリポジトリを削除したい場合は以下の手順を行います。

(1) 削除したいリポジトリのページを開き、"Admin"ボタンを押します。
f:id:toggtc:20120310004947p:image

(2) ページの下側にある"Delete this repository"を押します。
f:id:toggtc:20120310004954p:image

(3) 本当に消してもいいかを3分考えて、気持ちが変わらなかったら"I understand, delete this repository"を押しましょう。
f:id:toggtc:20120310005000p:image

C. コマンドのまとめ

後で(自分が)読み返すときのために、今回のGitコマンドと手順をまとめておきます。
f:id:toggtc:20120312004746j:plain

Gitを使った感想

Gitを使ってみて思ったことをつらつらと書いてみます。駄文になってしまったので、暇な方以外はスキップ推奨。

Gitは率直にいって強力です。その強力さもさることながら、アーキテクチャが分かりやすく、トラブルへの対処がしやすいと感じました。
分散リポジトリであり、気兼ねなくコミットできる点は魅力です。CVSSVNほど慎重にならなくてもいいので、修正作業において重要なポイントをコミットしておらず、訳のわからない状態になってしまった、なんてことが回避できそうです。開発スピードも上がりそうですよね。
またGitHubと組み合わせることでGitの恩恵が何倍にも跳ね上がります。ForkやPull Requestだけではなく、コミットされたソースコードに対してコメントが付けられます。レビューがとても楽になります。修正が確認できたらそのブランチを取り込めばいいのですから。
こういった機能は、(ソースを隠さないという意味で)オープンソースな文化が浸透している会社では、比較的採用しやすく、また成功しやすいと思います。

しかしながら、受諾開発系のプロジェクトでGitを採用するのはまだ敷居が高いと感じます。Windows環境で動かすにはCygwinが必要であったり日本語対応が怪しかったり、メンテナンスが面倒だったり等々問題があるようですが、今回Gitを使ってみて思ったのは、Gitの柔軟性が高いということです。柔軟性が高いというか、ある事を行うための手順の組み合わせがたくさんありすぎる、といったほうがいいでしょうか。良い意味でも悪い意味でも強力すぎるんです。
とりわけ技術者が流動的になりがちで、参加するメンバのレベルはまちまちで、さらに複数の会社からメンバが参画するような規模になるともう大変です。Gitを完全に浸透させるのは困難でしょう。他にも学ぶ必要のある技術がたくさんあるのですから、VCSの学習に貴重なリソースを潤沢には費やせません。スタートアップに掛かる費用は最小限に抑えたいわけです。(それに、Gitを勉強するためにこんだけお金と時間をいただきます、なんて相当理解のあるお客様でない限り通らないでしょう)
プロジェクトには確実に完遂できる仕組みが必要です。Gitの入門書を手渡すだけでは確実とは言えません。
たとえ開発者全員がGitを熟知しているという理想的な状況下であっても、Gitの採用にはリスクが付きまとうのです。分散リポジトリであり柔軟性が高いため、リポジトリの利用ルールやブランチの戦略を標準化しておかないと、マージ地獄に陥ってGitに振り回される状況になりかねません。
あと、何気に重要なのがバイナリファイルの扱いです。Gitにはファイルのロック機能が無いようです(あるのかな?)ので、Office系ファイルをリポジトリで管理しようと思ったら、これは手痛いですよね。
というわけで、

  • Gitを習熟してもらうための仕組み
  • リポジトリの利用ルール、ブランチ戦略等の標準化
  • バイナリファイルの取扱い

がクリアできれば、パラダイスが待っていそうです。
1点目については、Gitの解説コンテンツが充実してきているので、社内向けの資料を作るのに必要な材料は揃っている気がします(著作権等を無視しちゃダメですが)。
2点目については、"A successful Git branching model"と"git-flow"が参考になりそうです。
3点目は、SVNを基幹リポジトリにしてしまう、とか。それでソースをいじるときはsvn-gitを使ってみるのがいいかもしれません。やはり設計資料とコードは同期されていることが望ましいです。設計資料とコードがバラバラに管理されていると、取り漏れていた機能が期日間近に見つかってあたふたする、なんてことがあったりするかもしれませんよね。
また、GitHubについても障壁がありそうです。ソースコードを最終的に納品しなければならない場合、GitHubに開発資産を預けるというのは、お客様の了解を取りにくいものです。GitHubとの秘密保持契約やサポート契約、情報が流出した場合の保証等々超えるべき壁は高そうです。GitHubを採用するなら、そういった面に対する対策を立てておかないといけないでしょう(GitHubのエンタープライズ契約の内容を読んでいないので、案外壁は低いかもしれませんが)。
他にもGitまわりのインフラ構築ノウハウ等々、挙げたらキリが無いですね。なんだか大変そうですが、バージョン管理はどのプロジェクトにも必ず付きまとう問題です。

結局SVNが基幹リポジトリということになってしまいましたが、コーディングやソフトウェアテストにおけるGitの魅力は変わりません。CVSSVN等で勝ちパターンを構築済みの方もそうでない方も、ソフトウェア構成管理と合わせて、いま一度バージョン管理の方法を見なおしてみてはいかがでしょうか。

参考

nvie.com
A successful Git branching model
http://nvie.com/posts/a-successful-git-branching-model/

見えないチカラ
A successful Git branching model を翻訳しました
http://keijinsonyaban.blogspot.com/2010/10/successful-git-branching-model.html

hnwの日記
GitHubへpull requestする際のベストプラクティス
http://d.hatena.ne.jp/hnw/20110528

(DxD)∞
Gitのブランチで効率的に開発・運用・保守・管理する方法
http://dxd8.com/archives/218/

関連記事

homebrewをフォークするためのGit&GitHub入門
前編:Gitのセットアップ
中編 : Gitの仕組み
後編1:実践Git&GitHub (1/2)
後編2:実践Git&GitHub (2/2)

実践Git&GitHub - homebrewをフォークするためのGit&GitHub入門 後編(1/2)

前回:Gitの仕組み - homebrewをフォークするためのGit&GitHub入門 中編

今回はFormulaを実際に修正しながら、GitとGitHubの使い方を学んでいきます。
ghostscriptのFormulaを修正して、ghostscriptのバージョンを9.04から9.05に上げる修正を行います。

なお、ここではhomebrewのオリジナルを"本家"と呼称します。
#その他の用語は、Gitの用語Gitの仕組みを参照してください。

はじめに - ブランチの方針

ブランチを利用する流れは以下のようにします。
右側のラインが本家のmasterブランチです。左側がフォークしたリポジトリのブランチです(簡略化のためにリモートとローカルを同一視しています)。
f:id:toggtc:20120309215855p:plain

homebrewのFormulaに関しては、以下の方針とします。

  • 作業用(トピック)ブランチ"ghostscript-spike"を作って、そこで修正作業を行う
  • Pull Request用のブランチ"ghostscript-updated"を作成し、作業用ブランチのコミットをひとまとめにしたものを取り込む
  • Pull Request用のブランチでPull Requestを行う

こうすることで、本家側では1つのコミットログを見るだけで済みます

なぜmasterブランチで編集をしないのか

例えばmasterブランチを使って、ghostscriptの修正を行い、Pull Reqestしたとします。その後、masterブランチのままImageMagickの修正をしてしまった場合、タイミングが悪いと本家の方で、ghostscriptとImageMagickの両方を取り込んでしまうことになります。こうなってしまうと、本家の方でImageMagickの修正を除外して、本家のmasterブランチに変更を取り込む、といった面倒な作業を強いることなってしまいます。
また、修正対象のFormula用にブランチを別途作成したとしても、そのブランチに大量のコミットログがある場合、本家の方でコミットログを1つにまとめる、という作業をさせてしまうことになります。
そこで、別途Pull Request用のブランチを作成してコミットログを1つにまとめる、というわけです。
なお、masterブランチは本家の更新を取得するためだけに使います。masterブランチが本家の内容と異なると、masterブランチから派生させる全てのブランチが本家と異なることになります。そうすると、またしてもPull Requestするときに余計な混乱を招いてしまいますから。

1. Fork - リポジトリを自分のGitHubにコピーする

(1) homebrewのページを開きます。
https://github.com/mxcl/homebrew

(2) 次に、勇気を振り絞って右上にある"Fork"をクリックします。
フォークすると、作者にフォークしたよ、と通知されます。

f:id:toggtc:20120309215916j:image


(3) ちょっとだけ待つと、自動的にページが切り替わります。
f:id:toggtc:20120309215930j:image


(4) 自分のgithubにhomebrewのリポジトリがコピーされました。
URLはgit@github.com:toggtc/homebrew.gitになりました。
f:id:toggtc:20120309215943j:image

これでフォークが完了し、あなたのhomebewリポジトリが作成されました!

2. リポジトリの準備

この作業は初回の1度のみ行います。

2.1 clone

GitHub上に作成したリポジトリをローカルにコピーします。
git cloneコマンドのパラメータに、homebrewをフォークしたときに作られたあなたのリポジトリのURLを指定します。

$ git clone git@github.com:ユーザ名/homebrew.git
Cloning into 'homebrew'...
...
Resolving deltas: 100% (36367/36367), done.

# なお、このとき前々回設定したSSHパスフレーズが求められることがあります。

以降の作業は、cloneしたときに作成されたhomebrewディレクトリにて行います。

$ cd ./homebrew

2.2 remote add

この時点では、まだリモートリポジトリとしてはorigin(=自分のGitHubにあるhomebrewリポジトリ)のみです。
本家側の更新を取り込めるようにするためには、予め本家のリモートリポジトリを追加しておく必要があります。

$ git remote add upstream https://github.com/mxcl/homebrew.git

upstreamの部分は任意の名前で構いませんが、Fork元のリモートリポジトリをupstreamと呼ぶのが通例です。
リモートリポジトリをさらに追加する場合は、taroやhanako等、リポジトリを所有している人の名前(アカウント名)にすると分かりやすいでしょう。

念のため設定されているのかどうか、確認してみましょう。

$ cat ./.git/config
[core]
	repositoryformatversion = 0
...
[remote "upstream"]
	url = https://github.com/mxcl/homebrew.git
	fetch = +refs/heads/*:refs/remotes/upstream/*

ちゃんとupstreamが設定されていますね。

3. Pull Requestのチェック

修正あるいは追加するFormulaを決めたら、まずは本家にあがっているPull Requetsを確認しておきましょう。
以下のページから、Pull Requestsを閲覧することができます。
https://github.com/mxcl/homebrew/pulls

普段から200前後のPull Requestsが溜まっています。中の人も大変そうです。
同じ内容のPull Requestを重複させてしまうと本家の方々にご迷惑なので、重複していないか確認しておきます。#私もいろいろとご迷惑を掛けた一人です。
また、既に同じような内容のFormulaが投稿されているけど、少しだけ自分の変更したい内容と異なる場合は、そのPull Requestを開いて、コメントを書きこんでみましょう。要望が通るかもしれません。

また、homebrewやFormulaに何か問題があったり、Formulaの作成を依頼する場合はissuesに投稿するといいかもしれません
https://github.com/mxcl/homebrew/issues

4. 作業用ブランチの作成

Pull Requestが重複していないことを確認したら、修正作業のための一時的なブランチを作成します。このブランチはmasterブランチを元に作成します。

念のため、元となるmasterブランチを最新化してから、ブランチを作成してきます。

4.1 checkout master

まず、現在使用しているブランチを確認しておきます。--allオプションを指定することで、リモートブランチも表示されます

$ git branch --all
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/gh-pages
  remotes/origin/master

アスタリスクが付いてるものが現在チェックアウトされているブランチです。masterとなっていますね。
masterになっていなければ、git checkout masterをしてください。

$ git checkout master

ところで、上記には、upstreamが表示されていません。remote addしただけでは、単にリモートリポジトリへの設定を追加しただけなので、upstreamリモートブランチはまだ作成されていないのです。
#なお、gh-pagesというブランチはGitHub上のWebページを構成するためのブランチです。今回使わないため無視してください。

4.2 pull

では、masterブランチを最新化していきます。
ここまでで、リモートリポジトリは2つ設定されていますね(originとupstreamです)。この2つのリモートリポジトリからそれぞれ、最新の更新を取得していきます。

(1) まずはoriginから。

$ git pull  # もしくは git pull originでもよい
Already up-to-date.

(2) 次にupstreamの内容を取得・反映します。

$ git pull upstream
From https://github.com/mxcl/homebrew
 * [new branch]      gh-pages   -> upstream/gh-pages
 * [new branch]      master     -> upstream/master
You asked to pull from the remote 'upstream', but did not specify
a branch. Because this is not the default configured remote
for your current branch, you must specify a branch on the command line.

これで、本家の最新がローカルリポジトリに取り込まれました。

(3) ブランチの一覧を見てみましょう。

$ git branch -a
* master
  ...
  remotes/upstream/gh-pages
  remotes/upstream/master

upstreamが追加されていますね。

4.3 push master

この段階では、ローカルリポジトリに最新が取り込まれただけ、です。フォークした自分のリモートリポジトリ(origin)には反映されていません。
pushしてリモートリポジトリ側にも最新を反映させましょう。

$ git push origin master

4.4 branch spike

masterブランチが最新化されたので、masterブランチから作業用ブランチを派生させます。
ghostscriptのFormulaに対する修正作業用のブランチなので、ブランチ名はghostscript-spikeとしておきます。

$ git branch ghostscript-spike

5. 修正作業

ブランチが作成できたので、実際にFormulaを修正していきます。

5.1 checkout spike

作業用にghostscript-spikeというブランチを作成しました。ブランチを作成しただけでは、作業ツリーは変更されていません。ローカルリポジトリに新しくブランチが追加されただけです。
作業ツリーとは、ブランチ内から作業ディレクトリに展開されたファイル・ディレクトリ郡のことです。チェックアウトをすることで、ブランチの内容を作業ツリーに反映できるというわけです。

$ git checkout ghostscript-spike
Switched to branch 'ghostscript-spike'

5.2 Edit

作業ツリーにghostscript-spikeブランチの内容が展開されたので、ghostscript.rbを直接編集します。
今回は、ghostscriptのバージョンを9.04から9.05にアップデートするための変更を入れます。

■urlの変更
url 'http://downloads.ghostscript.com/public/ghostscript-9.04.tar.bz2'

url 'http://downloads.ghostscript.com/public/ghostscript-9.05.tar.gz'

md5の変更
md5 '9f6899e821ab6d78ab2c856f10fa3023'

md5 'f7c6f0431ca8d44ee132a55d583212c1'

■他、9.04用のパッチ処理を削除。

5.3 add

Gitの醍醐味、ステージングの時間がやってまいりました。

(1) ghostscript.rbの編集が終わった後の、現在の状態を確認してみましょう。git statusで確認できます。

$ git status
# On branch ghostscript-spike
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   ghostscript.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	ghostscript.rb~
no changes added to commit (use "git add" and/or "git commit -a")

># Changes not staged for commit:
># modified: ghostscript.rb
と表示されていることから、ghostscript.rbがステージされていないことが分かります。

また、トラックされていないファイルがありました。
># Untracked files:
># ghostscript.rb~
viで編集したときに~ファイルが作成されてしまったようです。rm ghostscript.rb~で消しておきました。

(2) さて、ghostscript.rbがステージされていないことが分かったので、git addコマンドでghostscript.rbをステージします。
ここでは、-iオプションを付けて、対話モードでステージしていきます。

$ git add -i
           staged     unstaged path
  1:    unchanged       +2/-24 Library/Formula/ghostscript.rb

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> # 入力を求められる

編集したファイルはghostscript.rbのみなので、ステージの候補にあがっているのは1ファイルだけですね。
(3) くどいですが、1を押してステータスを確認します。

What now> 1[Enter]
           staged     unstaged path
  1:    unchanged       +2/-24 Library/Formula/ghostscript.rb
...

ここで、unstagedの欄にある" +2/-24"は、差分において追加が2行、削除が-24行という意味です。
stagedの欄は"unchanged"となっているので、この時点ではまだステージされてい無いことが分かります。

5.3.1 updateコマンドを使った場合のステージ

(1) 変更したファイルをステージに追加するには、2:updateを実行します。

What now> 2[Enter]
           staged     unstaged path
  1:    unchanged       +2/-24 Library/Formula/ghostscript.rb
Update>> 

現在ステージに追加可能なファイルがリストアップされ、ここでまた入力が求められます。
(2) ここでは、Library/Formura/ghostscript.rbの1つしかなく、番号は1が振られているので、1を入力します。

Update>> 1[Enter]
           staged     unstaged path
* 1:    unchanged       +2/-24 Library/Formula/ghostscript.rb
Update>> 

(3) 今度は*が付きました。この状態でもう一度Enterを押すと確定されます。

Update>>  [Enter]
updated one path
…

updated one pathと表示されました。

(4) ここで、ステータスを確認しましょう。コマンドは1:statusですね。

What now> 1[Enter]
           staged     unstaged path
  1:       +2/-24      nothing Library/Formula/ghostscript.rb
…

今度はghostscript.rbがstagedになりました。この段階でステージは完了しました。
#なお、ステージから外すには3:revertのメニューを実行します。

(5) 更新対象のファイルがステージされたので、念のため差分をチェックしましょう。6:diffコマンドで確認できます。

What now> 6       # 6:diff
           staged     unstaged path
  1:       +2/-24      nothing Library/Formula/ghostscript.rb
Review diff>> 1    # 1を押して、ghostscript.rbを選択する
diff --git a/Library/Formula/ghostscript.rb b/Library/Formula/ghostscript.rb
index c2863fa..97c58d2 100644
--- a/Library/Formula/ghostscript.rb
+++ b/Library/Formula/ghostscript.rb
@@ -7,21 +7,15 @@ class GhostscriptFonts < Formula
 end
 
 class Ghostscript < Formula
-  url 'http://downloads.ghostscript.com/public/ghostscript-9.04.tar.bz2'
+  url 'http://downloads.ghostscript.com/public/ghostscript-9.05.tar.gz'
   head 'git://git.ghostscript.com/ghostpdl.git'
   homepage 'http://www.ghostscript.com/'
-  md5 '9f6899e821ab6d78ab2c856f10fa3023'
+  md5 'f7c6f0431ca8d44ee132a55d583212c1'
(以下省略) 

差分が確認できました。

(6) 7:quitでインタラクティブモードを終了します。

...
What now> 7
Bye.

Bye!

5.3.2 patchコマンドを使った場合のステージ

さきほどはupdateコマンドを使ってghostscript.rbの変更をステージしました。
今度は、patchコマンドを使ってステージしてみましょう。
まずはさきほどのステージを取り消します。

(1) もう一度、インタラクティブモードを起動します。
そして、3:revertを選択します。

$ git add -i
           staged     unstaged path
  1:       +2/-24      nothing Library/Formula/ghostscript.rb

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now> 3   # 3: revertを実行

(2) これまで同様、ファイルの候補が表示されるので、1を選択し、Enterで確定します。

           staged     unstaged path
  1:       +2/-24      nothing Library/Formula/ghostscript.rb
Revert>> 1    # ghostscript.rbを選択
           staged     unstaged path
* 1:       +2/-24      nothing Library/Formula/ghostscript.rb
Revert>> [Enter]
reverted one path

reverted on pathと表示されました。
(3) 7:quitでインタラクティブモードを終了します。これで、さきほどのステージはキャンセルされました。


(4) では、実際にpatchコマンドを使ってみます。add に-pオプションをつけるとpatchコマンドになります。
(インタラクティブモードからでも5:patchでpatchが使えます)

$ git add -p
diff --git a/Library/Formula/ghostscript.rb b/Library/Formula/ghostscript.rb
...
-  url 'http://downloads.ghostscript.com/public/ghostscript-9.04.tar.bz2'
+  url 'http://downloads.ghostscript.com/public/ghostscript-9.05.tar.gz'
...
-  md5 '9f6899e821ab6d78ab2c856f10fa3023'
+  md5 'f7c6f0431ca8d44ee132a55d583212c1'
...
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? 

差分が表示され、「このhunkをステージしますか?」と聞かれました。
patchではhunkと呼ばれる変更内容のかたまりごとにステージングできます。
updateコマンドではファイル単位でステージしたのに対し、patchではhunk単位でステージできます。変更差分を細かくチェックしながら、コミット対象を選別できるのでとても便利!

(4) >Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?
さて、入力を求められましたが、それぞれどんな意味でしょう?
?を押すと確認できます。(日本語訳を追記しています)

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? ?[Enter]
y - stage this hunk
        このhunkをステージする
n - do not stage this hunk
        このhunkはステージしない
q - quit; do not stage this hunk nor any of the remaining ones
        終了; このhunkをステージせず、残りのhunkについてもステージしない
a - stage this hunk and all later hunks in the file
        このhunkおよび以降のhunkを全てステージする 
d - do not stage this hunk nor any of the later hunks in the file
        このhunkをステージせず、以降のhunkについても全てステージしない
g - select a hunk to go to
        hunkを選択する
/ - search for a hunk matching the given regex
        正規表現にマッチするhunkを検索する
j - leave this hunk undecided, see next undecided hunk
        このhunkを決めないでおいて、次の未決のhunkをみる
J - leave this hunk undecided, see next hunk
        このhunkを決めないでおいて、次のhunkをみる
k - leave this hunk undecided, see previous undecided hunk
        このhunkを決めないでおいて、前の未決のhunkをみる
K - leave this hunk undecided, see previous hunk
        このhunkを決めないでおいて、前のhunkをみる
s - split the current hunk into smaller hunks
        現在のhunkをより小さいhunkに分割する
e - manually edit the current hunk
        現在のhunkを手動で編集する
? - print help
        ヘルプを表示する

hunkを直接編集できたりするんですねぇ、便利。
それはさておき、さきほどのhunkは問題ないので、yを押して確定しましょう。
(5) すると、次のhunkが表示され、同じように変更を取り込むかどうかを聞かれます。

Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? y
...
-
-__END__
-diff --git a/base/Makefile.in b/base/Makefile.in
-index 5b7847d..85e1a32 100644
---- a/base/Makefile.in
-+++ b/base/Makefile.in
...
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? y

同様にyを押して確定させます。
次のhunkが無ければ、patchは自動的に終了します。

5.4 commit

(1) コミットの前にstatusを確認しておきます。

$ git status
# On branch ghostscript-updated
# Your branch is behind 'origin/ghostscript-updated' by 2 commits, and can be fast-forwarded.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   Library/Formula/ghostscript.rb
#

># Changes to be committed:
to be committedだから、コミットされるのを待っている状態ということですね。

commitの直前でファイルを編集したらどうなるか

ではコミットを、
っとその前に、ステージングが終わった後で修正を入れたらどうなるのでしょうか?
(2) 試しに先頭行に"# this is comment."というコメント行を追加しました。
(3) コメント行を追加後、statusを見てみると、

           staged     unstaged path
  1:       +2/-24        +1/-0 Library/Formula/ghostscript.rb
...

unstagedの欄に、+1/-0が表示されています。コメント行が追加されたことが分かります。
(4) 5:patchを実行してみると、先ほど追加した1行分の差分しか出ていないことが分かります。

Patch update>> 
diff --git a/Library/Formula/ghostscript.rb b/Library/Formula/ghostscript.rb
…
+# this is comment.
…
Stage this hunk [y,n,q,a,d,/,e,?]? y

(5) yを押して反映しました。作業用のブランチですから、後で削除すればいいのです。

いざcommit

(6) ではコミットします。作業用のブランチですから気軽にポチッとな。

$ git commit

(7) ここでエディタが起動するので、コメントを記述します。

Gitでのコメントの書き方

Gitでは以下のようにコメントを書きます。

一行の要約
[空行]
変更の理由や内容

今回は単純に9.05にアップデートしただけなので、一行で記述しちゃいます。
"Ghostscript: update to 9.05"


記述した内容を保存してエディタを終了すれば、コミット処理が完了します。

(8) ついうっかり、(2)のところで"#this is comment"という行を追加して、しかもコミットまでしてしまいました。こんな行が入っていたら、本家の人から"What's this??"と言われてしまいますね。
さきほど追加した"#this is comment"の行を削除し、もう一度ステージングとコミットを行います。作業用ブランチなので気兼ねなく、ね。

面倒なので、今回はファイルを直接指定してステージしました。

$ git add Library/Formula/ghostscript.rb
$ git commit -m "Remove comment."
[ghostscript-spike d3d1b15] Remove comment.
 1 file changed, 1 deletion(-)

(9) ここまでで、2度コミットしました。コミットログはgit logコマンドで見ることができます。

$ git log --pretty=oneline
d3d1b15c323a0e8702c6d9a69bc3997be1d42b2e Remove comment.
1dd2ed5417b7c0a4dc35ca6bf679ffa343ea42ad Ghostscript: update to 9.05.
...

5.5 push

コミットが終わったら、サーバ側に反映しておきます。

ghostscript-spikeは作業用ブランチですから、サーバ側に置かずに作業することもできますが、サーバ側に置くといろいろと楽です。
例えば、開発用のMacでFormulaを修正して、それをサーバにアップさせた後、検証用のMacでFormulaが正しく機能するかを試しながらconfigureのパラメータを調整する、なんてことが出来ます。ビルドに時間の掛かるFormulaの検証をマシンパワーのある別のMacで行うときや、開発機とは別にクリーンな環境でビルド検証したい場合に便利です。

サーバ側のリポジトリに反映するにはpushを使います。初回はpush コマンドに-uオプションを忘れてはいけません。-uオプションをつけることで、push先のブランチをトラック出来るようになります。なお、このときconfigファイルに以下のような設定が追加されます。

[branch "ghostscript-spike"]
	remote = origin
	merge = refs/heads/ghostscript-spike

pushコマンドの形式には省略形がいろいろありますが、きっちり書くならこの形式。

$ git push -u <リモートリポジトリ> <ローカルブランチ>:<リモートブランチ>
$ git push -u origin ghostscript-spike:ghostscript-spike

リモートリポジトリにはghostscript-spikeというブランチはありませんが、上記の指定によりpushした時点でブランチが作成されます。
(1) ではpushします。

$ git push -u origin ghostscript-spike:ghostscript-spike
Counting objects: 14, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (10/10), 800 bytes, done.
Total 10 (delta 2), reused 0 (delta 0)
To git@github.com:toggtc/homebrew.git
   cb4768a..d3d1b15  ghostscript-spike -> ghostscript-spike
Branch ghostscript-spike set up to track remote branch ghostscript-spike from origin.


(2) GitHubに反映されたかどうかを見てみます。
f:id:toggtc:20120309220054p:image

反映されていますね!

6. 検証

実際に更新したFormulaを動かしたい場合は、GitHubのページからRawファイルのURLを取得してbrew installのパラメータに渡します。

(1) RawファイルのURLは、対象のFormulaのページを開いて「Raw」ボタンのリンクアドレスをコピーします。
f:id:toggtc:20120309220108p:image

f:id:toggtc:20120309220119p:image

(2) brew install のパラメータにRawファイルのURLを指定すればインストールが実行されます。

$ brew install https://raw.github.com … Library/Formula/ghostscript.rb

(3) 検証が済んだら、brew uninstall ghostscriptで削除できます。

$ brew uninstall ghostscript

なお、既に同一名のパッケージがインストールされているとインストール出来ませんので、事前にbrew uninstallしておきます。
#/usr/localとは別に、検証用のhomebrew環境を別ディレクトリに構築しておくといいかもしれないですね

おまけ:
なお、/usr/local側のhomebrewリポジトリをおかしくしてしまったら、以下のようにgit resetすると作業ツリーの変更等を破棄してリポジトリの最新の状態に置き換えられます。

git fetch https://github.com/mxcl/homebrew.git
git reset --hard FETCH_HEAD

それでもダメなら、homebrewを再構築してみるとうまくいくかもしれません。(無保証です)
http://toggtc.hatenablog.com/entry/2012/03/01/222352

7. 最新のマージ

Pull Request用のブランチを作る前に、本家の最新をマージしておきます。

7.1 checkout master

本家の最新を取り込むために、masterブランチに切り替えます。

$ git checkout master

後述のpullは、fetchとmergeを同時に行うのでしたね。(中編参照:http://toggtc.hatenablog.com/entry/2012/03/05/023137
mergeは現在の作業ツリーに対してマージ処理をしてしまうため、マージさせたいブランチに事前に切り替える必要があるのでした。

7.2 pull

一応、originの最新も取得しておきます。

$ git pull origin
$ git pull upstream

この時点でmasterブランチが最新化されていることを覚えておいてください。

7.3 push master

さきほどはghostscript-spikeをpushしましたが、今度は本家の最新を取り込んだmasterブランチをpushします。

$ git push -u origin master:master

7.4 checkout spike

後述のリベース作業を行うため、ghostscript-spikeブランチに切り替えます。

$ git checkout ghostscript-spike

7.5 rebase

masterが最新化できたので、リベースします。
リベースは簡単に言ってしまうと、ghotscript-spikeで行ったコミットを一度無かったことにして、最新の更新差分を反映し、その後でghostscript-spikeで行ったコミットを適用する処理です。
なぜ、ここでリベースをするかというと、Pull Requestするブランチの内容を、可能な限り本家のものと同じ状態にしたいからです。
rebaseのオプションには、どのブランチの更新をどのブランチに取り込ませるかを指定します。ここではmasterの更新をghostscript-spikeに適用しています。

$ git rebase master ghostscript-spike
Current branch ghostscript-spike is up to date.

今回は更新差分が無かったので、up to dateを表示されています。

rebaseとmergeの違い

rebaseとmergeの違いをオブジェクトグラフで見てみましょう。
以下の例では、本家のghostscriptが9.02だった頃にリポジトリをフォークを行い、その後、それぞれがコミットを加えたと仮定しています。
※オブジェクトグラフは簡略化しています。treeオブジェクトが無い、とか突っ込まないように。
f:id:toggtc:20120310004018p:image


この状態でmergeを行うと以下のようになります。
f:id:toggtc:20120310004029p:image


上図では、C2とC3の親コミットがC1であり、同一ファイルを参照しているため、マージする際にC1の子が2つになります。
ここで更新内容が競合するので手修正を行い、9.05に寄せます。ここでC4コミットが作成されます。
フォーク以降の赤枠部分の差分が、本家側はC3のみに対し、フォーク先はC3,C2,C4となっていることが分かります。
さらに、厳密にはC1にも差分が発生しています。本家のC1は子が1つなのに対し、フォーク先ではC3,C2の2つの子を持っています。

一方、rebaseを行うと以下のようなオブジェクトグラフが得られます。
f:id:toggtc:20120310004041p:image

フォーク先で変更したC2(9.05)を一旦無かったことにして、本家と同じようにC3(9.04)を加えた後、C3(9.04)の子としてC2’(9.05)を加えています。これなら差分はC2’(9.05)が増えるだけ、となり、フォーク元で更新を取り込むときに分かりやすいのです。

ただし、全てのケースでrebaseが有効であるとは限りません。
上記の例にあるC2’はC2とは似て非なるコミットです。なぜなら、rebase前と後ではオブジェクトグラフが異なるからです。前回、GitオブジェクトはImmutable(変更不可)であると述べました。ですから、既存のC2のparentにC3を設定したくてもできないのです。そこで、C3を親とする新たなコミットオブジェクトC2'を作成する、というわけです。

このことがどう影響するのでしょうか。
rebaseは差分コミットオブジェクトを無かったことにしてから再構築します。このため、rebaseする前のフォーク先のC2コミット(9.05)を、ある人がclone&checkoutして何らかの作業をしたとします。その状態であなたがrebaseをすると、C2コミットは無くなりますから、そのある人はマージ作業を強いられることになります。
特にGitHubの無料ユーザは、公開リポジトリしか作れません。もしかすると恥ずかしがり屋の誰かがあなたのリポジトリを(フォークする前に)こっそりcloneして、ローカルで作業をして、着々とPull Requestの準備を進めているかもしれません。そしてあなたの(私の)身勝手なrebaseで今夜も泣く泣くマージしているかもしれませんよ。なんてね。

7.6 push (ghostscript-spike)

rebaseした結果、何らかの差分を取り込んだ場合はリモート側に反映しておきましょう。

# git push -u <リモートリポジトリ> <ローカルブランチ>:<リモートブランチ>
$ git push -u origin ghostscript-spike:ghostscript-spike

8. Pull Request用ブランチの作成

8.1 checkout spike

Pull Reqest用ブランチを作成するため、元となるghostscript-spikeブランチに切り替えます。

$ git checkout ghostscript-spike

8.2 git checkout -b updated

checkoutコマンドに-bオプションをつけるとブランチを作成しつつ、チェックアウトを同時に行なってくれます。

$ git checkout -b ghostscript-updated
Switched to a new branch 'ghostscript-updated'

8.3 git rebase (squash)

圧縮コミットを行い、コミットログを1つにまとめます。

$ git rebase -i master

"-i"はインタラクティブモードの指定です。
masterブランチを指定しているのは、rebase対象のコミットを「フォークした時点以降」のコミットにするためです。
さきほどrebaseしたときの図でいうと、C3の位置が現在のmasterです。ここでは、C2'とC4以下にあるコミットを1まとめにしたいので、このような指定になるのです。
f:id:toggtc:20120310005159p:image

git rebase -i masterをすると、以下のような内容でエディタが起動します。

  1 pick f208bee GhostScript: update to 9.05.
  2 pick 20998bd Remove comment.
  3 
  4 # Rebase 4b41393..20998bd onto 4b41393
  5 #
  6 # Commands:
  7 #  p, pick = use commit
  8 #  r, reword = use commit, but edit the commit message
  9 #  e, edit = use commit, but stop for amending
 10 #  s, squash = use commit, but meld into previous commit
 11 #  f, fixup = like "squash", but discard this commit's log message
 12 #  x, exec = run command (the rest of the line) using shell
 13 #
 14 # If you remove a line here THAT COMMIT WILL BE LOST.
 15 # However, if you remove everything, the rebase will be aborted.
 16 #

先頭2行に表示されているのが、Fork以降に行った2つのコミットです。"Ghostscript: update to 9.05"と"Remove comment"を1つのコミットにします。
1行目はpickのままにしておきます。1行目にあるコミットを、そのままコミット対象にする、という意味です。
2行目については、pickからsquashに変更します。squashを指定すると、当該コミットとその直前のコミット(つまり1行目)を1つにまとめます。

  1 pick f208bee Ghostscript: update to 9.05.
  2 squash 20998bd Remove comment.

すると、またエディタが起動し、それぞれのコミットコメントを1つにまとめるように要求されます。

  1 # This is a combination of 3 commits.
  2 # The first commit's message is:
  3 Ghostscript: update to 9.05.
  4 
  5 # This is the 2nd commit message:$
  6 Remove comment.

ここでは2つ目のコミットコメントを削除して、"Ghostscript: update to 9.05"のみにしました。
編集を保存してエディタを終了すると、以下のようなメッセージが表示されます。

[detached HEAD e1c4135] Ghostscript: update to 9.05
 1 file changed, 62 insertions(+)
 create mode 100644 Library/Formula/ghostscript.rb
Successfully rebased and updated refs/heads/ghostscript-updated.

これで圧縮コミットの完了です。

8.4 push origin updated

圧縮コミットによってブランチを更新したのでpushして反映しましょう。
"-u"オプションを忘れずに。

$ git push -u origin ghostscript-updated:ghostscript-updated

9. Pull Request

Pull Request用のブランチがリモートに反映されたら、いよいよPull Requestを行います。

(1) まず、自分のhomebrewのGitHubページを開きます。
https://github.com/アカウント名/homebrew

(2) 右上に表示されている"Pull Request"ボタンを押します。
f:id:toggtc:20120310004250p:image

(3) "Change Commits"ボタンを押します。
#事前にpullしてもらうブランチを選択してからPull Requestを押してもOK
f:id:toggtc:20120310004304p:image

(4) ブランチ名をmasterからghostscript-updatedに変更して、"Update Commit Range"ボタンを押して確定します。
f:id:toggtc:20120310004321p:image

(5) 最後に、Pull Requestするときの件名とメッセージを入力して、"Send pull request"ボタンを押します。
f:id:toggtc:20120310004339p:image

なお、"Files Changed"タブを開くと、masterに対するファイルの差分が表示されます。
f:id:toggtc:20120310004355p:image

Pull Requestすると、本家に通知され、URLが払い出されます。
今回Pull Requestしたページは以下です。
https://github.com/mxcl/homebrew/pull/10379
#恥ずかしながら、パッチ処理のコードを一部削除しておらずadamv氏に突っ込まれていますが、なんとか本家に取り込んで貰えました。

今回はPull Request用のブランチを作成しましたが、もちろん、作業用ブランチに対して直接squashして、pull requestすることもできます。
しかし、requestしてみたら、いろいろと議論が発展して修正作業が必要になる場合も考えられなくはありません。そのため、ここでは作業用ブランチにあるコミットログはそのまま残しておいて、リリースブランチを別にしました。

10. 別のFormulaを更新する

上記の作業をひと通り終えると、既にhomebrewリポジトリをFork&cloneしている状態になるので、次からは「3. Pull Requestのチェック」から「9. Pull Request - 更新の取得を依頼する」の手順を行います。
#もちろん、この手順どおりでなけれなならない、ということはありません。Gitは多機能で恐ろしいほど柔軟性がありますので、好みの使い方ができます。


続く! 
実践Git&GitHub - homebrewをフォークするためのGit&GitHub入門 後編(2/2)
(続きの内容)
A.ブランチの削除
B. Forkしたリポジトリの削除
C. コマンドのまとめ
Gitを使った感想
参考