fix: tighten error classification and pull request selection

Only Gitea's archived-repository messages mean the repository is archived,
so an archived label is no longer reported as one. Refused POSTs to the
organization, team, user and SSH key collections name the resource they would
have created, and the existing pull request is looked up with escaped branch
names.

pullSelector without an argument refuses an explicit --repo, like gh: the
checked-out branch says nothing about another repository.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 22:48:50 +02:00
1 parent dc43272085
commit 71189fd15f
7 files changed
+41 -23

No files matched your search

+4 -3
View File
@@ -295,10 +295,11 @@ func argumentName(cmd *cobra.Command, argument string) string {
if group == cmd {
return text
}
if noun := group.Name(); noun == "pr" {
return "pull request " + text
noun := group.Name()
if noun == "pr" {
noun = "pull request"
}
return group.Name() + " " + text
return noun + " " + text
}
// localFlagsChanged reports whether any command-specific flag was supplied.
+3 -11
View File
@@ -5,6 +5,7 @@ package app
import (
"bufio"
"cmp"
"errors"
"fmt"
"io"
@@ -58,10 +59,10 @@ func (f downloadFlags) destination(name string) (path string, skip bool, err err
}
path = f.output
if path == "" || isDirectory(path) {
if name != filepath.Base(name) || name == "." || name == ".." || strings.ContainsAny(name, `/\`) {
if name == "" || name == "." || name == ".." || strings.ContainsAny(name, `/\`) {
return "", false, clierr.Wrap(clierr.Usage, fmt.Errorf("cannot use %q as a file name; pass --output", name))
}
path = filepath.Join(cmpOrDot(path, f.dir), name)
path = filepath.Join(cmp.Or(path, f.dir, "."), name)
}
if _, err := os.Lstat(path); err == nil {
if f.skipExisting {
@@ -76,15 +77,6 @@ func (f downloadFlags) destination(name string) (path string, skip bool, err err
return path, false, nil
}
func cmpOrDot(values ...string) string {
for _, value := range values {
if value != "" {
return value
}
}
return "."
}
func isDirectory(path string) bool {
info, err := os.Stat(path)
return (err == nil && info.IsDir()) || strings.HasSuffix(path, string(filepath.Separator)) || strings.HasSuffix(path, "/")
+2 -1
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"net"
"net/http"
"net/url"
"strings"
"syscall"
"time"
@@ -206,7 +207,7 @@ func existsHint(ctx context.Context, apiErr *transport.APIError, conflict *servi
var pull struct {
URL string `json:"html_url"`
}
if apiErr.Lookup(ctx, "repos/"+conflict.Scope+"/pulls/"+base+"/"+head, &pull) == http.StatusOK && pull.URL != "" {
if apiErr.Lookup(ctx, "repos/"+conflict.Scope+"/pulls/"+url.PathEscape(base)+"/"+url.PathEscape(head), &pull) == http.StatusOK && pull.URL != "" {
return "see " + pull.URL
}
case "wiki page":
+14 -7
View File
@@ -74,11 +74,14 @@ func parseRefURL(raw string, kind refKind) (ref, error) {
return ref{}, invalid
}
segments := strings.Split(strings.Trim(u.Path, "/"), "/")
positive := func(i int) int64 {
if i >= len(segments) {
return 0
at := func(i int) string {
if i < len(segments) {
return segments[i]
}
n, err := strconv.ParseInt(segments[i], 10, 64)
return ""
}
positive := func(i int) int64 {
n, err := strconv.ParseInt(at(i), 10, 64)
if err != nil || n < 1 {
return 0
}
@@ -88,12 +91,12 @@ func parseRefURL(raw string, kind refKind) (ref, error) {
for i := 2; i < len(segments); i++ {
result := ref{}
switch {
case kind == runRef && segments[i] == "actions" && i+2 < len(segments) && segments[i+1] == "runs" && positive(i+2) > 0:
case kind == runRef && at(i) == "actions" && at(i+1) == "runs" && positive(i+2) > 0:
result.Number = positive(i + 2)
if i+4 < len(segments) && segments[i+3] == "jobs" {
if at(i+3) == "jobs" {
result.Job = positive(i + 4)
}
case kind != runRef && (segments[i] == "issues" || segments[i] == "pulls") && positive(i+1) > 0:
case kind != runRef && (at(i) == "issues" || at(i) == "pulls") && positive(i+1) > 0:
result.Number = positive(i + 1)
default:
continue
@@ -188,6 +191,10 @@ func (a *application) sameRepository(first, second string) bool {
// checked-out branch.
func (a *application) pullSelector(cmd *cobra.Command, args []string) (target, int64, error) {
if len(args) == 0 || args[0] == "" {
// The checked-out branch says nothing about a repository chosen with --repo.
if a.options.repo != "" {
return target{}, 0, clierr.Wrap(clierr.Usage, errors.New("pass a pull request number, URL or branch with --repo"))
}
return a.currentPull(cmd.Context())
}
raw := args[0]
+5
View File
@@ -135,6 +135,11 @@ func TestPullSelectorDefaultsToTheCheckedOutBranch(t *testing.T) {
require.Equal(t, tc.want, n)
})
}
a, _ := refApplication(t, branch("feat/two", nil))
a.options.repo = "alice/project"
_, _, err := a.pullSelector(commandContext(), nil)
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
require.EqualError(t, err, "pass a pull request number, URL or branch with --repo")
}
func TestPullSelectorAcceptsNumbersURLsAndBranches(t *testing.T) {