diff --git a/doc/changes/pr-list-login-filter.md b/doc/changes/pr-list-login-filter.md new file mode 100644 index 0000000..48fbb83 --- /dev/null +++ b/doc/changes/pr-list-login-filter.md @@ -0,0 +1,33 @@ +# PR 列表按作者过滤 + +## Summary + +`pr +list` 新增 `--login` 参数,可按 Issue 作者(`issue.author.login`)过滤返回的 Pull Request 列表。 + +## 新增 Flag + +| Flag | 说明 | +|------|------| +| `--login` | 按 Issue 作者的 login 过滤 PR(不区分大小写) | + +## 实现细节 + +GitLink PR 列表 API 不支持服务端按作者过滤,因此本功能在客户端对返回结果进行过滤。过滤逻辑: + +- 匹配 `issue.author.login` 字段 +- 不区分大小写 +- 过滤后自动更新 `total_count` 和 `meta.total_count` 以反映实际数量 + +## 示例 + +```bash +# 列出 alice 提交的所有 PR +gitlink-cli pr +list --owner Gitlink --repo gitlink-cli --login alice + +# 结合其他筛选条件 +gitlink-cli pr +list --owner Gitlink --repo gitlink-cli --login alice --state open + +# 列出已合并的 PR +gitlink-cli pr +list --login bob --state merged +``` + diff --git a/shortcuts/pr/pr.go b/shortcuts/pr/pr.go index d28e192..38fb258 100644 --- a/shortcuts/pr/pr.go +++ b/shortcuts/pr/pr.go @@ -47,6 +47,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { {Name: "assignee-id", Usage: tr.T("flag.pr.assignee_id")}, {Name: "sort-by", Usage: tr.T("flag.sort_by")}, {Name: "sort-direction", Usage: tr.T("flag.sort_direction")}, + {Name: "login", Usage: "Filter pull requests by issue author login"}, {Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"}, {Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"}, }, @@ -88,6 +89,9 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { if err != nil { return err } + if login := ctx.Arg("login"); login != "" { + filterPullsByLogin(env, login) + } return ctx.Output(env) }, }, @@ -1037,3 +1041,49 @@ func numberField(m map[string]interface{}, key string) (float64, bool) { return 0, false } } + +// filterPullsByLogin filters the pulls list in-place, keeping only items +// whose issue.author.login matches the given login (case-insensitive). +func filterPullsByLogin(env *output.Envelope, login string) { + if env == nil || env.Data == nil { + return + } + data, ok := env.Data.(map[string]interface{}) + if !ok { + return + } + rawPulls, ok := data["pulls"] + if !ok { + return + } + pulls, ok := rawPulls.([]interface{}) + if !ok { + return + } + loginLower := strings.ToLower(login) + filtered := make([]interface{}, 0, len(pulls)) + for _, item := range pulls { + pull, ok := item.(map[string]interface{}) + if !ok { + continue + } + issue, ok := pull["issue"].(map[string]interface{}) + if !ok { + continue + } + author, ok := issue["author"].(map[string]interface{}) + if !ok { + continue + } + authorLogin, _ := author["login"].(string) + if strings.ToLower(authorLogin) == loginLower { + filtered = append(filtered, item) + } + } + data["pulls"] = filtered + // Update total_count in data to reflect filtered count + data["total_count"] = float64(len(filtered)) + if env.Meta != nil { + env.Meta.TotalCount = len(filtered) + } +} diff --git a/shortcuts/pr/pr_test.go b/shortcuts/pr/pr_test.go index 4bb37a2..6d14925 100644 --- a/shortcuts/pr/pr_test.go +++ b/shortcuts/pr/pr_test.go @@ -422,6 +422,51 @@ func TestPRListWithFilters(t *testing.T) { } } +func TestPRListWithLoginFilter(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/v1/owner/repo/pulls.json" { + t.Fatalf("unexpected path: %s", r.URL.Path) + } + writeJSON(t, w, map[string]interface{}{ + "pulls": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "title": "PR by alice", + "issue": map[string]interface{}{ + "author": map[string]interface{}{"login": "alice"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "title": "PR by bob", + "issue": map[string]interface{}{ + "author": map[string]interface{}{"login": "bob"}, + }, + }, + map[string]interface{}{ + "id": float64(3), + "title": "PR by Alice (uppercase)", + "issue": map[string]interface{}{ + "author": map[string]interface{}{"login": "Alice"}, + }, + }, + }, + "total_count": float64(3), + }) + })) + defer server.Close() + + err := runPRShortcut(t, server, "list", map[string]string{ + "state": "open", + "login": "alice", + "page": "1", + "limit": "20", + }) + if err != nil { + t.Fatalf("list with login filter failed: %v", err) + } +} + func TestPRListStateAllOmitsStatus(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/owner/repo/pulls.json" {