Refactor to use urfave/cli/v2 (#25959)

Replace #10912

And there are many new tests to cover the CLI behavior

There were some concerns about the "option order in hook scripts"
(https://github.com/go-gitea/gitea/pull/10912#issuecomment-1137543314),
it's not a problem now. Because the hook script uses `/gitea hook
--config=/app.ini pre-receive` format. The "config" is a global option,
it can appear anywhere.

----

## ⚠️ BREAKING ⚠️

This PR does it best to avoid breaking anything. The major changes are:

* `gitea` itself won't accept web's options: `--install-port` / `--pid`
/ `--port` / `--quiet` / `--verbose` .... They are `web` sub-command's
options.
    * Use `./gitea web --pid ....` instead
* `./gitea` can still run the `web` sub-command as shorthand, with
default options
* The sub-command's options must follow the sub-command
* Before: `./gitea --sub-opt subcmd` might equal to `./gitea subcmd
--sub-opt` (well, might not ...)
    * After: only `./gitea subcmd --sub-opt` could be used
    * The global options like `--config` are not affected
pull/26036/head^2
wxiaoguang 1 year ago committed by GitHub
parent 840830b655
commit d0dbe52e76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      assets/go-licenses.json
  2. 17
      cmd/actions.go
  3. 133
      cmd/admin.go
  4. 62
      cmd/admin_auth_ldap.go
  5. 2
      cmd/admin_auth_ldap_test.go
  6. 6
      cmd/admin_user.go
  7. 22
      cmd/admin_user_change_password.go
  8. 24
      cmd/admin_user_create.go
  9. 22
      cmd/admin_user_delete.go
  10. 24
      cmd/admin_user_generate_access_token.go
  11. 6
      cmd/admin_user_list.go
  12. 22
      cmd/admin_user_must_change_password.go
  13. 16
      cmd/cert.go
  14. 15
      cmd/cmd.go
  15. 4
      cmd/convert.go
  16. 9
      cmd/docs.go
  17. 29
      cmd/doctor.go
  18. 69
      cmd/dump.go
  19. 29
      cmd/dump_repo.go
  20. 50
      cmd/embedded.go
  21. 16
      cmd/generate.go
  22. 22
      cmd/hook.go
  23. 42
      cmd/keys.go
  24. 2
      cmd/mailer.go
  25. 196
      cmd/main.go
  26. 115
      cmd/main_test.go
  27. 40
      cmd/manager.go
  28. 155
      cmd/manager_logging.go
  29. 4
      cmd/migrate.go
  30. 49
      cmd/migrate_storage.go
  31. 21
      cmd/restore_repo.go
  32. 20
      cmd/serv.go
  33. 33
      cmd/web.go
  34. 28
      contrib/backport/backport.go
  35. 10
      contrib/environment-to-ini/environment-to-ini.go
  36. 2
      custom/conf/app.example.ini
  37. 2
      docs/content/doc/administration/config-cheat-sheet.en-us.md
  38. 3
      go.mod
  39. 7
      go.sum
  40. 154
      main.go
  41. 36
      tests/integration/cmd_keys_test.go

File diff suppressed because one or more lines are too long

@ -9,30 +9,31 @@ import (
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
// CmdActions represents the available actions sub-commands. // CmdActions represents the available actions sub-commands.
CmdActions = cli.Command{ CmdActions = &cli.Command{
Name: "actions", Name: "actions",
Usage: "", Usage: "",
Description: "Commands for managing Gitea Actions", Description: "Commands for managing Gitea Actions",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken, subcmdActionsGenRunnerToken,
}, },
} }
subcmdActionsGenRunnerToken = cli.Command{ subcmdActionsGenRunnerToken = &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,
Aliases: []string{"grt"}, Aliases: []string{"grt"},
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "scope, s", Name: "scope",
Value: "", Aliases: []string{"s"},
Usage: "{owner}[/{repo}] - leave empty for a global runner", Value: "",
Usage: "{owner}[/{repo}] - leave empty for a global runner",
}, },
}, },
} }

@ -26,15 +26,15 @@ import (
"code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
// CmdAdmin represents the available admin sub-command. // CmdAdmin represents the available admin sub-command.
CmdAdmin = cli.Command{ CmdAdmin = &cli.Command{
Name: "admin", Name: "admin",
Usage: "Command line interface to perform common administrative operations", Usage: "Command line interface to perform common administrative operations",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
subcmdUser, subcmdUser,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
@ -43,37 +43,37 @@ var (
}, },
} }
subcmdRepoSyncReleases = cli.Command{ subcmdRepoSyncReleases = &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{ subcmdRegenerate = &cli.Command{
Name: "regenerate", Name: "regenerate",
Usage: "Regenerate specific files", Usage: "Regenerate specific files",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
microcmdRegenHooks, microcmdRegenHooks,
microcmdRegenKeys, microcmdRegenKeys,
}, },
} }
microcmdRegenHooks = cli.Command{ microcmdRegenHooks = &cli.Command{
Name: "hooks", Name: "hooks",
Usage: "Regenerate git-hooks", Usage: "Regenerate git-hooks",
Action: runRegenerateHooks, Action: runRegenerateHooks,
} }
microcmdRegenKeys = cli.Command{ microcmdRegenKeys = &cli.Command{
Name: "keys", Name: "keys",
Usage: "Regenerate authorized_keys file", Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys, Action: runRegenerateKeys,
} }
subcmdAuth = cli.Command{ subcmdAuth = &cli.Command{
Name: "auth", Name: "auth",
Usage: "Modify external auth providers", Usage: "Modify external auth providers",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
microcmdAuthAddOauth, microcmdAuthAddOauth,
microcmdAuthUpdateOauth, microcmdAuthUpdateOauth,
cmdAuthAddLdapBindDn, cmdAuthAddLdapBindDn,
@ -87,44 +87,44 @@ var (
}, },
} }
microcmdAuthList = cli.Command{ microcmdAuthList = &cli.Command{
Name: "list", Name: "list",
Usage: "List auth sources", Usage: "List auth sources",
Action: runListAuth, Action: runListAuth,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.IntFlag{ &cli.IntFlag{
Name: "min-width", Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table", Usage: "Minimal cell width including any padding for the formatted table",
Value: 0, Value: 0,
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "tab-width", Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)", Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8, Value: 8,
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "padding", Name: "padding",
Usage: "padding added to a cell before computing its width", Usage: "padding added to a cell before computing its width",
Value: 1, Value: 1,
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "pad-char", Name: "pad-char",
Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`, Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`,
Value: "\t", Value: "\t",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "vertical-bars", Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns", Usage: "Set to true to print vertical bars between columns",
}, },
}, },
} }
idFlag = cli.Int64Flag{ idFlag = &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "ID of authentication source", Usage: "ID of authentication source",
} }
microcmdAuthDelete = cli.Command{ microcmdAuthDelete = &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific auth source", Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag}, Flags: []cli.Flag{idFlag},
@ -132,207 +132,208 @@ var (
} }
oauthCLIFlags = []cli.Flag{ oauthCLIFlags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
Usage: "Application Name", Usage: "Application Name",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "provider", Name: "provider",
Value: "", Value: "",
Usage: "OAuth2 Provider", Usage: "OAuth2 Provider",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "key", Name: "key",
Value: "", Value: "",
Usage: "Client ID (Key)", Usage: "Client ID (Key)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "secret", Name: "secret",
Value: "", Value: "",
Usage: "Client Secret", Usage: "Client Secret",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "auto-discover-url", Name: "auto-discover-url",
Value: "", Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)", Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "use-custom-urls", Name: "use-custom-urls",
Value: "false", Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints", Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "custom-tenant-id", Name: "custom-tenant-id",
Value: "", Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints", Usage: "Use custom Tenant ID for OAuth endpoints",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "custom-auth-url", Name: "custom-auth-url",
Value: "", Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)", Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "custom-token-url", Name: "custom-token-url",
Value: "", Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)", Usage: "Use a custom Token URL (option for GitLab/GitHub)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "custom-profile-url", Name: "custom-profile-url",
Value: "", Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)", Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "custom-email-url", Name: "custom-email-url",
Value: "", Value: "",
Usage: "Use a custom Email URL (option for GitHub)", Usage: "Use a custom Email URL (option for GitHub)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "icon-url", Name: "icon-url",
Value: "", Value: "",
Usage: "Custom icon URL for OAuth2 login source", Usage: "Custom icon URL for OAuth2 login source",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source", Usage: "Set to true to skip local 2fa for users authenticated by this source",
}, },
cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "scopes", Name: "scopes",
Value: nil, Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source", Usage: "Scopes to request when to authenticate against this OAuth2 source",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "required-claim-name", Name: "required-claim-name",
Value: "", Value: "",
Usage: "Claim name that has to be set to allow users to login with this source", Usage: "Claim name that has to be set to allow users to login with this source",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "required-claim-value", Name: "required-claim-value",
Value: "", Value: "",
Usage: "Claim value that has to be set to allow users to login with this source", Usage: "Claim value that has to be set to allow users to login with this source",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "group-claim-name", Name: "group-claim-name",
Value: "", Value: "",
Usage: "Claim name providing group names for this source", Usage: "Claim name providing group names for this source",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "admin-group", Name: "admin-group",
Value: "", Value: "",
Usage: "Group Claim value for administrator users", Usage: "Group Claim value for administrator users",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "restricted-group", Name: "restricted-group",
Value: "", Value: "",
Usage: "Group Claim value for restricted users", Usage: "Group Claim value for restricted users",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "group-team-map", Name: "group-team-map",
Value: "", Value: "",
Usage: "JSON mapping between groups and org teams", Usage: "JSON mapping between groups and org teams",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "group-team-map-removal", Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups", Usage: "Activate automatic team membership removal depending on groups",
}, },
} }
microcmdAuthUpdateOauth = cli.Command{ microcmdAuthUpdateOauth = &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:]...)...),
} }
microcmdAuthAddOauth = cli.Command{ microcmdAuthAddOauth = &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,
} }
subcmdSendMail = cli.Command{ subcmdSendMail = &cli.Command{
Name: "sendmail", Name: "sendmail",
Usage: "Send a message to all users", Usage: "Send a message to all users",
Action: runSendMail, Action: runSendMail,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "title", Name: "title",
Usage: `a title of a message`, Usage: `a title of a message`,
Value: "", Value: "",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "content", Name: "content",
Usage: "a content of a message", Usage: "a content of a message",
Value: "", Value: "",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "force,f", Name: "force",
Usage: "A flag to bypass a confirmation step", Aliases: []string{"f"},
Usage: "A flag to bypass a confirmation step",
}, },
}, },
} }
smtpCLIFlags = []cli.Flag{ smtpCLIFlags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
Usage: "Application Name", Usage: "Application Name",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "auth-type", Name: "auth-type",
Value: "PLAIN", Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN", Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Value: "", Value: "",
Usage: "SMTP Host", Usage: "SMTP Host",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "port", Name: "port",
Usage: "SMTP Port", Usage: "SMTP Port",
}, },
cli.BoolTFlag{ &cli.BoolFlag{
Name: "force-smtps", Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
}, },
cli.BoolTFlag{ &cli.BoolFlag{
Name: "skip-verify", Name: "skip-verify",
Usage: "Skip TLS verify.", Usage: "Skip TLS verify.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "helo-hostname", Name: "helo-hostname",
Value: "", Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname", Usage: "Hostname sent with HELO. Leave blank to send current hostname",
}, },
cli.BoolTFlag{ &cli.BoolFlag{
Name: "disable-helo", Name: "disable-helo",
Usage: "Disable SMTP helo.", Usage: "Disable SMTP helo.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "allowed-domains", Name: "allowed-domains",
Value: "", Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')", Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
}, },
cli.BoolTFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.", Usage: "Skip 2FA to log on.",
}, },
cli.BoolTFlag{ &cli.BoolFlag{
Name: "active", Name: "active",
Usage: "This Authentication Source is Activated.", Usage: "This Authentication Source is Activated.",
}, },
} }
microcmdAuthAddSMTP = cli.Command{ microcmdAuthAddSMTP = &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{ microcmdAuthUpdateSMTP = &cli.Command{
Name: "update-smtp", Name: "update-smtp",
Usage: "Update existing SMTP authentication source", Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP, Action: runUpdateSMTP,
@ -611,19 +612,19 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
conf.AllowedDomains = c.String("allowed-domains") conf.AllowedDomains = c.String("allowed-domains")
} }
if c.IsSet("force-smtps") { if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.BoolT("force-smtps") conf.ForceSMTPS = c.Bool("force-smtps")
} }
if c.IsSet("skip-verify") { if c.IsSet("skip-verify") {
conf.SkipVerify = c.BoolT("skip-verify") conf.SkipVerify = c.Bool("skip-verify")
} }
if c.IsSet("helo-hostname") { if c.IsSet("helo-hostname") {
conf.HeloHostname = c.String("helo-hostname") conf.HeloHostname = c.String("helo-hostname")
} }
if c.IsSet("disable-helo") { if c.IsSet("disable-helo") {
conf.DisableHelo = c.BoolT("disable-helo") conf.DisableHelo = c.Bool("disable-helo")
} }
if c.IsSet("skip-local-2fa") { if c.IsSet("skip-local-2fa") {
conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa") conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
} }
return nil return nil
} }
@ -647,7 +648,7 @@ func runAddSMTP(c *cli.Context) error {
} }
active := true active := true
if c.IsSet("active") { if c.IsSet("active") {
active = c.BoolT("active") active = c.Bool("active")
} }
var smtpConfig smtp.Source var smtpConfig smtp.Source
@ -696,7 +697,7 @@ func runUpdateSMTP(c *cli.Context) error {
} }
if c.IsSet("active") { if c.IsSet("active") {
source.IsActive = c.BoolT("active") source.IsActive = c.Bool("active")
} }
source.Cfg = smtpConfig source.Cfg = smtpConfig

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
type ( type (
@ -25,117 +25,117 @@ type (
var ( var (
commonLdapCLIFlags = []cli.Flag{ commonLdapCLIFlags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Authentication name.", Usage: "Authentication name.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "not-active", Name: "not-active",
Usage: "Deactivate the authentication source.", Usage: "Deactivate the authentication source.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "active", Name: "active",
Usage: "Activate the authentication source.", Usage: "Activate the authentication source.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "security-protocol", Name: "security-protocol",
Usage: "Security protocol name.", Usage: "Security protocol name.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-tls-verify", Name: "skip-tls-verify",
Usage: "Disable TLS verification.", Usage: "Disable TLS verification.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Usage: "The address where the LDAP server can be reached.", Usage: "The address where the LDAP server can be reached.",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "port", Name: "port",
Usage: "The port to use when connecting to the LDAP server.", Usage: "The port to use when connecting to the LDAP server.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "user-search-base", Name: "user-search-base",
Usage: "The LDAP base at which user accounts will be searched for.", Usage: "The LDAP base at which user accounts will be searched for.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "user-filter", Name: "user-filter",
Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "admin-filter", Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.", Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "restricted-filter", Name: "restricted-filter",
Usage: "An LDAP filter specifying if a user should be given restricted status.", Usage: "An LDAP filter specifying if a user should be given restricted status.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "allow-deactivate-all", Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.", Usage: "Allow empty search results to deactivate all users.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "username-attribute", Name: "username-attribute",
Usage: "The attribute of the user’s LDAP record containing the user name.", Usage: "The attribute of the user’s LDAP record containing the user name.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "firstname-attribute", Name: "firstname-attribute",
Usage: "The attribute of the user’s LDAP record containing the user’s first name.", Usage: "The attribute of the user’s LDAP record containing the user’s first name.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "surname-attribute", Name: "surname-attribute",
Usage: "The attribute of the user’s LDAP record containing the user’s surname.", Usage: "The attribute of the user’s LDAP record containing the user’s surname.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "email-attribute", Name: "email-attribute",
Usage: "The attribute of the user’s LDAP record containing the user’s email address.", Usage: "The attribute of the user’s LDAP record containing the user’s email address.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "public-ssh-key-attribute", Name: "public-ssh-key-attribute",
Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source", Usage: "Set to true to skip local 2fa for users authenticated by this source",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "avatar-attribute", Name: "avatar-attribute",
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
}, },
} }
ldapBindDnCLIFlags = append(commonLdapCLIFlags, ldapBindDnCLIFlags = 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.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "bind-password", Name: "bind-password",
Usage: "The password for the Bind DN, if any.", Usage: "The password for the Bind DN, if any.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "attributes-in-bind", Name: "attributes-in-bind",
Usage: "Fetch attributes in bind DN context.", Usage: "Fetch attributes in bind DN context.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "synchronize-users", Name: "synchronize-users",
Usage: "Enable user synchronization.", Usage: "Enable user synchronization.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "disable-synchronize-users", Name: "disable-synchronize-users",
Usage: "Disable user synchronization.", Usage: "Disable user synchronization.",
}, },
cli.UintFlag{ &cli.UintFlag{
Name: "page-size", Name: "page-size",
Usage: "Search page size.", Usage: "Search page size.",
}) })
ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
cli.StringFlag{ &cli.StringFlag{
Name: "user-dn", Name: "user-dn",
Usage: "The user’s DN.", Usage: "The user’s DN.",
}) })
cmdAuthAddLdapBindDn = cli.Command{ cmdAuthAddLdapBindDn = &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(c *cli.Context) error {
@ -144,7 +144,7 @@ var (
Flags: ldapBindDnCLIFlags, Flags: ldapBindDnCLIFlags,
} }
cmdAuthUpdateLdapBindDn = cli.Command{ cmdAuthUpdateLdapBindDn = &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(c *cli.Context) error {
@ -153,7 +153,7 @@ var (
Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
} }
cmdAuthAddLdapSimpleAuth = cli.Command{ cmdAuthAddLdapSimpleAuth = &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(c *cli.Context) error {
@ -162,7 +162,7 @@ var (
Flags: ldapSimpleAuthCLIFlags, Flags: ldapSimpleAuthCLIFlags,
} }
cmdAuthUpdateLdapSimpleAuth = cli.Command{ cmdAuthUpdateLdapSimpleAuth = &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(c *cli.Context) error {

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func TestAddLdapBindDn(t *testing.T) { func TestAddLdapBindDn(t *testing.T) {

@ -4,13 +4,13 @@
package cmd package cmd
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var subcmdUser = cli.Command{ var subcmdUser = &cli.Command{
Name: "user", Name: "user",
Usage: "Modify users", Usage: "Modify users",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
microcmdUserCreate, microcmdUserCreate,
microcmdUserList, microcmdUserList,
microcmdUserChangePassword, microcmdUserChangePassword,

@ -12,23 +12,25 @@ import (
pwd "code.gitea.io/gitea/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserChangePassword = cli.Command{ var microcmdUserChangePassword = &cli.Command{
Name: "change-password", Name: "change-password",
Usage: "Change a user's password", Usage: "Change a user's password",
Action: runChangePassword, Action: runChangePassword,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "username,u", Name: "username",
Value: "", Aliases: []string{"u"},
Usage: "The user to change password for", Value: "",
Usage: "The user to change password for",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "password,p", Name: "password",
Value: "", Aliases: []string{"p"},
Usage: "New password to set for user", Value: "",
Usage: "New password to set for user",
}, },
}, },
} }

@ -14,52 +14,52 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserCreate = cli.Command{ var microcmdUserCreate = &cli.Command{
Name: "create", Name: "create",
Usage: "Create a new user in database", Usage: "Create a new user in database",
Action: runCreateUser, Action: runCreateUser,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Username. DEPRECATED: use username instead", Usage: "Username. DEPRECATED: use username instead",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "username", Name: "username",
Usage: "Username", Usage: "Username",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "password", Name: "password",
Usage: "User password", Usage: "User password",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "email", Name: "email",
Usage: "User email address", Usage: "User email address",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "admin", Name: "admin",
Usage: "User is an admin", Usage: "User is an admin",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "random-password", Name: "random-password",
Usage: "Generate a random password for the user", Usage: "Generate a random password for the user",
}, },
cli.BoolFlag{ &cli.BoolFlag{
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, (Default: true)", Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "random-password-length", Name: "random-password-length",
Usage: "Length of the random password to be generated", Usage: "Length of the random password to be generated",
Value: 12, Value: 12,
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "access-token", Name: "access-token",
Usage: "Generate access token for the user", Usage: "Generate access token for the user",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "restricted", Name: "restricted",
Usage: "Make a restricted user account", Usage: "Make a restricted user account",
}, },

@ -11,26 +11,28 @@ import (
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserDelete = cli.Command{ var microcmdUserDelete = &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{
cli.Int64Flag{ &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "ID of user of the user to delete", Usage: "ID of user of the user to delete",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "username,u", Name: "username",
Usage: "Username of the user to delete", Aliases: []string{"u"},
Usage: "Username of the user to delete",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "email,e", Name: "email",
Usage: "Email of the user to delete", Aliases: []string{"e"},
Usage: "Email of the user to delete",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "purge", Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments", Usage: "Purge user, all their repositories, organizations and comments",
}, },

@ -9,27 +9,29 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserGenerateAccessToken = cli.Command{ var microcmdUserGenerateAccessToken = &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{
cli.StringFlag{ &cli.StringFlag{
Name: "username,u", Name: "username",
Usage: "Username", Aliases: []string{"u"},
Usage: "Username",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "token-name,t", Name: "token-name",
Usage: "Token name", Aliases: []string{"t"},
Value: "gitea-admin", Usage: "Token name",
Value: "gitea-admin",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "raw", Name: "raw",
Usage: "Display only the token value", Usage: "Display only the token value",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "scopes", Name: "scopes",
Value: "", Value: "",
Usage: "Comma separated list of scopes to apply to access token", Usage: "Comma separated list of scopes to apply to access token",

@ -10,15 +10,15 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserList = cli.Command{ var microcmdUserList = &cli.Command{
Name: "list", Name: "list",
Usage: "List users", Usage: "List users",
Action: runListUsers, Action: runListUsers,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "admin", Name: "admin",
Usage: "List only admin users", Usage: "List only admin users",
}, },

@ -9,23 +9,25 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var microcmdUserMustChangePassword = cli.Command{ var microcmdUserMustChangePassword = &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,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "all,A", Name: "all",
Usage: "All users must change password, except those explicitly excluded with --exclude", Aliases: []string{"A"},
Usage: "All users must change password, except those explicitly excluded with --exclude",
}, },
cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "exclude,e", Name: "exclude",
Usage: "Do not change the must-change-password flag for these users", Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "unset", Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it", Usage: "Instead of setting the must-change-password flag, unset it",
}, },
@ -48,7 +50,7 @@ func runMustChangePassword(c *cli.Context) error {
return err return err
} }
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args(), exclude) n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)
if err != nil { if err != nil {
return err return err
} }

@ -20,43 +20,43 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdCert represents the available cert sub-command. // CmdCert represents the available cert sub-command.
var CmdCert = cli.Command{ var CmdCert = &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.
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Action: runCert, Action: runCert,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Value: "", Value: "",
Usage: "Comma-separated hostnames and IPs to generate a certificate for", Usage: "Comma-separated hostnames and IPs to generate a certificate for",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "ecdsa-curve", Name: "ecdsa-curve",
Value: "", Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "rsa-bits", Name: "rsa-bits",
Value: 2048, Value: 2048,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "start-date", Name: "start-date",
Value: "", Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011", Usage: "Creation date formatted as Jan 1 15:04:05 2011",
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: "duration", Name: "duration",
Value: 365 * 24 * time.Hour, Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for", Usage: "Duration that certificate is valid for",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "ca", Name: "ca",
Usage: "whether this cert should be its own Certificate Authority", Usage: "whether this cert should be its own Certificate Authority",
}, },

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// 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
@ -109,15 +109,24 @@ 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 {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
}
}
return false
}
// 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(*cli.Context) error {
return func(c *cli.Context) error { return func(c *cli.Context) error {
level := defaultLevel level := defaultLevel
if c.Bool("quiet") || c.GlobalBoolT("quiet") { if globalBool(c, "quiet") {
level = log.FATAL level = log.FATAL
} }
if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") { if globalBool(c, "debug") || globalBool(c, "verbose") {
level = log.TRACE level = log.TRACE
} }
log.SetConsoleLogger(log.DEFAULT, "console-default", level) log.SetConsoleLogger(log.DEFAULT, "console-default", level)

@ -10,11 +10,11 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdConvert represents the available convert sub-command. // CmdConvert represents the available convert sub-command.
var CmdConvert = cli.Command{ var CmdConvert = &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 or MSSQL database from varchar to nvarchar", Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",

@ -8,11 +8,11 @@ import (
"os" "os"
"strings" "strings"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdDocs represents the available docs sub-command. // CmdDocs represents the available docs sub-command.
var CmdDocs = cli.Command{ var CmdDocs = &cli.Command{
Name: "docs", Name: "docs",
Usage: "Output CLI documentation", Usage: "Output CLI documentation",
Description: "A command to output Gitea's CLI documentation, optionally to a file.", Description: "A command to output Gitea's CLI documentation, optionally to a file.",
@ -23,8 +23,9 @@ var CmdDocs = cli.Command{
Usage: "Output man pages instead", Usage: "Output man pages instead",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "output, o", Name: "output",
Usage: "Path to output to instead of stdout (will overwrite if exists)", Aliases: []string{"o"},
Usage: "Path to output to instead of stdout (will overwrite if exists)",
}, },
}, },
} }

@ -18,57 +18,58 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"xorm.io/xorm" "xorm.io/xorm"
) )
// CmdDoctor represents the available doctor sub-command. // CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{ var CmdDoctor = &cli.Command{
Name: "doctor", Name: "doctor",
Usage: "Diagnose and optionally fix problems", Usage: "Diagnose and optionally fix problems",
Description: "A command to diagnose problems with the current Gitea 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 Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Action: runDoctor, Action: runDoctor,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "list", Name: "list",
Usage: "List the available checks", Usage: "List the available checks",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "default", Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
}, },
cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "run", Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)", Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "all", Name: "all",
Usage: "Run all the available checks", Usage: "Run all the available checks",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "fix", Name: "fix",
Usage: "Automatically fix what we can", Usage: "Automatically fix what we can",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "log-file", Name: "log-file",
Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`, Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`,
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "color, H", Name: "color",
Usage: "Use color for outputted information", Aliases: []string{"H"},
Usage: "Use color for outputted information",
}, },
}, },
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
cmdRecreateTable, cmdRecreateTable,
}, },
} }
var cmdRecreateTable = cli.Command{ var cmdRecreateTable = &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)",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "Print SQL commands sent", Usage: "Print SQL commands sent",
}, },

@ -22,7 +22,7 @@ import (
"gitea.com/go-chi/session" "gitea.com/go-chi/session"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
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 {
@ -96,64 +96,71 @@ var outputTypeEnum = &outputType{
} }
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
var CmdDump = cli.Command{ var CmdDump = &cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Gitea files and database", Usage: "Dump Gitea 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.
It can be used for backup and capture Gitea server image to send to maintainer`, It can be used for backup and capture Gitea server image to send to maintainer`,
Action: runDump, Action: runDump,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "file, f", Name: "file",
Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()), Aliases: []string{"f"},
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.", Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose, V", Name: "verbose",
Usage: "Show process details", Aliases: []string{"V"},
Usage: "Show process details",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "quiet, q", Name: "quiet",
Usage: "Only display warnings and errors", Aliases: []string{"q"},
Usage: "Only display warnings and errors",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "tempdir, t", Name: "tempdir",
Value: os.TempDir(), Aliases: []string{"t"},
Usage: "Temporary dir path", Value: os.TempDir(),
Usage: "Temporary dir path",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "database, d", Name: "database",
Usage: "Specify the database SQL syntax", Aliases: []string{"d"},
Usage: "Specify the database SQL syntax",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-repository, R", Name: "skip-repository",
Usage: "Skip the repository dumping", Aliases: []string{"R"},
Usage: "Skip the repository dumping",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-log, L", Name: "skip-log",
Usage: "Skip the log dumping", Aliases: []string{"L"},
Usage: "Skip the log dumping",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-custom-dir", Name: "skip-custom-dir",
Usage: "Skip custom directory", Usage: "Skip custom directory",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-lfs-data", Name: "skip-lfs-data",
Usage: "Skip LFS data", Usage: "Skip LFS data",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-attachment-data", Name: "skip-attachment-data",
Usage: "Skip attachment data", Usage: "Skip attachment data",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-package-data", Name: "skip-package-data",
Usage: "Skip package data", Usage: "Skip package data",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-index", Name: "skip-index",
Usage: "Skip bleve index data", Usage: "Skip bleve index data",
}, },
cli.GenericFlag{ &cli.GenericFlag{
Name: "type", Name: "type",
Value: outputTypeEnum, Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),

@ -19,57 +19,58 @@ import (
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdDumpRepository represents the available dump repository sub-command. // CmdDumpRepository represents the available dump repository sub-command.
var CmdDumpRepository = cli.Command{ var CmdDumpRepository = &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.",
Action: runDumpRepository, Action: runDumpRepository,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "git_service", Name: "git_service",
Value: "", Value: "",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "repo_dir, r", Name: "repo_dir",
Value: "./data", Aliases: []string{"r"},
Usage: "Repository dir path to store the data", Value: "./data",
Usage: "Repository dir path to store the data",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "clone_addr", Name: "clone_addr",
Value: "", Value: "",
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "auth_username", Name: "auth_username",
Value: "", Value: "",
Usage: "The username to visit the clone_addr", Usage: "The username to visit the clone_addr",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "auth_password", Name: "auth_password",
Value: "", Value: "",
Usage: "The password to visit the clone_addr", Usage: "The password to visit the clone_addr",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "auth_token", Name: "auth_token",
Value: "", Value: "",
Usage: "The personal token to visit the clone_addr", Usage: "The personal token to visit the clone_addr",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "owner_name", Name: "owner_name",
Value: "", Value: "",
Usage: "The data will be stored on a directory with owner name if not empty", Usage: "The data will be stored on a directory with owner name if not empty",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "repo_name", Name: "repo_name",
Value: "", Value: "",
Usage: "The data will be stored on a directory with repository name if not empty", Usage: "The data will be stored on a directory with repository name if not empty",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "units", Name: "units",
Value: "", Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma. Usage: `Which items will be migrated, one or more units should be separated as comma.

@ -19,70 +19,74 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdEmbedded represents the available extract sub-command. // CmdEmbedded represents the available extract sub-command.
var ( var (
CmdEmbedded = cli.Command{ CmdEmbedded = &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{ Subcommands: []*cli.Command{
subcmdList, subcmdList,
subcmdView, subcmdView,
subcmdExtract, subcmdExtract,
}, },
} }
subcmdList = cli.Command{ subcmdList = &cli.Command{
Name: "list", Name: "list",
Usage: "List files matching the given pattern", Usage: "List files matching the given pattern",
Action: runList, Action: runList,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "include-vendored,vendor", Name: "include-vendored",
Usage: "Include files under public/vendor as well", Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
}, },
}, },
} }
subcmdView = cli.Command{ subcmdView = &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,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "include-vendored,vendor", Name: "include-vendored",
Usage: "Include files under public/vendor as well", Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
}, },
}, },
} }
subcmdExtract = cli.Command{ subcmdExtract = &cli.Command{
Name: "extract", Name: "extract",
Usage: "Extract resources", Usage: "Extract resources",
Action: runExtract, Action: runExtract,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "include-vendored,vendor", Name: "include-vendored",
Usage: "Include files under public/vendor as well", Aliases: []string{"vendor"},
Usage: "Include files under public/vendor as well",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "overwrite", Name: "overwrite",
Usage: "Overwrite files if they already exist", Usage: "Overwrite files if they already exist",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "rename", Name: "rename",
Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)", Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "custom", Name: "custom",
Usage: "Extract to the 'custom' directory as per app.ini", Usage: "Extract to the 'custom' directory as per app.ini",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "destination,dest-dir", Name: "destination",
Usage: "Extract to the specified directory", Aliases: []string{"dest-dir"},
Usage: "Extract to the specified directory",
}, },
}, },
} }
@ -99,7 +103,7 @@ type assetFile struct {
func initEmbeddedExtractor(c *cli.Context) error { func initEmbeddedExtractor(c *cli.Context) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args()) patterns, err := compileCollectPatterns(c.Args().Slice())
if err != nil { if err != nil {
return err return err
} }
@ -175,7 +179,7 @@ func runExtractDo(c *cli.Context) error {
return err return err
} }
if len(c.Args()) == 0 { if c.NArg() == 0 {
return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)") return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
} }

@ -11,43 +11,43 @@ import (
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
// CmdGenerate represents the available generate sub-command. // CmdGenerate represents the available generate sub-command.
CmdGenerate = cli.Command{ CmdGenerate = &cli.Command{
Name: "generate", Name: "generate",
Usage: "Command line interface for running generators", Usage: "Command line interface for running generators",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
subcmdSecret, subcmdSecret,
}, },
} }
subcmdSecret = cli.Command{ subcmdSecret = &cli.Command{
Name: "secret", Name: "secret",
Usage: "Generate a secret token", Usage: "Generate a secret token",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
microcmdGenerateInternalToken, microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret, microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey, microcmdGenerateSecretKey,
}, },
} }
microcmdGenerateInternalToken = cli.Command{ microcmdGenerateInternalToken = &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{ microcmdGenerateLfsJwtSecret = &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{ microcmdGenerateSecretKey = &cli.Command{
Name: "SECRET_KEY", Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY", Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey, Action: runGenerateSecretKey,

@ -20,7 +20,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
@ -29,12 +29,12 @@ const (
var ( var (
// CmdHook represents the available hooks sub-command. // CmdHook represents the available hooks sub-command.
CmdHook = cli.Command{ CmdHook = &cli.Command{
Name: "hook", Name: "hook",
Usage: "Delegate commands to corresponding Git hooks", Usage: "Delegate commands to corresponding Git hooks",
Description: "This should only be called by Git", Description: "This should only be called by Git",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
subcmdHookPreReceive, subcmdHookPreReceive,
subcmdHookUpdate, subcmdHookUpdate,
subcmdHookPostReceive, subcmdHookPostReceive,
@ -42,47 +42,47 @@ var (
}, },
} }
subcmdHookPreReceive = cli.Command{ subcmdHookPreReceive = &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",
Action: runHookPreReceive, Action: runHookPreReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
subcmdHookUpdate = cli.Command{ subcmdHookUpdate = &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",
Action: runHookUpdate, Action: runHookUpdate,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
subcmdHookPostReceive = cli.Command{ subcmdHookPostReceive = &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",
Action: runHookPostReceive, Action: runHookPostReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
// Note: new hook since git 2.29 // Note: new hook since git 2.29
subcmdHookProcReceive = cli.Command{ subcmdHookProcReceive = &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",
Action: runHookProcReceive, Action: runHookProcReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },

@ -11,35 +11,39 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdKeys represents the available keys sub-command // CmdKeys represents the available keys sub-command
var CmdKeys = cli.Command{ var CmdKeys = &cli.Command{
Name: "keys", Name: "keys",
Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint", Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys, Action: runKeys,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "expected, e", Name: "expected",
Value: "git", Aliases: []string{"e"},
Usage: "Expected user for whom provide key commands", Value: "git",
Usage: "Expected user for whom provide key commands",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "username, u", Name: "username",
Value: "", Aliases: []string{"u"},
Usage: "Username trying to log in by SSH", Value: "",
Usage: "Username trying to log in by SSH",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "type, t", Name: "type",
Value: "", Aliases: []string{"t"},
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", Value: "",
Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "content, k", Name: "content",
Value: "", Aliases: []string{"k"},
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", Value: "",
Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)",
}, },
}, },
} }
@ -73,6 +77,6 @@ func runKeys(c *cli.Context) error {
if extra.Error != nil { if extra.Error != nil {
return extra.Error return extra.Error
} }
fmt.Println(strings.TrimSpace(authorizedString)) _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString))
return nil return nil
} }

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func runSendMail(c *cli.Context) error { func runSendMail(c *cli.Context) error {

@ -0,0 +1,196 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"os"
"reflect"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v2"
)
// cmdHelp is our own help subcommand with more information
func cmdHelp() *cli.Command {
c := &cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *cli.Context) (err error) {
args := c.Args()
if args.Present() {
err = cli.ShowCommandHelp(c, args.First())
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
CustomPath: %s
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
return c
}
var helpFlag = cli.HelpFlag
func init() {
// cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
}
func appGlobalFlags() []cli.Flag {
return []cli.Flag{
// make the builtin flags at the top
helpFlag,
cli.VersionFlag,
// shared configuration flags, they are for global and for each sub-command at the same time
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
&cli.StringFlag{
Name: "custom-path",
Aliases: []string{"C"},
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
&cli.StringFlag{
Name: "work-path",
Aliases: []string{"w"},
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
}
}
func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp())
}
for i := range command.Subcommands {
prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf
ctxLineage := ctx.Lineage()
for i := len(ctxLineage) - 1; i >= 0; i-- {
curCtx := ctxLineage[i]
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
args.CustomPath = curCtx.String("custom-path")
}
if curCtx.IsSet("config") && args.CustomConf == "" {
args.CustomConf = curCtx.String("config")
}
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx)
}
return action(ctx)
}
}
func reflectGet(v any, fieldName string) any {
e := reflect.ValueOf(v).Elem()
return e.FieldByName(fieldName).Interface()
}
// https://cli.urfave.org/migrate-v1-to-v2/#flag-aliases-are-done-differently
// Sadly v2 doesn't warn you if a comma is in the name. (https://github.com/urfave/cli/issues/1103)
func checkCommandFlags(c any) bool {
var cmds []*cli.Command
if app, ok := c.(*cli.App); ok {
cmds = app.Commands
} else {
cmds = c.(*cli.Command).Subcommands
}
ok := true
for _, cmd := range cmds {
for _, flag := range cmd.Flags {
flagName := reflectGet(flag, "Name").(string)
if strings.Contains(flagName, ",") {
ok = false
log.Error("cli.Flag can't have comma in its Name: %q, use Aliases instead", flagName)
}
}
if !checkCommandFlags(cmd) {
ok = false
}
}
return ok
}
func NewMainApp() *cli.App {
app := cli.NewApp()
app.EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{
CmdWeb,
CmdServ,
CmdHook,
CmdDump,
CmdAdmin,
CmdMigrate,
CmdKeys,
CmdConvert,
CmdDoctor,
CmdManager,
CmdEmbedded,
CmdMigrateStorage,
CmdDumpRepository,
CmdRestoreRepository,
CmdActions,
cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
}
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
CmdCert,
CmdGenerate,
CmdDocs,
}
app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, globalFlags...)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig {
prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
}
app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...)
if !checkCommandFlags(app) {
panic("some flags are incorrect") // this is a runtime check to help developers
}
return app
}

@ -4,9 +4,17 @@
package cmd package cmd
import ( import (
"fmt"
"os"
"path/filepath"
"strings"
"testing" "testing"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -14,3 +22,110 @@ func TestMain(m *testing.M) {
GiteaRootPath: "..", GiteaRootPath: "..",
}) })
} }
func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp() *cli.App {
app := NewMainApp()
testCmd := &cli.Command{
Name: "test-cmd",
Action: func(ctx *cli.Context) error {
_, _ = fmt.Fprint(app.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
}
prepareSubcommandWithConfig(testCmd, appGlobalFlags())
app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name
return app
}
func TestCliCmd(t *testing.T) {
defaultWorkPath := filepath.Dir(setting.AppPath)
defaultCustomPath := filepath.Join(defaultWorkPath, "custom")
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)"
cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct {
env map[string]string
cmd string
exp string
}{
// main command help
{
cmd: "./gitea help",
exp: "DEFAULT CONFIGURATION:",
},
// parse paths
{
cmd: "./gitea test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf),
},
{
cmd: "./gitea -c /tmp/app.ini test-cmd",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
cmd: "./gitea test-cmd -c /tmp/app.ini",
exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --work-path /tmp/other",
exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"),
},
{
env: map[string]string{"GITEA_WORK_DIR": "/tmp"},
cmd: "./gitea test-cmd --config /tmp/app-other.ini",
exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"),
},
}
app := newTestApp()
var envBackup []string
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
}
}
}
defer func() {
clearGiteaEnv()
for _, s := range envBackup {
k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases {
clearGiteaEnv()
for k, v := range c.env {
_ = os.Setenv(k, v)
}
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
out := new(strings.Builder)
app.Writer = out
err := app.Run(args)
assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
outStr := out.String()
assert.Contains(t, outStr, c.exp, c.cmd)
}
}

@ -9,16 +9,16 @@ import (
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
// CmdManager represents the manager command // CmdManager represents the manager command
CmdManager = cli.Command{ CmdManager = &cli.Command{
Name: "manager", Name: "manager",
Usage: "Manage the running gitea process", Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process", Description: "This is a command for managing the running gitea process",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
subcmdShutdown, subcmdShutdown,
subcmdRestart, subcmdRestart,
subcmdReloadTemplates, subcmdReloadTemplates,
@ -27,80 +27,80 @@ var (
subCmdProcesses, subCmdProcesses,
}, },
} }
subcmdShutdown = cli.Command{ subcmdShutdown = &cli.Command{
Name: "shutdown", Name: "shutdown",
Usage: "Gracefully shutdown the running process", Usage: "Gracefully shutdown the running process",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
Action: runShutdown, Action: runShutdown,
} }
subcmdRestart = cli.Command{ subcmdRestart = &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{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
Action: runRestart, Action: runRestart,
} }
subcmdReloadTemplates = cli.Command{ subcmdReloadTemplates = &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{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
Action: runReloadTemplates, Action: runReloadTemplates,
} }
subcmdFlushQueues = cli.Command{ subcmdFlushQueues = &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,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.DurationFlag{ &cli.DurationFlag{
Name: "timeout", Name: "timeout",
Value: 60 * time.Second, Value: 60 * time.Second,
Usage: "Timeout for the flushing process", Usage: "Timeout for the flushing process",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "non-blocking", Name: "non-blocking",
Usage: "Set to true to not wait for flush to complete before returning", Usage: "Set to true to not wait for flush to complete before returning",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
subCmdProcesses = cli.Command{ subCmdProcesses = &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,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "flat", Name: "flat",
Usage: "Show processes as flat table rather than as tree", Usage: "Show processes as flat table rather than as tree",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-system", Name: "no-system",
Usage: "Do not show system processes", Usage: "Do not show system processes",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "stacktraces", Name: "stacktraces",
Usage: "Show stacktraces", Usage: "Show stacktraces",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "json", Name: "json",
Usage: "Output as json", Usage: "Output as json",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "cancel", Name: "cancel",
Usage: "Process PID to cancel. (Only available for non-system processes.)", Usage: "Process PID to cancel. (Only available for non-system processes.)",
}, },

@ -10,49 +10,61 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
defaultLoggingFlags = []cli.Flag{ defaultLoggingFlags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "logger", Name: "logger",
Usage: `Logger name - will default to "default"`, Usage: `Logger name - will default to "default"`,
}, cli.StringFlag{ },
&cli.StringFlag{
Name: "writer", Name: "writer",
Usage: "Name of the log writer - will default to mode", Usage: "Name of the log writer - will default to mode",
}, cli.StringFlag{ },
&cli.StringFlag{
Name: "level", Name: "level",
Usage: "Logging level for the new logger", Usage: "Logging level for the new logger",
}, cli.StringFlag{ },
Name: "stacktrace-level, L", &cli.StringFlag{
Usage: "Stacktrace logging level", Name: "stacktrace-level",
}, cli.StringFlag{ Aliases: []string{"L"},
Name: "flags, F", Usage: "Stacktrace logging level",
Usage: "Flags for the logger", },
}, cli.StringFlag{ &cli.StringFlag{
Name: "expression, e", Name: "flags",
Usage: "Matching expression for the logger", Aliases: []string{"F"},
}, cli.StringFlag{ Usage: "Flags for the logger",
Name: "prefix, p", },
Usage: "Prefix for the logger", &cli.StringFlag{
}, cli.BoolFlag{ Name: "expression",
Aliases: []string{"e"},
Usage: "Matching expression for the logger",
},
&cli.StringFlag{
Name: "prefix",
Aliases: []string{"p"},
Usage: "Prefix for the logger",
},
&cli.BoolFlag{
Name: "color", Name: "color",
Usage: "Use color in the logs", Usage: "Use color in the logs",
}, cli.BoolFlag{ },
&cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
} }
subcmdLogging = cli.Command{ subcmdLogging = &cli.Command{
Name: "logging", Name: "logging",
Usage: "Adjust logging commands", Usage: "Adjust logging commands",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "pause", Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
@ -61,7 +73,7 @@ var (
Name: "resume", Name: "resume",
Usage: "Resume logging", Usage: "Resume logging",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
@ -70,7 +82,7 @@ var (
Name: "release-and-reopen", Name: "release-and-reopen",
Usage: "Cause Gitea to release and re-open files used for logging", Usage: "Cause Gitea to release and re-open files used for logging",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
@ -80,9 +92,9 @@ var (
Usage: "Remove a logger", Usage: "Remove a logger",
ArgsUsage: "[name] Name of logger to remove", ArgsUsage: "[name] Name of logger to remove",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, cli.StringFlag{ }, &cli.StringFlag{
Name: "logger", Name: "logger",
Usage: `Logger name - will default to "default"`, Usage: `Logger name - will default to "default"`,
}, },
@ -91,32 +103,45 @@ var (
}, { }, {
Name: "add", Name: "add",
Usage: "Add a logger", Usage: "Add a logger",
Subcommands: []cli.Command{ Subcommands: []*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, f", Name: "filename",
Usage: "Filename for the logger - this must be set.", Aliases: []string{"f"},
}, cli.BoolTFlag{ Usage: "Filename for the logger - this must be set.",
Name: "rotate, r", },
Usage: "Rotate logs", &cli.BoolFlag{
}, cli.Int64Flag{ Name: "rotate",
Name: "max-size, s", Aliases: []string{"r"},
Usage: "Maximum size in bytes before rotation", Usage: "Rotate logs",
}, cli.BoolTFlag{ },
Name: "daily, d", &cli.Int64Flag{
Usage: "Rotate logs daily", Name: "max-size",
}, cli.IntFlag{ Aliases: []string{"s"},
Name: "max-days, D", Usage: "Maximum size in bytes before rotation",
Usage: "Maximum number of daily logs to keep", },
}, cli.BoolTFlag{ &cli.BoolFlag{
Name: "compress, z", Name: "daily",
Usage: "Compress rotated logs", Aliases: []string{"d"},
}, cli.IntFlag{ Usage: "Rotate logs daily",
Name: "compression-level, Z", },
Usage: "Compression level to use", &cli.IntFlag{
Name: "max-days",
Aliases: []string{"D"},
Usage: "Maximum number of daily logs to keep",
},
&cli.BoolFlag{
Name: "compress",
Aliases: []string{"z"},
Usage: "Compress rotated logs",
},
&cli.IntFlag{
Name: "compression-level",
Aliases: []string{"Z"},
Usage: "Compression level to use",
}, },
}...), }...),
Action: runAddFileLogger, Action: runAddFileLogger,
@ -124,18 +149,25 @@ 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, R", Name: "reconnect-on-message",
Usage: "Reconnect to host for every message", Aliases: []string{"R"},
}, cli.BoolFlag{ Usage: "Reconnect to host for every message",
Name: "reconnect, r", },
Usage: "Reconnect to host when connection is dropped", &cli.BoolFlag{
}, cli.StringFlag{ Name: "reconnect",
Name: "protocol, P", Aliases: []string{"r"},
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)", Usage: "Reconnect to host when connection is dropped",
}, cli.StringFlag{ },
Name: "address, a", &cli.StringFlag{
Usage: "Host address and port to connect to (defaults to :7020)", Name: "protocol",
Aliases: []string{"P"},
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)",
},
&cli.StringFlag{
Name: "address",
Aliases: []string{"a"},
Usage: "Host address and port to connect to (defaults to :7020)",
}, },
}...), }...),
Action: runAddConnLogger, Action: runAddConnLogger,
@ -145,9 +177,10 @@ var (
Name: "log-sql", Name: "log-sql",
Usage: "Set LogSQL", Usage: "Set LogSQL",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, cli.BoolFlag{ },
&cli.BoolFlag{
Name: "off", Name: "off",
Usage: "Switch off SQL logging", Usage: "Switch off SQL logging",
}, },

@ -11,11 +11,11 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdMigrate represents the available migrate sub-command. // CmdMigrate represents the available migrate sub-command.
var CmdMigrate = cli.Command{ var CmdMigrate = &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 gitea admin create-user before starting the server.", Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",

@ -20,70 +20,73 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdMigrateStorage represents the available migrate storage sub-command. // CmdMigrateStorage represents the available migrate storage sub-command.
var CmdMigrateStorage = cli.Command{ var CmdMigrateStorage = &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",
Action: runMigrateStorage, Action: runMigrateStorage,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "type, t", Name: "type",
Value: "", Aliases: []string{"t"},
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'", Value: "",
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "storage, s", Name: "storage",
Value: "", Aliases: []string{"s"},
Usage: "New storage type: local (default) or minio", Value: "",
Usage: "New storage type: local (default) or minio",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "path, p", Name: "path",
Value: "", Aliases: []string{"p"},
Usage: "New storage placement if store is local (leave blank for default)", Value: "",
Usage: "New storage placement if store is local (leave blank for default)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-endpoint", Name: "minio-endpoint",
Value: "", Value: "",
Usage: "Minio storage endpoint", Usage: "Minio storage endpoint",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-access-key-id", Name: "minio-access-key-id",
Value: "", Value: "",
Usage: "Minio storage accessKeyID", Usage: "Minio storage accessKeyID",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-secret-access-key", Name: "minio-secret-access-key",
Value: "", Value: "",
Usage: "Minio storage secretAccessKey", Usage: "Minio storage secretAccessKey",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-bucket", Name: "minio-bucket",
Value: "", Value: "",
Usage: "Minio storage bucket", Usage: "Minio storage bucket",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-location", Name: "minio-location",
Value: "", Value: "",
Usage: "Minio storage location to create bucket", Usage: "Minio storage location to create bucket",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-base-path", Name: "minio-base-path",
Value: "", Value: "",
Usage: "Minio storage base path on the bucket", Usage: "Minio storage base path on the bucket",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "minio-use-ssl", Name: "minio-use-ssl",
Usage: "Enable SSL for minio", Usage: "Enable SSL for minio",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "minio-insecure-skip-verify", Name: "minio-insecure-skip-verify",
Usage: "Skip SSL verification", Usage: "Skip SSL verification",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "minio-checksum-algorithm", Name: "minio-checksum-algorithm",
Value: "", Value: "",
Usage: "Minio checksum algorithm (default/md5)", Usage: "Minio checksum algorithm (default/md5)",

@ -9,38 +9,39 @@ import (
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// CmdRestoreRepository represents the available restore a repository sub-command. // CmdRestoreRepository represents the available restore a repository sub-command.
var CmdRestoreRepository = cli.Command{ var CmdRestoreRepository = &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.",
Action: runRestoreRepository, Action: runRestoreRepository,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "repo_dir, r", Name: "repo_dir",
Value: "./data", Aliases: []string{"r"},
Usage: "Repository dir path to restore from", Value: "./data",
Usage: "Repository dir path to restore from",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "owner_name", Name: "owner_name",
Value: "", Value: "",
Usage: "Restore destination owner name", Usage: "Restore destination owner name",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "repo_name", Name: "repo_name",
Value: "", Value: "",
Usage: "Restore destination repository name", Usage: "Restore destination repository name",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "units", Name: "units",
Value: "", Value: "",
Usage: `Which items will be restored, one or more units should be separated as comma. Usage: `Which items will be restored, one or more units should be separated as comma.
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.`,
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "validation", Name: "validation",
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",
}, },

@ -32,7 +32,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" "github.com/urfave/cli/v2"
) )
const ( const (
@ -40,17 +40,17 @@ const (
) )
// CmdServ represents the available serv sub-command. // CmdServ represents the available serv sub-command.
var CmdServ = cli.Command{ var CmdServ = &cli.Command{
Name: "serv", Name: "serv",
Usage: "This command should only be called by SSH shell", Usage: "This command should only be called by SSH shell",
Description: "Serv provides access auth for repositories", Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ, Action: runServ,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "enable-pprof", Name: "enable-pprof",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
@ -119,7 +119,7 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
} }
_ = private.SSHLog(ctx, true, logMsg) _ = private.SSHLog(ctx, true, logMsg)
} }
return cli.NewExitError("", 1) return cli.Exit("", 1)
} }
// handleCliResponseExtra handles the extra response from the cli sub-commands // handleCliResponseExtra handles the extra response from the cli sub-commands
@ -130,7 +130,7 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
_, _ = fmt.Fprintln(os.Stdout, extra.UserMsg) _, _ = fmt.Fprintln(os.Stdout, extra.UserMsg)
} }
if extra.HasError() { if extra.HasError() {
return cli.NewExitError(extra.Error, 1) return cli.Exit(extra.Error, 1)
} }
return nil return nil
} }
@ -147,20 +147,20 @@ func runServ(c *cli.Context) error {
return nil return nil
} }
if len(c.Args()) < 1 { if c.NArg() < 1 {
if err := cli.ShowSubcommandHelp(c); err != nil { if err := cli.ShowSubcommandHelp(c); err != nil {
fmt.Printf("error showing subcommand help: %v\n", err) fmt.Printf("error showing subcommand help: %v\n", err)
} }
return nil return nil
} }
keys := strings.Split(c.Args()[0], "-") keys := strings.Split(c.Args().First(), "-")
if len(keys) != 2 || keys[0] != "key" { if len(keys) != 2 || keys[0] != "key" {
return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args()[0]) return fail(ctx, "Key ID format error", "Invalid key argument: %s", c.Args().First())
} }
keyID, err := strconv.ParseInt(keys[1], 10, 64) keyID, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil { if err != nil {
return fail(ctx, "Key ID parsing error", "Invalid key argument: %s", c.Args()[1]) return fail(ctx, "Key ID parsing error", "Invalid key argument: %s", c.Args().Get(1))
} }
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")

@ -23,14 +23,14 @@ import (
"code.gitea.io/gitea/routers/install" "code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof" "github.com/felixge/fgprof"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// 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{ var CmdWeb = &cli.Command{
Name: "web", Name: "web",
Usage: "Start Gitea web server", Usage: "Start Gitea web server",
Description: `Gitea web server is the only thing you need to run, Description: `Gitea web server is the only thing you need to run,
@ -38,26 +38,29 @@ and it takes care of all the other things for you`,
Before: PrepareConsoleLoggerLevel(log.INFO), Before: PrepareConsoleLoggerLevel(log.INFO),
Action: runWeb, Action: runWeb,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "port, p", Name: "port",
Value: "3000", Aliases: []string{"p"},
Usage: "Temporary port number to prevent conflict", Value: "3000",
Usage: "Temporary port number to prevent conflict",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "install-port", Name: "install-port",
Value: "3000", Value: "3000",
Usage: "Temporary port number to run the install page on to prevent conflict", Usage: "Temporary port number to run the install page on to prevent conflict",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "pid, P", Name: "pid",
Value: PIDFile, Aliases: []string{"P"},
Usage: "Custom pid file path", Value: PIDFile,
Usage: "Custom pid file path",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "quiet, q", Name: "quiet",
Usage: "Only display Fatal logging errors until logging is set-up", Aliases: []string{"q"},
Usage: "Only display Fatal logging errors until logging is set-up",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose", Name: "verbose",
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",
}, },

@ -18,7 +18,7 @@ import (
"syscall" "syscall"
"github.com/google/go-github/v53/github" "github.com/google/go-github/v53/github"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -32,55 +32,55 @@ func main() {
app.ArgsUsage = "<PR-to-backport>" app.ArgsUsage = "<PR-to-backport>"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "version", Name: "version",
Usage: "Version branch to backport on to", Usage: "Version branch to backport on to",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "upstream", Name: "upstream",
Value: "origin", Value: "origin",
Usage: "Upstream remote for the Gitea upstream", Usage: "Upstream remote for the Gitea upstream",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "release-branch", Name: "release-branch",
Value: "", Value: "",
Usage: "Release branch to backport on. Will default to release/<version>", Usage: "Release branch to backport on. Will default to release/<version>",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "cherry-pick", Name: "cherry-pick",
Usage: "SHA to cherry-pick as backport", Usage: "SHA to cherry-pick as backport",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "backport-branch", Name: "backport-branch",
Usage: "Backport branch to backport on to (default: backport-<pr>-<version>", Usage: "Backport branch to backport on to (default: backport-<pr>-<version>",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "remote", Name: "remote",
Value: "", Value: "",
Usage: "Remote for your fork of the Gitea upstream", Usage: "Remote for your fork of the Gitea upstream",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "fork-user", Name: "fork-user",
Value: "", Value: "",
Usage: "Forked user name on Github", Usage: "Forked user name on Github",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-fetch", Name: "no-fetch",
Usage: "Set this flag to prevent fetch of remote branches", Usage: "Set this flag to prevent fetch of remote branches",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-amend-message", Name: "no-amend-message",
Usage: "Set this flag to prevent automatic amendment of the commit message", Usage: "Set this flag to prevent automatic amendment of the commit message",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-push", Name: "no-push",
Usage: "Set this flag to prevent pushing the backport up to your fork", Usage: "Set this flag to prevent pushing the backport up to your fork",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-xdg-open", Name: "no-xdg-open",
Usage: "Set this flag to not use xdg-open to open the PR URL", Usage: "Set this flag to not use xdg-open to open the PR URL",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "continue", Name: "continue",
Usage: "Set this flag to continue from a git cherry-pick that has broken", Usage: "Set this flag to continue from a git cherry-pick that has broken",
}, },
@ -151,7 +151,7 @@ func runBackport(c *cli.Context) error {
localReleaseBranch := path.Join(upstream, upstreamReleaseBranch) localReleaseBranch := path.Join(upstream, upstreamReleaseBranch)
args := c.Args() args := c.Args().Slice()
if len(args) == 0 && pr == "" { if len(args) == 0 && pr == "" {
return fmt.Errorf("no PR number provided\nProvide a PR number to backport") return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
} else if len(args) != 1 && pr == "" { } else if len(args) != 1 && pr == "" {

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func main() { func main() {
@ -46,22 +46,22 @@ func main() {
and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found
on the configuration cheat sheet.` on the configuration cheat sheet.`
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "custom-path, C", Name: "custom-path, C",
Value: setting.CustomPath, Value: setting.CustomPath,
Usage: "Custom path file path", Usage: "Custom path file path",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "config, c", Name: "config, c",
Value: setting.CustomConf, Value: setting.CustomConf,
Usage: "Custom configuration file path", Usage: "Custom configuration file path",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "work-path, w", Name: "work-path, w",
Value: setting.AppWorkPath, Value: setting.AppWorkPath,
Usage: "Set the gitea working path", Usage: "Set the gitea working path",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out, o", Name: "out, o",
Value: "", Value: "",
Usage: "Destination file to write to", Usage: "Destination file to write to",

@ -12,7 +12,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; These values are environment-dependent but form the basis of a lot of values. They will be ;; These values are environment-dependent but form the basis of a lot of values. They will be
;; reported as part of the default configuration when running `gitea --help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up. ;; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
;; ;;
;; - _`AppPath`_: This is the absolute path of the running gitea binary. ;; - _`AppPath`_: This is the absolute path of the running gitea binary.
;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy: ;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:

@ -40,7 +40,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
## Default Configuration (non-`app.ini` configuration) ## Default Configuration (non-`app.ini` configuration)
These values are environment-dependent but form the basis of a lot of values. They will be These values are environment-dependent but form the basis of a lot of values. They will be
reported as part of the default configuration when running `gitea --help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up. reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up.
- _`AppPath`_: This is the absolute path of the running gitea binary. - _`AppPath`_: This is the absolute path of the running gitea binary.
- _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy: - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy:

@ -98,7 +98,7 @@ require (
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.11 github.com/ulikunitz/xz v0.5.11
github.com/urfave/cli v1.22.14 github.com/urfave/cli/v2 v2.25.7
github.com/xanzy/go-gitlab v0.86.0 github.com/xanzy/go-gitlab v0.86.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
@ -278,6 +278,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
go.etcd.io/bbolt v1.3.7 // indirect go.etcd.io/bbolt v1.3.7 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect

@ -80,7 +80,6 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.57.0 h1:X/QmUmFhpUvLgPSQb7fWOSi1wvqGn6tJ7w2a59c4xsg= github.com/ClickHouse/ch-go v0.57.0 h1:X/QmUmFhpUvLgPSQb7fWOSi1wvqGn6tJ7w2a59c4xsg=
github.com/ClickHouse/ch-go v0.57.0/go.mod h1:DR3iBn7OrrDj+KeUp1LbdxLEUDbW+5Qwdl/qkc+PQ+Y= github.com/ClickHouse/ch-go v0.57.0/go.mod h1:DR3iBn7OrrDj+KeUp1LbdxLEUDbW+5Qwdl/qkc+PQ+Y=
@ -1162,8 +1161,8 @@ github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
@ -1198,6 +1197,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs= github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=

@ -2,8 +2,7 @@
// Copyright 2016 The Gitea Authors. All rights reserved. // Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Gitea (git with a cup of tea) is a painless self-hosted Git Service. package main
package main // import "code.gitea.io/gitea"
import ( import (
"fmt" "fmt"
@ -22,17 +21,13 @@ import (
_ "code.gitea.io/gitea/modules/markup/csv" _ "code.gitea.io/gitea/modules/markup/csv"
_ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode" _ "code.gitea.io/gitea/modules/markup/orgmode"
"github.com/urfave/cli"
) )
// these flags will be set by the build flags
var ( var (
// Version holds the current Gitea version Version = "development" // program version for this build
Version = "development" Tags = "" // the Golang build tags
// Tags holds the build tags used MakeVersion = "" // "make" program version if built with make
Tags = ""
// MakeVersion holds the current Make version if built with make
MakeVersion = ""
) )
func init() { func init() {
@ -41,110 +36,12 @@ func init() {
setting.AppStartTime = time.Now().UTC() setting.AppStartTime = time.Now().UTC()
} }
// cmdHelp is our own help subcommand with more information
// test cases:
// ./gitea help
// ./gitea -h
// ./gitea web help
// ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info)
// ./gitea admin
// ./gitea admin help
// ./gitea admin auth help
// ./gitea -c /tmp/app.ini -h
// ./gitea -c /tmp/app.ini help
// ./gitea help -c /tmp/app.ini
// GITEA_WORK_DIR=/tmp ./gitea help
// GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other
// GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini
var cmdHelp = cli.Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]",
Action: func(c *cli.Context) (err error) {
args := c.Args()
if args.Present() {
err = cli.ShowCommandHelp(c, args.First())
} else {
err = cli.ShowAppHelp(c)
}
_, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION:
AppPath: %s
WorkPath: %s
CustomPath: %s
ConfigFile: %s
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
return err
},
}
func main() { func main() {
app := cli.NewApp() app := cmd.NewMainApp()
app.Name = "Gitea" app.Name = "Gitea"
app.Usage = "A painless self-hosted Git service" app.Usage = "A painless self-hosted Git service"
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".` app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = Version + formatBuiltWith() app.Version = Version + formatBuiltWith()
app.EnableBashCompletion = true
// these sub-commands need to use config file
subCmdWithIni := []cli.Command{
cmd.CmdWeb,
cmd.CmdServ,
cmd.CmdHook,
cmd.CmdDump,
cmd.CmdAdmin,
cmd.CmdMigrate,
cmd.CmdKeys,
cmd.CmdConvert,
cmd.CmdDoctor,
cmd.CmdManager,
cmd.CmdEmbedded,
cmd.CmdMigrateStorage,
cmd.CmdDumpRepository,
cmd.CmdRestoreRepository,
cmd.CmdActions,
cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so
}
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []cli.Command{
cmd.CmdCert,
cmd.CmdGenerate,
cmd.CmdDocs,
}
// shared configuration flags, they are for global and for each sub-command at the same time
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
globalFlags := []cli.Flag{
cli.HelpFlag,
cli.StringFlag{
Name: "custom-path, C",
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
},
cli.StringFlag{
Name: "config, c",
Value: setting.CustomConf,
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
},
cli.StringFlag{
Name: "work-path, w",
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
},
}
// Set the default to be equivalent to cmdWeb and add the default flags
app.Flags = append(app.Flags, globalFlags...)
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags
app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithIni {
prepareSubcommands(&subCmdWithIni[i], globalFlags)
}
app.Commands = append(app.Commands, subCmdWithIni...)
app.Commands = append(app.Commands, subCmdStandalone...)
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
@ -154,45 +51,6 @@ func main() {
log.GetManager().Close() log.GetManager().Close()
} }
func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) {
command.Flags = append(command.Flags, defaultFlags...)
command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true
if command.Name != "help" {
command.Subcommands = append(command.Subcommands, cmdHelp)
}
for i := range command.Subcommands {
prepareSubcommands(&command.Subcommands[i], defaultFlags)
}
}
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action any) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf
curCtx := ctx
for curCtx != nil {
if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path")
}
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
args.CustomPath = curCtx.String("custom-path")
}
if curCtx.IsSet("config") && args.CustomConf == "" {
args.CustomConf = curCtx.String("config")
}
curCtx = curCtx.Parent()
}
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx)
}
return action.(func(*cli.Context) error)(ctx)
}
}
func formatBuiltWith() string { func formatBuiltWith() string {
version := runtime.Version() version := runtime.Version()
if len(MakeVersion) > 0 { if len(MakeVersion) > 0 {

@ -5,17 +5,15 @@ package integration
import ( import (
"bytes" "bytes"
"flag"
"io"
"net/url" "net/url"
"os"
"testing" "testing"
"code.gitea.io/gitea/cmd" "code.gitea.io/gitea/cmd"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/urfave/cli" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
) )
func Test_CmdKeys(t *testing.T) { func Test_CmdKeys(t *testing.T) {
@ -38,26 +36,18 @@ func Test_CmdKeys(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
realStdout := os.Stdout // Backup Stdout out := new(bytes.Buffer)
r, w, _ := os.Pipe() app := cli.NewApp()
os.Stdout = w app.Writer = out
app.Commands = []*cli.Command{cmd.CmdKeys}
set := flag.NewFlagSet("keys", 0) cmd.CmdKeys.HideHelp = true
_ = set.Parse(tt.args) err := app.Run(append([]string{"prog"}, tt.args...))
context := cli.NewContext(&cli.App{Writer: os.Stdout}, set, nil) if tt.wantErr {
err := cmd.CmdKeys.Run(context) assert.Error(t, err)
if (err != nil) != tt.wantErr { } else {
t.Errorf("CmdKeys.Run() error = %v, wantErr %v", err, tt.wantErr) assert.NoError(t, err)
}
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
commandOutput := buf.String()
if tt.expectedOutput != commandOutput {
t.Errorf("expectedOutput: %#v, commandOutput: %#v", tt.expectedOutput, commandOutput)
} }
// Restore stdout assert.Equal(t, tt.expectedOutput, out.String())
os.Stdout = realStdout
}) })
} }
}) })

Loading…
Cancel
Save