diff --git a/doc/changes/repo-tree-default-branch.md b/doc/changes/repo-tree-default-branch.md index 0bd9e05..29c8a64 100644 --- a/doc/changes/repo-tree-default-branch.md +++ b/doc/changes/repo-tree-default-branch.md @@ -16,3 +16,10 @@ - `go test ./shortcuts/repo/`(默认 ref 断言改为「不携带 ref 参数」) - 生产 gitlink.org.cn 实测:默认分支为 `main` 与 `master` 的仓库均正常列出根目录。 + +## 追加:branch/pr/release 同类问题一并修复 + +- 新增 `RuntimeContext.DefaultBranch()`:读取仓库详情的 `default_branch`(缺失时回退 master) +- `branch +create --from` / `pr +create --base` 未指定时回退到仓库默认分支(不再硬编码 master) +- `release +create --target` 未指定时省略 `target_commitish`(平台自动落默认分支) +- 生产实测(默认分支为 main 的仓库):`repo +tree` / `branch +create` / `release +create` 均成功 diff --git a/shortcuts/branch/branch.go b/shortcuts/branch/branch.go index 0393ad5..e96cf26 100644 --- a/shortcuts/branch/branch.go +++ b/shortcuts/branch/branch.go @@ -37,7 +37,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { Description: tr.T("cmd.branch.create.short"), Flags: []common.Flag{ {Name: "name", Short: "n", Usage: tr.T("flag.branch.name"), Required: true}, - {Name: "from", Short: "f", Usage: tr.T("flag.branch.from"), Default: "master"}, + {Name: "from", Short: "f", Usage: tr.T("flag.branch.from")}, }, Run: func(ctx *common.RuntimeContext) error { if err := ctx.ResolveOwnerRepo(); err != nil { @@ -46,7 +46,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { name, _ := ctx.RequireArg("name") from := ctx.Arg("from") if from == "" { - from = "master" + var err error + if from, err = ctx.DefaultBranch(); err != nil { + return err + } } payload := map[string]interface{}{ "new_branch_name": name, diff --git a/shortcuts/branch/branch_test.go b/shortcuts/branch/branch_test.go index 1f1908f..b99d68b 100644 --- a/shortcuts/branch/branch_test.go +++ b/shortcuts/branch/branch_test.go @@ -81,12 +81,23 @@ func TestBranchCreate(t *testing.T) { } func TestBranchCreateDefaultFrom(t *testing.T) { - // When 'from' is not set, it defaults to "master" + // When 'from' is not set, it falls back to the repository default branch. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/v1/owner/repo/branches.json" { + switch r.URL.Path { + case "/owner/repo.json": + writeJSON(w, map[string]interface{}{"default_branch": "main"}) + case "/v1/owner/repo/branches.json": + var payload map[string]interface{} + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Fatalf("decode payload: %v", err) + } + if payload["old_branch_name"] != "main" { + t.Fatalf("expected old_branch_name to be default branch main, got %v", payload["old_branch_name"]) + } + writeJSON(w, map[string]interface{}{"name": "feature-y"}) + default: t.Fatalf("unexpected path: %s", r.URL.Path) } - writeJSON(w, map[string]interface{}{"name": "feature-y"}) })) defer server.Close() diff --git a/shortcuts/common/types.go b/shortcuts/common/types.go index 87a052a..ffd4338 100644 --- a/shortcuts/common/types.go +++ b/shortcuts/common/types.go @@ -105,6 +105,20 @@ func (ctx *RuntimeContext) OutputData(data interface{}) error { return output.Print(output.SuccessEnvelope(data, nil), ctx.Format) } +// DefaultBranch fetches the repository's default branch, falling back to "master". +func (ctx *RuntimeContext) DefaultBranch() (string, error) { + env, err := ctx.CallAPI("GET", ctx.RepoPath(), nil) + if err != nil { + return "", err + } + if data, ok := env.Data.(map[string]interface{}); ok { + if branch, ok := data["default_branch"].(string); ok && branch != "" { + return branch, nil + } + } + return "master", nil +} + // RepoPath returns the API path prefix for the current owner/repo. func (ctx *RuntimeContext) RepoPath() string { return fmt.Sprintf("/%s/%s", ctx.Owner, ctx.Repo) diff --git a/shortcuts/pr/pr.go b/shortcuts/pr/pr.go index 03f537f..d9cba36 100644 --- a/shortcuts/pr/pr.go +++ b/shortcuts/pr/pr.go @@ -96,7 +96,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "title", Short: "t", Usage: tr.T("flag.pr.title"), Required: true}, {Name: "body", Short: "b", Usage: tr.T("flag.pr.body")}, {Name: "head", Usage: tr.T("flag.pr.head"), Required: true}, - {Name: "base", Usage: tr.T("flag.pr.base"), Default: "master"}, + {Name: "base", Usage: tr.T("flag.pr.base")}, }, Run: func(ctx *common.RuntimeContext) error { if err := ctx.ResolveOwnerRepo(); err != nil { @@ -106,7 +106,10 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { head, _ := ctx.RequireArg("head") base := ctx.Arg("base") if base == "" { - base = "master" + var err error + if base, err = ctx.DefaultBranch(); err != nil { + return err + } } payload := map[string]interface{}{ "title": title, diff --git a/shortcuts/pr/pr_test.go b/shortcuts/pr/pr_test.go index eece6d9..7b28251 100644 --- a/shortcuts/pr/pr_test.go +++ b/shortcuts/pr/pr_test.go @@ -209,6 +209,10 @@ func TestPRCreate(t *testing.T) { func TestPRCreateNoBody(t *testing.T) { var payload map[string]interface{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/owner/repo.json" { + writeJSON(t, w, map[string]interface{}{"default_branch": "main"}) + return + } payload = decodeJSON(t, r) writeJSON(t, w, map[string]interface{}{"id": float64(43), "title": "feat: nob"}) })) @@ -224,6 +228,9 @@ func TestPRCreateNoBody(t *testing.T) { if _, ok := payload["body"]; ok { t.Fatal("body should not be in payload when not provided") } + if payload["base"] != "main" { + t.Fatalf("expected base to fall back to default branch main, got %v", payload["base"]) + } } // --- view --- diff --git a/shortcuts/release/release.go b/shortcuts/release/release.go index 21d7ec4..301abb0 100644 --- a/shortcuts/release/release.go +++ b/shortcuts/release/release.go @@ -42,7 +42,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "tag", Short: "t", Usage: tr.T("flag.release.tag"), Required: true}, {Name: "name", Short: "n", Usage: tr.T("flag.release.name"), Required: true}, {Name: "body", Short: "b", Usage: tr.T("flag.release.body")}, - {Name: "target", Usage: tr.T("flag.release.target"), Default: "master"}, + {Name: "target", Usage: tr.T("flag.release.target")}, {Name: "prerelease", Usage: tr.T("flag.release.prerelease"), Default: "false"}, {Name: "draft", Usage: "Mark as draft (true/false)", Default: "false"}, {Name: "attachment-ids", Usage: "Comma-separated attachment IDs"},