[インデックス 10477] ファイルの概要
このコミットは、Go言語のパッケージインストールツールである goinstall
の機能改善に関するものです。具体的には、Google Codeのサブリポジトリのサポートを追加し、リポジトリのマッチングロジックをテストするための新しいテストケースを導入しています。さらに、goinstall
がリポジトリのチェックアウトや更新が必要な場合にのみネットワークアクセスを行うように最適化されています。
コミット
commit 86c08e961136f01d34db7759166433d55e8914b2
Author: Andrew Gerrand <adg@golang.org>
Date: Tue Nov 22 07:10:25 2011 +1100
goinstall: support googlecode subrepos and add repo match tests
goinstall: don't hit network unless a checkout or update is required
R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/5343042
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/86c08e961136f01d34db7759166433d55e8914b2
元コミット内容
goinstall: support googlecode subrepos and add repo match tests
goinstall: don't hit network unless a checkout or update is required
R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/5343042
変更の背景
goinstall
は、Go言語のパッケージをリモートリポジトリから取得し、ローカルにインストールするためのコマンドラインツールです。このコミットが行われた2011年当時、Google Codeは多くのオープンソースプロジェクトで利用されており、特にGo言語のプロジェクトも多数ホストされていました。
従来の goinstall
は、project.googlecode.com/svn/trunk
のような形式のGoogle Codeリポジトリはサポートしていましたが、code.google.com/p/project.subrepo/path
のような形式の「サブリポジトリ」には対応していませんでした。これは、Google Codeが単一のプロジェクト内で複数のVCS(バージョン管理システム)リポジトリをホストできる機能を提供していたためです。ユーザーがこのようなサブリポジトリを goinstall
で取得しようとすると、正しく認識されず、ダウンロードに失敗する問題がありました。
また、goinstall
はリポジトリの存在確認やVCSタイプの特定のために、不必要にネットワークアクセスを行う可能性がありました。これは、特にオフライン環境やネットワークが不安定な環境でのパフォーマンス低下や、不必要な帯域幅の消費につながります。
このコミットは、これらの問題を解決するために、以下の2つの主要な目的を持って行われました。
- Google Codeサブリポジトリのサポート:
code.google.com/p/project.subrepo
形式のインポートパスを正しく解析し、対応するVCS(Git, Mercurial, Subversionなど)を特定してダウンロードできるようにする。 - ネットワークアクセスの最適化: リポジトリが既にローカルに存在し、かつ更新が不要な場合には、ネットワークにアクセスしないように
goinstall
のロジックを改善する。
前提知識の解説
goinstall
とは
goinstall
は、Go言語の初期のパッケージ管理ツールです。現在の go get
コマンドの前身にあたります。Goのソースコードは通常、import "path/to/package"
の形式でインポートされますが、goinstall
はこのインポートパスを解析し、対応するリモートリポジトリからソースコードをダウンロードして $GOPATH/src
以下に配置し、コンパイル・インストールまで行います。
バージョン管理システム (VCS)
- Git: 分散型バージョン管理システム。GitHubなどで広く利用されています。
- Mercurial (Hg): 分散型バージョン管理システム。Bitbucketなどで利用されていました。
- Subversion (SVN): 集中型バージョン管理システム。Google Codeなどで利用されていました。
- Bazaar (Bzr): 分散型バージョン管理システム。Launchpadなどで利用されていました。
goinstall
は、これらのVCSに対応し、それぞれのコマンド(git clone
, hg clone
, svn checkout
, bzr branch
など)を内部的に呼び出してリポジトリを操作します。
Google Code Project Hosting
2016年にサービスを終了しましたが、かつてGoogleが提供していたオープンソースプロジェクトのホスティングサービスです。Git, Mercurial, SubversionのいずれかのVCSをサポートし、単一のプロジェクト内で複数のリポジトリ(メインリポジトリとサブリポジトリ)を持つことができました。サブリポジトリは通常、code.google.com/p/project.subrepo
のようなURL構造を持っていました。
リポジトリの検出とマッチング
goinstall
がインポートパスを受け取った際、それがどのVCSのどのリポジトリに対応するかを判断する必要があります。これには、正規表現によるURLパターンのマッチングや、特定のホスティングサービスのAPI(例: Bitbucket API)を利用したVCSタイプの検出などが含まれます。
技術的詳細
このコミットの主要な変更点は、リポジトリの検出ロジックの再設計と、ネットワークアクセスの条件付き実行です。
RemoteRepo
インターフェースの導入
以前は vcsMatch
という構造体がVCSとリポジトリの情報を保持していましたが、このコミットでは RemoteRepo
という新しいインターフェースが導入されました。
type RemoteRepo interface {
// IsCheckedOut returns whether this repository is checked
// out inside the given srcDir (eg, $GOPATH/src).
IsCheckedOut(srcDir string) bool
// Repo returns the information about this repository: its url,
// the part of the import path that forms the repository root,
// and the version control system it uses. It may discover this
// information by using the supplied client to make HTTP requests.
Repo(_ *http.Client) (url, root string, vcs *vcs, err error)
}
このインターフェースは、以下の2つのメソッドを定義します。
IsCheckedOut(srcDir string) bool
: 指定されたsrcDir
内にこのリポジトリが既にチェックアウトされているかどうかを報告します。これにより、不必要なネットワークアクセスを避けることができます。Repo(_ *http.Client) (url, root string, vcs *vcs, err error)
: リポジトリのURL、リポジトリのルートとなるインポートパス、および使用されているVCSに関する情報を返します。このメソッドは、必要に応じてHTTPクライアントを使用してネットワークアクセスを行うことができます。
baseRepo
構造体
RemoteRepo
インターフェースの基本的な実装として baseRepo
構造体が導入されました。これは、リポジトリのURL、ルート、およびVCSの基本的な情報を保持します。
type baseRepo struct {
url, root string
vcs *vcs
}
特定のホスティングサービス向けのリポジトリ実装
googleSubrepo
:code.google.com/p/project.subrepo
形式のGoogle Codeサブリポジトリを処理するためのRemoteRepo
実装です。この実装は、Google CodeのソースチェックアウトページをスクレイピングしてVCSタイプ(hg, git, svn)を検出します。これは、Google CodeがAPIを提供していなかったため、HTML解析によってVCS情報を取得する必要があったためです。bitbucketRepo
:bitbucket.org
のリポジトリを処理するためのRemoteRepo
実装です。この実装は、BitbucketのAPI (https://api.bitbucket.org/1.0/repositories/
) を利用して、リポジトリのVCSタイプ(git, hg)を検出します。
これらのカスタム実装により、goinstall
は各ホスティングサービスの特性(APIの有無、VCS情報の提供方法など)に応じて、より正確にリポジトリ情報を取得できるようになりました。
findPublicRepo
と findAnyRepo
の変更
findPublicRepo(importPath string) (RemoteRepo, error)
: 既知のパブリックホスティングサイト(Google Code, GitHub, Bitbucket, Launchpadなど)のパターンにimportPath
が一致するかどうかをチェックし、一致した場合は対応するRemoteRepo
インターフェースの実装を返します。findAnyRepo(importPath string) RemoteRepo
:importPath
内に.git
,.hg
,.svn
,.bzr
などのVCSサフィックスが含まれている場合に、それに対応するRemoteRepo
実装を返します。
これらの関数は、RemoteRepo
インターフェースを返すように変更され、リポジトリ情報の取得とVCSタイプの特定がより抽象化されました。
download
関数の最適化
download
関数は、パッケージのダウンロードと更新の主要なロジックを担っています。このコミットでは、download
関数が RemoteRepo
インターフェースを利用するように変更されました。
最も重要な変更は、repo.IsCheckedOut(srcDir)
を呼び出すことで、リポジトリが既にローカルに存在するかどうかを最初に確認する点です。
- もしリポジトリが既にチェックアウトされている場合、
goinstall
は-u
(update) フラグが指定されている場合にのみ、repo.Repo(http.DefaultClient)
を呼び出してネットワークアクセスを行い、リポジトリを更新します。 - リポジトリがまだチェックアウトされていない場合のみ、
repo.Repo(http.DefaultClient)
を呼び出してリポジトリのURLとVCS情報を取得し、vcs.clone
コマンドでリポジトリをクローンします。
これにより、goinstall
は不必要なネットワークアクセスを大幅に削減し、パフォーマンスを向上させることができます。
テストの追加 (download_test.go
)
新しい download_test.go
ファイルが追加され、findPublicRepo
関数の動作を検証するための包括的なテストケースが導入されました。これらのテストは、Google Codeサブリポジトリ、Bitbucket、GitHub、Launchpadなど、様々なホスティングサイトのインポートパスが正しく解析され、適切なVCSタイプとリポジトリURLが返されることを確認します。特に、BitbucketやGoogle Codeサブリポジトリのように、VCSタイプを検出するためにネットワークアクセス(HTTPリクエスト)が必要なケースも模擬的にテストされています。
コアとなるコードの変更箇所
src/cmd/goinstall/download.go
vcs
構造体からdefaultHosts
フィールドが削除され、vcsMap
というグローバルマップにVCS定義が移動。RemoteRepo
インターフェースの定義と、その実装であるbaseRepo
,googleSubrepo
,bitbucketRepo
構造体の追加。knownHosts
変数のgetVcs
フィールドがrepo
フィールドに変更され、RemoteRepo
を返す関数を指すように変更。googleVcs
,githubVcs
,bitbucketVcs
,launchpadVcs
関数が、それぞれmatchGoogleRepo
,matchGithubRepo
,matchBitbucketRepo
,matchLaunchpadRepo
にリファクタリングされ、RemoteRepo
を返すように変更。findPublicRepo
およびfindAnyRepo
関数がRemoteRepo
を返すように変更。download
関数がRemoteRepo
インターフェースを利用するように変更され、IsCheckedOut
メソッドによるネットワークアクセスの最適化が実装。checkoutRepo
関数がRemoteRepo
を引数に取るように変更され、リポジトリのチェックアウトと更新ロジックをカプセル化。isDir
ヘルパー関数の追加。
src/cmd/goinstall/download_test.go
(新規ファイル)
FindPublicRepoTests
というテストデータ構造の定義。TestFindPublicRepo
関数によるfindPublicRepo
のテスト実装。testTransport
構造体とRoundTrip
メソッドによるHTTPトランスポートのモック実装。これにより、ネットワークアクセスを伴うリポジトリ検出ロジック(Google Codeスクレイピング、Bitbucket API呼び出し)をテスト可能に。
src/cmd/goinstall/doc.go
- Google Codeサブリポジトリのインポートパス形式 (
code.google.com/p/project.subrepo/sub/directory
) に関するドキュメントの追加。
src/cmd/goinstall/main.go
install
関数内でfindPublicRepo
の呼び出しと、public
フラグの設定方法がRemoteRepo
インターフェースの利用に合わせて調整。
コアとなるコードの解説
src/cmd/goinstall/download.go
の変更点
// download checks out or updates the specified package from the remote server.
func download(importPath, srcDir string) (public bool, err error) {
if strings.Contains(importPath, "..") {
err = errors.New("invalid path (contains ..)")
return
}
repo, err := findPublicRepo(importPath) // まずパブリックリポジトリとして検出を試みる
if err != nil {
return false, err
}
if repo != nil { // パブリックリポジトリとして検出された場合
public = true
} else { // パブリックリポジトリとして検出されなかった場合、任意のVCSサフィックスを持つリポジトリとして検出を試みる
repo = findAnyRepo(importPath)
}
if repo == nil { // どちらの方法でも検出できなかった場合
err = errors.New("cannot download: " + importPath)
return
}
err = checkoutRepo(srcDir, repo) // リポジトリのチェックアウトまたは更新を実行
return
}
// checkoutRepo checks out repo into srcDir (if it's not checked out already)
// and, if the -u flag is set, updates the repository.
func checkoutRepo(srcDir string, repo RemoteRepo) error {
if !repo.IsCheckedOut(srcDir) { // リポジトリがまだチェックアウトされていない場合
// do checkout
url, root, vcs, err := repo.Repo(http.DefaultClient) // ネットワークアクセスを伴う可能性のあるRepoメソッドを呼び出し
if err != nil {
return err
}
repoPath := filepath.Join(srcDir, root)
parent, _ := filepath.Split(repoPath)
if err = os.MkdirAll(parent, 0777); err != nil {
return err
}
// クローンコマンドを実行
if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, url, repoPath); err != nil {
return err
}
return vcs.updateRepo(repoPath) // クローン後、リポジトリを更新
}
if *update { // リポジトリが既にチェックアウトされており、かつ-uフラグが指定されている場合
// do update
_, root, vcs, err := repo.Repo(http.DefaultClient) // ネットワークアクセスを伴う可能性のあるRepoメソッドを呼び出し
if err != nil {
return err
}
repoPath := filepath.Join(srcDir, root)
// pullコマンドを実行(VCSがサポートしている場合)
if vcs.pull != "" {
if vcs.pullForceFlag != "" {
if err = run(repoPath, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil {
return err
}
} else if err = run(repoPath, nil, vcs.cmd, vcs.pull); err != nil {
return err
}
}
return vcs.updateRepo(repoPath) // リポジトリを更新
}
return nil // リポジトリが既にチェックアウトされており、-uフラグも指定されていない場合、何もしない
}
download
関数は、まず findPublicRepo
で既知のホスティングサイトのリポジトリとして検出を試みます。成功すれば public
フラグを true
に設定します。失敗した場合は findAnyRepo
でVCSサフィックスによる検出を試みます。どちらも失敗すればエラーを返します。
最も重要なのは checkoutRepo
関数です。この関数は、repo.IsCheckedOut(srcDir)
を呼び出すことで、リポジトリがローカルに存在するかどうかを最初に確認します。
- リポジトリがローカルに存在しない場合:
repo.Repo(http.DefaultClient)
を呼び出してリポジトリのURLとVCS情報を取得し、vcs.clone
コマンドでリポジトリをクローンします。この際に初めてネットワークアクセスが発生します。 - リポジトリがローカルに存在する場合:
*update
グローバル変数(コマンドラインの-u
フラグに対応)がtrue
でない限り、repo.Repo
を呼び出さず、したがってネットワークアクセスも行いません。これにより、不必要なネットワークヒットが回避されます。-u
がtrue
の場合のみ、repo.Repo
を呼び出して最新の情報を取得し、vcs.pull
やvcs.update
コマンドでリポジトリを更新します。
src/cmd/goinstall/download_test.go
の変更点
// testTransport は http.RoundTripper インターフェースを実装し、HTTPリクエストをモックする。
type testTransport struct {
expectURL string // 期待されるリクエストURL
responseBody string // 返すレスポンスボディ
}
func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if g, e := req.URL.String(), t.expectURL; g != e {
return nil, errors.New("want " + e) // URLが一致しない場合はエラー
}
body := ioutil.NopCloser(bytes.NewBufferString(t.responseBody))
return &http.Response{
StatusCode: http.StatusOK,
Body: body,
}, nil
}
func TestFindPublicRepo(t *testing.T) {
for _, test := range FindPublicRepoTests {
client := http.DefaultClient
if test.transport != nil { // testTransport が設定されている場合、カスタムHTTPクライアントを使用
client = &http.Client{Transport: test.transport}
}
repo, err := findPublicRepo(test.pkg)
// ... (エラーチェックと結果の検証)
url, root, vcs, err := repo.Repo(client) // カスタムクライアントをRepoメソッドに渡す
// ... (エラーチェックと結果の検証)
}
}
download_test.go
では、testTransport
というカスタム http.RoundTripper
実装が導入されています。これにより、findPublicRepo
が内部で http.Client
を使用してネットワークアクセスを行う場合でも、実際のネットワークに接続することなく、事前に定義されたレスポンスを返すことができます。これは、Google CodeのスクレイピングやBitbucket APIの呼び出しといった、ネットワーク依存のロジックを単体テストする上で非常に重要です。TestFindPublicRepo
は、この testTransport
を利用して、様々なインポートパスに対する findPublicRepo
の動作を検証しています。
関連リンク
- Go言語の公式ドキュメント (当時の
goinstall
に関する情報が含まれている可能性がありますが、現在はgo get
に置き換わっています): https://go.dev/ - Google Code Project Hosting (サービス終了済み): https://code.google.com/
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- このコミットのGoレビューシステム上の変更リスト (CL): https://golang.org/cl/5343042 (現在はアクセスできない可能性があります)
- Go言語のパッケージ管理の歴史に関する記事 (例: "Go Modules: The Story So Far" など)
- Git, Mercurial, Subversion, Bazaar の各VCSの公式ドキュメント
- Bitbucket API ドキュメント (当時のバージョン): https://developer.atlassian.com/bitbucket/api/1/ (当時のAPIバージョンは異なる可能性があります)