2010/01/07

git リポジトリを HTTP で公開する設定でハマリました

私の関係しているプロジェクトで、空 git リポジトリを作成し、HTTP 経由でアクセス出来るように設定する必要がありました。

git リポジトリ HTTP 経由でアクセスできるように公開する設定方法に関しては、「Setting up a git repository which can be pushed into and pulled from over HTTP(S).が大変参考になります。また、ありがたい事に、このドキュメントは、Yuanyingさんによって日本語訳も公開されています。

既に過去何回か行った案件であり、且つドキュメントが整備されているにも関わらず、情けないことに再びハマってしまったので、いくつかポイントを整理しておこうと思います。

まずサーバ側の設定作業ですが、setup-git-server-over-http.txtに書かれている通り、概要は次のような手順になります。

1. HTTP サーバを用意する
2. 認証を含めた WebDav の設定を行う
3. リポジトリを準備する

「リポジトリを準備する」は、コマンドライン的にはこんな感じです。

% su
# cd /var/www  # HTTPサーバで設定したリポジトリの公開場所
# mkdir sample_rep.git
# cd sample_rep.git
# git init --bare
Initialized empty Git repository in /var/www/sample_rep.git/
# git update-server-info
# chown -R www . # HTTPサーバを動作させる user が www である場合

ポイントは「git update-server-info を実行すること」「パーミッションを正しく設定しておくこと」の2点で、非常にシンプルです。

このリポジトリを pull/push するクライアント側の設定手順は、以下の通りです。ここで、git クライアントは MacOS X 上の 1.6.6 を想定していますが、特に OS 依存の箇所はないかと思います。またサーバとクライアントの間には firewall は存在しないことを仮定しています。
  1. ~/.netrc を適切に設定する
  2. ユーザ名の含まれていない URL を用いて、リポジトリを clone する
  3. ローカルリポジトリへファイルを commit する
  4. リモート側のリポジトリとローカル側の参照を指定して push する
以下、これらの作業に関係して、私がハマってしまった事例とともにを説明します。

1. ~/.netrc を使うのであれば、URL へユーザ名を埋め込んではならない

認証の必要な http URL へ git でアクセスするには2つの指定方法があります。1つは、ユーザ名をURL内へ埋め込む方法で、もう1つは curl経由でのアクセスを利用し curl が参照する .netrc へ認証情報を書いておく方法です。

前者は、例えば、こんな感じになります。

% git clone http://username:password@server.example.com/sample_rep.git

とはいえ、URLへパスワードを直接書いてしまう方法は好ましい方法ではなく、あまり現実には使われないかと思います。

パスワードを埋め込まない指定も可能です。

% git clone http://username@server.example.com/sample_rep.git

この場合、別途パスワードの入力が求められます。

% git clone http://username@server.example.com/sample_rep.git
Initialized empty Git repository in /users/n-miyo/p/sample_rep/.git/
Password: 

この方式の問題は、頻繁にパスワードの入力を求められることです。何度もパスワードを入力することは、危機意識が薄くなるので、あまり好ましいことではありません。

一方、ホームディレクトリへ .netrc というファイルを作成し、その中に認証情報を書いておく、という方式も利用可能です。HTTP アクセスで利用される libcurl がこの情報を参照し、適切に認証情報を送り出してくれます。

.netrc の記述方法に関しては、MacOS X では ftp(1) のマニュアルページに詳しいですが、基本的には、machine 行へ該当ホスト情報、login へユーザ名、password へパスワードを書いておきます。

% cat ~/.netrc
machine server.example.com
login n-miyo
password PASSWORD

このファイルが読まれてしまわないよう、パーミッションを 600 へ設定するなど、十分気を付けましょう。

.netrc を使う方法であれば、URL へユーザ名やパスワードを埋め込む必要はありません。

% git clone http://server.example.com/sample_rep.git
Initialized empty Git repository in /users/n-miyo/p/sample_rep/.git/
% 

さて、~/.netrc が適切に設定されている状態で、前者の様に URL 内へユーザ名を埋め込んだ場合はどうなるのでしょうか。この場合、まずはパスワードの入力が求められることになります。もし、return を空打ちするなどにより、パスワードへ空文字が入力された場合には .netrc の設定が使われることになります(パスワードを入力した場合には、そのパスワードが利用されます)。

私は、はじめにユーザ名付きの URL でサーバへの接続性を確認をし、その後、~/.netrc を設定する、という手順を踏んでしまったため、どんなに正しく ~/.netrc を設定しても Password 入力プロンプトが表示され続けてしまうことになり、悩んでしまいました。

もし、.netrc を適切に設定したにも関わらず、Password を求められる状況になった場合には、リモートリポジトリの URL を確認してみてください。git remote -v で確認できます。

% git remote -v
origin http://server.example.com/sample_rep.git (fetch)
origin http://server.example.com/sample_rep.git (push)


2. 空リポジトリから clone した場合、初回 git push へは引数指定が必要

既に commit の存在する git リポジトリを clone した場合、この clone 元へローカルの変更を push する場合、特に引数を指定する必要はありません。

% git push
Fetching remote heads...
refs/
...

一方、今回のように、空の git リポジトリから clone により作成したリポジトリへ push する場合には、push 先と、push 対象の branch を明示的に指定しなければなりません。例えば、.git/config で「origin」として命名されているリモートリポジトリへ、ローカルの「master」ブランチを push する場合には、次の通りです。

% git push origin master

ただし、もちろん push する前には、ローカルのリポジトリへ何らかの commit が必要です。commit が存在しない場合、push はエラーになります

% git push origin master
error: src refspec master does not match any.
error: failed to push some refs to 'http://server.example.com/sample_rep.git'

私は git push の引数を正しく理解していなかったため、「clone 出来ているのだから、push できない筈はない」と、サーバの認証設定ばかりを疑い、時間をとられてしまいました。

空リポジトリから clone した場合、 git push へ失敗する場合には、push の引数で明示的に push に必要な情報が指定されているか、または、ローカルリポジトリへ commit が存在するかを確認しましょう。


3. 認証の失敗メッセージへ注意する

認証の必要な URL に対して git clone や push を行い、その動作が認証エラーになると、その旨を示すエラーが表示されます。

% git clone http://server.example.com/sample_rep.git
Initialized empty Git repository in /users/n-miyo/p/sample_rep/.git/
error: The requested URL returned error: 401 while accessing http://server.example.com/sample_rep.git/info/refs?service=git-upload-pack

fatal: HTTP request failed

しかし、特定条件では、一見認証エラーなのか、設定エラーなのか戸惑うエラーが表示される場合もあります。

例えば HTTP サーバの WebDAV で「clone は誰でも可能だが push は禁止する」というような、すなわち clone 時には認証は必要ない、という設定がなされていたとします。この状態で、ローカルで行った変更を push しようとした場合、例えば .netrc の認証情報が誤っていると、git push は次のようなエラーを表示します。

% git push origin master
Unable to create branch path http://server.example.com/sample_rep.git/info/
error: cannot lock existing info/refs
fatal: git-http-push failed

先ほどと異なり、HTTP のステータスコードは表示されず、lock の獲得に失敗したことを示すエラーです。一見、HTTPサーバ側でのファイルパーミション設定誤りを疑いたくなりますが、実際パーミションの設定が誤っていた場合のエラーは次のようになります。

% git push origin master
Fetching remote heads...
 refs/
 refs/heads/
 refs/tags/
updating 'refs/heads/master'
 from 0000000000000000000000000000000000000000
 to   94d642b7e985ae90956700855e158d0e885628ba
Unable to lock remote branch refs/heads/master
Updating remote server info
PUT error: curl result=22, HTTP code=403
fatal: git-http-push failed

同じくロックに関するエラーメッセージが表示されていますが、こちらは最後に、HTTPステータスコードの403(Forbidden)も示されています。

私は、「lock が獲得出来ないのは、ファイルのアクセス権が問題だ」と誤った思い込みへ陥り、本来いじる必要のない、サーバ側のパーミション設定と格闘するハメになりました。

HTTPサーバへ Apache を使っている場合、例えば、httpd-access.log を見るとこの両者の判別がつけやすくなります。例えば認証が失敗している場合、MKCOL コマンドに対するステータスコードとして 401 が返ります。

192.168.1.1 - - [07/Jan/2010:21:26:47 +0900] "MKCOL /var/www/sample_rep.git/info/ HTTP/1.1" 401 401 "-" "git/1.6.6"

一方、HTTPサーバの設定を誤って書き込めない場合には、PUT のステータスコードが 403 を示します。

192.168.1.1 - n-miyo [07/Jan/2010:21:27:04 +0900] "PUT /var/www/sample_rep.git/info/refs HTTP/1.1" 403 234 "-" "git/1.6.6"

この差を頭の片隅に置いておくと、git push でエラーになった場合、原因究明の一助にはなるかもしれません。

一度設定できてしまえば http 経由での git アクセスは、快適に利用することが可能です。
これらの情報がトラブルシュートのお役に立てばと思います。

0 件のコメント:

コメントを投稿