mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
fix: seed issue create from --template, edit and delete inline review comments, explain workflow run refs (follow gh)
Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
9 files changed
+122
-21
No files matched your search
@@ -425,7 +425,8 @@ archive, or run `make docs`.
|
|||||||
(unless it approves), team creation needs a permission, migrations from
|
(unless it approves), team creation needs a permission, migrations from
|
||||||
GitHub, Gitea, GitLab and Gogs need an access token. `workflow run` uses the
|
GitHub, Gitea, GitLab and Gogs need an access token. `workflow run` uses the
|
||||||
checked-out branch (which must be on the server) or the default branch when
|
checked-out branch (which must be on the server) or the default branch when
|
||||||
there is no `--ref`, and refuses unknown inputs, missing required inputs and
|
there is no `--ref`; the workflow must exist on the default branch even with
|
||||||
|
`--ref`, as in `gh`. It also refuses unknown inputs, missing required inputs and
|
||||||
values that a choice, boolean or number input does not accept.
|
values that a choice, boolean or number input does not accept.
|
||||||
- `pr merge` merges the head it read and refuses when the branch moved in the
|
- `pr merge` merges the head it read and refuses when the branch moved in the
|
||||||
meantime; run it again to merge the new head.
|
meantime; run it again to merge the new head.
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ Inputs are checked against the inputs that the workflow declares: unknown inputs
|
|||||||
required inputs and values that a choice or boolean input does not accept are refused. In a
|
required inputs and values that a choice or boolean input does not accept are refused. In a
|
||||||
terminal, tea asks for missing required inputs, or for every input when none is given.
|
terminal, tea asks for missing required inputs, or for every input when none is given.
|
||||||
|
|
||||||
|
The workflow is looked up among those the server lists from the default branch, so it must exist
|
||||||
|
there; --ref only selects the branch or tag whose version of it runs, as in gh. List them with
|
||||||
|
tea workflow list.
|
||||||
|
|
||||||
Without --ref, the workflow runs on the checked-out branch, which must be on the server, or on
|
Without --ref, the workflow runs on the checked-out branch, which must be on the server, or on
|
||||||
the default branch when the repository does not come from the working directory.`,
|
the default branch when the repository does not come from the working directory.`,
|
||||||
Example: `# run a workflow on the default or current branch
|
Example: `# run a workflow on the default or current branch
|
||||||
@@ -196,6 +200,10 @@ $ tea workflow run nightly --follow`,
|
|||||||
}
|
}
|
||||||
service := services.Actions{Client: t.client, Repository: t.Repo}
|
service := services.Actions{Client: t.client, Repository: t.Repo}
|
||||||
workflow, err := service.FindWorkflow(cmd.Context(), args[0])
|
workflow, err := service.FindWorkflow(cmd.Context(), args[0])
|
||||||
|
if notFound, ok := errors.AsType[*services.NotFoundError](err); ok && notFound.Kind == "workflow" {
|
||||||
|
// Like gh, the workflow comes from the server's list, so --ref cannot add one.
|
||||||
|
return fmt.Errorf("%w; workflows are listed from the default branch and must exist there, even with --ref; see tea workflow list%s", err, a.suggestedRepo(t))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,14 @@ func TestWorkflowRunChecksInputsBeforeDispatching(t *testing.T) {
|
|||||||
"workflow nightly.yaml is disabled; enable it with: tea workflow enable nightly.yaml -R alice/project")
|
"workflow nightly.yaml is disabled; enable it with: tea workflow enable nightly.yaml -R alice/project")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWorkflowRunExplainsWorkflowsComeFromTheDefaultBranch(t *testing.T) {
|
||||||
|
server, requests := actionsAPI(t, map[string]string{"GET actions/workflows": workflowsFixture})
|
||||||
|
deps, _, _ := actionsDeps(t, server.URL, false)
|
||||||
|
err := Execute(context.Background(), deps, []string{"workflow", "run", "branch-only", "-R", "alice/project", "--ref", "feature"})
|
||||||
|
require.EqualError(t, err, `workflow "branch-only" not found in alice/project; workflows are listed from the default branch and must exist there, even with --ref; see tea workflow list -R alice/project`)
|
||||||
|
require.Equal(t, []string{"GET actions/workflows"}, *requests)
|
||||||
|
}
|
||||||
|
|
||||||
// dispatchingPrompter answers workflow inputs: selections with their first choice, confirmations
|
// dispatchingPrompter answers workflow inputs: selections with their first choice, confirmations
|
||||||
// with yes, and text by title.
|
// with yes, and text by title.
|
||||||
type dispatchingPrompter struct {
|
type dispatchingPrompter struct {
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ func issueCommandPath(cmd *cobra.Command) string {
|
|||||||
|
|
||||||
// commentName names a comment in messages: "comment 81 on alice/x#1".
|
// commentName names a comment in messages: "comment 81 on alice/x#1".
|
||||||
func commentName(t target, comment services.Comment) string {
|
func commentName(t target, comment services.Comment) string {
|
||||||
|
if comment.IssueNumber == 0 {
|
||||||
|
// Inline review comments cannot be fetched, so their discussion is unknown.
|
||||||
|
return fmt.Sprintf("comment %d on %s", comment.ID, t.Repo)
|
||||||
|
}
|
||||||
return fmt.Sprintf("comment %d on %s#%d", comment.ID, t.Repo, comment.IssueNumber)
|
return fmt.Sprintf("comment %d on %s#%d", comment.ID, t.Repo, comment.IssueNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +163,10 @@ func lastComment(cmd *cobra.Command, t target, n int64) (services.Comment, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// comment returns the comment that the ID argument names, which a command of kind may change: pr
|
// comment returns the comment that the ID argument names, which a command of kind may change: pr
|
||||||
// commands refuse comments on issues.
|
// commands refuse comments on issues. With changing set, as for edit and delete, a comment that
|
||||||
func (a *application) comment(cmd *cobra.Command, arg string, kind refKind) (target, services.Comment, error) {
|
// cannot be fetched, such as an inline review comment, is returned by its ID alone: the edit and
|
||||||
|
// delete endpoints accept it.
|
||||||
|
func (a *application) comment(cmd *cobra.Command, arg string, kind refKind, changing bool) (target, services.Comment, error) {
|
||||||
id, err := number(arg)
|
id, err := number(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target{}, services.Comment{}, err
|
return target{}, services.Comment{}, err
|
||||||
@@ -170,6 +176,9 @@ func (a *application) comment(cmd *cobra.Command, arg string, kind refKind) (tar
|
|||||||
return t, services.Comment{}, err
|
return t, services.Comment{}, err
|
||||||
}
|
}
|
||||||
current, err := (services.Comments{Client: t.client, Repository: t.Repo}).View(cmd.Context(), id)
|
current, err := (services.Comments{Client: t.client, Repository: t.Repo}).View(cmd.Context(), id)
|
||||||
|
if notPlain := (services.NotPlainCommentError{}); changing && errors.As(err, ¬Plain) {
|
||||||
|
return t, services.Comment{ID: id}, nil
|
||||||
|
}
|
||||||
if err == nil && kind == pullRef && !current.Pull {
|
if err == nil && kind == pullRef && !current.Pull {
|
||||||
err = fmt.Errorf("comment %d in %s is on an issue, not a pull request; use %s %d", id, t.Repo, issueCommandPath(cmd), id)
|
err = fmt.Errorf("comment %d in %s is on an issue, not a pull request; use %s %d", id, t.Repo, issueCommandPath(cmd), id)
|
||||||
}
|
}
|
||||||
@@ -338,7 +347,7 @@ $ tea %[1]s comment list %[2]s --json id,author,body`, words.group, words.exampl
|
|||||||
list.ValidArgsFunction = a.completeDiscussions(kind)
|
list.ValidArgsFunction = a.completeDiscussions(kind)
|
||||||
|
|
||||||
view := &cobra.Command{Use: "view <comment-id>", Short: "View a comment", Example: fmt.Sprintf("$ tea %s comment view 81", words.group), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
view := &cobra.Command{Use: "view <comment-id>", Short: "View a comment", Example: fmt.Sprintf("$ tea %s comment view 81", words.group), Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
t, comment, err := a.comment(cmd, args[0], kind)
|
t, comment, err := a.comment(cmd, args[0], kind, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -366,7 +375,7 @@ $ tea %[1]s comment edit 81`, words.group), Args: argsExact(1), RunE: func(cmd *
|
|||||||
if err := editFlags.validate(cmd); err != nil {
|
if err := editFlags.validate(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t, comment, err := a.comment(cmd, args[0], kind)
|
t, comment, err := a.comment(cmd, args[0], kind, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -375,7 +384,7 @@ $ tea %[1]s comment edit 81`, words.group), Args: argsExact(1), RunE: func(cmd *
|
|||||||
editFlags.register(edit)
|
editFlags.register(edit)
|
||||||
|
|
||||||
remove := &cobra.Command{Use: "delete <comment-id>", Short: "Delete a comment", Example: fmt.Sprintf("$ tea %s comment delete 81 --yes", words.group), Annotations: map[string]string{"tea.text": "comment deletion does not return a resource", "tea.confirm": "true"}, Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
remove := &cobra.Command{Use: "delete <comment-id>", Short: "Delete a comment", Example: fmt.Sprintf("$ tea %s comment delete 81 --yes", words.group), Annotations: map[string]string{"tea.text": "comment deletion does not return a resource", "tea.confirm": "true"}, Args: argsExact(1), RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
t, comment, err := a.comment(cmd, args[0], kind)
|
t, comment, err := a.comment(cmd, args[0], kind, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,3 +192,33 @@ func TestViewCommentsCannotBeCombinedWithJSON(t *testing.T) {
|
|||||||
require.ErrorContains(t, err, "tea pr review list <number> --json")
|
require.ErrorContains(t, err, "tea pr review list <number> --json")
|
||||||
require.Empty(t, out.String())
|
require.Empty(t, out.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommentEditAndDeleteReachInlineReviewComments(t *testing.T) {
|
||||||
|
var calls []string
|
||||||
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
calls = append(calls, r.Method+" "+r.URL.Path)
|
||||||
|
switch r.Method + " " + r.URL.Path {
|
||||||
|
case "GET /api/v1/repos/alice/project/issues/comments/40":
|
||||||
|
w.WriteHeader(http.StatusNoContent) // Gitea's answer for inline review comments
|
||||||
|
case "PATCH /api/v1/repos/alice/project/issues/comments/40":
|
||||||
|
require.Equal(t, "Fixed", payloadField(t, r, "body"))
|
||||||
|
_, _ = w.Write([]byte(`{"id":40,"body":"Fixed"}`))
|
||||||
|
case "DELETE /api/v1/repos/alice/project/issues/comments/40":
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
deps, _, _ := serverDeps(t, server.URL)
|
||||||
|
run := func(args ...string) error {
|
||||||
|
return Execute(context.Background(), deps, append([]string{"pr", "comment"}, append(args, "-R", "alice/project", "--no-input")...))
|
||||||
|
}
|
||||||
|
require.NoError(t, run("edit", "40", "--body", "Fixed"))
|
||||||
|
require.NoError(t, run("delete", "40", "--yes"))
|
||||||
|
require.Contains(t, calls, "PATCH /api/v1/repos/alice/project/issues/comments/40")
|
||||||
|
require.Contains(t, calls, "DELETE /api/v1/repos/alice/project/issues/comments/40")
|
||||||
|
// Viewing still explains why an inline review comment cannot be shown.
|
||||||
|
require.ErrorContains(t, run("view", "40"), "not a plain comment")
|
||||||
|
}
|
||||||
@@ -42,30 +42,50 @@ func TestIssueCreateSeedsFromTemplate(t *testing.T) {
|
|||||||
run := func(args ...string) error {
|
run := func(args ...string) error {
|
||||||
return Execute(context.Background(), deps, append([]string{"issue", "create", "-R", "alice/project", "--no-input", "--json=number"}, args...))
|
return Execute(context.Background(), deps, append([]string{"issue", "create", "-R", "alice/project", "--no-input", "--json=number"}, args...))
|
||||||
}
|
}
|
||||||
require.NoError(t, run("--template", "bug report"))
|
err := run("--template", "bug report")
|
||||||
|
require.ErrorContains(t, err, "missing required input: supply --title", "the template title is a placeholder and is never filed")
|
||||||
|
require.NoError(t, run("--template", "bug report", "--title", "Crash"))
|
||||||
require.NoError(t, run("--template", "Bug Report", "--title", "Explicit", "--body", "Override"))
|
require.NoError(t, run("--template", "Bug Report", "--title", "Explicit", "--body", "Override"))
|
||||||
require.NoError(t, run("--template", "feature", "--title", "Idea", "--editor"))
|
require.NoError(t, run("--template", "feature", "--title", "Idea", "--editor"))
|
||||||
require.Equal(t, "Describe the idea", editor.initial)
|
require.Equal(t, "Describe the idea", editor.initial)
|
||||||
err := run("--template", "missing", "--title", "x")
|
err = run("--template", "missing", "--title", "x")
|
||||||
require.ErrorContains(t, err, `issue template "missing" does not exist; available: Bug Report, Feature`)
|
require.ErrorContains(t, err, `issue template "missing" does not exist; available: Bug Report, Feature`)
|
||||||
require.Equal(t, clierr.Failure, clierr.ExitCode(err))
|
require.Equal(t, clierr.Failure, clierr.ExitCode(err))
|
||||||
err = run("--template", "feature")
|
err = run("--template", "feature")
|
||||||
require.ErrorContains(t, err, "missing required input: supply --title")
|
require.ErrorContains(t, err, "missing required input: supply --title")
|
||||||
deps.Streams.In = strings.NewReader("From stdin")
|
deps.Streams.In = strings.NewReader("From stdin")
|
||||||
require.NoError(t, run("--template", "bug report", "--body-file", "-"))
|
require.NoError(t, run("--template", "bug report", "--title", "Crash", "--body-file", "-"))
|
||||||
err = run("--template", "feature", "--body-file", "-")
|
err = run("--template", "feature", "--body-file", "-")
|
||||||
require.ErrorContains(t, err, "--body-file - requires --title")
|
require.ErrorContains(t, err, "--body-file - requires --title")
|
||||||
require.Len(t, created, 4)
|
require.Len(t, created, 4)
|
||||||
for i, want := range []string{
|
for i, want := range []string{
|
||||||
`{"title":"[bug] ","body":"Steps:"}`,
|
`{"title":"Crash","body":"Steps:"}`,
|
||||||
`{"title":"Explicit","body":"Override"}`,
|
`{"title":"Explicit","body":"Override"}`,
|
||||||
`{"title":"Idea","body":"edited idea"}`,
|
`{"title":"Idea","body":"edited idea"}`,
|
||||||
`{"title":"[bug] ","body":"From stdin"}`,
|
`{"title":"Crash","body":"From stdin"}`,
|
||||||
} {
|
} {
|
||||||
requirePayload(t, want, strings.NewReader(created[i]))
|
requirePayload(t, want, strings.NewReader(created[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssueCreateTemplateOpensFormWithTemplateBody(t *testing.T) {
|
||||||
|
var createPayload map[string]any
|
||||||
|
server := newAuthenticatedServer(selectablesHandler(t, true, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.NoError(t, json.NewDecoder(r.Body).Decode(&createPayload))
|
||||||
|
_, _ = w.Write([]byte(`{"number":9,"title":"Mine"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
form := &recordingIssueForm{values: terminal.IssueFormValues{Title: "Mine", Body: "## Steps"}}
|
||||||
|
deps, _, _ := serverDeps(t, server.URL)
|
||||||
|
deps.IssueForm = form
|
||||||
|
deps.Prompter = &scriptedPrompter{accept: true}
|
||||||
|
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
||||||
|
require.NoError(t, Execute(context.Background(), deps, []string{"issue", "create", "-R", "alice/project", "--json=number", "--interactive", "--template", "bug"}))
|
||||||
|
require.Equal(t, 1, form.calls)
|
||||||
|
require.Equal(t, &terminal.IssueTemplateChoice{Name: "Bug", About: "Report", Body: "## Steps"}, form.choices.Template, "the form starts with the template body and an empty title")
|
||||||
|
require.Equal(t, "Mine", createPayload["title"])
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssueCreateUploadsAttachmentsAndKeepsIssueOnFailure(t *testing.T) {
|
func TestIssueCreateUploadsAttachmentsAndKeepsIssueOnFailure(t *testing.T) {
|
||||||
var requests, uploaded []string
|
var requests, uploaded []string
|
||||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
+17
-7
@@ -203,7 +203,11 @@ func (a *application) issueCreateCommand() *cobra.Command {
|
|||||||
var useEditor, web bool
|
var useEditor, web bool
|
||||||
var assignees, labelNames, attachmentPaths []string
|
var assignees, labelNames, attachmentPaths []string
|
||||||
cmd := &cobra.Command{Use: "create", Aliases: []string{"new"}, Short: "Create an issue", Long: `Create an issue in a repository. Without flags on a terminal, a form asks for the title and
|
cmd := &cobra.Command{Use: "create", Aliases: []string{"new"}, Short: "Create an issue", Long: `Create an issue in a repository. Without flags on a terminal, a form asks for the title and
|
||||||
body, then optionally for assignees, a milestone, labels and a due date.`, Example: `$ tea issue create
|
body, then optionally for assignees, a milestone, labels and a due date.
|
||||||
|
|
||||||
|
--template starts from the body of a repository issue template; on a terminal the form opens with
|
||||||
|
that body and an empty title. Without a terminal, --title is required, and --body or --body-file
|
||||||
|
replace the template body.`, Example: `$ tea issue create
|
||||||
$ tea issue create --title "Crash on start" --body "Steps to reproduce…" --label bug --milestone v1.0
|
$ tea issue create --title "Crash on start" --body "Steps to reproduce…" --label bug --milestone v1.0
|
||||||
$ tea issue create --template "Bug report"`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) (runErr error) {
|
$ tea issue create --template "Bug report"`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) (runErr error) {
|
||||||
var draft terminal.Draft
|
var draft terminal.Draft
|
||||||
@@ -243,8 +247,8 @@ $ tea issue create --template "Bug report"`, Args: argsExact(0), RunE: func(cmd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !localFlagsChanged(cmd) && a.canPrompt() && a.deps.IssueForm != nil {
|
if !localFlagsChanged(cmd, "template") && a.canPrompt() && a.deps.IssueForm != nil {
|
||||||
return a.createIssueInteractively(cmd)
|
return a.createIssueInteractively(cmd, strings.TrimSpace(templateName))
|
||||||
}
|
}
|
||||||
var target target
|
var target target
|
||||||
var templateBody string
|
var templateBody string
|
||||||
@@ -258,9 +262,7 @@ $ tea issue create --template "Bug report"`, Args: argsExact(0), RunE: func(cmd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !cmd.Flags().Changed("title") {
|
// Like gh, the template only seeds the body; its title is a placeholder, so --title is required.
|
||||||
title = template.Title
|
|
||||||
}
|
|
||||||
templateBody = issueTemplateBody(template)
|
templateBody = issueTemplateBody(template)
|
||||||
}
|
}
|
||||||
if web {
|
if web {
|
||||||
@@ -344,7 +346,8 @@ func (a *application) issueCreated(cmd *cobra.Command, t target, item services.I
|
|||||||
return a.created(cmd, item, item.URL)
|
return a.created(cmd, item, item.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *application) createIssueInteractively(cmd *cobra.Command) (runErr error) {
|
// createIssueInteractively runs the create form; a non-empty templateName pre-fills its body.
|
||||||
|
func (a *application) createIssueInteractively(cmd *cobra.Command, templateName string) (runErr error) {
|
||||||
target, err := a.resolve(cmd.Context(), true)
|
target, err := a.resolve(cmd.Context(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -363,6 +366,13 @@ func (a *application) createIssueInteractively(cmd *cobra.Command) (runErr error
|
|||||||
choices.Templates = append(choices.Templates, terminal.IssueTemplateChoice{Name: template.Name, About: template.About, Title: template.Title, Body: issueTemplateBody(template)})
|
choices.Templates = append(choices.Templates, terminal.IssueTemplateChoice{Name: template.Name, About: template.About, Title: template.Title, Body: issueTemplateBody(template)})
|
||||||
}
|
}
|
||||||
choices.Milestones, choices.Labels = milestoneChoices(selectables.Milestones), labelChoices(selectables.Labels)
|
choices.Milestones, choices.Labels = milestoneChoices(selectables.Milestones), labelChoices(selectables.Labels)
|
||||||
|
if templateName != "" {
|
||||||
|
template, err := issueTemplate(cmd.Context(), service, templateName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
choices.Template = &terminal.IssueTemplateChoice{Name: template.Name, About: template.About, Body: issueTemplateBody(template)}
|
||||||
|
}
|
||||||
values, err := a.deps.IssueForm.CreateIssue(cmd.Context(), choices)
|
values, err := a.deps.IssueForm.CreateIssue(cmd.Context(), choices)
|
||||||
defer finishDraft(&values.Draft, &runErr)
|
defer finishDraft(&values.Draft, &runErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -61,6 +61,17 @@ func (s Comments) Page(ctx context.Context, n int64, limit, page int) ([]Comment
|
|||||||
return pageOf(items, limit, page)
|
return pageOf(items, limit, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotPlainCommentError reports a comment that the single-comment endpoint answers with no body,
|
||||||
|
// such as an inline review comment. Such comments can still be edited and deleted by ID.
|
||||||
|
type NotPlainCommentError struct {
|
||||||
|
ID int64
|
||||||
|
Repository string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e NotPlainCommentError) Error() string {
|
||||||
|
return fmt.Sprintf("comment %d in %s is not a plain comment, such as an inline review comment, which this command cannot show", e.ID, e.Repository)
|
||||||
|
}
|
||||||
|
|
||||||
// View retrieves a comment by its repository-scoped ID.
|
// View retrieves a comment by its repository-scoped ID.
|
||||||
func (s Comments) View(ctx context.Context, id int64) (Comment, error) {
|
func (s Comments) View(ctx context.Context, id int64) (Comment, error) {
|
||||||
status := 0
|
status := 0
|
||||||
@@ -73,7 +84,7 @@ func (s Comments) View(ctx context.Context, id int64) (Comment, error) {
|
|||||||
})
|
})
|
||||||
if status == http.StatusNoContent {
|
if status == http.StatusNoContent {
|
||||||
// Gitea answers without a body for the comments that are not plain ones, such as inline review comments.
|
// Gitea answers without a body for the comments that are not plain ones, such as inline review comments.
|
||||||
return Comment{}, fmt.Errorf("comment %d in %s is not a plain comment, such as an inline review comment, which this command cannot show", id, s.Repository)
|
return Comment{}, NotPlainCommentError{ID: id, Repository: s.Repository}
|
||||||
}
|
}
|
||||||
return item, Classify(err, "comment", strconv.FormatInt(id, 10), s.Repository)
|
return item, Classify(err, "comment", strconv.FormatInt(id, 10), s.Repository)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ type BodyEditor func(context.Context, string) (Draft, error)
|
|||||||
|
|
||||||
// IssueFormChoices supplies options for an interactive issue create form.
|
// IssueFormChoices supplies options for an interactive issue create form.
|
||||||
type IssueFormChoices struct {
|
type IssueFormChoices struct {
|
||||||
Templates []IssueTemplateChoice
|
Templates []IssueTemplateChoice
|
||||||
|
// Template, when set, is used instead of asking: the form starts with its body and an empty title.
|
||||||
|
Template *IssueTemplateChoice
|
||||||
Assignees []string
|
Assignees []string
|
||||||
Milestones []IssueMilestoneChoice
|
Milestones []IssueMilestoneChoice
|
||||||
Labels []IssueLabelChoice
|
Labels []IssueLabelChoice
|
||||||
@@ -64,7 +66,9 @@ type IssueForm interface {
|
|||||||
// CreateIssue runs the interactive issue create form on stderr.
|
// CreateIssue runs the interactive issue create form on stderr.
|
||||||
func (p *FormPrompter) CreateIssue(ctx context.Context, choices IssueFormChoices) (IssueFormValues, error) {
|
func (p *FormPrompter) CreateIssue(ctx context.Context, choices IssueFormChoices) (IssueFormValues, error) {
|
||||||
title, body := "", ""
|
title, body := "", ""
|
||||||
if len(choices.Templates) > 0 {
|
if choices.Template != nil {
|
||||||
|
body = choices.Template.Body
|
||||||
|
} else if len(choices.Templates) > 0 {
|
||||||
options := []huh.Option[int]{huh.NewOption("[none]", -1)}
|
options := []huh.Option[int]{huh.NewOption("[none]", -1)}
|
||||||
for i, template := range choices.Templates {
|
for i, template := range choices.Templates {
|
||||||
label := template.Name
|
label := template.Name
|
||||||
|
|||||||
Reference in new issue
Block a user