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

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるバージョン管理システム (VCS) のエラーメッセージの改善に関するものです。具体的には、go get コマンドなどが「既知のバージョン管理システムを使用していません」というエラーを報告する際に、誤ったディレクトリ名を表示していた問題を修正します。

コミット

このコミットは、go get コマンドがバージョン管理システムを検出できない場合に表示するエラーメッセージにおいて、ユーザーが意図したディレクトリではなく、GOPATH のルートディレクトリを表示してしまう不具合を修正します。これにより、ユーザーは問題の発生源を正確に特定できるようになります。

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

https://github.com/golang/go/commit/b99fdb2a11ae58834a2c0c646f54d0a587dd269c

元コミット内容

commit b99fdb2a11ae58834a2c0c646f54d0a587dd269c
Author: Russ Cox <rsc@golang.org>
Date:   Tue Sep 10 15:28:29 2013 -0400

    cmd/go: report correct directory for 'no version control'
    
    The scan starts at the directory we care about and works
    backward to the GOPATH root. The error should say the
    original directory name, not the name of the GOPATH root.
    
    Fixes #6175.
    
    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/13366050

変更の背景

この変更は、Go の issue #6175 に対応するものです。元の問題は、go get コマンドが特定のディレクトリでバージョン管理システムを検出できなかった場合に、エラーメッセージがユーザーが実際に操作しようとしていたディレクトリではなく、GOPATH のルートディレクトリを指してしまうというものでした。

例えば、$GOPATH/src/foo というディレクトリで go get -u foo を実行し、foo がバージョン管理下にない場合、エラーメッセージは「directory "$GOPATH" is not using a known version control system」のように表示されていました。しかし、ユーザーが実際に問題に直面しているのは $GOPATH/src/foo であり、GOPATH ルートではありません。この誤解を招くメッセージは、ユーザーが問題の原因を特定し、解決するのを困難にしていました。

このコミットは、エラーメッセージが常にユーザーがコマンドを実行した元のディレクトリを正確に報告するようにすることで、このユーザーエクスペリエンスの問題を解決することを目的としています。

前提知識の解説

  • Go Modules と GOPATH: Go 1.11 以降、Go Modules が導入され、プロジェクトの依存関係管理とビルドの仕組みが大きく変わりました。しかし、このコミットが作成された2013年時点では、Go Modules は存在せず、Go のプロジェクトは GOPATH という環境変数によって管理されていました。GOPATH は、Go のソースコード、パッケージ、バイナリが配置されるワークスペースのルートディレクトリを指します。通常、$GOPATH/src にソースコードが、$GOPATH/pkg にコンパイル済みパッケージが、$GOPATH/bin に実行可能バイナリが格納されます。
  • go get コマンド: go get は、Go のパッケージとその依存関係をダウンロードし、インストールするためのコマンドです。このコマンドは、指定されたパッケージのソースコードを、そのパッケージが利用しているバージョン管理システム (Git, Mercurial, Subversion など) を介して取得します。
  • バージョン管理システム (VCS): ソフトウェアのソースコードの変更履歴を管理するためのシステムです。Git、Mercurial (hg)、Subversion (svn) などが代表的です。go get コマンドは、これらのVCSツールを内部的に利用してリモートリポジトリからコードを取得します。
  • src/cmd/go/vcs.go: Go のソースコードツリー内にあるこのファイルは、go コマンドが様々なバージョン管理システムとどのように連携するかを定義しています。具体的には、リポジトリのルートを特定したり、VCS固有のコマンドを実行したりするロジックが含まれています。
  • fmt.Errorf: Go 言語の標準ライブラリ fmt パッケージに含まれる関数で、フォーマットされたエラー文字列を生成するために使用されます。

技術的詳細

cmd/go の内部では、vcsForDir 関数が、与えられたディレクトリがどのバージョン管理システムを使用しているかを判断するために、そのディレクトリから GOPATH ルートに向かって親ディレクトリを遡ってスキャンします。このスキャンは、.git.hg といったVCS固有のメタデータディレクトリを探すことで行われます。

元の実装では、vcsForDir 関数がVCSを検出できなかった場合、エラーメッセージを生成する際に、スキャンが最終的に到達したディレクトリ(多くの場合、GOPATH のルートディレクトリ)をエラーメッセージに含めていました。

このコミットの修正は、この挙動を変更します。具体的には、vcsForDir 関数が最初に受け取った、ユーザーがコマンドを実行した「元のディレクトリ」を origDir という変数に保存します。そして、VCSが見つからなかった場合にエラーメッセージを生成する際に、スキャン中に到達したディレクトリではなく、この origDir を使用するように変更します。

これにより、エラーメッセージは常にユーザーが意図した操作の対象となったディレクトリを正確に指し示すようになり、ユーザーはより迅速に問題の原因を特定できるようになります。

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

変更は主に src/cmd/go/vcs.go ファイルの vcsForDir 関数にあります。

--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -321,6 +321,7 @@ func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) {
 		return nil, "", fmt.Errorf("directory %q is outside source root %q", dir, srcRoot)
 	}
 
+	origDir := dir
 	for len(dir) > len(srcRoot) {
 		for _, vcs := range vcsList {
 			if fi, err := os.Stat(filepath.Join(dir, "."+vcs.cmd)); err == nil && fi.IsDir() {
@@ -337,7 +338,7 @@ func vcsForDir(p *Package) (vcs *vcsCmd, root string, err error) {
 		dir = ndir
 	}
 
-	return nil, "", fmt.Errorf("directory %q is not using a known version control system", dir)
+	return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir)
 }
 
 // repoRoot represents a version control system, a repo, and a root of

また、この修正を検証するためのテストケースが src/cmd/go/test.bash に追加されています。

--- a/src/cmd/go/test.bash
+++ b/src/cmd/go/test.bash
@@ -139,6 +139,18 @@ if ! ./testgo test ./testdata/testimport/*.go; then
 		ok=false
 fi
 
+TEST version control error message includes correct directory
+export GOPATH=$(pwd)/testdata/shadow/root1
+if ./testgo get -u foo 2>testdata/err; then
+	echo "go get -u foo succeeded unexpectedly"
+	ok=false
+elif ! grep testdata/shadow/root1/src/foo testdata/err >/dev/null; then
+	echo "go get -u error does not mention shadow/root1/src/foo:"
+	cat testdata/err
+	ok=false
+fi
+unset GOPATH
+
 # Test that without $GOBIN set, binaries get installed
 # into the GOPATH bin directory.
 TEST install into GOPATH

コアとなるコードの解説

src/cmd/go/vcs.go の変更点:

  1. origDir := dir の追加: vcsForDir 関数の冒頭で、引数として渡された dir (ユーザーがコマンドを実行した元のディレクトリ) の値を origDir という新しい変数に保存しています。この origDir は、関数内でディレクトリを遡るスキャンが行われる間も、元のディレクトリのパスを保持し続けます。

  2. エラーメッセージの変更: return nil, "", fmt.Errorf("directory %q is not using a known version control system", dir)return nil, "", fmt.Errorf("directory %q is not using a known version control system", origDir) に変更されています。 これにより、VCS が見つからなかった場合に生成されるエラーメッセージは、スキャンによって変更された dir 変数の値ではなく、常にユーザーが最初に指定した origDir の値を参照するようになります。

src/cmd/go/test.bash の変更点:

追加されたテストケースは、以下の手順でこの修正が正しく機能するかを検証しています。

  1. GOPATH を一時的に $(pwd)/testdata/shadow/root1 に設定します。
  2. ./testgo get -u foo を実行し、エラー出力を testdata/err にリダイレクトします。foo はバージョン管理下にないことを想定しています。
  3. go get -u foo が予期せず成功した場合、テストは失敗します。
  4. testdata/err の内容に testdata/shadow/root1/src/foo (元のディレクトリ) が含まれているかを grep で確認します。含まれていない場合、エラーメッセージが正しくないためテストは失敗します。
  5. 最後に GOPATHunset して、環境をクリーンアップします。

このテストは、エラーメッセージが修正された通り、元のディレクトリ名を正確に報告していることを保証します。

関連リンク

参考にした情報源リンク

  • Go issue #6175 の内容
  • Go の cmd/go ソースコード (src/cmd/go/vcs.go および src/cmd/go/test.bash)
  • Go の GOPATH に関する公式ドキュメント (当時の情報に基づく)
  • Go の go get コマンドに関する公式ドキュメント (当時の情報に基づく)
  • Go のバージョン管理システム連携に関する一般的な知識
  • fmt.Errorf の Go 言語ドキュメント
  • os.Stat および filepath.Join の Go 言語ドキュメント
  • grep コマンドの一般的な使用法