[インデックス 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/)