[インデックス 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言語の正規表現パッケージに関する公式ドキュメント。