diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..007d410 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Binaries +gosetup +update +*.exe + +# Test binaries +*.test + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store diff --git a/cmd/update/main.go b/cmd/update/main.go index b4b629f..4af2b15 100644 --- a/cmd/update/main.go +++ b/cmd/update/main.go @@ -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: \t + 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 diff --git a/tools.json b/tools.json index 87dfc66..48c1dea 100644 --- a/tools.json +++ b/tools.json @@ -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" } ]