Add godoc, fix commit hash resolution for submodules

- Add godoc tool (now a separate submodule at cmd/godoc/v0.1.0-deprecated)
- Fix update tool to handle godoc's special tag format
- All tools now use commit hashes instead of version tags
This commit is contained in:
Jeffrey Paul 2025-12-18 02:05:19 -08:00
parent 33c099ae4f
commit a414a82306
3 changed files with 184 additions and 35 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Binaries
gosetup
update
*.exe
# Test binaries
*.test
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store

View File

@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
@ -26,6 +27,11 @@ type ToolsFile struct {
type ModuleInfo struct {
Version string `json:"Version"`
Time string `json:"Time"`
Origin struct {
VCS string `json:"VCS"`
URL string `json:"URL"`
Hash string `json:"Hash"`
} `json:"Origin"`
}
func main() {
@ -100,7 +106,7 @@ func main() {
output = append(output, '\n')
absPath, _ := filepath.Abs(*outputPath)
if err := os.WriteFile(*outputPath, output, 0644); err != nil {
if err := os.WriteFile(*outputPath, output, 0o644); err != nil {
fmt.Fprintf(os.Stderr, "error writing %s: %v\n", *outputPath, err)
os.Exit(1)
}
@ -114,26 +120,81 @@ func main() {
}
// getModulePath extracts the module path from a package path
// e.g., "golang.org/x/tools/cmd/goimports" -> "golang.org/x/tools"
func getModulePath(pkg string) string {
// Handle special cases where cmd is in a subpath
if strings.Contains(pkg, "/cmd/") {
parts := strings.Split(pkg, "/cmd/")
return parts[0]
}
// For packages like "github.com/cweill/gotests/gotests"
// we need the parent module
if strings.Contains(pkg, "/gojson") && strings.Contains(pkg, "ChimeraCoder") {
return "github.com/ChimeraCoder/gojson"
}
if strings.HasSuffix(pkg, "/gotests") {
return strings.TrimSuffix(pkg, "/gotests")
}
// gopls is a submodule of golang.org/x/tools
if strings.HasSuffix(pkg, "/gopls") {
return pkg
}
return pkg
}
// getRepoPath extracts the git repository path from a module path
func getRepoPath(modPath string) string {
// golang.org/x/tools/gopls -> golang.org/x/tools
if strings.HasPrefix(modPath, "golang.org/x/") {
parts := strings.Split(modPath, "/")
if len(parts) >= 3 {
return strings.Join(parts[:3], "/")
}
}
return modPath
}
// getGitURL returns the git URL for a module path
func getGitURL(modPath string) string {
// Get the repo path first
repoPath := getRepoPath(modPath)
// Handle vanity imports
switch {
case strings.HasPrefix(repoPath, "golang.org/x/"):
name := strings.TrimPrefix(repoPath, "golang.org/x/")
return "https://github.com/golang/" + name + ".git"
case strings.HasPrefix(repoPath, "honnef.co/go/tools"):
return "https://github.com/dominikh/go-tools.git"
case strings.HasPrefix(repoPath, "mvdan.cc/"):
name := strings.TrimPrefix(repoPath, "mvdan.cc/")
return "https://github.com/mvdan/" + name + ".git"
default:
// Assume github.com/user/repo format
parts := strings.Split(repoPath, "/")
if len(parts) >= 3 {
return "https://" + strings.Join(parts[:3], "/") + ".git"
}
return "https://" + repoPath + ".git"
}
}
// getTagPattern returns the git tag pattern for a module
func getTagPattern(pkg, version string) string {
// gopls has tags like "gopls/v0.21.0"
if strings.Contains(pkg, "/gopls") {
return "gopls/" + version
}
// godoc has tags like "cmd/godoc/v0.1.0-deprecated"
if strings.HasSuffix(pkg, "/cmd/godoc") {
return "cmd/godoc/" + version
}
return version
}
// pseudoVersionRe matches pseudo-versions like v0.0.0-20250907133731-34b10582faa4
var pseudoVersionRe = regexp.MustCompile(`^v\d+\.\d+\.\d+-\d{14}-([a-f0-9]{12})$`)
func fetchLatestVersion(tool Tool) (Tool, error) {
modPath := getModulePath(tool.Package)
// First get the latest version info
cmd := exec.Command("go", "list", "-m", "-json", modPath+"@latest")
output, err := cmd.Output()
if err != nil {
@ -153,14 +214,80 @@ func fetchLatestVersion(tool Tool) (Tool, error) {
}
}
// Check if it's already a pseudo-version with commit hash
if matches := pseudoVersionRe.FindStringSubmatch(info.Version); matches != nil {
// Extract the 12-char hash from pseudo-version, we need the full hash
shortHash := matches[1]
gitURL := getGitURL(modPath)
fullHash, err := resolveShortHash(gitURL, shortHash)
if err != nil {
return Tool{}, fmt.Errorf("resolve hash failed: %w", err)
}
return Tool{
Name: tool.Name,
Package: tool.Package,
Version: fullHash,
Date: date,
}, nil
}
// It's a tagged version, resolve to commit hash
gitURL := getGitURL(modPath)
tagPattern := getTagPattern(tool.Package, info.Version)
hash, err := resolveTagToHash(gitURL, tagPattern)
if err != nil {
return Tool{}, fmt.Errorf("resolve tag failed: %w", err)
}
return Tool{
Name: tool.Name,
Package: tool.Package,
Version: info.Version,
Version: hash,
Date: date,
}, nil
}
func resolveTagToHash(gitURL, tag string) (string, error) {
cmd := exec.Command("git", "ls-remote", "--tags", gitURL, tag)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("git ls-remote failed: %w", err)
}
lines := strings.TrimSpace(string(output))
if lines == "" {
return "", fmt.Errorf("tag %s not found", tag)
}
// Format: <hash>\t<ref>
parts := strings.Fields(lines)
if len(parts) < 1 {
return "", fmt.Errorf("unexpected git ls-remote output")
}
return parts[0], nil
}
func resolveShortHash(gitURL, shortHash string) (string, error) {
// Use git ls-remote with the short hash to find full hash
cmd := exec.Command("git", "ls-remote", gitURL)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("git ls-remote failed: %w", err)
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(parts) >= 1 && strings.HasPrefix(parts[0], shortHash) {
return parts[0], nil
}
}
return "", fmt.Errorf("hash %s not found", shortHash)
}
type result struct {
idx int
tool Tool

View File

@ -3,175 +3,181 @@
{
"name": "gopls",
"package": "golang.org/x/tools/gopls",
"version": "v0.21.0",
"version": "ecc727ef4e92b7170abe1881910c4c8773800196",
"date": "2025-12-05"
},
{
"name": "gofumpt",
"package": "mvdan.cc/gofumpt",
"version": "v0.9.2",
"version": "5504461061294d4d80f5a7d2fdfa98d9a75ae69b",
"date": "2025-10-21"
},
{
"name": "goimports",
"package": "golang.org/x/tools/cmd/goimports",
"version": "v0.40.0",
"version": "00b22d96a3616723b0ee0341fb34c40b73e19c96",
"date": "2025-12-08"
},
{
"name": "godoc",
"package": "golang.org/x/tools/cmd/godoc",
"version": "ccfe399eae9bf60bad2b93ac1f9a37d08d3c46df",
"date": "2025-09-04"
},
{
"name": "golangci-lint",
"package": "github.com/golangci/golangci-lint/cmd/golangci-lint",
"version": "v1.64.8",
"version": "8b37f14162043f908949f1b363d061dc9ba713c0",
"date": "2025-03-17"
},
{
"name": "staticcheck",
"package": "honnef.co/go/tools/cmd/staticcheck",
"version": "v0.6.1",
"version": "b7ae2809b76e7d4ab5a4cc907919fc80b7d84c68",
"date": "2025-03-05"
},
{
"name": "errcheck",
"package": "github.com/kisielk/errcheck",
"version": "v1.9.0",
"version": "11c27a7ce69d583465d80d808817d22d6653ee34",
"date": "2025-02-19"
},
{
"name": "revive",
"package": "github.com/mgechev/revive",
"version": "v1.13.0",
"version": "ac5f398440705ae79abf836674f46c24a2494949",
"date": "2025-11-13"
},
{
"name": "godef",
"package": "github.com/rogpeppe/godef",
"version": "v1.1.2",
"version": "8318a6814d22e99151760ac2fc890f55088a3192",
"date": "2020-03-03"
},
{
"name": "gotests",
"package": "github.com/cweill/gotests/gotests",
"version": "v1.9.0",
"version": "f6659a2f552227d25e79651377cddc1ffbd49006",
"date": "2025-10-23"
},
{
"name": "gomodifytags",
"package": "github.com/fatih/gomodifytags",
"version": "v1.17.0",
"version": "0af24e19f5a325b1e6ef83692b8946b546491586",
"date": "2024-07-15"
},
{
"name": "impl",
"package": "github.com/josharian/impl",
"version": "v1.5.0",
"version": "923c93ed5ade114ce3bb9e77c71e52e8b6724cf7",
"date": "2025-12-09"
},
{
"name": "fillstruct",
"package": "github.com/davidrjenni/reftools/cmd/fillstruct",
"version": "v0.0.0-20250907133731-34b10582faa4",
"version": "34b10582faa4220d684a595b3e1237f244707e23",
"date": "2025-09-07"
},
{
"name": "fillswitch",
"package": "github.com/davidrjenni/reftools/cmd/fillswitch",
"version": "v0.0.0-20250907133731-34b10582faa4",
"version": "34b10582faa4220d684a595b3e1237f244707e23",
"date": "2025-09-07"
},
{
"name": "fixplurals",
"package": "github.com/davidrjenni/reftools/cmd/fixplurals",
"version": "v0.0.0-20250907133731-34b10582faa4",
"version": "34b10582faa4220d684a595b3e1237f244707e23",
"date": "2025-09-07"
},
{
"name": "dlv",
"package": "github.com/go-delve/delve/cmd/dlv",
"version": "v1.25.2",
"version": "498ee9c27223fed032af8856f7a62590a63b9439",
"date": "2025-08-27"
},
{
"name": "gotags",
"package": "github.com/jstemmer/gotags",
"version": "v1.4.1",
"version": "4cd81528d803e5044b3732a5e7a1dfab5ddc1514",
"date": "2017-04-03"
},
{
"name": "gogetdoc",
"package": "github.com/zmb3/gogetdoc",
"version": "v0.0.0-20190228002656-b37376c5da6a",
"version": "b37376c5da6aeb900611837098f40f81972e63e4",
"date": "2019-02-28"
},
{
"name": "iferr",
"package": "github.com/koron/iferr",
"version": "v0.0.0-20240122035601-9c3e2fbe4bd1",
"version": "9c3e2fbe4bd19a7f0338e42bb483562ed4cf4d50",
"date": "2024-01-22"
},
{
"name": "asmfmt",
"package": "github.com/klauspost/asmfmt/cmd/asmfmt",
"version": "v1.3.2",
"version": "ef134b9cec704e2b7b336fb02153b7d1a58247da",
"date": "2022-03-30"
},
{
"name": "motion",
"package": "github.com/fatih/motion",
"version": "v1.2.0",
"version": "3360433d74666f4e1250c857656bc7178d0f3933",
"date": "2023-02-16"
},
{
"name": "gojson",
"package": "github.com/ChimeraCoder/gojson/gojson",
"version": "v1.1.0",
"version": "fa01aa3a208834638989d1255282988a4865629d",
"date": "2018-08-18"
},
{
"name": "golines",
"package": "github.com/segmentio/golines",
"version": "v0.13.0",
"version": "8f32f0f7e89c30f572c7f2cd3b2a48016b9d8bbf",
"date": "2025-08-21"
},
{
"name": "gocyclo",
"package": "github.com/fzipp/gocyclo/cmd/gocyclo",
"version": "v0.6.0",
"version": "62aa1f84d9ea7d68ecc499a74e98f188f63b650e",
"date": "2022-06-15"
},
{
"name": "ineffassign",
"package": "github.com/gordonklaus/ineffassign",
"version": "v0.2.0",
"version": "cc8665bbd67af3cf6a54bc55bef821949f7ce0e9",
"date": "2025-08-24"
},
{
"name": "misspell",
"package": "github.com/client9/misspell/cmd/misspell",
"version": "v0.3.4",
"version": "7888c6b6ce89353cd98e196bce3c3f9e4cdf31f6",
"date": "2018-03-09"
},
{
"name": "unconvert",
"package": "github.com/mdempsky/unconvert",
"version": "v0.0.0-20250216222326-4a038b3d31f5",
"version": "4a038b3d31f56ff5ba511953b745c80a2317e4ae",
"date": "2025-02-16"
},
{
"name": "gopkgs",
"package": "github.com/uudashr/gopkgs/v2/cmd/gopkgs",
"version": "v2.1.2",
"version": "4d0f738b8305f9b3a4b8d6471aaa60e9211636a4",
"date": "2020-02-14"
},
{
"name": "go-outline",
"package": "github.com/ramya-rao-a/go-outline",
"version": "v0.0.0-20210608161538-9736a4bde949",
"version": "9736a4bde949f321d201e5eaa5ae2bcde011bf00",
"date": "2021-06-08"
},
{
"name": "go-symbols",
"package": "github.com/acroca/go-symbols",
"version": "v0.1.1",
"version": "b3af8b100b8a7d0136e0c17820a066d8365f1316",
"date": "2019-01-14"
}
]