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\#% .PHONY: test\#%
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) @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES)
.PHONY: coverage .PHONY: coverage

View file

@ -934,6 +934,11 @@
"path": "github.com/urfave/cli/v2/LICENSE", "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" "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", "name": "github.com/valyala/fastjson",
"path": "github.com/valyala/fastjson/LICENSE", "path": "github.com/valyala/fastjson/LICENSE",

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ import (
"forgejo.org/models/auth" "forgejo.org/models/auth"
"forgejo.org/services/auth/source/ldap" "forgejo.org/services/auth/source/ldap"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
type ( type (
@ -23,8 +23,8 @@ type (
} }
) )
var ( func commonLdapCLIFlags() []cli.Flag {
commonLdapCLIFlags = []cli.Flag{ return []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Authentication name.", Usage: "Authentication name.",
@ -102,8 +102,10 @@ var (
Usage: "The attribute of the users LDAP record containing the users avatar.", Usage: "The attribute of the users LDAP record containing the users avatar.",
}, },
} }
}
ldapBindDnCLIFlags = append(commonLdapCLIFlags, func ldapBindDnCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "bind-dn", Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.", Usage: "The DN to bind to the LDAP server with when searching for the user.",
@ -128,49 +130,59 @@ var (
Name: "page-size", Name: "page-size",
Usage: "Search page size.", Usage: "Search page size.",
}) })
}
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, func ldapSimpleAuthCLIFlags() []cli.Flag {
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "user-dn", Name: "user-dn",
Usage: "The user's DN.", Usage: "The user's DN.",
}) })
}
microcmdAuthAddLdapBindDn = &cli.Command{ func microcmdAuthAddLdapBindDn() *cli.Command {
return &cli.Command{
Name: "add-ldap", Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source", Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapBindDn(c) return newAuthService().addLdapBindDn(ctx, cli)
}, },
Flags: ldapBindDnCLIFlags, Flags: ldapBindDnCLIFlags(),
} }
}
microcmdAuthUpdateLdapBindDn = &cli.Command{ func microcmdAuthUpdateLdapBindDn() *cli.Command {
return &cli.Command{
Name: "update-ldap", Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source", Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapBindDn(c) 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", Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source", Usage: "Add new LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().addLdapSimpleAuth(c) return newAuthService().addLdapSimpleAuth(ctx, cli)
}, },
Flags: ldapSimpleAuthCLIFlags, Flags: ldapSimpleAuthCLIFlags(),
} }
}
microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
return &cli.Command{
Name: "update-ldap-simple", Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source", Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cli *cli.Command) error {
return newAuthService().updateLdapSimpleAuth(c) return newAuthService().updateLdapSimpleAuth(ctx, cli)
}, },
Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
} }
) }
// newAuthService creates a service with default functions. // newAuthService creates a service with default functions.
func newAuthService() *authService { func newAuthService() *authService {
@ -183,7 +195,7 @@ func newAuthService() *authService {
} }
// parseAuthSource assigns values on authSource according to command line flags. // 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") { if c.IsSet("name") {
authSource.Name = c.String("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. // 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") { if c.IsSet("name") {
config.Name = c.String("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. // 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. // 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 { if err := argsSet(c, "id"); err != nil {
return nil, err 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. // 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 { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { 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. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(c *cli.Context) error { func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { 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. // 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 { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { 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. // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@ -13,10 +14,11 @@ import (
"forgejo.org/modules/setting" "forgejo.org/modules/setting"
user_service "forgejo.org/services/user" 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", Name: "change-password",
Usage: "Change a user's password", Usage: "Change a user's password",
Action: runChangePassword, Action: runChangePassword,
@ -39,14 +41,15 @@ var microcmdUserChangePassword = &cli.Command{
Value: true, Value: true,
}, },
}, },
}
} }
func runChangePassword(c *cli.Context) error { func runChangePassword(ctx context.Context, c *cli.Command) error {
if err := argsSet(c, "username", "password"); err != nil { if err := argsSet(c, "username", "password"); err != nil {
return err return err
} }
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -15,10 +16,11 @@ import (
"forgejo.org/modules/optional" "forgejo.org/modules/optional"
"forgejo.org/modules/setting" "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", Name: "create",
Usage: "Create a new user in database", Usage: "Create a new user in database",
Action: runCreateUser, Action: runCreateUser,
@ -51,7 +53,6 @@ var microcmdUserCreate = &cli.Command{
Name: "must-change-password", Name: "must-change-password",
Usage: "Set this option to false to prevent forcing the user to change their password after initial login", Usage: "Set this option to false to prevent forcing the user to change their password after initial login",
Value: true, Value: true,
DisableDefaultText: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
@ -81,9 +82,10 @@ var microcmdUserCreate = &cli.Command{
Usage: `The full, human-readable name of the user`, Usage: `The full, human-readable name of the user`,
}, },
}, },
}
} }
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 // 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. // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings() setting.LoadSettings()
@ -108,10 +110,10 @@ func runCreateUser(c *cli.Context) error {
username = c.String("username") username = c.String("username")
} else { } else {
username = c.String("name") 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() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -12,10 +13,11 @@ import (
"forgejo.org/modules/storage" "forgejo.org/modules/storage"
user_service "forgejo.org/services/user" 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", Name: "delete",
Usage: "Delete specific user by id, name or email", Usage: "Delete specific user by id, name or email",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -39,14 +41,15 @@ var microcmdUserDelete = &cli.Command{
}, },
}, },
Action: runDeleteUser, 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") { 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") 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() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

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

View file

@ -4,16 +4,18 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
user_model "forgejo.org/models/user" 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", Name: "list",
Usage: "List users", Usage: "List users",
Action: runListUsers, Action: runListUsers,
@ -23,10 +25,11 @@ var microcmdUserList = &cli.Command{
Usage: "List only admin users", Usage: "List only admin users",
}, },
}, },
}
} }
func runListUsers(c *cli.Context) error { func runListUsers(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {

View file

@ -4,15 +4,17 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
user_model "forgejo.org/models/user" 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", Name: "must-change-password",
Usage: "Set the must change password flag for the provided users or all users", Usage: "Set the must change password flag for the provided users or all users",
Action: runMustChangePassword, Action: runMustChangePassword,
@ -32,10 +34,11 @@ var microcmdUserMustChangePassword = &cli.Command{
Usage: "Instead of setting the must-change-password flag, unset it", Usage: "Instead of setting the must-change-password flag, unset it",
}, },
}, },
}
} }
func runMustChangePassword(c *cli.Context) error { func runMustChangePassword(ctx context.Context, c *cli.Command) error {
ctx, cancel := installSignals() ctx, cancel := installSignals(ctx)
defer cancel() defer cancel()
if c.NArg() == 0 && !c.IsSet("all") { if c.NArg() == 0 && !c.IsSet("all") {

View file

@ -6,6 +6,7 @@
package cmd package cmd
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
@ -20,11 +21,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdCert represents the available cert sub-command. // CmdCert represents the available cert sub-command.
var CmdCert = &cli.Command{ func cmdCert() *cli.Command {
return &cli.Command{
Name: "cert", Name: "cert",
Usage: "Generate self-signed certificate", Usage: "Generate self-signed certificate",
Description: `Generate a self-signed X.509 certificate for a TLS server. Description: `Generate a self-signed X.509 certificate for a TLS server.
@ -61,6 +63,7 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Usage: "whether this cert should be its own Certificate Authority", Usage: "whether this cert should be its own Certificate Authority",
}, },
}, },
}
} }
func publicKey(priv any) any { func publicKey(priv any) any {
@ -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 { if err := argsSet(c, "host"); err != nil {
return err return err
} }

View file

@ -20,21 +20,23 @@ import (
"forgejo.org/modules/setting" "forgejo.org/modules/setting"
"forgejo.org/modules/util" "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 // argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context. // 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 { for _, a := range args {
if !c.IsSet(a) { if !c.IsSet(a) {
return errors.New(a + " is not set") 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 errors.New(a + " is required")
} }
} }
}
return nil return nil
} }
@ -73,8 +75,8 @@ If this is the intended configuration file complete the [database] section.`, se
return nil return nil
} }
func installSignals() (context.Context, context.CancelFunc) { func installSignals(ctx context.Context) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(ctx)
go func() { go func() {
// install notify // install notify
signalChannel := make(chan os.Signal, 1) 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) 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() { for _, ctx := range c.Lineage() {
if ctx.Bool(name) { if ctx.Bool(name) {
return true 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. // 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. // 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 { func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, cli *cli.Command) (context.Context, error) {
return func(c *cli.Context) error { return func(ctx context.Context, cli *cli.Command) (context.Context, error) {
level := defaultLevel level := defaultLevel
if globalBool(c, "quiet") { if globalBool(cli, "quiet") {
level = log.FATAL level = log.FATAL
} }
if globalBool(c, "debug") || globalBool(c, "verbose") { if globalBool(cli, "debug") || globalBool(cli, "verbose") {
level = log.TRACE level = log.TRACE
} }
log.SetConsoleLogger(log.DEFAULT, "console-default", level) 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 package cmd
import ( import (
"context"
"fmt" "fmt"
golog "log" golog "log"
"os" "os"
@ -19,23 +20,26 @@ import (
"forgejo.org/modules/setting" "forgejo.org/modules/setting"
"forgejo.org/services/doctor" "forgejo.org/services/doctor"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// CmdDoctor represents the available doctor sub-command. // CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{ func cmdDoctor() *cli.Command {
return &cli.Command{
Name: "doctor", Name: "doctor",
Usage: "Diagnose and optionally fix problems, convert or re-create database tables", 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.", 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{ Commands: []*cli.Command{
cmdDoctorCheck, cmdDoctorCheck(),
cmdRecreateTable, cmdRecreateTable(),
cmdDoctorConvert, cmdDoctorConvert(),
}, },
}
} }
var cmdDoctorCheck = &cli.Command{ func cmdDoctorCheck() *cli.Command {
return &cli.Command{
Name: "check", Name: "check",
Usage: "Diagnose and optionally fix problems", 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.", 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.",
@ -71,9 +75,11 @@ var cmdDoctorCheck = &cli.Command{
Usage: "Use color for outputted information", Usage: "Use color for outputted information",
}, },
}, },
}
} }
var cmdRecreateTable = &cli.Command{ func cmdRecreateTable() *cli.Command {
return &cli.Command{
Name: "recreate-table", Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.", Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
@ -89,10 +95,11 @@ 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.`, You should back-up your database before doing this and ensure that your database is up-to-date first.`,
Action: runRecreateTable, Action: runRecreateTable,
}
} }
func runRecreateTable(ctx *cli.Context) error { func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals() stdCtx, cancel := installSignals(stdCtx)
defer cancel() defer cancel()
// Redirect the default golog to here // 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 // Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
@ -165,8 +172,8 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
} }
} }
func runDoctorCheck(ctx *cli.Context) error { func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
stdCtx, cancel := installSignals() stdCtx, cancel := installSignals(stdCtx)
defer cancel() defer cancel()
colorize := log.CanColorStdout colorize := log.CanColorStdout

View file

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

View file

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

View file

@ -5,6 +5,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -23,7 +24,7 @@ import (
"code.forgejo.org/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/mholt/archiver/v3" "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 { 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()) return fmt.Errorf("allowed values are %s", o.Join())
} }
func (o *outputType) Get() any {
return o.String()
}
func (o outputType) String() string { func (o outputType) String() string {
if o.selected == "" { if o.selected == "" {
return o.Default return o.Default
@ -97,7 +102,8 @@ var outputTypeEnum = &outputType{
} }
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{ func cmdDump() *cli.Command {
return &cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Forgejo files and database", Usage: "Dump Forgejo files and database",
Description: `Dump compresses all related files and database into zip file. Description: `Dump compresses all related files and database into zip file.
@ -170,6 +176,7 @@ It can be used for backup and capture Forgejo server image to send to maintainer
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
}, },
}, },
}
} }
func fatal(format string, args ...any) { func fatal(format string, args ...any) {
@ -177,7 +184,7 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(ctx *cli.Context) error { func runDump(stdCtx context.Context, ctx *cli.Command) error {
var file *os.File var file *os.File
fileName := ctx.String("file") fileName := ctx.String("file")
outType := ctx.String("type") outType := ctx.String("type")
@ -222,7 +229,7 @@ func runDump(ctx *cli.Context) error {
return errors.New("--quiet and --verbose cannot both be set") return errors.New("--quiet and --verbose cannot both be set")
} }
stdCtx, cancel := installSignals() stdCtx, cancel := installSignals(stdCtx)
defer cancel() defer cancel()
err := db.InitEngine(stdCtx) err := db.InitEngine(stdCtx)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -17,7 +18,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
func TestMain(m *testing.M) { 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) 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") app := NewMainApp("version", "version-extra")
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithConfig(testCmd, appGlobalFlags()) prepareSubcommandWithConfig(testCmd, appGlobalFlags)
app.Commands = append(app.Commands, testCmd) app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name app.DefaultCommand = testCmd.Name
return app return app
@ -43,7 +44,7 @@ type runResult struct {
ExitCode int ExitCode int
} }
func runTestApp(app *cli.App, args ...string) (runResult, error) { func runTestApp(app *cli.Command, args ...string) (runResult, error) {
outBuf := new(strings.Builder) outBuf := new(strings.Builder)
errBuf := new(strings.Builder) errBuf := new(strings.Builder)
app.Writer = outBuf app.Writer = outBuf
@ -66,7 +67,6 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)" cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)" cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct { 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 { for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) { 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 { for k, v := range c.env {
t.Setenv(k, v) t.Setenv(k, v)
} }
@ -123,34 +128,34 @@ func TestCliCmd(t *testing.T) {
r, err := runTestApp(app, args...) r, err := runTestApp(app, args...)
require.NoError(t, err, c.cmd) require.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, 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) { 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") r, err := runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr) 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") r, err = runTestApp(app, "./gitea", "test-cmd")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 2, r.ExitCode) assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr) 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") r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
require.Error(t, err) require.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in 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") r, err = runTestApp(app, "./gitea", "test-cmd")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

9
go.mod
View file

@ -5,7 +5,7 @@ go 1.24
toolchain go1.24.3 toolchain go1.24.3
require ( 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-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
code.forgejo.org/forgejo/go-rpmutils v1.0.0 code.forgejo.org/forgejo/go-rpmutils v1.0.0
code.forgejo.org/forgejo/levelqueue 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/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.12 github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.6 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/valyala/fastjson v1.6.4
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.12 github.com/yuin/goldmark v1.7.12
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.126.0 gitlab.com/gitlab-org/api/client-go v0.129.0
go.uber.org/mock v0.5.1 go.uber.org/mock v0.5.2
golang.org/x/crypto v0.38.0 golang.org/x/crypto v0.38.0
golang.org/x/image v0.27.0 golang.org/x/image v0.27.0
golang.org/x/net v0.40.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 v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/mod v0.24.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 golang.org/x/tools v0.31.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // 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 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 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.8 h1:cL5XgOcKffqMdKDOqGCXfMc2OBX89xYvGSj2mz3E/VQ=
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/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 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU=
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM= 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= 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/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 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 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 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 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/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 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= 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.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE=
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/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 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 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/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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 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.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 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 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.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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 { if err != nil {
return nil, err return nil, err
} }
return gr.TagList, err return gr.Topics, err
} }
// GetMilestones returns milestones // GetMilestones returns milestones

View file

@ -4,6 +4,7 @@
package integration package integration
import ( import (
"fmt"
"net/url" "net/url"
"testing" "testing"
@ -18,7 +19,7 @@ import (
func Test_Cmd_AdminUser(t *testing.T) { func Test_Cmd_AdminUser(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) { onGiteaRun(t, func(*testing.T, *url.URL) {
for _, testCase := range []struct { for i, testCase := range []struct {
name string name string
options []string options []string
mustChangePassword bool mustChangePassword bool
@ -46,7 +47,7 @@ func Test_Cmd_AdminUser(t *testing.T) {
} { } {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
defer tests.PrintCurrentTest(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 := []string{"user", "create", "--username", name, "--password", "password", "--email", name + "@example.com"}
options = append(options, testCase.options...) 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 "code.forgejo.org/f3/gof3/v3/tree/tests/f3"
f3_tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge" f3_tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
func runApp(ctx context.Context, args ...string) (string, error) { 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 = f3_logger.ContextSetLogger(ctx, l)
ctx = forgejo.ContextSetNoInit(ctx, true) ctx = forgejo.ContextSetNoInit(ctx, true)
app := cli.NewApp() app := cli.Command{}
app.Writer = l.GetBuffer() app.Root().Writer = l.GetBuffer()
app.ErrWriter = l.GetBuffer() app.Root().ErrWriter = l.GetBuffer()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -48,7 +48,7 @@ func runApp(ctx context.Context, args ...string) (string, error) {
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
forgejo.SubcmdF3Mirror(ctx), forgejo.SubcmdF3Mirror(ctx),
} }
err := app.Run(args) err := app.Run(ctx, args)
fmt.Println(l.String()) fmt.Println(l.String())

View file

@ -24,8 +24,8 @@ func Test_CmdKeys(t *testing.T) {
wantErr bool wantErr bool
expectedOutput string expectedOutput string
}{ }{
{"test_empty_1", []string{"--username=git", "--type=test", "--content=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, ""}, {"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", "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="}, []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 { if tt.wantErr {
require.Error(t, err) require.Error(t, err)
assert.Equal(t, tt.expectedOutput, string(exitErr.Stderr))
} else { } else {
require.NoError(t, err) require.NoError(t, err)
}
assert.Equal(t, tt.expectedOutput, out) 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) "GITEA_WORK_DIR="+setting.AppWorkPath)
cmd.Stdin = stdin cmd.Stdin = stdin
out, err := cmd.Output() 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 return string(out), err
} }