chore(upgrade): urfave/cli from v2 to v3 (#8035)
Some checks failed
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Integration tests for the release process / release-simulation (push) Has been cancelled

urfave/cli  v2 will eventually become unmaintained, switch over to v3 which is the latest supported version.

Note: the `docs` command would be a lot of work to restore with v3 ([the package is still in alpha](https://github.com/urfave/cli-docs)) An alternative to avoid a breaking change would be to not upgrade from v2 to v3 for that reason alone.
Note: these commits were cherry-picked from https://code.forgejo.org/forgefriends/forgefriends
Note: it is best reviewed side by side with no display of whitespace changes (there are a lot of those when converting vars to func).

- a few functional changes were necessary and are noted in context in the file changes tab
- https://cli.urfave.org/migrate-v2-to-v3/ upgrade instructions were followed in the most minimal way possible
- upgrade gof3 to v3.10.8 which includes and upgrade from urfave/cli  v2 to urfave/cli  v3
- upgrade gitlab.com/gitlab-org/api/client-go v0.129.0 because it is an indirect dependency of gof3 and requires a change because of a deprecated field that otherwise triggers a lint error but nothing else otherwise
- verified that the [script](https://codeberg.org/forgejo/docs/src/branch/next/scripts/cli-docs.sh) that generates the [CLI documentation](https://codeberg.org/forgejo/docs/src/branch/next/scripts/cli-docs.sh) still works. There are cosmetic differences and the **help** subcommand is no longer advertised (although it is still supported) but the `--help` option is advertised as expected so it is fine.
- end-to-end tests [passed](https://code.forgejo.org/forgejo/end-to-end/pulls/667) (they use the Forgejo CLI to some extent)

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [x] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Breaking features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/8035): <!--number 8035 --><!--line 0 --><!--description VGhlIGBmb3JnZWpvIGRvY3NgIGNvbW1hbmQgaXMgZGVwcmVjYXRlZCBhbmQgQ0xJIGVycm9ycyBhcmUgbm93IGRpc3BsYXllZCBvbiBzdGRlcnIgaW5zdGVhZCBvZiBzdGRvdXQuIFRoZXNlIGJyZWFraW5nIGNoYW5nZXMgaGFwcGVuZWQgYmVjYXVzZSB0aGUgcGFja2FnZSB1c2VkIHRvIHBhcnNlIHRoZSBjb21tYW5kIGxpbmUgYXJndW1lbnRzIHdhcyBbdXBncmFkZWQgZnJvbSB2MiB0byB2M10oaHR0cHM6Ly9jbGkudXJmYXZlLm9yZy9taWdyYXRlLXYyLXRvLXYzLykuIEEgW3NlcGFyYXRlIHByb2plY3Qgd2FzIGluaXRpYXRlZF0oaHR0cHM6Ly9naXRodWIuY29tL3VyZmF2ZS9jbGktZG9jcykgdG8gcmUtaW1wbGVtZW50IHRoZSBgZG9jc2AgY29tbWFuZCwgYnV0IGl0IGlzIG5vdCB5ZXQgcHJvZHVjdGlvbiByZWFkeS4=-->The `forgejo docs` command is deprecated and CLI errors are now displayed on stderr instead of stdout. These breaking changes happened because the package used to parse the command line arguments was [upgraded from v2 to v3](https://cli.urfave.org/migrate-v2-to-v3/). A [separate project was initiated](https://github.com/urfave/cli-docs) to re-implement the `docs` command, but it is not yet production ready.<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: limiting-factor <limiting-factor@posteo.com>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8035
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2025-06-01 22:16:37 +02:00
parent dec17ba704
commit 55d8910255
53 changed files with 1219 additions and 1119 deletions

View file

@ -557,7 +557,7 @@ test-check:
.PHONY: test\#%
test\#%:
@echo "Running go test with -tags '$(TEST_TAGS)'..."
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
.PHONY: coverage

View file

@ -934,6 +934,11 @@
"path": "github.com/urfave/cli/v2/LICENSE",
"licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/urfave/cli/v3",
"path": "github.com/urfave/cli/v3/LICENSE",
"licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
},
{
"name": "github.com/valyala/fastjson",
"path": "github.com/valyala/fastjson/LICENSE",

View file

@ -4,25 +4,28 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdActions represents the available actions sub-commands.
CmdActions = &cli.Command{
func cmdActions() *cli.Command {
return &cli.Command{
Name: "actions",
Usage: "Manage Forgejo Actions",
Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken,
Commands: []*cli.Command{
subcmdActionsGenRunnerToken(),
},
}
}
subcmdActionsGenRunnerToken = &cli.Command{
func subcmdActionsGenRunnerToken() *cli.Command {
return &cli.Command{
Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server",
Action: runGenerateActionsRunnerToken,
@ -36,10 +39,10 @@ var (
},
},
}
)
}
func runGenerateActionsRunnerToken(c *cli.Context) error {
ctx, cancel := installSignals()
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -15,56 +15,64 @@ import (
"forgejo.org/modules/log"
repo_module "forgejo.org/modules/repository"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdAdmin represents the available admin sub-command.
CmdAdmin = &cli.Command{
func cmdAdmin() *cli.Command {
return &cli.Command{
Name: "admin",
Usage: "Perform common administrative operations",
Subcommands: []*cli.Command{
subcmdUser,
subcmdRepoSyncReleases,
subcmdRegenerate,
subcmdAuth,
subcmdSendMail,
Commands: []*cli.Command{
subcmdUser(),
subcmdRepoSyncReleases(),
subcmdRegenerate(),
subcmdAuth(),
subcmdSendMail(),
},
}
}
subcmdRepoSyncReleases = &cli.Command{
func subcmdRepoSyncReleases() *cli.Command {
return &cli.Command{
Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases,
}
}
subcmdRegenerate = &cli.Command{
func subcmdRegenerate() *cli.Command {
return &cli.Command{
Name: "regenerate",
Usage: "Regenerate specific files",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
microcmdRegenHooks,
microcmdRegenKeys,
},
}
subcmdAuth = &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Subcommands: []*cli.Command{
microcmdAuthAddOauth,
microcmdAuthUpdateOauth,
microcmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP,
microcmdAuthList,
microcmdAuthDelete,
},
}
subcmdSendMail = &cli.Command{
func subcmdAuth() *cli.Command {
return &cli.Command{
Name: "auth",
Usage: "Modify external auth providers",
Commands: []*cli.Command{
microcmdAuthAddOauth(),
microcmdAuthUpdateOauth(),
microcmdAuthAddLdapBindDn(),
microcmdAuthUpdateLdapBindDn(),
microcmdAuthAddLdapSimpleAuth(),
microcmdAuthUpdateLdapSimpleAuth(),
microcmdAuthAddSMTP(),
microcmdAuthUpdateSMTP(),
microcmdAuthList(),
microcmdAuthDelete(),
},
}
}
func subcmdSendMail() *cli.Command {
return &cli.Command{
Name: "sendmail",
Usage: "Send a message to all users",
Action: runSendMail,
@ -86,15 +94,17 @@ var (
},
},
}
}
idFlag = &cli.Int64Flag{
func idFlag() *cli.Int64Flag {
return &cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
)
}
func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -13,17 +14,20 @@ import (
"forgejo.org/models/db"
auth_service "forgejo.org/services/auth"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
microcmdAuthDelete = &cli.Command{
func microcmdAuthDelete() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag},
Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth,
}
microcmdAuthList = &cli.Command{
}
func microcmdAuthList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
@ -54,10 +58,10 @@ var (
},
},
}
)
}
func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals()
func runListAuth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -90,12 +94,12 @@ func runListAuth(c *cli.Context) error {
return nil
}
func runDeleteAuth(c *cli.Context) error {
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -11,7 +11,7 @@ import (
"forgejo.org/models/auth"
"forgejo.org/services/auth/source/ldap"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
type (
@ -23,8 +23,8 @@ type (
}
)
var (
commonLdapCLIFlags = []cli.Flag{
func commonLdapCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "Authentication name.",
@ -102,8 +102,10 @@ var (
Usage: "The attribute of the users LDAP record containing the users avatar.",
},
}
}
ldapBindDnCLIFlags = append(commonLdapCLIFlags,
func ldapBindDnCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.",
@ -128,49 +130,59 @@ var (
Name: "page-size",
Usage: "Search page size.",
})
}
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
func ldapSimpleAuthCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{
Name: "user-dn",
Usage: "The user's DN.",
})
}
microcmdAuthAddLdapBindDn = &cli.Command{
func microcmdAuthAddLdapBindDn() *cli.Command {
return &cli.Command{
Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().addLdapBindDn(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapBindDn(ctx, cli)
},
Flags: ldapBindDnCLIFlags,
Flags: ldapBindDnCLIFlags(),
}
}
microcmdAuthUpdateLdapBindDn = &cli.Command{
func microcmdAuthUpdateLdapBindDn() *cli.Command {
return &cli.Command{
Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().updateLdapBindDn(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapBindDn(ctx, cli)
},
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
}
}
microcmdAuthAddLdapSimpleAuth = &cli.Command{
func microcmdAuthAddLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().addLdapSimpleAuth(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapSimpleAuth(ctx, cli)
},
Flags: ldapSimpleAuthCLIFlags,
Flags: ldapSimpleAuthCLIFlags(),
}
}
microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error {
return newAuthService().updateLdapSimpleAuth(c)
Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapSimpleAuth(ctx, cli)
},
Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
}
}
)
// newAuthService creates a service with default functions.
func newAuthService() *authService {
@ -183,7 +195,7 @@ func newAuthService() *authService {
}
// parseAuthSource assigns values on authSource according to command line flags.
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
func parseAuthSource(c *cli.Command, authSource *auth.Source) {
if c.IsSet("name") {
authSource.Name = c.String("name")
}
@ -202,7 +214,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
}
// parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("name") {
config.Name = c.String("name")
}
@ -289,7 +301,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil {
return nil, err
}
@ -307,12 +319,12 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp
}
// addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(c *cli.Context) error {
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -336,8 +348,8 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
}
// updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals()
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -358,12 +370,12 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
}
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {
@ -387,8 +399,8 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
}
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals()
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := a.initDB(ctx); err != nil {

View file

@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestAddLdapBindDn(t *testing.T) {
@ -225,12 +225,12 @@ func TestAddLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthAddLdapBindDn.Flags
app := cli.Command{}
app.Flags = microcmdAuthAddLdapBindDn().Flags
app.Action = service.addLdapBindDn
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -454,12 +454,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
app := cli.Command{}
app.Flags = microcmdAuthAddLdapSimpleAuth().Flags
app.Action = service.addLdapSimpleAuth
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -915,12 +915,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapBindDn.Flags
app := cli.Command{}
app.Flags = microcmdAuthUpdateLdapBindDn().Flags
app.Action = service.updateLdapBindDn
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {
@ -1303,12 +1303,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
}
// Create a copy of command to test
app := cli.NewApp()
app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
app := cli.Command{}
app.Flags = microcmdAuthUpdateLdapSimpleAuth().Flags
app.Action = service.updateLdapSimpleAuth
// Run it
err := app.Run(c.args)
err := app.Run(t.Context(), c.args)
if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"net/url"
@ -11,11 +12,11 @@ import (
auth_model "forgejo.org/models/auth"
"forgejo.org/services/auth/source/oauth2"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
oauthCLIFlags = []cli.Flag{
func oauthCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@ -120,23 +121,27 @@ var (
Usage: "Activate automatic team membership removal depending on groups",
},
}
}
microcmdAuthAddOauth = &cli.Command{
func microcmdAuthAddOauth() *cli.Command {
return &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
Flags: oauthCLIFlags(),
}
}
microcmdAuthUpdateOauth = &cli.Command{
func microcmdAuthUpdateOauth() *cli.Command {
return &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...),
}
}
)
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
@ -168,15 +173,15 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
}
}
func runAddOauth(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddOauth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
config := parseOAuth2Config(c)
config := parseOAuth2Config(ctx, c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
@ -192,12 +197,12 @@ func runAddOauth(c *cli.Context) error {
})
}
func runUpdateOauth(c *cli.Context) error {
func runUpdateOauth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"strings"
@ -11,11 +12,11 @@ import (
"forgejo.org/modules/util"
"forgejo.org/services/auth/source/smtp"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
smtpCLIFlags = []cli.Flag{
func smtpCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
@ -71,23 +72,27 @@ var (
Value: true,
},
}
}
microcmdAuthAddSMTP = &cli.Command{
func microcmdAuthAddSMTP() *cli.Command {
return &cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: runAddSMTP,
Flags: smtpCLIFlags,
Flags: smtpCLIFlags(),
}
}
microcmdAuthUpdateSMTP = &cli.Command{
func microcmdAuthUpdateSMTP() *cli.Command {
return &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...),
}
}
)
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@ -123,8 +128,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
return nil
}
func runAddSMTP(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddSMTP(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -163,12 +168,12 @@ func runAddSMTP(c *cli.Context) error {
})
}
func runUpdateSMTP(c *cli.Context) error {
func runUpdateSMTP(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,11 +4,13 @@
package cmd
import (
"context"
asymkey_model "forgejo.org/models/asymkey"
"forgejo.org/modules/graceful"
repo_service "forgejo.org/services/repository"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
@ -25,8 +27,8 @@ var (
}
)
func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {
@ -35,8 +37,8 @@ func runRegenerateHooks(_ *cli.Context) error {
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals()
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,18 +4,20 @@
package cmd
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var subcmdUser = &cli.Command{
func subcmdUser() *cli.Command {
return &cli.Command{
Name: "user",
Usage: "Modify users",
Subcommands: []*cli.Command{
microcmdUserCreate,
microcmdUserList,
microcmdUserChangePassword,
microcmdUserDelete,
microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword,
Commands: []*cli.Command{
microcmdUserCreate(),
microcmdUserList(),
microcmdUserChangePassword(),
microcmdUserDelete(),
microcmdUserGenerateAccessToken(),
microcmdUserMustChangePassword(),
},
}
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
@ -13,10 +14,11 @@ import (
"forgejo.org/modules/setting"
user_service "forgejo.org/services/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserChangePassword = &cli.Command{
func microcmdUserChangePassword() *cli.Command {
return &cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
@ -40,13 +42,14 @@ var microcmdUserChangePassword = &cli.Command{
},
},
}
}
func runChangePassword(c *cli.Context) error {
func runChangePassword(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "username", "password"); err != nil {
return err
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -15,10 +16,11 @@ import (
"forgejo.org/modules/optional"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserCreate = &cli.Command{
func microcmdUserCreate() *cli.Command {
return &cli.Command{
Name: "create",
Usage: "Create a new user in database",
Action: runCreateUser,
@ -51,7 +53,6 @@ var microcmdUserCreate = &cli.Command{
Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true,
DisableDefaultText: true,
},
&cli.IntFlag{
Name: "random-password-length",
@ -82,8 +83,9 @@ var microcmdUserCreate = &cli.Command{
},
},
}
}
func runCreateUser(c *cli.Context) error {
func runCreateUser(ctx context.Context, c *cli.Command) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
@ -108,10 +110,10 @@ func runCreateUser(c *cli.Context) error {
username = c.String("username")
} else {
username = c.String("name")
_, _ = fmt.Fprint(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
_, _ = fmt.Fprint(c.Root().ErrWriter, "--name flag is deprecated. Use --username instead.\n")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -12,10 +13,11 @@ import (
"forgejo.org/modules/storage"
user_service "forgejo.org/services/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserDelete = &cli.Command{
func microcmdUserDelete() *cli.Command {
return &cli.Command{
Name: "delete",
Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{
@ -40,13 +42,14 @@ var microcmdUserDelete = &cli.Command{
},
Action: runDeleteUser,
}
}
func runDeleteUser(c *cli.Context) error {
func runDeleteUser(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,16 +4,18 @@
package cmd
import (
"context"
"errors"
"fmt"
auth_model "forgejo.org/models/auth"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserGenerateAccessToken = &cli.Command{
func microcmdUserGenerateAccessToken() *cli.Command {
return &cli.Command{
Name: "generate-access-token",
Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{
@ -40,13 +42,14 @@ var microcmdUserGenerateAccessToken = &cli.Command{
},
Action: runGenerateAccessToken,
}
}
func runGenerateAccessToken(c *cli.Context) error {
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("you must provide a username to generate a token for")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,16 +4,18 @@
package cmd
import (
"context"
"fmt"
"os"
"text/tabwriter"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserList = &cli.Command{
func microcmdUserList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List users",
Action: runListUsers,
@ -24,9 +26,10 @@ var microcmdUserList = &cli.Command{
},
},
}
}
func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
func runListUsers(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if err := initDB(ctx); err != nil {

View file

@ -4,15 +4,17 @@
package cmd
import (
"context"
"errors"
"fmt"
user_model "forgejo.org/models/user"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var microcmdUserMustChangePassword = &cli.Command{
func microcmdUserMustChangePassword() *cli.Command {
return &cli.Command{
Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword,
@ -33,9 +35,10 @@ var microcmdUserMustChangePassword = &cli.Command{
},
},
}
}
func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals()
func runMustChangePassword(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
if c.NArg() == 0 && !c.IsSet("all") {

View file

@ -6,6 +6,7 @@
package cmd
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -20,11 +21,12 @@ import (
"strings"
"time"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdCert represents the available cert sub-command.
var CmdCert = &cli.Command{
func cmdCert() *cli.Command {
return &cli.Command{
Name: "cert",
Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server.
@ -62,6 +64,7 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
},
},
}
}
func publicKey(priv any) any {
switch k := priv.(type) {
@ -89,7 +92,7 @@ func pemBlockForKey(priv any) *pem.Block {
}
}
func runCert(c *cli.Context) error {
func runCert(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "host"); err != nil {
return err
}

View file

@ -20,21 +20,23 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/modules/util"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context.
func argsSet(c *cli.Context, args ...string) error {
func argsSet(c *cli.Command, args ...string) error {
for _, a := range args {
if !c.IsSet(a) {
return errors.New(a + " is not set")
}
if util.IsEmptyString(c.String(a)) {
if s, ok := c.Value(a).(string); ok {
if util.IsEmptyString(s) {
return errors.New(a + " is required")
}
}
}
return nil
}
@ -73,8 +75,8 @@ If this is the intended configuration file complete the [database] section.`, se
return nil
}
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(ctx)
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
@ -109,7 +111,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
func globalBool(c *cli.Context, name string) bool {
func globalBool(c *cli.Command, name string) bool {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
@ -120,16 +122,16 @@ func globalBool(c *cli.Context, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(c *cli.Context) error {
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
level := defaultLevel
if globalBool(c, "quiet") {
if globalBool(cli, "quiet") {
level = log.FATAL
}
if globalBool(c, "debug") || globalBool(c, "verbose") {
if globalBool(cli, "debug") || globalBool(cli, "verbose") {
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return nil
return ctx, nil
}
}

View file

@ -1,65 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"strings"
"github.com/urfave/cli/v2"
)
// CmdDocs represents the available docs sub-command.
var CmdDocs = &cli.Command{
Name: "docs",
Usage: "Output CLI documentation",
Description: "A command to output Forgejo's CLI documentation, optionally to a file.",
Action: runDocs,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "man",
Usage: "Output man pages instead",
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
},
},
}
func runDocs(ctx *cli.Context) error {
docs, err := ctx.App.ToMarkdown()
if ctx.Bool("man") {
docs, err = ctx.App.ToMan()
}
if err != nil {
return err
}
if !ctx.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040
firstHashtagIndex := strings.Index(docs, "#")
if firstHashtagIndex > 0 {
docs = docs[firstHashtagIndex:]
}
}
out := os.Stdout
if ctx.String("output") != "" {
fi, err := os.Create(ctx.String("output"))
if err != nil {
return err
}
defer fi.Close()
out = fi
}
_, err = fmt.Fprintln(out, docs)
return err
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"fmt"
golog "log"
"os"
@ -19,23 +20,26 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/services/doctor"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{
func cmdDoctor() *cli.Command {
return &cli.Command{
Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Subcommands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
Commands: []*cli.Command{
cmdDoctorCheck(),
cmdRecreateTable(),
cmdDoctorConvert(),
},
}
}
var cmdDoctorCheck = &cli.Command{
func cmdDoctorCheck() *cli.Command {
return &cli.Command{
Name: "check",
Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
@ -72,8 +76,10 @@ var cmdDoctorCheck = &cli.Command{
},
},
}
}
var cmdRecreateTable = &cli.Command{
func cmdRecreateTable() *cli.Command {
return &cli.Command{
Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
@ -90,9 +96,10 @@ This command will cause Xorm to recreate tables, copying over the data and delet
You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable,
}
}
func runRecreateTable(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
// Redirect the default golog to here
@ -143,7 +150,7 @@ func runRecreateTable(ctx *cli.Context) error {
})
}
func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
func setupDoctorDefaultLogger(ctx *cli.Command, colorize bool) {
// Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
@ -165,8 +172,8 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
}
}
func runDoctorCheck(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
colorize := log.CanColorStdout

View file

@ -4,25 +4,28 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// cmdDoctorConvert represents the available convert sub-command.
var cmdDoctorConvert = &cli.Command{
func cmdDoctorConvert() *cli.Command {
return &cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4",
Action: runDoctorConvert,
}
}
func runDoctorConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDoctorConvert(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -11,7 +11,7 @@ import (
"forgejo.org/services/doctor"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestDoctorRun(t *testing.T) {
@ -22,12 +22,12 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true,
})
app := cli.NewApp()
app.Commands = []*cli.Command{cmdDoctorCheck}
err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
app := cli.Command{}
app.Commands = []*cli.Command{cmdDoctorCheck()}
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
require.NoError(t, err)
err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"})
require.ErrorContains(t, err, `unknown checks: "no-such"`)
}

View file

@ -5,6 +5,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
@ -23,7 +24,7 @@ import (
"code.forgejo.org/go-chi/session"
"github.com/mholt/archiver/v3"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
@ -84,6 +85,10 @@ func (o *outputType) Set(value string) error {
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o *outputType) Get() any {
return o.String()
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
@ -97,7 +102,8 @@ var outputTypeEnum = &outputType{
}
// CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{
func cmdDump() *cli.Command {
return &cli.Command{
Name: "dump",
Usage: "Dump Forgejo files and database",
Description: `Dump compresses all related files and database into zip file.
@ -171,13 +177,14 @@ It can be used for backup and capture Forgejo server image to send to maintainer
},
},
}
}
func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
log.Fatal(format, args...)
}
func runDump(ctx *cli.Context) error {
func runDump(stdCtx context.Context, ctx *cli.Command) error {
var file *os.File
fileName := ctx.String("file")
outType := ctx.String("type")
@ -222,7 +229,7 @@ func runDump(ctx *cli.Context) error {
return errors.New("--quiet and --verbose cannot both be set")
}
stdCtx, cancel := installSignals()
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
err := db.InitEngine(stdCtx)

View file

@ -19,11 +19,12 @@ import (
"forgejo.org/services/convert"
"forgejo.org/services/migrations"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdDumpRepository represents the available dump repository sub-command.
var CmdDumpRepository = &cli.Command{
func cmdDumpRepository() *cli.Command {
return &cli.Command{
Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.",
@ -78,9 +79,10 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
},
},
}
}
func runDumpRepository(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -19,23 +20,25 @@ import (
"forgejo.org/modules/util"
"github.com/gobwas/glob"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdEmbedded represents the available extract sub-command.
var (
CmdEmbedded = &cli.Command{
func cmdEmbedded() *cli.Command {
return &cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",
Subcommands: []*cli.Command{
subcmdList,
subcmdView,
subcmdExtract,
Commands: []*cli.Command{
subcmdList(),
subcmdView(),
subcmdExtract(),
},
}
}
subcmdList = &cli.Command{
func subcmdList() *cli.Command {
return &cli.Command{
Name: "list",
Usage: "List files matching the given pattern",
Action: runList,
@ -47,8 +50,10 @@ var (
},
},
}
}
subcmdView = &cli.Command{
func subcmdView() *cli.Command {
return &cli.Command{
Name: "view",
Usage: "View a file matching the given pattern",
Action: runView,
@ -60,8 +65,10 @@ var (
},
},
}
}
subcmdExtract = &cli.Command{
func subcmdExtract() *cli.Command {
return &cli.Command{
Name: "extract",
Usage: "Extract resources",
Action: runExtract,
@ -90,9 +97,9 @@ var (
},
},
}
}
matchedAssetFiles []assetFile
)
var matchedAssetFiles []assetFile
type assetFile struct {
fs *assetfs.LayeredFS
@ -100,7 +107,7 @@ type assetFile struct {
path string
}
func initEmbeddedExtractor(c *cli.Context) error {
func initEmbeddedExtractor(_ context.Context, c *cli.Command) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice())
@ -115,32 +122,32 @@ func initEmbeddedExtractor(c *cli.Context) error {
return nil
}
func runList(c *cli.Context) error {
if err := runListDo(c); err != nil {
func runList(ctx context.Context, c *cli.Command) error {
if err := runListDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runView(c *cli.Context) error {
if err := runViewDo(c); err != nil {
func runView(ctx context.Context, c *cli.Command) error {
if err := runViewDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runExtract(c *cli.Context) error {
if err := runExtractDo(c); err != nil {
func runExtract(ctx context.Context, c *cli.Command) error {
if err := runExtractDo(ctx, c); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return err
}
return nil
}
func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runListDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -151,8 +158,8 @@ func runListDo(c *cli.Context) error {
return nil
}
func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runViewDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -174,8 +181,8 @@ func runViewDo(c *cli.Context) error {
return nil
}
func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil {
func runExtractDo(ctx context.Context, c *cli.Command) error {
if err := initEmbeddedExtractor(ctx, c); err != nil {
return err
}
@ -271,7 +278,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil
}
func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true)
if err != nil {

View file

@ -17,14 +17,14 @@ import (
"forgejo.org/modules/setting"
private_routers "forgejo.org/routers/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CmdActions(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "actions",
Usage: "Commands for managing Forgejo Actions",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
SubcmdActionsGenerateRunnerToken(ctx),
SubcmdActionsGenerateRunnerSecret(ctx),
SubcmdActionsRegister(ctx),
@ -37,7 +37,7 @@ func SubcmdActionsGenerateRunnerToken(ctx context.Context) *cli.Command {
Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server",
Before: prepareWorkPathAndCustomConf(ctx),
Action: func(cliCtx *cli.Context) error { return RunGenerateActionsRunnerToken(ctx, cliCtx) },
Action: RunGenerateActionsRunnerToken,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "scope",
@ -53,7 +53,7 @@ func SubcmdActionsGenerateRunnerSecret(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "generate-secret",
Usage: "Generate a secret suitable for input to the register subcommand",
Action: func(cliCtx *cli.Context) error { return RunGenerateSecret(ctx, cliCtx) },
Action: RunGenerateSecret,
}
}
@ -62,7 +62,7 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
Name: "register",
Usage: "Idempotent registration of a runner using a shared secret",
Before: prepareWorkPathAndCustomConf(ctx),
Action: func(cliCtx *cli.Context) error { return RunRegister(ctx, cliCtx) },
Action: RunRegister,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "secret",
@ -106,19 +106,19 @@ func SubcmdActionsRegister(ctx context.Context) *cli.Command {
}
}
func readSecret(ctx context.Context, cliCtx *cli.Context) (string, error) {
if cliCtx.IsSet("secret") {
return cliCtx.String("secret"), nil
func readSecret(ctx context.Context, cli *cli.Command) (string, error) {
if cli.IsSet("secret") {
return cli.String("secret"), nil
}
if cliCtx.IsSet("secret-stdin") {
if cli.IsSet("secret-stdin") {
buf, err := io.ReadAll(ContextGetStdin(ctx))
if err != nil {
return "", err
}
return string(buf), nil
}
if cliCtx.IsSet("secret-file") {
path := cliCtx.String("secret-file")
if cli.IsSet("secret-file") {
path := cli.String("secret-file")
buf, err := os.ReadFile(path)
if err != nil {
return "", err
@ -139,18 +139,18 @@ func validateSecret(secret string) error {
return nil
}
func getLabels(cliCtx *cli.Context) (*[]string, error) {
if !cliCtx.Bool("keep-labels") {
lblValue := strings.Split(cliCtx.String("labels"), ",")
func getLabels(cli *cli.Command) (*[]string, error) {
if !cli.Bool("keep-labels") {
lblValue := strings.Split(cli.String("labels"), ",")
return &lblValue, nil
}
if cliCtx.String("labels") != "" {
if cli.String("labels") != "" {
return nil, errors.New("--labels and --keep-labels should not be used together")
}
return nil, nil
}
func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
func RunRegister(ctx context.Context, cli *cli.Command) error {
var cancel context.CancelFunc
if !ContextGetNoInit(ctx) {
ctx, cancel = installSignals(ctx)
@ -162,17 +162,17 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
}
setting.MustInstalled()
secret, err := readSecret(ctx, cliCtx)
secret, err := readSecret(ctx, cli)
if err != nil {
return err
}
if err := validateSecret(secret); err != nil {
return err
}
scope := cliCtx.String("scope")
name := cliCtx.String("name")
version := cliCtx.String("version")
labels, err := getLabels(cliCtx)
scope := cli.String("scope")
name := cli.String("name")
version := cli.String("version")
labels, err := getLabels(cli)
if err != nil {
return err
}
@ -210,7 +210,7 @@ func RunRegister(ctx context.Context, cliCtx *cli.Context) error {
return nil
}
func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
func RunGenerateSecret(ctx context.Context, cli *cli.Command) error {
runner := actions_model.ActionRunner{}
if err := runner.GenerateToken(); err != nil {
return err
@ -221,7 +221,7 @@ func RunGenerateSecret(ctx context.Context, cliCtx *cli.Context) error {
return nil
}
func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) error {
func RunGenerateActionsRunnerToken(ctx context.Context, cli *cli.Command) error {
if !ContextGetNoInit(ctx) {
var cancel context.CancelFunc
ctx, cancel = installSignals(ctx)
@ -230,7 +230,7 @@ func RunGenerateActionsRunnerToken(ctx context.Context, cliCtx *cli.Context) err
setting.MustInstalled()
scope := cliCtx.String("scope")
scope := cli.String("scope")
respText, extra := private.GenerateActionsRunnerToken(ctx, scope)
if extra.HasError() {

View file

@ -4,14 +4,13 @@
package forgejo
import (
"context"
"fmt"
"testing"
"forgejo.org/services/context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestActions_getLabels(t *testing.T) {
@ -54,21 +53,21 @@ func TestActions_getLabels(t *testing.T) {
},
}
flags := SubcmdActionsRegister(context.Context{}).Flags
flags := SubcmdActionsRegister(t.Context()).Flags
for _, c := range cases {
t.Run(fmt.Sprintf("args: %v", c.args), func(t *testing.T) {
// Create a copy of command to test
var result *resultType
app := cli.NewApp()
app := cli.Command{}
app.Flags = flags
app.Action = func(ctx *cli.Context) error {
app.Action = func(_ context.Context, ctx *cli.Command) error {
labels, err := getLabels(ctx)
result = &resultType{labels, err}
return nil
}
// Run it
_ = app.Run(c.args)
_ = app.Run(t.Context(), c.args)
// Test the results
require.NotNil(t, result)

View file

@ -20,7 +20,7 @@ import (
f3_cmd "code.forgejo.org/f3/gof3/v3/cmd"
f3_logger "code.forgejo.org/f3/gof3/v3/logger"
f3_util "code.forgejo.org/f3/gof3/v3/util"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func CmdF3(ctx context.Context) *cli.Command {
@ -28,21 +28,21 @@ func CmdF3(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "f3",
Usage: "F3",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
SubcmdF3Mirror(ctx),
},
}
}
func SubcmdF3Mirror(ctx context.Context) *cli.Command {
mirrorCmd := f3_cmd.CreateCmdMirror(ctx)
mirrorCmd := f3_cmd.CreateCmdMirror()
mirrorCmd.Before = prepareWorkPathAndCustomConf(ctx)
f3Action := mirrorCmd.Action
mirrorCmd.Action = func(c *cli.Context) error { return runMirror(ctx, c, f3Action) }
mirrorCmd.Action = func(ctx context.Context, cli *cli.Command) error { return runMirror(ctx, cli, f3Action) }
return mirrorCmd
}
func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error {
func runMirror(ctx context.Context, c *cli.Command, action cli.ActionFunc) error {
setting.LoadF3Setting()
if !setting.F3.Enabled {
return errors.New("F3 is disabled, it is not ready to be used and is only present for development purposes")
@ -69,7 +69,7 @@ func runMirror(ctx context.Context, c *cli.Context, action cli.ActionFunc) error
}
}
err := action(c)
err := action(ctx, c)
if panicError, ok := err.(f3_util.PanicError); ok {
log.Debug("F3 Stack trace\n%s", panicError.Stack())
}

View file

@ -16,7 +16,7 @@ import (
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
type key int
@ -34,7 +34,7 @@ func CmdForgejo(ctx context.Context) *cli.Command {
Name: "forgejo-cli",
Usage: "Forgejo CLI",
Flags: []cli.Flag{},
Subcommands: []*cli.Command{
Commands: []*cli.Command{
CmdActions(ctx),
CmdF3(ctx),
},
@ -147,12 +147,12 @@ func handleCliResponseExtra(ctx context.Context, extra private.ResponseExtra) er
return cli.Exit(extra.Error, 1)
}
func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) error {
return func(c *cli.Context) error {
func prepareWorkPathAndCustomConf(ctx context.Context) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
if !ContextGetNoInit(ctx) {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
for _, curCtx := range c.Lineage() {
for _, curCtx := range cli.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
@ -165,6 +165,6 @@ func prepareWorkPathAndCustomConf(ctx context.Context) func(c *cli.Context) erro
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
}
return nil
return ctx, nil
}
}

View file

@ -5,56 +5,65 @@
package cmd
import (
"context"
"fmt"
"os"
"forgejo.org/modules/generate"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdGenerate represents the available generate sub-command.
CmdGenerate = &cli.Command{
func cmdGenerate() *cli.Command {
return &cli.Command{
Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens",
Subcommands: []*cli.Command{
subcmdSecret,
Commands: []*cli.Command{
subcmdSecret(),
},
}
}
subcmdSecret = &cli.Command{
func subcmdSecret() *cli.Command {
return &cli.Command{
Name: "secret",
Usage: "Generate a secret token",
Subcommands: []*cli.Command{
microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey,
Commands: []*cli.Command{
microcmdGenerateInternalToken(),
microcmdGenerateLfsJwtSecret(),
microcmdGenerateSecretKey(),
},
}
}
microcmdGenerateInternalToken = &cli.Command{
func microcmdGenerateInternalToken() *cli.Command {
return &cli.Command{
Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken,
}
}
microcmdGenerateLfsJwtSecret = &cli.Command{
func microcmdGenerateLfsJwtSecret() *cli.Command {
return &cli.Command{
Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret,
}
}
microcmdGenerateSecretKey = &cli.Command{
func microcmdGenerateSecretKey() *cli.Command {
return &cli.Command{
Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey,
}
)
}
func runGenerateInternalToken(c *cli.Context) error {
func runGenerateInternalToken(ctx context.Context, c *cli.Command) error {
internalToken, err := generate.NewInternalToken()
if err != nil {
return err
@ -69,7 +78,7 @@ func runGenerateInternalToken(c *cli.Context) error {
return nil
}
func runGenerateLfsJwtSecret(c *cli.Context) error {
func runGenerateLfsJwtSecret(ctx context.Context, c *cli.Command) error {
_, jwtSecretBase64 := generate.NewJwtSecret()
fmt.Printf("%s", jwtSecretBase64)
@ -81,7 +90,7 @@ func runGenerateLfsJwtSecret(c *cli.Context) error {
return nil
}
func runGenerateSecretKey(c *cli.Context) error {
func runGenerateSecretKey(ctx context.Context, c *cli.Command) error {
secretKey, err := generate.NewSecretKey()
if err != nil {
return err

View file

@ -21,29 +21,31 @@ import (
repo_module "forgejo.org/modules/repository"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
hookBatchSize = 30
)
var (
// CmdHook represents the available hooks sub-command.
CmdHook = &cli.Command{
func cmdHook() *cli.Command {
return &cli.Command{
Name: "hook",
Usage: "(internal) Should only be called by Git",
Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []*cli.Command{
subcmdHookPreReceive,
subcmdHookUpdate,
subcmdHookPostReceive,
subcmdHookProcReceive,
Commands: []*cli.Command{
subcmdHookPreReceive(),
subcmdHookUpdate(),
subcmdHookPostReceive(),
subcmdHookProcReceive(),
},
}
}
subcmdHookPreReceive = &cli.Command{
func subcmdHookPreReceive() *cli.Command {
return &cli.Command{
Name: "pre-receive",
Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git",
@ -54,7 +56,10 @@ var (
},
},
}
subcmdHookUpdate = &cli.Command{
}
func subcmdHookUpdate() *cli.Command {
return &cli.Command{
Name: "update",
Usage: "Delegate update Git hook",
Description: "This command should only be called by Git",
@ -65,7 +70,10 @@ var (
},
},
}
subcmdHookPostReceive = &cli.Command{
}
func subcmdHookPostReceive() *cli.Command {
return &cli.Command{
Name: "post-receive",
Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git",
@ -76,8 +84,11 @@ var (
},
},
}
}
// Note: new hook since git 2.29
subcmdHookProcReceive = &cli.Command{
func subcmdHookProcReceive() *cli.Command {
return &cli.Command{
Name: "proc-receive",
Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git",
@ -88,7 +99,7 @@ var (
},
},
}
)
}
type delayWriter struct {
internal io.Writer
@ -161,11 +172,11 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
func runHookPreReceive(c *cli.Context) error {
func runHookPreReceive(ctx context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -291,13 +302,13 @@ Forgejo or set your environment appropriately.`, "")
}
// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update
func runHookUpdate(c *cli.Context) error {
func runHookUpdate(ctx context.Context, c *cli.Command) error {
// Now if we're an internal don't do anything else
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
if c.NArg() != 3 {
@ -323,8 +334,8 @@ func runHookUpdate(c *cli.Context) error {
return fail(ctx, fmt.Sprintf("The modification of %s is skipped as it's an internal reference.", refFullName), "")
}
func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
func runHookPostReceive(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -487,8 +498,8 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) {
}
}
func runHookProcReceive(c *cli.Context) error {
ctx, cancel := installSignals()
func runHookProcReceive(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)

View file

@ -19,7 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Capture what's being written into a standard file descriptor.
@ -134,14 +134,14 @@ func TestDelayWriter(t *testing.T) {
defer ts.Close()
defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookPreReceive}
app := cli.Command{}
app.Commands = []*cli.Command{subcmdHookPreReceive()}
t.Run("Should delay", func(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
finish := captureOutput(t, os.Stdout)
err = app.Run([]string{"./forgejo", "pre-receive"})
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
@ -153,7 +153,7 @@ func TestDelayWriter(t *testing.T) {
defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
finish := captureOutput(t, os.Stdout)
err = app.Run([]string{"./forgejo", "pre-receive"})
err = app.Run(t.Context(), []string{"./forgejo", "pre-receive"})
require.NoError(t, err)
out := finish()
@ -163,15 +163,15 @@ func TestDelayWriter(t *testing.T) {
}
func TestRunHookUpdate(t *testing.T) {
app := cli.NewApp()
app.Commands = []*cli.Command{subcmdHookUpdate}
app := cli.Command{}
app.Commands = []*cli.Command{subcmdHookUpdate()}
t.Run("Removal of internal reference", func(t *testing.T) {
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
defer test.MockVariableValue(&setting.IsProd, false)()
finish := captureOutput(t, os.Stderr)
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
out := finish()
require.Error(t, err)
@ -183,7 +183,7 @@ func TestRunHookUpdate(t *testing.T) {
defer test.MockVariableValue(&setting.IsProd, false)()
finish := captureOutput(t, os.Stderr)
err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
out := finish()
require.Error(t, err)
@ -191,12 +191,12 @@ func TestRunHookUpdate(t *testing.T) {
})
t.Run("Removal of branch", func(t *testing.T) {
err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
err := app.Run(t.Context(), []string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
require.NoError(t, err)
})
t.Run("Not enough arguments", func(t *testing.T) {
err := app.Run([]string{"./forgejo", "update"})
err := app.Run(t.Context(), []string{"./forgejo", "update"})
require.NoError(t, err)
})
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"strings"
@ -11,11 +12,12 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdKeys represents the available keys sub-command
var CmdKeys = &cli.Command{
func cmdKeys() *cli.Command {
return &cli.Command{
Name: "keys",
Usage: "(internal) Should only be called by SSH server",
Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint",
@ -48,8 +50,9 @@ var CmdKeys = &cli.Command{
},
},
}
}
func runKeys(c *cli.Context) error {
func runKeys(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("No username provided")
}
@ -68,7 +71,7 @@ func runKeys(c *cli.Context) error {
return errors.New("No key type and content provided")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), true)
@ -78,6 +81,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil {
return extra.Error
}
_, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text))
return nil
}

View file

@ -4,16 +4,17 @@
package cmd
import (
"context"
"fmt"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals()
func runSendMail(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -14,7 +14,7 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// cmdHelp is our own help subcommand with more information
@ -25,18 +25,18 @@ func cmdHelp() *cli.Command {
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *cli.Context) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
Action: func(ctx context.Context, c *cli.Command) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Forgejo
targetCmdIdx := 0
if c.Command.Name == "help" {
if c.Name == "help" {
targetCmdIdx = 1
}
if lineage[targetCmdIdx+1].Command != nil {
err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
if targetCmdIdx+1 < len(lineage) {
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name)
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
_, _ = fmt.Fprintf(c.Root().Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
@ -77,25 +77,25 @@ func appGlobalFlags() []cli.Flag {
}
}
func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
func prepareSubcommandWithConfig(command *cli.Command, globalFlags func() []cli.Flag) {
command.Flags = append(globalFlags(), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp())
command.Commands = append(command.Commands, cmdHelp())
}
for i := range command.Subcommands {
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
for i := range command.Commands {
prepareSubcommandWithConfig(command.Commands[i], globalFlags)
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(_ context.Context, _ *cli.Command) error {
return func(ctx context.Context, cli *cli.Command) error {
var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags
for _, curCtx := range ctx.Lineage() {
for _, curCtx := range cli.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
@ -107,15 +107,15 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context)
}
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
if cli.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx)
return cmdHelp().Action(ctx, cli)
}
return action(ctx)
return action(ctx, cli)
}
}
func NewMainApp(version, versionExtra string) *cli.App {
func NewMainApp(version, versionExtra string) *cli.Command {
path, err := os.Executable()
if err != nil {
panic(err)
@ -124,7 +124,7 @@ func NewMainApp(version, versionExtra string) *cli.App {
subCmdsStandalone := make([]*cli.Command, 0, 10)
subCmdWithConfig := make([]*cli.Command, 0, 10)
globalFlags := make([]cli.Flag, 0, 10)
globalFlags := func() []cli.Flag { return []cli.Flag{} }
//
// If the executable is forgejo-cli, provide a Forgejo specific CLI
@ -133,14 +133,16 @@ func NewMainApp(version, versionExtra string) *cli.App {
if executable == "forgejo-cli" {
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
globalFlags = append(globalFlags, []cli.Flag{
globalFlags = func() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
},
&cli.BoolFlag{
Name: "verbose",
},
}...)
}
}
} else {
//
// Otherwise provide a Gitea compatible CLI which includes Forgejo
@ -149,55 +151,54 @@ func NewMainApp(version, versionExtra string) *cli.App {
// binary and rename it to forgejo if they want.
//
subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
subCmdWithConfig = append(subCmdWithConfig, CmdActions)
subCmdWithConfig = append(subCmdWithConfig, cmdActions())
}
return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
}
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
app := cli.NewApp()
app.HelpName = "forgejo"
func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs func() []cli.Flag) *cli.Command {
app := &cli.Command{}
app.Name = "Forgejo"
app.Usage = "Beyond coding. We forge."
app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = version + versionExtra
app.EnableBashCompletion = true
app.EnableShellCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
CmdWeb,
CmdServ,
CmdHook,
CmdKeys,
CmdDump,
CmdAdmin,
CmdMigrate,
CmdDoctor,
CmdManager,
CmdEmbedded,
CmdMigrateStorage,
CmdDumpRepository,
CmdRestoreRepository,
cmdWeb(),
cmdServ(),
cmdHook(),
cmdKeys(),
cmdDump(),
cmdAdmin(),
cmdMigrate(),
cmdDoctor(),
cmdManager(),
cmdEmbedded(),
cmdMigrateStorage(),
cmdDumpRepository(),
cmdRestoreRepository(),
}
subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
CmdGenerate,
CmdDocs,
cmdCert(),
cmdGenerate(),
}
subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
app.DefaultCommand = CmdWeb.Name
app.DefaultCommand = cmdWeb().Name
globalFlags := appGlobalFlags()
globalFlags = append(globalFlags, globalFlagsArgs...)
globalFlags := func() []cli.Flag {
return append(appGlobalFlags(), globalFlagsArgs()...)
}
app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, globalFlags...)
app.Flags = append(app.Flags, globalFlags()...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
@ -210,8 +211,8 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
return app
}
func RunMainApp(app *cli.App, args ...string) error {
err := app.Run(args)
func RunMainApp(app *cli.Command, args ...string) error {
err := app.Run(context.Background(), args)
if err == nil {
return nil
}
@ -220,7 +221,7 @@ func RunMainApp(app *cli.App, args ...string) error {
cli.OsExiter(1)
return err
}
_, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
_, _ = fmt.Fprintf(app.Root().ErrWriter, "Command error: %v\n", err)
cli.OsExiter(1)
return err
}

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"io"
@ -17,7 +18,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func TestMain(m *testing.M) {
@ -28,10 +29,10 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
func newTestApp(testCmdAction func(_ context.Context, ctx *cli.Command) error) *cli.Command {
app := NewMainApp("version", "version-extra")
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
prepareSubcommandWithConfig(testCmd, appGlobalFlags)
app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name
return app
@ -43,7 +44,7 @@ type runResult struct {
ExitCode int
}
func runTestApp(app *cli.App, args ...string) (runResult, error) {
func runTestApp(app *cli.Command, args ...string) (runResult, error) {
outBuf := new(strings.Builder)
errBuf := new(strings.Builder)
app.Writer = outBuf
@ -66,7 +67,6 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
@ -110,12 +110,17 @@ func TestCliCmd(t *testing.T) {
},
}
app := newTestApp(func(ctx *cli.Context) error {
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
})
for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) {
defer test.MockProtect(&setting.AppWorkPath)()
defer test.MockProtect(&setting.CustomPath)()
defer test.MockProtect(&setting.CustomConf)()
app := newTestApp(func(_ context.Context, ctx *cli.Command) error {
_, _ = fmt.Fprint(ctx.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
})
for k, v := range c.env {
t.Setenv(k, v)
}
@ -123,34 +128,34 @@ func TestCliCmd(t *testing.T) {
r, err := runTestApp(app, args...)
require.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd+"\n"+r.Stdout)
})
}
}
func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") })
app := newTestApp(func(_ context.Context, ctx *cli.Command) error { return errors.New("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx *cli.Context) error { return nil })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
require.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
assert.Empty(t, r.Stdout)
app = newTestApp(func(ctx *cli.Context) error { return nil })
app = newTestApp(func(_ context.Context, ctx *cli.Command) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd")
require.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called

View file

@ -4,30 +4,34 @@
package cmd
import (
"context"
"os"
"time"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
// CmdManager represents the manager command
CmdManager = &cli.Command{
func cmdManager() *cli.Command {
return &cli.Command{
Name: "manager",
Usage: "Manage the running forgejo process",
Description: "This is a command for managing the running forgejo process",
Subcommands: []*cli.Command{
subcmdShutdown,
subcmdRestart,
subcmdReloadTemplates,
subcmdFlushQueues,
subcmdLogging,
subCmdProcesses,
Commands: []*cli.Command{
subcmdShutdown(),
subcmdRestart(),
subcmdReloadTemplates(),
subcmdFlushQueues(),
subcmdLogging(),
subCmdProcesses(),
},
}
subcmdShutdown = &cli.Command{
}
func subcmdShutdown() *cli.Command {
return &cli.Command{
Name: "shutdown",
Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{
@ -37,7 +41,10 @@ var (
},
Action: runShutdown,
}
subcmdRestart = &cli.Command{
}
func subcmdRestart() *cli.Command {
return &cli.Command{
Name: "restart",
Usage: "Gracefully restart the running process - (not implemented for windows servers)",
Flags: []cli.Flag{
@ -47,7 +54,10 @@ var (
},
Action: runRestart,
}
subcmdReloadTemplates = &cli.Command{
}
func subcmdReloadTemplates() *cli.Command {
return &cli.Command{
Name: "reload-templates",
Usage: "Reload template files in the running process",
Flags: []cli.Flag{
@ -57,7 +67,10 @@ var (
},
Action: runReloadTemplates,
}
subcmdFlushQueues = &cli.Command{
}
func subcmdFlushQueues() *cli.Command {
return &cli.Command{
Name: "flush-queues",
Usage: "Flush queues in the running process",
Action: runFlushQueues,
@ -76,7 +89,10 @@ var (
},
},
}
subCmdProcesses = &cli.Command{
}
func subCmdProcesses() *cli.Command {
return &cli.Command{
Name: "processes",
Usage: "Display running processes within the current process",
Action: runProcesses,
@ -106,10 +122,10 @@ var (
},
},
}
)
}
func runShutdown(c *cli.Context) error {
ctx, cancel := installSignals()
func runShutdown(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -117,8 +133,8 @@ func runShutdown(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
func runRestart(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -126,8 +142,8 @@ func runRestart(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runReloadTemplates(c *cli.Context) error {
ctx, cancel := installSignals()
func runReloadTemplates(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -135,8 +151,8 @@ func runReloadTemplates(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
func runFlushQueues(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -144,8 +160,8 @@ func runFlushQueues(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals()
func runProcesses(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)

View file

@ -4,6 +4,7 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
@ -11,11 +12,11 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/private"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
var (
defaultLoggingFlags = []cli.Flag{
func defaultLoggingFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "logger",
Usage: `Logger name - will default to "default"`,
@ -56,11 +57,13 @@ var (
Name: "debug",
},
}
}
subcmdLogging = &cli.Command{
func subcmdLogging() *cli.Command {
return &cli.Command{
Name: "logging",
Usage: "Adjust logging commands",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "pause",
Usage: "Pause logging (Forgejo will buffer logs up to a certain point and will drop them after that point)",
@ -104,11 +107,11 @@ var (
}, {
Name: "add",
Usage: "Add a logger",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "file",
Usage: "Add a file logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.StringFlag{
Name: "filename",
Aliases: []string{"f"},
@ -152,7 +155,7 @@ var (
}, {
Name: "conn",
Usage: "Add a net conn logger",
Flags: append(defaultLoggingFlags, []cli.Flag{
Flags: append(defaultLoggingFlags(), []cli.Flag{
&cli.BoolFlag{
Name: "reconnect-on-message",
Aliases: []string{"R"},
@ -193,10 +196,10 @@ var (
},
},
}
)
}
func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runRemoveLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -210,8 +213,8 @@ func runRemoveLogger(c *cli.Context) error {
return handleCliResponseExtra(extra)
}
func runAddConnLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddConnLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -237,11 +240,11 @@ func runAddConnLogger(c *cli.Context) error {
if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
}
return commonAddLogger(c, mode, vals)
return commonAddLogger(ctx, c, mode, vals)
}
func runAddFileLogger(c *cli.Context) error {
ctx, cancel := installSignals()
func runAddFileLogger(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -270,10 +273,10 @@ func runAddFileLogger(c *cli.Context) error {
if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level")
}
return commonAddLogger(c, mode, vals)
return commonAddLogger(ctx, c, mode, vals)
}
func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String()
}
@ -300,15 +303,15 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if c.IsSet("writer") {
writer = c.String("writer")
}
ctx, cancel := installSignals()
ctx, cancel := installSignals(ctx)
defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra)
}
func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runPauseLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -317,8 +320,8 @@ func runPauseLogging(c *cli.Context) error {
return nil
}
func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runResumeLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -327,8 +330,8 @@ func runResumeLogging(c *cli.Context) error {
return nil
}
func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals()
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)
@ -337,8 +340,8 @@ func runReleaseReopenLogging(c *cli.Context) error {
return nil
}
func runSetLogSQL(c *cli.Context) error {
ctx, cancel := installSignals()
func runSetLogSQL(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setup(ctx, c.Bool("debug"), false)

View file

@ -11,19 +11,21 @@ import (
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdMigrate represents the available migrate sub-command.
var CmdMigrate = &cli.Command{
func cmdMigrate() *cli.Command {
return &cli.Command{
Name: "migrate",
Usage: "Migrate the database",
Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.",
Action: runMigrate,
}
}
func runMigrate(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runMigrate(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -22,12 +22,13 @@ import (
"forgejo.org/modules/setting"
"forgejo.org/modules/storage"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"xorm.io/xorm"
)
// CmdMigrateStorage represents the available migrate storage sub-command.
var CmdMigrateStorage = &cli.Command{
func cmdMigrateStorage() *cli.Command {
return &cli.Command{
Name: "migrate-storage",
Usage: "Migrate the storage",
Description: "Copies stored files from storage configured in app.ini to parameter-configured storage",
@ -96,6 +97,7 @@ var CmdMigrateStorage = &cli.Command{
},
},
}
}
func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, attach *repo_model.Attachment) error {
@ -182,8 +184,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
})
}
func runMigrateStorage(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals(stdCtx)
defer cancel()
if err := initDB(stdCtx); err != nil {

View file

@ -4,16 +4,18 @@
package cmd
import (
"context"
"strings"
"forgejo.org/modules/private"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// CmdRestoreRepository represents the available restore a repository sub-command.
var CmdRestoreRepository = &cli.Command{
func cmdRestoreRepository() *cli.Command {
return &cli.Command{
Name: "restore-repo",
Usage: "Restore the repository from disk",
Description: "This is a command for restoring the repository data.",
@ -47,9 +49,10 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
},
},
}
}
func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
func runRestoreRepository(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
setting.MustInstalled()

View file

@ -33,7 +33,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
const (
@ -41,7 +41,8 @@ const (
)
// CmdServ represents the available serv sub-command.
var CmdServ = &cli.Command{
func cmdServ() *cli.Command {
return &cli.Command{
Name: "serv",
Usage: "(internal) Should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
@ -56,6 +57,7 @@ var CmdServ = &cli.Command{
},
},
}
}
func setup(ctx context.Context, debug, gitNeeded bool) {
if debug {
@ -131,8 +133,8 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
return nil
}
func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
func runServ(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals(ctx)
defer cancel()
// FIXME: This needs to internationalised

View file

@ -26,14 +26,15 @@ import (
"forgejo.org/routers/install"
"github.com/felixge/fgprof"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// PIDFile could be set from build tag
var PIDFile = "/run/gitea.pid"
// CmdWeb represents the available web sub-command.
var CmdWeb = &cli.Command{
func cmdWeb() *cli.Command {
return &cli.Command{
Name: "web",
Usage: "Start the Forgejo web server",
Description: `The Forgejo web server is the only thing you need to run,
@ -69,6 +70,7 @@ and it takes care of all the other things for you`,
},
},
}
}
func runHTTPRedirector() {
_, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true)
@ -128,7 +130,7 @@ func showWebStartupMessage(msg string) {
}
}
func serveInstall(ctx *cli.Context) error {
func serveInstall(_ context.Context, ctx *cli.Command) error {
showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
@ -161,7 +163,7 @@ func serveInstall(ctx *cli.Context) error {
return nil
}
func serveInstalled(ctx *cli.Context) error {
func serveInstalled(_ context.Context, ctx *cli.Command) error {
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
setting.MustInstalled()
@ -233,7 +235,7 @@ func servePprof() {
finished()
}
func runWeb(ctx *cli.Context) error {
func runWeb(ctx context.Context, cli *cli.Command) error {
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
@ -251,12 +253,12 @@ func runWeb(ctx *cli.Context) error {
}
// Set pid file setting
if ctx.IsSet("pid") {
createPIDFile(ctx.String("pid"))
if cli.IsSet("pid") {
createPIDFile(cli.String("pid"))
}
if !setting.InstallLock {
if err := serveInstall(ctx); err != nil {
if err := serveInstall(ctx, cli); err != nil {
return err
}
} else {
@ -267,7 +269,7 @@ func runWeb(ctx *cli.Context) error {
go servePprof()
}
return serveInstalled(ctx)
return serveInstalled(ctx, cli)
}
func setPort(port string) error {

View file

@ -4,16 +4,17 @@
package main
import (
"context"
"os"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func main() {
app := cli.NewApp()
app := cli.Command{}
app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the forgejo configuration
@ -72,13 +73,13 @@ func main() {
},
}
app.Action = runEnvironmentToIni
err := app.Run(os.Args)
err := app.Run(context.Background(), os.Args)
if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err)
}
}
func runEnvironmentToIni(c *cli.Context) error {
func runEnvironmentToIni(ctx context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{

9
go.mod
View file

@ -5,7 +5,7 @@ go 1.24
toolchain go1.24.3
require (
code.forgejo.org/f3/gof3/v3 v3.10.6
code.forgejo.org/f3/gof3/v3 v3.10.8
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
code.forgejo.org/forgejo/go-rpmutils v1.0.0
code.forgejo.org/forgejo/levelqueue v1.0.0
@ -94,12 +94,13 @@ require (
github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.6
github.com/urfave/cli/v3 v3.3.3
github.com/valyala/fastjson v1.6.4
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.12
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.126.0
go.uber.org/mock v0.5.1
gitlab.com/gitlab-org/api/client-go v0.129.0
go.uber.org/mock v0.5.2
golang.org/x/crypto v0.38.0
golang.org/x/image v0.27.0
golang.org/x/net v0.40.0
@ -237,7 +238,7 @@ require (
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.31.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect

18
go.sum
View file

@ -1,7 +1,7 @@
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
code.forgejo.org/f3/gof3/v3 v3.10.6 h1:Ru/Iz+pqM8IPi7atUHE7+q7v3O3DRbYgMFqrFTsO1m8=
code.forgejo.org/f3/gof3/v3 v3.10.6/go.mod h1:K6lQCWQIyN/5rjP/OJL9fMA6fd++satndE20w/I6Kss=
code.forgejo.org/f3/gof3/v3 v3.10.8 h1:cL5XgOcKffqMdKDOqGCXfMc2OBX89xYvGSj2mz3E/VQ=
code.forgejo.org/f3/gof3/v3 v3.10.8/go.mod h1:ovgb7R8o7k6poQKQ+KWXHOD9uIoanB6tNSmA3kKOMCI=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM=
code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs=
@ -535,6 +535,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I=
github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@ -562,8 +564,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
gitlab.com/gitlab-org/api/client-go v0.126.0 h1:VV5TdkF6pMbEdFGvbR2CwEgJwg6qdg1u3bj5eD2tiWk=
gitlab.com/gitlab-org/api/client-go v0.126.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE=
gitlab.com/gitlab-org/api/client-go v0.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE=
gitlab.com/gitlab-org/api/client-go v0.129.0/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -571,8 +573,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
@ -684,8 +686,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

1
release-notes/8035.md Normal file
View file

@ -0,0 +1 @@
The `forgejo docs` command is deprecated and CLI errors are now displayed on stderr instead of stdout. These breaking changes happened because the package used to parse the command line arguments was [upgraded from v2 to v3](https://cli.urfave.org/migrate-v2-to-v3/). A [separate project was initiated](https://github.com/urfave/cli-docs) to re-implement the `docs` command, but it is not yet production ready.

View file

@ -213,7 +213,7 @@ func (g *GitlabDownloader) GetTopics() ([]string, error) {
if err != nil {
return nil, err
}
return gr.TagList, err
return gr.Topics, err
}
// GetMilestones returns milestones

View file

@ -4,6 +4,7 @@
package integration
import (
"fmt"
"net/url"
"testing"
@ -18,7 +19,7 @@ import (
func Test_Cmd_AdminUser(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
for _, testCase := range []struct {
for i, testCase := range []struct {
name string
options []string
mustChangePassword bool
@ -46,7 +47,7 @@ func Test_Cmd_AdminUser(t *testing.T) {
} {
t.Run(testCase.name, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
name := "testuser"
name := fmt.Sprintf("testuser%d", i)
options := []string{"user", "create", "--username", name, "--password", "password", "--email", name + "@example.com"}
options = append(options, testCase.options...)

View file

@ -25,7 +25,7 @@ import (
f3_tests "code.forgejo.org/f3/gof3/v3/tree/tests/f3"
f3_tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
func runApp(ctx context.Context, args ...string) (string, error) {
@ -33,10 +33,10 @@ func runApp(ctx context.Context, args ...string) (string, error) {
ctx = f3_logger.ContextSetLogger(ctx, l)
ctx = forgejo.ContextSetNoInit(ctx, true)
app := cli.NewApp()
app := cli.Command{}
app.Writer = l.GetBuffer()
app.ErrWriter = l.GetBuffer()
app.Root().Writer = l.GetBuffer()
app.Root().ErrWriter = l.GetBuffer()
defer func() {
if r := recover(); r != nil {
@ -48,7 +48,7 @@ func runApp(ctx context.Context, args ...string) (string, error) {
app.Commands = []*cli.Command{
forgejo.SubcmdF3Mirror(ctx),
}
err := app.Run(args)
err := app.Run(ctx, args)
fmt.Println(l.String())

View file

@ -24,8 +24,8 @@ func Test_CmdKeys(t *testing.T) {
wantErr bool
expectedOutput string
}{
{"test_empty_1", []string{"--username=git", "--type=test", "--content=test"}, true, ""},
{"test_empty_2", []string{"-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, ""},
{"test_empty_1", []string{"--username=git", "--type=test", "--content=test"}, true, "Command error: internal API error response, status=500, err=public key does not exist [id: 0]\n"},
{"test_empty_2", []string{"-e", "git", "-u", "git", "-t", "test", "-k", "test"}, true, "Command error: internal API error response, status=500, err=public key does not exist [id: 0]\n"},
{
"with_key",
[]string{"-e", "git", "-u", "git", "-t", "ssh-rsa", "-k", "AAAAB3NzaC1yc2EAAAADAQABAAABgQDWVj0fQ5N8wNc0LVNA41wDLYJ89ZIbejrPfg/avyj3u/ZohAKsQclxG4Ju0VirduBFF9EOiuxoiFBRr3xRpqzpsZtnMPkWVWb+akZwBFAx8p+jKdy4QXR/SZqbVobrGwip2UjSrri1CtBxpJikojRIZfCnDaMOyd9Jp6KkujvniFzUWdLmCPxUE9zhTaPu0JsEP7MW0m6yx7ZUhHyfss+NtqmFTaDO+QlMR7L2QkDliN2Jl3Xa3PhuWnKJfWhdAq1Cw4oraKUOmIgXLkuiuxVQ6mD3AiFupkmfqdHq6h+uHHmyQqv3gU+/sD8GbGAhf6ftqhTsXjnv1Aj4R8NoDf9BS6KRkzkeun5UisSzgtfQzjOMEiJtmrep2ZQrMGahrXa+q4VKr0aKJfm+KlLfwm/JztfsBcqQWNcTURiCFqz+fgZw0Ey/de0eyMzldYTdXXNRYCKjs9bvBK+6SSXRM7AhftfQ0ZuoW5+gtinPrnmoOaSCEJbAiEiTO/BzOHgowiM="},
@ -44,10 +44,11 @@ func Test_CmdKeys(t *testing.T) {
}
if tt.wantErr {
require.Error(t, err)
assert.Equal(t, tt.expectedOutput, string(exitErr.Stderr))
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.expectedOutput, out)
}
})
}
})

View file

@ -109,6 +109,9 @@ func runMainAppWithStdin(stdin io.Reader, subcommand string, args ...string) (st
"GITEA_WORK_DIR="+setting.AppWorkPath)
cmd.Stdin = stdin
out, err := cmd.Output()
if ee, ok := err.(*exec.ExitError); ok {
log.Error("%s %v exit on error %s", os.Args[0], args, ee.Stderr)
}
return string(out), err
}