mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
fix: address verified review findings (pulls-1)
Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
15 files changed
+202
-8
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -116,7 +117,7 @@ is not on the remote yet is pushed first, with its upstream set.`, Args: argsExa
|
||||
}
|
||||
}
|
||||
if web {
|
||||
return a.openBrowser(cmd, repositoryURL(t)+"/compare/"+options.Base+"..."+options.Head)
|
||||
return a.openBrowser(cmd, repositoryURL(t)+"/compare/"+compareRef(options.Base)+"..."+compareRef(options.Head))
|
||||
}
|
||||
var commitBody string
|
||||
if options.Title == "" {
|
||||
@@ -373,3 +374,9 @@ func (a *application) reportCreatedPull(cmd *cobra.Command, t target, item servi
|
||||
a.done(cmd, "Created pull request %s", refLabel(t.Repo, item.Number, item.Title))
|
||||
return a.created(cmd, item, item.URL)
|
||||
}
|
||||
|
||||
// compareRef escapes a branch name, or OWNER:BRANCH, for a compare URL, where a '#' or '%' in the
|
||||
// name would otherwise start a fragment or an invalid escape.
|
||||
func compareRef(ref string) string {
|
||||
return strings.ReplaceAll(url.PathEscape(ref), "%2F", "/")
|
||||
}
|
||||
@@ -307,3 +307,9 @@ func TestPullCreateHelpListsDefaultsOnce(t *testing.T) {
|
||||
require.Contains(t, out.String(), "(default true)")
|
||||
require.NotContains(t, out.String(), "(default true) (default true)")
|
||||
}
|
||||
|
||||
func TestCompareRefEscapesBranchNames(t *testing.T) {
|
||||
require.Equal(t, "feature/login", compareRef("feature/login"))
|
||||
require.Equal(t, "fix%2312", compareRef("fix#12"))
|
||||
require.Equal(t, "bob:fix%25x", compareRef("bob:fix%x"))
|
||||
}
|
||||
@@ -194,6 +194,9 @@ removes or adds that marker.`, Annotations: pullArgumentsAnnotation(nil), Args:
|
||||
if item, err = service.Edit(cmd.Context(), n, services.PullEdit{Title: &title}); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.Draft != undo {
|
||||
return errors.New("the title was changed, but Gitea still " + map[bool]string{true: "does not treat", false: "treats"}[undo] + " the pull request as a draft; this instance may use a different draft prefix, so edit the title with: tea pr edit " + strconv.FormatInt(n, 10) + " --title")
|
||||
}
|
||||
if undo {
|
||||
a.done(cmd, "Converted pull request %s to a draft", refLabel(t.Repo, n, item.Title))
|
||||
} else {
|
||||
|
||||
@@ -67,3 +67,14 @@ func TestPullEditAddsAssigneesWithoutExtraReads(t *testing.T) {
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"pr", "edit", "7", "-R", "alice/project", "--add-assignees", "bob,Alice", "--json=number"}))
|
||||
require.Equal(t, []string{"GET /api/v1/repos/alice/project/issues/7", "PATCH /api/v1/repos/alice/project/pulls/7"}, calls)
|
||||
}
|
||||
|
||||
func TestPullReadyFailsWhenGiteaStillSeesADraft(t *testing.T) {
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"number":7,"title":"Draft: x","draft":true}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
deps, _, diagnostics := serverDeps(t, server.URL)
|
||||
err := Execute(context.Background(), deps, []string{"pr", "ready", "7", "-R", "alice/project"})
|
||||
require.ErrorContains(t, err, "still treats the pull request as a draft")
|
||||
require.NotContains(t, diagnostics.String(), "Marked")
|
||||
}
|
||||
@@ -245,3 +245,19 @@ func TestPullMergeRequiresNumberWithoutInteractive(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "pull request number")
|
||||
}
|
||||
|
||||
func TestPullMergeReportsNoBranchDeletionForAGitPullRequests(t *testing.T) {
|
||||
agit := `{"number":3,"title":"Add login","state":"open","base":{"ref":"main"},"head":{"ref":"refs/pull/3/head","label":"","sha":"abc123"}}`
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.URL.Path == "/api/v1/repos/alice/project":
|
||||
_, _ = w.Write([]byte(mergeRepository))
|
||||
case r.Method == http.MethodGet:
|
||||
_, _ = w.Write([]byte(agit))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
deps, _, diagnostics := serverDeps(t, server.URL)
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"pr", "merge", "3", "-R", "alice/project", "--yes", "--delete-branch"}))
|
||||
require.NotContains(t, diagnostics.String(), "emote branch")
|
||||
}
|
||||
@@ -128,6 +128,8 @@ $ tea pr close 13 -c "Superseded by #15" --delete-branch`
|
||||
// head branch of another open pull request, which deleting it would close.
|
||||
func (a *application) deleteHead(cmd *cobra.Command, service services.Pulls, item services.Pull, merging, local bool) {
|
||||
switch {
|
||||
case item.Head == "":
|
||||
// An AGit pull request has no head branch to delete.
|
||||
case item.HeadRemoteDeleted && merging:
|
||||
a.done(cmd, "Deleted remote branch %s", item.Head)
|
||||
case item.HeadRemoteDeleted:
|
||||
@@ -358,7 +360,7 @@ the base branch.`, Annotations: pullArgumentsAnnotation(map[string]string{"tea.c
|
||||
} else {
|
||||
a.done(cmd, "%s pull request %s", mergeVerbs[method], ref)
|
||||
if deleteBranch {
|
||||
a.deleteHead(cmd, service, merged, true, deleteLocal)
|
||||
a.deleteHead(cmd, service, merged, !item.HeadRemoteDeleted, deleteLocal)
|
||||
}
|
||||
}
|
||||
return a.jsonResult(cmd, merged)
|
||||
|
||||
@@ -6,6 +6,7 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -54,3 +55,60 @@ func TestPullDiffNameOnly(t *testing.T) {
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"pr", "diff", "2", "-R", "alice/project", "--name-only"}))
|
||||
require.Equal(t, "one.txt\ndocs/two.md\n", out.String())
|
||||
}
|
||||
|
||||
func TestPullCloseCommentsFirstAndDeletesTheBranchOnlyWhenAsked(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
flags []string
|
||||
deleted bool
|
||||
}{
|
||||
{nil, false},
|
||||
{[]string{"--delete-branch"}, true},
|
||||
} {
|
||||
var requests []string
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests = append(requests, r.Method+" "+r.URL.Path)
|
||||
switch {
|
||||
case r.Method == http.MethodGet:
|
||||
_, _ = w.Write([]byte(`{"number":3,"title":"Add login","state":"open","base":{"ref":"main"},"head":{"ref":"feature","sha":"abc123","repo":{"full_name":"alice/project","permissions":{"push":true}}}}`))
|
||||
case r.Method == http.MethodPatch:
|
||||
_, _ = w.Write([]byte(`{"number":3,"title":"Add login","state":"closed","base":{"ref":"main"},"head":{"ref":"feature","sha":"abc123","repo":{"full_name":"alice/project","permissions":{"push":true}}}}`))
|
||||
case r.Method == http.MethodPost:
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte(`{"id":1}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}))
|
||||
deps, _, _ := serverDeps(t, server.URL)
|
||||
deps.Git = &fakeGit{results: map[string]string{"symbolic-ref --short HEAD": "main"}}
|
||||
require.NoError(t, Execute(context.Background(), deps, append([]string{"pr", "close", "3", "-R", "alice/project", "-c", "Superseded"}, tc.flags...)))
|
||||
server.Close()
|
||||
comment := slices.Index(requests, "POST /api/v1/repos/alice/project/issues/3/comments")
|
||||
edit := slices.Index(requests, "PATCH /api/v1/repos/alice/project/pulls/3")
|
||||
require.True(t, comment >= 0 && comment < edit, requests)
|
||||
require.Equal(t, tc.deleted, slices.Contains(requests, "DELETE /api/v1/repos/alice/project/branches/feature"), requests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullCloseAndReopenRefuseMergedPullRequests(t *testing.T) {
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodGet, r.Method)
|
||||
_, _ = w.Write([]byte(`{"number":3,"title":"Add login","state":"closed","merged":true}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
for verb, want := range map[string]string{"close": "cannot be closed", "reopen": "cannot be reopened"} {
|
||||
deps, _, _ := serverDeps(t, server.URL)
|
||||
require.ErrorContains(t, Execute(context.Background(), deps, []string{"pr", verb, "3", "-R", "alice/project"}), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullReopenWarnsWhenAlreadyOpen(t *testing.T) {
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, http.MethodGet, r.Method)
|
||||
_, _ = w.Write([]byte(`{"number":3,"title":"Add login","state":"open"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
deps, _, diagnostics := serverDeps(t, server.URL)
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"pr", "reopen", "3", "-R", "alice/project"}))
|
||||
require.Contains(t, diagnostics.String(), "is already open")
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (a *application) pullListCommand() *cobra.Command {
|
||||
var web bool
|
||||
cmd := &cobra.Command{Use: "list", Short: "List pull requests", Long: `List the pull requests of a repository, newest first.
|
||||
|
||||
The reviewers, reviewer_teams and merged_by JSON fields come from separate requests for
|
||||
The reviewers, reviewer_teams, review_decision and merged_by JSON fields come from separate requests for
|
||||
each pull request, which tea only makes when --json names them.`, Args: argsExact(0), RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
if id, err := strconv.ParseInt(milestone, 10, 64); err == nil && id < 1 {
|
||||
return clierr.Wrap(clierr.Usage, errors.New("--milestone must be a milestone title or a positive ID"))
|
||||
@@ -78,10 +78,14 @@ each pull request, which tea only makes when --json names them.`, Args: argsExac
|
||||
return err
|
||||
}
|
||||
items := window.Items
|
||||
if a.jsonSelects("reviewers", "reviewer_teams", "merged_by") {
|
||||
if items, err = service.ViewAll(cmd.Context(), items); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case a.jsonSelects("review_decision"):
|
||||
items, err = service.DecideAll(cmd.Context(), items)
|
||||
case a.jsonSelects("reviewers", "reviewer_teams", "merged_by"):
|
||||
items, err = service.ViewAll(cmd.Context(), items)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noun := map[string]string{"open": "open pull requests", "closed": "closed pull requests", "merged": "merged pull requests", "all": "pull requests"}[state]
|
||||
what := noun + " in " + t.Repo
|
||||
|
||||
@@ -90,3 +90,24 @@ func TestPullListFillsReviewersWhenSelected(t *testing.T) {
|
||||
require.JSONEq(t, `[{"number":13,"reviewers":["bob"]}]`, out.String())
|
||||
require.Equal(t, 1, views)
|
||||
}
|
||||
|
||||
func TestPullListFillsReviewDecisionWhenSelected(t *testing.T) {
|
||||
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v1/repos/alice/project/pulls/13/reviews":
|
||||
if r.URL.Query().Get("page") > "1" {
|
||||
_, _ = w.Write([]byte(`[]`))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`[{"id":1,"state":"APPROVED","user":{"login":"bob"}}]`))
|
||||
case "/api/v1/repos/alice/project/pulls/13":
|
||||
_, _ = w.Write([]byte(`{"number":13}`))
|
||||
default:
|
||||
_, _ = w.Write([]byte(`[{"number":13}]`))
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
deps, out, _ := serverDeps(t, server.URL)
|
||||
require.NoError(t, Execute(context.Background(), deps, []string{"pr", "list", "-R", "alice/project", "--json", "number,review_decision"}))
|
||||
require.JSONEq(t, `[{"number":13,"review_decision":"approved"}]`, out.String())
|
||||
}
|
||||
Reference in new issue
Block a user