loading up the forgejo repo on tangled to test page performance
1// Copyright 2023 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package cmd
5
6import (
7 "context"
8 "fmt"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "forgejo.org/cmd/forgejo"
14 "forgejo.org/modules/log"
15 "forgejo.org/modules/setting"
16
17 "github.com/urfave/cli/v2"
18)
19
20// cmdHelp is our own help subcommand with more information
21// Keep in mind that the "./gitea help"(subcommand) is different from "./gitea --help"(flag), the flag doesn't parse the config or output "DEFAULT CONFIGURATION:" information
22func cmdHelp() *cli.Command {
23 c := &cli.Command{
24 Name: "help",
25 Aliases: []string{"h"},
26 Usage: "Shows a list of commands or help for one command",
27 ArgsUsage: "[command]",
28 Action: func(c *cli.Context) (err error) {
29 lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
30 targetCmdIdx := 0
31 if c.Command.Name == "help" {
32 targetCmdIdx = 1
33 }
34 if lineage[targetCmdIdx+1].Command != nil {
35 err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
36 } else {
37 err = cli.ShowAppHelp(c)
38 }
39 _, _ = fmt.Fprintf(c.App.Writer, `
40DEFAULT CONFIGURATION:
41 AppPath: %s
42 WorkPath: %s
43 CustomPath: %s
44 ConfigFile: %s
45
46`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
47 return err
48 },
49 }
50 return c
51}
52
53func appGlobalFlags() []cli.Flag {
54 return []cli.Flag{
55 // make the builtin flags at the top
56 cli.HelpFlag,
57
58 // shared configuration flags, they are for global and for each sub-command at the same time
59 // eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
60 // keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
61 &cli.StringFlag{
62 Name: "custom-path",
63 Aliases: []string{"C"},
64 Usage: "Set custom path (defaults to '{WorkPath}/custom')",
65 },
66 &cli.StringFlag{
67 Name: "config",
68 Aliases: []string{"c"},
69 Value: setting.CustomConf,
70 Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
71 },
72 &cli.StringFlag{
73 Name: "work-path",
74 Aliases: []string{"w"},
75 Usage: "Set Forgejo's working path (defaults to the directory of the Forgejo binary)",
76 },
77 }
78}
79
80func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
81 command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
82 command.Action = prepareWorkPathAndCustomConf(command.Action)
83 command.HideHelp = true
84 if command.Name != "help" {
85 command.Subcommands = append(command.Subcommands, cmdHelp())
86 }
87 for i := range command.Subcommands {
88 prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
89 }
90}
91
92// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
93// 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
94func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
95 return func(ctx *cli.Context) error {
96 var args setting.ArgWorkPathAndCustomConf
97 // from children to parent, check the global flags
98 for _, curCtx := range ctx.Lineage() {
99 if curCtx.IsSet("work-path") && args.WorkPath == "" {
100 args.WorkPath = curCtx.String("work-path")
101 }
102 if curCtx.IsSet("custom-path") && args.CustomPath == "" {
103 args.CustomPath = curCtx.String("custom-path")
104 }
105 if curCtx.IsSet("config") && args.CustomConf == "" {
106 args.CustomConf = curCtx.String("config")
107 }
108 }
109 setting.InitWorkPathAndCommonConfig(os.Getenv, args)
110 if ctx.Bool("help") || action == nil {
111 // the default behavior of "urfave/cli": "nil action" means "show help"
112 return cmdHelp().Action(ctx)
113 }
114 return action(ctx)
115 }
116}
117
118func NewMainApp(version, versionExtra string) *cli.App {
119 path, err := os.Executable()
120 if err != nil {
121 panic(err)
122 }
123 executable := filepath.Base(path)
124
125 subCmdsStandalone := make([]*cli.Command, 0, 10)
126 subCmdWithConfig := make([]*cli.Command, 0, 10)
127 globalFlags := make([]cli.Flag, 0, 10)
128
129 //
130 // If the executable is forgejo-cli, provide a Forgejo specific CLI
131 // that is NOT compatible with Gitea.
132 //
133 if executable == "forgejo-cli" {
134 subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdActions(context.Background()))
135 subCmdWithConfig = append(subCmdWithConfig, forgejo.CmdF3(context.Background()))
136 globalFlags = append(globalFlags, []cli.Flag{
137 &cli.BoolFlag{
138 Name: "quiet",
139 },
140 &cli.BoolFlag{
141 Name: "verbose",
142 },
143 }...)
144 } else {
145 //
146 // Otherwise provide a Gitea compatible CLI which includes Forgejo
147 // specific additions under the forgejo-cli subcommand. It allows
148 // admins to migration from Gitea to Forgejo by replacing the gitea
149 // binary and rename it to forgejo if they want.
150 //
151 subCmdsStandalone = append(subCmdsStandalone, forgejo.CmdForgejo(context.Background()))
152 subCmdWithConfig = append(subCmdWithConfig, CmdActions)
153 }
154
155 return innerNewMainApp(version, versionExtra, subCmdsStandalone, subCmdWithConfig, globalFlags)
156}
157
158func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmdWithConfigArgs []*cli.Command, globalFlagsArgs []cli.Flag) *cli.App {
159 app := cli.NewApp()
160 app.HelpName = "forgejo"
161 app.Name = "Forgejo"
162 app.Usage = "Beyond coding. We forge."
163 app.Description = `By default, forgejo will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
164 app.Version = version + versionExtra
165 app.EnableBashCompletion = true
166
167 // these sub-commands need to use config file
168 subCmdWithConfig := []*cli.Command{
169 cmdHelp(), // the "help" sub-command was used to show the more information for "work path" and "custom config"
170 CmdWeb,
171 CmdServ,
172 CmdHook,
173 CmdKeys,
174 CmdDump,
175 CmdAdmin,
176 CmdMigrate,
177 CmdDoctor,
178 CmdManager,
179 CmdEmbedded,
180 CmdMigrateStorage,
181 CmdDumpRepository,
182 CmdRestoreRepository,
183 }
184
185 subCmdWithConfig = append(subCmdWithConfig, subCmdWithConfigArgs...)
186
187 // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
188 subCmdStandalone := []*cli.Command{
189 CmdCert,
190 CmdGenerate,
191 CmdDocs,
192 }
193 subCmdStandalone = append(subCmdStandalone, subCmdsStandaloneArgs...)
194
195 app.DefaultCommand = CmdWeb.Name
196
197 globalFlags := appGlobalFlags()
198 globalFlags = append(globalFlags, globalFlagsArgs...)
199 app.Flags = append(app.Flags, cli.VersionFlag)
200 app.Flags = append(app.Flags, globalFlags...)
201 app.HideHelp = true // use our own help action to show helps (with more information like default config)
202 app.Before = PrepareConsoleLoggerLevel(log.INFO)
203 for i := range subCmdWithConfig {
204 prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
205 }
206 app.Commands = append(app.Commands, subCmdWithConfig...)
207 app.Commands = append(app.Commands, subCmdStandalone...)
208
209 setting.InitGiteaEnvVars()
210 return app
211}
212
213func RunMainApp(app *cli.App, args ...string) error {
214 err := app.Run(args)
215 if err == nil {
216 return nil
217 }
218 if strings.HasPrefix(err.Error(), "flag provided but not defined:") {
219 // the cli package should already have output the error message, so just exit
220 cli.OsExiter(1)
221 return err
222 }
223 _, _ = fmt.Fprintf(app.ErrWriter, "Command error: %v\n", err)
224 cli.OsExiter(1)
225 return err
226}