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

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるタグ選択ロジックの変更に関するものです。具体的には、go get コマンドがリモートリポジトリからパッケージを取得する際に、どのバージョンタグ(リリースバージョンなど)を選択するかというロジックが更新されています。

コミット

commit d09943aeaf9469f567f1935eb641dc6c2e2dedaa
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 26 14:25:28 2012 -0400

    cmd/go: new tag selection logic
    
    The new logic is "use go1 if it's there, otherwise no tag."
    Nothing needs to say "I require go1.0.1", and I want to
    preserve some flexibility in defining what tags mean.
    
    Right now (before go1.0.1) there is only one possible tag,
    "go1", and I'd like to keep it that way.
    
    R=golang-dev, bradfitz, r, adg
    CC=golang-dev
    https://golang.org/cl/6112060

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

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

元コミット内容

このコミットの元の内容は、cmd/go のタグ選択ロジックを「go1 タグが存在すればそれを使用し、そうでなければタグを使用しない」という新しいロジックに変更することです。これは、特定のパッチバージョン(例: go1.0.1)を要求する必要がないようにし、将来的なタグの定義における柔軟性を維持することを目的としています。コミット時点では、go1.0.1 リリース前であり、利用可能なタグは go1 のみであるため、この単純化されたロジックが導入されました。

変更の背景

この変更の背景には、Go言語の初期のリリース戦略と、go get コマンドの動作に関する設計思想があります。

Go言語は、バージョン1(Go 1)のリリースに向けて開発が進められていました。Go 1は、Go言語の安定したAPIと互換性を保証する最初のメジャーリリースとして位置づけられており、その後のGo言語の進化の基盤となるものでした。このコミットが行われた2012年4月は、Go 1の正式リリース(2012年3月28日)から間もない時期であり、Go 1.0.1のようなパッチリリースが計画され始めた段階でした。

go get コマンドは、Goのパッケージ管理において重要な役割を担っており、指定されたURLからソースコードをダウンロードし、ビルドしてインストールする機能を提供します。この際、リポジトリに複数のバージョンタグが存在する場合、go get はどのタグのコードを取得すべきかを決定する必要があります。

従来のタグ選択ロジックは、release.rN(リリース候補版)、weekly.YYYY-MM-DD(週次スナップショット)、goX(メジャーバージョン)といった複数のタグ形式に対応しており、より複雑なバージョンマッチングを行っていました。しかし、Go 1のリリースを控え、Goエコシステム全体で「Go 1互換性」という概念が重視される中で、特定のパッチバージョン(例: go1.0.1)を明示的に指定して取得する必要があるのか、という疑問が生じました。

コミットメッセージにある「Nothing needs to say "I require go1.0.1"」という記述は、Go 1の安定性が確立されれば、通常は最新のGo 1互換バージョンを取得すれば十分であり、特定のパッチリリースに厳密に依存する必要はない、という思想を反映しています。また、「preserve some flexibility in defining what tags mean」という点は、将来的にタグのセマンティクスが変化する可能性を考慮し、現在の段階で過度に複雑なタグ選択ロジックを固定化しないという意図を示しています。

したがって、この変更は、Go 1のリリースに伴うバージョン管理の単純化と、将来の柔軟性を確保するための戦略的な判断として行われました。

前提知識の解説

Go言語のバージョン管理とgo get

Go言語では、モジュールシステムが導入される以前は、go get コマンドが主に外部パッケージの取得と管理を担っていました。go get は、指定されたインポートパス(例: github.com/user/repo)に基づいて、対応するバージョン管理システム(Git, Mercurialなど)からソースコードをクローンし、GOPATH 内に配置します。

初期のGoでは、セマンティックバージョニング(SemVer)のような厳密なバージョン管理規則がエコシステム全体で統一されておらず、各リポジトリが独自のタグ付け規則を採用していることがありました。そのため、go get がどのバージョンのコードを取得すべきかを判断するロジックは、Goのリリース戦略と密接に関連していました。

Gitのタグ

Gitにおけるタグは、特定のコミットに永続的な名前を付けるための参照です。通常、ソフトウェアのリリースバージョン(例: v1.0.0, v1.2.3)を示すために使用されます。タグは、ブランチのように移動することなく、常に同じコミットを指し示します。

Go 1の互換性保証

Go 1は、Go言語の歴史において非常に重要なマイルストーンです。Go 1のリリース以降、Goチームは「Go 1 Compatibility Promise」(Go 1互換性保証)を掲げ、Go 1で書かれたプログラムは、将来のGoのバージョンでも動作し続けることを保証しました。これは、Goエコシステムの安定性と成長を促進するための重要な方針であり、開発者が安心してGo言語を採用できる基盤となりました。

この互換性保証の存在により、特定のパッチバージョン(例: go1.0.1)に厳密に依存するよりも、単に「Go 1互換」の最新バージョンを取得するという考え方がより合理的になります。

src/cmd/go/get.gosrc/cmd/go/tag_test.go

  • src/cmd/go/get.go: このファイルは、go get コマンドの主要なロジックを実装しています。外部リポジトリからのコード取得、依存関係の解決、そして本コミットで変更されるタグ選択ロジックなどが含まれます。
  • src/cmd/go/tag_test.go: このファイルは、get.go 内のタグ選択ロジック(特に selectTag 関数)の単体テストを定義しています。様々なバージョン文字列と利用可能なタグの組み合わせに対して、期待されるタグが正しく選択されるかを検証します。

技術的詳細

このコミットの技術的な変更は、主に src/cmd/go/get.go 内の selectTag 関数と、それに関連する src/cmd/go/tag_test.go のテストケースに集中しています。

selectTag 関数の変更

selectTag 関数は、goVersiongo get コマンドに渡されるバージョン指定、またはデフォルトのGoバージョン)と、リモートリポジトリで利用可能な tags のリストを受け取り、最も適切なタグを返す役割を担っています。

変更前: 変更前の selectTag 関数は、以下の複数のタグ形式に対応する複雑なロジックを持っていました。

  1. release.rN 形式: release.r58.2 のようなバージョン指定に対して、go.r58.1 のようなタグをマッチさせようとします。浮動小数点数としてバージョンを解析し、数値比較に基づいて最適なタグを選択していました。
  2. weekly.YYYY-MM-DD 形式: weekly.2010-01-02 のようなバージョン指定に対して、go.weekly.2010-01-02 のようなタグをマッチさせようとします。日付文字列の比較に基づいて最適なタグを選択していました。
  3. goX 形式: go1, go1.1 のようなバージョン指定に対して、go1.0.1, go1.9.2 のようなタグをマッチさせようとします。goTag.MatchStringcmpGoVersion というヘルパー関数を使用して、Goバージョン文字列の比較に基づいて最適なタグを選択していました。

これらのロジックは、Go 1リリース以前の多様なタグ付け慣習に対応するためのものでした。

変更後: 変更後の selectTag 関数は、非常に単純化されています。

func selectTag(goVersion string, tags []string) (match string) {
	for _, t := range tags {
		if t == "go1" {
			return "go1"
		}
	}
	return ""

	/*
		// 以前の複雑なロジックはコメントアウトされている
		if goTag.MatchString(goVersion) {
			v := goVersion
			for _, t := range tags {
				if !goTag.MatchString(t) {
					continue
				}
				if cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
					match = t
				}
			}
		}

		return match
	*/
}

この新しいロジックは、引数 tags のリストをイテレートし、もし go1 という文字列と完全に一致するタグが見つかれば、即座に "go1" を返します。go1 タグが見つからなければ、空文字列 "" を返します。

以前の複雑なロジックは、コードから削除される代わりに、コメントアウトされたブロックとして残されています。これは、将来的に再び複雑なタグ選択ロジックが必要になった場合に備えて、そのコードを簡単に参照できるようにするための措置と考えられます。しかし、コミットメッセージの意図からすると、当面は go1 のみを特別扱いし、それ以外のタグは無視するという強い方針が示されています。

tag_test.go の変更

selectTag 関数のロジック変更に伴い、そのテストケースも大幅に修正されています。

変更前: 変更前の selectTagTests 変数には、release.rNweekly.YYYY-MM-DDgoX 形式のバージョン指定と、それに対応する期待されるタグの組み合わせが多数定義されていました。これらのテストケースは、以前の複雑なタグ選択ロジックの各パスを検証するためのものでした。

変更後: 変更後の selectTagTests は、以前のテストケースのほとんどがコメントアウトされ、代わりに非常に単純なテストケースが一つだけ追加されています。

var selectTagTests = []struct {
	version  string
	selected string
}{
	/*
		// 以前の多数のテストケースはコメントアウトされている
		{"release.r57", ""},
		// ... (多数のテストケース) ...
		{"go6", "go5"},

		// faulty versions:
		{"release.f00", ""},
		// ... (多数のテストケース) ...
		{"go2.0", ""},
	*/
	{"anything", "go1"}, // 新しく追加されたテストケース
}

新しく追加されたテストケース {"anything", "go1"} は、goVersion が何であっても(この場合は "anything")、selectTag 関数が go1 タグを返すことを期待しています。これは、selectTag 関数が goVersion 引数をほとんど無視し、利用可能なタグの中に "go1" があればそれを優先的に選択するという新しいロジックを反映しています。

変更の意図と影響

この変更は、Go 1のリリース直後という時期において、Goのバージョン管理戦略を単純化し、安定性を重視するGo 1の精神に合致させることを目的としています。

  • 単純化: 複雑なタグマッチングロジックを排除し、go1 タグの存在のみをチェックするようにすることで、go get の動作が予測しやすくなります。
  • Go 1への集中: Go 1が安定版として確立された後、開発者は通常、Go 1互換の最新バージョンを望むため、特定のパッチバージョン(例: go1.0.1)に厳密に依存する必要がなくなります。
  • 柔軟性の確保: 将来的にGoのバージョン管理戦略が進化する可能性を考慮し、現時点では最も単純なロジックに留めることで、将来の変更に対する柔軟性を残しています。コミットメッセージにある「preserve some flexibility in defining what tags mean」という点がこれを裏付けています。
  • テストの反映: テストケースの変更は、新しいロジックが正しく実装されていることを確認するためのものです。以前の複雑なテストケースがコメントアウトされたのは、それらがもはや新しいロジックでは意味をなさないためです。

この変更により、go get は、リポジトリに go1 タグが存在すれば常にそのタグのコードを取得し、そうでなければタグなし(つまり、デフォルトブランチの最新コミット)のコードを取得するようになります。これは、Go 1の安定性を前提とした、より堅牢でシンプルな依存関係解決の第一歩と言えます。

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

src/cmd/go/get.go

--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -335,56 +335,32 @@ var goTag = regexp.MustCompile(
 // Version "goX" (or "goX.Y" or "goX.Y.Z") matches tags of the same form.
 // Version "release.rN" matches tags of the form "go.rN" (N being a floating-point number).
 // Version "weekly.YYYY-MM-DD" matches tags like "go.weekly.YYYY-MM-DD".
-+//
-+// NOTE(rsc): Eventually we will need to decide on some logic here.
-+// For now, there is only "go1".  This matches the docs in go help get.
 func selectTag(goVersion string, tags []string) (match string) {
--	const rPrefix = "release.r"
--	if strings.HasPrefix(goVersion, rPrefix) {
--		p := "go.r"
--		v, err := strconv.ParseFloat(goVersion[len(rPrefix):], 64)
--		if err != nil {
--			return ""
--		}
--		var matchf float64
--		for _, t := range tags {
--			if !strings.HasPrefix(t, p) {
--				continue
--			}
--			tf, err := strconv.ParseFloat(t[len(p):], 64)
--			if err != nil {
--				continue
--			}
--			if matchf < tf && tf <= v {
--				match, matchf = t, tf
--			}
-+	for _, t := range tags {
-+		if t == "go1" {
-+			return "go1"
++		}
++	}
++	return ""
++
++	/*
++		if goTag.MatchString(goVersion) {
++			v := goVersion
++			for _, t := range tags {
++				if !goTag.MatchString(t) {
++					continue
++				}
++				if cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
++					match = t
++				}
++			}
++		}
++
++		return match
++	*/
+ }
+ 
  // cmpGoVersion returns -1, 0, +1 reporting whether

src/cmd/go/tag_test.go

--- a/src/cmd/go/tag_test.go
+++ b/src/cmd/go/tag_test.go
@@ -50,41 +50,44 @@ var selectTagTests = []struct {
 	version  string
 	selected string
 }{
--	{"release.r57", ""},
--	{"release.r58.2", "go.r58.1"},
--	{"release.r59", "go.r59"},
--	{"release.r59.1", "go.r59.1"},
--	{"release.r60", "go.r59.1"},
--	{"release.r60.1", "go.r59.1"},
--	{"release.r61", "go.r61"},
--	{"release.r66", "go.r61.1"},
--	{"weekly.2010-01-01", ""},
--	{"weekly.2010-01-02", "go.weekly.2010-01-02"},
--	{"weekly.2010-01-02.1", "go.weekly.2010-01-02"},
--	{"weekly.2010-01-03", "go.weekly.2010-01-02"},
--	{"weekly.2011-10-12", "go.weekly.2011-10-12"},
--	{"weekly.2011-10-12.1", "go.weekly.2011-10-12.1"},
--	{"weekly.2011-10-13", "go.weekly.2011-10-12.1"},
--	{"weekly.2011-10-14", "go.weekly.2011-10-14"},
--	{"weekly.2011-10-14.1", "go.weekly.2011-10-14"},
--	{"weekly.2011-11-01", "go.weekly.2011-11-01"},
--	{"weekly.2014-01-01", "go.weekly.2011-11-01"},
--	{"go1", "go1"},
--	{"go1.1", "go1.0.1"},
--	{"go1.998", "go1.9.2"},
--	{"go1.1000", "go1.999"},
--	{"go6", "go5"},
-+	/*
-+		{"release.r57", ""},
-+		{"release.r58.2", "go.r58.1"},
-+		{"release.r59", "go.r59"},
-+		{"release.r59.1", "go.r59.1"},
-+		{"release.r60", "go.r59.1"},
-+		{"release.r60.1", "go.r59.1"},
-+		{"release.r61", "go.r61"},
-+		{"release.r66", "go.r61.1"},
-+		{"weekly.2010-01-01", ""},
-+		{"weekly.2010-01-02", "go.weekly.2010-01-02"},
-+		{"weekly.2010-01-02.1", "go.weekly.2010-01-02"},
-+		{"weekly.2010-01-03", "go.weekly.2010-01-02"},
-+		{"weekly.2011-10-12", "go.weekly.2011-10-12"},
-+		{"weekly.2011-10-12.1", "go.weekly.2011-10-12.1"},
-+		{"weekly.2011-10-13", "go.weekly.2011-10-12.1"},
-+		{"weekly.2011-10-14", "go.weekly.2011-10-14"},
-+		{"weekly.2011-10-14.1", "go.weekly.2011-10-14"},
-+		{"weekly.2011-11-01", "go.weekly.2011-11-01"},
-+		{"weekly.2014-01-01", "go.weekly.2011-11-01"},
-+		{"go1", "go1"},
-+		{"go1.1", "go1.0.1"},
-+		{"go1.998", "go1.9.2"},
-+		{"go1.1000", "go1.999"},
-+		{"go6", "go5"},
-+
-+		// faulty versions:
-+		{"release.f00", ""},
-+		{"weekly.1999-01-01", ""},
-+		{"junk", ""},
-+		{"", ""},
-+		{"go2x", ""},
-+		{"go200000000000", ""},
-+		{"go2.", ""},
-+		{"go2.0", ""},
-+	*/
++	{"anything", "go1"},
+}

コアとなるコードの解説

selectTag 関数の変更点

selectTag 関数は、Goの go get コマンドがリモートリポジトリからソースコードを取得する際に、どのGitタグ(バージョン)を選択するかを決定する中心的なロジックを担っています。

変更前は、この関数は非常に複雑なロジックを持っていました。これは、Go 1リリース以前のGoプロジェクトが採用していた多様なタグ付け慣習(例: release.rNweekly.YYYY-MM-DDgoX など)に対応するためでした。各タグ形式に対して、正規表現や数値/日付の比較を用いて、指定された goVersion に最も近い、または適切なタグを見つけ出す試みが行われていました。

しかし、このコミットによって、その複雑なロジックは完全に削除(コメントアウト)され、非常に単純な新しいロジックに置き換えられました。

func selectTag(goVersion string, tags []string) (match string) {
	for _, t := range tags {
		if t == "go1" {
			return "go1"
		}
	}
	return ""
}

この新しいロジックは、以下の手順で動作します。

  1. タグのイテレーション: 引数として渡された tags スライス(リモートリポジトリで利用可能なすべてのタグのリスト)を一つずつループで処理します。
  2. go1 タグのチェック: 各タグ t が文字列 "go1" と完全に一致するかどうかをチェックします。
  3. 即時リターン: もし "go1" タグが見つかった場合、関数は直ちに "go1" を返して終了します。これは、go get が常に go1 タグを最優先で選択することを示しています。
  4. 空文字列のリターン: ループが終了しても "go1" タグが見つからなかった場合、関数は空文字列 "" を返します。これは、go1 タグが存在しない場合は、特定のバージョンタグを選択せず、デフォルトのブランチ(通常は master または main)の最新コミットを使用することを示唆しています。

この変更は、Go 1のリリースに伴うGoエコシステムの成熟を反映しています。Go 1が安定版として確立されたことで、多くのプロジェクトはGo 1互換性を前提とするようになり、特定のパッチバージョン(例: go1.0.1)に厳密に依存するよりも、単に「Go 1互換の最新版」を取得することが一般的になりました。このロジックの単純化は、go get の動作をより予測可能にし、Go 1互換性保証の精神に合致させるものです。

コメントアウトされた古いロジックは、将来的に必要になった場合に備えて残されていますが、このコミットの時点では、go1 タグのみが特別扱いされるべきであるという強い意図が示されています。

selectTagTests の変更点

selectTagTests は、selectTag 関数の動作を検証するためのテストデータ構造です。

変更前は、release.rNweekly.YYYY-MM-DDgoX といった様々なバージョン指定と、それに対応する期待されるタグの組み合わせが多数定義されていました。これらのテストは、以前の複雑なタグ選択ロジックの各パスが正しく機能することを確認するためのものでした。

変更後は、これらの多数のテストケースがすべてコメントアウトされ、代わりに以下の非常に単純なテストケースが一つだけ追加されました。

	{"anything", "go1"},

この新しいテストケースは、goVersion 引数(この場合は "anything" というダミーの値)が何であっても、selectTag 関数が go1 タグを返すことを期待しています。これは、selectTag 関数が goVersion 引数をほとんど無視し、利用可能なタグの中に "go1" があればそれを優先的に選択するという新しいロジックを直接的に反映しています。

このテストケースの変更は、コードの変更が意図通りに機能していることを確認するためのものであり、同時に、以前の複雑なタグ選択ロジックがもはやテストの対象ではないことを示しています。

関連リンク

  • Go 1 Compatibility Promise: https://go.dev/doc/go1compat
  • go get command documentation (Go 1.0): https://go.dev/doc/go1.html#get (Go 1.0のドキュメントは直接リンクが難しいですが、Go 1のリリースノートやアーカイブされたドキュメントで確認できます。)

参考にした情報源リンク

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるタグ選択ロジックの変更に関するものです。具体的には、go get コマンドがリモートリポジトリからパッケージを取得する際に、どのバージョンタグ(リリースバージョンなど)を選択するかというロジックが更新されています。

コミット

commit d09943aeaf9469f567f1935eb641dc6c2e2dedaa
Author: Russ Cox <rsc@golang.org>
Date:   Thu Apr 26 14:25:28 2012 -0400

    cmd/go: new tag selection logic
    
    The new logic is "use go1 if it's there, otherwise no tag."
    Nothing needs to say "I require go1.0.1", and I want to
    preserve some flexibility in defining what tags mean.
    
    Right now (before go1.0.1) there is only one possible tag,
    "go1", and I'd like to keep it that way.
    
    R=golang-dev, bradfitz, r, adg
    CC=golang-dev
    https://golang.org/cl/6112060

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

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

元コミット内容

このコミットの元の内容は、cmd/go のタグ選択ロジックを「go1 タグが存在すればそれを使用し、そうでなければタグを使用しない」という新しいロジックに変更することです。これは、特定のパッチバージョン(例: go1.0.1)を要求する必要がないようにし、将来的なタグの定義における柔軟性を維持することを目的としています。コミット時点では、go1.0.1 リリース前であり、利用可能なタグは go1 のみであるため、この単純化されたロジックが導入されました。

変更の背景

この変更の背景には、Go言語の初期のリリース戦略と、go get コマンドの動作に関する設計思想があります。

Go言語は、バージョン1(Go 1)のリリースに向けて開発が進められていました。Go 1は、Go言語の安定したAPIと互換性を保証する最初のメジャーリリースとして位置づけられており、その後のGo言語の進化の基盤となるものでした。このコミットが行われた2012年4月は、Go 1の正式リリース(2012年3月28日)から間もない時期であり、Go 1.0.1のようなパッチリリースが計画され始めた段階でした。

go get コマンドは、Goのパッケージ管理において重要な役割を担っており、指定されたURLからソースコードをダウンロードし、ビルドしてインストールする機能を提供します。この際、リポジトリに複数のバージョンタグが存在する場合、go get はどのタグのコードを取得すべきかを決定する必要があります。

従来のタグ選択ロジックは、release.rN(リリース候補版)、weekly.YYYY-MM-DD(週次スナップショット)、goX(メジャーバージョン)といった複数のタグ形式に対応しており、より複雑なバージョンマッチングを行っていました。しかし、Go 1のリリースを控え、Goエコシステム全体で「Go 1互換性」という概念が重視される中で、特定のパッチバージョン(例: go1.0.1)を明示的に指定して取得する必要があるのか、という疑問が生じました。

コミットメッセージにある「Nothing needs to say "I require go1.0.1"」という記述は、Go 1の安定性が確立されれば、通常は最新のGo 1互換バージョンを取得すれば十分であり、特定のパッチリリースに厳密に依存する必要はない、という思想を反映しています。また、「preserve some flexibility in defining what tags mean」という点は、将来的にタグのセマンティクスが変化する可能性を考慮し、現在の段階で過度に複雑なタグ選択ロジックを固定化しないという意図を示しています。

したがって、この変更は、Go 1のリリースに伴うバージョン管理の単純化と、将来の柔軟性を確保するための戦略的な判断として行われました。

前提知識の解説

Go言語のバージョン管理とgo get

Go言語では、モジュールシステムが導入される以前は、go get コマンドが主に外部パッケージの取得と管理を担っていました。go get は、指定されたインポートパス(例: github.com/user/repo)に基づいて、対応するバージョン管理システム(Git, Mercurialなど)からソースコードをクローンし、GOPATH 内に配置します。

初期のGoでは、セマンティックバージョニング(SemVer)のような厳密なバージョン管理規則がエコシステム全体で統一されておらず、各リポジトリが独自のタグ付け規則を採用していることがありました。そのため、go get がどのバージョンのコードを取得すべきかを判断するロジックは、Goのリリース戦略と密接に関連していました。

Gitのタグ

Gitにおけるタグは、特定のコミットに永続的な名前を付けるための参照です。通常、ソフトウェアのリリースバージョン(例: v1.0.0, v1.2.3)を示すために使用されます。タグは、ブランチのように移動することなく、常に同じコミットを指し示します。

Go 1の互換性保証

Go 1は、Go言語の歴史において非常に重要なマイルストーンです。Go 1のリリース以降、Goチームは「Go 1 Compatibility Promise」(Go 1互換性保証)を掲げ、Go 1で書かれたプログラムは、将来のGoのバージョンでも動作し続けることを保証しました。これは、Goエコシステムの安定性と成長を促進するための重要な方針であり、開発者が安心してGo言語を採用できる基盤となりました。

この互換性保証の存在により、特定のパッチバージョン(例: go1.0.1)に厳密に依存するよりも、単に「Go 1互換」の最新バージョンを取得するという考え方がより合理的になります。

src/cmd/go/get.gosrc/cmd/go/tag_test.go

  • src/cmd/go/get.go: このファイルは、go get コマンドの主要なロジックを実装しています。外部リポジトリからのコード取得、依存関係の解決、そして本コミットで変更されるタグ選択ロジックなどが含まれます。
  • src/cmd/go/tag_test.go: このファイルは、get.go 内のタグ選択ロジック(特に selectTag 関数)の単体テストを定義しています。様々なバージョン文字列と利用可能なタグの組み合わせに対して、期待されるタグが正しく選択されるかを検証します。

技術的詳細

このコミットの技術的な変更は、主に src/cmd/go/get.go 内の selectTag 関数と、それに関連する src/cmd/go/tag_test.go のテストケースに集中しています。

selectTag 関数の変更

selectTag 関数は、goVersiongo get コマンドに渡されるバージョン指定、またはデフォルトのGoバージョン)と、リモートリポジトリで利用可能な tags のリストを受け取り、最も適切なタグを返す役割を担っています。

変更前: 変更前の selectTag 関数は、以下の複数のタグ形式に対応する複雑なロジックを持っていました。

  1. release.rN 形式: release.r58.2 のようなバージョン指定に対して、go.r58.1 のようなタグをマッチさせようとします。浮動小数点数としてバージョンを解析し、数値比較に基づいて最適なタグを選択していました。
  2. weekly.YYYY-MM-DD 形式: weekly.2010-01-02 のようなバージョン指定に対して、go.weekly.2010-01-02 のようなタグをマッチさせようとします。日付文字列の比較に基づいて最適なタグを選択していました。
  3. goX 形式: go1, go1.1 のようなバージョン指定に対して、go1.0.1, go1.9.2 のようなタグをマッチさせようとします。goTag.MatchStringcmpGoVersion というヘルパー関数を使用して、Goバージョン文字列の比較に基づいて最適なタグを選択していました。

これらのロジックは、Go 1リリース以前の多様なタグ付け慣習に対応するためのものでした。

変更後: 変更後の selectTag 関数は、非常に単純化されています。

func selectTag(goVersion string, tags []string) (match string) {
	for _, t := range tags {
		if t == "go1" {
			return "go1"
		}
	}
	return ""

	/*
		// 以前の複雑なロジックはコメントアウトされている
		if goTag.MatchString(goVersion) {
			v := goVersion
			for _, t := range tags {
				if !goTag.MatchString(t) {
					continue
				}
				if cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
					match = t
				}
			}
		}

		return match
	*/
}

この新しいロジックは、引数 tags のリストをイテレートし、もし go1 という文字列と完全に一致するタグが見つかれば、即座に "go1" を返します。go1 タグが見つからなければ、空文字列 "" を返します。

以前の複雑なロジックは、コードから削除される代わりに、コメントアウトされたブロックとして残されています。これは、将来的に再び複雑なタグ選択ロジックが必要になった場合に備えて、そのコードを簡単に参照できるようにするための措置と考えられます。しかし、コミットメッセージの意図からすると、当面は go1 のみを特別扱いし、それ以外のタグは無視するという強い方針が示されています。

tag_test.go の変更

selectTag 関数のロジック変更に伴い、そのテストケースも大幅に修正されています。

変更前: 変更前の selectTagTests 変数には、release.rNweekly.YYYY-MM-DDgoX 形式のバージョン指定と、それに対応する期待されるタグの組み合わせが多数定義されていました。これらのテストケースは、以前の複雑なタグ選択ロジックの各パスを検証するためのものでした。

変更後: 変更後の selectTagTests は、以前のテストケースのほとんどがコメントアウトされ、代わりに非常に単純なテストケースが一つだけ追加されています。

var selectTagTests = []struct {
	version  string
	selected string
}{
	/*
		// 以前の多数のテストケースはコメントアウトされている
		{"release.r57", ""},
		// ... (多数のテストケース) ...
		{"go6", "go5"},

		// faulty versions:
		{"release.f00", ""},
		// ... (多数のテストケース) ...
		{"go2.0", ""},
	*/
	{"anything", "go1"}, // 新しく追加されたテストケース
}

新しく追加されたテストケース {"anything", "go1"} は、goVersion が何であっても(この場合は "anything")、selectTag 関数が go1 タグを返すことを期待しています。これは、selectTag 関数が goVersion 引数をほとんど無視し、利用可能なタグの中に "go1" があればそれを優先的に選択するという新しいロジックを反映しています。

変更の意図と影響

この変更は、Go 1のリリース直後という時期において、Goのバージョン管理戦略を単純化し、安定性を重視するGo 1の精神に合致させることを目的としています。

  • 単純化: 複雑なタグマッチングロジックを排除し、go1 タグの存在のみをチェックするようにすることで、go get の動作が予測しやすくなります。
  • Go 1への集中: Go 1が安定版として確立された後、開発者は通常、Go 1互換の最新バージョンを望むため、特定のパッチバージョン(例: go1.0.1)に厳密に依存する必要がなくなります。
  • 柔軟性の確保: 将来的にGoのバージョン管理戦略が進化する可能性を考慮し、現時点では最も単純なロジックに留めることで、将来の変更に対する柔軟性を残しています。コミットメッセージにある「preserve some flexibility in defining what tags mean」という点がこれを裏付けています。
  • テストの反映: テストケースの変更は、新しいロジックが正しく実装されていることを確認するためのものです。以前の複雑なテストケースがコメントアウトされたのは、それらがもはや新しいロジックでは意味をなさないためです。

この変更により、go get は、リポジトリに go1 タグが存在すれば常にそのタグのコードを取得し、そうでなければタグなし(つまり、デフォルトブランチの最新コミット)のコードを取得するようになります。これは、Go 1の安定性を前提とした、より堅牢でシンプルな依存関係解決の第一歩と言えます。

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

src/cmd/go/get.go

--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -335,56 +335,32 @@ var goTag = regexp.MustCompile(
 // Version "goX" (or "goX.Y" or "goX.Y.Z") matches tags of the same form.
 // Version "release.rN" matches tags of the form "go.rN" (N being a floating-point number).
 // Version "weekly.YYYY-MM-DD" matches tags like "go.weekly.YYYY-MM-DD".
-+//
-+// NOTE(rsc): Eventually we will need to decide on some logic here.
-+// For now, there is only "go1".  This matches the docs in go help get.
 func selectTag(goVersion string, tags []string) (match string) {
--	const rPrefix = "release.r"
--	if strings.HasPrefix(goVersion, rPrefix) {
--		p := "go.r"
--		v, err := strconv.ParseFloat(goVersion[len(rPrefix):], 64)
--		if err != nil {
--			return ""
--		}
--		var matchf float64
--		for _, t := range tags {
--			if !strings.HasPrefix(t, p) {
--				continue
--			}
--			tf, err := strconv.ParseFloat(t[len(p):], 64)
--			if err != nil {
--				continue
--			}
--			if matchf < tf && tf <= v {
--				match, matchf = t, tf
--			}
-+	for _, t := range tags {
-+		if t == "go1" {
-+			return "go1"
++		}
++	}
++	return ""
++
++	/*
++		if goTag.MatchString(goVersion) {
++			v := goVersion
++			for _, t := range tags {
++				if !goTag.MatchString(t) {
++					continue
++				}
++				if cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
++					match = t
++				}
++			}
++		}
++
++		return match
++	*/
+ }
+ 
  // cmpGoVersion returns -1, 0, +1 reporting whether

src/cmd/go/tag_test.go

--- a/src/cmd/go/tag_test.go
+++ b/src/cmd/go/tag_test.go
@@ -50,41 +50,44 @@ var selectTagTests = []struct {
 	version  string
 	selected string
 }{
--	{"release.r57", ""},
--	{"release.r58.2", "go.r58.1"},
--	{"release.r59", "go.r59"},
--	{"release.r59.1", "go.r59.1"},
--	{"release.r60", "go.r59.1"},
--	{"release.r60.1", "go.r59.1"},
--	{"release.r61", "go.r61"},
--	{"release.r66", "go.r61.1"},
--	{"weekly.2010-01-01", ""},
--	{"weekly.2010-01-02", "go.weekly.2010-01-02"},
--	{"weekly.2010-01-02.1", "go.weekly.2010-01-02"},
--	{"weekly.2010-01-03", "go.weekly.2010-01-02"},
--	{"weekly.2011-10-12", "go.weekly.2011-10-12"},
--	{"weekly.2011-10-12.1", "go.weekly.2011-10-12.1"},
--	{"weekly.2011-10-13", "go.weekly.2011-10-12.1"},
--	{"weekly.2011-10-14", "go.weekly.2011-10-14"},
--	{"weekly.2011-10-14.1", "go.weekly.2011-10-14"},
--	{"weekly.2011-11-01", "go.weekly.2011-11-01"},
--	{"weekly.2014-01-01", "go.weekly.2011-11-01"},
--	{"go1", "go1"},
--	{"go1.1", "go1.0.1"},
--	{"go1.998", "go1.9.2"},
--	{"go1.1000", "go1.999"},
--	{"go6", "go5"},
-+	/*
-+		{"release.r57", ""},
-+		{"release.r58.2", "go.r58.1"},
-+		{"release.r59", "go.r59"},
-+		{"release.r59.1", "go.r59.1"},
-+		{"release.r60", "go.r59.1"},
-+		{"release.r60.1", "go.r59.1"},
-+		{"release.r61", "go.r61"},
-+		{"release.r66", "go.r61.1"},
-+		{"weekly.2010-01-01", ""},
-+		{"weekly.2010-01-02", "go.weekly.2010-01-02"},
-+		{"weekly.2010-01-02.1", "go.weekly.2010-01-02"},
-+		{"weekly.2010-01-03", "go.weekly.2010-01-02"},
-+		{"weekly.2011-10-12", "go.weekly.2011-10-12"},
-+		{"weekly.2011-10-12.1", "go.weekly.2011-10-12.1"},
-+		{"weekly.2011-10-13", "go.weekly.2011-10-12.1"},
-+		{"weekly.2011-10-14", "go.weekly.2011-10-14"},
-+		{"weekly.2011-10-14.1", "go.weekly.2011-10-14"},
-+		{"weekly.2011-11-01", "go.weekly.2011-11-01"},
-+		{"weekly.2014-01-01", "go.weekly.2011-11-01"},
-+		{"go1", "go1"},
-+		{"go1.1", "go1.0.1"},
-+		{"go1.998", "go1.9.2"},
-+		{"go1.1000", "go1.999"},
-+		{"go6", "go5"},
-+
-+		// faulty versions:
-+		{"release.f00", ""},
-+		{"weekly.1999-01-01", ""},
-+		{"junk", ""},
-+		{"", ""},
-+		{"go2x", ""},
-+		{"go200000000000", ""},
-+		{"go2.", ""},
-+		{"go2.0", ""},
-+	*/
++	{"anything", "go1"},
+}

コアとなるコードの解説

selectTag 関数の変更点

selectTag 関数は、Goの go get コマンドがリモートリポジトリからソースコードを取得する際に、どのGitタグ(バージョン)を選択するかを決定する中心的なロジックを担っています。

変更前は、この関数は非常に複雑なロジックを持っていました。これは、Go 1リリース以前のGoプロジェクトが採用していた多様なタグ付け慣習(例: release.rNweekly.YYYY-MM-DDgoX など)に対応するためでした。各タグ形式に対して、正規表現や数値/日付の比較を用いて、指定された goVersion に最も近い、または適切なタグを見つけ出す試みが行われていました。

しかし、このコミットによって、その複雑なロジックは完全に削除(コメントアウト)され、非常に単純な新しいロジックに置き換えられました。

func selectTag(goVersion string, tags []string) (match string) {
	for _, t := range tags {
		if t == "go1" {
			return "go1"
		}
	}
	return ""
}

この新しいロジックは、以下の手順で動作します。

  1. タグのイテレーション: 引数として渡された tags スライス(リモートリポジトリで利用可能なすべてのタグのリスト)を一つずつループで処理します。
  2. go1 タグのチェック: 各タグ t が文字列 "go1" と完全に一致するかどうかをチェックします。
  3. 即時リターン: もし "go1" タグが見つかった場合、関数は直ちに "go1" を返して終了します。これは、go get が常に go1 タグを最優先で選択することを示しています。
  4. 空文字列のリターン: ループが終了しても "go1" タグが見つからなかった場合、関数は空文字列 "" を返します。これは、go1 タグが存在しない場合は、特定のバージョンタグを選択せず、デフォルトのブランチ(通常は master または main)の最新コミットを使用することを示唆しています。

この変更は、Go 1のリリースに伴うGoエコシステムの成熟を反映しています。Go 1が安定版として確立されたことで、多くのプロジェクトはGo 1互換性を前提とするようになり、特定のパッチバージョン(例: go1.0.1)に厳密に依存するよりも、単に「Go 1互換の最新版」を取得することが一般的になりました。このロジックの単純化は、go get の動作をより予測可能にし、Go 1互換性保証の精神に合致させるものです。

コメントアウトされた古いロジックは、将来的に必要になった場合に備えて残されていますが、このコミットの時点では、go1 タグのみが特別扱いされるべきであるという強い意図が示されています。

selectTagTests の変更点

selectTagTests は、selectTag 関数の動作を検証するためのテストデータ構造です。

変更前は、release.rNweekly.YYYY-MM-DDgoX といった様々なバージョン指定と、それに対応する期待されるタグの組み合わせが多数定義されていました。これらのテストは、以前の複雑なタグ選択ロジックの各パスが正しく機能することを確認するためのものでした。

変更後は、これらの多数のテストケースがすべてコメントアウトされ、代わりに以下の非常に単純なテストケースが一つだけ追加されました。

	{"anything", "go1"},

この新しいテストケースは、goVersion 引数(この場合は "anything" というダミーの値)が何であっても、selectTag 関数が go1 タグを返すことを期待しています。これは、selectTag 関数が goVersion 引数をほとんど無視し、利用可能なタグの中に "go1" があればそれを優先的に選択するという新しいロジックを直接的に反映しています。

このテストケースの変更は、コードの変更が意図通りに機能していることを確認するためのものであり、同時に、以前の複雑なタグ選択ロジックがもはやテストの対象ではないことを示しています。

関連リンク

  • Go 1 Compatibility Promise: https://go.dev/doc/go1compat
  • go get command documentation (Go 1.0): https://go.dev/doc/go1.html#get (Go 1.0のドキュメントは直接リンクが難しいですが、Go 1のリリースノートやアーカイブされたドキュメントで確認できます。)

参考にした情報源リンク