[インデックス 10126] ファイルの概要
このコミットは、goinstall
ツールにおけるバージョン管理システム (VCS) の選択ロジックを改善するものです。特に、BitBucket のように同じインポートパススキームで複数の VCS (Git と Mercurial) をホストするサイトに対応するため、より柔軟なアプローチが導入されました。従来の正規表現による厳密なマッチングから、サイトを識別した後にそのサイト固有のロジックで適切な VCS を判断する方式へと変更されています。
コミット
commit d066e02adc0f343b178a0d8191e719e1218ffe80
Author: Julian Phillips <julian@quantumfyre.co.uk>
Date: Thu Oct 27 17:45:07 2011 +0900
goinstall: More intelligent vcs selection for common sites
goinstall has built in support for a few common code hosting sites. The
identification of which vcs tool should be used was based purely on a
regex match against the provided import path. The problem with this
approach is that it requires distinct import paths for different vcs
tools on the same site.
Since bitbucket has recently starting hosting Git repositories under the
same bitbucket.org/user/project scheme as it already hosts Mercurial
repositories, now would seem a good time to take a more flexible
approach.
We still match the import path against a list of regexes, but now the
match is purely to distinguish the different hosting sites. Once the
site is identified, the specified function is called with the repo and
path matched out of the import string. This function is responsible for
creating the vcsMatch structure that tells us what we need to download
the code.
For github and launchpad, only one vcs tool is currently supported, so
these functions can simply return a vcsMatch structure. For googlecode,
we retain the behaviour of determing the vcs from the import path - but
now it is done by the function instead of the regex. For bitbucket, we
use api.bitbucket.org to find out what sort of repository the specified
import path corresponds to - and then construct the appropriate vcsMatch
structure.
R=golang-dev, adg
CC=golang-dev, rsc
https://golang.org/cl/5306069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d066e02adc0f343b178a0d8191e719e1218ffe80
元コミット内容
goinstall
ツールは、いくつかの一般的なコードホスティングサイトに対応していますが、これまでは提供されたインポートパスに対する正規表現のマッチングのみに基づいて使用すべき VCS ツールを識別していました。このアプローチの問題点は、同じサイト上で異なる VCS ツールを使用する場合に、それぞれ異なるインポートパスを必要とすることでした。
BitBucket が最近、既存の Mercurial リポジトリと同じ bitbucket.org/user/project
スキームで Git リポジトリのホスティングを開始したため、より柔軟なアプローチを採用する良い機会となりました。
変更後もインポートパスは正規表現のリストと照合されますが、そのマッチングは純粋に異なるホスティングサイトを区別するためだけに行われます。サイトが識別されると、インポート文字列からマッチしたリポジトリとパスが指定された関数に渡されます。この関数が、コードをダウンロードするために必要な vcsMatch
構造体を作成する責任を負います。
GitHub と Launchpad の場合、現在サポートされている VCS ツールは1つだけなので、これらの関数は単純に vcsMatch
構造体を返すことができます。Google Code の場合、VCS をインポートパスから決定する動作は維持されますが、正規表現ではなく関数によって行われるようになりました。BitBucket の場合、api.bitbucket.org
を使用して、指定されたインポートパスがどの種類のリポジトリに対応するかを調べ、適切な vcsMatch
構造体を構築します。
変更の背景
このコミットの主な背景は、コードホスティングサービスの進化、特に BitBucket のようなプラットフォームが、単一のインポートパス形式で複数のバージョン管理システム(VCS)をサポートし始めたことです。
従来の goinstall
は、インポートパスの正規表現パターンに基づいて、どの VCS(Git、Mercurial、SVN、Bazaarなど)を使用すべきかを判断していました。例えば、bitbucket.org/user/project
のようなパスを見た場合、goinstall
はそれが Mercurial リポジトリであると決めつけていました。しかし、BitBucket が同じ bitbucket.org/user/project
の形式で Git リポジトリもホストし始めたため、この単純な正規表現ベースの識別方法では、どちらの VCS を使うべきかを正確に判断できなくなりました。
この問題に対処するため、goinstall
はよりインテリジェントな VCS 選択メカニズムを必要としました。具体的には、インポートパスからホスティングサイトを特定した後、そのサイトに特化したロジックを用いて、実際に使用されている VCS を動的に判別する仕組みが求められました。これにより、将来的に他のホスティングサイトが同様の多VCSサポートを開始した場合にも、より柔軟に対応できるようになります。
前提知識の解説
goinstall
goinstall
は、Go 言語の初期のパッケージ管理ツールの一つです。Go 1.0 のリリース以前に存在し、Go のソースコードリポジトリからパッケージをフェッチし、ビルドしてインストールする機能を提供していました。現在の go get
コマンドの前身にあたります。goinstall
は、指定されたインポートパス(例: github.com/user/repo/package
)を解析し、対応するコードホスティングサイトから適切なバージョン管理システム(VCS)を使用してソースコードを取得します。
VCS (Version Control System)
VCS は、ソフトウェア開発におけるソースコードやその他のファイルの変更履歴を管理するためのシステムです。主な VCS には以下のようなものがあります。
- Git: 分散型バージョン管理システム。GitHub, GitLab, BitBucket などで広く利用されています。
- Mercurial (Hg): 分散型バージョン管理システム。BitBucket で特に人気がありました。
- Subversion (SVN): 集中型バージョン管理システム。
- Bazaar (Bzr): 分散型バージョン管理システム。Launchpad などで利用されていました。
goinstall
は、これらの VCS ツールを内部的に呼び出してリポジトリをクローンまたはチェックアウトします。
Go のインポートパス
Go 言語では、パッケージはインポートパスによって識別されます。このインポートパスは、通常、コードがホストされている場所(ドメイン名とリポジトリパス)を反映しています。例えば、import "github.com/user/repo/package"
は、GitHub 上の特定のリポジトリにあるパッケージを指します。goinstall
や go get
はこのインポートパスを解析し、対応するリポジトリからコードを取得します。
正規表現 (Regular Expressions)
正規表現は、文字列のパターンを記述するための強力なツールです。このコミット以前の goinstall
では、インポートパスが特定のホスティングサイトのパターンに一致するかどうかを判断するために正規表現が使用されていました。例えば、bitbucket.org/
で始まるパスは Mercurial リポジトリである、といった具合です。しかし、同じパターンが複数の VCS に対応するようになったことで、正規表現だけでは不十分になりました。
BitBucket API
API (Application Programming Interface) は、ソフトウェアコンポーネントが互いに通信するためのインターフェースのセットです。BitBucket API は、BitBucket のサービスとプログラム的にやり取りするためのインターフェースを提供します。このコミットでは、BitBucket API を使用して、特定のインポートパスに対応するリポジトリが Git と Mercurial のどちらであるかを問い合わせることで、goinstall
が正確な VCS を判断できるようになりました。具体的には、リポジトリのメタデータ(VCS タイプなど)を取得するために HTTP リクエストが送信され、JSON 形式でレスポンスが返されます。
技術的詳細
このコミットの核心は、goinstall
がコードホスティングサイトからリポジトリをダウンロードする際の VCS 選択ロジックを根本的に変更した点にあります。
従来の VCS 選択ロジックの問題点
変更前は、vcs
構造体(hg
, git
, svn
, bzr
など)の中に defaultHosts
というフィールドがあり、各 VCS が対応するホスティングサイトの正規表現パターンとプロトコル、サフィックスを持っていました。findPublicRepo
関数は、与えられたパッケージパスに対して、これらの defaultHosts
の正規表現を順に試行し、最初にマッチした正規表現に基づいて VCS を決定していました。
このアプローチは、例えば bitbucket.org
が常に Mercurial リポジトリをホストしているという前提であれば機能しました。しかし、BitBucket が同じ bitbucket.org/user/project
というパス形式で Git リポジトリもホストし始めたことで、この静的な正規表現マッチングでは、どちらの VCS を使うべきかを区別できなくなりました。
新しい VCS 選択ロジック
新しいアプローチでは、以下の点が変更されました。
-
vcs
構造体からのdefaultHosts
の削除: 各 VCS ツール(hg
,git
など)から、特定のホスティングサイトに関する情報(defaultHosts
)が削除されました。これにより、VCS ツール自体は汎用的なダウンロードロジックに集中できるようになります。 -
host
構造体の再定義とknownHosts
の導入: 新しいhost
構造体は、正規表現パターン (pattern
) と、そのパターンにマッチした場合に呼び出される関数 (getVcs
) を持つようになりました。knownHosts
というグローバル変数に、主要なコードホスティングサイト(Google Code, GitHub, BitBucket, Launchpad)に対応するhost
エントリのリストが定義されました。type host struct { pattern *regexp.Regexp getVcs func(repo, path string) (*vcsMatch, os.Error) } var knownHosts = []host{ {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/(svn|git|hg))(/[a-z0-9A-Z_.\-/]*)?$`), googleVcs,}, {regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), githubVcs,}, {regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), bitbucketVcs,}, {regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`), launchpadVcs,}, }
-
findPublicRepo
関数の変更:findPublicRepo
関数は、もはや各 VCS のdefaultHosts
をループするのではなく、新しく定義されたknownHosts
リストをループします。 インポートパスがhost.pattern
にマッチした場合、そのhost
に関連付けられたgetVcs
関数が呼び出されます。このgetVcs
関数が、インポートパスから抽出されたリポジトリ情報とパス情報を受け取り、最終的に適切なvcsMatch
構造体(どの VCS を使うべきか、リポジトリの URL は何かなど)を返します。func findPublicRepo(pkg string) (*vcsMatch, os.Error) { for _, host := range knownHosts { if hm := host.pattern.FindStringSubmatch(pkg); hm != nil { return host.getVcs(hm[1], hm[2]) } } return nil, nil }
-
サイト固有の
getVcs
関数の導入: 各ホスティングサイトに対して、VCS を特定するための専用の関数が導入されました。-
googleVcs(repo, path string) (*vcsMatch, os.Error)
: Google Code の場合、リポジトリパスに含まれるsvn
,git
,hg
の文字列に基づいて VCS を判断します。これは従来の正規表現による判断を関数内に移した形です。 -
githubVcs(repo, path string) (*vcsMatch, os.Error)
: GitHub の場合、常に Git リポジトリとして扱います。 -
launchpadVcs(repo, path string) (*vcsMatch, os.Error)
: Launchpad の場合、常に Bazaar リポジトリとして扱います。 -
bitbucketVcs(repo, path string) (*vcsMatch, os.Error)
: この関数が最も重要な変更点です。 BitBucket の場合、https://api.bitbucket.org/1.0/repositories/
エンドポイントに対して HTTP GET リクエストを送信し、BitBucket API を呼び出します。API から返される JSON レスポンスをパースし、scm
フィールド(Source Code Management の略)からリポジトリの VCS タイプ(git
またはhg
)を動的に取得します。これにより、同じインポートパスでも、API の情報に基づいて適切な VCS を選択できるようになりました。// bitbucketVcs 関数内で BitBucket API を呼び出す部分 r, err := http.Get(bitbucketApiUrl + parts[1]) // ... エラーハンドリング ... var response struct { Vcs string `json:"scm"` } err = json.NewDecoder(r.Body).Decode(&response) // ... エラーハンドリング ... switch response.Vcs { case "git": return &vcsMatch{&git, repo, "http://" + repo + ".git"}, nil case "hg": return &vcsMatch{&hg, repo, "http://" + repo}, nil }
-
この新しい設計により、goinstall
はより柔軟になり、将来的に新しい VCS やホスティングサイトの変更にも対応しやすくなりました。特に BitBucket のケースでは、外部 API を利用することで、インポートパスだけでは判断できない情報を取得し、正確な VCS 選択を実現しています。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/cmd/goinstall/download.go
と src/cmd/goinstall/doc.go
の2つのファイルに集中しています。
-
src/cmd/goinstall/doc.go
:BitBucket (Mercurial)
の記述がBitBucket (Git, Mercurial)
に変更され、BitBucket が Git リポジトリもサポートしていることが明記されました。
-
src/cmd/goinstall/download.go
:vcs
構造体からdefaultHosts
フィールドが削除されました。type host struct
の定義が変更され、pattern
とgetVcs
関数を持つようになりました。knownHosts
という新しいグローバル変数が導入され、各ホスティングサイトの正規表現と、VCS を特定する関数 (getVcs
) のマッピングが定義されました。googleVcs
,githubVcs
,bitbucketVcs
,launchpadVcs
という新しい関数が追加されました。これらはそれぞれ、対応するホスティングサイトのインポートパスから適切なvcsMatch
構造体を生成するロジックを含んでいます。bitbucketVcs
関数内でnet/http
パッケージとencoding/json
パッケージが使用され、BitBucket API を呼び出してリポジトリの VCS タイプを動的に取得するロジックが実装されました。findPublicRepo
関数のロジックが変更され、vcsList
のdefaultHosts
をループする代わりに、knownHosts
をループし、マッチしたhost
のgetVcs
関数を呼び出すようになりました。import "json"
が追加されました。
コアとなるコードの解説
src/cmd/goinstall/doc.go
の変更
--- a/src/cmd/goinstall/doc.go
+++ b/src/cmd/goinstall/doc.go
@@ -58,7 +58,7 @@ download the code if necessary.
Goinstall recognizes packages from a few common code hosting sites:
- BitBucket (Mercurial)
+ BitBucket (Git, Mercurial)
import "bitbucket.org/user/project"
import "bitbucket.org/user/project/sub/directory"
この変更は、goinstall
が BitBucket で Git リポジトリもサポートするようになったことをユーザーに伝えるためのドキュメントの更新です。
src/cmd/goinstall/download.go
の変更
vcs
構造体からの defaultHosts
の削除
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -56,12 +57,6 @@ type vcs struct {
defaultHosts []host
}\n-type host struct {\n-\tpattern *regexp.Regexp\n-\tprotocol string\n-\tsuffix string\n-}\n-\n var hg = vcs{\n name: "Mercurial",
cmd: "hg",
@@ -75,10 +70,6 @@ var hg = vcs{
check: "identify",
protocols: []string{"https", "http"},
suffix: ".hg",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^([a-z0-9\\-]+\\.googlecode\\.com/hg)(/[a-z0-9A-Z_.\\-/]*)?$`), "https", ""},\n-\t\t{regexp.MustCompile(`^(bitbucket\\.org/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`), "http", ""},\n-\t},\n }\n
var git = vcs{
name: "Git",
@@ -94,10 +85,6 @@ var git = vcs{
check: "ls-remote",
protocols: []string{"git", "https", "http"},
suffix: ".git",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^([a-z0-9\\-]+\\.googlecode\\.com/git)(/[a-z0-9A-Z_.\\-/]*)?$`), "https", ""},\n-\t\t{regexp.MustCompile(`^(github\\.com/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`), "http", ".git"},\n-\t},\n }\n
var svn = vcs{
name: "Subversion",
@@ -110,9 +97,6 @@ var svn = vcs{
check: "info",
protocols: []string{"https", "http", "svn"},
suffix: ".svn",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^([a-z0-9\\-]+\\.googlecode\\.com/svn)(/[a-z0-9A-Z_.\\-/]*)?$`), "https", ""},\n-\t},\n }\n
var bzr = vcs{
name: "Bazaar",
@@ -130,10 +114,6 @@ var bzr = vcs{
check: "info",
protocols: []string{"https", "http", "bzr"},
suffix: ".bzr",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^(launchpad\\.net/([a-z0-9A-Z_.\\-]+(/[a-z0-9A-Z_.\\-]+)?|~[a-z0-9A-Z_.\\-]+/(\\+junk|[a-z0-9A-Z_.\\-]+)/[a-z0-9A-Z_.\\-]+))(/[a-z0-9A-Z_.\\-/]+)?$`), "https", ""},\n-\t},\n }\n
var vcsList = []*vcs{&git, &hg, &bzr, &svn}\n
各 VCS 定義から defaultHosts
フィールドが削除されました。これは、VCS の選択ロジックが各 VCS の定義から分離され、より中央集中的な knownHosts
に移行したことを意味します。
新しい host
構造体と knownHosts
の導入
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -130,30 +114,110 @@ var bzr = vcs{
check: "info",
protocols: []string{"https", "http", "bzr"},
suffix: ".bzr",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^(launchpad\\.net/([a-z0-9A-Z_.\\-]+(/[a-z0-9A-Z_.\\-]+)?|~[a-z0-9A-Z_.\\-]+/(\\+junk|[a-z0-9A-Z_.\\-]+)/[a-z0-9A-Z_.\\-]+))(/[a-z0-9A-Z_.\\-/]+)?$`), "https", ""},\n-\t},\n }\n
var vcsList = []*vcs{&git, &hg, &bzr, &svn}\n
+type host struct {\n+\tpattern *regexp.Regexp\n+\tgetVcs func(repo, path string) (*vcsMatch, os.Error)\n+}\n+\n+var knownHosts = []host{\n+\t{\n+\t\tregexp.MustCompile(`^([a-z0-9\\-]+\\.googlecode\\.com/(svn|git|hg))(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tgoogleVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(github\\.com/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tgithubVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(bitbucket\\.org/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tbitbucketVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(launchpad\\.net/([a-z0-9A-Z_.\\-]+(/[a-z0-9A-Z_.\\-]+)?|~[a-z0-9A-Z_.\\-]+/(\\+junk|[a-z0-9A-Z_.\\-]+)/[a-z0-9A-Z_.\\-]+))(/[a-z0-9A-Z_.\\-/]+)?$`),\n+\t\tlaunchpadVcs,\n+\t},\n+}\n+\n type vcsMatch struct {\n *vcs\n prefix, repo string\n }\n
新しい host
構造体は、インポートパスの正規表現パターン (pattern
) と、そのパターンにマッチした場合に呼び出される関数 (getVcs
) を定義します。knownHosts
は、goinstall
が認識する主要なホスティングサイトのリストであり、それぞれに対応する getVcs
関数が指定されています。
サイト固有の getVcs
関数の実装
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -130,30 +114,110 @@ var bzr = vcs{
check: "info",
protocols: []string{"https", "http", "bzr"},
suffix: ".bzr",
-\tdefaultHosts: []host{\n-\t\t{regexp.MustCompile(`^(launchpad\\.net/([a-z0-9A-Z_.\\-]+(/[a-z0-9A-Z_.\\-]+)?|~[a-z0-9A-Z_.\\-]+/(\\+junk|[a-z0-9A-Z_.\\-]+)/[a-z0-9A-Z_.\\-]+))(/[a-z0-9A-Z_.\\-/]+)?$`), "https", ""},\n-\t},\n }\n
var vcsList = []*vcs{&git, &hg, &bzr, &svn}\n
+type host struct {\n+\tpattern *regexp.Regexp\n+\tgetVcs func(repo, path string) (*vcsMatch, os.Error)\n+}\n+\n+var knownHosts = []host{\n+\t{\n+\t\tregexp.MustCompile(`^([a-z0-9\\-]+\\.googlecode\\.com/(svn|git|hg))(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tgoogleVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(github\\.com/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tgithubVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(bitbucket\\.org/[a-z0-9A-Z_.\\-]+/[a-z0-9A-Z_.\\-]+)(/[a-z0-9A-Z_.\\-/]*)?$`),\n+\t\tbitbucketVcs,\n+\t},\n+\t{\n+\t\tregexp.MustCompile(`^(launchpad\\.net/([a-z0-9A-Z_.\\-]+(/[a-z0-9A-Z_.\\-]+)?|~[a-z0-9A-Z_.\\-]+/(\\+junk|[a-z0-9A-Z_.\\-]+)/[a-z0-9A-Z_.\\-]+))(/[a-z0-9A-Z_.\\-/]+)?$`),\n+\t\tlaunchpadVcs,\n+\t},\n+}\n+\n type vcsMatch struct {\n *vcs\n prefix, repo string\n }\n \n+func googleVcs(repo, path string) (*vcsMatch, os.Error) {\n+\tparts := strings.SplitN(repo, \"/\", 2)\n+\turl := \"https://\" + repo\n+\tswitch parts[1] {\n+\tcase \"svn\":\n+\t\treturn &vcsMatch{&svn, repo, url}, nil\n+\tcase \"git\":\n+\t\treturn &vcsMatch{&git, repo, url}, nil\n+\tcase \"hg\":\n+\t\treturn &vcsMatch{&hg, repo, url}, nil\n+\t}\n+\treturn nil, os.NewError(\"unsupported googlecode vcs: \" + parts[1])\n+}\n+\n+func githubVcs(repo, path string) (*vcsMatch, os.Error) {\n+\tif strings.HasSuffix(repo, \".git\") {\n+\t\treturn nil, os.NewError(\"path must not include .git suffix\")\n+\t}\n+\treturn &vcsMatch{&git, repo, \"http://\" + repo + \".git\"}, nil\n+}\n+\n+func bitbucketVcs(repo, path string) (*vcsMatch, os.Error) {\n+\tconst bitbucketApiUrl = \"https://api.bitbucket.org/1.0/repositories/\"\n+\n+\tif strings.HasSuffix(repo, \".git\") {\n+\t\treturn nil, os.NewError(\"path must not include .git suffix\")\n+\t}\n+\n+\tparts := strings.SplitN(repo, \"/\", 2)\n+\n+\t// Ask the bitbucket API what kind of repository this is.\n+\tr, err := http.Get(bitbucketApiUrl + parts[1])\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"error querying BitBucket API: %v\", err)\n+\t}\n+\tdefer r.Body.Close()\n+\n+\t// Did we get a useful response?\n+\tif r.StatusCode != 200 {\n+\t\treturn nil, fmt.Errorf(\"error querying BitBucket API: %v\", r.Status)\n+\t}\n+\n+\tvar response struct {\n+\t\tVcs string `json:\"scm\"`\n+\t}\n+\terr = json.NewDecoder(r.Body).Decode(&response)\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"error querying BitBucket API: %v\", err)\n+\t}\n+\n+\t// Now we should be able to construct a vcsMatch structure\n+\tswitch response.Vcs {\n+\tcase \"git\":\n+\t\treturn &vcsMatch{&git, repo, \"http://\" + repo + \".git\"}, nil\n+\tcase \"hg\":\n+\t\treturn &vcsMatch{&hg, repo, \"http://\" + repo}, nil\n+\t}\n+\n+\treturn nil, os.NewError(\"unsupported bitbucket vcs: \" + response.Vcs)\n+}\n+\n+func launchpadVcs(repo, path string) (*vcsMatch, os.Error) {\n+\treturn &vcsMatch{&bzr, repo, \"https://\" + repo}, nil\n+}\n+\n // findPublicRepo checks whether pkg is located at one of\n // the supported code hosting sites and, if so, returns a match.\n func findPublicRepo(pkg string) (*vcsMatch, os.Error) {\n-\tfor _, v := range vcsList {\n-\t\tfor _, host := range v.defaultHosts {\n-\t\t\tif hm := host.pattern.FindStringSubmatch(pkg); hm != nil {\n-\t\t\t\tif host.suffix != \"\" && strings.HasSuffix(hm[1], host.suffix) {\n-\t\t\t\t\treturn nil, os.NewError(\"repository \" + pkg + \" should not have \" + v.suffix + \" suffix\")\n-\t\t\t\t}\n-\t\t\t\trepo := host.protocol + \"://\" + hm[1] + host.suffix\n-\t\t\t\treturn &vcsMatch{v, hm[1], repo}, nil\n-\t\t\t}\n+\tfor _, host := range knownHosts {\n+\t\tif hm := host.pattern.FindStringSubmatch(pkg); hm != nil {\n+\t\t\treturn host.getVcs(hm[1], hm[2])\n \t\t}\n \t}\n \treturn nil, nil\n```
* **`googleVcs`**: Google Code のインポートパスは `googlecode.com/(svn|git|hg)` の形式で VCS タイプを含んでいるため、パスの `parts[1]` を見て `svn`, `git`, `hg` のいずれかを判断し、対応する `vcsMatch` を返します。
* **`githubVcs`**: GitHub は主に Git を使用するため、常に Git の `vcsMatch` を返します。`.git` サフィックスが含まれている場合はエラーを返します。
* **`bitbucketVcs`**:
* `bitbucketApiUrl` 定数で BitBucket API のエンドポイントを定義します。
* インポートパスに `.git` サフィックスが含まれている場合はエラーを返します。
* `http.Get` を使用して BitBucket API に HTTP リクエストを送信し、リポジトリの情報を取得します。
* レスポンスのステータスコードが 200 でない場合や、JSON のデコードに失敗した場合はエラーを返します。
* `json.NewDecoder(r.Body).Decode(&response)` を使用して、API から返された JSON レスポンスを `response` 構造体にデコードします。`response` 構造体は `Vcs string \`json:"scm"\` を持ち、これにより JSON の `scm` フィールドの値が `Vcs` フィールドにマッピングされます。
* デコードされた `response.Vcs` の値(`git` または `hg`)に基づいて、適切な `vcsMatch` 構造体(Git または Mercurial)を構築して返します。
* **`launchpadVcs`**: Launchpad は主に Bazaar を使用するため、常に Bazaar の `vcsMatch` を返します。
#### `findPublicRepo` 関数の変更
```diff
--- a/src/cmd/goinstall/download.go
+++ b/src/cmd/goinstall/download.go
@@ -160,13 +144,9 @@ func launchpadVcs(repo, path string) (*vcsMatch, os.Error) {
// findPublicRepo checks whether pkg is located at one of
// the supported code hosting sites and, if so, returns a match.
func findPublicRepo(pkg string) (*vcsMatch, os.Error) {
-\tfor _, v := range vcsList {\n-\t\tfor _, host := range v.defaultHosts {\n-\t\t\tif hm := host.pattern.FindStringSubmatch(pkg); hm != nil {\n-\t\t\t\tif host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) {\n-\t\t\t\t\treturn nil, os.NewError("repository " + pkg + " should not have " + v.suffix + " suffix")\n-\t\t\t\t}\n-\t\t\t\trepo := host.protocol + "://" + hm[1] + host.suffix\n-\t\t\t\treturn &vcsMatch{v, hm[1], repo}, nil\n-\t\t\t}\n+\tfor _, host := range knownHosts {\n+\t\tif hm := host.pattern.FindStringSubmatch(pkg); hm != nil {\n+\t\t\treturn host.getVcs(hm[1], hm[2])\n \t\t}\n \t}\n \treturn nil, nil\n```
`findPublicRepo` 関数は、`knownHosts` リストをループし、各 `host` の `pattern` と `pkg` をマッチさせます。マッチが見つかった場合、その `host` に関連付けられた `getVcs` 関数を呼び出し、その結果を返します。これにより、VCS の選択がサイト固有のロジックに委ねられるようになりました。
## 関連リンク
* Go CL 5306069: https://golang.org/cl/5306069
## 参考にした情報源リンク
* BitBucket API Documentation (当時のバージョン): [https://developer.atlassian.com/bitbucket/api/1/rest/](https://developer.atlassian.com/bitbucket/api/1/rest/) (当時の API ドキュメントは現在と異なる可能性があります)
* Go 言語のパッケージ管理に関する歴史: [https://go.dev/doc/go1.0#goinstall](https://go.dev/doc/go1.0#goinstall) (Go 1.0 のリリースノートに `goinstall` から `go get` への移行に関する記述があります)
* Go 言語の正規表現パッケージ: [https://pkg.go.dev/regexp](https://pkg.go.dev/regexp)
* Go 言語の HTTP クライアントパッケージ: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
* Go 言語の JSON エンコーディング/デコーディングパッケージ: [https://pkg.go.dev/encoding/json](https://pkg.go.dev/encoding/json)
* Git 公式サイト: [https://git-scm.com/](https://git-scm.com/)
* Mercurial 公式サイト: [https://www.mercurial-scm.org/](https://www.mercurial-scm.org/)
* Subversion 公式サイト: [https://subversion.apache.org/](https://subversion.apache.org/)
* Bazaar 公式サイト: [http://bazaar.canonical.com/](http://bazaar.canonical.com/)
* GitHub: [https://github.com/](https://github.com/)
* BitBucket: [https://bitbucket.org/](https://bitbucket.org/)
* Google Code (現在は閉鎖): (当時の情報源として言及)
* Launchpad: [https://launchpad.net/](https://launchpad.net/)