mirror of
https://gitea.com/gitea/tea.git
synced 2026-04-05 07:53:32 +02:00
replace log.Fatal/os.Exit with error returns (#941)
* Use stdlib encoders * Reduce some duplication * Remove global pagination state * Dedupe JSON detail types * Bump golangci-lint Reviewed-on: https://gitea.com/gitea/tea/pulls/941 Co-authored-by: techknowlogick <techknowlogick@gitea.com> Co-committed-by: techknowlogick <techknowlogick@gitea.com>
This commit is contained in:
committed by
techknowlogick
parent
21881525a8
commit
b05e03416b
2
Makefile
2
Makefile
@@ -8,7 +8,7 @@ GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
|
||||
|
||||
# Tool packages with pinned versions
|
||||
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0
|
||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
|
||||
|
||||
ifneq ($(DRONE_TAG),)
|
||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||
|
||||
@@ -169,7 +169,7 @@ tea man --out ./tea.man
|
||||
|
||||
## Compilation
|
||||
|
||||
Make sure you have a current go version installed (1.13 or newer).
|
||||
Make sure you have a current Go version installed (1.26 or newer).
|
||||
|
||||
- To compile the source yourself with the recommended flags & tags:
|
||||
```sh
|
||||
|
||||
@@ -36,7 +36,13 @@ func runRunsDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("run ID is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
runIDStr := cmd.Args().First()
|
||||
|
||||
@@ -83,7 +83,13 @@ func parseTimeFlag(value string) (time.Time, error) {
|
||||
|
||||
// RunRunsList lists workflow runs
|
||||
func RunRunsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
// Parse time filters
|
||||
@@ -98,7 +104,7 @@ func RunRunsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
// Build list options
|
||||
listOpts := flags.GetListOptions()
|
||||
listOpts := flags.GetListOptions(cmd)
|
||||
|
||||
runs, _, err := client.ListRepoActionRuns(c.Owner, c.Repo, gitea.ListRepoActionRunsOptions{
|
||||
ListOptions: listOpts,
|
||||
@@ -112,15 +118,13 @@ func RunRunsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
if runs == nil {
|
||||
print.ActionRunsList(nil, c.Output)
|
||||
return nil
|
||||
return print.ActionRunsList(nil, c.Output)
|
||||
}
|
||||
|
||||
// Filter by time if specified
|
||||
filteredRuns := filterRunsByTime(runs.WorkflowRuns, since, until)
|
||||
|
||||
print.ActionRunsList(filteredRuns, c.Output)
|
||||
return nil
|
||||
return print.ActionRunsList(filteredRuns, c.Output)
|
||||
}
|
||||
|
||||
// filterRunsByTime filters runs based on time range
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
package runs
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestFilterRunsByTime(t *testing.T) {
|
||||
@@ -75,3 +80,32 @@ func TestFilterRunsByTime(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRunsListRequiresRepoContext(t *testing.T) {
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.Chdir(t.TempDir()))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWd))
|
||||
})
|
||||
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "test",
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "token",
|
||||
User: "tester",
|
||||
Default: true,
|
||||
}},
|
||||
})
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: CmdRunsList.Name,
|
||||
Flags: CmdRunsList.Flags,
|
||||
}
|
||||
require.NoError(t, cmd.Set("login", "test"))
|
||||
|
||||
err = RunRunsList(stdctx.Background(), cmd)
|
||||
require.ErrorContains(t, err, "remote repository required")
|
||||
}
|
||||
|
||||
@@ -42,7 +42,13 @@ func runRunsLogs(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("run ID is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
runIDStr := cmd.Args().First()
|
||||
@@ -78,7 +84,7 @@ func runRunsLogs(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
// Otherwise, fetch all jobs and their logs
|
||||
jobs, _, err := client.ListRepoActionRunJobs(c.Owner, c.Repo, runID, gitea.ListRepoActionJobsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get jobs: %w", err)
|
||||
|
||||
@@ -38,7 +38,13 @@ func runRunsView(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("run ID is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
runIDStr := cmd.Args().First()
|
||||
@@ -59,7 +65,7 @@ func runRunsView(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
// Fetch and print jobs if requested
|
||||
if cmd.Bool("jobs") {
|
||||
jobs, _, err := client.ListRepoActionRunJobs(c.Owner, c.Repo, runID, gitea.ListRepoActionJobsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get jobs: %w", err)
|
||||
@@ -67,7 +73,9 @@ func runRunsView(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
if jobs != nil && len(jobs.Jobs) > 0 {
|
||||
fmt.Printf("\nJobs:\n\n")
|
||||
print.ActionWorkflowJobsList(jobs.Jobs, c.Output)
|
||||
if err := print.ActionWorkflowJobsList(jobs.Jobs, c.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,13 @@ func runSecretsCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("secret name is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
secretName := cmd.Args().First()
|
||||
|
||||
@@ -35,7 +35,13 @@ func runSecretsDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("secret name is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
secretName := cmd.Args().First()
|
||||
@@ -50,7 +56,7 @@ func runSecretsDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := client.DeleteRepoActionSecret(c.Owner, c.Repo, secretName)
|
||||
_, err = client.DeleteRepoActionSecret(c.Owner, c.Repo, secretName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -29,16 +29,21 @@ var CmdSecretsList = cli.Command{
|
||||
|
||||
// RunSecretsList list action secrets
|
||||
func RunSecretsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
secrets, _, err := client.ListRepoActionSecret(c.Owner, c.Repo, gitea.ListRepoActionSecretOption{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.ActionSecretsList(secrets, c.Output)
|
||||
return nil
|
||||
return print.ActionSecretsList(secrets, c.Output)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestSecretsListFlags(t *testing.T) {
|
||||
@@ -61,3 +67,32 @@ func TestSecretsListValidation(t *testing.T) {
|
||||
// This is fine - list commands typically ignore extra args
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSecretsListRequiresRepoContext(t *testing.T) {
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.Chdir(t.TempDir()))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWd))
|
||||
})
|
||||
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "test",
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "token",
|
||||
User: "tester",
|
||||
Default: true,
|
||||
}},
|
||||
})
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: CmdSecretsList.Name,
|
||||
Flags: CmdSecretsList.Flags,
|
||||
}
|
||||
require.NoError(t, cmd.Set("login", "test"))
|
||||
|
||||
err = RunSecretsList(stdctx.Background(), cmd)
|
||||
require.ErrorContains(t, err, "remote repository required")
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ func runVariablesDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("variable name is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
variableName := cmd.Args().First()
|
||||
@@ -50,7 +56,7 @@ func runVariablesDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := client.DeleteRepoActionVariable(c.Owner, c.Repo, variableName)
|
||||
_, err = client.DeleteRepoActionVariable(c.Owner, c.Repo, variableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,7 +31,13 @@ var CmdVariablesList = cli.Command{
|
||||
|
||||
// RunVariablesList list action variables
|
||||
func RunVariablesList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
if name := cmd.String("name"); name != "" {
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
package variables
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestVariablesListFlags(t *testing.T) {
|
||||
@@ -61,3 +67,32 @@ func TestVariablesListValidation(t *testing.T) {
|
||||
// This is fine - list commands typically ignore extra args
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunVariablesListRequiresRepoContext(t *testing.T) {
|
||||
oldWd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.Chdir(t.TempDir()))
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(oldWd))
|
||||
})
|
||||
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "test",
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "token",
|
||||
User: "tester",
|
||||
Default: true,
|
||||
}},
|
||||
})
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: CmdVariablesList.Name,
|
||||
Flags: CmdVariablesList.Flags,
|
||||
}
|
||||
require.NoError(t, cmd.Set("login", "test"))
|
||||
|
||||
err = RunVariablesList(stdctx.Background(), cmd)
|
||||
require.ErrorContains(t, err, "remote repository required")
|
||||
}
|
||||
|
||||
@@ -40,7 +40,13 @@ func runVariablesSet(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("variable name is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
variableName := cmd.Args().First()
|
||||
|
||||
@@ -32,7 +32,13 @@ var CmdWorkflowsList = cli.Command{
|
||||
|
||||
// RunWorkflowsList lists workflow files in the repository
|
||||
func RunWorkflowsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
// Try to list workflow files from .gitea/workflows directory
|
||||
@@ -71,7 +77,7 @@ func RunWorkflowsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
// Get recent runs to check activity
|
||||
runs, _, err := client.ListRepoActionRuns(c.Owner, c.Repo, gitea.ListRepoActionRunsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err == nil && runs != nil {
|
||||
for _, run := range runs.WorkflowRuns {
|
||||
@@ -81,6 +87,5 @@ func RunWorkflowsList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
print.WorkflowsList(workflows, workflowStatus, c.Output)
|
||||
return nil
|
||||
return print.WorkflowsList(workflows, workflowStatus, c.Output)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ var cmdAdminUsers = cli.Command{
|
||||
}
|
||||
|
||||
func runAdminUserDetail(_ stdctx.Context, cmd *cli.Command, u string) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
user, _, err := client.GetUserInfo(u)
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,10 @@ var CmdUserList = cli.Command{
|
||||
|
||||
// RunUserList list users
|
||||
func RunUserList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields, err := userFieldsFlag.GetValues(cmd)
|
||||
if err != nil {
|
||||
@@ -43,13 +46,11 @@ func RunUserList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
client := ctx.Login.Client()
|
||||
users, _, err := client.AdminListUsers(gitea.AdminListUsersOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.UserList(users, ctx.Output, fields)
|
||||
|
||||
return nil
|
||||
return print.UserList(users, ctx.Output, fields)
|
||||
}
|
||||
|
||||
257
cmd/api.go
257
cmd/api.go
@@ -97,128 +97,39 @@ Note: if your endpoint contains ? or &, quote it to prevent shell expansion
|
||||
Flags: append(apiFlags(), flags.LoginRepoFlags...),
|
||||
}
|
||||
|
||||
type preparedAPIRequest struct {
|
||||
Method string
|
||||
Endpoint string
|
||||
Headers map[string]string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func runApi(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
|
||||
// Get the endpoint argument
|
||||
if cmd.NArg() < 1 {
|
||||
return fmt.Errorf("endpoint argument required")
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint := cmd.Args().First()
|
||||
|
||||
// Expand placeholders in endpoint
|
||||
endpoint = expandPlaceholders(endpoint, ctx)
|
||||
|
||||
// Parse headers
|
||||
headers := make(map[string]string)
|
||||
for _, h := range cmd.StringSlice("header") {
|
||||
parts := strings.SplitN(h, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid header format: %q (expected key:value)", h)
|
||||
}
|
||||
headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
request, err := prepareAPIRequest(cmd, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build request body from fields
|
||||
var body io.Reader
|
||||
stringFields := cmd.StringSlice("field")
|
||||
typedFields := cmd.StringSlice("Field")
|
||||
dataRaw := cmd.String("data")
|
||||
|
||||
if dataRaw != "" && (len(stringFields) > 0 || len(typedFields) > 0) {
|
||||
return fmt.Errorf("--data/-d cannot be combined with --field/-f or --Field/-F")
|
||||
}
|
||||
|
||||
if dataRaw != "" {
|
||||
var dataBytes []byte
|
||||
var dataSource string
|
||||
if strings.HasPrefix(dataRaw, "@") {
|
||||
filename := dataRaw[1:]
|
||||
var err error
|
||||
if filename == "-" {
|
||||
dataBytes, err = io.ReadAll(os.Stdin)
|
||||
dataSource = "stdin"
|
||||
} else {
|
||||
dataBytes, err = os.ReadFile(filename)
|
||||
dataSource = filename
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %q: %w", dataRaw, err)
|
||||
}
|
||||
} else {
|
||||
dataBytes = []byte(dataRaw)
|
||||
}
|
||||
if !json.Valid(dataBytes) {
|
||||
if dataSource != "" {
|
||||
return fmt.Errorf("--data/-d value from %s is not valid JSON", dataSource)
|
||||
}
|
||||
return fmt.Errorf("--data/-d value is not valid JSON")
|
||||
}
|
||||
body = bytes.NewReader(dataBytes)
|
||||
} else if len(stringFields) > 0 || len(typedFields) > 0 {
|
||||
bodyMap := make(map[string]any)
|
||||
|
||||
// Process string fields (-f)
|
||||
for _, f := range stringFields {
|
||||
parts := strings.SplitN(f, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field format: %q (expected key=value)", f)
|
||||
}
|
||||
key := parts[0]
|
||||
if key == "" {
|
||||
return fmt.Errorf("field key cannot be empty in %q", f)
|
||||
}
|
||||
if _, exists := bodyMap[key]; exists {
|
||||
return fmt.Errorf("duplicate field key %q", key)
|
||||
}
|
||||
bodyMap[key] = parts[1]
|
||||
}
|
||||
|
||||
// Process typed fields (-F)
|
||||
for _, f := range typedFields {
|
||||
parts := strings.SplitN(f, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid field format: %q (expected key=value)", f)
|
||||
}
|
||||
key := parts[0]
|
||||
if key == "" {
|
||||
return fmt.Errorf("field key cannot be empty in %q", f)
|
||||
}
|
||||
if _, exists := bodyMap[key]; exists {
|
||||
return fmt.Errorf("duplicate field key %q", key)
|
||||
}
|
||||
value := parts[1]
|
||||
|
||||
parsedValue, err := parseTypedValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse field %q: %w", key, err)
|
||||
}
|
||||
bodyMap[key] = parsedValue
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(bodyMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode request body: %w", err)
|
||||
}
|
||||
body = bytes.NewReader(bodyBytes)
|
||||
if request.Body != nil {
|
||||
body = bytes.NewReader(request.Body)
|
||||
}
|
||||
|
||||
// Create API client and make request
|
||||
client := api.NewClient(ctx.Login)
|
||||
method := strings.ToUpper(cmd.String("method"))
|
||||
if !cmd.IsSet("method") {
|
||||
if body != nil {
|
||||
method = "POST"
|
||||
} else {
|
||||
method = "GET"
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.Do(method, endpoint, body, headers)
|
||||
resp, err := client.Do(request.Method, request.Endpoint, body, request.Headers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: failed to close response body: %v\n", closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
// Print headers to stderr if requested (so redirects/pipes work correctly)
|
||||
if cmd.Bool("include") {
|
||||
@@ -249,7 +160,11 @@ func runApi(_ stdctx.Context, cmd *cli.Command) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
if closeErr := file.Close(); closeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: failed to close output file: %v\n", closeErr)
|
||||
}
|
||||
}()
|
||||
output = file
|
||||
}
|
||||
|
||||
@@ -267,6 +182,126 @@ func runApi(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareAPIRequest(cmd *cli.Command, ctx *context.TeaContext) (*preparedAPIRequest, error) {
|
||||
var err error
|
||||
|
||||
// Get the endpoint argument
|
||||
if cmd.NArg() < 1 {
|
||||
return nil, fmt.Errorf("endpoint argument required")
|
||||
}
|
||||
endpoint := cmd.Args().First()
|
||||
|
||||
// Expand placeholders in endpoint
|
||||
endpoint = expandPlaceholders(endpoint, ctx)
|
||||
|
||||
// Parse headers
|
||||
headers := make(map[string]string)
|
||||
for _, h := range cmd.StringSlice("header") {
|
||||
parts := strings.SplitN(h, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid header format: %q (expected key:value)", h)
|
||||
}
|
||||
headers[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
// Build request body from fields
|
||||
var bodyBytes []byte
|
||||
stringFields := cmd.StringSlice("field")
|
||||
typedFields := cmd.StringSlice("Field")
|
||||
dataRaw := cmd.String("data")
|
||||
|
||||
if dataRaw != "" && (len(stringFields) > 0 || len(typedFields) > 0) {
|
||||
return nil, fmt.Errorf("--data/-d cannot be combined with --field/-f or --Field/-F")
|
||||
}
|
||||
|
||||
if dataRaw != "" {
|
||||
var dataBytes []byte
|
||||
var dataSource string
|
||||
if strings.HasPrefix(dataRaw, "@") {
|
||||
filename := dataRaw[1:]
|
||||
if filename == "-" {
|
||||
dataBytes, err = io.ReadAll(os.Stdin)
|
||||
dataSource = "stdin"
|
||||
} else {
|
||||
dataBytes, err = os.ReadFile(filename)
|
||||
dataSource = filename
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %w", dataRaw, err)
|
||||
}
|
||||
} else {
|
||||
dataBytes = []byte(dataRaw)
|
||||
}
|
||||
if !json.Valid(dataBytes) {
|
||||
if dataSource != "" {
|
||||
return nil, fmt.Errorf("--data/-d value from %s is not valid JSON", dataSource)
|
||||
}
|
||||
return nil, fmt.Errorf("--data/-d value is not valid JSON")
|
||||
}
|
||||
bodyBytes = dataBytes
|
||||
} else if len(stringFields) > 0 || len(typedFields) > 0 {
|
||||
bodyMap := make(map[string]any)
|
||||
|
||||
// Process string fields (-f)
|
||||
for _, f := range stringFields {
|
||||
parts := strings.SplitN(f, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid field format: %q (expected key=value)", f)
|
||||
}
|
||||
key := parts[0]
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("field key cannot be empty in %q", f)
|
||||
}
|
||||
if _, exists := bodyMap[key]; exists {
|
||||
return nil, fmt.Errorf("duplicate field key %q", key)
|
||||
}
|
||||
bodyMap[key] = parts[1]
|
||||
}
|
||||
|
||||
// Process typed fields (-F)
|
||||
for _, f := range typedFields {
|
||||
parts := strings.SplitN(f, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid field format: %q (expected key=value)", f)
|
||||
}
|
||||
key := parts[0]
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("field key cannot be empty in %q", f)
|
||||
}
|
||||
if _, exists := bodyMap[key]; exists {
|
||||
return nil, fmt.Errorf("duplicate field key %q", key)
|
||||
}
|
||||
value := parts[1]
|
||||
|
||||
parsedValue, err := parseTypedValue(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse field %q: %w", key, err)
|
||||
}
|
||||
bodyMap[key] = parsedValue
|
||||
}
|
||||
|
||||
bodyBytes, err = json.Marshal(bodyMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode request body: %w", err)
|
||||
}
|
||||
}
|
||||
method := strings.ToUpper(cmd.String("method"))
|
||||
if !cmd.IsSet("method") {
|
||||
if bodyBytes != nil {
|
||||
method = "POST"
|
||||
} else {
|
||||
method = "GET"
|
||||
}
|
||||
}
|
||||
|
||||
return &preparedAPIRequest{
|
||||
Method: method,
|
||||
Endpoint: endpoint,
|
||||
Headers: headers,
|
||||
Body: bodyBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseTypedValue parses a value for -F flag, handling:
|
||||
// - @filename: read content from file
|
||||
// - @-: read content from stdin
|
||||
|
||||
@@ -7,11 +7,8 @@ import (
|
||||
stdctx "context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
@@ -254,35 +251,18 @@ func TestParseTypedValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// runApiWithArgs sets up a test server that captures requests, configures the
|
||||
// login to point at it, and runs the api command with the given CLI args.
|
||||
// Returns the captured HTTP method, body bytes, and any error from the command.
|
||||
// runApiWithArgs configures a test login, parses the command line, and captures
|
||||
// the prepared request without opening sockets or making HTTP requests.
|
||||
func runApiWithArgs(t *testing.T, args []string) (method string, body []byte, err error) {
|
||||
t.Helper()
|
||||
|
||||
var mu sync.Mutex
|
||||
var capturedMethod string
|
||||
var capturedBody []byte
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, readErr := io.ReadAll(r.Body)
|
||||
if readErr != nil {
|
||||
t.Fatalf("failed to read request body: %v", readErr)
|
||||
}
|
||||
mu.Lock()
|
||||
capturedMethod = r.Method
|
||||
capturedBody = b
|
||||
mu.Unlock()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "testLogin",
|
||||
URL: server.URL,
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "test-token",
|
||||
User: "testUser",
|
||||
Default: true,
|
||||
@@ -295,7 +275,19 @@ func runApiWithArgs(t *testing.T, args []string) (method string, body []byte, er
|
||||
cmd := cli.Command{
|
||||
Name: "api",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: runApi,
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request, err := prepareAPIRequest(cmd, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
capturedMethod = request.Method
|
||||
capturedBody = append([]byte(nil), request.Body...)
|
||||
return nil
|
||||
},
|
||||
Flags: append(apiFlags(), []cli.Flag{
|
||||
&cli.StringFlag{Name: "login", Aliases: []string{"l"}},
|
||||
&cli.StringFlag{Name: "repo", Aliases: []string{"r"}},
|
||||
@@ -308,8 +300,6 @@ func runApiWithArgs(t *testing.T, args []string) (method string, body []byte, er
|
||||
fullArgs := append([]string{"api", "--login", "testLogin"}, args...)
|
||||
runErr := cmd.Run(stdctx.Background(), fullArgs)
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return capturedMethod, capturedBody, runErr
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,13 @@ var CmdReleaseAttachmentCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runReleaseAttachmentCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
|
||||
@@ -32,8 +32,13 @@ var CmdReleaseAttachmentDelete = cli.Command{
|
||||
}
|
||||
|
||||
func runReleaseAttachmentDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
|
||||
@@ -31,8 +31,13 @@ var CmdReleaseAttachmentList = cli.Command{
|
||||
|
||||
// RunReleaseAttachmentList list release attachments
|
||||
func RunReleaseAttachmentList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
tag := ctx.Args().First()
|
||||
@@ -46,14 +51,13 @@ func RunReleaseAttachmentList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
attachments, _, err := ctx.Login.Client().ListReleaseAttachments(ctx.Owner, ctx.Repo, release.ID, gitea.ListReleaseAttachmentsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.ReleaseAttachmentsList(attachments, ctx.Output)
|
||||
return nil
|
||||
return print.ReleaseAttachmentsList(attachments, ctx.Output)
|
||||
}
|
||||
|
||||
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
||||
|
||||
@@ -38,8 +38,13 @@ var CmdBranchesList = cli.Command{
|
||||
|
||||
// RunBranchesList list branches
|
||||
func RunBranchesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner := ctx.Owner
|
||||
if ctx.IsSet("owner") {
|
||||
@@ -48,16 +53,15 @@ func RunBranchesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
var branches []*gitea.Branch
|
||||
var protections []*gitea.BranchProtection
|
||||
var err error
|
||||
branches, _, err = ctx.Login.Client().ListRepoBranches(owner, ctx.Repo, gitea.ListRepoBranchesOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protections, _, err = ctx.Login.Client().ListBranchProtections(owner, ctx.Repo, gitea.ListBranchProtectionsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -68,6 +72,5 @@ func RunBranchesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
print.BranchesList(branches, protections, ctx.Output, fields)
|
||||
return nil
|
||||
return print.BranchesList(branches, protections, ctx.Output, fields)
|
||||
}
|
||||
|
||||
@@ -45,8 +45,13 @@ var CmdBranchesUnprotect = cli.Command{
|
||||
|
||||
// RunBranchesProtect function to protect/unprotect a list of branches
|
||||
func RunBranchesProtect(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.Args().Present() {
|
||||
return fmt.Errorf("must specify at least one branch")
|
||||
|
||||
@@ -48,7 +48,10 @@ When a host is specified in the repo-slug, it will override the login specified
|
||||
}
|
||||
|
||||
func runRepoClone(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
teaCmd := context.InitCommand(cmd)
|
||||
teaCmd, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := teaCmd.Args()
|
||||
if args.Len() < 1 {
|
||||
|
||||
@@ -36,8 +36,13 @@ var CmdAddComment = cli.Command{
|
||||
}
|
||||
|
||||
func runAddComment(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := ctx.Args()
|
||||
if args.Len() == 0 {
|
||||
|
||||
93
cmd/detail_json.go
Normal file
93
cmd/detail_json.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
type detailLabelData struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type detailCommentData struct {
|
||||
ID int64 `json:"id"`
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type detailReviewData struct {
|
||||
ID int64 `json:"id"`
|
||||
Reviewer string `json:"reviewer"`
|
||||
State gitea.ReviewStateType `json:"state"`
|
||||
Body string `json:"body"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
func buildDetailLabels(labels []*gitea.Label) []detailLabelData {
|
||||
labelSlice := make([]detailLabelData, 0, len(labels))
|
||||
for _, label := range labels {
|
||||
labelSlice = append(labelSlice, detailLabelData{
|
||||
Name: label.Name,
|
||||
Color: label.Color,
|
||||
Description: label.Description,
|
||||
})
|
||||
}
|
||||
return labelSlice
|
||||
}
|
||||
|
||||
func buildDetailAssignees(assignees []*gitea.User) []string {
|
||||
assigneeSlice := make([]string, 0, len(assignees))
|
||||
for _, assignee := range assignees {
|
||||
assigneeSlice = append(assigneeSlice, username(assignee))
|
||||
}
|
||||
return assigneeSlice
|
||||
}
|
||||
|
||||
func buildDetailComments(comments []*gitea.Comment) []detailCommentData {
|
||||
commentSlice := make([]detailCommentData, 0, len(comments))
|
||||
for _, comment := range comments {
|
||||
commentSlice = append(commentSlice, detailCommentData{
|
||||
ID: comment.ID,
|
||||
Author: username(comment.Poster),
|
||||
Body: comment.Body,
|
||||
Created: comment.Created,
|
||||
})
|
||||
}
|
||||
return commentSlice
|
||||
}
|
||||
|
||||
func buildDetailReviews(reviews []*gitea.PullReview) []detailReviewData {
|
||||
reviewSlice := make([]detailReviewData, 0, len(reviews))
|
||||
for _, review := range reviews {
|
||||
reviewSlice = append(reviewSlice, detailReviewData{
|
||||
ID: review.ID,
|
||||
Reviewer: username(review.Reviewer),
|
||||
State: review.State,
|
||||
Body: review.Body,
|
||||
Created: review.Submitted,
|
||||
})
|
||||
}
|
||||
return reviewSlice
|
||||
}
|
||||
|
||||
func username(user *gitea.User) string {
|
||||
if user == nil {
|
||||
return "ghost"
|
||||
}
|
||||
return user.UserName
|
||||
}
|
||||
|
||||
func writeIndentedJSON(w io.Writer, data any) error {
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", "\t")
|
||||
return encoder.Encode(data)
|
||||
}
|
||||
@@ -39,16 +39,33 @@ var OutputFlag = cli.StringFlag{
|
||||
}
|
||||
|
||||
var (
|
||||
paging gitea.ListOptions
|
||||
// ErrPage indicates that the provided page value is invalid (less than -1 or equal to 0).
|
||||
ErrPage = errors.New("page cannot be smaller than 1")
|
||||
// ErrLimit indicates that the provided limit value is invalid (negative).
|
||||
ErrLimit = errors.New("limit cannot be negative")
|
||||
)
|
||||
|
||||
// GetListOptions returns configured paging struct
|
||||
func GetListOptions() gitea.ListOptions {
|
||||
return paging
|
||||
const (
|
||||
defaultPageValue = 1
|
||||
defaultLimitValue = 30
|
||||
)
|
||||
|
||||
// GetListOptions returns list options derived from the active command.
|
||||
func GetListOptions(cmd *cli.Command) gitea.ListOptions {
|
||||
page := cmd.Int("page")
|
||||
if page == 0 {
|
||||
page = defaultPageValue
|
||||
}
|
||||
|
||||
pageSize := cmd.Int("limit")
|
||||
if pageSize == 0 {
|
||||
pageSize = defaultLimitValue
|
||||
}
|
||||
|
||||
return gitea.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
}
|
||||
|
||||
// PaginationFlags provides all pagination related flags
|
||||
@@ -62,14 +79,13 @@ var PaginationPageFlag = cli.IntFlag{
|
||||
Name: "page",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "specify page",
|
||||
Value: 1,
|
||||
Value: defaultPageValue,
|
||||
Validator: func(i int) error {
|
||||
if i < 1 && i != -1 {
|
||||
return ErrPage
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Destination: &paging.Page,
|
||||
}
|
||||
|
||||
// PaginationLimitFlag provides flag for pagination options
|
||||
@@ -77,14 +93,13 @@ var PaginationLimitFlag = cli.IntFlag{
|
||||
Name: "limit",
|
||||
Aliases: []string{"lm"},
|
||||
Usage: "specify limit of items per page",
|
||||
Value: 30,
|
||||
Value: defaultLimitValue,
|
||||
Validator: func(i int) error {
|
||||
if i < 0 {
|
||||
return ErrLimit
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Destination: &paging.PageSize,
|
||||
}
|
||||
|
||||
// LoginOutputFlags defines login and output flags that should
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
@@ -123,3 +124,29 @@ func TestPaginationFailures(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetListOptionsDoesNotLeakBetweenCommands(t *testing.T) {
|
||||
var results []gitea.ListOptions
|
||||
|
||||
run := func(args []string) {
|
||||
t.Helper()
|
||||
|
||||
cmd := cli.Command{
|
||||
Name: "test-paging",
|
||||
Action: func(_ context.Context, cmd *cli.Command) error {
|
||||
results = append(results, GetListOptions(cmd))
|
||||
return nil
|
||||
},
|
||||
Flags: PaginationFlags,
|
||||
}
|
||||
|
||||
require.NoError(t, cmd.Run(context.Background(), args))
|
||||
}
|
||||
|
||||
run([]string{"test", "--page", "5", "--limit", "10"})
|
||||
run([]string{"test"})
|
||||
|
||||
require.Len(t, results, 2)
|
||||
assert.Equal(t, gitea.ListOptions{Page: 5, PageSize: 10}, results[0])
|
||||
assert.Equal(t, gitea.ListOptions{Page: defaultPageValue, PageSize: defaultLimitValue}, results[1])
|
||||
}
|
||||
|
||||
117
cmd/issues.go
117
cmd/issues.go
@@ -5,7 +5,6 @@ package cmd
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -20,11 +19,7 @@ import (
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type labelData struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type labelData = detailLabelData
|
||||
|
||||
type issueData struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -41,13 +36,17 @@ type issueData struct {
|
||||
Comments []commentData `json:"comments"`
|
||||
}
|
||||
|
||||
type commentData struct {
|
||||
ID int64 `json:"id"`
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
Body string `json:"body"`
|
||||
type issueDetailClient interface {
|
||||
GetIssue(owner, repo string, index int64) (*gitea.Issue, *gitea.Response, error)
|
||||
GetIssueReactions(owner, repo string, index int64) ([]*gitea.Reaction, *gitea.Response, error)
|
||||
}
|
||||
|
||||
type issueCommentClient interface {
|
||||
ListIssueComments(owner, repo string, index int64, opt gitea.ListIssueCommentOptions) ([]*gitea.Comment, *gitea.Response, error)
|
||||
}
|
||||
|
||||
type commentData = detailCommentData
|
||||
|
||||
// CmdIssues represents to login a gitea server.
|
||||
var CmdIssues = cli.Command{
|
||||
Name: "issues",
|
||||
@@ -80,17 +79,35 @@ func runIssues(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runIssueDetail(_ stdctx.Context, cmd *cli.Command, index string) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
if ctx.IsSet("owner") {
|
||||
ctx.Owner = ctx.String("owner")
|
||||
}
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
|
||||
idx, err := utils.ArgToIndex(index)
|
||||
ctx, idx, err := resolveIssueDetailContext(cmd, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
return runIssueDetailWithClient(ctx, idx, ctx.Login.Client())
|
||||
}
|
||||
|
||||
func resolveIssueDetailContext(cmd *cli.Command, index string) (*context.TeaContext, int64, error) {
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if ctx.IsSet("owner") {
|
||||
ctx.Owner = ctx.String("owner")
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
idx, err := utils.ArgToIndex(index)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return ctx, idx, nil
|
||||
}
|
||||
|
||||
func runIssueDetailWithClient(ctx *context.TeaContext, idx int64, client issueDetailClient) error {
|
||||
issue, _, err := client.GetIssue(ctx.Owner, ctx.Repo, idx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,59 +137,37 @@ func runIssueDetail(_ stdctx.Context, cmd *cli.Command, index string) error {
|
||||
}
|
||||
|
||||
func runIssueDetailAsJSON(ctx *context.TeaContext, issue *gitea.Issue) error {
|
||||
c := ctx.Login.Client()
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions()}
|
||||
|
||||
labelSlice := make([]labelData, 0, len(issue.Labels))
|
||||
for _, label := range issue.Labels {
|
||||
labelSlice = append(labelSlice, labelData{label.Name, label.Color, label.Description})
|
||||
return runIssueDetailAsJSONWithClient(ctx, issue, ctx.Login.Client())
|
||||
}
|
||||
|
||||
assigneesSlice := make([]string, 0, len(issue.Assignees))
|
||||
for _, assignee := range issue.Assignees {
|
||||
assigneesSlice = append(assigneesSlice, assignee.UserName)
|
||||
func runIssueDetailAsJSONWithClient(ctx *context.TeaContext, issue *gitea.Issue, c issueCommentClient) error {
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions(ctx.Command)}
|
||||
comments := []*gitea.Comment{}
|
||||
|
||||
if ctx.Bool("comments") {
|
||||
var err error
|
||||
comments, _, err = c.ListIssueComments(ctx.Owner, ctx.Repo, issue.Index, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
issueSlice := issueData{
|
||||
return writeIndentedJSON(ctx.Writer, buildIssueData(issue, comments))
|
||||
}
|
||||
|
||||
func buildIssueData(issue *gitea.Issue, comments []*gitea.Comment) issueData {
|
||||
return issueData{
|
||||
ID: issue.ID,
|
||||
Index: issue.Index,
|
||||
Title: issue.Title,
|
||||
State: issue.State,
|
||||
Created: issue.Created,
|
||||
User: issue.Poster.UserName,
|
||||
User: username(issue.Poster),
|
||||
Body: issue.Body,
|
||||
Labels: labelSlice,
|
||||
Assignees: assigneesSlice,
|
||||
Labels: buildDetailLabels(issue.Labels),
|
||||
Assignees: buildDetailAssignees(issue.Assignees),
|
||||
URL: issue.HTMLURL,
|
||||
ClosedAt: issue.Closed,
|
||||
Comments: make([]commentData, 0),
|
||||
Comments: buildDetailComments(comments),
|
||||
}
|
||||
|
||||
if ctx.Bool("comments") {
|
||||
comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, issue.Index, opts)
|
||||
issueSlice.Comments = make([]commentData, 0, len(comments))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
issueSlice.Comments = append(issueSlice.Comments, commentData{
|
||||
ID: comment.ID,
|
||||
Author: comment.Poster.UserName,
|
||||
Body: comment.Body, // Selected Field
|
||||
Created: comment.Created,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(issueSlice, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(ctx.Writer, "%s\n", jsonData)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,8 +31,13 @@ var CmdIssuesClose = cli.Command{
|
||||
|
||||
// editIssueState abstracts the arg parsing to edit the given issue
|
||||
func editIssueState(_ stdctx.Context, cmd *cli.Command, opts gitea.EditIssueOption) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Args().Len() == 0 {
|
||||
return fmt.Errorf("missing required argument: %s", ctx.Command.ArgsUsage)
|
||||
}
|
||||
|
||||
@@ -26,8 +26,13 @@ var CmdIssuesCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runIssuesCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.IsInteractiveMode() {
|
||||
err := interact.CreateIssue(ctx.Login, ctx.Owner, ctx.Repo)
|
||||
|
||||
@@ -30,8 +30,13 @@ use an empty string (eg. --milestone "").`,
|
||||
}
|
||||
|
||||
func runIssuesEdit(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmd.Args().Present() {
|
||||
return fmt.Errorf("must specify at least one issue index")
|
||||
|
||||
@@ -33,7 +33,10 @@ var CmdIssuesList = cli.Command{
|
||||
|
||||
// RunIssuesList list issues
|
||||
func RunIssuesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := flags.ParseState(ctx.String("state"))
|
||||
if err != nil {
|
||||
@@ -69,7 +72,7 @@ func RunIssuesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
var issues []*gitea.Issue
|
||||
if ctx.Repo != "" {
|
||||
issues, _, err = ctx.Login.Client().ListRepoIssues(owner, ctx.Repo, gitea.ListIssueOption{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
State: state,
|
||||
Type: kind,
|
||||
KeyWord: ctx.String("keyword"),
|
||||
@@ -86,7 +89,7 @@ func RunIssuesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
} else {
|
||||
issues, _, err = ctx.Login.Client().ListIssues(gitea.ListIssueOption{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
State: state,
|
||||
Type: kind,
|
||||
KeyWord: ctx.String("keyword"),
|
||||
@@ -109,6 +112,5 @@ func RunIssuesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
print.IssuesPullsList(issues, ctx.Output, fields)
|
||||
return nil
|
||||
return print.IssuesPullsList(issues, ctx.Output, fields)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
stdctx "context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -27,6 +24,51 @@ const (
|
||||
testRepo = "testRepo"
|
||||
)
|
||||
|
||||
type fakeIssueCommentClient struct {
|
||||
owner string
|
||||
repo string
|
||||
index int64
|
||||
comments []*gitea.Comment
|
||||
}
|
||||
|
||||
func (f *fakeIssueCommentClient) ListIssueComments(owner, repo string, index int64, _ gitea.ListIssueCommentOptions) ([]*gitea.Comment, *gitea.Response, error) {
|
||||
f.owner = owner
|
||||
f.repo = repo
|
||||
f.index = index
|
||||
return f.comments, nil, nil
|
||||
}
|
||||
|
||||
type fakeIssueDetailClient struct {
|
||||
owner string
|
||||
repo string
|
||||
index int64
|
||||
issue *gitea.Issue
|
||||
reactions []*gitea.Reaction
|
||||
}
|
||||
|
||||
func (f *fakeIssueDetailClient) GetIssue(owner, repo string, index int64) (*gitea.Issue, *gitea.Response, error) {
|
||||
f.owner = owner
|
||||
f.repo = repo
|
||||
f.index = index
|
||||
return f.issue, nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeIssueDetailClient) GetIssueReactions(owner, repo string, index int64) ([]*gitea.Reaction, *gitea.Response, error) {
|
||||
f.owner = owner
|
||||
f.repo = repo
|
||||
f.index = index
|
||||
return f.reactions, nil, nil
|
||||
}
|
||||
|
||||
func toCommentPointers(comments []gitea.Comment) []*gitea.Comment {
|
||||
result := make([]*gitea.Comment, 0, len(comments))
|
||||
for i := range comments {
|
||||
comment := comments[i]
|
||||
result = append(result, &comment)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func createTestIssue(comments int, isClosed bool) gitea.Issue {
|
||||
issue := gitea.Issue{
|
||||
ID: 42,
|
||||
@@ -160,25 +202,11 @@ func TestRunIssueDetailAsJSON(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if path == fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", testOwner, testRepo, testCase.issue.Index) {
|
||||
jsonComments, err := json.Marshal(testCase.comments)
|
||||
if err != nil {
|
||||
require.NoError(t, err, "Testing setup failed: failed to marshal comments")
|
||||
client := &fakeIssueCommentClient{
|
||||
comments: toCommentPointers(testCase.comments),
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(jsonComments)
|
||||
require.NoError(t, err, "Testing setup failed: failed to write out comments")
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
|
||||
testContext.Login.URL = server.URL
|
||||
testContext.Login.URL = "https://gitea.example.com"
|
||||
testCase.issue.HTMLURL = fmt.Sprintf("%s/%s/%s/issues/%d/", testContext.Login.URL, testOwner, testRepo, testCase.issue.Index)
|
||||
|
||||
var outBuffer bytes.Buffer
|
||||
@@ -187,16 +215,19 @@ func TestRunIssueDetailAsJSON(t *testing.T) {
|
||||
testContext.ErrWriter = &errBuffer
|
||||
|
||||
if testCase.flagComments {
|
||||
_ = testContext.Command.Set("comments", "true")
|
||||
require.NoError(t, testContext.Set("comments", "true"))
|
||||
} else {
|
||||
_ = testContext.Command.Set("comments", "false")
|
||||
require.NoError(t, testContext.Set("comments", "false"))
|
||||
}
|
||||
|
||||
err := runIssueDetailAsJSON(&testContext, &testCase.issue)
|
||||
|
||||
server.Close()
|
||||
err := runIssueDetailAsJSONWithClient(&testContext, &testCase.issue, client)
|
||||
|
||||
require.NoError(t, err, "Failed to run issue detail as JSON")
|
||||
if testCase.flagComments {
|
||||
assert.Equal(t, testOwner, client.owner)
|
||||
assert.Equal(t, testRepo, client.repo)
|
||||
assert.Equal(t, testCase.issue.Index, client.index)
|
||||
}
|
||||
|
||||
out := outBuffer.String()
|
||||
|
||||
@@ -269,7 +300,7 @@ func TestRunIssueDetailUsesOwnerFlag(t *testing.T) {
|
||||
issueIndex := int64(12)
|
||||
expectedOwner := "overrideOwner"
|
||||
expectedRepo := "overrideRepo"
|
||||
issue := gitea.Issue{
|
||||
issue := &gitea.Issue{
|
||||
ID: 99,
|
||||
Index: issueIndex,
|
||||
Title: "Owner override test",
|
||||
@@ -281,34 +312,10 @@ func TestRunIssueDetailUsesOwnerFlag(t *testing.T) {
|
||||
HTMLURL: "https://example.test/issues/12",
|
||||
}
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", expectedOwner, expectedRepo, issueIndex):
|
||||
jsonIssue, err := json.Marshal(issue)
|
||||
require.NoError(t, err, "Testing setup failed: failed to marshal issue")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(jsonIssue)
|
||||
require.NoError(t, err, "Testing setup failed: failed to write issue")
|
||||
case fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", expectedOwner, expectedRepo, issueIndex):
|
||||
jsonReactions, err := json.Marshal([]gitea.Reaction{})
|
||||
require.NoError(t, err, "Testing setup failed: failed to marshal reactions")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(jsonReactions)
|
||||
require.NoError(t, err, "Testing setup failed: failed to write reactions")
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
config.SetConfigForTesting(config.LocalConfig{
|
||||
Logins: []config.Login{{
|
||||
Name: "testLogin",
|
||||
URL: server.URL,
|
||||
URL: "https://gitea.example.com",
|
||||
Token: "token",
|
||||
User: "loginUser",
|
||||
Default: true,
|
||||
@@ -333,9 +340,19 @@ func TestRunIssueDetailUsesOwnerFlag(t *testing.T) {
|
||||
require.NoError(t, cmd.Set("login", "testLogin"))
|
||||
require.NoError(t, cmd.Set("repo", expectedRepo))
|
||||
require.NoError(t, cmd.Set("owner", expectedOwner))
|
||||
require.NoError(t, cmd.Set("output", "json"))
|
||||
require.NoError(t, cmd.Set("comments", "false"))
|
||||
|
||||
err := runIssueDetail(stdctx.Background(), &cmd, fmt.Sprintf("%d", issueIndex))
|
||||
require.NoError(t, err, "Expected runIssueDetail to succeed")
|
||||
teaCtx, idx, err := resolveIssueDetailContext(&cmd, fmt.Sprintf("%d", issueIndex))
|
||||
require.NoError(t, err)
|
||||
|
||||
client := &fakeIssueDetailClient{
|
||||
issue: issue,
|
||||
reactions: []*gitea.Reaction{},
|
||||
}
|
||||
|
||||
err = runIssueDetailWithClient(teaCtx, idx, client)
|
||||
require.NoError(t, err, "Expected runIssueDetail to succeed")
|
||||
assert.Equal(t, expectedOwner, client.owner)
|
||||
assert.Equal(t, expectedRepo, client.repo)
|
||||
assert.Equal(t, issueIndex, client.index)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,13 @@ var CmdLabelCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runLabelCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labelFile := ctx.String("file")
|
||||
if len(labelFile) == 0 {
|
||||
|
||||
@@ -31,8 +31,13 @@ var CmdLabelDelete = cli.Command{
|
||||
}
|
||||
|
||||
func runLabelDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
labelID := ctx.Int64("id")
|
||||
client := ctx.Login.Client()
|
||||
|
||||
@@ -36,12 +36,17 @@ var CmdLabelsList = cli.Command{
|
||||
|
||||
// RunLabelsList list labels.
|
||||
func RunLabelsList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := ctx.Login.Client()
|
||||
labels, _, err := client.ListRepoLabels(ctx.Owner, ctx.Repo, gitea.ListLabelsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -51,6 +56,5 @@ func RunLabelsList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return task.LabelsExport(labels, ctx.String("save"))
|
||||
}
|
||||
|
||||
print.LabelsList(labels, ctx.Output)
|
||||
return nil
|
||||
return print.LabelsList(labels, ctx.Output)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,13 @@ var CmdLabelUpdate = cli.Command{
|
||||
}
|
||||
|
||||
func runLabelUpdate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id := ctx.Int64("id")
|
||||
var pName, pColor, pDescription *string
|
||||
@@ -61,7 +66,6 @@ func runLabelUpdate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
pDescription = &description
|
||||
}
|
||||
|
||||
var err error
|
||||
_, _, err = ctx.Login.Client().EditLabel(ctx.Owner, ctx.Repo, id, gitea.EditLabelOption{
|
||||
Name: pName,
|
||||
Color: pColor,
|
||||
|
||||
@@ -42,7 +42,10 @@ func runLogins(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runLoginDetail(name string) error {
|
||||
l := config.GetLoginByName(name)
|
||||
l, err := config.GetLoginByName(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if l == nil {
|
||||
fmt.Printf("Login '%s' do not exist\n\n", name)
|
||||
return nil
|
||||
|
||||
@@ -101,7 +101,11 @@ var CmdLoginHelper = cli.Command{
|
||||
// Use --login flag if provided, otherwise fall back to host lookup
|
||||
var userConfig *config.Login
|
||||
if loginName := cmd.String("login"); loginName != "" {
|
||||
userConfig = config.GetLoginByName(loginName)
|
||||
var lookupErr error
|
||||
userConfig, lookupErr = config.GetLoginByName(loginName)
|
||||
if lookupErr != nil {
|
||||
log.Fatal(lookupErr)
|
||||
}
|
||||
if userConfig == nil {
|
||||
log.Fatalf("Login '%s' not found", loginName)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,5 @@ func RunLoginList(_ context.Context, cmd *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
print.LoginsList(logins, cmd.String("output"))
|
||||
return nil
|
||||
return print.LoginsList(logins, cmd.String("output"))
|
||||
}
|
||||
|
||||
@@ -38,7 +38,10 @@ func runLoginOAuthRefresh(_ context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
// Get the login from config
|
||||
login := config.GetLoginByName(loginName)
|
||||
login, err := config.GetLoginByName(loginName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if login == nil {
|
||||
return fmt.Errorf("login '%s' not found", loginName)
|
||||
}
|
||||
@@ -49,7 +52,7 @@ func runLoginOAuthRefresh(_ context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
// Try to refresh the token
|
||||
err := auth.RefreshAccessToken(login)
|
||||
err = auth.RefreshAccessToken(login)
|
||||
if err == nil {
|
||||
fmt.Printf("Successfully refreshed OAuth token for %s\n", loginName)
|
||||
return nil
|
||||
|
||||
@@ -40,8 +40,13 @@ func runMilestones(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runMilestoneDetail(_ stdctx.Context, cmd *cli.Command, name string) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
milestone, _, err := client.GetMilestoneByName(ctx.Owner, ctx.Repo, name)
|
||||
|
||||
@@ -50,7 +50,10 @@ var CmdMilestonesCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runMilestonesCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
date := ctx.String("deadline")
|
||||
deadline := &time.Time{}
|
||||
|
||||
@@ -24,10 +24,15 @@ var CmdMilestonesDelete = cli.Command{
|
||||
}
|
||||
|
||||
func deleteMilestone(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
client := ctx.Login.Client()
|
||||
|
||||
_, err := client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First())
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
_, err = client.DeleteMilestoneByName(ctx.Owner, ctx.Repo, ctx.Args().First())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -71,8 +71,13 @@ var CmdMilestoneRemoveIssue = cli.Command{
|
||||
}
|
||||
|
||||
func runMilestoneIssueList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
state, err := flags.ParseState(ctx.String("state"))
|
||||
@@ -97,7 +102,7 @@ func runMilestoneIssueList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
issues, _, err := client.ListRepoIssues(ctx.Owner, ctx.Repo, gitea.ListIssueOption{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
Milestones: []string{milestone},
|
||||
Type: kind,
|
||||
State: state,
|
||||
@@ -110,13 +115,17 @@ func runMilestoneIssueList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
print.IssuesPullsList(issues, ctx.Output, fields)
|
||||
return nil
|
||||
return print.IssuesPullsList(issues, ctx.Output, fields)
|
||||
}
|
||||
|
||||
func runMilestoneIssueAdd(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
if ctx.Args().Len() != 2 {
|
||||
return fmt.Errorf("need two arguments")
|
||||
@@ -145,8 +154,13 @@ func runMilestoneIssueAdd(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runMilestoneIssueRemove(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
if ctx.Args().Len() != 2 {
|
||||
return fmt.Errorf("need two arguments")
|
||||
|
||||
@@ -40,8 +40,13 @@ var CmdMilestonesList = cli.Command{
|
||||
|
||||
// RunMilestonesList list milestones
|
||||
func RunMilestonesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields, err := fieldsFlag.GetValues(cmd)
|
||||
if err != nil {
|
||||
@@ -58,13 +63,12 @@ func RunMilestonesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
|
||||
client := ctx.Login.Client()
|
||||
milestones, _, err := client.ListRepoMilestones(ctx.Owner, ctx.Repo, gitea.ListMilestoneOption{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
State: state,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.MilestonesList(milestones, ctx.Output, fields)
|
||||
return nil
|
||||
return print.MilestonesList(milestones, ctx.Output, fields)
|
||||
}
|
||||
|
||||
@@ -29,8 +29,13 @@ var CmdMilestonesReopen = cli.Command{
|
||||
}
|
||||
|
||||
func editMilestoneStatus(_ stdctx.Context, cmd *cli.Command, close bool) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Args().Len() == 0 {
|
||||
return fmt.Errorf("missing required argument: %s", ctx.Command.ArgsUsage)
|
||||
}
|
||||
@@ -41,6 +46,13 @@ func editMilestoneStatus(_ stdctx.Context, cmd *cli.Command, close bool) error {
|
||||
}
|
||||
|
||||
client := ctx.Login.Client()
|
||||
repoURL := ""
|
||||
if ctx.Args().Len() > 1 {
|
||||
repoURL, err = ctx.GetRemoteRepoHTMLURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, ms := range ctx.Args().Slice() {
|
||||
opts := gitea.EditMilestoneOption{
|
||||
State: &state,
|
||||
@@ -52,7 +64,7 @@ func editMilestoneStatus(_ stdctx.Context, cmd *cli.Command, close bool) error {
|
||||
}
|
||||
|
||||
if ctx.Args().Len() > 1 {
|
||||
fmt.Printf("%s/milestone/%d\n", ctx.GetRemoteRepoHTMLURL(), milestone.ID)
|
||||
fmt.Printf("%s/milestone/%d\n", repoURL, milestone.ID)
|
||||
} else {
|
||||
print.MilestoneDetails(milestone)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package notifications
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"log"
|
||||
|
||||
"code.gitea.io/tea/cmd/flags"
|
||||
"code.gitea.io/tea/modules/context"
|
||||
@@ -64,12 +63,15 @@ func listNotifications(_ stdctx.Context, cmd *cli.Command, status []gitea.Notify
|
||||
var news []*gitea.NotificationThread
|
||||
var err error
|
||||
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
all := ctx.Bool("mine")
|
||||
|
||||
// This enforces pagination (see https://github.com/go-gitea/gitea/issues/16733)
|
||||
listOpts := flags.GetListOptions()
|
||||
listOpts := flags.GetListOptions(cmd)
|
||||
if listOpts.Page == 0 {
|
||||
listOpts.Page = 1
|
||||
}
|
||||
@@ -91,7 +93,9 @@ func listNotifications(_ stdctx.Context, cmd *cli.Command, status []gitea.Notify
|
||||
SubjectTypes: subjects,
|
||||
})
|
||||
} else {
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
news, _, err = client.ListRepoNotifications(ctx.Owner, ctx.Repo, gitea.ListNotificationOptions{
|
||||
ListOptions: listOpts,
|
||||
Status: status,
|
||||
@@ -99,9 +103,8 @@ func listNotifications(_ stdctx.Context, cmd *cli.Command, status []gitea.Notify
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
print.NotificationsList(news, ctx.Output, fields)
|
||||
return nil
|
||||
return print.NotificationsList(news, ctx.Output, fields)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ var CmdNotificationsMarkRead = cli.Command{
|
||||
ArgsUsage: "[all | <notification id>]",
|
||||
Flags: flags.NotificationFlags,
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter, err := flags.NotificationStateFlag.GetValues(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -44,7 +47,10 @@ var CmdNotificationsMarkUnread = cli.Command{
|
||||
ArgsUsage: "[all | <notification id>]",
|
||||
Flags: flags.NotificationFlags,
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter, err := flags.NotificationStateFlag.GetValues(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -65,7 +71,10 @@ var CmdNotificationsMarkPinned = cli.Command{
|
||||
ArgsUsage: "[all | <notification id>]",
|
||||
Flags: flags.NotificationFlags,
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter, err := flags.NotificationStateFlag.GetValues(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -85,7 +94,10 @@ var CmdNotificationsUnpin = cli.Command{
|
||||
ArgsUsage: "[all | <notification id>]",
|
||||
Flags: flags.NotificationFlags,
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := []string{string(gitea.NotifyStatusPinned)}
|
||||
// NOTE: we implicitly mark it as read, to match web UI semantics. marking as unread might be more useful?
|
||||
return markNotificationAs(ctx, filter, gitea.NotifyStatusRead)
|
||||
@@ -109,7 +121,9 @@ func markNotificationAs(cmd *context.TeaContext, filterStates []string, targetSt
|
||||
if allRepos {
|
||||
_, _, err = client.ReadNotifications(opts)
|
||||
} else {
|
||||
cmd.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
if err := cmd.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err = client.ReadRepoNotifications(cmd.Owner, cmd.Repo, opts)
|
||||
}
|
||||
|
||||
|
||||
16
cmd/open.go
16
cmd/open.go
@@ -28,8 +28,13 @@ var CmdOpen = cli.Command{
|
||||
}
|
||||
|
||||
func runOpen(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var suffix string
|
||||
number := ctx.Args().Get(0)
|
||||
@@ -74,5 +79,10 @@ func runOpen(_ stdctx.Context, cmd *cli.Command) error {
|
||||
suffix = number
|
||||
}
|
||||
|
||||
return open.Run(path.Join(ctx.GetRemoteRepoHTMLURL(), suffix))
|
||||
repoURL, err := ctx.GetRemoteRepoHTMLURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return open.Run(path.Join(repoURL, suffix))
|
||||
}
|
||||
|
||||
@@ -31,7 +31,10 @@ var CmdOrgs = cli.Command{
|
||||
}
|
||||
|
||||
func runOrganizations(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
teaCtx := context.InitCommand(cmd)
|
||||
teaCtx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if teaCtx.Args().Len() == 1 {
|
||||
return runOrganizationDetail(teaCtx)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,10 @@ var CmdOrganizationCreate = cli.Command{
|
||||
|
||||
// RunOrganizationCreate sets up a new organization
|
||||
func RunOrganizationCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Args().Len() < 1 {
|
||||
return fmt.Errorf("organization name is required")
|
||||
|
||||
@@ -28,7 +28,10 @@ var CmdOrganizationDelete = cli.Command{
|
||||
|
||||
// RunOrganizationDelete delete user organization
|
||||
func RunOrganizationDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := ctx.Login.Client()
|
||||
|
||||
|
||||
@@ -29,17 +29,18 @@ var CmdOrganizationList = cli.Command{
|
||||
|
||||
// RunOrganizationList list user organizations
|
||||
func RunOrganizationList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
userOrganizations, _, err := client.ListUserOrgs(ctx.Login.User, gitea.ListOrgsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.OrganizationsList(userOrganizations, ctx.Output)
|
||||
|
||||
return nil
|
||||
return print.OrganizationsList(userOrganizations, ctx.Output)
|
||||
}
|
||||
|
||||
81
cmd/pulls.go
81
cmd/pulls.go
@@ -5,7 +5,6 @@ package cmd
|
||||
|
||||
import (
|
||||
stdctx "context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -20,26 +19,11 @@ import (
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type pullLabelData struct {
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
type pullLabelData = detailLabelData
|
||||
|
||||
type pullReviewData struct {
|
||||
ID int64 `json:"id"`
|
||||
Reviewer string `json:"reviewer"`
|
||||
State gitea.ReviewStateType `json:"state"`
|
||||
Body string `json:"body"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
type pullReviewData = detailReviewData
|
||||
|
||||
type pullCommentData struct {
|
||||
ID int64 `json:"id"`
|
||||
Author string `json:"author"`
|
||||
Created time.Time `json:"created"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
type pullCommentData = detailCommentData
|
||||
|
||||
type pullData struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -103,8 +87,13 @@ func runPulls(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runPullDetail(_ stdctx.Context, cmd *cli.Command, index string) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
idx, err := utils.ArgToIndex(index)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -149,28 +138,7 @@ func runPullDetail(_ stdctx.Context, cmd *cli.Command, index string) error {
|
||||
|
||||
func runPullDetailAsJSON(ctx *context.TeaContext, pr *gitea.PullRequest, reviews []*gitea.PullReview) error {
|
||||
c := ctx.Login.Client()
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions()}
|
||||
|
||||
labelSlice := make([]pullLabelData, 0, len(pr.Labels))
|
||||
for _, label := range pr.Labels {
|
||||
labelSlice = append(labelSlice, pullLabelData{label.Name, label.Color, label.Description})
|
||||
}
|
||||
|
||||
assigneesSlice := make([]string, 0, len(pr.Assignees))
|
||||
for _, assignee := range pr.Assignees {
|
||||
assigneesSlice = append(assigneesSlice, assignee.UserName)
|
||||
}
|
||||
|
||||
reviewsSlice := make([]pullReviewData, 0, len(reviews))
|
||||
for _, review := range reviews {
|
||||
reviewsSlice = append(reviewsSlice, pullReviewData{
|
||||
ID: review.ID,
|
||||
Reviewer: review.Reviewer.UserName,
|
||||
State: review.State,
|
||||
Body: review.Body,
|
||||
Created: review.Submitted,
|
||||
})
|
||||
}
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions(ctx.Command)}
|
||||
|
||||
mergedBy := ""
|
||||
if pr.MergedBy != nil {
|
||||
@@ -184,10 +152,10 @@ func runPullDetailAsJSON(ctx *context.TeaContext, pr *gitea.PullRequest, reviews
|
||||
State: pr.State,
|
||||
Created: pr.Created,
|
||||
Updated: pr.Updated,
|
||||
User: pr.Poster.UserName,
|
||||
User: username(pr.Poster),
|
||||
Body: pr.Body,
|
||||
Labels: labelSlice,
|
||||
Assignees: assigneesSlice,
|
||||
Labels: buildDetailLabels(pr.Labels),
|
||||
Assignees: buildDetailAssignees(pr.Assignees),
|
||||
URL: pr.HTMLURL,
|
||||
Base: pr.Base.Ref,
|
||||
Head: pr.Head.Ref,
|
||||
@@ -198,7 +166,7 @@ func runPullDetailAsJSON(ctx *context.TeaContext, pr *gitea.PullRequest, reviews
|
||||
MergedAt: pr.Merged,
|
||||
MergedBy: mergedBy,
|
||||
ClosedAt: pr.Closed,
|
||||
Reviews: reviewsSlice,
|
||||
Reviews: buildDetailReviews(reviews),
|
||||
Comments: make([]pullCommentData, 0),
|
||||
}
|
||||
|
||||
@@ -208,23 +176,8 @@ func runPullDetailAsJSON(ctx *context.TeaContext, pr *gitea.PullRequest, reviews
|
||||
return err
|
||||
}
|
||||
|
||||
pullSlice.Comments = make([]pullCommentData, 0, len(comments))
|
||||
for _, comment := range comments {
|
||||
pullSlice.Comments = append(pullSlice.Comments, pullCommentData{
|
||||
ID: comment.ID,
|
||||
Author: comment.Poster.UserName,
|
||||
Body: comment.Body,
|
||||
Created: comment.Created,
|
||||
})
|
||||
}
|
||||
pullSlice.Comments = buildDetailComments(comments)
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(pullSlice, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(ctx.Writer, "%s\n", jsonData)
|
||||
|
||||
return err
|
||||
return writeIndentedJSON(ctx.Writer, pullSlice)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ var CmdPullsApprove = cli.Command{
|
||||
Description: "Approve a pull request",
|
||||
ArgsUsage: "<pull index> [<comment>]",
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runPullReview(ctx, gitea.ReviewStateApproved, false)
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
|
||||
@@ -34,11 +34,16 @@ var CmdPullsCheckout = cli.Command{
|
||||
}
|
||||
|
||||
func runPullsCheckout(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{
|
||||
LocalRepo: true,
|
||||
RemoteRepo: true,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("pull request index is required")
|
||||
}
|
||||
|
||||
@@ -32,8 +32,13 @@ var CmdPullsClean = cli.Command{
|
||||
}
|
||||
|
||||
func runPullsClean(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{LocalRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{LocalRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("pull request index is required")
|
||||
}
|
||||
|
||||
@@ -49,11 +49,16 @@ var CmdPullsCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runPullsCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{
|
||||
LocalRepo: true,
|
||||
RemoteRepo: true,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// no args -> interactive mode
|
||||
if ctx.IsInteractiveMode() {
|
||||
|
||||
@@ -17,8 +17,13 @@ import (
|
||||
|
||||
// editPullState abstracts the arg parsing to edit the given pull request
|
||||
func editPullState(_ stdctx.Context, cmd *cli.Command, opts gitea.EditPullRequestOption) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Args().Len() == 0 {
|
||||
return fmt.Errorf("pull request index is required")
|
||||
}
|
||||
|
||||
@@ -30,8 +30,13 @@ var CmdPullsList = cli.Command{
|
||||
|
||||
// RunPullsList return list of pulls
|
||||
func RunPullsList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := flags.ParseState(ctx.String("state"))
|
||||
if err != nil {
|
||||
@@ -39,7 +44,7 @@ func RunPullsList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
prs, _, err := ctx.Login.Client().ListRepoPullRequests(ctx.Owner, ctx.Repo, gitea.ListPullRequestsOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
State: state,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -51,6 +56,5 @@ func RunPullsList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
print.PullsList(prs, ctx.Output, fields)
|
||||
return nil
|
||||
return print.PullsList(prs, ctx.Output, fields)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,13 @@ var CmdPullsMerge = cli.Command{
|
||||
},
|
||||
}, flags.AllDefaultFlags...),
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Args().Len() != 1 {
|
||||
// If no PR index is provided, try interactive mode
|
||||
|
||||
@@ -19,7 +19,10 @@ var CmdPullsReject = cli.Command{
|
||||
Description: "Request changes to a pull request",
|
||||
ArgsUsage: "<pull index> <reason>",
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return runPullReview(ctx, gitea.ReviewStateRequestChanges, true)
|
||||
},
|
||||
Flags: flags.AllDefaultFlags,
|
||||
|
||||
@@ -22,8 +22,13 @@ var CmdPullsReview = cli.Command{
|
||||
Description: "Interactively review a pull request",
|
||||
ArgsUsage: "<pull index>",
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Args().Len() != 1 {
|
||||
return fmt.Errorf("must specify a PR index")
|
||||
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
|
||||
// runPullReview handles the common logic for approving/rejecting pull requests
|
||||
func runPullReview(ctx *context.TeaContext, state gitea.ReviewStateType, requireComment bool) error {
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minArgs := 1
|
||||
if requireComment {
|
||||
|
||||
@@ -68,8 +68,13 @@ var CmdReleaseCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runReleaseCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tag := ctx.String("tag")
|
||||
if cmd.Args().Present() {
|
||||
|
||||
@@ -35,8 +35,13 @@ var CmdReleaseDelete = cli.Command{
|
||||
}
|
||||
|
||||
func runReleaseDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
if !ctx.Args().Present() {
|
||||
|
||||
@@ -58,8 +58,13 @@ var CmdReleaseEdit = cli.Command{
|
||||
}
|
||||
|
||||
func runReleaseEdit(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
var isDraft, isPre *bool
|
||||
|
||||
@@ -31,18 +31,22 @@ var CmdReleaseList = cli.Command{
|
||||
|
||||
// RunReleasesList list releases
|
||||
func RunReleasesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
releases, _, err := ctx.Login.Client().ListReleases(ctx.Owner, ctx.Repo, gitea.ListReleasesOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.ReleasesList(releases, ctx.Output)
|
||||
return nil
|
||||
return print.ReleasesList(releases, ctx.Output)
|
||||
}
|
||||
|
||||
func getReleaseByTag(owner, repo, tag string, client *gitea.Client) (*gitea.Release, error) {
|
||||
|
||||
@@ -45,7 +45,10 @@ func runRepos(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runRepoDetail(_ stdctx.Context, cmd *cli.Command, path string) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
repoOwner, repoName := utils.GetOwnerAndRepo(path, ctx.Owner)
|
||||
repo, _, err := client.GetRepo(repoOwner, repoName)
|
||||
|
||||
@@ -103,11 +103,13 @@ var CmdRepoCreate = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoCreate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
var (
|
||||
repo *gitea.Repository
|
||||
err error
|
||||
trustmodel gitea.TrustModel
|
||||
)
|
||||
|
||||
|
||||
@@ -83,7 +83,10 @@ var CmdRepoCreateFromTemplate = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoCreateFromTemplate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
templateOwner, templateRepo := utils.GetOwnerAndRepo(ctx.String("template"), ctx.Login.User)
|
||||
|
||||
@@ -46,7 +46,10 @@ var CmdRepoRm = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := ctx.Login.Client()
|
||||
|
||||
@@ -76,7 +79,7 @@ func runRepoDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := client.DeleteRepo(owner, repoName)
|
||||
_, err = client.DeleteRepo(owner, repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -60,8 +60,13 @@ var CmdRepoEdit = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoEdit(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
opts := gitea.EditRepoOption{}
|
||||
|
||||
@@ -33,8 +33,13 @@ var CmdRepoFork = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoFork(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
opts := gitea.CreateForkOption{}
|
||||
|
||||
@@ -58,7 +58,10 @@ var CmdReposList = cli.Command{
|
||||
|
||||
// RunReposList list repositories
|
||||
func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
teaCmd := context.InitCommand(cmd)
|
||||
teaCmd, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := teaCmd.Login.Client()
|
||||
|
||||
typeFilter, err := getTypeFilter(cmd)
|
||||
@@ -76,11 +79,11 @@ func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
// not an org, treat as user
|
||||
rps, _, err = client.ListUserRepos(owner, gitea.ListReposOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
} else {
|
||||
rps, _, err = client.ListOrgRepos(owner, gitea.ListOrgReposOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
@@ -92,7 +95,7 @@ func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
rps, _, err = client.SearchRepos(gitea.SearchRepoOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
StarredByUserID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -105,11 +108,11 @@ func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rps = paginateRepos(allRepos, flags.GetListOptions())
|
||||
rps = paginateRepos(allRepos, flags.GetListOptions(cmd))
|
||||
} else {
|
||||
var err error
|
||||
rps, _, err = client.ListMyRepos(gitea.ListReposOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -126,8 +129,7 @@ func RunReposList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
print.ReposList(reposFiltered, teaCmd.Output, fields)
|
||||
return nil
|
||||
return print.ReposList(reposFiltered, teaCmd.Output, fields)
|
||||
}
|
||||
|
||||
func filterReposByType(repos []*gitea.Repository, t gitea.RepoType) []*gitea.Repository {
|
||||
|
||||
@@ -109,11 +109,13 @@ var CmdRepoMigrate = cli.Command{
|
||||
}
|
||||
|
||||
func runRepoMigrate(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
var (
|
||||
repo *gitea.Repository
|
||||
err error
|
||||
service gitea.GitServiceType
|
||||
)
|
||||
|
||||
|
||||
@@ -58,7 +58,10 @@ var CmdReposSearch = cli.Command{
|
||||
}
|
||||
|
||||
func runReposSearch(_ stdctx.Context, cmd *cli.Command) error {
|
||||
teaCmd := context.InitCommand(cmd)
|
||||
teaCmd, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := teaCmd.Login.Client()
|
||||
|
||||
var ownerID int64
|
||||
@@ -109,7 +112,7 @@ func runReposSearch(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
rps, _, err := client.SearchRepos(gitea.SearchRepoOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
OwnerID: ownerID,
|
||||
IsPrivate: isPrivate,
|
||||
IsArchived: isArchived,
|
||||
@@ -127,6 +130,5 @@ func runReposSearch(_ stdctx.Context, cmd *cli.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
print.ReposList(rps, teaCmd.Output, fields)
|
||||
return nil
|
||||
return print.ReposList(rps, teaCmd.Output, fields)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,13 @@ var CmdTrackedTimesAdd = cli.Command{
|
||||
}
|
||||
|
||||
func runTrackedTimesAdd(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText)
|
||||
|
||||
@@ -26,8 +26,13 @@ var CmdTrackedTimesDelete = cli.Command{
|
||||
}
|
||||
|
||||
func runTrackedTimesDelete(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
if ctx.Args().Len() < 2 {
|
||||
|
||||
@@ -72,12 +72,16 @@ Depending on your permissions on the repository, only your own tracked times mig
|
||||
|
||||
// RunTimesList list repositories
|
||||
func RunTimesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
var times []*gitea.TrackedTime
|
||||
var err error
|
||||
var from, until time.Time
|
||||
var fields []string
|
||||
|
||||
@@ -95,7 +99,7 @@ func RunTimesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
opts := gitea.ListTrackedTimesOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
Since: from,
|
||||
Before: until,
|
||||
}
|
||||
@@ -133,6 +137,5 @@ func RunTimesList(_ stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
print.TrackedTimesList(times, ctx.Output, fields, ctx.Bool("total"))
|
||||
return nil
|
||||
return print.TrackedTimesList(times, ctx.Output, fields, ctx.Bool("total"))
|
||||
}
|
||||
|
||||
@@ -25,8 +25,13 @@ var CmdTrackedTimesReset = cli.Command{
|
||||
}
|
||||
|
||||
func runTrackedTimesReset(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx.Ensure(context.CtxRequirement{RemoteRepo: true})
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
if ctx.Args().Len() != 1 {
|
||||
|
||||
@@ -64,7 +64,10 @@ func runWebhooksDefault(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
func runWebhookDetail(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
|
||||
webhookID, err := utils.ArgToIndex(cmd.Args().First())
|
||||
|
||||
@@ -59,7 +59,10 @@ func runWebhooksCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("webhook URL is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
webhookType := gitea.HookType(cmd.String("type"))
|
||||
@@ -95,7 +98,6 @@ func runWebhooksCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
var hook *gitea.Hook
|
||||
var err error
|
||||
if c.IsGlobal {
|
||||
return fmt.Errorf("global webhooks not yet supported in this version")
|
||||
} else if len(c.Org) > 0 {
|
||||
|
||||
@@ -37,7 +37,10 @@ func runWebhooksDelete(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("webhook ID is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
webhookID, err := utils.ArgToIndex(cmd.Args().First())
|
||||
|
||||
@@ -30,26 +30,27 @@ var CmdWebhooksList = cli.Command{
|
||||
|
||||
// RunWebhooksList list webhooks
|
||||
func RunWebhooksList(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
var hooks []*gitea.Hook
|
||||
var err error
|
||||
if c.IsGlobal {
|
||||
return fmt.Errorf("global webhooks not yet supported in this version")
|
||||
} else if len(c.Org) > 0 {
|
||||
hooks, _, err = client.ListOrgHooks(c.Org, gitea.ListHooksOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
} else {
|
||||
hooks, _, err = client.ListRepoHooks(c.Owner, c.Repo, gitea.ListHooksOptions{
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(cmd),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
print.WebhooksList(hooks, c.Output)
|
||||
return nil
|
||||
return print.WebhooksList(hooks, c.Output)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,10 @@ func runWebhooksUpdate(ctx stdctx.Context, cmd *cli.Command) error {
|
||||
return fmt.Errorf("webhook ID is required")
|
||||
}
|
||||
|
||||
c := context.InitCommand(cmd)
|
||||
c, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := c.Login.Client()
|
||||
|
||||
webhookID, err := utils.ArgToIndex(cmd.Args().First())
|
||||
|
||||
@@ -19,7 +19,10 @@ var CmdWhoami = cli.Command{
|
||||
Usage: "Show current logged in user",
|
||||
ArgsUsage: " ", // command does not accept arguments
|
||||
Action: func(_ stdctx.Context, cmd *cli.Command) error {
|
||||
ctx := context.InitCommand(cmd)
|
||||
ctx, err := context.InitCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := ctx.Login.Client()
|
||||
user, _, _ := client.GetMyUserInfo()
|
||||
print.UserDetails(user)
|
||||
|
||||
5
main.go
5
main.go
@@ -6,10 +6,12 @@ package main // import "code.gitea.io/tea"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"code.gitea.io/tea/cmd"
|
||||
teacontext "code.gitea.io/tea/modules/context"
|
||||
"code.gitea.io/tea/modules/debug"
|
||||
)
|
||||
|
||||
@@ -18,6 +20,9 @@ func main() {
|
||||
app.Flags = append(app.Flags, debug.CliFlag())
|
||||
err := app.Run(context.Background(), preprocessArgs(os.Args))
|
||||
if err != nil {
|
||||
if errors.Is(err, teacontext.ErrCommandCanceled) {
|
||||
os.Exit(0)
|
||||
}
|
||||
// app.Run already exits for errors implementing ErrorCoder,
|
||||
// so we only handle generic errors with code 1 here.
|
||||
fmt.Fprintf(app.ErrWriter, "Error: %v\n", err)
|
||||
|
||||
@@ -42,10 +42,19 @@ var (
|
||||
// config contain if loaded local tea config
|
||||
config LocalConfig
|
||||
loadConfigOnce sync.Once
|
||||
configPathMu sync.Mutex
|
||||
configPathTestOverride string
|
||||
)
|
||||
|
||||
// GetConfigPath return path to tea config file
|
||||
func GetConfigPath() string {
|
||||
configPathMu.Lock()
|
||||
override := configPathTestOverride
|
||||
configPathMu.Unlock()
|
||||
if override != "" {
|
||||
return override
|
||||
}
|
||||
|
||||
configFilePath, err := xdg.ConfigFile("tea/config.yml")
|
||||
|
||||
var exists bool
|
||||
@@ -71,6 +80,13 @@ func GetConfigPath() string {
|
||||
return configFilePath
|
||||
}
|
||||
|
||||
// SetConfigPathForTesting overrides the config path used by helpers in tests.
|
||||
func SetConfigPathForTesting(path string) {
|
||||
configPathMu.Lock()
|
||||
configPathTestOverride = path
|
||||
configPathMu.Unlock()
|
||||
}
|
||||
|
||||
// GetPreferences returns preferences based on the config file
|
||||
func GetPreferences() Preferences {
|
||||
_ = loadConfig()
|
||||
|
||||
@@ -72,28 +72,36 @@ func TestConfigLock_MutexProtection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func useTempConfigPath(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "config.yml")
|
||||
SetConfigPathForTesting(configPath)
|
||||
t.Cleanup(func() {
|
||||
SetConfigPathForTesting("")
|
||||
})
|
||||
|
||||
return configPath
|
||||
}
|
||||
|
||||
func TestReloadConfigFromDisk(t *testing.T) {
|
||||
configPath := useTempConfigPath(t)
|
||||
|
||||
// Save original config state
|
||||
originalConfig := config
|
||||
|
||||
// Create a temp config file
|
||||
tmpDir, err := os.MkdirTemp("", "tea-reload-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
config = LocalConfig{Logins: []Login{{Name: "stale"}}}
|
||||
if err := os.WriteFile(configPath, []byte("logins:\n - name: test\n"), 0o600); err != nil {
|
||||
t.Fatalf("failed to write temp config: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// We can't easily change GetConfigPath, so we test that reloadConfigFromDisk
|
||||
// handles a missing file gracefully (returns nil and resets config)
|
||||
config = LocalConfig{Logins: []Login{{Name: "test"}}}
|
||||
|
||||
// Call reload - since the actual config path likely exists or doesn't,
|
||||
// we just verify it doesn't panic and returns without error or with expected error
|
||||
err = reloadConfigFromDisk()
|
||||
// The function should either succeed or return an error, not panic
|
||||
err := reloadConfigFromDisk()
|
||||
if err != nil {
|
||||
// This is acceptable - config file might not exist in test environment
|
||||
t.Logf("reloadConfigFromDisk returned error (expected in test env): %v", err)
|
||||
t.Fatalf("reloadConfigFromDisk returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(config.Logins) != 1 || config.Logins[0].Name != "test" {
|
||||
t.Fatalf("expected config to reload test login, got %+v", config.Logins)
|
||||
}
|
||||
|
||||
// Restore original config
|
||||
@@ -101,6 +109,8 @@ func TestReloadConfigFromDisk(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWithConfigLock(t *testing.T) {
|
||||
useTempConfigPath(t)
|
||||
|
||||
executed := false
|
||||
err := withConfigLock(func() error {
|
||||
executed = true
|
||||
@@ -115,6 +125,8 @@ func TestWithConfigLock(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWithConfigLock_PropagatesError(t *testing.T) {
|
||||
useTempConfigPath(t)
|
||||
|
||||
expectedErr := fmt.Errorf("test error")
|
||||
err := withConfigLock(func() error {
|
||||
return expectedErr
|
||||
@@ -126,6 +138,8 @@ func TestWithConfigLock_PropagatesError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDoubleCheckedLocking_SimulatedRefresh(t *testing.T) {
|
||||
useTempConfigPath(t)
|
||||
|
||||
// This test simulates the double-checked locking pattern
|
||||
// by having multiple goroutines try to "refresh" simultaneously
|
||||
|
||||
|
||||
@@ -164,18 +164,17 @@ func SetDefaultLogin(name string) error {
|
||||
}
|
||||
|
||||
// GetLoginByName get login by name (case insensitive)
|
||||
func GetLoginByName(name string) *Login {
|
||||
err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
func GetLoginByName(name string) (*Login, error) {
|
||||
if err := loadConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, l := range config.Logins {
|
||||
if strings.EqualFold(l.Name, name) {
|
||||
return &l
|
||||
for i := range config.Logins {
|
||||
if strings.EqualFold(config.Logins[i].Name, name) {
|
||||
return &config.Logins[i], nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetLoginByToken get login by token
|
||||
|
||||
@@ -6,9 +6,8 @@ package context
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/git"
|
||||
@@ -23,6 +22,9 @@ import (
|
||||
|
||||
var errNotAGiteaRepo = errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
||||
|
||||
// ErrCommandCanceled is returned when the user explicitly cancels an interactive prompt.
|
||||
var ErrCommandCanceled = errors.New("command canceled")
|
||||
|
||||
// TeaContext contains all context derived during command initialization and wraps cli.Context
|
||||
type TeaContext struct {
|
||||
*cli.Command
|
||||
@@ -38,9 +40,11 @@ type TeaContext struct {
|
||||
|
||||
// GetRemoteRepoHTMLURL returns the web-ui url of the remote repo,
|
||||
// after ensuring a remote repo is present in the context.
|
||||
func (ctx *TeaContext) GetRemoteRepoHTMLURL() string {
|
||||
ctx.Ensure(CtxRequirement{RemoteRepo: true})
|
||||
return path.Join(ctx.Login.URL, ctx.Owner, ctx.Repo)
|
||||
func (ctx *TeaContext) GetRemoteRepoHTMLURL() (string, error) {
|
||||
if err := ctx.Ensure(CtxRequirement{RemoteRepo: true}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimRight(ctx.Login.URL, "/") + "/" + ctx.Owner + "/" + ctx.Repo, nil
|
||||
}
|
||||
|
||||
// IsInteractiveMode returns true if the command is running in interactive mode
|
||||
@@ -57,7 +61,7 @@ func shouldPromptFallbackLogin(login *config.Login, canPrompt bool) bool {
|
||||
// available the repo slug. It does this by reading the config file for logins, parsing
|
||||
// the remotes of the .git repo specified in repoFlag or $PWD, and using overrides from
|
||||
// command flags. If a local git repo can't be found, repo slug values are unset.
|
||||
func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
func InitCommand(cmd *cli.Command) (*TeaContext, error) {
|
||||
// these flags are used as overrides to the context detection via local git repo
|
||||
repoFlag := cmd.String("repo")
|
||||
loginFlag := cmd.String("login")
|
||||
@@ -75,7 +79,7 @@ func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
// check if repoFlag can be interpreted as path to local repo.
|
||||
if len(repoFlag) != 0 {
|
||||
if repoFlagPathExists, err = utils.DirExists(repoFlag); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if repoFlagPathExists {
|
||||
repoPath = repoFlag
|
||||
@@ -88,7 +92,7 @@ func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
|
||||
if repoPath == "" {
|
||||
if repoPath, err = os.Getwd(); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +101,7 @@ func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
envLogin := GetLoginByEnvVar()
|
||||
if envLogin != nil {
|
||||
if _, err := utils.ValidateAuthenticationMethod(envLogin.URL, envLogin.Token, "", "", false, "", ""); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
extraLogins = append(extraLogins, *envLogin)
|
||||
}
|
||||
@@ -108,7 +112,7 @@ func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
if err == errNotAGiteaRepo || err == gogit.ErrRepositoryNotExists {
|
||||
// we can deal with that, commands needing the optional values use ctx.Ensure()
|
||||
} else {
|
||||
log.Fatal(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,20 +129,20 @@ func InitCommand(cmd *cli.Command) *TeaContext {
|
||||
|
||||
// override login from flag, or use default login if repo based detection failed
|
||||
if len(loginFlag) != 0 {
|
||||
c.Login = config.GetLoginByName(loginFlag)
|
||||
if c.Login, err = config.GetLoginByName(loginFlag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Login == nil {
|
||||
log.Fatalf("Login name '%s' does not exist", loginFlag)
|
||||
return nil, fmt.Errorf("login name '%s' does not exist", loginFlag)
|
||||
}
|
||||
} else if c.Login == nil {
|
||||
if c.Login, err = config.GetDefaultLogin(); err != nil {
|
||||
if err.Error() == "No available login" {
|
||||
// TODO: maybe we can directly start interact.CreateLogin() (only if
|
||||
// we're sure we can interactively!), as gh cli does.
|
||||
fmt.Println(`No gitea login configured. To start using tea, first run
|
||||
return nil, fmt.Errorf(`no gitea login configured. To start using tea, first run
|
||||
tea login add
|
||||
and then run your command again.`)
|
||||
and then run your command again`)
|
||||
}
|
||||
os.Exit(1)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only prompt for confirmation if the fallback login is not explicitly set as default
|
||||
@@ -150,10 +154,10 @@ and then run your command again.`)
|
||||
Value(&fallback).
|
||||
WithTheme(theme.GetTheme()).
|
||||
Run(); err != nil {
|
||||
log.Fatalf("Get confirm failed: %v", err)
|
||||
return nil, fmt.Errorf("get confirm failed: %w", err)
|
||||
}
|
||||
if !fallback {
|
||||
os.Exit(1)
|
||||
return nil, ErrCommandCanceled
|
||||
}
|
||||
} else if !c.Login.Default {
|
||||
fmt.Fprintf(os.Stderr, "NOTE: no gitea login detected, falling back to login '%s' in non-interactive mode.\n", c.Login.Name)
|
||||
@@ -166,5 +170,5 @@ and then run your command again.`)
|
||||
c.IsGlobal = globalFlag
|
||||
c.Command = cmd
|
||||
c.Output = cmd.String("output")
|
||||
return &c
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
@@ -4,31 +4,28 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Ensure checks if requirements on the context are set, and terminates otherwise.
|
||||
func (ctx *TeaContext) Ensure(req CtxRequirement) {
|
||||
// Ensure checks if requirements on the context are set.
|
||||
func (ctx *TeaContext) Ensure(req CtxRequirement) error {
|
||||
if req.LocalRepo && ctx.LocalRepo == nil {
|
||||
fmt.Println("Local repository required: Execute from a repo dir, or specify a path with --repo.")
|
||||
os.Exit(1)
|
||||
return errors.New("local repository required: execute from a repo dir, or specify a path with --repo")
|
||||
}
|
||||
|
||||
if req.RemoteRepo && len(ctx.RepoSlug) == 0 {
|
||||
fmt.Println("Remote repository required: Specify ID via --repo or execute from a local git repo.")
|
||||
os.Exit(1)
|
||||
return errors.New("remote repository required: specify id via --repo or execute from a local git repo")
|
||||
}
|
||||
|
||||
if req.Org && len(ctx.Org) == 0 {
|
||||
fmt.Println("Organization required: Specify organization via --org.")
|
||||
os.Exit(1)
|
||||
return errors.New("organization required: specify organization via --org")
|
||||
}
|
||||
|
||||
if req.Global && !ctx.IsGlobal {
|
||||
fmt.Println("Global scope required: Specify --global.")
|
||||
os.Exit(1)
|
||||
return errors.New("global scope required: specify --global")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CtxRequirement specifies context needed for operation
|
||||
|
||||
113
modules/context/context_require_test.go
Normal file
113
modules/context/context_require_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
"code.gitea.io/tea/modules/git"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnsureReturnsRequirementErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx TeaContext
|
||||
req CtxRequirement
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "missing local repo",
|
||||
ctx: TeaContext{},
|
||||
req: CtxRequirement{LocalRepo: true},
|
||||
wantErr: "local repository required",
|
||||
},
|
||||
{
|
||||
name: "missing remote repo",
|
||||
ctx: TeaContext{},
|
||||
req: CtxRequirement{RemoteRepo: true},
|
||||
wantErr: "remote repository required",
|
||||
},
|
||||
{
|
||||
name: "missing org",
|
||||
ctx: TeaContext{},
|
||||
req: CtxRequirement{Org: true},
|
||||
wantErr: "organization required",
|
||||
},
|
||||
{
|
||||
name: "missing global scope",
|
||||
ctx: TeaContext{},
|
||||
req: CtxRequirement{Global: true},
|
||||
wantErr: "global scope required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.ctx.Ensure(tt.req)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureSucceedsWhenRequirementsMet(t *testing.T) {
|
||||
ctx := TeaContext{
|
||||
LocalRepo: &git.TeaRepo{},
|
||||
RepoSlug: "owner/repo",
|
||||
Owner: "owner",
|
||||
Repo: "repo",
|
||||
Org: "myorg",
|
||||
IsGlobal: true,
|
||||
}
|
||||
err := ctx.Ensure(CtxRequirement{
|
||||
LocalRepo: true,
|
||||
RemoteRepo: true,
|
||||
Org: true,
|
||||
Global: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnsureSucceedsWithNoRequirements(t *testing.T) {
|
||||
ctx := TeaContext{}
|
||||
err := ctx.Ensure(CtxRequirement{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetRemoteRepoHTMLURL(t *testing.T) {
|
||||
t.Run("requires remote repo", func(t *testing.T) {
|
||||
ctx := &TeaContext{}
|
||||
_, err := ctx.GetRemoteRepoHTMLURL()
|
||||
require.ErrorContains(t, err, "remote repository required")
|
||||
})
|
||||
|
||||
t.Run("returns repo url when context is complete", func(t *testing.T) {
|
||||
ctx := &TeaContext{
|
||||
Login: &config.Login{URL: "https://gitea.example.com"},
|
||||
RepoSlug: "owner/repo",
|
||||
Owner: "owner",
|
||||
Repo: "repo",
|
||||
}
|
||||
|
||||
url, err := ctx.GetRemoteRepoHTMLURL()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://gitea.example.com/owner/repo", url)
|
||||
})
|
||||
|
||||
t.Run("trims trailing slash from login URL", func(t *testing.T) {
|
||||
ctx := &TeaContext{
|
||||
Login: &config.Login{URL: "https://gitea.example.com/"},
|
||||
RepoSlug: "owner/repo",
|
||||
Owner: "owner",
|
||||
Repo: "repo",
|
||||
}
|
||||
|
||||
url, err := ctx.GetRemoteRepoHTMLURL()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://gitea.example.com/owner/repo", url)
|
||||
})
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
// If that flag is unset, and output is not piped, prompts the user first.
|
||||
func ShowCommentsMaybeInteractive(ctx *context.TeaContext, idx int64, totalComments int) error {
|
||||
if ctx.Bool("comments") {
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions()}
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions(ctx.Command)}
|
||||
c := ctx.Login.Client()
|
||||
comments, _, err := c.ListIssueComments(ctx.Owner, ctx.Repo, idx, opts)
|
||||
if err != nil {
|
||||
@@ -40,7 +40,7 @@ func ShowCommentsMaybeInteractive(ctx *context.TeaContext, idx int64, totalComme
|
||||
// ShowCommentsPaginated prompts if issue/pr comments should be shown and continues to do so.
|
||||
func ShowCommentsPaginated(ctx *context.TeaContext, idx int64, totalComments int) error {
|
||||
c := ctx.Login.Client()
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions()}
|
||||
opts := gitea.ListIssueCommentOptions{ListOptions: flags.GetListOptions(ctx.Command)}
|
||||
prompt := "show comments?"
|
||||
commentsLoaded := 0
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ func getPullIndex(ctx *context.TeaContext, branch string) (int64, error) {
|
||||
c := ctx.Login.Client()
|
||||
opts := gitea.ListPullRequestsOptions{
|
||||
State: gitea.StateOpen,
|
||||
ListOptions: flags.GetListOptions(),
|
||||
ListOptions: flags.GetListOptions(ctx.Command),
|
||||
}
|
||||
selected := ""
|
||||
loadMoreOption := "PR not found? Load more PRs..."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user