[インデックス 11586] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go
におけるバージョン管理システム (VCS) の統合、特にGitリポジトリの扱いに関するものです。具体的には、go get
コマンドなどがリポジトリから特定のバージョン(タグ)を取得する際に、従来のGitタグに加えてGitブランチも「リリース用のタグ」として扱えるように拡張する変更が加えられています。これにより、開発者はGitのブランチ名を指定して特定のリリースバージョンを取得できるようになり、より柔軟なバージョン管理が可能になります。
コミット
commit 7207898fe489e967e42877f9a8dc092636c206dc
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Fri Feb 3 03:03:13 2012 -0200
cmd/go: add support for release tags via git branches
R=rsc, remyoudompheng, gustavo, dsymonds
CC=golang-dev
https://golang.org/cl/5617057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7207898fe489e967e42877f9a8dc092636c206dc
元コミット内容
このコミットは、cmd/go
ツールがGitリポジトリからバージョンを取得する際の挙動を変更します。以前は、go get -u
などで特定のバージョンを指定する際、Gitの「タグ」のみが考慮されていました。この変更により、Gitの「ブランチ」もリリースバージョンを示すものとして認識され、指定されたブランチをチェックアウトできるようになります。これは、特に長期サポート (LTS) ブランチや、特定の機能セットを安定版として提供するブランチ運用を行っているプロジェクトにとって有用です。
変更の背景
Goの go get
コマンドは、指定されたパッケージのソースコードをダウンロードし、ビルドするために使用されます。このコマンドは、VCS (Version Control System) と連携して動作し、リポジトリから適切なバージョンを取得します。
このコミットが導入された背景には、当時のGoエコシステムにおけるバージョン管理の慣習と、go get
の機能的な制約がありました。
- タグとブランチの使い分け: Gitでは、タグは通常、特定のコミットに対する不変の参照(例:
v1.0.0
)として使用され、ブランチは開発の並行ライン(例:master
,develop
,feature/x
)として使用されます。しかし、一部のプロジェクトでは、安定版のリリースを特定のブランチ(例:release-1.x
)で管理し、そのブランチの最新コミットを「最新の安定版」として提供する運用モデルを採用していました。 go get
のタグ依存: 従来のgo get
は、特定のバージョンを指定する際にGitのタグを優先的に探していました。そのため、ブランチ名を指定しても、それがタグとして認識されない限り、意図したバージョンのコードを取得できないという問題がありました。- 柔軟性の向上: 開発者が
go get
を通じて、タグだけでなくブランチもバージョン指定の手段として利用できるようにすることで、より多様なプロジェクトのバージョン管理戦略に対応し、ツールの柔軟性を高める必要がありました。
この変更は、go get
がGitリポジトリからコードを取得する際の「タグ」の解釈を拡張し、ブランチもその対象に含めることで、これらの課題を解決しようとするものです。
前提知識の解説
このコミットの変更内容を理解するためには、以下の概念について理解しておく必要があります。
-
Go Modules と
go get
:go get
: Go言語のパッケージ管理コマンドの一つで、リモートリポジトリからGoのソースコードをダウンロードし、依存関係を解決するために使用されます。Go Modulesが導入される前は、主に$GOPATH
内にソースコードを配置し、依存関係を管理していました。このコミットが作成された2012年時点では、Go Modulesはまだ存在せず、go get
はVCSと直接連携して動作していました。- Go Modules (補足): 現在のGoでは、Go Modulesが標準の依存関係管理システムとなっています。Go Modulesでは、
go.mod
ファイルで依存関係が明示的に定義され、バージョンはセマンティックバージョニングに基づいて管理されます。しかし、このコミットの時点では、go get
がVCSのタグやブランチを直接解釈する機能が重要でした。
-
Gitのタグとブランチ:
- ブランチ (Branch): Gitにおける開発の並行ラインです。新しい機能開発やバグ修正を行う際にブランチを作成し、独立した作業を進めることができます。ブランチは常に最新のコミットを指し示し、新しいコミットが追加されるとブランチの参照も移動します。
- タグ (Tag): Gitにおける特定のコミットに対する不変の参照です。通常、リリースバージョン(例:
v1.0.0
)や重要なマイルストーンを示すために使用されます。タグは一度作成されると、その参照先が移動することはありません。 git show-ref
: Gitコマンドの一つで、リポジトリ内の参照(ブランチ、タグ、リモート追跡ブランチなど)を一覧表示します。例えば、git show-ref --tags
はタグの一覧を、git show-ref --heads
はローカルブランチの一覧を表示します。git show-ref tags/v1.0.0
のように特定の参照を検索することも可能です。
-
正規表現 (Regular Expression):
- 文字列のパターンを記述するための強力なツールです。このコミットでは、Gitコマンドの出力からタグやブランチの名前を抽出するために正規表現が使用されています。
^(\S+)$
: 行の先頭 (^
) から末尾 ($
) まで、空白文字以外の任意の文字 (\S+
) が1回以上続くパターンにマッチし、その部分をキャプチャグループ (()
) で抽出します。(?:tags|origin)/(\S+)$
:tags/
またはorigin/
のいずれか ((?:...)
は非キャプチャグループ) の後に、空白文字以外の任意の文字 (\S+
) が1回以上続くパターンにマッチし、その\S+
の部分をキャプチャグループで抽出します。
-
Go言語の
regexp
パッケージ:- Go言語の標準ライブラリで、正規表現を扱うための機能を提供します。
regexp.MustCompile
は正規表現をコンパイルし、FindAllString
やFindAllStringSubmatch
は文字列内でパターンにマッチする部分を検索します。
- Go言語の標準ライブラリで、正規表現を扱うための機能を提供します。
技術的詳細
このコミットの主要な変更は、src/cmd/go/vcs.go
ファイル内の vcsGit
構造体の定義と、それに付随する tags
および tagSync
メソッドのロジック変更にあります。
-
vcsCmd
構造体の拡張:vcsCmd
構造体にtagLookupCmd []tagCmd
という新しいフィールドが追加されました。これは、特定のタグ(またはブランチ名)が与えられたときに、それが実際にGitリポジトリ内のどの参照(タグまたはブランチ)に対応するかを検索するためのコマンドを定義します。
-
vcsGit
のtagCmd
の変更:vcsGit
はGitリポジトリを扱うためのvcsCmd
の実装です。- 以前の
tagCmd
は{"tag",
^(\S+)$}
で、git tag
コマンドの出力からタグ名を直接抽出していました。 - 新しい
tagCmd
は{"show-ref",
(?:tags|origin)/(\S+)$}
となりました。これはgit show-ref
コマンドを使用し、その出力からtags/
またはorigin/
で始まる参照(つまり、Gitタグまたはリモート追跡ブランチ)を抽出し、その後の名前部分をキャプチャするように変更されました。これにより、go get
はGitタグだけでなく、origin/master
のようなブランチも「タグ」として認識できるようになります。
-
vcsGit
のtagLookupCmd
の追加:tagLookupCmd
に{"show-ref tags/{tag} origin/{tag}",
((?:tags|origin)/\S+)$}
が追加されました。- このコマンドは、ユーザーが指定した
{tag}
が、tags/{tag}
(Gitタグ)またはorigin/{tag}
(リモート追跡ブランチ)のいずれかに対応するかをgit show-ref
を使って確認します。 - 正規表現
((?:tags|origin)/\S+)$
は、マッチした参照の完全な名前(例:tags/v1.0.0
やorigin/my-branch
)をキャプチャします。
-
run1
関数のリファクタリング:run
とrunOutput
の内部で呼び出されるrun1
関数から、output
というブーリアン引数が削除されました。これは機能的な変更ではなく、run
とrunOutput
がそれぞれrun1
を呼び出す際に、出力の扱いをより明確にするためのリファクタリングです。runOutput
はrun1
の結果をそのまま返し、run
は結果を破棄します。
-
tags
メソッドの変更:vcsCmd.tags
メソッドは、tagCmd
で定義されたコマンドを実行し、その出力からタグ名を抽出します。- 変更前は
re.FindAllString
を使用していましたが、これは正規表現にマッチする文字列全体を返します。 - 変更後は
re.FindAllStringSubmatch
を使用し、その結果のm[1]
(最初のキャプチャグループ)をtags
スライスに追加するように変更されました。これは、新しいtagCmd
の正規表現が(\S+)
というキャプチャグループを持つため、そのグループ内の値(実際のタグ/ブランチ名)を抽出するために必要です。
-
tagSync
メソッドの変更:vcsCmd.tagSync
メソッドは、指定されたtag
にリポジトリを同期(チェックアウト)する役割を担います。- このコミットの最も重要な変更点の一つは、
tagSync
の冒頭にtagLookupCmd
を使用した新しいロジックが追加されたことです。 - もし
tag
が指定されている場合、tagLookupCmd
に定義されたコマンド(例:git show-ref tags/{tag} origin/{tag}
)を実行し、その出力から正規表現を使って実際のGit参照(tags/v1.0.0
やorigin/my-branch
など)を検索します。 - もし有効な参照が見つかれば、その参照で
tag
変数を更新し、その後のv.run(dir, v.tagSyncCmd)
でgit checkout {tag}
が実行される際に、正しいGit参照が使用されるようにします。 - これにより、ユーザーが
v1.0.0
と指定した場合でも、master
と指定した場合でも、go get
が適切にGitリポジトリをチェックアウトできるようになります。
これらの変更により、cmd/go
はGitリポジトリのブランチを「リリースバージョン」として認識し、go get
コマンドでブランチ名を指定して特定のコードベースを取得する機能が実現されました。
コアとなるコードの変更箇所
src/cmd/go/vcs.go
ファイルにおける主要な変更箇所は以下の通りです。
-
vcsCmd
構造体へのtagLookupCmd
フィールドの追加:--- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -24,6 +24,7 @@ type vcsCmd struct { downloadCmd string // command to download updates into an existing repository tagCmd []tagCmd // commands to list tags + tagLookupCmd []tagCmd // commands to lookup tags before running tagSyncCmd tagSyncCmd string // command to sync to specific tag tagSyncDefault string // command to sync to default tag }
-
vcsGit
定義におけるtagCmd
の変更とtagLookupCmd
の追加:--- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -83,7 +84,14 @@ var vcsGit = &vcsCmd{ createCmd: "clone {repo} {dir}", downloadCmd: "fetch", - tagCmd: []tagCmd{{"tag", `^(\S+)$`}}, + tagCmd: []tagCmd{ + // tags/xxx matches a git tag named xxx + // origin/xxx matches a git branch named xxx on the default remote repository + {"show-ref", `(?:tags|origin)/(\S+)$`}, + }, + tagLookupCmd: []tagCmd{ + {"show-ref tags/{tag} origin/{tag}", `((?:tags|origin)/\S+)$`}, + }, tagSyncCmd: "checkout {tag}", tagSyncDefault: "checkout origin/master", }
-
run1
関数のシグネチャ変更 (リファクタリング):--- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -128,17 +136,17 @@ func (v *vcsCmd) String() string { // command's combined stdout+stderr to standard error. // Otherwise run discards the command's output. func (v *vcsCmd) run(dir string, cmd string, keyval ...string) error { - _, err := v.run1(dir, false, cmd, keyval) + _, err := v.run1(dir, cmd, keyval) return err } // runOutput is like run but returns the output of the command. func (v *vcsCmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) { - return v.run1(dir, true, cmd, keyval) + return v.run1(dir, cmd, keyval) } // run1 is the generalized implementation of run and runOutput. -func (v *vcsCmd) run1(dir string, output bool, cmdline string, keyval []string) ([]byte, error) { +func (v *vcsCmd) run1(dir string, cmdline string, keyval []string) ([]byte, error) { m := make(map[string]string) for i := 0; i < len(keyval); i += 2 { m[keyval[i]] = keyval[i+1]
-
tags
メソッドでの正規表現マッチングの変更:--- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -187,7 +195,9 @@ func (v *vcsCmd) tags(dir string) ([]string, error) { return nil, err } re := regexp.MustCompile(`(?m-s)` + tc.pattern) - tags = append(tags, re.FindAllString(string(out), -1)...) + for _, m := range re.FindAllStringSubmatch(string(out), -1) { + tags = append(tags, m[1]) + } } return tags, nil }
-
tagSync
メソッドでのtagLookupCmd
を使用したタグ解決ロジックの追加:--- a/src/cmd/go/vcs.go +++ b/src/cmd/go/vcs.go @@ -198,6 +208,20 @@ func (v *vcsCmd) tagSync(dir, tag string) error { if v.tagSyncCmd == "" { return nil } + if tag != "" { + for _, tc := range v.tagLookupCmd { + out, err := v.runOutput(dir, tc.cmd, "tag", tag) + if err != nil { + return err + } + re := regexp.MustCompile(`(?m-s)` + tc.pattern) + m := re.FindStringSubmatch(string(out)) + if len(m) > 1 { + tag = m[1] + break + } + } + } if tag == "" && v.tagSyncDefault != "" { return v.run(dir, v.tagSyncDefault) }
コアとなるコードの解説
このコミットの核心は、go
コマンドがGitリポジトリの「タグ」をどのように解釈し、同期するかというロジックの変更にあります。
-
vcsCmd
構造体とtagLookupCmd
:vcsCmd
は、Goがサポートする様々なVCS(Git, Mercurial, Subversionなど)の操作を抽象化するための構造体です。tagLookupCmd
は、ユーザーが指定した文字列(例:v1.0.0
やmaster
)が、Gitリポジトリ内のどの実際の参照(tags/v1.0.0
やorigin/master
)に対応するかを特定するためのコマンドと正規表現のペアを定義します。これにより、go get
はユーザーの意図をより正確に解釈できるようになります。
-
vcsGit
のtagCmd
とtagLookupCmd
の連携:vcsGit.tagCmd
は、リポジトリ内の利用可能な「タグ」(この変更後はブランチも含む)を列挙するためにgit show-ref
を使用します。正規表現(?:tags|origin)/(\S+)$
は、tags/
またはorigin/
で始まる参照から、その後の実際の名前(例:v1.0.0
やmaster
)を抽出します。vcsGit.tagLookupCmd
は、特定の名前が与えられたときに、それがGitタグなのか、それともリモート追跡ブランチなのかを明示的に確認するためにgit show-ref tags/{tag} origin/{tag}
を実行します。これにより、例えばユーザーがmaster
と指定した場合に、それがorigin/master
ブランチとして正しく解決されるようになります。
-
tags
メソッドの正規表現キャプチャ:tags
メソッドは、tagCmd
の結果を処理し、利用可能なタグ(およびブランチ名)のリストを返します。re.FindAllStringSubmatch(string(out), -1)
とm[1]
の使用は、正規表現のキャプチャグループの重要性を示しています。tagCmd
の正規表現(?:tags|origin)/(\S+)$
において、(\S+)
が実際のタグ/ブランチ名をキャプチャするグループであり、この変更によってそのキャプチャされた値が正しく抽出されるようになりました。
-
tagSync
メソッドのタグ解決ロジック:tagSync
メソッドは、指定されたtag
にリポジトリをチェックアウトする最終的な処理を行います。- このメソッドの冒頭に追加されたループは、
tagLookupCmd
を利用して、ユーザーが指定したtag
文字列を実際のGit参照(例:tags/v1.0.0
やorigin/my-branch
)に解決しようとします。 - もし解決に成功すれば、
tag
変数は解決された完全な参照名で更新され、その後のgit checkout {tag}
コマンドが正確なターゲットに対して実行されることを保証します。これにより、go get
はブランチ名を指定された場合でも、そのブランチの最新コミットを正しく取得できるようになります。
この一連の変更により、go
コマンドはGitリポジトリのブランチを「リリースバージョン」として扱い、開発者がより柔軟なバージョン指定を行えるようになりました。
関連リンク
- Gerrit Code Review: https://golang.org/cl/5617057
- このコミットに対応するGoプロジェクトのGerritコードレビューページです。当時の議論やレビューコメント、変更の経緯などを確認できます。
参考にした情報源リンク
- Git Documentation - git-tag: https://git-scm.com/docs/git-tag
- Git Documentation - git-branch: https://git-scm.com/docs/git-branch
- Git Documentation - git-show-ref: https://git-scm.com/docs/git-show-ref
- Go Command Documentation (当時の情報に基づく):
go get
コマンドの当時の挙動に関する情報源。 - Go regexp package: https://pkg.go.dev/regexp
- Go言語の正規表現パッケージに関する公式ドキュメント。
FindAllStringSubmatch
などの関数の詳細を確認できます。
- Go言語の正規表現パッケージに関する公式ドキュメント。