選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

main.go 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "fmt"
  6. "os"
  7. "reflect"
  8. "strings"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/setting"
  11. "code.gitea.io/gitea/modules/util"
  12. "github.com/urfave/cli/v2"
  13. )
  14. // cmdHelp is our own help subcommand with more information
  15. func cmdHelp() *cli.Command {
  16. c := &cli.Command{
  17. Name: "help",
  18. Aliases: []string{"h"},
  19. Usage: "Shows a list of commands or help for one command",
  20. ArgsUsage: "[command]",
  21. Action: func(c *cli.Context) (err error) {
  22. lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
  23. targetCmdIdx := 0
  24. if c.Command.Name == "help" {
  25. targetCmdIdx = 1
  26. }
  27. if lineage[targetCmdIdx+1].Command != nil {
  28. err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
  29. } else {
  30. err = cli.ShowAppHelp(c)
  31. }
  32. _, _ = fmt.Fprintf(c.App.Writer, `
  33. DEFAULT CONFIGURATION:
  34. AppPath: %s
  35. WorkPath: %s
  36. CustomPath: %s
  37. ConfigFile: %s
  38. `, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
  39. return err
  40. },
  41. }
  42. return c
  43. }
  44. var helpFlag = cli.HelpFlag
  45. func init() {
  46. // cli.HelpFlag = nil TODO: after https://github.com/urfave/cli/issues/1794 we can use this
  47. }
  48. func appGlobalFlags() []cli.Flag {
  49. return []cli.Flag{
  50. // make the builtin flags at the top
  51. helpFlag,
  52. cli.VersionFlag,
  53. // shared configuration flags, they are for global and for each sub-command at the same time
  54. // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
  55. // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
  56. &cli.StringFlag{
  57. Name: "custom-path",
  58. Aliases: []string{"C"},
  59. Usage: "Set custom path (defaults to '{WorkPath}/custom')",
  60. },
  61. &cli.StringFlag{
  62. Name: "config",
  63. Aliases: []string{"c"},
  64. Value: setting.CustomConf,
  65. Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
  66. },
  67. &cli.StringFlag{
  68. Name: "work-path",
  69. Aliases: []string{"w"},
  70. Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
  71. },
  72. }
  73. }
  74. func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
  75. command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
  76. command.Action = prepareWorkPathAndCustomConf(command.Action)
  77. command.HideHelp = true
  78. if command.Name != "help" {
  79. command.Subcommands = append(command.Subcommands, cmdHelp())
  80. }
  81. for i := range command.Subcommands {
  82. prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
  83. }
  84. }
  85. // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
  86. // 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
  87. func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
  88. return func(ctx *cli.Context) error {
  89. var args setting.ArgWorkPathAndCustomConf
  90. // from children to parent, check the global flags
  91. for _, curCtx := range ctx.Lineage() {
  92. if curCtx.IsSet("work-path") && args.WorkPath == "" {
  93. args.WorkPath = curCtx.String("work-path")
  94. }
  95. if curCtx.IsSet("custom-path") && args.CustomPath == "" {
  96. args.CustomPath = curCtx.String("custom-path")
  97. }
  98. if curCtx.IsSet("config") && args.CustomConf == "" {
  99. args.CustomConf = curCtx.String("config")
  100. }
  101. }
  102. setting.InitWorkPathAndCommonConfig(os.Getenv, args)
  103. if ctx.Bool("help") || action == nil {
  104. // the default behavior of "urfave/cli": "nil action" means "show help"
  105. return cmdHelp().Action(ctx)
  106. }
  107. return action(ctx)
  108. }
  109. }
  110. func reflectGet(v any, fieldName string) any {
  111. e := reflect.ValueOf(v).Elem()
  112. return e.FieldByName(fieldName).Interface()
  113. }
  114. // https://cli.urfave.org/migrate-v1-to-v2/#flag-aliases-are-done-differently
  115. // Sadly v2 doesn't warn you if a comma is in the name. (https://github.com/urfave/cli/issues/1103)
  116. func checkCommandFlags(c any) bool {
  117. var cmds []*cli.Command
  118. if app, ok := c.(*cli.App); ok {
  119. cmds = app.Commands
  120. } else {
  121. cmds = c.(*cli.Command).Subcommands
  122. }
  123. ok := true
  124. for _, cmd := range cmds {
  125. for _, flag := range cmd.Flags {
  126. flagName := reflectGet(flag, "Name").(string)
  127. if strings.Contains(flagName, ",") {
  128. ok = false
  129. log.Error("cli.Flag can't have comma in its Name: %q, use Aliases instead", flagName)
  130. }
  131. }
  132. if !checkCommandFlags(cmd) {
  133. ok = false
  134. }
  135. }
  136. return ok
  137. }
  138. func NewMainApp() *cli.App {
  139. app := cli.NewApp()
  140. app.EnableBashCompletion = true
  141. // these sub-commands need to use config file
  142. subCmdWithConfig := []*cli.Command{
  143. CmdWeb,
  144. CmdServ,
  145. CmdHook,
  146. CmdDump,
  147. CmdAdmin,
  148. CmdMigrate,
  149. CmdKeys,
  150. CmdDoctor,
  151. CmdManager,
  152. CmdEmbedded,
  153. CmdMigrateStorage,
  154. CmdDumpRepository,
  155. CmdRestoreRepository,
  156. CmdActions,
  157. cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
  158. }
  159. cmdConvert := util.ToPointer(*cmdDoctorConvert)
  160. cmdConvert.Hidden = true // still support the legacy "./gitea doctor" by the hidden sub-command, remove it in next release
  161. subCmdWithConfig = append(subCmdWithConfig, cmdConvert)
  162. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
  163. subCmdStandalone := []*cli.Command{
  164. CmdCert,
  165. CmdGenerate,
  166. CmdDocs,
  167. }
  168. app.DefaultCommand = CmdWeb.Name
  169. globalFlags := appGlobalFlags()
  170. app.Flags = append(app.Flags, globalFlags...)
  171. app.HideHelp = true // use our own help action to show helps (with more information like default config)
  172. app.Before = PrepareConsoleLoggerLevel(log.INFO)
  173. for i := range subCmdWithConfig {
  174. prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
  175. }
  176. app.Commands = append(app.Commands, subCmdWithConfig...)
  177. app.Commands = append(app.Commands, subCmdStandalone...)
  178. if !checkCommandFlags(app) {
  179. panic("some flags are incorrect") // this is a runtime check to help developers
  180. }
  181. return app
  182. }