diff --git a/cmd/api/api.go b/cmd/api/api.go index ec3d076..02dc164 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -65,6 +65,28 @@ func validateAPIArgs(c *cobra.Command, args []string) error { // path conversion, e.g. "C:/Program Files/Git/v1/owner/repo" for input "/v1/owner/repo". var msysPathRe = regexp.MustCompile(`^[A-Za-z]:/`) +// restoreAPIPath restores an API path polluted by MSYS2/Git Bash path +// conversion on Windows, e.g. "C:/Program Files/Git/v1/owner/repo" -> "/v1/owner/repo". +// If the path does not start with a drive letter, or no known API prefix is +// found, the original path is returned unchanged. +func restoreAPIPath(path string) string { + if !msysPathRe.MatchString(path) { + return path + } + // Pick the EARLIEST occurrence among known API prefixes, so a path like + // ".../api/v1/users" restores to "/api/v1/users" rather than "/v1/users". + bestIdx := -1 + for _, prefix := range []string{"/v1/", "/v2/", "/api/", "/users/", "/projects/"} { + if idx := strings.Index(path, prefix); idx >= 0 && (bestIdx == -1 || idx < bestIdx) { + bestIdx = idx + } + } + if bestIdx >= 0 { + return path[bestIdx:] + } + return path +} + func runAPI(c *cobra.Command, args []string) error { batchFile, _ := c.Flags().GetString("batch-file") if batchFile != "" { @@ -76,15 +98,7 @@ func runAPI(c *cobra.Command, args []string) error { // Fix MSYS2/Git Bash path auto-conversion on Windows: // "/v1/owner/repo" is rewritten to "C:/Program Files/Git/v1/owner/repo". - // Detect the drive-letter prefix and restore the original API path. - if msysPathRe.MatchString(path) { - for _, prefix := range []string{"/v1/", "/v2/", "/api/", "/users/", "/projects/"} { - if idx := strings.Index(path, prefix); idx >= 0 { - path = path[idx:] - break - } - } - } + path = restoreAPIPath(path) if !strings.HasPrefix(path, "/") { path = "/" + path diff --git a/cmd/api/api_test.go b/cmd/api/api_test.go index dad41a8..c0e35bc 100644 --- a/cmd/api/api_test.go +++ b/cmd/api/api_test.go @@ -402,3 +402,25 @@ func writeBatchPlan(t *testing.T, payload interface{}) string { } return path } + +func TestRestoreAPIPath(t *testing.T) { + tests := []struct { + name string + path string + want string + }{ + {"normal v1 path unchanged", "/v1/owner/repo", "/v1/owner/repo"}, + {"msys2 polluted v1", "C:/Program Files/Git/v1/owner/repo", "/v1/owner/repo"}, + {"msys2 polluted v2", "D:/Git/v2/x/y", "/v2/x/y"}, + {"msys2 polluted api prefix", "C:/Program Files/Git/api/v1/users", "/api/v1/users"}, + {"drive letter but no known prefix", "C:/something/else", "C:/something/else"}, + {"empty path", "", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := restoreAPIPath(tt.path); got != tt.want { + t.Fatalf("restoreAPIPath(%q) = %q, want %q", tt.path, got, tt.want) + } + }) + } +} diff --git a/doc/changes/api-msys2-path.md b/doc/changes/api-msys2-path.md new file mode 100644 index 0000000..52c20b1 --- /dev/null +++ b/doc/changes/api-msys2-path.md @@ -0,0 +1,35 @@ +# Fix: Windows Git Bash 下 `gitlink-cli api` 路径被 MSYS2 污染导致 404 + +## 问题 + +在 Windows Git Bash(MSYS2)环境下执行: + +```bash +gitlink-cli api GET /v1/owner/repo +``` + +路径参数 `/v1/owner/repo` 会被 MSYS2 自动改写为类似 `C:/Program Files/Git/v1/owner/repo` 的 Windows 路径——MSYS2 把以 `/` 开头的命令行参数当成 Unix 路径,转换为 Git 安装目录。结果 API 请求路径错误,返回 404。影响所有 Windows Git Bash 用户。 + +## 根因 + +MSYS2 的 POSIX→Windows 路径转换会对命令行参数中以 `/` 开头的字符串生效,且无法通过 shell 转义稳定规避(`MSYS_NO_PATHCONV` 等环境变量依赖用户配置,不可靠)。 + +## 修复 + +在 `cmd/api` 的 `runAPI` 中,对取到的 `path` 调用 `restoreAPIPath` 还原: + +- 检测首部是否为盘符(正则 `^[A-Za-z]:/`); +- 若是,按常见 API 前缀(`/v1/` `/v2/` `/api/` `/users/` `/projects/`)在污染后的路径里定位原始起点并截取; +- 无盘符或无匹配前缀时原样返回,不影响其他平台与正常路径。 + +`restoreAPIPath` 为纯函数,便于单元测试。 + +## 影响 + +仅 Windows 受益,其他平台行为不变。改动集中在 `cmd/api/api.go`(约 +25 行,含函数与注释)。 + +## Tests + +```bash +go test ./cmd/api/... -run TestRestoreAPIPath -v +```