[インデックス 12566] ファイルの概要
このコミットは、cmd/godoc
ツールにおけるディレクトリ読み込みの不具合を修正するものです。具体的には、以前の変更(CL 5783076)によって導入された誤ったロジックを元に戻し、godoc
がGoファイルを含まないディレクトリを正しく処理できるように修正しています。特に、/doc
パスに対する特殊な処理("doc hack")が誤りであったことを指摘し、既存のコードが本来意図していた挙動に戻すことで問題を解決しています。
コミット
commit da8efae9fe6a3d5f5e6dffc2c70c835fa6724cbb
Author: Russ Cox <rsc@golang.org>
Date: Mon Mar 12 13:10:37 2012 -0400
cmd/godoc: fix directory read
Undo CL 5783076 and apply correct fix.
The /doc hack is wrong. The code to handle this case was
already there and just needs a simple fix:
// We didn't find any directories containing Go files.
// If some directory returned successfully, use that.
- if len(all) == 0 && first != nil {
+ if !haveGo {
for _, d := range first {
haveName[d.Name()] = true
all = append(all, d)
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5783079
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/da8efae9fe6a3d5f5e6dffc2c70c835fa6724cbb
元コミット内容
cmd/godoc: fix directory read
Undo CL 5783076 and apply correct fix.
The /doc hack is wrong. The code to handle this case was
already there and just needs a simple fix:
// We didn't find any directories containing Go files.
// If some directory returned successfully, use that.
- if len(all) == 0 && first != nil {
+ if !haveGo {
for _, d := range first {
haveName[d.Name()] = true
all = append(all, d)
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5783079
変更の背景
このコミットは、Go言語のドキュメンテーションツールであるgodoc
のReadDir
関数における不具合を修正するために行われました。以前の変更(CL 5783076)が、特定の条件下でディレクトリの内容を正しく読み取れない問題を引き起こしていました。特に、Goファイルを含まないディレクトリの処理や、/doc
パスに対する不適切な特殊処理(コミットメッセージで「/doc hack」と表現されているもの)が問題の原因でした。
godoc
は、Goのソースコードからドキュメンテーションを生成し、Webブラウザで閲覧可能にするツールです。そのため、ファイルシステムを正確に走査し、ディレクトリ内のファイルを適切に識別する能力が不可欠です。CL 5783076は、おそらく特定のケースを解決しようとしたものの、より広範なディレクトリ読み込みロジックを壊してしまったか、あるいは不必要な複雑さを導入してしまったと考えられます。
このコミットの目的は、その誤った変更を元に戻し、godoc
のReadDir
関数が、Goファイルが存在しないディレクトリであっても、そのサブディレクトリや関連ファイルを適切にリストアップできるようにすることです。コミットメッセージが示唆するように、この問題に対処するための正しいロジックは既に存在しており、以前の変更がそれを妨げていたため、その変更を元に戻すことが「正しい修正」とされています。
前提知識の解説
godoc
godoc
は、Go言語の公式ドキュメンテーションツールです。Goのソースコード(.go
ファイル)からコメントや関数シグネチャなどを解析し、自動的にAPIドキュメンテーションを生成します。このドキュメンテーションは、コマンドラインから参照できるだけでなく、HTTPサーバーとして起動してWebブラウザ経由で閲覧することも可能です。godoc
は、Goの標準ライブラリのドキュメンテーションを提供するために広く利用されており、Go開発者にとって非常に重要なツールです。
os.FileInfo
Go言語のos
パッケージは、オペレーティングシステムとのインタラクションを提供します。os.FileInfo
インターフェースは、ファイルやディレクトリに関する情報(名前、サイズ、パーミッション、最終更新時刻など)を抽象的に表現するためのものです。os.ReadDir
やos.Stat
などの関数は、このos.FileInfo
インターフェースを実装した値を返します。godoc
のReadDir
関数も、内部でファイルシステムを読み取る際にos.FileInfo
のリストを扱います。
strings.HasSuffix
strings
パッケージは、文字列操作のためのユーティリティ関数を提供します。strings.HasSuffix(s, suffix string) bool
関数は、文字列s
が指定されたsuffix
で終わるかどうかを判定します。このコミットのコードでは、ファイル名が.go
で終わるかどうかをチェックするために使用されています。
append関数
Go言語の組み込み関数であるappend
は、スライスに要素を追加するために使用されます。append(slice []T, elems ...T) []T
のように使用し、新しい要素を追加したスライスを返します。元のスライスの容量が不足している場合は、より大きな容量を持つ新しい基底配列が割り当てられ、要素がコピーされます。
map (連想配列/ハッシュマップ)
Go言語のmap
は、キーと値のペアを格納するデータ構造で、他の言語における連想配列やハッシュマップに相当します。キーは一意であり、それに対応する値に高速にアクセスできます。このコミットのコードでは、haveName
というmap[string]bool
型の変数が使用されており、これは既に処理されたファイルやディレクトリの名前を追跡し、重複を防ぐために使われています。
GoにおけるCL (Change List)
Goプロジェクトの開発では、Gerritというコードレビューシステムが使われています。Gerritでは、一連の変更は「Change List (CL)」として管理されます。各CLは、一つ以上のコミットから構成されることがあり、レビューと承認を経てGoのリポジトリにマージされます。コミットメッセージに記載されているCL 5783076
やCL 5783079
は、Gerritにおける特定の変更セットを指します。
技術的詳細
このコミットの核心は、src/cmd/godoc/filesystem.go
内のnameSpace.ReadDir
メソッドのロジック修正にあります。このメソッドは、godoc
がファイルシステムを仮想的に読み取るためのインターフェースを提供します。
元のコード(CL 5783076によって導入されたと思われる部分)では、useFiles
というフラグが導入され、特に/doc
パスに対しては常にファイルを含めるという特殊なロジックが適用されていました。これはコミットメッセージで「/doc hack」と批判されている部分です。
修正前のコードの主要な問題点は以下の2点です。
-
/doc
パスの特殊処理 (useFiles
フラグ):useFiles := false // Always include all files under /doc. if path == "/doc" || strings.HasPrefix(path, "/doc/") { useFiles = true // always include docs }
このロジックは、
/doc
以下のパスでは無条件にすべてのファイルを含めるようにしていました。しかし、godoc
の本来の意図は、Goファイルが存在しないディレクトリではサブディレクトリのみをリストアップし、Goファイルが存在するディレクトリではそのディレクトリ内のGoファイルとサブディレクトリをリストアップすることです。この「hack」は、その本来のロジックを歪めていました。 -
Goファイルが見つからなかった場合のフォールバックロジックの誤り:
// We didn't find any directories containing Go files. // If some directory returned successfully, use that. if len(all) == 0 && first != nil { for _, d := range first { haveName[d.Name()] = true all = append(all, d) } }
この部分では、
all
スライス(最終的に返されるファイル情報のリスト)が空であり、かつfirst
(最初に成功したディレクトリ読み込みの結果)が存在する場合に、first
の内容をall
に追加していました。しかし、godoc
のロジックでは、haveGo
というフラグが、現在のディレクトリまたはそのサブディレクトリにGoファイルが見つかったかどうかを示します。Goファイルが見つからなかった場合でも、サブディレクトリは表示されるべきです。len(all) == 0
という条件は、all
がGoファイルを含むディレクトリからのエントリでまだ埋められていない場合にのみ適用されるべきであり、haveGo
の状態と連動させるべきでした。
このコミットは、これらの問題を以下のように修正しています。
-
/doc
パスの特殊処理の削除:useFiles
フラグの初期化と、/doc
パスに対する条件分岐が削除されました。これにより、godoc
はすべてのパスに対して一貫したディレクトリ読み込みロジックを適用するようになります。 -
Goファイルが見つからなかった場合のフォールバックロジックの修正:
if len(all) == 0 && first != nil
という条件が、より適切で意図を反映したif !haveGo
に変更されました。haveGo
は、nameSpace.ReadDir
が走査したディレクトリツリーのどこかでGoファイルが見つかった場合にtrue
になるフラグです。!haveGo
という条件は、「Goファイルが全く見つからなかった場合」を正確に表します。この場合、first
(最初に読み込みに成功したディレクトリの内容)をall
に追加することで、Goファイルがないディレクトリでもサブディレクトリが適切に表示されるようになります。- さらに、
haveName[d.Name()]
のチェックが追加され、first
からall
に要素を追加する際に、既にall
に含まれている(例えば、別のパスから追加された)エントリの重複を防ぐようになりました。これは、haveName
マップが既に処理された名前を追跡しているため、より堅牢な重複排除メカニズムを提供します。
これらの変更により、godoc
はファイルシステムをより正確に、かつ意図された通りに走査できるようになり、Goファイルが存在しないディレクトリでも適切な表示が行われるようになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/godoc/filesystem.go b/src/cmd/godoc/filesystem.go
index 869e23ca25..e7092ff287 100644
--- a/src/cmd/godoc/filesystem.go
+++ b/src/cmd/godoc/filesystem.go
@@ -420,17 +420,11 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
first = dir
}\n
- useFiles := false
-
- // Always include all files under /doc.
- if path == "/doc" || strings.HasPrefix(path, "/doc/") {
- useFiles = true // always include docs
- }
-
// If we don't yet have Go files in 'all' and this directory
// has some, add all the files from this directory.
// Otherwise, only add subdirectories.
- if !useFiles && !haveGo {
+ useFiles := false
+ if !haveGo {
for _, d := range dir {
if strings.HasSuffix(d.Name(), ".go") {
useFiles = true
@@ -451,10 +445,12 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
// We didn't find any directories containing Go files.
// If some directory returned successfully, use that.
- if len(all) == 0 && first != nil {
+ if !haveGo {
for _, d := range first {
-\t\t\thaveName[d.Name()] = true
-\t\t\tall = append(all, d)
+\t\t\tif !haveName[d.Name()] {\n+\t\t\t\thaveName[d.Name()] = true
+\t\t\t\tall = append(all, d)
+\t\t\t}\n }
}
コアとなるコードの解説
このコミットは、src/cmd/godoc/filesystem.go
ファイルのnameSpace.ReadDir
関数に対して行われた変更です。
-
/doc
パスの特殊処理の削除:- useFiles := false - - // Always include all files under /doc. - if path == "/doc" || strings.HasPrefix(path, "/doc/") { - useFiles = true // always include docs - }
この部分では、以前のコミットで導入された
useFiles
変数の初期化と、/doc
パスに対する特殊な条件分岐が削除されています。これにより、godoc
は/doc
パスに対しても他のパスと同様の一般的なファイル読み込みロジックを適用するようになります。コミットメッセージにある「/doc hack」がこの部分を指しており、これが不適切であると判断されたため削除されました。 -
useFiles
変数の再定義と条件の変更:- if !useFiles && !haveGo { + useFiles := false + if !haveGo {
useFiles
変数が削除されたため、その後のif !useFiles && !haveGo
という条件式は無効になります。この変更では、useFiles
が再度false
で初期化され、条件式から!useFiles
が削除されています。これにより、このブロックはhaveGo
(Goファイルが見つかったかどうか)の状態のみに基づいて実行されるようになります。つまり、「まだGoファイルが見つかっていない場合」に、現在のディレクトリ内のGoファイルをall
に追加するかどうかを判断するロジックが、よりシンプルかつ意図通りに動作するようになります。 -
Goファイルが見つからなかった場合のフォールバックロジックの修正:
- if len(all) == 0 && first != nil { + if !haveGo { for _, d := range first { -\t\t\thaveName[d.Name()] = true -\t\t\tall = append(all, d) +\t\t\tif !haveName[d.Name()] {\n+\t\t\t\thaveName[d.Name()] = true +\t\t\t\tall = append(all, d) +\t\t\t}\n } }
この変更は、
ReadDir
関数がGoファイルを含むディレクトリを全く見つけられなかった場合のフォールバックロジックを修正しています。- 条件式の変更:
if len(all) == 0 && first != nil
からif !haveGo
に変更されました。- 元の条件
len(all) == 0 && first != nil
は、「all
スライスが空(Goファイルがまだ追加されていない)であり、かつfirst
(最初に成功したディレクトリ読み込みの結果)が存在する場合」を意味していました。これは、Goファイルが見つからなかった場合にサブディレクトリを表示するという意図を完全にカバーしていませんでした。 - 新しい条件
!haveGo
は、「これまでにGoファイルが全く見つからなかった場合」を直接的に示します。この場合、first
に含まれるエントリ(通常はサブディレクトリ)をall
に追加することで、Goファイルがないディレクトリでもサブディレクトリが正しく表示されるようになります。
- 元の条件
- 重複排除の追加:
for _, d := range first
ループ内で、if !haveName[d.Name()]
という条件が追加されました。- これは、
first
からall
に要素を追加する際に、既にhaveName
マップに登録されている(つまり、既に処理済みまたは追加済みの)エントリをスキップするためのものです。これにより、同じファイルやディレクトリが複数回all
スライスに追加されるのを防ぎ、結果の正確性と効率性を向上させます。
- これは、
- 条件式の変更:
これらの変更により、godoc
のディレクトリ読み込みロジックはより堅牢になり、Goファイルが存在しないディレクトリの表示や、/doc
パスの処理が意図通りに行われるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/da8efae9fe6a3d5f5e6dffc2c70c835fa6724cbb
- Go CL 5783079: https://golang.org/cl/5783079
参考にした情報源リンク
- Go CL 5783076 (Reverted by this commit): https://golang.org/cl/5783076 (This link was found by searching for "golang CL 5783076" based on the commit message.)
- Go
os
package documentation: https://pkg.go.dev/os - Go
strings
package documentation: https://pkg.go.dev/strings - Go
append
function documentation: https://go.dev/ref/spec#Appending_and_copying_slices - Go
map
documentation: https://go.dev/ref/spec#Map_types - Gerrit Code Review: https://gerrit-review.googlesource.com/
godoc
command documentation: https://pkg.go.dev/cmd/godoc- Go
os.FileInfo
interface: https://pkg.go.dev/os#FileInfo - Go
os.ReadDir
function: https://pkg.go.dev/os#ReadDir - Go
strings.HasSuffix
function: https://pkg.go.dev/strings#HasSuffix - Go
append
function: https://pkg.go.dev/builtin#append - Go
map
type: https://pkg.go.dev/builtin#map - Go
godoc
tool: https://go.dev/blog/godoc - Go
cmd/godoc
source code: https://github.com/golang/go/tree/master/src/cmd/godoc - Go
src/cmd/godoc/filesystem.go
source code (at the time of commit): https://github.com/golang/go/blob/da8efae9fe6a3d5f5e6dffc2c70c835fa6724cbb/src/cmd/godoc/filesystem.go - Go
src/cmd/godoc/filesystem.go
source code (current master): https://github.com/golang/go/blob/master/src/cmd/godoc/filesystem.go