Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19446] ファイルの概要

src/cmd/go/vcs.go は、Go言語のビルドツールである go コマンドの一部であり、Goパッケージのインポートパスからバージョン管理システム (VCS) のリポジトリルートを特定するロジックを扱っています。これは、go get コマンドがリモートリポジトリからソースコードを取得し、ローカル環境にインストールする際に不可欠な役割を担っています。具体的には、インポートパスのパターンに基づいて、Git、Mercurial、SubversionなどのどのVCSを使用すべきかを判断し、対応するリポジトリのURLを構築する機能を提供します。

コミット

Rob Pikeによる2014年5月27日のコミットで、go get コマンドが http:// または https:// を含むインポートパスに対して、より分かりやすいエラーメッセージを出すように改善されました。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/7f638e90231e56c523ae51ba18dbbf084db8b86e

元コミット内容

cmd/go: improve error message when import path contains http://
Common mistake (at least for me) because hg etc. require the prefix
while the go command forbids it.

Before:
% go get http://code.google.com/p/go.text/unicode/norm
package http:/code.google.com/p/go.text/unicode/norm: unrecognized import path "http:/code.google.com/p/go.text/unicode/norm"

After:
% go get http://code.google.com/p/go.text/unicode/norm
package http:/code.google.com/p/go.text/unicode/norm: "http://" not allowed in import path

LGTM=ruiu, rsc
R=rsc, ruiu
CC=golang-codereviews
https://golang.org/cl/97630046

変更の背景

Go言語の go get コマンドは、パッケージのインポートパスを直接リポジトリのURLとして解釈するのではなく、特定のルールに基づいてリポジトリを特定し、ソースコードを取得します。例えば、code.google.com/p/go.text/unicode/norm のようなパスが与えられると、Goツールは自動的に適切なプロトコル(Git、Mercurial、Subversionなど)とリポジトリの場所を推測してソースコードを取得します。Goのインポートパスには、通常 http://https:// といったプロトコルスキームを含めないのが慣習です。

しかし、Mercurial (hg) や Git のような一部のバージョン管理システムでは、リポジトリをクローンする際に http://https:// といったスキームを明示的に指定するのが一般的です。このため、Go開発者がこれらのVCSの習慣に慣れていると、go get コマンドを使用する際にも誤って http://https:// をインポートパスに含めてしまうという共通の誤りがありました。

このコミット以前は、http:// が含まれたインポートパスに対して go get を実行すると、「unrecognized import path」という一般的なエラーメッセージが表示されていました。このメッセージは、なぜインポートパスが認識されないのかを具体的に示しておらず、ユーザーが問題の原因を特定するのを困難にしていました。

この変更の目的は、この一般的な誤用に対して、より具体的で分かりやすいエラーメッセージを提供することです。これにより、ユーザーはインポートパスにスキームを含めるべきではないというGoの慣習をすぐに理解し、問題を解決できるようになります。

前提知識の解説

  • go get コマンド: Go言語のパッケージ管理ツールの一部であり、リモートリポジトリからGoパッケージのソースコードを取得し、ローカルの GOPATH (Go 1.11以降はGo Modulesのキャッシュ) にインストールするために使用されます。go get は、インポートパスに基づいて自動的に適切なバージョン管理システム(Git, Mercurial, Subversionなど)を検出し、リポジトリをクローンします。
  • Goのインポートパス: Go言語では、パッケージはファイルシステムのパスと対応するインポートパスによって識別されます。例えば、"github.com/user/repo/package" のような形式です。Goツールは、このインポートパスからリポジトリのホスト(github.com)、ユーザー名(user)、リポジトリ名(repo)、およびパッケージのサブパス(package)を解析し、ソースコードを取得します。重要なのは、Goのインポートパスには通常、http://https:// といったプロトコルスキームを含めないという慣習がある点です。
  • バージョン管理システム (VCS) のURL: GitやMercurialなどのVCSでは、リポジトリのクローンやフェッチを行う際に、https://github.com/user/repo.git のようにプロトコルスキームを含む完全なURLを指定するのが一般的です。このVCSの習慣とGoのインポートパスの慣習との間にギャップがあり、今回のエラーメッセージ改善の背景となりました。

技術的詳細

このコミットは、src/cmd/go/vcs.go ファイル内の repoRootForImportPathStatic 関数に修正を加えています。この関数は、与えられたインポートパスからリポジトリのルートを静的に決定しようとします。

変更の核心は、インポートパスが http:// または https:// で始まるかどうかを検出するための正規表現の導入と、それに続くエラーメッセージの生成ロジックの変更です。

  1. 正規表現の導入:

    var httpPrefixRE = regexp.MustCompile(`^https?:`)
    

    httpPrefixRE という新しいグローバル変数が導入され、regexp.MustCompile を使用して正規表現 ^https?: がコンパイルされます。

    • ^: 文字列の先頭にマッチします。
    • http: リテラル文字列 "http" にマッチします。
    • s?: "s" が0回または1回出現することにマッチします。これにより "http" と "https" の両方に対応します。
    • :: リテラル文字 ":" にマッチします。 この正規表現は、インポートパスが http: または https: で始まるかどうかを効率的にチェックするために使用されます。
  2. エラー検出ロジックの変更: 以前は strings.Contains(importPath, "://") を使用して :// が含まれているかをチェックし、一般的な invalid import path エラーを返していました。 新しいロジックでは、httpPrefixRE.FindStringIndex(importPath) を使用して、インポートパスの先頭に http: または https: が存在するかどうかを検出します。

    • FindStringIndex メソッドは、正規表現にマッチする最初の部分文字列の開始インデックスと終了インデックスを含む2要素の []int スライスを返します。マッチしない場合は nil を返します。
  3. エラーメッセージの生成: loc := httpPrefixRE.FindStringIndex(importPath); loc != nil の条件が真(つまり、http: または https: が検出された)の場合、新しいエラーメッセージが生成されます。

    return nil, fmt.Errorf("%q not allowed in import path", importPath[loc[0]:loc[1]]+"//")
    
    • importPath[loc[0]:loc[1]] は、マッチした http: または https: の部分文字列を抽出します。
    • これに // を連結することで、ユーザーが入力したであろう http:// または https:// の形式を再現します。
    • fmt.Errorf("%q not allowed in import path", ...) は、この再現されたスキームを含む文字列を引用符で囲み、「"http://" not allowed in import path」のような、より具体的で分かりやすいエラーメッセージを生成します。

この変更により、go get はインポートパスの誤用をより正確に診断し、ユーザーに直接的なフィードバックを提供できるようになりました。

コアとなるコードの変更箇所

--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -354,6 +354,8 @@ type repoRoot struct {
 	root string
 }

+var httpPrefixRE = regexp.MustCompile(`^https?:`)
+
 // repoRootForImportPath analyzes importPath to determine the
 // version control system, and code repository to use.
 func repoRootForImportPath(importPath string) (*repoRoot, error) {
@@ -390,8 +392,12 @@ var errUnknownSite = errors.New("dynamic lookup required to find mapping")
 //
 // If scheme is non-empty, that scheme is forced.
 func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
-	if strings.Contains(importPath, "://") {
-		return nil, fmt.Errorf("invalid import path %q", importPath)
+	// A common error is to use https://packagepath because that's what
+	// hg and git require. Diagnose this helpfully.
+	if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil {
+		// The importPath has been cleaned, so has only one slash. The pattern
+		// ignores the slashes; the error message puts them back on the RHS at least.
+		return nil, fmt.Errorf("%q not allowed in import path", importPath[loc[0]:loc[1]]+"//")
 	}
 	for _, srv := range vcsPaths {
 	\tif !strings.HasPrefix(importPath, srv.prefix) {

コアとなるコードの解説

  • var httpPrefixRE = regexp.MustCompile(^https?:): この行は、http: または https: で始まる文字列を検出するための正規表現をコンパイルし、httpPrefixRE という変数に格納しています。regexp.MustCompile は、正規表現のコンパイルに失敗した場合にパニックを発生させるため、プログラムの初期化時など、正規表現が常に有効であることが保証される場所で使用されます。

  • if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil { ... }: repoRootForImportPathStatic 関数内で、この条件文が追加されました。httpPrefixRE.FindStringIndex(importPath) は、importPath の先頭が http: または https: にマッチするかどうかをチェックします。

    • もしマッチした場合、loc にはマッチした部分文字列の開始インデックスと終了インデックスが格納されたスライスが代入され、条件は真となります。
    • マッチしなかった場合、locnil となり、条件は偽となります。
  • return nil, fmt.Errorf("%q not allowed in import path", importPath[loc[0]:loc[1]]+"//"): http: または https: が検出された場合、この行が実行されます。

    • importPath[loc[0]:loc[1]] は、インポートパスから正規表現がマッチした部分(例: "http:" または "https:")を抽出します。
    • これに +"//" を連結することで、ユーザーが誤って入力したであろう http:// または https:// の形式を再構築します。
    • fmt.Errorf を使用して、この再構築された文字列を引用符で囲み、「"http://" not allowed in import path」のような、より具体的でユーザーフレンドリーなエラーメッセージを生成して返します。これにより、ユーザーはインポートパスにプロトコルスキームを含めるべきではないという明確な指示を受け取ることができます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (特に go get コマンドとパッケージのインポートパスに関するセクション)
  • Go言語の正規表現パッケージ regexp のドキュメント