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

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

このコミットは、Go言語のコマンドラインツール cmd/go におけるバージョン管理システム (VCS) とタグ選択ロジックの更新に関するものです。具体的には、Go 1リリースで導入された新しいタグフォーマットに対応するための変更が含まれています。

コミット

commit d6c9af6a4ec531369340e51cb008da514477ef17
Author: Russ Cox <rsc@golang.org>
Date:   Tue Mar 27 00:17:50 2012 -0400

    cmd/go: update for go1 tag format
    
    R=golang-dev, r, r
    CC=golang-dev
    https://golang.org/cl/5919048

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

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

元コミット内容

cmd/go: update for go1 tag format

R=golang-dev, r, r
CC=golang-dev
https://golang.org/cl/5919048

変更の背景

このコミットは、Go言語がバージョン1(Go 1)をリリースするにあたり、そのバージョン管理とリリースプロセスの変更に対応するために行われました。Go 1以前は、リリースバージョンは release.rNgo.weekly.YYYY-MM-DD のような形式でタグ付けされていました。しかし、Go 1のリリースに伴い、より簡潔で標準的な goXgoX.YgoX.Y.Z といった形式のタグが導入されることになりました。

cmd/go ツールは、go get コマンドなどで外部パッケージを取得する際に、特定のバージョン(タグ)を選択するロジックを持っています。このロジックが新しい go1 形式のタグを正しく認識し、既存の release.rNweekly タグと共存できるように更新する必要がありました。特に、go get が指定されたGoバージョンに最も近いタグを選択する際の挙動を、新しいタグフォーマットに合わせて調整することが求められました。

この変更は、Go 1の安定版リリースに向けた準備の一環であり、Goエコシステム全体の整合性を保つ上で重要なステップでした。

前提知識の解説

Go言語のバージョン管理とタグ

Go言語のプロジェクトでは、Gitなどのバージョン管理システムが利用され、特定のリリースや重要な時点には「タグ」が付けられます。これらのタグは、特定のコミットを指し示すポインタのようなもので、開発者が特定のバージョンのコードを簡単に取得できるようにするために使用されます。

Go 1以前は、以下のようなタグ形式が使われていました。

  • release.rN: リリース候補や安定版リリースを示すタグ。N はリビジョン番号。例: release.r60
  • go.weekly.YYYY-MM-DD: 週次スナップショットを示すタグ。例: go.weekly.2011-11-01

Go 1以降は、セマンティックバージョニングに近い形式が採用され、以下のようなタグ形式が導入されました。

  • goX: メジャーバージョン。例: go1, go5
  • goX.Y: マイナーバージョン。例: go1.0.1, go1.9
  • goX.Y.Z: パッチバージョン。例: go1.0.1

cmd/go ツールと go get

cmd/go はGo言語の公式ツールチェインに含まれるコマンドラインツールです。その中でも go get コマンドは、リモートリポジトリからGoパッケージをダウンロードし、ビルドしてインストールするために広く使われます。go get は、指定されたパッケージのソースコードを取得する際に、リポジトリのタグ情報を利用して特定のバージョンを選択することがあります。

正規表現 (Regular Expression)

正規表現は、文字列のパターンを記述するための強力なツールです。このコミットでは、新しいGoタグのフォーマットを検証するために正規表現が導入されています。

  • ^: 文字列の先頭にマッチ
  • $: 文字列の末尾にマッチ
  • go: リテラル文字列 "go" にマッチ
  • (0|[1-9][0-9]{0,3}): 0 または 1-9999 の数字にマッチ(先頭の0は許可しないが、0単体は許可)
    • 0: 数字の0
    • [1-9][0-9]{0,3}: 1から9の数字が1つ、その後に0から9の数字が0〜3つ続く(例: 1, 10, 123, 1234)
  • \.: リテラル文字 "." にマッチ(. は正規表現の特殊文字なのでエスケープが必要)
  • *: 直前の要素が0回以上繰り返される
  • ([1-9][0-9]{0,3}): 1-9999 の数字にマッチ(先頭の0は許可しない)

この正規表現 ^go((0|[1-9][0-9]{0,3})\\.)*([1-9][0-9]{0,3})$ は、go で始まり、その後に . で区切られた最大4桁の数字が続き、最後の数字も最大4桁で . で終わらない形式のタグ(例: go1, go1.2, go1.2.3)にマッチするように設計されています。ただし、go1.0go1.0.0 のように .0 で終わるバージョンは許可しないという制約があります。

技術的詳細

このコミットの主要な変更点は、src/cmd/go/get.go ファイル内の selectTag 関数と、新たに追加された goTag 正規表現、そして cmpGoVersion 関数です。

  1. goTag 正規表現の導入:

    • goTag = regexp.MustCompile(^go((0|[1-9][0-9]{0,3}).)*([1-9][0-9]{0,3})$)
    • この正規表現は、go1, go1.2.3 のような新しいGoリリースタグのフォーマットを厳密に定義し、検証するために使用されます。数字は最大4桁で、不必要な先行ゼロがなく、バージョンが .0 で終わらないという制約が組み込まれています。
  2. selectTag 関数の変更:

    • selectTag 関数は、与えられたGoバージョン文字列と利用可能なタグのリストから、最も近いマッチングタグを選択する役割を担っています。
    • 既存の release.rN および weekly タグの処理ロジックに加えて、新しい goTag フォーマットのタグを処理するセクションが追加されました。
    • 新しいセクションでは、goTag.MatchString(goVersion) を使用して入力バージョンが新しいGoタグフォーマットに合致するかをチェックします。
    • 合致する場合、利用可能なタグの中から goTag.MatchString(t) で新しいGoタグフォーマットに合致するタグのみを抽出し、cmpGoVersion 関数を使ってバージョンを比較し、最も近い(かつ指定バージョンを超えない)タグを選択します。
  3. cmpGoVersion 関数の追加:

    • cmpGoVersion(x, y string) int は、2つのGoバージョン文字列 xy を比較し、x < y なら -1x == y なら 0x > y なら +1 を返します。
    • この関数は、まず goTag.MatchString を使って入力文字列が正しいGoタグフォーマットであるかを検証します。不正なフォーマットの文字列は、正しいフォーマットの文字列よりも小さいと判断されます。
    • 次に、go プレフィックスを除去し、. で分割して各バージョンコンポーネント(例: 1, 2, 3)を数値として比較します。
    • これにより、go1.9go1.10 のようなバージョンを正しく比較できるようになります(文字列比較では go1.10go1.9 より小さいと誤認される可能性があるため)。
  4. src/cmd/go/tag_test.go のテストケースの追加:

    • 新しいGoタグフォーマット (go1, go1.0.1, go1.999 など) と、それらが正しく選択されるかどうかのテストケースが selectTagTestTagsselectTagTests に追加されました。
    • また、不正なGoタグフォーマット (go2x, go2.0 など) が正しく無視されるかどうかのテストケースも追加されています。
  5. src/cmd/go/vcs.go のコメント修正:

    • vcsHg 構造体内のコメントが go.1 branch から go1 branch に修正され、新しいタグフォーマットに合わせた記述になっています。これは機能的な変更ではなく、ドキュメントの整合性を保つためのものです。

これらの変更により、cmd/go ツールはGo 1リリース以降の新しいタグフォーマットを適切に処理し、go get コマンドが期待通りに動作するようになります。

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

src/cmd/go/get.go

--- a/src/cmd/go/get.go
+++ b/src/cmd/go/get.go
@@ -11,6 +11,7 @@ import (
 	"go/build"
 	"os"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"strconv"
 	"strings"
@@ -323,9 +324,18 @@ func downloadPackage(p *Package) error {
 	return nil
 }
 
+// goTag matches go release tags such as go1 and go1.2.3.
+// The numbers involved must be small (at most 4 digits),
+// have no unnecessary leading zeros, and the version cannot
+// end in .0 - it is go1, not go1.0 or go1.0.0.
+var goTag = regexp.MustCompile(
+	`^go((0|[1-9][0-9]{0,3})\.)*([1-9][0-9]{0,3})$`,
+)
+
 // selectTag returns the closest matching tag for a given version.
 // Closest means the latest one that is not after the current release.
-// Version "release.rN" matches tags of the form "go.rN" (N being a decimal).
+// 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".
 func selectTag(goVersion string, tags []string) (match string) {
 	const rPrefix = "release.r"
@@ -349,6 +359,7 @@ func selectTag(goVersion string, tags []string) (match string) {
 			}
 		}
 	}
+
 	const wPrefix = "weekly."
 	if strings.HasPrefix(goVersion, wPrefix) {
 		p := "go.weekly."
@@ -362,5 +373,54 @@ func selectTag(goVersion string, tags []string) (match string) {
 			}
 		}
 	}
+\
+\tif goTag.MatchString(goVersion) {
+\t\tv := goVersion
+\t\tfor _, t := range tags {
+\t\t\tif !goTag.MatchString(t) {
+\t\t\t\tcontinue
+\t\t\t}
+\t\t\tif cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 {
+\t\t\t\tmatch = t
+\t\t\t}
+\t\t}
+\t}
+\
 	return match
 }
+
+// cmpGoVersion returns -1, 0, +1 reporting whether
+// x < y, x == y, or x > y.
+func cmpGoVersion(x, y string) int {
+	// Malformed strings compare less than well-formed strings.
+	if !goTag.MatchString(x) {
+		return -1
+	}
+	if !goTag.MatchString(y) {
+		return +1
+	}
+
+	// Compare numbers in sequence.
+	xx := strings.Split(x[len("go"):], ".")
+	yy := strings.Split(y[len("go"):], ".")
+
+	for i := 0; i < len(xx) && i < len(yy); i++ {
+		// The Atoi are guaranteed to succeed
+		// because the versions match goTag.
+		xi, _ := strconv.Atoi(xx[i])
+		yi, _ := strconv.Atoi(yy[i])
+		if xi < yi {
+			return -1
+		} else if xi > yi {
+			return +1
+		}
+	}
+
+	if len(xx) < len(yy) {
+		return -1
+	}
+	if len(xx) > len(yy) {
+		return +1
+	}
+	return 0
+}

src/cmd/go/tag_test.go

--- a/src/cmd/go/tag_test.go
+++ b/src/cmd/go/tag_test.go
@@ -18,6 +18,12 @@ var selectTagTestTags = []string{
 	"go.weekly.2011-10-12.1",
 	"go.weekly.2011-10-14",
 	"go.weekly.2011-11-01",
+	"go1",
+	"go1.0.1",
+	"go1.999",
+	"go1.9.2",
+	"go5",
+
 	// these should be ignored:
 	"release.r59",
 	"release.r59.1",
@@ -30,6 +36,14 @@ var selectTagTestTags = []string{
 	"go.f00",
 	"go!r60",
 	"go.1999-01-01",
+	"go.2x",
+	"go.20000000000000",
+	"go.2.",
+	"go.2.0",
+	"go2x",
+	"go20000000000000",
+	"go2.",
+	"go2.0",
 }
 
 var selectTagTests = []struct {
@@ -56,11 +70,21 @@ var selectTagTests = []struct {
 	{"weekly.2011-11-01", "go.weekly.2011-11-01"},
 	{"weekly.2014-01-01", "go.weekly.2011-11-01"},
 	{"weekly.3000-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", ""},
 }
 
 func TestSelectTag(t *testing.T) {

src/cmd/go/vcs.go

--- a/src/cmd/go/vcs.go
+++ b/src/cmd/go/vcs.go
@@ -71,7 +71,7 @@ var vcsHg = &vcsCmd{
 
 	// We allow both tag and branch names as 'tags'
 	// for selecting a version.  This lets people have
-	// a go.release.r60 branch and a go.1 branch
+	// a go.release.r60 branch and a go1 branch
 	// and make changes in both, without constantly
 	// editing .hgtags.
 	tagCmd: []tagCmd{

コアとなるコードの解説

src/cmd/go/get.go

  • regexp パッケージのインポート: 新たに正規表現を使用するために regexp パッケージがインポートされています。
  • goTag 変数:
    • var goTag = regexp.MustCompile(...) で、Go 1以降の新しいタグフォーマット (go1, go1.2.3 など) にマッチする正規表現が定義されています。
    • この正規表現は、数字が最大4桁であること、先行ゼロがないこと(ただし 0 単体は許可)、そして .0 で終わらないことを強制します。これにより、Goのバージョン命名規則に厳密に従うタグのみが有効と判断されます。
  • selectTag 関数の変更:
    • 既存の release.rN および weekly タグの処理ロジックの下に、新しい if goTag.MatchString(goVersion) ブロックが追加されています。
    • このブロックは、goVersion が新しいGoタグフォーマットに合致する場合に実行されます。
    • for _, t := range tags ループ内で、利用可能なすべてのタグ t に対して goTag.MatchString(t) を実行し、新しいGoタグフォーマットに合致するタグのみを対象とします。
    • cmpGoVersion(match, t) < 0 && cmpGoVersion(t, v) <= 0 という条件は、現在見つかっている最適なマッチ match よりも t が新しいバージョンであり、かつ t が要求されたバージョン v を超えない場合に matcht に更新するというロジックです。これにより、要求されたバージョンに最も近い、かつそれを超えない最新のタグが選択されます。
  • cmpGoVersion 関数の追加:
    • この関数は、2つのGoバージョン文字列 xy を比較し、その相対的な順序を整数で返します。
    • 不正なフォーマットの処理: 最初に !goTag.MatchString(x)!goTag.MatchString(y) で、入力文字列が goTag 正規表現にマッチするかをチェックします。マッチしない(不正なフォーマットの)文字列は、マッチする(正しいフォーマットの)文字列よりも小さいと判断されます。これにより、不正なタグが比較ロジックに影響を与えるのを防ぎます。
    • 数値としての比較: strings.Split(x[len("go"):], ".") を使用して、go プレフィックスを除去し、残りのバージョン文字列を . で分割して数値の配列に変換します(例: go1.2.3["1", "2", "3"] となり、これが [1, 2, 3] として比較されます)。
    • strconv.Atoi は、goTag 正規表現によって数値であることが保証されているため、エラーハンドリングは省略されています。
    • 各コンポーネントを数値として順に比較し、異なる値が見つかった時点で大小関係を決定します。
    • 長さの比較: すべての共通コンポーネントが等しい場合、バージョン文字列の長さ(コンポーネントの数)を比較します。コンポーネントが多い方が新しいバージョンと判断されます(例: go1.2.3go1.2 より新しい)。

src/cmd/go/tag_test.go

  • selectTagTestTags の更新:
    • テストで使用されるタグのリストに、go1, go1.0.1, go1.999, go1.9.2, go5 といった新しいGoタグフォーマットの有効なタグが追加されています。
    • また、go.2x, go.20000000000000, go.2., go.2.0, go2x, go200000000000, go2., go2.0 といった、新しい goTag 正規表現では無効となるタグも追加され、これらが正しく無視されることをテストします。
  • selectTagTests の更新:
    • selectTag 関数の挙動を検証するためのテストケースが追加されています。
    • {"go1", "go1"}: go1 を要求した場合に go1 が選択される。
    • {"go1.1", "go1.0.1"}: go1.1 を要求した場合に、利用可能なタグの中で go1.1 を超えず、最も近い go1.0.1 が選択される。
    • {"go1.998", "go1.9.2"}: go1.998 を要求した場合に go1.9.2 が選択される。
    • {"go1.1000", "go1.999"}: go1.1000 を要求した場合に go1.999 が選択される。
    • {"go6", "go5"}: go6 を要求した場合に go5 が選択される。
    • 不正なバージョン文字列 (go2x, go200000000000, go2., go2.0) が与えられた場合に、空文字列 "" が返される(つまり、マッチするタグがない)ことを確認するテストも追加されています。

src/cmd/go/vcs.go

  • コメントの修正: go.1 branch という古い表記が go1 branch という新しい表記に更新されています。これはコードの動作には影響しませんが、ドキュメントの正確性を向上させます。

これらの変更は、Go言語のバージョン管理システムがGo 1リリースに合わせて進化し、cmd/go ツールがその新しい規則を正確に解釈・適用できるようにするための重要なステップでした。

関連リンク

  • Go 1 Release Notes (Go 1のリリースノートには、バージョン管理に関する直接的な言及は少ないですが、Go 1がGo言語の安定した基盤を確立したことを示しています。)
  • golang/go GitHub Repository

参考にした情報源リンク

src/cmd/go/tag_test.go

  • selectTagTestTags の更新:
    • テストで使用されるタグのリストに、go1, go1.0.1, go1.999, go1.9.2, go5 といった新しいGoタグフォーマットの有効なタグが追加されています。
    • また、go.2x, go.20000000000000, go.2., go.2.0, go2x, go200000000000, go2., go2.0 といった、新しい goTag 正規表現では無効となるタグも追加され、これらが正しく無視されることをテストします。
  • selectTagTests の更新:
    • selectTag 関数の挙動を検証するためのテストケースが追加されています。
    • {"go1", "go1"}: go1 を要求した場合に go1 が選択される。
    • {"go1.1", "go1.0.1"}: go1.1 を要求した場合に、利用可能なタグの中で go1.1 を超えず、最も近い go1.0.1 が選択される。
    • {"go1.998", "go1.9.2"}: go1.998 を要求した場合に go1.9.2 が選択される。
    • {"go1.1000", "go1.999"}: go1.1000 を要求した場合に go1.999 が選択される。
    • {"go6", "go5"}: go6 を要求した場合に go5 が選択される。
    • 不正なバージョン文字列 (go2x, go200000000000, go2., go2.0) が与えられた場合に、空文字列 "" が返される(つまり、マッチするタグがない)ことを確認するテストも追加されています。

src/cmd/go/vcs.go

  • コメントの修正: go.1 branch という古い表記が go1 branch という新しい表記に更新されています。これはコードの動作には影響しませんが、ドキュメントの正確性を向上させます。

これらの変更は、Go言語のバージョン管理システムがGo 1リリースに合わせて進化し、cmd/go ツールがその新しい規則を正確に解釈・適用できるようにするための重要なステップでした。

関連リンク

  • Go 1 Release Notes (Go 1のリリースノートには、バージョン管理に関する直接的な言及は少ないですが、Go 1がGo言語の安定した基盤を確立したことを示しています。)
  • golang/go GitHub Repository

参考にした情報源リンク

src/cmd/go/tag_test.go

  • selectTagTestTags の更新:
    • テストで使用されるタグのリストに、go1, go1.0.1, go1.999, go1.9.2, go5 といった新しいGoタグフォーマットの有効なタグが追加されています。
    • また、go.2x, go.20000000000000, go.2., go.2.0, go2x, go200000000000, go2., go2.0 といった、新しい goTag 正規表現では無効となるタグも追加され、これらが正しく無視されることをテストします。
  • selectTagTests の更新:
    • selectTag 関数の挙動を検証するためのテストケースが追加されています。
    • {"go1", "go1"}: go1 を要求した場合に go1 が選択される。
    • {"go1.1", "go1.0.1"}: go1.1 を要求した場合に、利用可能なタグの中で go1.1 を超えず、最も近い go1.0.1 が選択される。
    • {"go1.998", "go1.9.2"}: go1.998 を要求した場合に go1.9.2 が選択される。
    • {"go1.1000", "go1.999"}: go1.1000 を要求した場合に go1.999 が選択される。
    • {"go6", "go5"}: go6 を要求した場合に go5 が選択される。
    • 不正なバージョン文字列 (go2x, go200000000000, go2., go2.0) が与えられた場合に、空文字列 "" が返される(つまり、マッチするタグがない)ことを確認するテストも追加されています。

src/cmd/go/vcs.go

  • コメントの修正: go.1 branch という古い表記が go1 branch という新しい表記に更新されています。これはコードの動作には影響しませんが、ドキュメントの正確性を向上させます。

これらの変更は、Go言語のバージョン管理システムがGo 1リリースに合わせて進化し、cmd/go ツールがその新しい規則を正確に解釈・適用できるようにするための重要なステップでした。

関連リンク

  • Go 1 Release Notes (Go 1のリリースノートには、バージョン管理に関する直接的な言及は少ないですが、Go 1がGo言語の安定した基盤を確立したことを示しています。)
  • golang/go GitHub Repository

参考にした情報源リンク

src/cmd/go/tag_test.go

  • selectTagTestTags の更新:
    • テストで使用されるタグのリストに、go1, go1.0.1, go1.999, go1.9.2, go5 といった新しいGoタグフォーマットの有効なタグが追加されています。
    • また、go.2x, go.20000000000000, go.2., go.2.0, go2x, go200000000000, go2., go2.0 といった、新しい goTag 正規表現では無効となるタグも追加され、これらが正しく無視されることをテストします。
  • selectTagTests の更新:
    • selectTag 関数の挙動を検証するためのテストケースが追加されています。
    • {"go1", "go1"}: go1 を要求した場合に go1 が選択される。
    • {"go1.1", "go1.0.1"}: go1.1 を要求した場合に、利用可能なタグの中で go1.1 を超えず、最も近い go1.0.1 が選択される。
    • {"go1.998", "go1.9.2"}: go1.998 を要求した場合に go1.9.2 が選択される。
    • {"go1.1000", "go1.999"}: go1.1000 を要求した場合に go1.999 が選択される。
    • {"go6", "go5"}: go6 を要求した場合に go5 が選択される。
    • 不正なバージョン文字列 (go2x, go200000000000, go2., go2.0) が与えられた場合に、空文字列 "" が返される(つまり、マッチするタグがない)ことを確認するテストも追加されています。

src/cmd/go/vcs.go

  • コメントの修正: go.1 branch という古い表記が go1 branch という新しい表記に更新されています。これはコードの動作には影響しませんが、ドキュメントの正確性を向上させます。

これらの変更は、Go言語のバージョン管理システムがGo 1リリースに合わせて進化し、cmd/go ツールがその新しい規則を正確に解釈・適用できるようにするための重要なステップでした。

関連リンク

  • Go 1 Release Notes (Go 1のリリースノートには、バージョン管理に関する直接的な言及は少ないですが、Go 1がGo言語の安定した基盤を確立したことを示しています。)
  • golang/go GitHub Repository

参考にした情報源リンク