feat(issue): support threaded replies via --reply-to and +comment-replies
- issue +comment --reply-to <id>: reply to an existing comment; the server requires both parent_id and reply_id, so both are set - issue +comment-replies: list a comment's replies via the children_journals endpoint with --page/--limit Production-verified: parent -> reply -> list replies -> delete round-trip on a real issue. 3 new unit tests and bilingual docs. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
029622ff52
commit
b08d3d04a8
|
|
@ -356,6 +356,10 @@ gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 -
|
|||
# Add a comment
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
|
||||
|
||||
# Reply to a comment and list a comment's replies
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Agreed" --reply-to 456
|
||||
gitlink-cli issue +comment-replies --owner Gitlink --repo forgeplus -n 123 -c 456
|
||||
|
||||
# List, edit, and delete issue comments
|
||||
gitlink-cli issue +comments --owner Gitlink --repo forgeplus -n 123 --category comment
|
||||
gitlink-cli issue +comment-edit --owner Gitlink --repo forgeplus -n 123 -c 456 -b "Updated"
|
||||
|
|
|
|||
|
|
@ -367,6 +367,10 @@ gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 -
|
|||
# 添加评论
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "已修复"
|
||||
|
||||
# 回复评论并列出某条评论的回复
|
||||
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "同意" --reply-to 456
|
||||
gitlink-cli issue +comment-replies --owner Gitlink --repo forgeplus -n 123 -c 456
|
||||
|
||||
# 列出、编辑、删除议题评论
|
||||
gitlink-cli issue +comments --owner Gitlink --repo forgeplus -n 123 --category comment
|
||||
gitlink-cli issue +comment-edit --owner Gitlink --repo forgeplus -n 123 -c 456 -b "更新内容"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"cmd.issue.comment.short": "Add a comment to an issue",
|
||||
"cmd.issue.comment_delete.short": "Delete an issue comment",
|
||||
"cmd.issue.comment_edit.short": "Edit an issue comment",
|
||||
"cmd.issue.comment_replies.short": "List replies to an issue comment",
|
||||
"cmd.issue.comments.short": "List issue comments",
|
||||
"cmd.issue.create.short": "Create a new issue",
|
||||
"cmd.issue.list.short": "List issues",
|
||||
|
|
@ -162,6 +163,7 @@
|
|||
"flag.issue.batch_process.limit": "Maximum issues to process, capped at 100",
|
||||
"flag.issue.body": "Issue description",
|
||||
"flag.issue.comment_id": "Comment (journal) ID from issue +comments",
|
||||
"flag.issue.comment_reply_to": "Parent comment (journal) ID to reply to",
|
||||
"flag.issue.comments.category": "Filter: comment/operate (default: all)",
|
||||
"flag.issue.comments.keyword": "Search keyword in comment content",
|
||||
"flag.issue.label": "Label ID",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"cmd.issue.comment.short": "给议题添加评论",
|
||||
"cmd.issue.comment_delete.short": "删除议题评论",
|
||||
"cmd.issue.comment_edit.short": "编辑议题评论",
|
||||
"cmd.issue.comment_replies.short": "列出议题评论的回复",
|
||||
"cmd.issue.comments.short": "列出议题评论",
|
||||
"cmd.issue.create.short": "创建新议题",
|
||||
"cmd.issue.list.short": "列出议题",
|
||||
|
|
@ -162,6 +163,7 @@
|
|||
"flag.issue.batch_process.limit": "最多处理的议题数,上限 100",
|
||||
"flag.issue.body": "议题描述",
|
||||
"flag.issue.comment_id": "评论(journal)ID,可由 issue +comments 获取",
|
||||
"flag.issue.comment_reply_to": "要回复的父评论(journal)ID",
|
||||
"flag.issue.comments.category": "筛选:comment/operate(默认:全部)",
|
||||
"flag.issue.comments.keyword": "评论内容搜索关键词",
|
||||
"flag.issue.label": "标签 ID",
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
Description: tr.T("cmd.issue.comment.short"),
|
||||
Flags: appendIssueNumberFlags(
|
||||
common.Flag{Name: "body", Short: "b", Usage: tr.T("flag.comment.body"), Required: true},
|
||||
common.Flag{Name: "reply-to", Usage: tr.T("flag.issue.comment_reply_to")},
|
||||
),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -290,6 +291,14 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
payload := map[string]interface{}{
|
||||
"notes": body,
|
||||
}
|
||||
if replyTo := ctx.Arg("reply-to"); replyTo != "" {
|
||||
id, err := strconv.Atoi(replyTo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--reply-to must be an integer, got %q", replyTo)
|
||||
}
|
||||
payload["parent_id"] = id
|
||||
payload["reply_id"] = id
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/issues/%s/journals", v1RepoPath(ctx), number), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -330,6 +339,39 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "comment-replies",
|
||||
Description: tr.T("cmd.issue.comment_replies.short"),
|
||||
Flags: appendIssueNumberFlags(
|
||||
common.Flag{Name: "comment-id", Short: "c", Usage: tr.T("flag.issue.comment_id"), Required: true},
|
||||
common.Flag{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
common.Flag{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := issueNumberArg(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commentID, err := ctx.RequireArg("comment-id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := strconv.Atoi(commentID); err != nil {
|
||||
return fmt.Errorf("--comment-id must be an integer, got %q", commentID)
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("%s/issues/%s/journals/%s/children_journals", v1RepoPath(ctx), number, commentID), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "comment-edit",
|
||||
Description: tr.T("cmd.issue.comment_edit.short"),
|
||||
|
|
|
|||
|
|
@ -1210,3 +1210,56 @@ func TestIssueCommentDeleteRequiresCommentID(t *testing.T) {
|
|||
t.Fatal("expected error when --comment-id is missing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueCommentReplyToSetsParentID(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/issues/7/journals.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"id": float64(101)})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "comment", map[string]string{
|
||||
"number": "7", "body": "a reply", "reply-to": "99",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("comment failed: %v", err)
|
||||
}
|
||||
assertEqual(t, payload["notes"], "a reply")
|
||||
assertEqual(t, payload["parent_id"], float64(99))
|
||||
assertEqual(t, payload["reply_id"], float64(99))
|
||||
}
|
||||
|
||||
func TestIssueCommentRepliesUsesChildrenJournalsPath(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/issues/7/journals/99/children_journals.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": float64(0), "journals": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "comment-replies", map[string]string{
|
||||
"number": "7", "comment-id": "99",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("comment-replies failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueCommentRepliesRejectsNonIntegerCommentID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "comment-replies", map[string]string{
|
||||
"number": "7", "comment-id": "abc",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-integer --comment-id")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue