diff --git a/.eslintrc b/.eslintrc index 6de0c1f9fa06..77d9dc1228ee 100644 --- a/.eslintrc +++ b/.eslintrc @@ -280,7 +280,7 @@ rules: no-unused-expressions: [2] no-unused-labels: [2] no-unused-private-class-members: [2] - no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, ignoreRestSiblings: false}] + no-unused-vars: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_, ignoreRestSiblings: false}] no-use-before-define: [2, nofunc] no-useless-backreference: [0] no-useless-call: [2] diff --git a/cmd/hook.go b/cmd/hook.go index 05fa6e56c133..2b62b61ca6fb 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -309,7 +309,7 @@ func runHookPostReceive(c *cli.Context) error { defer cancel() // First of all run update-server-info no matter what - if _, err := git.NewCommand(ctx, "update-server-info").Run(); err != nil { + if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil { return fmt.Errorf("Failed to call 'git update-server-info': %v", err) } diff --git a/cmd/manager.go b/cmd/manager.go index 50b66cc7f230..03fe23aa9e3f 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -10,7 +10,6 @@ import ( "os" "time" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "github.com/urfave/cli" @@ -27,6 +26,7 @@ var ( subcmdRestart, subcmdFlushQueues, subcmdLogging, + subCmdProcesses, }, } subcmdShutdown = cli.Command{ @@ -68,326 +68,38 @@ var ( }, }, } - defaultLoggingFlags = []cli.Flag{ - cli.StringFlag{ - Name: "group, g", - Usage: "Group to add logger to - will default to \"default\"", - }, cli.StringFlag{ - Name: "name, n", - Usage: "Name of the new logger - will default to mode", - }, cli.StringFlag{ - Name: "level, l", - Usage: "Logging level for the new logger", - }, cli.StringFlag{ - Name: "stacktrace-level, L", - Usage: "Stacktrace logging level", - }, cli.StringFlag{ - Name: "flags, F", - Usage: "Flags for the logger", - }, cli.StringFlag{ - Name: "expression, e", - Usage: "Matching expression for the logger", - }, cli.StringFlag{ - Name: "prefix, p", - Usage: "Prefix for the logger", - }, cli.BoolFlag{ - Name: "color", - Usage: "Use color in the logs", - }, cli.BoolFlag{ - Name: "debug", - }, - } - subcmdLogging = cli.Command{ - Name: "logging", - Usage: "Adjust logging commands", - Subcommands: []cli.Command{ - { - Name: "pause", - Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "debug", - }, - }, - Action: runPauseLogging, - }, { - Name: "resume", - Usage: "Resume logging", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "debug", - }, - }, - Action: runResumeLogging, - }, { - Name: "release-and-reopen", - Usage: "Cause Gitea to release and re-open files used for logging", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "debug", - }, - }, - Action: runReleaseReopenLogging, - }, { - Name: "remove", - Usage: "Remove a logger", - ArgsUsage: "[name] Name of logger to remove", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "debug", - }, cli.StringFlag{ - Name: "group, g", - Usage: "Group to add logger to - will default to \"default\"", - }, - }, - Action: runRemoveLogger, - }, { - Name: "add", - Usage: "Add a logger", - Subcommands: []cli.Command{ - { - Name: "console", - Usage: "Add a console logger", - Flags: append(defaultLoggingFlags, - cli.BoolFlag{ - Name: "stderr", - Usage: "Output console logs to stderr - only relevant for console", - }), - Action: runAddConsoleLogger, - }, { - Name: "file", - Usage: "Add a file logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ - cli.StringFlag{ - Name: "filename, f", - Usage: "Filename for the logger - this must be set.", - }, cli.BoolTFlag{ - Name: "rotate, r", - Usage: "Rotate logs", - }, cli.Int64Flag{ - Name: "max-size, s", - Usage: "Maximum size in bytes before rotation", - }, cli.BoolTFlag{ - Name: "daily, d", - Usage: "Rotate logs daily", - }, cli.IntFlag{ - Name: "max-days, D", - Usage: "Maximum number of daily logs to keep", - }, cli.BoolTFlag{ - Name: "compress, z", - Usage: "Compress rotated logs", - }, cli.IntFlag{ - Name: "compression-level, Z", - Usage: "Compression level to use", - }, - }...), - Action: runAddFileLogger, - }, { - Name: "conn", - Usage: "Add a net conn logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ - cli.BoolFlag{ - Name: "reconnect-on-message, R", - Usage: "Reconnect to host for every message", - }, cli.BoolFlag{ - Name: "reconnect, r", - Usage: "Reconnect to host when connection is dropped", - }, cli.StringFlag{ - Name: "protocol, P", - Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)", - }, cli.StringFlag{ - Name: "address, a", - Usage: "Host address and port to connect to (defaults to :7020)", - }, - }...), - Action: runAddConnLogger, - }, { - Name: "smtp", - Usage: "Add an SMTP logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ - cli.StringFlag{ - Name: "username, u", - Usage: "Mail server username", - }, cli.StringFlag{ - Name: "password, P", - Usage: "Mail server password", - }, cli.StringFlag{ - Name: "host, H", - Usage: "Mail server host (defaults to: 127.0.0.1:25)", - }, cli.StringSliceFlag{ - Name: "send-to, s", - Usage: "Email address(es) to send to", - }, cli.StringFlag{ - Name: "subject, S", - Usage: "Subject header of sent emails", - }, - }...), - Action: runAddSMTPLogger, - }, - }, + subCmdProcesses = cli.Command{ + Name: "processes", + Usage: "Display running processes within the current process", + Action: runProcesses, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + }, + cli.BoolFlag{ + Name: "flat", + Usage: "Show processes as flat table rather than as tree", + }, + cli.BoolFlag{ + Name: "no-system", + Usage: "Do not show system proceses", + }, + cli.BoolFlag{ + Name: "stacktraces", + Usage: "Show stacktraces", + }, + cli.BoolFlag{ + Name: "json", + Usage: "Output as json", + }, + cli.StringFlag{ + Name: "cancel", + Usage: "Process PID to cancel. (Only available for non-system processes.)", }, }, } ) -func runRemoveLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) - group := c.String("group") - if len(group) == 0 { - group = log.DEFAULT - } - name := c.Args().First() - ctx, cancel := installSignals() - defer cancel() - - statusCode, msg := private.RemoveLogger(ctx, group, name) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil -} - -func runAddSMTPLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) - vals := map[string]interface{}{} - mode := "smtp" - if c.IsSet("host") { - vals["host"] = c.String("host") - } else { - vals["host"] = "127.0.0.1:25" - } - - if c.IsSet("username") { - vals["username"] = c.String("username") - } - if c.IsSet("password") { - vals["password"] = c.String("password") - } - - if !c.IsSet("send-to") { - return fmt.Errorf("Some recipients must be provided") - } - vals["sendTos"] = c.StringSlice("send-to") - - if c.IsSet("subject") { - vals["subject"] = c.String("subject") - } else { - vals["subject"] = "Diagnostic message from Gitea" - } - - return commonAddLogger(c, mode, vals) -} - -func runAddConnLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) - vals := map[string]interface{}{} - mode := "conn" - vals["net"] = "tcp" - if c.IsSet("protocol") { - switch c.String("protocol") { - case "udp": - vals["net"] = "udp" - case "unix": - vals["net"] = "unix" - } - } - if c.IsSet("address") { - vals["address"] = c.String("address") - } else { - vals["address"] = ":7020" - } - if c.IsSet("reconnect") { - vals["reconnect"] = c.Bool("reconnect") - } - if c.IsSet("reconnect-on-message") { - vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") - } - return commonAddLogger(c, mode, vals) -} - -func runAddFileLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) - vals := map[string]interface{}{} - mode := "file" - if c.IsSet("filename") { - vals["filename"] = c.String("filename") - } else { - return fmt.Errorf("filename must be set when creating a file logger") - } - if c.IsSet("rotate") { - vals["rotate"] = c.Bool("rotate") - } - if c.IsSet("max-size") { - vals["maxsize"] = c.Int64("max-size") - } - if c.IsSet("daily") { - vals["daily"] = c.Bool("daily") - } - if c.IsSet("max-days") { - vals["maxdays"] = c.Int("max-days") - } - if c.IsSet("compress") { - vals["compress"] = c.Bool("compress") - } - if c.IsSet("compression-level") { - vals["compressionLevel"] = c.Int("compression-level") - } - return commonAddLogger(c, mode, vals) -} - -func runAddConsoleLogger(c *cli.Context) error { - setup("manager", c.Bool("debug")) - vals := map[string]interface{}{} - mode := "console" - if c.IsSet("stderr") && c.Bool("stderr") { - vals["stderr"] = c.Bool("stderr") - } - return commonAddLogger(c, mode, vals) -} - -func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error { - if len(c.String("level")) > 0 { - vals["level"] = log.FromString(c.String("level")).String() - } - if len(c.String("stacktrace-level")) > 0 { - vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String() - } - if len(c.String("expression")) > 0 { - vals["expression"] = c.String("expression") - } - if len(c.String("prefix")) > 0 { - vals["prefix"] = c.String("prefix") - } - if len(c.String("flags")) > 0 { - vals["flags"] = log.FlagsFromString(c.String("flags")) - } - if c.IsSet("color") { - vals["colorize"] = c.Bool("color") - } - group := "default" - if c.IsSet("group") { - group = c.String("group") - } - name := mode - if c.IsSet("name") { - name = c.String("name") - } - ctx, cancel := installSignals() - defer cancel() - - statusCode, msg := private.AddLogger(ctx, group, name, mode, vals) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil -} - func runShutdown(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() @@ -433,47 +145,16 @@ func runFlushQueues(c *cli.Context) error { return nil } -func runPauseLogging(c *cli.Context) error { +func runProcesses(c *cli.Context) error { ctx, cancel := installSignals() defer cancel() setup("manager", c.Bool("debug")) - statusCode, msg := private.PauseLogging(ctx) + statusCode, msg := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) switch statusCode { case http.StatusInternalServerError: return fail("InternalServerError", msg) } - fmt.Fprintln(os.Stdout, msg) - return nil -} - -func runResumeLogging(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - setup("manager", c.Bool("debug")) - statusCode, msg := private.ResumeLogging(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) - return nil -} - -func runReleaseReopenLogging(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - setup("manager", c.Bool("debug")) - statusCode, msg := private.ReleaseReopenLogging(ctx) - switch statusCode { - case http.StatusInternalServerError: - return fail("InternalServerError", msg) - } - - fmt.Fprintln(os.Stdout, msg) return nil } diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go new file mode 100644 index 000000000000..eb311d28926c --- /dev/null +++ b/cmd/manager_logging.go @@ -0,0 +1,382 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net/http" + "os" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "github.com/urfave/cli" +) + +var ( + defaultLoggingFlags = []cli.Flag{ + cli.StringFlag{ + Name: "group, g", + Usage: "Group to add logger to - will default to \"default\"", + }, cli.StringFlag{ + Name: "name, n", + Usage: "Name of the new logger - will default to mode", + }, cli.StringFlag{ + Name: "level, l", + Usage: "Logging level for the new logger", + }, cli.StringFlag{ + Name: "stacktrace-level, L", + Usage: "Stacktrace logging level", + }, cli.StringFlag{ + Name: "flags, F", + Usage: "Flags for the logger", + }, cli.StringFlag{ + Name: "expression, e", + Usage: "Matching expression for the logger", + }, cli.StringFlag{ + Name: "prefix, p", + Usage: "Prefix for the logger", + }, cli.BoolFlag{ + Name: "color", + Usage: "Use color in the logs", + }, cli.BoolFlag{ + Name: "debug", + }, + } + + subcmdLogging = cli.Command{ + Name: "logging", + Usage: "Adjust logging commands", + Subcommands: []cli.Command{ + { + Name: "pause", + Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + }, + }, + Action: runPauseLogging, + }, { + Name: "resume", + Usage: "Resume logging", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + }, + }, + Action: runResumeLogging, + }, { + Name: "release-and-reopen", + Usage: "Cause Gitea to release and re-open files used for logging", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + }, + }, + Action: runReleaseReopenLogging, + }, { + Name: "remove", + Usage: "Remove a logger", + ArgsUsage: "[name] Name of logger to remove", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + }, cli.StringFlag{ + Name: "group, g", + Usage: "Group to add logger to - will default to \"default\"", + }, + }, + Action: runRemoveLogger, + }, { + Name: "add", + Usage: "Add a logger", + Subcommands: []cli.Command{ + { + Name: "console", + Usage: "Add a console logger", + Flags: append(defaultLoggingFlags, + cli.BoolFlag{ + Name: "stderr", + Usage: "Output console logs to stderr - only relevant for console", + }), + Action: runAddConsoleLogger, + }, { + Name: "file", + Usage: "Add a file logger", + Flags: append(defaultLoggingFlags, []cli.Flag{ + cli.StringFlag{ + Name: "filename, f", + Usage: "Filename for the logger - this must be set.", + }, cli.BoolTFlag{ + Name: "rotate, r", + Usage: "Rotate logs", + }, cli.Int64Flag{ + Name: "max-size, s", + Usage: "Maximum size in bytes before rotation", + }, cli.BoolTFlag{ + Name: "daily, d", + Usage: "Rotate logs daily", + }, cli.IntFlag{ + Name: "max-days, D", + Usage: "Maximum number of daily logs to keep", + }, cli.BoolTFlag{ + Name: "compress, z", + Usage: "Compress rotated logs", + }, cli.IntFlag{ + Name: "compression-level, Z", + Usage: "Compression level to use", + }, + }...), + Action: runAddFileLogger, + }, { + Name: "conn", + Usage: "Add a net conn logger", + Flags: append(defaultLoggingFlags, []cli.Flag{ + cli.BoolFlag{ + Name: "reconnect-on-message, R", + Usage: "Reconnect to host for every message", + }, cli.BoolFlag{ + Name: "reconnect, r", + Usage: "Reconnect to host when connection is dropped", + }, cli.StringFlag{ + Name: "protocol, P", + Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)", + }, cli.StringFlag{ + Name: "address, a", + Usage: "Host address and port to connect to (defaults to :7020)", + }, + }...), + Action: runAddConnLogger, + }, { + Name: "smtp", + Usage: "Add an SMTP logger", + Flags: append(defaultLoggingFlags, []cli.Flag{ + cli.StringFlag{ + Name: "username, u", + Usage: "Mail server username", + }, cli.StringFlag{ + Name: "password, P", + Usage: "Mail server password", + }, cli.StringFlag{ + Name: "host, H", + Usage: "Mail server host (defaults to: 127.0.0.1:25)", + }, cli.StringSliceFlag{ + Name: "send-to, s", + Usage: "Email address(es) to send to", + }, cli.StringFlag{ + Name: "subject, S", + Usage: "Subject header of sent emails", + }, + }...), + Action: runAddSMTPLogger, + }, + }, + }, + }, + } +) + +func runRemoveLogger(c *cli.Context) error { + setup("manager", c.Bool("debug")) + group := c.String("group") + if len(group) == 0 { + group = log.DEFAULT + } + name := c.Args().First() + ctx, cancel := installSignals() + defer cancel() + + statusCode, msg := private.RemoveLogger(ctx, group, name) + switch statusCode { + case http.StatusInternalServerError: + return fail("InternalServerError", msg) + } + + fmt.Fprintln(os.Stdout, msg) + return nil +} + +func runAddSMTPLogger(c *cli.Context) error { + setup("manager", c.Bool("debug")) + vals := map[string]interface{}{} + mode := "smtp" + if c.IsSet("host") { + vals["host"] = c.String("host") + } else { + vals["host"] = "127.0.0.1:25" + } + + if c.IsSet("username") { + vals["username"] = c.String("username") + } + if c.IsSet("password") { + vals["password"] = c.String("password") + } + + if !c.IsSet("send-to") { + return fmt.Errorf("Some recipients must be provided") + } + vals["sendTos"] = c.StringSlice("send-to") + + if c.IsSet("subject") { + vals["subject"] = c.String("subject") + } else { + vals["subject"] = "Diagnostic message from Gitea" + } + + return commonAddLogger(c, mode, vals) +} + +func runAddConnLogger(c *cli.Context) error { + setup("manager", c.Bool("debug")) + vals := map[string]interface{}{} + mode := "conn" + vals["net"] = "tcp" + if c.IsSet("protocol") { + switch c.String("protocol") { + case "udp": + vals["net"] = "udp" + case "unix": + vals["net"] = "unix" + } + } + if c.IsSet("address") { + vals["address"] = c.String("address") + } else { + vals["address"] = ":7020" + } + if c.IsSet("reconnect") { + vals["reconnect"] = c.Bool("reconnect") + } + if c.IsSet("reconnect-on-message") { + vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") + } + return commonAddLogger(c, mode, vals) +} + +func runAddFileLogger(c *cli.Context) error { + setup("manager", c.Bool("debug")) + vals := map[string]interface{}{} + mode := "file" + if c.IsSet("filename") { + vals["filename"] = c.String("filename") + } else { + return fmt.Errorf("filename must be set when creating a file logger") + } + if c.IsSet("rotate") { + vals["rotate"] = c.Bool("rotate") + } + if c.IsSet("max-size") { + vals["maxsize"] = c.Int64("max-size") + } + if c.IsSet("daily") { + vals["daily"] = c.Bool("daily") + } + if c.IsSet("max-days") { + vals["maxdays"] = c.Int("max-days") + } + if c.IsSet("compress") { + vals["compress"] = c.Bool("compress") + } + if c.IsSet("compression-level") { + vals["compressionLevel"] = c.Int("compression-level") + } + return commonAddLogger(c, mode, vals) +} + +func runAddConsoleLogger(c *cli.Context) error { + setup("manager", c.Bool("debug")) + vals := map[string]interface{}{} + mode := "console" + if c.IsSet("stderr") && c.Bool("stderr") { + vals["stderr"] = c.Bool("stderr") + } + return commonAddLogger(c, mode, vals) +} + +func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error { + if len(c.String("level")) > 0 { + vals["level"] = log.FromString(c.String("level")).String() + } + if len(c.String("stacktrace-level")) > 0 { + vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String() + } + if len(c.String("expression")) > 0 { + vals["expression"] = c.String("expression") + } + if len(c.String("prefix")) > 0 { + vals["prefix"] = c.String("prefix") + } + if len(c.String("flags")) > 0 { + vals["flags"] = log.FlagsFromString(c.String("flags")) + } + if c.IsSet("color") { + vals["colorize"] = c.Bool("color") + } + group := "default" + if c.IsSet("group") { + group = c.String("group") + } + name := mode + if c.IsSet("name") { + name = c.String("name") + } + ctx, cancel := installSignals() + defer cancel() + + statusCode, msg := private.AddLogger(ctx, group, name, mode, vals) + switch statusCode { + case http.StatusInternalServerError: + return fail("InternalServerError", msg) + } + + fmt.Fprintln(os.Stdout, msg) + return nil +} + +func runPauseLogging(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setup("manager", c.Bool("debug")) + statusCode, msg := private.PauseLogging(ctx) + switch statusCode { + case http.StatusInternalServerError: + return fail("InternalServerError", msg) + } + + fmt.Fprintln(os.Stdout, msg) + return nil +} + +func runResumeLogging(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setup("manager", c.Bool("debug")) + statusCode, msg := private.ResumeLogging(ctx) + switch statusCode { + case http.StatusInternalServerError: + return fail("InternalServerError", msg) + } + + fmt.Fprintln(os.Stdout, msg) + return nil +} + +func runReleaseReopenLogging(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setup("manager", c.Bool("debug")) + statusCode, msg := private.ReleaseReopenLogging(ctx) + switch statusCode { + case http.StatusInternalServerError: + return fail("InternalServerError", msg) + } + + fmt.Fprintln(os.Stdout, msg) + return nil +} diff --git a/cmd/web.go b/cmd/web.go index 710c12775fd0..8c7c02617274 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/install" @@ -59,6 +60,9 @@ and it takes care of all the other things for you`, } func runHTTPRedirector() { + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true) + defer finished() + source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect) dest := strings.TrimSuffix(setting.AppURL, "/") log.Info("Redirecting: %s to %s", source, dest) @@ -141,8 +145,10 @@ func runWeb(ctx *cli.Context) error { if setting.EnablePprof { go func() { + _, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true) log.Info("Starting pprof server on localhost:6060") log.Info("%v", http.ListenAndServe("localhost:6060", nil)) + finished() }() } @@ -204,6 +210,8 @@ func listen(m http.Handler, handleRedirector bool) error { if setting.Protocol != setting.HTTPUnix && setting.Protocol != setting.FCGIUnix { listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort) } + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: Gitea Server", process.SystemProcessType, true) + defer finished() log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL) // This can be useful for users, many users do wrong to their config and get strange behaviors behind a reverse-proxy. // A user may fix the configuration mistake when he sees this log. diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 459d4f0a7697..7dbeb14a0e64 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "github.com/caddyserver/certmagic" @@ -107,6 +108,9 @@ func runACME(listenAddr string, m http.Handler) error { if enableHTTPChallenge { go func() { + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true) + defer finished() + log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 80a2c6716de5..3b75a5c843d2 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -503,6 +503,13 @@ Manage running server operations: - `--host value`, `-H value`: Mail server host (defaults to: 127.0.0.1:25) - `--send-to value`, `-s value`: Email address(es) to send to - `--subject value`, `-S value`: Subject header of sent emails + - `processes`: Display Gitea processes and goroutine information + - Options: + - `--flat`: Show processes as flat table rather than as tree + - `--no-system`: Do not show system processes + - `--stacktraces`: Show stacktraces for goroutines associated with processes + - `--json`: Output as json + - `--cancel PID`: Send cancel to process with PID. (Only for non-system processes.) ### dump-repo diff --git a/go.mod b/go.mod index e57867ed31aa..0378ccd5e7bb 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/golang-jwt/jwt/v4 v4.3.0 github.com/google/go-github/v39 v39.2.0 + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 github.com/google/uuid v1.3.0 github.com/gorilla/feeds v1.1.1 github.com/gorilla/sessions v1.2.1 @@ -202,7 +203,7 @@ require ( github.com/jhump/protoreflect v1.8.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/kevinburke/ssh_config v1.1.0 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 5c052a33e1e3..d8624a28e67f 100644 --- a/go.sum +++ b/go.sum @@ -761,6 +761,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= @@ -1003,8 +1004,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o= -github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4= github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= diff --git a/integrations/api_repo_git_tags_test.go b/integrations/api_repo_git_tags_test.go index 7884560df849..9e870d248913 100644 --- a/integrations/api_repo_git_tags_test.go +++ b/integrations/api_repo_git_tags_test.go @@ -28,8 +28,8 @@ func TestAPIGitTags(t *testing.T) { token := getTokenForLoggedInUser(t, session) // Set up git config for the tagger - git.NewCommand(git.DefaultContext, "config", "user.name", user.Name).RunInDir(repo.RepoPath()) - git.NewCommand(git.DefaultContext, "config", "user.email", user.Email).RunInDir(repo.RepoPath()) + _ = git.NewCommand(git.DefaultContext, "config", "user.name", user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()}) + _ = git.NewCommand(git.DefaultContext, "config", "user.email", user.Email).Run(&git.RunOpts{Dir: repo.RepoPath()}) gitRepo, _ := git.OpenRepository(git.DefaultContext, repo.RepoPath()) defer gitRepo.Close() diff --git a/integrations/git_helper_for_declarative_test.go b/integrations/git_helper_for_declarative_test.go index 674fad5f18bf..1ea594b739c8 100644 --- a/integrations/git_helper_for_declarative_test.go +++ b/integrations/git_helper_for_declarative_test.go @@ -134,7 +134,7 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { // Init repository in dstPath assert.NoError(t, git.InitRepository(git.DefaultContext, dstPath, false)) // forcibly set default branch to master - _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) @@ -153,49 +153,49 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { func doGitAddRemote(dstPath, remoteName string, u *url.URL) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, "remote", "add", remoteName, u.String()).RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, "remote", "add", remoteName, u.String()).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepository(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, append([]string{"push", "-u"}, args...)...).RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"push", "-u"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, append([]string{"push"}, args...)...).RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"push"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.Error(t, err) } } func doGitCreateBranch(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, "checkout", "-b", branch).RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, "checkout", "-b", branch).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitCheckoutBranch(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "checkout"), args...)...).RunInDir(dstPath) + _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "checkout"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitMerge(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, append([]string{"merge"}, args...)...).RunInDir(dstPath) + _, _, err := git.NewCommand(git.DefaultContext, append([]string{"merge"}, args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } func doGitPull(dstPath string, args ...string) func(*testing.T) { return func(t *testing.T) { - _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "pull"), args...)...).RunInDir(dstPath) + _, _, err := git.NewCommandNoGlobals(append(append(git.AllowLFSFiltersArgs(), "pull"), args...)...).RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) } } diff --git a/integrations/git_test.go b/integrations/git_test.go index e48d17c9b05e..85f08606ee4b 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -160,9 +160,9 @@ func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS strin return } prefix := "lfs-data-file-" - _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").RunInDir(dstPath) + err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track", prefix+"*").RunInDir(dstPath) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("track", prefix+"*").RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) err = git.AddChanges(dstPath, false, ".gitattributes") assert.NoError(t, err) @@ -292,20 +292,20 @@ func lockTest(t *testing.T, repoPath string) { } func lockFileTest(t *testing.T, filename, repoPath string) { - _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunInDir(repoPath) + _, _, err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock", filename).RunInDir(repoPath) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("lock", filename).RunStdString(&git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunInDir(repoPath) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("locks").RunStdString(&git.RunOpts{Dir: repoPath}) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock", filename).RunInDir(repoPath) + _, _, err = git.NewCommand(git.DefaultContext, "lfs").AddArguments("unlock", filename).RunStdString(&git.RunOpts{Dir: repoPath}) assert.NoError(t, err) } func doCommitAndPush(t *testing.T, size int, repoPath, prefix string) string { name, err := generateCommitWithNewData(size, repoPath, "user2@example.com", "User Two", prefix) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunInDir(repoPath) // Push + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: repoPath}) // Push assert.NoError(t, err) return name } @@ -671,7 +671,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB }) t.Run("Push", func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).RunInDir(dstPath) + err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath}) if !assert.NoError(t, err) { return } @@ -692,7 +692,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB assert.Contains(t, "Testing commit 1", prMsg.Body) assert.Equal(t, commit, prMsg.Head.Sha) - _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunInDir(dstPath) + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) if !assert.NoError(t, err) { return } @@ -745,7 +745,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB }) t.Run("Push2", func(t *testing.T) { - _, err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).RunInDir(dstPath) + err := git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o", "topic="+headBranch).Run(&git.RunOpts{Dir: dstPath}) if !assert.NoError(t, err) { return } @@ -757,7 +757,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headB assert.Equal(t, false, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) - _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunInDir(dstPath) + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master/test/"+headBranch).RunStdString(&git.RunOpts{Dir: dstPath}) if !assert.NoError(t, err) { return } diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go index f865a3371ede..4e063bbdb6f2 100644 --- a/integrations/pull_merge_test.go +++ b/integrations/pull_merge_test.go @@ -269,25 +269,24 @@ func TestCantMergeUnrelated(t *testing.T) { }).(*repo_model.Repository) path := repo_model.RepoPath(user1.Name, repo1.Name) - _, err := git.NewCommand(git.DefaultContext, "read-tree", "--empty").RunInDir(path) + err := git.NewCommand(git.DefaultContext, "read-tree", "--empty").Run(&git.RunOpts{Dir: path}) assert.NoError(t, err) stdin := bytes.NewBufferString("Unrelated File") var stdout strings.Builder - err = git.NewCommand(git.DefaultContext, "hash-object", "-w", "--stdin").RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: path, - Stdin: stdin, - Stdout: &stdout, + err = git.NewCommand(git.DefaultContext, "hash-object", "-w", "--stdin").Run(&git.RunOpts{ + Dir: path, + Stdin: stdin, + Stdout: &stdout, }) assert.NoError(t, err) sha := strings.TrimSpace(stdout.String()) - _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo", "100644", sha, "somewher-over-the-rainbow").RunInDir(path) + _, _, err = git.NewCommand(git.DefaultContext, "update-index", "--add", "--replace", "--cacheinfo", "100644", sha, "somewher-over-the-rainbow").RunStdString(&git.RunOpts{Dir: path}) assert.NoError(t, err) - treeSha, err := git.NewCommand(git.DefaultContext, "write-tree").RunInDir(path) + treeSha, _, err := git.NewCommand(git.DefaultContext, "write-tree").RunStdString(&git.RunOpts{Dir: path}) assert.NoError(t, err) treeSha = strings.TrimSpace(treeSha) @@ -308,17 +307,16 @@ func TestCantMergeUnrelated(t *testing.T) { stdout.Reset() err = git.NewCommand(git.DefaultContext, "commit-tree", treeSha). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: path, - Stdin: messageBytes, - Stdout: &stdout, + Run(&git.RunOpts{ + Env: env, + Dir: path, + Stdin: messageBytes, + Stdout: &stdout, }) assert.NoError(t, err) commitSha := strings.TrimSpace(stdout.String()) - _, err = git.NewCommand(git.DefaultContext, "branch", "unrelated", commitSha).RunInDir(path) + _, _, err = git.NewCommand(git.DefaultContext, "branch", "unrelated", commitSha).RunStdString(&git.RunOpts{Dir: path}) assert.NoError(t, err) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") diff --git a/integrations/repo_tag_test.go b/integrations/repo_tag_test.go index 20bfc555b87d..ef7a11422ea9 100644 --- a/integrations/repo_tag_test.go +++ b/integrations/repo_tag_test.go @@ -66,10 +66,10 @@ func TestCreateNewTagProtected(t *testing.T) { doGitClone(dstPath, u)(t) - _, err = git.NewCommand(git.DefaultContext, "tag", "v-2").RunInDir(dstPath) + _, _, err = git.NewCommand(git.DefaultContext, "tag", "v-2").RunStdString(&git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunInDir(dstPath) + _, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath}) assert.Error(t, err) assert.Contains(t, err.Error(), "Tag v-2 is protected") }) diff --git a/integrations/repo_test.go b/integrations/repo_test.go index 677ba57f8076..872d3f24d1f7 100644 --- a/integrations/repo_test.go +++ b/integrations/repo_test.go @@ -155,7 +155,7 @@ func TestViewRepoWithSymlinks(t *testing.T) { return fmt.Sprintf("%s: %s", file, cls) }) assert.Len(t, items, 5) - assert.Equal(t, "a: svg octicon-file-directory", items[0]) + assert.Equal(t, "a: svg octicon-file-directory-fill", items[0]) assert.Equal(t, "link_b: svg octicon-file-submodule", items[1]) assert.Equal(t, "link_d: svg octicon-file-symlink-file", items[2]) assert.Equal(t, "link_hi: svg octicon-file-symlink-file", items[3]) diff --git a/models/action_list.go b/models/action_list.go index ce621753a46f..c180a82552c5 100644 --- a/models/action_list.go +++ b/models/action_list.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" ) // ActionList defines a list of actions @@ -22,7 +23,7 @@ func (actions ActionList) getUserIDs() []int64 { userIDs[action.ActUserID] = struct{}{} } } - return keysInt64(userIDs) + return container.KeysInt64(userIDs) } func (actions ActionList) loadUsers(e db.Engine) (map[int64]*user_model.User, error) { @@ -52,7 +53,7 @@ func (actions ActionList) getRepoIDs() []int64 { repoIDs[action.RepoID] = struct{}{} } } - return keysInt64(repoIDs) + return container.KeysInt64(repoIDs) } func (actions ActionList) loadRepositories(e db.Engine) error { diff --git a/models/error.go b/models/error.go index cbfb60790f33..6233b2ea853d 100644 --- a/models/error.go +++ b/models/error.go @@ -586,18 +586,18 @@ func (err ErrBranchesEqual) Error() string { return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName) } -// ErrNotAllowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. -type ErrNotAllowedToMerge struct { +// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. +type ErrDisallowedToMerge struct { Reason string } -// IsErrNotAllowedToMerge checks if an error is an ErrNotAllowedToMerge. -func IsErrNotAllowedToMerge(err error) bool { - _, ok := err.(ErrNotAllowedToMerge) +// IsErrDisallowedToMerge checks if an error is an ErrDisallowedToMerge. +func IsErrDisallowedToMerge(err error) bool { + _, ok := err.(ErrDisallowedToMerge) return ok } -func (err ErrNotAllowedToMerge) Error() string { +func (err ErrDisallowedToMerge) Error() string { return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) } @@ -765,36 +765,6 @@ func (err ErrPullWasClosed) Error() string { return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index) } -// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created -type ErrForbiddenIssueReaction struct { - Reaction string -} - -// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction. -func IsErrForbiddenIssueReaction(err error) bool { - _, ok := err.(ErrForbiddenIssueReaction) - return ok -} - -func (err ErrForbiddenIssueReaction) Error() string { - return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction) -} - -// ErrReactionAlreadyExist is used when a existing reaction was try to created -type ErrReactionAlreadyExist struct { - Reaction string -} - -// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist. -func IsErrReactionAlreadyExist(err error) bool { - _, ok := err.(ErrReactionAlreadyExist) - return ok -} - -func (err ErrReactionAlreadyExist) Error() string { - return fmt.Sprintf("reaction '%s' already exists", err.Reaction) -} - // __________ .__ .__ __________ __ // \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_ // | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ diff --git a/models/helper.go b/models/helper.go index 15df42453901..c5f2d7a5b83f 100644 --- a/models/helper.go +++ b/models/helper.go @@ -6,17 +6,8 @@ package models import ( repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" ) -func keysInt64(m map[int64]struct{}) []int64 { - keys := make([]int64, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - return keys -} - func valuesRepository(m map[int64]*repo_model.Repository) []*repo_model.Repository { values := make([]*repo_model.Repository, 0, len(m)) for _, v := range m { @@ -24,11 +15,3 @@ func valuesRepository(m map[int64]*repo_model.Repository) []*repo_model.Reposito } return values } - -func valuesUser(m map[int64]*user_model.User) []*user_model.User { - values := make([]*user_model.User, 0, len(m)) - for _, v := range m { - values = append(values, v) - } - return values -} diff --git a/models/issue.go b/models/issue.go index 99688f2ec9c8..cf5e4fd8b6fe 100644 --- a/models/issue.go +++ b/models/issue.go @@ -72,7 +72,7 @@ type Issue struct { Attachments []*repo_model.Attachment `xorm:"-"` Comments []*Comment `xorm:"-"` - Reactions ReactionList `xorm:"-"` + Reactions issues.ReactionList `xorm:"-"` TotalTrackedTime int64 `xorm:"-"` Assignees []*user_model.User `xorm:"-"` ForeignReference *foreignreference.ForeignReference `xorm:"-"` @@ -244,8 +244,7 @@ func (issue *Issue) loadReactions(ctx context.Context) (err error) { if issue.Reactions != nil { return nil } - e := db.GetEngine(ctx) - reactions, _, err := findReactions(e, FindReactionsOptions{ + reactions, _, err := issues.FindReactions(ctx, issues.FindReactionsOptions{ IssueID: issue.ID, }) if err != nil { @@ -255,7 +254,7 @@ func (issue *Issue) loadReactions(ctx context.Context) (err error) { return err } // Load reaction user data - if _, err := ReactionList(reactions).loadUsers(e, issue.Repo); err != nil { + if _, err := issues.ReactionList(reactions).LoadUsers(ctx, issue.Repo); err != nil { return err } @@ -2111,7 +2110,7 @@ func deleteIssue(ctx context.Context, issue *Issue) error { &IssueAssignees{}, &IssueUser{}, &Notification{}, - &Reaction{}, + &issues.Reaction{}, &IssueWatch{}, &Stopwatch{}, &TrackedTime{}, @@ -2429,7 +2428,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin } if _, err = sess.In("issue_id", deleteCond). - Delete(&Reaction{}); err != nil { + Delete(&issues.Reaction{}); err != nil { return } diff --git a/models/issue_comment.go b/models/issue_comment.go index 7fb013ae924c..332ae7bdc5e3 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -244,7 +244,7 @@ type Comment struct { CommitSHA string `xorm:"VARCHAR(40)"` Attachments []*repo_model.Attachment `xorm:"-"` - Reactions ReactionList `xorm:"-"` + Reactions issues.ReactionList `xorm:"-"` // For view issue page. ShowRole RoleDescriptor `xorm:"-"` @@ -631,11 +631,11 @@ func (c *Comment) LoadTime() error { return err } -func (c *Comment) loadReactions(e db.Engine, repo *repo_model.Repository) (err error) { +func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository) (err error) { if c.Reactions != nil { return nil } - c.Reactions, _, err = findReactions(e, FindReactionsOptions{ + c.Reactions, _, err = issues.FindReactions(ctx, issues.FindReactionsOptions{ IssueID: c.IssueID, CommentID: c.ID, }) @@ -643,7 +643,7 @@ func (c *Comment) loadReactions(e db.Engine, repo *repo_model.Repository) (err e return err } // Load reaction user data - if _, err := c.Reactions.loadUsers(e, repo); err != nil { + if _, err := c.Reactions.LoadUsers(ctx, repo); err != nil { return err } return nil @@ -651,7 +651,7 @@ func (c *Comment) loadReactions(e db.Engine, repo *repo_model.Repository) (err e // LoadReactions loads comment reactions func (c *Comment) LoadReactions(repo *repo_model.Repository) error { - return c.loadReactions(db.GetEngine(db.DefaultContext), repo) + return c.loadReactions(db.DefaultContext, repo) } func (c *Comment) loadReview(e db.Engine) (err error) { @@ -1146,14 +1146,15 @@ func DeleteComment(comment *Comment) error { } defer committer.Close() - if err := deleteComment(db.GetEngine(ctx), comment); err != nil { + if err := deleteComment(ctx, comment); err != nil { return err } return committer.Commit() } -func deleteComment(e db.Engine, comment *Comment) error { +func deleteComment(ctx context.Context, comment *Comment) error { + e := db.GetEngine(ctx) if _, err := e.ID(comment.ID).NoAutoCondition().Delete(comment); err != nil { return err } @@ -1177,7 +1178,7 @@ func deleteComment(e db.Engine, comment *Comment) error { return err } - return deleteReaction(e, &ReactionOptions{Comment: comment}) + return issues.DeleteReaction(ctx, &issues.ReactionOptions{CommentID: comment.ID}) } // CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS diff --git a/models/issue_comment_list.go b/models/issue_comment_list.go index 23a2756dcf2c..2107f4790f8c 100644 --- a/models/issue_comment_list.go +++ b/models/issue_comment_list.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" ) // CommentList defines a list of comments @@ -22,7 +23,7 @@ func (comments CommentList) getPosterIDs() []int64 { posterIDs[comment.PosterID] = struct{}{} } } - return keysInt64(posterIDs) + return container.KeysInt64(posterIDs) } func (comments CommentList) loadPosters(e db.Engine) error { @@ -75,7 +76,7 @@ func (comments CommentList) getLabelIDs() []int64 { ids[comment.LabelID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadLabels(e db.Engine) error { @@ -125,7 +126,7 @@ func (comments CommentList) getMilestoneIDs() []int64 { ids[comment.MilestoneID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadMilestones(e db.Engine) error { @@ -168,7 +169,7 @@ func (comments CommentList) getOldMilestoneIDs() []int64 { ids[comment.OldMilestoneID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadOldMilestones(e db.Engine) error { @@ -211,7 +212,7 @@ func (comments CommentList) getAssigneeIDs() []int64 { ids[comment.AssigneeID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadAssignees(e db.Engine) error { @@ -267,7 +268,7 @@ func (comments CommentList) getIssueIDs() []int64 { ids[comment.IssueID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } // Issues returns all the issues of comments @@ -342,7 +343,7 @@ func (comments CommentList) getDependentIssueIDs() []int64 { ids[comment.DependentIssueID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadDependentIssues(ctx context.Context) error { @@ -444,7 +445,7 @@ func (comments CommentList) getReviewIDs() []int64 { ids[comment.ReviewID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (comments CommentList) loadReviews(e db.Engine) error { diff --git a/models/issue_list.go b/models/issue_list.go index b516e7336e02..8d7a3121b091 100644 --- a/models/issue_list.go +++ b/models/issue_list.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "xorm.io/builder" ) @@ -32,7 +33,7 @@ func (issues IssueList) getRepoIDs() []int64 { repoIDs[issue.RepoID] = struct{}{} } } - return keysInt64(repoIDs) + return container.KeysInt64(repoIDs) } func (issues IssueList) loadRepositories(e db.Engine) ([]*repo_model.Repository, error) { @@ -83,7 +84,7 @@ func (issues IssueList) getPosterIDs() []int64 { posterIDs[issue.PosterID] = struct{}{} } } - return keysInt64(posterIDs) + return container.KeysInt64(posterIDs) } func (issues IssueList) loadPosters(e db.Engine) error { @@ -189,7 +190,7 @@ func (issues IssueList) getMilestoneIDs() []int64 { ids[issue.MilestoneID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } func (issues IssueList) loadMilestones(e db.Engine) error { diff --git a/models/issues/main_test.go b/models/issues/main_test.go index af71f038d63e..1c786d005f1e 100644 --- a/models/issues/main_test.go +++ b/models/issues/main_test.go @@ -9,8 +9,18 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" ) +func init() { + setting.SetCustomPathAndConf("", "", "") + setting.LoadForTest() +} + func TestMain(m *testing.M) { - unittest.MainTest(m, filepath.Join("..", ".."), "") + unittest.MainTest(m, filepath.Join("..", ".."), + "reaction.yml", + "user.yml", + "repository.yml", + ) } diff --git a/models/issue_reaction.go b/models/issues/reaction.go similarity index 68% rename from models/issue_reaction.go rename to models/issues/reaction.go index 45b1d64fe113..87d6ff431054 100644 --- a/models/issue_reaction.go +++ b/models/issues/reaction.go @@ -2,21 +2,53 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package issues import ( "bytes" + "context" "fmt" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "xorm.io/builder" ) +// ErrForbiddenIssueReaction is used when a forbidden reaction was try to created +type ErrForbiddenIssueReaction struct { + Reaction string +} + +// IsErrForbiddenIssueReaction checks if an error is a ErrForbiddenIssueReaction. +func IsErrForbiddenIssueReaction(err error) bool { + _, ok := err.(ErrForbiddenIssueReaction) + return ok +} + +func (err ErrForbiddenIssueReaction) Error() string { + return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction) +} + +// ErrReactionAlreadyExist is used when a existing reaction was try to created +type ErrReactionAlreadyExist struct { + Reaction string +} + +// IsErrReactionAlreadyExist checks if an error is a ErrReactionAlreadyExist. +func IsErrReactionAlreadyExist(err error) bool { + _, ok := err.(ErrReactionAlreadyExist) + return ok +} + +func (err ErrReactionAlreadyExist) Error() string { + return fmt.Sprintf("reaction '%s' already exists", err.Reaction) +} + // Reaction represents a reactions on issues and comments. type Reaction struct { ID int64 `xorm:"pk autoincr"` @@ -30,6 +62,36 @@ type Reaction struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` } +// LoadUser load user of reaction +func (r *Reaction) LoadUser() (*user_model.User, error) { + if r.User != nil { + return r.User, nil + } + user, err := user_model.GetUserByIDCtx(db.DefaultContext, r.UserID) + if err != nil { + return nil, err + } + r.User = user + return user, nil +} + +// RemapExternalUser ExternalUserRemappable interface +func (r *Reaction) RemapExternalUser(externalName string, externalID, userID int64) error { + r.OriginalAuthor = externalName + r.OriginalAuthorID = externalID + r.UserID = userID + return nil +} + +// GetUserID ExternalUserRemappable interface +func (r *Reaction) GetUserID() int64 { return r.UserID } + +// GetExternalName ExternalUserRemappable interface +func (r *Reaction) GetExternalName() string { return r.OriginalAuthor } + +// GetExternalID ExternalUserRemappable interface +func (r *Reaction) GetExternalID() int64 { return r.OriginalAuthorID } + func init() { db.RegisterModel(new(Reaction)) } @@ -71,24 +133,25 @@ func (opts *FindReactionsOptions) toConds() builder.Cond { } // FindCommentReactions returns a ReactionList of all reactions from an comment -func FindCommentReactions(comment *Comment) (ReactionList, int64, error) { - return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{ - IssueID: comment.IssueID, - CommentID: comment.ID, +func FindCommentReactions(issueID, commentID int64) (ReactionList, int64, error) { + return FindReactions(db.DefaultContext, FindReactionsOptions{ + IssueID: issueID, + CommentID: commentID, }) } // FindIssueReactions returns a ReactionList of all reactions from an issue -func FindIssueReactions(issue *Issue, listOptions db.ListOptions) (ReactionList, int64, error) { - return findReactions(db.GetEngine(db.DefaultContext), FindReactionsOptions{ +func FindIssueReactions(issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { + return FindReactions(db.DefaultContext, FindReactionsOptions{ ListOptions: listOptions, - IssueID: issue.ID, + IssueID: issueID, CommentID: -1, }) } -func findReactions(e db.Engine, opts FindReactionsOptions) ([]*Reaction, int64, error) { - sess := e. +// FindReactions returns a ReactionList of all reactions from an issue or a comment +func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList, int64, error) { + sess := db.GetEngine(ctx). Where(opts.toConds()). In("reaction.`type`", setting.UI.Reactions). Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id") @@ -105,24 +168,21 @@ func findReactions(e db.Engine, opts FindReactionsOptions) ([]*Reaction, int64, return reactions, count, err } -func createReaction(e db.Engine, opts *ReactionOptions) (*Reaction, error) { +func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) { reaction := &Reaction{ - Type: opts.Type, - UserID: opts.Doer.ID, - IssueID: opts.Issue.ID, + Type: opts.Type, + UserID: opts.DoerID, + IssueID: opts.IssueID, + CommentID: opts.CommentID, } findOpts := FindReactionsOptions{ - IssueID: opts.Issue.ID, - CommentID: -1, // reaction to issue only + IssueID: opts.IssueID, + CommentID: opts.CommentID, Reaction: opts.Type, - UserID: opts.Doer.ID, - } - if opts.Comment != nil { - reaction.CommentID = opts.Comment.ID - findOpts.CommentID = opts.Comment.ID + UserID: opts.DoerID, } - existingR, _, err := findReactions(e, findOpts) + existingR, _, err := FindReactions(ctx, findOpts) if err != nil { return nil, err } @@ -130,7 +190,7 @@ func createReaction(e db.Engine, opts *ReactionOptions) (*Reaction, error) { return existingR[0], ErrReactionAlreadyExist{Reaction: opts.Type} } - if _, err := e.Insert(reaction); err != nil { + if err := db.Insert(ctx, reaction); err != nil { return nil, err } @@ -139,10 +199,10 @@ func createReaction(e db.Engine, opts *ReactionOptions) (*Reaction, error) { // ReactionOptions defines options for creating or deleting reactions type ReactionOptions struct { - Type string - Doer *user_model.User - Issue *Issue - Comment *Comment + Type string + DoerID int64 + IssueID int64 + CommentID int64 } // CreateReaction creates reaction for issue or comment. @@ -157,7 +217,7 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) { } defer committer.Close() - reaction, err := createReaction(db.GetEngine(ctx), opts) + reaction, err := createReaction(ctx, opts) if err != nil { return reaction, err } @@ -169,88 +229,56 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) { } // CreateIssueReaction creates a reaction on issue. -func CreateIssueReaction(doer *user_model.User, issue *Issue, content string) (*Reaction, error) { +func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, error) { return CreateReaction(&ReactionOptions{ - Type: content, - Doer: doer, - Issue: issue, + Type: content, + DoerID: doerID, + IssueID: issueID, }) } // CreateCommentReaction creates a reaction on comment. -func CreateCommentReaction(doer *user_model.User, issue *Issue, comment *Comment, content string) (*Reaction, error) { +func CreateCommentReaction(doerID, issueID, commentID int64, content string) (*Reaction, error) { return CreateReaction(&ReactionOptions{ - Type: content, - Doer: doer, - Issue: issue, - Comment: comment, + Type: content, + DoerID: doerID, + IssueID: issueID, + CommentID: commentID, }) } -func deleteReaction(e db.Engine, opts *ReactionOptions) error { - reaction := &Reaction{ - Type: opts.Type, - } - if opts.Doer != nil { - reaction.UserID = opts.Doer.ID - } - if opts.Issue != nil { - reaction.IssueID = opts.Issue.ID - } - if opts.Comment != nil { - reaction.CommentID = opts.Comment.ID - } - _, err := e.Where("original_author_id = 0").Delete(reaction) - return err -} - // DeleteReaction deletes reaction for issue or comment. -func DeleteReaction(opts *ReactionOptions) error { - ctx, committer, err := db.TxContext() - if err != nil { - return err - } - defer committer.Close() - - if err := deleteReaction(db.GetEngine(ctx), opts); err != nil { - return err +func DeleteReaction(ctx context.Context, opts *ReactionOptions) error { + reaction := &Reaction{ + Type: opts.Type, + UserID: opts.DoerID, + IssueID: opts.IssueID, + CommentID: opts.CommentID, } - return committer.Commit() + _, err := db.GetEngine(ctx).Where("original_author_id = 0").Delete(reaction) + return err } // DeleteIssueReaction deletes a reaction on issue. -func DeleteIssueReaction(doer *user_model.User, issue *Issue, content string) error { - return DeleteReaction(&ReactionOptions{ - Type: content, - Doer: doer, - Issue: issue, +func DeleteIssueReaction(doerID, issueID int64, content string) error { + return DeleteReaction(db.DefaultContext, &ReactionOptions{ + Type: content, + DoerID: doerID, + IssueID: issueID, }) } // DeleteCommentReaction deletes a reaction on comment. -func DeleteCommentReaction(doer *user_model.User, issue *Issue, comment *Comment, content string) error { - return DeleteReaction(&ReactionOptions{ - Type: content, - Doer: doer, - Issue: issue, - Comment: comment, +func DeleteCommentReaction(doerID, issueID, commentID int64, content string) error { + return DeleteReaction(db.DefaultContext, &ReactionOptions{ + Type: content, + DoerID: doerID, + IssueID: issueID, + CommentID: commentID, }) } -// LoadUser load user of reaction -func (r *Reaction) LoadUser() (*user_model.User, error) { - if r.User != nil { - return r.User, nil - } - user, err := user_model.GetUserByIDCtx(db.DefaultContext, r.UserID) - if err != nil { - return nil, err - } - r.User = user - return user, nil -} - // ReactionList represents list of reactions type ReactionList []*Reaction @@ -286,17 +314,26 @@ func (list ReactionList) getUserIDs() []int64 { userIDs[reaction.UserID] = struct{}{} } } - return keysInt64(userIDs) + return container.KeysInt64(userIDs) +} + +func valuesUser(m map[int64]*user_model.User) []*user_model.User { + values := make([]*user_model.User, 0, len(m)) + for _, v := range m { + values = append(values, v) + } + return values } -func (list ReactionList) loadUsers(e db.Engine, repo *repo_model.Repository) ([]*user_model.User, error) { +// LoadUsers loads reactions' all users +func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) { if len(list) == 0 { return nil, nil } userIDs := list.getUserIDs() userMaps := make(map[int64]*user_model.User, len(userIDs)) - err := e. + err := db.GetEngine(ctx). In("id", userIDs). Find(&userMaps) if err != nil { @@ -315,11 +352,6 @@ func (list ReactionList) loadUsers(e db.Engine, repo *repo_model.Repository) ([] return valuesUser(userMaps), nil } -// LoadUsers loads reactions' all users -func (list ReactionList) LoadUsers(repo *repo_model.Repository) ([]*user_model.User, error) { - return list.loadUsers(db.GetEngine(db.DefaultContext), repo) -} - // GetFirstUsers returns first reacted user display names separated by comma func (list ReactionList) GetFirstUsers() string { var buffer bytes.Buffer @@ -343,20 +375,3 @@ func (list ReactionList) GetMoreUserCount() int { } return len(list) - setting.UI.ReactionMaxUserNum } - -// RemapExternalUser ExternalUserRemappable interface -func (r *Reaction) RemapExternalUser(externalName string, externalID, userID int64) error { - r.OriginalAuthor = externalName - r.OriginalAuthorID = externalID - r.UserID = userID - return nil -} - -// GetUserID ExternalUserRemappable interface -func (r *Reaction) GetUserID() int64 { return r.UserID } - -// GetExternalName ExternalUserRemappable interface -func (r *Reaction) GetExternalName() string { return r.OriginalAuthor } - -// GetExternalID ExternalUserRemappable interface -func (r *Reaction) GetExternalID() int64 { return r.OriginalAuthorID } diff --git a/models/issue_reaction_test.go b/models/issues/reaction_test.go similarity index 59% rename from models/issue_reaction_test.go rename to models/issues/reaction_test.go index 886d19e55f77..b1216a3a695c 100644 --- a/models/issue_reaction_test.go +++ b/models/issues/reaction_test.go @@ -1,7 +1,8 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models + +package issues import ( "testing" @@ -15,13 +16,13 @@ import ( "github.com/stretchr/testify/assert" ) -func addReaction(t *testing.T, doer *user_model.User, issue *Issue, comment *Comment, content string) { +func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) { var reaction *Reaction var err error - if comment == nil { - reaction, err = CreateIssueReaction(doer, issue, content) + if commentID == 0 { + reaction, err = CreateIssueReaction(doerID, issueID, content) } else { - reaction, err = CreateCommentReaction(doer, issue, comment, content) + reaction, err = CreateCommentReaction(doerID, issueID, commentID, content) } assert.NoError(t, err) assert.NotNil(t, reaction) @@ -32,11 +33,11 @@ func TestIssueAddReaction(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + var issue1ID int64 = 1 - addReaction(t, user1, issue1, nil, "heart") + addReaction(t, user1.ID, issue1ID, 0, "heart") - unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) + unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}) } func TestIssueAddDuplicateReaction(t *testing.T) { @@ -44,19 +45,19 @@ func TestIssueAddDuplicateReaction(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + var issue1ID int64 = 1 - addReaction(t, user1, issue1, nil, "heart") + addReaction(t, user1.ID, issue1ID, 0, "heart") reaction, err := CreateReaction(&ReactionOptions{ - Doer: user1, - Issue: issue1, - Type: "heart", + DoerID: user1.ID, + IssueID: issue1ID, + Type: "heart", }) assert.Error(t, err) assert.Equal(t, ErrReactionAlreadyExist{Reaction: "heart"}, err) - existingR := unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}).(*Reaction) + existingR := unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}).(*Reaction) assert.Equal(t, existingR.ID, reaction.ID) } @@ -65,14 +66,14 @@ func TestIssueDeleteReaction(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + var issue1ID int64 = 1 - addReaction(t, user1, issue1, nil, "heart") + addReaction(t, user1.ID, issue1ID, 0, "heart") - err := DeleteIssueReaction(user1, issue1, "heart") + err := DeleteIssueReaction(user1.ID, issue1ID, "heart") assert.NoError(t, err) - unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID}) + unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}) } func TestIssueReactionCount(t *testing.T) { @@ -86,22 +87,26 @@ func TestIssueReactionCount(t *testing.T) { user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) ghost := user_model.NewGhostUser() - issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) + var issueID int64 = 2 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) - addReaction(t, user1, issue, nil, "heart") - addReaction(t, user2, issue, nil, "heart") - addReaction(t, user3, issue, nil, "heart") - addReaction(t, user3, issue, nil, "+1") - addReaction(t, user4, issue, nil, "+1") - addReaction(t, user4, issue, nil, "heart") - addReaction(t, ghost, issue, nil, "-1") + addReaction(t, user1.ID, issueID, 0, "heart") + addReaction(t, user2.ID, issueID, 0, "heart") + addReaction(t, user3.ID, issueID, 0, "heart") + addReaction(t, user3.ID, issueID, 0, "+1") + addReaction(t, user4.ID, issueID, 0, "+1") + addReaction(t, user4.ID, issueID, 0, "heart") + addReaction(t, ghost.ID, issueID, 0, "-1") - err := issue.loadReactions(db.DefaultContext) + reactionsList, _, err := FindReactions(db.DefaultContext, FindReactionsOptions{ + IssueID: issueID, + }) + assert.NoError(t, err) + assert.Len(t, reactionsList, 7) + _, err = reactionsList.LoadUsers(db.DefaultContext, repo) assert.NoError(t, err) - assert.Len(t, issue.Reactions, 7) - - reactions := issue.Reactions.GroupByType() + reactions := reactionsList.GroupByType() assert.Len(t, reactions["heart"], 4) assert.Equal(t, 2, reactions["heart"].GetMoreUserCount()) assert.Equal(t, user1.DisplayName()+", "+user2.DisplayName(), reactions["heart"].GetFirstUsers()) @@ -118,13 +123,12 @@ func TestIssueCommentAddReaction(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + var issue1ID int64 = 1 + var comment1ID int64 = 1 - comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + addReaction(t, user1.ID, issue1ID, comment1ID, "heart") - addReaction(t, user1, issue1, comment1, "heart") - - unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) + unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID}) } func TestIssueCommentDeleteReaction(t *testing.T) { @@ -135,21 +139,22 @@ func TestIssueCommentDeleteReaction(t *testing.T) { user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}).(*repo_model.Repository) - - comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + var issue1ID int64 = 1 + var comment1ID int64 = 1 - addReaction(t, user1, issue1, comment1, "heart") - addReaction(t, user2, issue1, comment1, "heart") - addReaction(t, user3, issue1, comment1, "heart") - addReaction(t, user4, issue1, comment1, "+1") + addReaction(t, user1.ID, issue1ID, comment1ID, "heart") + addReaction(t, user2.ID, issue1ID, comment1ID, "heart") + addReaction(t, user3.ID, issue1ID, comment1ID, "heart") + addReaction(t, user4.ID, issue1ID, comment1ID, "+1") - err := comment1.LoadReactions(repo1) + reactionsList, _, err := FindReactions(db.DefaultContext, FindReactionsOptions{ + IssueID: issue1ID, + CommentID: comment1ID, + }) assert.NoError(t, err) - assert.Len(t, comment1.Reactions, 4) + assert.Len(t, reactionsList, 4) - reactions := comment1.Reactions.GroupByType() + reactions := reactionsList.GroupByType() assert.Len(t, reactions["heart"], 3) assert.Len(t, reactions["+1"], 1) } @@ -159,12 +164,11 @@ func TestIssueCommentReactionCount(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User) - issue1 := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) - - comment1 := unittest.AssertExistsAndLoadBean(t, &Comment{ID: 1}).(*Comment) + var issue1ID int64 = 1 + var comment1ID int64 = 1 - addReaction(t, user1, issue1, comment1, "heart") - assert.NoError(t, DeleteCommentReaction(user1, issue1, comment1, "heart")) + addReaction(t, user1.ID, issue1ID, comment1ID, "heart") + assert.NoError(t, DeleteCommentReaction(user1.ID, issue1ID, comment1ID, "heart")) - unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1.ID, CommentID: comment1.ID}) + unittest.AssertNotExistsBean(t, &Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID}) } diff --git a/models/migrate_test.go b/models/migrate_test.go index f4af7ffe373f..6da434d76a27 100644 --- a/models/migrate_test.go +++ b/models/migrate_test.go @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/models/foreignreference" + issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -42,7 +43,7 @@ func assertCreateIssues(t *testing.T, isPull bool) { label := unittest.AssertExistsAndLoadBean(t, &Label{ID: 1}).(*Label) milestone := unittest.AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) assert.EqualValues(t, milestone.ID, 1) - reaction := &Reaction{ + reaction := &issues_model.Reaction{ Type: "heart", UserID: owner.ID, } @@ -60,7 +61,7 @@ func assertCreateIssues(t *testing.T, isPull bool) { Poster: owner, IsClosed: true, Labels: []*Label{label}, - Reactions: []*Reaction{reaction}, + Reactions: []*issues_model.Reaction{reaction}, ForeignReference: &foreignreference.ForeignReference{ ForeignIndex: strconv.FormatInt(foreignIndex, 10), RepoID: repo.ID, @@ -75,7 +76,7 @@ func assertCreateIssues(t *testing.T, isPull bool) { err = i.LoadAttributes() assert.NoError(t, err) assert.EqualValues(t, strconv.FormatInt(foreignIndex, 10), i.ForeignReference.ForeignIndex) - unittest.AssertExistsAndLoadBean(t, &Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID}) + unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID}) } func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) { @@ -91,7 +92,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) { issue := unittest.AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) _ = issue.LoadRepo() owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User) - reaction := &Reaction{ + reaction := &issues_model.Reaction{ Type: "heart", UserID: owner.ID, } @@ -101,7 +102,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) { Poster: owner, IssueID: issue.ID, Issue: issue, - Reactions: []*Reaction{reaction}, + Reactions: []*issues_model.Reaction{reaction}, } err := InsertIssueComments([]*Comment{comment}) diff --git a/models/migrations/v128.go b/models/migrations/v128.go index 1454088c89a7..2aa68f9b6474 100644 --- a/models/migrations/v128.go +++ b/models/migrations/v128.go @@ -83,17 +83,17 @@ func fixMergeBase(x *xorm.Engine) error { if !pr.HasMerged { var err error - pr.MergeBase, err = git.NewCommand(git.DefaultContext, "merge-base", "--", pr.BaseBranch, gitRefName).RunInDir(repoPath) + pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, "merge-base", "--", pr.BaseBranch, gitRefName).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { var err2 error - pr.MergeBase, err2 = git.NewCommand(git.DefaultContext, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunInDir(repoPath) + pr.MergeBase, _, err2 = git.NewCommand(git.DefaultContext, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) if err2 != nil { log.Error("Unable to get merge base for PR ID %d, Index %d in %s/%s. Error: %v & %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err, err2) continue } } } else { - parentsString, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunInDir(repoPath) + parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -106,7 +106,7 @@ func fixMergeBase(x *xorm.Engine) error { args := append([]string{"merge-base", "--"}, parents[1:]...) args = append(args, gitRefName) - pr.MergeBase, err = git.NewCommand(git.DefaultContext, args...).RunInDir(repoPath) + pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, args...).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/models/migrations/v134.go b/models/migrations/v134.go index 3a8fd96b7c0f..16e8ec8e9425 100644 --- a/models/migrations/v134.go +++ b/models/migrations/v134.go @@ -80,7 +80,7 @@ func refixMergeBase(x *xorm.Engine) error { gitRefName := fmt.Sprintf("refs/pull/%d/head", pr.Index) - parentsString, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunInDir(repoPath) + parentsString, _, err := git.NewCommand(git.DefaultContext, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get parents for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue @@ -94,7 +94,7 @@ func refixMergeBase(x *xorm.Engine) error { args := append([]string{"merge-base", "--"}, parents[1:]...) args = append(args, gitRefName) - pr.MergeBase, err = git.NewCommand(git.DefaultContext, args...).RunInDir(repoPath) + pr.MergeBase, _, err = git.NewCommand(git.DefaultContext, args...).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { log.Error("Unable to get merge base for merged PR ID %d, Index %d in %s/%s. Error: %v", pr.ID, pr.Index, baseRepo.OwnerName, baseRepo.Name, err) continue diff --git a/models/notification.go b/models/notification.go index b5217777bfd4..9d0dc38aa4e2 100644 --- a/models/notification.go +++ b/models/notification.go @@ -15,6 +15,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -520,7 +521,7 @@ func (nl NotificationList) getPendingRepoIDs() []int64 { ids[notification.RepoID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } // LoadRepos loads repositories from database @@ -596,7 +597,7 @@ func (nl NotificationList) getPendingIssueIDs() []int64 { ids[notification.IssueID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } // LoadIssues loads issues from database @@ -682,7 +683,7 @@ func (nl NotificationList) getPendingCommentIDs() []int64 { ids[notification.CommentID] = struct{}{} } } - return keysInt64(ids) + return container.KeysInt64(ids) } // LoadComments loads comments from database diff --git a/models/org_team.go b/models/org_team.go index cf810cad337e..695f803dbfdb 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -498,11 +499,6 @@ func AddTeamMember(team *organization.Team, userID int64) error { } defer committer.Close() - // Get team and its repositories. - if err := team.GetRepositoriesCtx(ctx); err != nil { - return err - } - sess := db.GetEngine(ctx) if err := db.Insert(ctx, &organization.TeamUser{ @@ -518,17 +514,51 @@ func AddTeamMember(team *organization.Team, userID int64) error { team.NumMembers++ // Give access to team repositories. - for _, repo := range team.Repos { - if err := recalculateUserAccess(ctx, repo, userID); err != nil { - return err - } - if setting.Service.AutoWatchNewRepos { - if err = repo_model.WatchRepoCtx(ctx, userID, repo.ID, true); err != nil { - return err + // update exist access if mode become bigger + subQuery := builder.Select("repo_id").From("team_repo"). + Where(builder.Eq{"team_id": team.ID}) + + if _, err := sess.Where("user_id=?", userID). + In("repo_id", subQuery). + And("mode < ?", team.AccessMode). + SetExpr("mode", team.AccessMode). + Update(new(Access)); err != nil { + return fmt.Errorf("update user accesses: %v", err) + } + + // for not exist access + var repoIDs []int64 + accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID}) + if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { + return fmt.Errorf("select id accesses: %v", err) + } + + accesses := make([]*Access, 0, 100) + for i, repoID := range repoIDs { + accesses = append(accesses, &Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode}) + if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { + if err = db.Insert(ctx, accesses); err != nil { + return fmt.Errorf("insert new user accesses: %v", err) } + accesses = accesses[:0] } } + // watch could be failed, so run it in a goroutine + if setting.Service.AutoWatchNewRepos { + // Get team and its repositories. + if err := team.GetRepositoriesCtx(db.DefaultContext); err != nil { + log.Error("getRepositories failed: %v", err) + } + go func(repos []*repo_model.Repository) { + for _, repo := range repos { + if err = repo_model.WatchRepoCtx(db.DefaultContext, userID, repo.ID, true); err != nil { + log.Error("watch repo failed: %v", err) + } + } + }(team.Repos) + } + return committer.Commit() } diff --git a/models/organization/team.go b/models/organization/team.go index 16170058412f..077fba6a60cf 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -239,15 +239,17 @@ func (t *Team) GetMembersCtx(ctx context.Context) (err error) { // UnitEnabled returns if the team has the given unit type enabled func (t *Team) UnitEnabled(tp unit.Type) bool { - return t.unitEnabled(db.DefaultContext, tp) + return t.UnitAccessMode(tp) > perm.AccessModeNone } -func (t *Team) unitEnabled(ctx context.Context, tp unit.Type) bool { - return t.UnitAccessMode(ctx, tp) > perm.AccessModeNone +// UnitAccessMode returns if the team has the given unit type enabled +// it is called in templates, should not be replaced by `UnitAccessModeCtx(ctx ...)` +func (t *Team) UnitAccessMode(tp unit.Type) perm.AccessMode { + return t.UnitAccessModeCtx(db.DefaultContext, tp) } -// UnitAccessMode returns if the team has the given unit type enabled -func (t *Team) UnitAccessMode(ctx context.Context, tp unit.Type) perm.AccessMode { +// UnitAccessModeCtx returns if the team has the given unit type enabled +func (t *Team) UnitAccessModeCtx(ctx context.Context, tp unit.Type) perm.AccessMode { if err := t.getUnits(ctx); err != nil { log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error()) } diff --git a/models/pull.go b/models/pull.go index ec1fde02595b..6abd9f04b24f 100644 --- a/models/pull.go +++ b/models/pull.go @@ -222,22 +222,19 @@ func (pr *PullRequest) loadProtectedBranch(ctx context.Context) (err error) { } // GetDefaultMergeMessage returns default message used when merging pull request -func (pr *PullRequest) GetDefaultMergeMessage() string { +func (pr *PullRequest) GetDefaultMergeMessage() (string, error) { if pr.HeadRepo == nil { var err error pr.HeadRepo, err = repo_model.GetRepositoryByID(pr.HeadRepoID) if err != nil { - log.Error("GetRepositoryById[%d]: %v", pr.HeadRepoID, err) - return "" + return "", fmt.Errorf("GetRepositoryById[%d]: %v", pr.HeadRepoID, err) } } if err := pr.LoadIssue(); err != nil { - log.Error("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err) - return "" + return "", fmt.Errorf("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err) } if err := pr.LoadBaseRepo(); err != nil { - log.Error("LoadBaseRepo: %v", err) - return "" + return "", fmt.Errorf("LoadBaseRepo: %v", err) } issueReference := "#" @@ -246,10 +243,10 @@ func (pr *PullRequest) GetDefaultMergeMessage() string { } if pr.BaseRepoID == pr.HeadRepoID { - return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch) + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil } - return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch) + return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), nil } // ReviewCount represents a count of Reviews @@ -335,19 +332,17 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error { } // GetDefaultSquashMessage returns default message used when squash and merging pull request -func (pr *PullRequest) GetDefaultSquashMessage() string { +func (pr *PullRequest) GetDefaultSquashMessage() (string, error) { if err := pr.LoadIssue(); err != nil { - log.Error("LoadIssue: %v", err) - return "" + return "", fmt.Errorf("LoadIssue: %v", err) } if err := pr.LoadBaseRepo(); err != nil { - log.Error("LoadBaseRepo: %v", err) - return "" + return "", fmt.Errorf("LoadBaseRepo: %v", err) } if pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker) { - return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index) + return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index), nil } - return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index) + return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index), nil } // GetGitRefName returns git ref for hidden pull request branch diff --git a/models/pull_test.go b/models/pull_test.go index 2567984cc133..9098b611617a 100644 --- a/models/pull_test.go +++ b/models/pull_test.go @@ -261,11 +261,15 @@ func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) - assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", pr.GetDefaultMergeMessage()) + msg, err := pr.GetDefaultMergeMessage() + assert.NoError(t, err) + assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", msg) pr.BaseRepoID = 1 pr.HeadRepoID = 2 - assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage()) + msg, err = pr.GetDefaultMergeMessage() + assert.NoError(t, err) + assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", msg) } func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { @@ -283,9 +287,13 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest) - assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", pr.GetDefaultMergeMessage()) + msg, err := pr.GetDefaultMergeMessage() + assert.NoError(t, err) + assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", msg) pr.BaseRepoID = 1 pr.HeadRepoID = 2 - assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", pr.GetDefaultMergeMessage()) + msg, err = pr.GetDefaultMergeMessage() + assert.NoError(t, err) + assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", msg) } diff --git a/models/repo_list.go b/models/repo_list.go index 36f57abcc5b9..2c6be0a5768a 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -13,6 +13,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -62,7 +63,7 @@ func (repos RepositoryList) loadAttributes(e db.Engine) error { users := make(map[int64]*user_model.User, len(set)) if err := e. Where("id > 0"). - In("id", keysInt64(set)). + In("id", container.KeysInt64(set)). Find(&users); err != nil { return fmt.Errorf("find users: %v", err) } diff --git a/models/repo_permission.go b/models/repo_permission.go index 4dfc63d4e160..3e9ad04482d9 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -250,7 +250,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use for _, u := range repo.Units { var found bool for _, team := range teams { - teamMode := team.UnitAccessMode(ctx, u.Type) + teamMode := team.UnitAccessModeCtx(ctx, u.Type) if teamMode > perm_model.AccessModeNone { m := perm.UnitsMode[u.Type] if m < teamMode { diff --git a/models/review.go b/models/review.go index 6ef8d530b684..f360d459cfde 100644 --- a/models/review.go +++ b/models/review.go @@ -273,7 +273,7 @@ func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio } if !pr.ProtectedBranch.EnableApprovalsWhitelist { - return team.UnitAccessMode(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil + return team.UnitAccessModeCtx(ctx, unit.TypeCode) >= perm.AccessModeWrite, nil } return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil diff --git a/models/user.go b/models/user.go index 77442228093d..e8307796293f 100644 --- a/models/user.go +++ b/models/user.go @@ -14,6 +14,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -76,7 +77,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { &IssueUser{UID: u.ID}, &user_model.EmailAddress{UID: u.ID}, &user_model.UserOpenID{UID: u.ID}, - &Reaction{UserID: u.ID}, + &issues.Reaction{UserID: u.ID}, &organization.TeamUser{UID: u.ID}, &Collaboration{UserID: u.ID}, &Stopwatch{UserID: u.ID}, @@ -100,14 +101,14 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) { } for _, comment := range comments { - if err = deleteComment(e, comment); err != nil { + if err = deleteComment(ctx, comment); err != nil { return err } } } // Delete Reactions - if err = deleteReaction(e, &ReactionOptions{Doer: u}); err != nil { + if err = issues.DeleteReaction(ctx, &issues.ReactionOptions{DoerID: u.ID}); err != nil { return err } } diff --git a/modules/base/tool.go b/modules/base/tool.go index bf53a8ea8a8e..47ce125853fd 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -279,7 +279,7 @@ func EntryIcon(entry *git.TreeEntry) string { } return "file-symlink-file" case entry.IsDir(): - return "file-directory" + return "file-directory-fill" case entry.IsSubModule(): return "file-submodule" } diff --git a/modules/container/map.go b/modules/container/map.go new file mode 100644 index 000000000000..3519de09512e --- /dev/null +++ b/modules/container/map.go @@ -0,0 +1,14 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package container + +// KeysInt64 returns keys slice for a map with int64 key +func KeysInt64(m map[int64]struct{}) []int64 { + keys := make([]int64, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} diff --git a/modules/context/org.go b/modules/context/org.go index f8607bd29f53..427e4910c0de 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" ) // Organization contains organization context @@ -119,6 +120,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { } ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember + ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsPublicMember"] = func(uid int64) bool { is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid) return is diff --git a/modules/context/private.go b/modules/context/private.go index 6e5ef1bd121c..b57ba102e69a 100644 --- a/modules/context/private.go +++ b/modules/context/private.go @@ -79,6 +79,6 @@ func PrivateContexter() func(http.Handler) http.Handler { // the underlying request has timed out from the ssh/http push func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) { // We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work - ctx.Override, _, cancel = process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI)) + ctx.Override, _, cancel = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true) return } diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go index 5a780ed2a215..8f5c61a5dab2 100644 --- a/modules/doctor/mergebase.go +++ b/modules/doctor/mergebase.go @@ -44,17 +44,17 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro if !pr.HasMerged { var err error - pr.MergeBase, err = git.NewCommand(ctx, "merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunInDir(repoPath) + pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { var err2 error - pr.MergeBase, err2 = git.NewCommand(ctx, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunInDir(repoPath) + pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath}) if err2 != nil { logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2) return nil } } } else { - parentsString, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunInDir(repoPath) + parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil @@ -67,7 +67,7 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro args := append([]string{"merge-base", "--"}, parents[1:]...) args = append(args, pr.GetGitRefName()) - pr.MergeBase, err = git.NewCommand(ctx, args...).RunInDir(repoPath) + pr.MergeBase, _, err = git.NewCommand(ctx, args...).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err) return nil diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go index f1b7773c288f..60c190cf9884 100644 --- a/modules/doctor/misc.go +++ b/modules/doctor/misc.go @@ -95,11 +95,11 @@ func checkEnablePushOptions(ctx context.Context, logger log.Logger, autofix bool defer r.Close() if autofix { - _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunInDir(r.Path) + _, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions", "true").RunStdString(&git.RunOpts{Dir: r.Path}) return err } - value, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunInDir(r.Path) + value, _, err := git.NewCommand(ctx, "config", "receive.advertisePushOptions").RunStdString(&git.RunOpts{Dir: r.Path}) if err != nil { return err } diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 60598ecb495f..9af5c9e78ac1 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" ) @@ -25,6 +26,9 @@ func (m *Manager) Init() { // Run runs the manager within a provided context func (m *Manager) Run(ctx context.Context) { + ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: EventSource", process.SystemProcessType, true) + defer finished() + then := timeutil.TimeStampNow().Add(-2) timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime) loop: diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 66ca118de5a4..5a0a82b13a9c 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -34,10 +34,9 @@ func EnsureValidGitRepository(ctx context.Context, repoPath string) error { stderr := strings.Builder{} err := NewCommand(ctx, "rev-parse"). SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stderr: &stderr, + Run(&RunOpts{ + Dir: repoPath, + Stderr: &stderr, }) if err != nil { return ConcatenateError(err, (&stderr).String()) @@ -65,12 +64,11 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, stderr := strings.Builder{} err := NewCommand(ctx, "cat-file", "--batch-check"). SetDescription(fmt.Sprintf("%s cat-file --batch-check [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stdin: batchStdinReader, - Stdout: batchStdoutWriter, - Stderr: &stderr, + Run(&RunOpts{ + Dir: repoPath, + Stdin: batchStdinReader, + Stdout: batchStdoutWriter, + Stderr: &stderr, }) if err != nil { _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) @@ -110,12 +108,11 @@ func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi stderr := strings.Builder{} err := NewCommand(ctx, "cat-file", "--batch"). SetDescription(fmt.Sprintf("%s cat-file --batch [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stdin: batchStdinReader, - Stdout: batchStdoutWriter, - Stderr: &stderr, + Run(&RunOpts{ + Dir: repoPath, + Stdin: batchStdinReader, + Stdout: batchStdoutWriter, + Stderr: &stderr, }) if err != nil { _ = batchStdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) diff --git a/modules/git/command.go b/modules/git/command.go index 5d2e1dd67acd..3dd12e421e40 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -14,6 +14,7 @@ import ( "os/exec" "strings" "time" + "unsafe" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" @@ -93,34 +94,8 @@ func (c *Command) AddArguments(args ...string) *Command { return c } -// RunInDirTimeoutEnvPipeline executes the command in given directory with given timeout, -// it pipes stdout and stderr to given io.Writer. -func (c *Command) RunInDirTimeoutEnvPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer) error { - return c.RunInDirTimeoutEnvFullPipeline(env, timeout, dir, stdout, stderr, nil) -} - -// RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout, -// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. -func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error { - return c.RunInDirTimeoutEnvFullPipelineFunc(env, timeout, dir, stdout, stderr, stdin, nil) -} - -// RunInDirTimeoutEnvFullPipelineFunc executes the command in given directory with given timeout, -// it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin. Between cmd.Start and cmd.Wait the passed in function is run. -func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader, fn func(context.Context, context.CancelFunc) error) error { - return c.RunWithContext(&RunContext{ - Env: env, - Timeout: timeout, - Dir: dir, - Stdout: stdout, - Stderr: stderr, - Stdin: stdin, - PipelineFunc: fn, - }) -} - -// RunContext represents parameters to run the command -type RunContext struct { +// RunOpts represents parameters to run the command +type RunOpts struct { Env []string Timeout time.Duration Dir string @@ -129,16 +104,19 @@ type RunContext struct { PipelineFunc func(context.Context, context.CancelFunc) error } -// RunWithContext run the command with context -func (c *Command) RunWithContext(rc *RunContext) error { - if rc.Timeout == -1 { - rc.Timeout = defaultCommandExecutionTimeout +// Run runs the command with the RunOpts +func (c *Command) Run(opts *RunOpts) error { + if opts == nil { + opts = &RunOpts{} + } + if opts.Timeout <= 0 { + opts.Timeout = defaultCommandExecutionTimeout } - if len(rc.Dir) == 0 { + if len(opts.Dir) == 0 { log.Debug("%s", c) } else { - log.Debug("%s: %v", rc.Dir, c) + log.Debug("%s: %v", opts.Dir, c) } desc := c.desc @@ -157,17 +135,17 @@ func (c *Command) RunWithContext(rc *RunContext) error { args[urlArgIndex] = util.SanitizeCredentialURLs(args[urlArgIndex]) } } - desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), rc.Dir) + desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), opts.Dir) } - ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, rc.Timeout, desc) + ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc) defer finished() cmd := exec.CommandContext(ctx, c.name, c.args...) - if rc.Env == nil { + if opts.Env == nil { cmd.Env = os.Environ() } else { - cmd.Env = rc.Env + cmd.Env = opts.Env } cmd.Env = append( @@ -179,16 +157,16 @@ func (c *Command) RunWithContext(rc *RunContext) error { "GIT_NO_REPLACE_OBJECTS=1", ) - cmd.Dir = rc.Dir - cmd.Stdout = rc.Stdout - cmd.Stderr = rc.Stderr - cmd.Stdin = rc.Stdin + cmd.Dir = opts.Dir + cmd.Stdout = opts.Stdout + cmd.Stderr = opts.Stderr + cmd.Stdin = opts.Stdin if err := cmd.Start(); err != nil { return err } - if rc.PipelineFunc != nil { - err := rc.PipelineFunc(ctx, cancel) + if opts.PipelineFunc != nil { + err := opts.PipelineFunc(ctx, cancel) if err != nil { cancel() _ = cmd.Wait() @@ -203,90 +181,69 @@ func (c *Command) RunWithContext(rc *RunContext) error { return ctx.Err() } -// RunInDirTimeoutPipeline executes the command in given directory with given timeout, -// it pipes stdout and stderr to given io.Writer. -func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error { - return c.RunInDirTimeoutEnvPipeline(nil, timeout, dir, stdout, stderr) -} - -// RunInDirTimeoutFullPipeline executes the command in given directory with given timeout, -// it pipes stdout and stderr to given io.Writer, and stdin from the given io.Reader -func (c *Command) RunInDirTimeoutFullPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error { - return c.RunInDirTimeoutEnvFullPipeline(nil, timeout, dir, stdout, stderr, stdin) +type RunStdError interface { + error + Stderr() string } -// RunInDirTimeout executes the command in given directory with given timeout, -// and returns stdout in []byte and error (combined with stderr). -func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) { - return c.RunInDirTimeoutEnv(nil, timeout, dir) +type runStdError struct { + err error + stderr string + errMsg string } -// RunInDirTimeoutEnv executes the command in given directory with given timeout, -// and returns stdout in []byte and error (combined with stderr). -func (c *Command) RunInDirTimeoutEnv(env []string, timeout time.Duration, dir string) ([]byte, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil { - return nil, ConcatenateError(err, stderr.String()) +func (r *runStdError) Error() string { + // the stderr must be in the returned error text, some code only checks `strings.Contains(err.Error(), "git error")` + if r.errMsg == "" { + r.errMsg = ConcatenateError(r.err, r.stderr).Error() } - if stdout.Len() > 0 && log.IsTrace() { - tracelen := stdout.Len() - if tracelen > 1024 { - tracelen = 1024 - } - log.Trace("Stdout:\n %s", stdout.Bytes()[:tracelen]) - } - return stdout.Bytes(), nil -} - -// RunInDirPipeline executes the command in given directory, -// it pipes stdout and stderr to given io.Writer. -func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error { - return c.RunInDirFullPipeline(dir, stdout, stderr, nil) + return r.errMsg } -// RunInDirFullPipeline executes the command in given directory, -// it pipes stdout and stderr to given io.Writer. -func (c *Command) RunInDirFullPipeline(dir string, stdout, stderr io.Writer, stdin io.Reader) error { - return c.RunInDirTimeoutFullPipeline(-1, dir, stdout, stderr, stdin) +func (r *runStdError) Unwrap() error { + return r.err } -// RunInDirBytes executes the command in given directory -// and returns stdout in []byte and error (combined with stderr). -func (c *Command) RunInDirBytes(dir string) ([]byte, error) { - return c.RunInDirTimeout(-1, dir) +func (r *runStdError) Stderr() string { + return r.stderr } -// RunInDir executes the command in given directory -// and returns stdout in string and error (combined with stderr). -func (c *Command) RunInDir(dir string) (string, error) { - return c.RunInDirWithEnv(dir, nil) +func bytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go) } -// RunInDirWithEnv executes the command in given directory -// and returns stdout in string and error (combined with stderr). -func (c *Command) RunInDirWithEnv(dir string, env []string) (string, error) { - stdout, err := c.RunInDirTimeoutEnv(env, -1, dir) +// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). +func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) { + stdoutBytes, stderrBytes, err := c.RunStdBytes(opts) + stdout = bytesToString(stdoutBytes) + stderr = bytesToString(stderrBytes) if err != nil { - return "", err + return stdout, stderr, &runStdError{err: err, stderr: stderr} } - return string(stdout), nil + // even if there is no err, there could still be some stderr output, so we just return stdout/stderr as they are + return stdout, stderr, nil } -// RunTimeout executes the command in default working directory with given timeout, -// and returns stdout in string and error (combined with stderr). -func (c *Command) RunTimeout(timeout time.Duration) (string, error) { - stdout, err := c.RunInDirTimeout(timeout, "") +// RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). +func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { + if opts == nil { + opts = &RunOpts{} + } + if opts.Stdout != nil || opts.Stderr != nil { + // we must panic here, otherwise there would be bugs if developers set Stdin/Stderr by mistake, and it would be very difficult to debug + panic("stdout and stderr field must be nil when using RunStdBytes") + } + stdoutBuf := &bytes.Buffer{} + stderrBuf := &bytes.Buffer{} + opts.Stdout = stdoutBuf + opts.Stderr = stderrBuf + err := c.Run(opts) + stderr = stderrBuf.Bytes() if err != nil { - return "", err + return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)} } - return string(stdout), nil -} - -// Run executes the command in default working directory -// and returns stdout in string and error (combined with stderr). -func (c *Command) Run() (string, error) { - return c.RunTimeout(-1) + // even if there is no err, there could still be some stderr output + return stdoutBuf.Bytes(), stderr, nil } // AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests diff --git a/modules/git/command_race_test.go b/modules/git/command_race_test.go new file mode 100644 index 000000000000..9eb29fcfab5a --- /dev/null +++ b/modules/git/command_race_test.go @@ -0,0 +1,40 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build race +// +build race + +package git + +import ( + "context" + "testing" + "time" +) + +func TestRunWithContextNoTimeout(t *testing.T) { + maxLoops := 10 + + // 'git --version' does not block so it must be finished before the timeout triggered. + cmd := NewCommand(context.Background(), "--version") + for i := 0; i < maxLoops; i++ { + if err := cmd.Run(&RunOpts{}); err != nil { + t.Fatal(err) + } + } +} + +func TestRunWithContextTimeout(t *testing.T) { + maxLoops := 10 + + // 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered. + cmd := NewCommand(context.Background(), "hash-object", "--stdin") + for i := 0; i < maxLoops; i++ { + if err := cmd.Run(&RunOpts{Timeout: 1 * time.Millisecond}); err != nil { + if err != context.DeadlineExceeded { + t.Fatalf("Testing %d/%d: %v", i, maxLoops, err) + } + } + } +} diff --git a/modules/git/command_test.go b/modules/git/command_test.go index f92f526d2dc6..67d4ca388e2b 100644 --- a/modules/git/command_test.go +++ b/modules/git/command_test.go @@ -1,40 +1,29 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -//go:build race -// +build race - package git import ( "context" "testing" - "time" -) -func TestRunInDirTimeoutPipelineNoTimeout(t *testing.T) { - maxLoops := 1000 + "github.com/stretchr/testify/assert" +) - // 'git --version' does not block so it must be finished before the timeout triggered. +func TestRunWithContextStd(t *testing.T) { cmd := NewCommand(context.Background(), "--version") - for i := 0; i < maxLoops; i++ { - if err := cmd.RunInDirTimeoutPipeline(-1, "", nil, nil); err != nil { - t.Fatal(err) - } - } -} - -func TestRunInDirTimeoutPipelineAlwaysTimeout(t *testing.T) { - maxLoops := 1000 + stdout, stderr, err := cmd.RunStdString(&RunOpts{}) + assert.NoError(t, err) + assert.Empty(t, stderr) + assert.Contains(t, stdout, "git version") - // 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered. - cmd := NewCommand(context.Background(), "hash-object", "--stdin") - for i := 0; i < maxLoops; i++ { - if err := cmd.RunInDirTimeoutPipeline(1*time.Microsecond, "", nil, nil); err != nil { - if err != context.DeadlineExceeded { - t.Fatalf("Testing %d/%d: %v", i, maxLoops, err) - } - } + cmd = NewCommand(context.Background(), "--no-such-arg") + stdout, stderr, err = cmd.RunStdString(&RunOpts{}) + if assert.Error(t, err) { + assert.Equal(t, stderr, err.Stderr()) + assert.Contains(t, err.Stderr(), "unknown option:") + assert.Contains(t, err.Error(), "exit status 129 - unknown option:") + assert.Empty(t, stdout) } } diff --git a/modules/git/commit.go b/modules/git/commit.go index 340a7e21dd64..8337e54fef27 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -94,7 +94,7 @@ func AddChangesWithArgs(repoPath string, globalArgs []string, all bool, files .. cmd.AddArguments("--all") } cmd.AddArguments("--") - _, err := cmd.AddArguments(files...).RunInDir(repoPath) + _, _, err := cmd.AddArguments(files...).RunStdString(&RunOpts{Dir: repoPath}) return err } @@ -130,7 +130,7 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt } cmd.AddArguments("-m", opts.Message) - _, err := cmd.RunInDir(repoPath) + _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) // No stderr but exit status 1 means nothing to commit. if err != nil && err.Error() == "exit status 1" { return nil @@ -151,7 +151,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file cmd.AddArguments(files...) } - stdout, err := cmd.RunInDir(repoPath) + stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return 0, err } @@ -168,7 +168,7 @@ func CommitsCountFiles(ctx context.Context, repoPath string, revision, relpath [ cmd.AddArguments(relpath...) } - stdout, err := cmd.RunInDir(repoPath) + stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return 0, err } @@ -206,7 +206,7 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { } if err := CheckGitVersionAtLeast("1.8"); err == nil { - _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunInDir(c.repo.Path) + _, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path}) if err == nil { return true, nil } @@ -219,7 +219,7 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { return false, err } - result, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunInDir(c.repo.Path) + result, _, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { return false, err } @@ -381,7 +381,7 @@ func (c *Commit) GetBranchName() (string, error) { } args = append(args, "--name-only", "--no-undefined", c.ID.String()) - data, err := NewCommand(c.repo.Ctx, args...).RunInDir(c.repo.Path) + data, _, err := NewCommand(c.repo.Ctx, args...).RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where git can not describe commit if strings.Contains(err.Error(), "cannot describe") { @@ -407,7 +407,7 @@ func (c *Commit) LoadBranchName() (err error) { // GetTagName gets the current tag name for given commit func (c *Commit) GetTagName() (string, error) { - data, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always", c.ID.String()).RunInDir(c.repo.Path) + data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always", c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where there is no tag for this commit if strings.Contains(err.Error(), "no tag exactly matches") { @@ -486,11 +486,10 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi stderr := new(bytes.Buffer) args := []string{"log", "--name-status", "-c", "--pretty=format:", "--parents", "--no-renames", "-z", "-1", commitID} - err := NewCommand(ctx, args...).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stdout: w, - Stderr: stderr, + err := NewCommand(ctx, args...).Run(&RunOpts{ + Dir: repoPath, + Stdout: w, + Stderr: stderr, }) w.Close() // Close writer to exit parsing goroutine if err != nil { @@ -503,7 +502,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { - commitID, err := NewCommand(ctx, "rev-parse", shortID).RunInDir(repoPath) + commitID, _, err := NewCommand(ctx, "rev-parse", shortID).RunStdString(&RunOpts{Dir: repoPath}) if err != nil { if strings.Contains(err.Error(), "exit status 128") { return "", ErrNotExist{shortID, ""} diff --git a/modules/git/diff.go b/modules/git/diff.go index e15b2aa41058..e11f63cabd6c 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -36,11 +36,10 @@ func GetRawDiff(ctx context.Context, repoPath, commitID string, diffType RawDiff func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error { stderr := new(bytes.Buffer) cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R", commitID) - if err := cmd.RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stdout: writer, - Stderr: stderr, + if err := cmd.Run(&RunOpts{ + Dir: repoPath, + Stdout: writer, + Stderr: stderr, }); err != nil { return fmt.Errorf("Run: %v - %s", err, stderr) } @@ -97,11 +96,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff stderr := new(bytes.Buffer) cmd := NewCommand(repo.Ctx, args...) - if err = cmd.RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: writer, - Stderr: stderr, + if err = cmd.Run(&RunOpts{ + Dir: repo.Path, + Stdout: writer, + Stderr: stderr, }); err != nil { return fmt.Errorf("Run: %v - %s", err, stderr) } @@ -301,11 +299,10 @@ func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []s // Run `git diff --name-only` to get the names of the changed files err = NewCommand(repo.Ctx, "diff", "--name-only", oldCommitID, newCommitID). - RunWithContext(&RunContext{ - Env: env, - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, + Run(&RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdoutWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { // Close the writer end of the pipe to begin processing _ = stdoutWriter.Close() diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go new file mode 100644 index 000000000000..c9aa5233e1e2 --- /dev/null +++ b/modules/git/foreachref/format.go @@ -0,0 +1,84 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package foreachref + +import ( + "encoding/hex" + "fmt" + "io" + "strings" +) + +var ( + nullChar = []byte("\x00") + dualNullChar = []byte("\x00\x00") +) + +// Format supports specifying and parsing an output format for 'git +// for-each-ref'. See See git-for-each-ref(1) for available fields. +type Format struct { + // fieldNames hold %(fieldname)s to be passed to the '--format' flag of + // for-each-ref. See git-for-each-ref(1) for available fields. + fieldNames []string + + // fieldDelim is the character sequence that is used to separate fields + // for each reference. fieldDelim and refDelim should be selected to not + // interfere with each other and to not be present in field values. + fieldDelim []byte + // fieldDelimStr is a string representation of fieldDelim. Used to save + // us from repetitive reallocation whenever we need the delimiter as a + // string. + fieldDelimStr string + // refDelim is the character sequence used to separate reference from + // each other in the output. fieldDelim and refDelim should be selected + // to not interfere with each other and to not be present in field + // values. + refDelim []byte +} + +// NewFormat creates a forEachRefFormat using the specified fieldNames. See +// git-for-each-ref(1) for available fields. +func NewFormat(fieldNames ...string) Format { + return Format{ + fieldNames: fieldNames, + fieldDelim: nullChar, + fieldDelimStr: string(nullChar), + refDelim: dualNullChar, + } +} + +// Flag returns a for-each-ref --format flag value that captures the fieldNames. +func (f Format) Flag() string { + var formatFlag strings.Builder + for i, field := range f.fieldNames { + // field key and field value + formatFlag.WriteString(fmt.Sprintf("%s %%(%s)", field, field)) + + if i < len(f.fieldNames)-1 { + // note: escape delimiters to allow control characters as + // delimiters. For example, '%00' for null character or '%0a' + // for newline. + formatFlag.WriteString(f.hexEscaped(f.fieldDelim)) + } + } + formatFlag.WriteString(f.hexEscaped(f.refDelim)) + return formatFlag.String() +} + +// Parser returns a Parser capable of parsing 'git for-each-ref' output produced +// with this Format. +func (f Format) Parser(r io.Reader) *Parser { + return NewParser(r, f) +} + +// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0" +// would turn into "%0a%00". +func (f Format) hexEscaped(delim []byte) string { + escaped := "" + for i := 0; i < len(delim); i++ { + escaped += "%" + hex.EncodeToString([]byte{delim[i]}) + } + return escaped +} diff --git a/modules/git/foreachref/format_test.go b/modules/git/foreachref/format_test.go new file mode 100644 index 000000000000..5aca10f75257 --- /dev/null +++ b/modules/git/foreachref/format_test.go @@ -0,0 +1,67 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package foreachref_test + +import ( + "testing" + + "code.gitea.io/gitea/modules/git/foreachref" + + "github.com/stretchr/testify/require" +) + +func TestFormat_Flag(t *testing.T) { + tests := []struct { + name string + + givenFormat foreachref.Format + + wantFlag string + }{ + { + name: "references are delimited by dual null chars", + + // no reference fields requested + givenFormat: foreachref.NewFormat(), + + // only a reference delimiter field in --format + wantFlag: "%00%00", + }, + + { + name: "a field is a space-separated key-value pair", + + givenFormat: foreachref.NewFormat("refname:short"), + + // only a reference delimiter field + wantFlag: "refname:short %(refname:short)%00%00", + }, + + { + name: "fields are separated by a null char field-delimiter", + + givenFormat: foreachref.NewFormat("refname:short", "author"), + + wantFlag: "refname:short %(refname:short)%00author %(author)%00%00", + }, + + { + name: "multiple fields", + + givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"), + + wantFlag: "refname:short %(refname:short)%00objecttype %(objecttype)%00objectname %(objectname)%00%00", + }, + } + + for _, test := range tests { + tc := test // don't close over loop variable + t.Run(tc.name, func(t *testing.T) { + gotFlag := tc.givenFormat.Flag() + + require.Equal(t, tc.wantFlag, gotFlag, "unexpected for-each-ref --format string. wanted: '%s', got: '%s'", tc.wantFlag, gotFlag) + }) + } +} diff --git a/modules/git/foreachref/parser.go b/modules/git/foreachref/parser.go new file mode 100644 index 000000000000..eb8b77d9038b --- /dev/null +++ b/modules/git/foreachref/parser.go @@ -0,0 +1,131 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package foreachref + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strings" +) + +// Parser parses 'git for-each-ref' output according to a given output Format. +type Parser struct { + // tokenizes 'git for-each-ref' output into "reference paragraphs". + scanner *bufio.Scanner + + // format represents the '--format' string that describes the expected + // 'git for-each-ref' output structure. + format Format + + // err holds the last encountered error during parsing. + err error +} + +// NewParser creates a 'git for-each-ref' output parser that will parse all +// references in the provided Reader. The references in the output are assumed +// to follow the specified Format. +func NewParser(r io.Reader, format Format) *Parser { + scanner := bufio.NewScanner(r) + + // in addition to the reference delimiter we specified in the --format, + // `git for-each-ref` will always add a newline after every reference. + refDelim := make([]byte, 0, len(format.refDelim)+1) + refDelim = append(refDelim, format.refDelim...) + refDelim = append(refDelim, '\n') + + // Split input into delimiter-separated "reference blocks". + scanner.Split( + func(data []byte, atEOF bool) (advance int, token []byte, err error) { + // Scan until delimiter, marking end of reference. + delimIdx := bytes.Index(data, refDelim) + if delimIdx >= 0 { + token := data[:delimIdx] + advance := delimIdx + len(refDelim) + return advance, token, nil + } + // If we're at EOF, we have a final, non-terminated reference. Return it. + if atEOF { + return len(data), data, nil + } + // Not yet a full field. Request more data. + return 0, nil, nil + }) + + return &Parser{ + scanner: scanner, + format: format, + err: nil, + } +} + +// Next returns the next reference as a collection of key-value pairs. nil +// denotes EOF but is also returned on errors. The Err method should always be +// consulted after Next returning nil. +// +// It could, for example return something like: +// +// { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" } +// +func (p *Parser) Next() map[string]string { + if !p.scanner.Scan() { + return nil + } + fields, err := p.parseRef(p.scanner.Text()) + if err != nil { + p.err = err + return nil + } + return fields +} + +// Err returns the latest encountered parsing error. +func (p *Parser) Err() error { + return p.err +} + +// parseRef parses out all key-value pairs from a single reference block, such as +// +// "objecttype tag\0refname:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27" +// +func (p *Parser) parseRef(refBlock string) (map[string]string, error) { + if refBlock == "" { + // must be at EOF + return nil, nil + } + + fieldValues := make(map[string]string) + + fields := strings.Split(refBlock, p.format.fieldDelimStr) + if len(fields) != len(p.format.fieldNames) { + return nil, fmt.Errorf("unexpected number of reference fields: wanted %d, was %d", + len(fields), len(p.format.fieldNames)) + } + for i, field := range fields { + field = strings.TrimSpace(field) + + var fieldKey string + var fieldVal string + firstSpace := strings.Index(field, " ") + if firstSpace > 0 { + fieldKey = field[:firstSpace] + fieldVal = field[firstSpace+1:] + } else { + // could be the case if the requested field had no value + fieldKey = field + } + + // enforce the format order of fields + if p.format.fieldNames[i] != fieldKey { + return nil, fmt.Errorf("unexpected field name at position %d: wanted: '%s', was: '%s'", + i, p.format.fieldNames[i], fieldKey) + } + + fieldValues[fieldKey] = fieldVal + } + + return fieldValues, nil +} diff --git a/modules/git/foreachref/parser_test.go b/modules/git/foreachref/parser_test.go new file mode 100644 index 000000000000..cb36428604a6 --- /dev/null +++ b/modules/git/foreachref/parser_test.go @@ -0,0 +1,228 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package foreachref_test + +import ( + "errors" + "fmt" + "io" + "strings" + "testing" + + "code.gitea.io/gitea/modules/git/foreachref" + "code.gitea.io/gitea/modules/json" + + "github.com/stretchr/testify/require" +) + +type refSlice = []map[string]string + +func TestParser(t *testing.T) { + tests := []struct { + name string + + givenFormat foreachref.Format + givenInput io.Reader + + wantRefs refSlice + wantErr bool + expectedErr error + }{ + // this would, for example, be the result when running `git + // for-each-ref refs/tags` on a repo without tags. + { + name: "no references on empty input", + + givenFormat: foreachref.NewFormat("refname:short"), + givenInput: strings.NewReader(``), + + wantRefs: []map[string]string{}, + }, + + // note: `git for-each-ref` will add a newline between every + // reference (in addition to the ref-delimiter we've chosen) + { + name: "single field requested, single reference in output", + + givenFormat: foreachref.NewFormat("refname:short"), + givenInput: strings.NewReader("refname:short v0.0.1\x00\x00" + "\n"), + + wantRefs: []map[string]string{ + {"refname:short": "v0.0.1"}, + }, + }, + { + name: "single field requested, multiple references in output", + + givenFormat: foreachref.NewFormat("refname:short"), + givenInput: strings.NewReader( + "refname:short v0.0.1\x00\x00" + "\n" + + "refname:short v0.0.2\x00\x00" + "\n" + + "refname:short v0.0.3\x00\x00" + "\n"), + + wantRefs: []map[string]string{ + {"refname:short": "v0.0.1"}, + {"refname:short": "v0.0.2"}, + {"refname:short": "v0.0.3"}, + }, + }, + + { + name: "multiple fields requested for each reference", + + givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"), + givenInput: strings.NewReader( + + "refname:short v0.0.1\x00objecttype commit\x00objectname 7b2c5ac9fc04fc5efafb60700713d4fa609b777b\x00\x00" + "\n" + + "refname:short v0.0.2\x00objecttype commit\x00objectname a1f051bc3eba734da4772d60e2d677f47cf93ef4\x00\x00" + "\n" + + "refname:short v0.0.3\x00objecttype commit\x00objectname ef82de70bb3f60c65fb8eebacbb2d122ef517385\x00\x00" + "\n", + ), + + wantRefs: []map[string]string{ + { + "refname:short": "v0.0.1", + "objecttype": "commit", + "objectname": "7b2c5ac9fc04fc5efafb60700713d4fa609b777b", + }, + { + "refname:short": "v0.0.2", + "objecttype": "commit", + "objectname": "a1f051bc3eba734da4772d60e2d677f47cf93ef4", + }, + { + "refname:short": "v0.0.3", + "objecttype": "commit", + "objectname": "ef82de70bb3f60c65fb8eebacbb2d122ef517385", + }, + }, + }, + + { + name: "must handle multi-line fields such as 'content'", + + givenFormat: foreachref.NewFormat("refname:short", "contents", "author"), + givenInput: strings.NewReader( + "refname:short v0.0.1\x00contents Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.\x00author Foo Bar 1507832733 +0200\x00\x00" + "\n" + + "refname:short v0.0.2\x00contents Update CI config (#651)\n\n\x00author John Doe 1521643174 +0000\x00\x00" + "\n" + + "refname:short v0.0.3\x00contents Fixed code sample for bash completion (#687)\n\n\x00author Foo Baz 1524836750 +0200\x00\x00" + "\n", + ), + + wantRefs: []map[string]string{ + { + "refname:short": "v0.0.1", + "contents": "Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.", + "author": "Foo Bar 1507832733 +0200", + }, + { + "refname:short": "v0.0.2", + "contents": "Update CI config (#651)", + "author": "John Doe 1521643174 +0000", + }, + { + "refname:short": "v0.0.3", + "contents": "Fixed code sample for bash completion (#687)", + "author": "Foo Baz 1524836750 +0200", + }, + }, + }, + + { + name: "must handle fields without values", + + givenFormat: foreachref.NewFormat("refname:short", "object", "objecttype"), + givenInput: strings.NewReader( + "refname:short v0.0.1\x00object \x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.2\x00object \x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.3\x00object \x00objecttype commit\x00\x00" + "\n", + ), + + wantRefs: []map[string]string{ + { + "refname:short": "v0.0.1", + "object": "", + "objecttype": "commit", + }, + { + "refname:short": "v0.0.2", + "object": "", + "objecttype": "commit", + }, + { + "refname:short": "v0.0.3", + "object": "", + "objecttype": "commit", + }, + }, + }, + + { + name: "must fail when the number of fields in the input doesn't match expected format", + + givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"), + givenInput: strings.NewReader( + "refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n", + ), + + wantErr: true, + expectedErr: errors.New("unexpected number of reference fields: wanted 2, was 3"), + }, + + { + name: "must fail input fields don't match expected format", + + givenFormat: foreachref.NewFormat("refname:short", "objectname"), + givenInput: strings.NewReader( + "refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" + + "refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n", + ), + + wantErr: true, + expectedErr: errors.New("unexpected field name at position 1: wanted: 'objectname', was: 'objecttype'"), + }, + } + + for _, test := range tests { + tc := test // don't close over loop variable + t.Run(tc.name, func(t *testing.T) { + parser := tc.givenFormat.Parser(tc.givenInput) + + // + // parse references from input + // + gotRefs := make([]map[string]string, 0) + for { + ref := parser.Next() + if ref == nil { + break + } + gotRefs = append(gotRefs, ref) + } + err := parser.Err() + + // + // verify expectations + // + if tc.wantErr { + require.Error(t, err) + require.EqualError(t, err, tc.expectedErr.Error()) + } else { + require.NoError(t, err, "for-each-ref parser unexpectedly failed with: %v", err) + require.Equal(t, tc.wantRefs, gotRefs, "for-each-ref parser produced unexpected reference set. wanted: %v, got: %v", pretty(tc.wantRefs), pretty(gotRefs)) + } + }) + } +} + +func pretty(v interface{}) string { + data, err := json.MarshalIndent(v, "", " ") + if err != nil { + // shouldn't happen + panic(fmt.Sprintf("json-marshalling failed: %v", err)) + } + return string(data) +} diff --git a/modules/git/git.go b/modules/git/git.go index 14940d1f162a..b97bb149001b 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -54,9 +54,9 @@ func LoadGitVersion() error { return nil } - stdout, err := NewCommand(context.Background(), "version").Run() - if err != nil { - return err + stdout, _, runErr := NewCommand(context.Background(), "version").RunStdString(nil) + if runErr != nil { + return runErr } fields := strings.Fields(stdout) @@ -74,6 +74,7 @@ func LoadGitVersion() error { versionString = fields[2] } + var err error gitVersion, err = version.NewVersion(versionString) return err } @@ -124,7 +125,9 @@ func VersionInfo() string { func Init(ctx context.Context) error { DefaultContext = ctx - defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second + if setting.Git.Timeout.Default > 0 { + defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second + } if err := SetExecutablePath(setting.Git.Path); err != nil { return err @@ -295,10 +298,5 @@ func checkAndRemoveConfig(key, value string) error { // Fsck verifies the connectivity and validity of the objects in the database func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error { - // Make sure timeout makes sense. - if timeout <= 0 { - timeout = -1 - } - _, err := NewCommand(ctx, "fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath) - return err + return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath}) } diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 0571a4dd20c6..ffd0a0991bf4 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -55,11 +55,10 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p go func() { stderr := strings.Builder{} - err := NewCommand(ctx, args...).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repository, - Stdout: stdoutWriter, - Stderr: &stderr, + err := NewCommand(ctx, args...).Run(&RunOpts{ + Dir: repository, + Stdout: stdoutWriter, + Stderr: &stderr, }) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go index 6948131e460d..40dd2bca2936 100644 --- a/modules/git/pipeline/catfile.go +++ b/modules/git/pipeline/catfile.go @@ -27,12 +27,11 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := git.NewCommand(ctx, "cat-file", "--batch-check") - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdin: shasToCheckReader, - Stdout: catFileCheckWriter, - Stderr: stderr, + if err := cmd.Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdin: shasToCheckReader, + Stdout: catFileCheckWriter, + Stderr: stderr, }); err != nil { _ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %v - %s", tmpBasePath, err, errbuf.String())) } @@ -46,11 +45,10 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := git.NewCommand(ctx, "cat-file", "--batch-check", "--batch-all-objects") - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: catFileCheckWriter, - Stderr: stderr, + if err := cmd.Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: catFileCheckWriter, + Stderr: stderr, }); err != nil { log.Error("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String()) err = fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String()) @@ -67,12 +65,11 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile stderr := new(bytes.Buffer) var errbuf strings.Builder - if err := git.NewCommand(ctx, "cat-file", "--batch").RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: catFileBatchWriter, - Stdin: shasToBatchReader, - Stderr: stderr, + if err := git.NewCommand(ctx, "cat-file", "--batch").Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: catFileBatchWriter, + Stdin: shasToBatchReader, + Stderr: stderr, }); err != nil { _ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String())) } diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 1d43080a5a01..31c10c6002f6 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -53,11 +53,10 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { go func() { stderr := strings.Builder{} - err := git.NewCommand(repo.Ctx, "rev-list", "--all").RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: revListWriter, - Stderr: &stderr, + err := git.NewCommand(repo.Ctx, "rev-list", "--all").Run(&git.RunOpts{ + Dir: repo.Path, + Stdout: revListWriter, + Stderr: &stderr, }) if err != nil { _ = revListWriter.CloseWithError(git.ConcatenateError(err, (&stderr).String())) diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go index 357322070ece..8356e7023445 100644 --- a/modules/git/pipeline/namerev.go +++ b/modules/git/pipeline/namerev.go @@ -23,12 +23,11 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS stderr := new(bytes.Buffer) var errbuf strings.Builder - if err := git.NewCommand(ctx, "name-rev", "--stdin", "--name-only", "--always").RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: nameRevStdinWriter, - Stdin: shasToNameReader, - Stderr: stderr, + if err := git.NewCommand(ctx, "name-rev", "--stdin", "--name-only", "--always").Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: nameRevStdinWriter, + Stdin: shasToNameReader, + Stderr: stderr, }); err != nil { _ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %v - %s", tmpBasePath, err, errbuf.String())) } diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index a1f8f079f959..02619cb58304 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -25,11 +25,10 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all") - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: basePath, - Stdout: revListWriter, - Stderr: stderr, + if err := cmd.Run(&git.RunOpts{ + Dir: basePath, + Stdout: revListWriter, + Stderr: stderr, }); err != nil { log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) err = fmt.Errorf("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) @@ -45,11 +44,10 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync. stderr := new(bytes.Buffer) var errbuf strings.Builder cmd := git.NewCommand(ctx, "rev-list", "--objects", headSHA, "--not", baseSHA) - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: revListWriter, - Stderr: stderr, + if err := cmd.Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: revListWriter, + Stderr: stderr, }); err != nil { log.Error("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String()) errChan <- fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String()) diff --git a/modules/git/remote.go b/modules/git/remote.go index dfd0686d8bef..536b1681cecf 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -22,7 +22,7 @@ func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.UR cmd = NewCommand(ctx, "config", "--get", "remote."+remoteName+".url") } - result, err := cmd.RunInDir(repoPath) + result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return nil, err } diff --git a/modules/git/repo.go b/modules/git/repo.go index b06b7feea264..3176e276959a 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -59,7 +59,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro // IsRepoURLAccessible checks if given repository URL is accessible. func IsRepoURLAccessible(ctx context.Context, url string) bool { - _, err := NewCommand(ctx, "ls-remote", "-q", "-h", url, "HEAD").Run() + _, _, err := NewCommand(ctx, "ls-remote", "-q", "-h", url, "HEAD").RunStdString(nil) return err == nil } @@ -74,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error { if bare { cmd.AddArguments("--bare") } - _, err = cmd.RunInDir(repoPath) + _, _, err = cmd.RunStdString(&RunOpts{Dir: repoPath}) return err } @@ -82,11 +82,10 @@ func InitRepository(ctx context.Context, repoPath string, bare bool) error { func (repo *Repository) IsEmpty() (bool, error) { var errbuf, output strings.Builder if err := NewCommand(repo.Ctx, "show-ref", "--head", "^HEAD$"). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: &output, - Stderr: &errbuf, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: &output, + Stderr: &errbuf, }); err != nil { if err.Error() == "exit status 1" && errbuf.String() == "" { return true, nil @@ -174,7 +173,7 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo } stderr := new(bytes.Buffer) - if err = cmd.RunWithContext(&RunContext{ + if err = cmd.Run(&RunOpts{ Timeout: opts.Timeout, Env: envs, Stdout: io.Discard, @@ -219,7 +218,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { opts.Timeout = -1 } - err := cmd.RunWithContext(&RunContext{ + err := cmd.Run(&RunOpts{ Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath, @@ -261,7 +260,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { // GetLatestCommitTime returns time for latest commit in repository (across all branches) func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) { cmd := NewCommand(ctx, "for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") - stdout, err := cmd.RunInDir(repoPath) + stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return time.Time{}, err } @@ -278,7 +277,7 @@ type DivergeObject struct { func checkDivergence(ctx context.Context, repoPath, baseBranch, targetBranch string) (int, error) { branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch) cmd := NewCommand(ctx, "rev-list", "--count", branches) - stdout, err := cmd.RunInDir(repoPath) + stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return -1, err } @@ -315,23 +314,23 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io. defer os.RemoveAll(tmp) env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) - _, err = NewCommand(ctx, "init", "--bare").RunInDirWithEnv(tmp, env) + _, _, err = NewCommand(ctx, "init", "--bare").RunStdString(&RunOpts{Dir: tmp, Env: env}) if err != nil { return err } - _, err = NewCommand(ctx, "reset", "--soft", commit).RunInDirWithEnv(tmp, env) + _, _, err = NewCommand(ctx, "reset", "--soft", commit).RunStdString(&RunOpts{Dir: tmp, Env: env}) if err != nil { return err } - _, err = NewCommand(ctx, "branch", "-m", "bundle").RunInDirWithEnv(tmp, env) + _, _, err = NewCommand(ctx, "branch", "-m", "bundle").RunStdString(&RunOpts{Dir: tmp, Env: env}) if err != nil { return err } tmpFile := filepath.Join(tmp, "bundle") - _, err = NewCommand(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunInDirWithEnv(tmp, env) + _, _, err = NewCommand(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env}) if err != nil { return err } diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go index b7c339c271fd..4a97989949f3 100644 --- a/modules/git/repo_archive.go +++ b/modules/git/repo_archive.go @@ -57,11 +57,10 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t ) var stderr strings.Builder - err := NewCommand(ctx, args...).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: target, - Stderr: &stderr, + err := NewCommand(ctx, args...).Run(&RunOpts{ + Dir: repo.Path, + Stdout: target, + Stderr: &stderr, }) if err != nil { return ConcatenateError(err, stderr.String()) diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index ce24b0a7a341..6481474a9640 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -76,12 +76,11 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[ cmd := NewCommand(repo.Ctx, cmdArgs...) - if err := cmd.RunWithContext(&RunContext{ - Env: env, - Timeout: -1, - Dir: repo.Path, - Stdout: stdOut, - Stderr: stdErr, + if err := cmd.Run(&RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdOut, + Stderr: stdErr, }); err != nil { return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String()) } @@ -189,13 +188,12 @@ func (c *CheckAttributeReader) Run() error { _ = c.stdOut.Close() }() stdErr := new(bytes.Buffer) - err := c.cmd.RunWithContext(&RunContext{ - Env: c.env, - Timeout: -1, - Dir: c.Repo.Path, - Stdin: c.stdinReader, - Stdout: c.stdOut, - Stderr: stdErr, + err := c.cmd.Run(&RunOpts{ + Env: c.env, + Dir: c.Repo.Path, + Stdin: c.stdinReader, + Stdout: c.stdOut, + Stderr: stdErr, PipelineFunc: func(_ context.Context, _ context.CancelFunc) error { select { case <-c.running: diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go index a71122527f29..6fe6d235ba92 100644 --- a/modules/git/repo_blame.go +++ b/modules/git/repo_blame.go @@ -8,12 +8,13 @@ import "fmt" // FileBlame return the Blame object of file func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { - return NewCommand(repo.Ctx, "blame", "--root", "--", file).RunInDirBytes(path) + stdout, _, err := NewCommand(repo.Ctx, "blame", "--root", "--", file).RunStdBytes(&RunOpts{Dir: path}) + return stdout, err } // LineBlame returns the latest commit at the given line func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { - res, err := NewCommand(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunInDir(path) + res, _, err := NewCommand(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunStdString(&RunOpts{Dir: path}) if err != nil { return nil, err } diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index e0d47ad7acbe..8e455480e727 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -24,7 +24,7 @@ const PullRequestPrefix = "refs/for/" // IsReferenceExist returns true if given reference exists in the repository. func IsReferenceExist(ctx context.Context, repoPath, name string) bool { - _, err := NewCommand(ctx, "show-ref", "--verify", "--", name).RunInDir(repoPath) + _, _, err := NewCommand(ctx, "show-ref", "--verify", "--", name).RunStdString(&RunOpts{Dir: repoPath}) return err == nil } @@ -46,7 +46,7 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { if repo == nil { return nil, fmt.Errorf("nil repo") } - stdout, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -65,13 +65,14 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) { // SetDefaultBranch sets default branch of repository. func (repo *Repository) SetDefaultBranch(name string) error { - _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunStdString(&RunOpts{Dir: repo.Path}) return err } // GetDefaultBranch gets default branch of repository. func (repo *Repository) GetDefaultBranch() (string, error) { - return NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path}) + return stdout, err } // GetBranch returns a branch by it's name @@ -133,7 +134,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro } cmd.AddArguments("--", name) - _, err := cmd.RunInDir(repo.Path) + _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) return err } @@ -143,7 +144,7 @@ func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error { cmd := NewCommand(repo.Ctx, "branch") cmd.AddArguments("--", branch, oldbranchOrCommit) - _, err := cmd.RunInDir(repo.Path) + _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) return err } @@ -156,13 +157,13 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error { } cmd.AddArguments(name, url) - _, err := cmd.RunInDir(repo.Path) + _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) return err } // RemoveRemote removes a remote from repository. func (repo *Repository) RemoveRemote(name string) error { - _, err := NewCommand(repo.Ctx, "remote", "rm", name).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "remote", "rm", name).RunStdString(&RunOpts{Dir: repo.Path}) return err } @@ -173,6 +174,6 @@ func (branch *Branch) GetCommit() (*Commit, error) { // RenameBranch rename a branch func (repo *Repository) RenameBranch(from, to string) error { - _, err := NewCommand(repo.Ctx, "branch", "-m", from, to).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "branch", "-m", from, to).RunStdString(&RunOpts{Dir: repo.Path}) return err } diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index f595b6d9a86a..4393db10f950 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -112,11 +112,10 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal if arg != "" { args = append(args, arg) } - err := NewCommand(ctx, args...).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repoPath, - Stdout: stdoutWriter, - Stderr: stderrBuilder, + err := NewCommand(ctx, args...).Run(&RunOpts{ + Dir: repoPath, + Stdout: stdoutWriter, + Stderr: stderrBuilder, }) if err != nil { if stderrBuilder.Len() == 0 { diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 8e059ce0ea25..e6fec4d1a32e 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -58,12 +58,12 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, relpath = `\` + relpath } - stdout, err := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunInDir(repo.Path) - if err != nil { - return nil, err + stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunStdString(&RunOpts{Dir: repo.Path}) + if runErr != nil { + return nil, runErr } - id, err = NewIDFromString(stdout) + id, err := NewIDFromString(stdout) if err != nil { return nil, err } @@ -73,9 +73,9 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit, // GetCommitByPath returns the last commit of relative path. func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { - stdout, err := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunInDirBytes(repo.Path) - if err != nil { - return nil, err + stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunStdBytes(&RunOpts{Dir: repo.Path}) + if runErr != nil { + return nil, runErr } commits, err := repo.parsePrettyFormatLogToList(stdout) @@ -86,8 +86,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { } func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) { - stdout, err := NewCommand(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize), - "--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunInDirBytes(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize), + "--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co // search for commits matching given constraints and keywords in commit msg cmd.AddArguments(args...) - stdout, err := cmd.RunInDirBytes(repo.Path) + stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -161,7 +161,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co hashCmd.AddArguments(v) // search with given constraints for commit matching sha hash of v - hashMatching, err := hashCmd.RunInDirBytes(repo.Path) + hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil || bytes.Contains(stdout, hashMatching) { continue } @@ -175,7 +175,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co } func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { - stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", id1, id2).RunInDirBytes(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", id1, id2).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -185,7 +185,7 @@ func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) { // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2 // You must ensure that id1 and id2 are valid commit ids. func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) { - stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunInDirBytes(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return false, err } @@ -211,11 +211,10 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( err := NewCommand(repo.Ctx, "log", revision, "--follow", "--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page), prettyLogFormat, "--", file). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: &stderr, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: stdoutWriter, + Stderr: &stderr, }) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String())) @@ -244,8 +243,8 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) ( // CommitsByFileAndRangeNoFollow return the commits according revision file and the page func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) { - stdout, err := NewCommand(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50), - "--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunInDirBytes(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50), + "--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -254,11 +253,11 @@ func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, pag // FilesCountBetween return the number of files changed between two commits func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) { - stdout, err := NewCommand(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil && strings.Contains(err.Error(), "no merge base") { // git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated. // previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that... - stdout, err = NewCommand(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunInDir(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path}) } if err != nil { return 0, err @@ -272,13 +271,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error) var stdout []byte var err error if before == nil { - stdout, err = NewCommand(repo.Ctx, "rev-list", last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } else { - stdout, err = NewCommand(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list before last so let's try that... - stdout, err = NewCommand(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } } if err != nil { @@ -292,13 +291,13 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in var stdout []byte var err error if before == nil { - stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } else { - stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list --max-count n before last so let's try that... - stdout, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunInDirBytes(repo.Path) + stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path}) } } if err != nil { @@ -344,9 +343,9 @@ func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) { cmd.AddArguments(prettyLogFormat, id.String()) } - stdout, err := cmd.RunInDirBytes(repo.Path) - if err != nil { - return nil, err + stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) + if runErr != nil { + return nil, runErr } formattedLog, err := repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) @@ -381,7 +380,7 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, erro func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { if CheckGitVersionAtLeast("2.7.0") == nil { - stdout, err := NewCommand(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -390,7 +389,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) return branches, nil } - stdout, err := NewCommand(repo.Ctx, "branch", "--contains", commit.ID.String()).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -429,7 +428,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit { // IsCommitInBranch check if the commit is on the branch func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) { - stdout, err := NewCommand(repo.Ctx, "branch", "--contains", commitID, branch).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commitID, branch).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return false, err } diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index 3693f7883fbb..f3504f25d870 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -50,7 +50,7 @@ func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) { } } - actualCommitID, err := NewCommand(repo.Ctx, "rev-parse", "--verify", commitID).RunInDir(repo.Path) + actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", commitID).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { if strings.Contains(err.Error(), "unknown revision or path") || strings.Contains(err.Error(), "fatal: Needed a single revision") { diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index b65565c98c97..c9afe35b1a2e 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -18,7 +18,7 @@ import ( // ResolveReference resolves a name to a reference func (repo *Repository) ResolveReference(name string) (string, error) { - stdout, err := NewCommand(repo.Ctx, "show-ref", "--hash", name).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash", name).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { return "", ErrNotExist{name, ""} @@ -51,19 +51,19 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) { // SetReference sets the commit ID string of given reference (e.g. branch or tag). func (repo *Repository) SetReference(name, commitID string) error { - _, err := NewCommand(repo.Ctx, "update-ref", name, commitID).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "update-ref", name, commitID).RunStdString(&RunOpts{Dir: repo.Path}) return err } // RemoveReference removes the given reference (e.g. branch or tag). func (repo *Repository) RemoveReference(name string) error { - _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunStdString(&RunOpts{Dir: repo.Path}) return err } // IsCommitExist returns true if given commit exists in current repository. func (repo *Repository) IsCommitExist(name string) bool { - _, err := NewCommand(repo.Ctx, "cat-file", "-e", name).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "cat-file", "-e", name).RunStdString(&RunOpts{Dir: repo.Path}) return err == nil } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 5549c9259125..075b59ad06c6 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -13,7 +13,7 @@ import ( // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { if CheckGitVersionAtLeast("2.18") == nil { - if _, err := NewCommand(ctx, "commit-graph", "write").RunInDir(repoPath); err != nil { + if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index aa8015af14e5..f6b4f7764538 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -40,13 +40,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri if tmpRemote != "origin" { tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base // Fetch commit into a temporary branch in order to be able to handle commits and tags - _, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path}) if err == nil { base = tmpBaseName } } - stdout, err := NewCommand(repo.Ctx, "merge-base", "--", base, head).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "merge-base", "--", base, head).RunStdString(&RunOpts{Dir: repo.Path}) return strings.TrimSpace(stdout), base, err } @@ -93,7 +93,8 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string, // We have a common base - therefore we know that ... should work if !fileOnly { - logs, err := NewCommand(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunInDirBytes(repo.Path) + var logs []byte + logs, _, err = NewCommand(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -147,22 +148,20 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis } if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only", base+separator+head). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, - Stderr: stderr, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, + Stderr: stderr, }); err != nil { if strings.Contains(stderr.String(), "no merge base") { // git >= 2.28 now returns an error if base and head have become unrelated. // previously it would return the results of git diff -z --name-only base head so let's try that... w = &lineCountWriter{} stderr.Reset() - if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only", base, head).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, - Stderr: stderr, + if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only", base, head).Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, + Stderr: stderr, }); err == nil { return w.numLines, nil } @@ -192,7 +191,7 @@ func GetDiffShortStat(ctx context.Context, repoPath string, args ...string) (num "--shortstat", }, args...) - stdout, err := NewCommand(ctx, args...).RunInDir(repoPath) + stdout, _, err := NewCommand(ctx, args...).RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return 0, 0, 0, err } @@ -248,26 +247,23 @@ func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, bi // GetDiff generates and returns patch data between given revisions, optimized for human readability func (repo *Repository) GetDiff(base, head string, w io.Writer) error { - return NewCommand(repo.Ctx, "diff", "-p", base, head).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, + return NewCommand(repo.Ctx, "diff", "-p", base, head).Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, }) } // GetDiffBinary generates and returns patch data between given revisions, including binary diffs. func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { if CheckGitVersionAtLeast("1.7.7") == nil { - return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, + return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, }) } - return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, + return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, }) } @@ -275,18 +271,16 @@ func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { func (repo *Repository) GetPatch(base, head string, w io.Writer) error { stderr := new(bytes.Buffer) err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base+"..."+head). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, - Stderr: stderr, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, + Stderr: stderr, }) if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) { return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base, head). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, }) } return err @@ -296,11 +290,10 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error { func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error { stderr := new(bytes.Buffer) err := NewCommand(repo.Ctx, "diff", "-p", "--binary", base+"..."+head). - RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: w, - Stderr: stderr, + Run(&RunOpts{ + Dir: repo.Path, + Stdout: w, + Stderr: stderr, }) if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) { return repo.GetDiffBinary(base, head, w) diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go index 14eb894be626..abbb349159d1 100644 --- a/modules/git/repo_gpg.go +++ b/modules/git/repo_gpg.go @@ -34,7 +34,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, Sign: true, } - value, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunInDir(repo.Path) + value, _, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunStdString(&RunOpts{Dir: repo.Path}) sign, valid := ParseBool(strings.TrimSpace(value)) if !sign || !valid { gpgSettings.Sign = false @@ -42,13 +42,13 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings, return gpgSettings, nil } - signingKey, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunInDir(repo.Path) + signingKey, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunStdString(&RunOpts{Dir: repo.Path}) gpgSettings.KeyID = strings.TrimSpace(signingKey) - defaultEmail, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunInDir(repo.Path) + defaultEmail, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunStdString(&RunOpts{Dir: repo.Path}) gpgSettings.Email = strings.TrimSpace(defaultEmail) - defaultName, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunInDir(repo.Path) + defaultName, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunStdString(&RunOpts{Dir: repo.Path}) gpgSettings.Name = strings.TrimSpace(defaultName) if err := gpgSettings.LoadPublicKeyContent(); err != nil { diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 53de0f1cb8ec..ae68dcaa87c1 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -18,7 +18,7 @@ import ( // ReadTreeToIndex reads a treeish to the index func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error { if len(treeish) != 40 { - res, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunInDir(repo.Path) + res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return err } @@ -38,7 +38,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error if len(indexFilename) > 0 { env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0]) } - _, err := NewCommand(repo.Ctx, "read-tree", id.String()).RunInDirWithEnv(repo.Path, env) + _, _, err := NewCommand(repo.Ctx, "read-tree", id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env}) if err != nil { return err } @@ -69,7 +69,7 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpD // EmptyIndex empties the index func (repo *Repository) EmptyIndex() error { - _, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunStdString(&RunOpts{Dir: repo.Path}) return err } @@ -81,7 +81,7 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { cmd.AddArguments(arg) } } - res, err := cmd.RunInDirBytes(repo.Path) + res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } @@ -106,29 +106,28 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error { buffer.WriteByte('\000') } } - return cmd.RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdin: bytes.NewReader(buffer.Bytes()), - Stdout: stdout, - Stderr: stderr, + return cmd.Run(&RunOpts{ + Dir: repo.Path, + Stdin: bytes.NewReader(buffer.Bytes()), + Stdout: stdout, + Stderr: stderr, }) } // AddObjectToIndex adds the provided object hash to the index at the provided filename func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error { cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename) - _, err := cmd.RunInDir(repo.Path) + _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) return err } // WriteTree writes the current index as a tree to the object db and returns its hash func (repo *Repository) WriteTree() (*Tree, error) { - res, err := NewCommand(repo.Ctx, "write-tree").RunInDir(repo.Path) - if err != nil { - return nil, err + stdout, _, runErr := NewCommand(repo.Ctx, "write-tree").RunStdString(&RunOpts{Dir: repo.Path}) + if runErr != nil { + return nil, runErr } - id, err := NewIDFromString(strings.TrimSpace(res)) + id, err := NewIDFromString(strings.TrimSpace(stdout)) if err != nil { return nil, err } diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go index 378e657ce4f4..af448b01104a 100644 --- a/modules/git/repo_object.go +++ b/modules/git/repo_object.go @@ -45,12 +45,11 @@ func (repo *Repository) hashObject(reader io.Reader) (string, error) { cmd := NewCommand(repo.Ctx, "hash-object", "-w", "--stdin") stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err := cmd.RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdin: reader, - Stdout: stdout, - Stderr: stderr, + err := cmd.Run(&RunOpts{ + Dir: repo.Path, + Stdin: reader, + Stdout: stdout, + Stderr: stderr, }) if err != nil { return "", err diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go index 42295e43ac58..40e8a247c748 100644 --- a/modules/git/repo_ref_nogogit.go +++ b/modules/git/repo_ref_nogogit.go @@ -23,11 +23,10 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) { go func() { stderrBuilder := &strings.Builder{} - err := NewCommand(repo.Ctx, "for-each-ref").RunWithContext(&RunContext{ - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: stderrBuilder, + err := NewCommand(repo.Ctx, "for-each-ref").Run(&RunOpts{ + Dir: repo.Path, + Stdout: stdoutWriter, + Stderr: stderrBuilder, }) if err != nil { _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderrBuilder.String())) diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go index 598ec37a2c61..c0c91c6fc618 100644 --- a/modules/git/repo_stats.go +++ b/modules/git/repo_stats.go @@ -39,12 +39,12 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) since := fromTime.Format(time.RFC3339) - stdout, err := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunInDirBytes(repo.Path) - if err != nil { - return nil, err + stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunStdString(&RunOpts{Dir: repo.Path}) + if runErr != nil { + return nil, runErr } - c, err := strconv.ParseInt(strings.TrimSpace(string(stdout)), 10, 64) + c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) if err != nil { return nil, err } @@ -67,12 +67,11 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) } stderr := new(strings.Builder) - err = NewCommand(repo.Ctx, args...).RunWithContext(&RunContext{ - Env: []string{}, - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, - Stderr: stderr, + err = NewCommand(repo.Ctx, args...).Run(&RunOpts{ + Env: []string{}, + Dir: repo.Path, + Stdout: stdoutWriter, + Stderr: stderr, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() scanner := bufio.NewScanner(stdoutReader) diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index d1b076ffc3f8..8444e8d035a0 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -8,8 +8,10 @@ package git import ( "context" "fmt" + "io" "strings" + "code.gitea.io/gitea/modules/git/foreachref" "code.gitea.io/gitea/modules/util" ) @@ -23,13 +25,13 @@ func IsTagExist(ctx context.Context, repoPath, name string) bool { // CreateTag create one tag in the repository func (repo *Repository) CreateTag(name, revision string) error { - _, err := NewCommand(repo.Ctx, "tag", "--", name, revision).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "tag", "--", name, revision).RunStdString(&RunOpts{Dir: repo.Path}) return err } // CreateAnnotatedTag create one annotated tag in the repository func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error { - _, err := NewCommand(repo.Ctx, "tag", "-a", "-m", message, "--", name, revision).RunInDir(repo.Path) + _, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m", message, "--", name, revision).RunStdString(&RunOpts{Dir: repo.Path}) return err } @@ -39,7 +41,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { return "", fmt.Errorf("SHA is too short: %s", sha) } - stdout, err := NewCommand(repo.Ctx, "show-ref", "--tags", "-d").RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "-d").RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return "", err } @@ -62,7 +64,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA) func (repo *Repository) GetTagID(name string) (string, error) { - stdout, err := NewCommand(repo.Ctx, "show-ref", "--tags", "--", name).RunInDir(repo.Path) + stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "--", name).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return "", err } @@ -111,37 +113,98 @@ func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) { // GetTagInfos returns all tag infos of the repository. func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { - // TODO this a slow implementation, makes one git command per tag - stdout, err := NewCommand(repo.Ctx, "tag").RunInDir(repo.Path) - if err != nil { - return nil, 0, err - } + forEachRefFmt := foreachref.NewFormat("objecttype", "refname:short", "object", "objectname", "creator", "contents", "contents:signature") - tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n") - tagsTotal := len(tagNames) - - if page != 0 { - tagNames = util.PaginateSlice(tagNames, page, pageSize).([]string) - } + stdoutReader, stdoutWriter := io.Pipe() + defer stdoutReader.Close() + defer stdoutWriter.Close() + stderr := strings.Builder{} + rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr} - tags := make([]*Tag, 0, len(tagNames)) - for _, tagName := range tagNames { - tagName = strings.TrimSpace(tagName) - if len(tagName) == 0 { - continue + go func() { + err := NewCommand(repo.Ctx, "for-each-ref", "--format", forEachRefFmt.Flag(), "--sort", "-*creatordate", "refs/tags").Run(rc) + if err != nil { + _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String())) + } else { + _ = stdoutWriter.Close() + } + }() + + var tags []*Tag + parser := forEachRefFmt.Parser(stdoutReader) + for { + ref := parser.Next() + if ref == nil { + break } - tag, err := repo.GetTag(tagName) + tag, err := parseTagRef(ref) if err != nil { - return nil, tagsTotal, err + return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err) } - tag.Name = tagName tags = append(tags, tag) } + if err := parser.Err(); err != nil { + return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err) + } + sortTagsByTime(tags) + tagsTotal := len(tags) + if page != 0 { + tags = util.PaginateSlice(tags, page, pageSize).([]*Tag) + } + return tags, tagsTotal, nil } +// parseTagRef parses a tag from a 'git for-each-ref'-produced reference. +func parseTagRef(ref map[string]string) (tag *Tag, err error) { + tag = &Tag{ + Type: ref["objecttype"], + Name: ref["refname:short"], + } + + tag.ID, err = NewIDFromString(ref["objectname"]) + if err != nil { + return nil, fmt.Errorf("parse objectname '%s': %v", ref["objectname"], err) + } + + if tag.Type == "commit" { + // lightweight tag + tag.Object = tag.ID + } else { + // annotated tag + tag.Object, err = NewIDFromString(ref["object"]) + if err != nil { + return nil, fmt.Errorf("parse object '%s': %v", ref["object"], err) + } + } + + tag.Tagger, err = newSignatureFromCommitline([]byte(ref["creator"])) + if err != nil { + return nil, fmt.Errorf("parse tagger: %w", err) + } + + tag.Message = ref["contents"] + // strip PGP signature if present in contents field + pgpStart := strings.Index(tag.Message, beginpgp) + if pgpStart >= 0 { + tag.Message = tag.Message[0:pgpStart] + } + + // annotated tag with GPG signature + if tag.Type == "tag" && ref["contents:signature"] != "" { + payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n", + tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message)) + tag.Signature = &CommitGPGSignature{ + Signature: ref["contents:signature"], + Payload: payload, + } + } + + return tag, nil +} + // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) { id, err := NewIDFromString(sha) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 0e6afabb4f7c..9d8467286205 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRepository_GetTags(t *testing.T) { @@ -195,3 +196,184 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { assert.True(t, IsErrNotExist(err)) assert.Nil(t, tag4) } + +func TestRepository_parseTagRef(t *testing.T) { + tests := []struct { + name string + + givenRef map[string]string + + want *Tag + wantErr bool + expectedErr error + }{ + { + name: "lightweight tag", + + givenRef: map[string]string{ + "objecttype": "commit", + "refname:short": "v1.9.1", + // object will be empty for lightweight tags + "object": "", + "objectname": "ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + "contents:signature": "", + }, + + want: &Tag{ + Name: "v1.9.1", + ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + Type: "commit", + Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", + Signature: nil, + }, + }, + + { + name: "annotated tag", + + givenRef: map[string]string{ + "objecttype": "tag", + "refname:short": "v0.0.1", + // object will refer to commit hash for annotated tag + "object": "3325fd8a973321fd59455492976c042dde3fd1ca", + "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + "contents:signature": "", + }, + + want: &Tag{ + Name: "v0.0.1", + ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + Type: "tag", + Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", + Signature: nil, + }, + }, + + { + name: "annotated tag with signature", + + givenRef: map[string]string{ + "objecttype": "tag", + "refname:short": "v0.0.1", + "object": "3325fd8a973321fd59455492976c042dde3fd1ca", + "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + "contents:signature": `-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + }, + + want: &Tag{ + Name: "v0.0.1", + ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + Type: "tag", + Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", + Signature: &CommitGPGSignature{ + Signature: `-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + Payload: `object 3325fd8a973321fd59455492976c042dde3fd1ca +type commit +tag v0.0.1 +tagger Foo Bar 1565789218 +0300 + +Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + }, + }, + }, + } + + for _, test := range tests { + tc := test // don't close over loop variable + t.Run(tc.name, func(t *testing.T) { + got, err := parseTagRef(tc.givenRef) + + if tc.wantErr { + require.Error(t, err) + require.ErrorIs(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, got) + } + }) + } +} + +func parseAuthorLine(t *testing.T, committer string) *Signature { + t.Helper() + + sig, err := newSignatureFromCommitline([]byte(committer)) + if err != nil { + t.Fatalf("parse author line '%s': %v", committer, err) + } + + return sig +} diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 3219b569a53d..3e7a9c2cfbce 100644 --- a/modules/git/repo_tree.go +++ b/modules/git/repo_tree.go @@ -60,13 +60,12 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - err = cmd.RunWithContext(&RunContext{ - Env: env, - Timeout: -1, - Dir: repo.Path, - Stdin: messageBytes, - Stdout: stdout, - Stderr: stderr, + err = cmd.Run(&RunOpts{ + Env: env, + Dir: repo.Path, + Stdin: messageBytes, + Stdout: stdout, + Stderr: stderr, }) if err != nil { diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index 0089d2c9a4f2..cc156ea916c3 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -22,7 +22,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { // GetTree find the tree object in the repository. func (repo *Repository) GetTree(idStr string) (*Tree, error) { if len(idStr) != 40 { - res, err := NewCommand(repo.Ctx, "rev-parse", "--verify", idStr).RunInDir(repo.Path) + res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", idStr).RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/tree.go b/modules/git/tree.go index f34e0554d7ab..a83336f3dbd3 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -55,7 +55,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error cmd.AddArguments(arg) } } - res, err := cmd.RunInDirBytes(repo.Path) + res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index d02fe8a00622..f852c5a51e65 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -81,16 +81,17 @@ func (t *Tree) ListEntries() (Entries, error) { } } - stdout, err := NewCommand(t.repo.Ctx, "ls-tree", "-l", t.ID.String()).RunInDirBytes(t.repo.Path) - if err != nil { - if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "fatal: not a tree object") { + stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l", t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) + if runErr != nil { + if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { return nil, ErrNotExist{ ID: t.ID.String(), } } - return nil, err + return nil, runErr } + var err error t.entries, err = parseTreeEntries(stdout, t) if err == nil { t.entriesParsed = true @@ -104,11 +105,13 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) { if t.entriesRecursiveParsed { return t.entriesRecursive, nil } - stdout, err := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-l", "-r", t.ID.String()).RunInDirBytes(t.repo.Path) - if err != nil { - return nil, err + + stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-l", "-r", t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path}) + if runErr != nil { + return nil, runErr } + var err error t.entriesRecursive, err = parseTreeEntries(stdout, t) if err == nil { t.entriesRecursiveParsed = true diff --git a/modules/gitgraph/graph.go b/modules/gitgraph/graph.go index e15441b8831d..271382525a4a 100644 --- a/modules/gitgraph/graph.go +++ b/modules/gitgraph/graph.go @@ -64,11 +64,10 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo scanner := bufio.NewScanner(stdoutReader) - if err := graphCmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: r.Path, - Stdout: stdoutWriter, - Stderr: stderr, + if err := graphCmd.Run(&git.RunOpts{ + Dir: r.Path, + Stdout: stdoutWriter, + Stderr: stderr, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() defer stdoutReader.Close() diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index 6fbb2bda29ab..b22b7b5860ae 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -18,6 +18,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" ) @@ -73,7 +74,7 @@ func (g *Manager) start(ctx context.Context) { // Set the running state & handle signals g.setState(stateRunning) - go g.handleSignals(ctx) + go g.handleSignals(g.managerCtx) // Handle clean up of unused provided listeners and delayed start-up startupDone := make(chan struct{}) @@ -112,6 +113,9 @@ func (g *Manager) start(ctx context.Context) { } func (g *Manager) handleSignals(ctx context.Context) { + ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Graceful: HandleSignals", process.SystemProcessType, true) + defer finished() + signalChannel := make(chan os.Signal, 1) signal.Notify( diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go index 309b33bedf1f..1abb3c0219ad 100644 --- a/modules/indexer/code/bleve.go +++ b/modules/indexer/code/bleve.go @@ -191,9 +191,10 @@ func (b *BleveIndexer) addUpdate(ctx context.Context, batchWriter git.WriteClose size := update.Size + var err error if !update.Sized { - stdout, err := git.NewCommand(ctx, "cat-file", "-s", update.BlobSha). - RunInDir(repo.RepoPath()) + var stdout string + stdout, _, err = git.NewCommand(ctx, "cat-file", "-s", update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return err } @@ -210,7 +211,7 @@ func (b *BleveIndexer) addUpdate(ctx context.Context, batchWriter git.WriteClose return err } - _, _, size, err := git.ReadBatchLine(batchReader) + _, _, size, err = git.ReadBatchLine(batchReader) if err != nil { return err } diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go index dd6ba19995ca..7263f27657c8 100644 --- a/modules/indexer/code/elastic_search.go +++ b/modules/indexer/code/elastic_search.go @@ -220,10 +220,10 @@ func (b *ElasticSearchIndexer) addUpdate(ctx context.Context, batchWriter git.Wr } size := update.Size - + var err error if !update.Sized { - stdout, err := git.NewCommand(ctx, "cat-file", "-s", update.BlobSha). - RunInDir(repo.RepoPath()) + var stdout string + stdout, _, err = git.NewCommand(ctx, "cat-file", "-s", update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return nil, err } @@ -240,7 +240,7 @@ func (b *ElasticSearchIndexer) addUpdate(ctx context.Context, batchWriter git.Wr return nil, err } - _, _, size, err := git.ReadBatchLine(batchReader) + _, _, size, err = git.ReadBatchLine(batchReader) if err != nil { return nil, err } diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index 62444f6251dd..60018af20c74 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -29,7 +29,7 @@ type repoChanges struct { } func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) { - stdout, err := git.NewCommand(ctx, "show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunInDir(repo.RepoPath()) + stdout, _, err := git.NewCommand(ctx, "show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return "", err } @@ -92,30 +92,32 @@ func parseGitLsTreeOutput(stdout []byte) ([]fileUpdate, error) { // genesisChanges get changes to add repo to the indexer for the first time func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*repoChanges, error) { var changes repoChanges - stdout, err := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", "-r", revision). - RunInDirBytes(repo.RepoPath()) - if err != nil { - return nil, err + stdout, _, runErr := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", "-r", revision).RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()}) + if runErr != nil { + return nil, runErr } + + var err error changes.Updates, err = parseGitLsTreeOutput(stdout) return &changes, err } // nonGenesisChanges get changes since the previous indexer update func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*repoChanges, error) { - diffCmd := git.NewCommand(ctx, "diff", "--name-status", - repo.CodeIndexerStatus.CommitSha, revision) - stdout, err := diffCmd.RunInDir(repo.RepoPath()) - if err != nil { + diffCmd := git.NewCommand(ctx, "diff", "--name-status", repo.CodeIndexerStatus.CommitSha, revision) + stdout, _, runErr := diffCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) + if runErr != nil { // previous commit sha may have been removed by a force push, so // try rebuilding from scratch - log.Warn("git diff: %v", err) - if err = indexer.Delete(repo.ID); err != nil { + log.Warn("git diff: %v", runErr) + if err := indexer.Delete(repo.ID); err != nil { return nil, err } return genesisChanges(ctx, repo, revision) } + var changes repoChanges + var err error updatedFilenames := make([]string, 0, 10) for _, line := range strings.Split(stdout, "\n") { line = strings.TrimSpace(line) @@ -169,7 +171,7 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio cmd := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", revision, "--") cmd.AddArguments(updatedFilenames...) - lsTreeStdout, err := cmd.RunInDirBytes(repo.RepoPath()) + lsTreeStdout, _, err := cmd.RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()}) if err != nil { return nil, err } diff --git a/modules/indexer/code/indexer.go b/modules/indexer/code/indexer.go index d897fcccd548..3ead3261e9c0 100644 --- a/modules/indexer/code/indexer.go +++ b/modules/indexer/code/indexer.go @@ -7,6 +7,7 @@ package code import ( "context" "os" + "runtime/pprof" "strconv" "strings" "time" @@ -15,6 +16,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -116,7 +118,7 @@ func Init() { return } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel, finished := process.GetManager().AddTypedContext(context.Background(), "Service: CodeIndexer", process.SystemProcessType, false) graceful.GetManager().RunAtTerminate(func() { select { @@ -128,6 +130,7 @@ func Init() { log.Debug("Closing repository indexer") indexer.Close() log.Info("PID: %d Repository Indexer closed", os.Getpid()) + finished() }) waitChannel := make(chan time.Duration) @@ -172,6 +175,7 @@ func Init() { } go func() { + pprof.SetGoroutineLabels(ctx) start := time.Now() var ( rIndexer Indexer @@ -247,6 +251,7 @@ func Init() { if setting.Indexer.StartupTimeout > 0 { go func() { + pprof.SetGoroutineLabels(ctx) timeout := setting.Indexer.StartupTimeout if graceful.GetManager().IsChild() && setting.GracefulHammerTime > 0 { timeout += setting.GracefulHammerTime diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 3aaa27eed213..1343b0bddd37 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "os" + "runtime/pprof" "sync" "time" @@ -16,6 +17,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -100,6 +102,8 @@ var ( // InitIssueIndexer initialize issue indexer, syncReindex is true then reindex until // all issue index done. func InitIssueIndexer(syncReindex bool) { + ctx, _, finished := process.GetManager().AddTypedContext(context.Background(), "Service: IssueIndexer", process.SystemProcessType, false) + waitChannel := make(chan time.Duration) // Create the Queue @@ -165,6 +169,7 @@ func InitIssueIndexer(syncReindex bool) { // Create the Indexer go func() { + pprof.SetGoroutineLabels(ctx) start := time.Now() log.Info("PID %d: Initializing Issue Indexer: %s", os.Getpid(), setting.Indexer.IssueType) var populate bool @@ -193,11 +198,13 @@ func InitIssueIndexer(syncReindex bool) { if issueIndexer != nil { issueIndexer.Close() } + finished() log.Info("PID: %d Issue Indexer closed", os.Getpid()) }) log.Debug("Created Bleve Indexer") case "elasticsearch": graceful.GetManager().RunWithShutdownFns(func(_, atTerminate func(func())) { + pprof.SetGoroutineLabels(ctx) issueIndexer, err := NewElasticSearchIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName) if err != nil { log.Fatal("Unable to initialize Elastic Search Issue Indexer at connection: %s Error: %v", setting.Indexer.IssueConnStr, err) @@ -208,10 +215,12 @@ func InitIssueIndexer(syncReindex bool) { } populate = !exist holder.set(issueIndexer) + atTerminate(finished) }) case "db": issueIndexer := &DBIndexer{} holder.set(issueIndexer) + graceful.GetManager().RunAtTerminate(finished) default: holder.cancel() log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType) @@ -251,6 +260,7 @@ func InitIssueIndexer(syncReindex bool) { } } else if setting.Indexer.StartupTimeout > 0 { go func() { + pprof.SetGoroutineLabels(ctx) timeout := setting.Indexer.StartupTimeout if graceful.GetManager().IsChild() && setting.GracefulHammerTime > 0 { timeout += setting.GracefulHammerTime @@ -272,6 +282,8 @@ func InitIssueIndexer(syncReindex bool) { // populateIssueIndexer populate the issue indexer with issue data func populateIssueIndexer(ctx context.Context) { + ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: PopulateIssueIndexer", process.SystemProcessType, true) + defer finished() for page := 1; ; page++ { select { case <-ctx.Done(): diff --git a/modules/log/event.go b/modules/log/event.go index b20dac17c73e..f66ecd179b70 100644 --- a/modules/log/event.go +++ b/modules/log/event.go @@ -5,9 +5,13 @@ package log import ( + "context" "fmt" + "runtime/pprof" "sync" "time" + + "code.gitea.io/gitea/modules/process" ) // Event represents a logging event @@ -34,6 +38,8 @@ type EventLogger interface { // ChannelledLog represents a cached channel to a LoggerProvider type ChannelledLog struct { + ctx context.Context + finished context.CancelFunc name string provider string queue chan *Event @@ -44,8 +50,9 @@ type ChannelledLog struct { } // NewChannelledLog a new logger instance with given logger provider and config. -func NewChannelledLog(name, provider, config string, bufferLength int64) (*ChannelledLog, error) { +func NewChannelledLog(parent context.Context, name, provider, config string, bufferLength int64) (*ChannelledLog, error) { if log, ok := providers[provider]; ok { + l := &ChannelledLog{ queue: make(chan *Event, bufferLength), flush: make(chan bool), @@ -58,6 +65,7 @@ func NewChannelledLog(name, provider, config string, bufferLength int64) (*Chann } l.name = name l.provider = provider + l.ctx, _, l.finished = process.GetManager().AddTypedContext(parent, fmt.Sprintf("Logger: %s(%s)", l.name, l.provider), process.SystemProcessType, false) go l.Start() return l, nil } @@ -66,6 +74,8 @@ func NewChannelledLog(name, provider, config string, bufferLength int64) (*Chann // Start processing the ChannelledLog func (l *ChannelledLog) Start() { + pprof.SetGoroutineLabels(l.ctx) + defer l.finished() for { select { case event, ok := <-l.queue: @@ -140,6 +150,8 @@ func (l *ChannelledLog) GetName() string { // MultiChannelledLog represents a cached channel to a LoggerProvider type MultiChannelledLog struct { + ctx context.Context + finished context.CancelFunc name string bufferLength int64 queue chan *Event @@ -156,7 +168,11 @@ type MultiChannelledLog struct { // NewMultiChannelledLog a new logger instance with given logger provider and config. func NewMultiChannelledLog(name string, bufferLength int64) *MultiChannelledLog { + ctx, _, finished := process.GetManager().AddTypedContext(context.Background(), fmt.Sprintf("Logger: %s", name), process.SystemProcessType, false) + m := &MultiChannelledLog{ + ctx: ctx, + finished: finished, name: name, queue: make(chan *Event, bufferLength), flush: make(chan bool), @@ -277,6 +293,9 @@ func (m *MultiChannelledLog) Start() { m.rwmutex.Unlock() return } + pprof.SetGoroutineLabels(m.ctx) + defer m.finished() + m.started = true m.rwmutex.Unlock() paused := false diff --git a/modules/log/multichannel.go b/modules/log/multichannel.go index 8d94eb2b22de..273df81df15e 100644 --- a/modules/log/multichannel.go +++ b/modules/log/multichannel.go @@ -31,7 +31,7 @@ func newLogger(name string, buffer int64) *MultiChannelledLogger { // SetLogger sets new logger instance with given logger provider and config. func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error { - eventLogger, err := NewChannelledLog(name, provider, config, l.bufferLength) + eventLogger, err := NewChannelledLog(l.ctx, name, provider, config, l.bufferLength) if err != nil { return fmt.Errorf("Failed to create sublogger (%s): %v", name, err) } diff --git a/modules/nosql/manager.go b/modules/nosql/manager.go index a89b5bb63390..dab30812ce73 100644 --- a/modules/nosql/manager.go +++ b/modules/nosql/manager.go @@ -5,10 +5,12 @@ package nosql import ( + "context" "strconv" "sync" "time" + "code.gitea.io/gitea/modules/process" "github.com/go-redis/redis/v8" "github.com/syndtr/goleveldb/leveldb" ) @@ -17,7 +19,9 @@ var manager *Manager // Manager is the nosql connection manager type Manager struct { - mutex sync.Mutex + ctx context.Context + finished context.CancelFunc + mutex sync.Mutex RedisConnections map[string]*redisClientHolder LevelDBConnections map[string]*levelDBHolder @@ -46,7 +50,10 @@ func init() { // GetManager returns a Manager and initializes one as singleton is there's none yet func GetManager() *Manager { if manager == nil { + ctx, _, finished := process.GetManager().AddTypedContext(context.Background(), "Service: NoSQL", process.SystemProcessType, false) manager = &Manager{ + ctx: ctx, + finished: finished, RedisConnections: make(map[string]*redisClientHolder), LevelDBConnections: make(map[string]*levelDBHolder), } diff --git a/modules/nosql/manager_leveldb.go b/modules/nosql/manager_leveldb.go index de4ef14d7dcf..d69ae8880008 100644 --- a/modules/nosql/manager_leveldb.go +++ b/modules/nosql/manager_leveldb.go @@ -7,6 +7,7 @@ package nosql import ( "fmt" "path" + "runtime/pprof" "strconv" "strings" @@ -50,7 +51,31 @@ func (m *Manager) CloseLevelDB(connection string) error { } // GetLevelDB gets a levelDB for a particular connection -func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) { +func (m *Manager) GetLevelDB(connection string) (db *leveldb.DB, err error) { + // Because we want associate any goroutines created by this call to the main nosqldb context we need to + // wrap this in a goroutine labelled with the nosqldb context + done := make(chan struct{}) + var recovered interface{} + go func() { + defer func() { + recovered = recover() + if recovered != nil { + log.Critical("PANIC during GetLevelDB: %v\nStacktrace: %s", recovered, log.Stack(2)) + } + close(done) + }() + pprof.SetGoroutineLabels(m.ctx) + + db, err = m.getLevelDB(connection) + }() + <-done + if recovered != nil { + panic(recovered) + } + return +} + +func (m *Manager) getLevelDB(connection string) (*leveldb.DB, error) { // Convert the provided connection description to the common format uri := ToLevelDBURI(connection) @@ -168,15 +193,18 @@ func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) { if err != nil { if !errors.IsCorrupted(err) { if strings.Contains(err.Error(), "resource temporarily unavailable") { - return nil, fmt.Errorf("unable to lock level db at %s: %w", dataDir, err) + err = fmt.Errorf("unable to lock level db at %s: %w", dataDir, err) + return nil, err } - return nil, fmt.Errorf("unable to open level db at %s: %w", dataDir, err) - } - db.db, err = leveldb.RecoverFile(dataDir, opts) - if err != nil { + err = fmt.Errorf("unable to open level db at %s: %w", dataDir, err) return nil, err } + db.db, err = leveldb.RecoverFile(dataDir, opts) + } + + if err != nil { + return nil, err } for _, name := range db.name { diff --git a/modules/nosql/manager_redis.go b/modules/nosql/manager_redis.go index 0ff01dcac239..b82f899db042 100644 --- a/modules/nosql/manager_redis.go +++ b/modules/nosql/manager_redis.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "net/url" "path" + "runtime/pprof" "strconv" "strings" @@ -43,7 +44,31 @@ func (m *Manager) CloseRedisClient(connection string) error { } // GetRedisClient gets a redis client for a particular connection -func (m *Manager) GetRedisClient(connection string) redis.UniversalClient { +func (m *Manager) GetRedisClient(connection string) (client redis.UniversalClient) { + // Because we want associate any goroutines created by this call to the main nosqldb context we need to + // wrap this in a goroutine labelled with the nosqldb context + done := make(chan struct{}) + var recovered interface{} + go func() { + defer func() { + recovered = recover() + if recovered != nil { + log.Critical("PANIC during GetRedisClient: %v\nStacktrace: %s", recovered, log.Stack(2)) + } + close(done) + }() + pprof.SetGoroutineLabels(m.ctx) + + client = m.getRedisClient(connection) + }() + <-done + if recovered != nil { + panic(recovered) + } + return +} + +func (m *Manager) getRedisClient(connection string) redis.UniversalClient { m.mutex.Lock() defer m.mutex.Unlock() client, ok := m.RedisConnections[connection] diff --git a/modules/private/manager.go b/modules/private/manager.go index 2543e141ea41..8405bf2c83d8 100644 --- a/modules/private/manager.go +++ b/modules/private/manager.go @@ -7,6 +7,7 @@ package private import ( "context" "fmt" + "io" "net/http" "net/url" "time" @@ -189,3 +190,25 @@ func RemoveLogger(ctx context.Context, group, name string) (int, string) { return http.StatusOK, "Removed" } + +// Processes return the current processes from this gitea instance +func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces, json bool, cancel string) (int, string) { + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/processes?flat=%t&no-system=%t&stacktraces=%t&json=%t&cancel-pid=%s", flat, noSystem, stacktraces, json, url.QueryEscape(cancel)) + + req := newInternalRequest(ctx, reqURL, "GET") + resp, err := req.Response() + if err != nil { + return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return resp.StatusCode, decodeJSONError(resp).Err + } + + _, err = io.Copy(out, resp.Body) + if err != nil { + return http.StatusInternalServerError, err.Error() + } + return http.StatusOK, "" +} diff --git a/modules/process/error.go b/modules/process/error.go new file mode 100644 index 000000000000..7a72bda40e3b --- /dev/null +++ b/modules/process/error.go @@ -0,0 +1,26 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package process + +import "fmt" + +// Error is a wrapped error describing the error results of Process Execution +type Error struct { + PID IDType + Description string + Err error + CtxErr error + Stdout string + Stderr string +} + +func (err *Error) Error() string { + return fmt.Sprintf("exec(%s:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr) +} + +// Unwrap implements the unwrappable implicit interface for go1.13 Unwrap() +func (err *Error) Unwrap() error { + return err.Err +} diff --git a/modules/process/manager.go b/modules/process/manager.go index 50dbbbe6c807..5d7aee760f58 100644 --- a/modules/process/manager.go +++ b/modules/process/manager.go @@ -6,13 +6,8 @@ package process import ( - "bytes" "context" - "fmt" - "io" - "os/exec" "runtime/pprof" - "sort" "strconv" "sync" "time" @@ -30,6 +25,18 @@ var ( DefaultContext = context.Background() ) +// DescriptionPProfLabel is a label set on goroutines that have a process attached +const DescriptionPProfLabel = "process-description" + +// PIDPProfLabel is a label set on goroutines that have a process attached +const PIDPProfLabel = "pid" + +// PPIDPProfLabel is a label set on goroutines that have a process attached +const PPIDPProfLabel = "ppid" + +// ProcessTypePProfLabel is a label set on goroutines that have a process attached +const ProcessTypePProfLabel = "process-type" + // IDType is a pid type type IDType string @@ -44,15 +51,15 @@ type Manager struct { next int64 lastTime int64 - processes map[IDType]*Process + processMap map[IDType]*process } // GetManager returns a Manager and initializes one as singleton if there's none yet func GetManager() *Manager { managerInit.Do(func() { manager = &Manager{ - processes: make(map[IDType]*Process), - next: 1, + processMap: make(map[IDType]*process), + next: 1, } }) return manager @@ -69,12 +76,25 @@ func GetManager() *Manager { func (pm *Manager) AddContext(parent context.Context, description string) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) { ctx, cancel = context.WithCancel(parent) - ctx, pid, finished := pm.Add(ctx, description, cancel) + ctx, _, finished = pm.Add(ctx, description, cancel, NormalProcessType, true) - return &Context{ - Context: ctx, - pid: pid, - }, cancel, finished + return ctx, cancel, finished +} + +// AddTypedContext creates a new context and adds it as a process. Once the process is finished, finished must be called +// to remove the process from the process table. It should not be called until the process is finished but must always be called. +// +// cancel should be used to cancel the returned context, however it will not remove the process from the process table. +// finished will cancel the returned context and remove it from the process table. +// +// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the +// process table. +func (pm *Manager) AddTypedContext(parent context.Context, description, processType string, currentlyRunning bool) (ctx context.Context, cancel context.CancelFunc, finished FinishedFunc) { + ctx, cancel = context.WithCancel(parent) + + ctx, _, finished = pm.Add(ctx, description, cancel, processType, currentlyRunning) + + return ctx, cancel, finished } // AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called @@ -86,52 +106,65 @@ func (pm *Manager) AddContext(parent context.Context, description string) (ctx c // Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the // process table. func (pm *Manager) AddContextTimeout(parent context.Context, timeout time.Duration, description string) (ctx context.Context, cancel context.CancelFunc, finshed FinishedFunc) { + if timeout <= 0 { + // it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct + panic("the timeout must be greater than zero, otherwise the context will be cancelled immediately") + } + ctx, cancel = context.WithTimeout(parent, timeout) - ctx, pid, finshed := pm.Add(ctx, description, cancel) + ctx, _, finshed = pm.Add(ctx, description, cancel, NormalProcessType, true) - return &Context{ - Context: ctx, - pid: pid, - }, cancel, finshed + return ctx, cancel, finshed } // Add create a new process -func (pm *Manager) Add(ctx context.Context, description string, cancel context.CancelFunc) (context.Context, IDType, FinishedFunc) { +func (pm *Manager) Add(ctx context.Context, description string, cancel context.CancelFunc, processType string, currentlyRunning bool) (context.Context, IDType, FinishedFunc) { parentPID := GetParentPID(ctx) pm.mutex.Lock() start, pid := pm.nextPID() - parent := pm.processes[parentPID] + parent := pm.processMap[parentPID] if parent == nil { parentPID = "" } - process := &Process{ + process := &process{ PID: pid, ParentPID: parentPID, Description: description, Start: start, Cancel: cancel, + Type: processType, } - finished := func() { - cancel() - pm.remove(process) - pprof.SetGoroutineLabels(ctx) + var finished FinishedFunc + if currentlyRunning { + finished = func() { + cancel() + pm.remove(process) + pprof.SetGoroutineLabels(ctx) + } + } else { + finished = func() { + cancel() + pm.remove(process) + } } - if parent != nil { - parent.AddChild(process) - } - pm.processes[pid] = process + pm.processMap[pid] = process pm.mutex.Unlock() - pprofCtx := pprof.WithLabels(ctx, pprof.Labels("process-description", description, "ppid", string(parentPID), "pid", string(pid))) - pprof.SetGoroutineLabels(pprofCtx) + pprofCtx := pprof.WithLabels(ctx, pprof.Labels(DescriptionPProfLabel, description, PPIDPProfLabel, string(parentPID), PIDPProfLabel, string(pid), ProcessTypePProfLabel, processType)) + if currentlyRunning { + pprof.SetGoroutineLabels(pprofCtx) + } - return pprofCtx, pid, finished + return &Context{ + Context: pprofCtx, + pid: pid, + }, pid, finished } // nextPID will return the next available PID. pm.mutex should already be locked. @@ -156,142 +189,24 @@ func (pm *Manager) nextPID() (start time.Time, pid IDType) { // Remove a process from the ProcessManager. func (pm *Manager) Remove(pid IDType) { pm.mutex.Lock() - delete(pm.processes, pid) + delete(pm.processMap, pid) pm.mutex.Unlock() } -func (pm *Manager) remove(process *Process) { +func (pm *Manager) remove(process *process) { pm.mutex.Lock() - if p := pm.processes[process.PID]; p == process { - delete(pm.processes, process.PID) + defer pm.mutex.Unlock() + if p := pm.processMap[process.PID]; p == process { + delete(pm.processMap, process.PID) } - parent := pm.processes[process.ParentPID] - pm.mutex.Unlock() - - if parent == nil { - return - } - - parent.RemoveChild(process) } // Cancel a process in the ProcessManager. func (pm *Manager) Cancel(pid IDType) { pm.mutex.Lock() - process, ok := pm.processes[pid] + process, ok := pm.processMap[pid] pm.mutex.Unlock() - if ok { + if ok && process.Type != SystemProcessType { process.Cancel() } } - -// Processes gets the processes in a thread safe manner -func (pm *Manager) Processes(onlyRoots bool) []*Process { - pm.mutex.Lock() - processes := make([]*Process, 0, len(pm.processes)) - if onlyRoots { - for _, process := range pm.processes { - if _, has := pm.processes[process.ParentPID]; !has { - processes = append(processes, process) - } - } - } else { - for _, process := range pm.processes { - processes = append(processes, process) - } - } - pm.mutex.Unlock() - - sort.Slice(processes, func(i, j int) bool { - left, right := processes[i], processes[j] - - return left.Start.Before(right.Start) - }) - - return processes -} - -// Exec a command and use the default timeout. -func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDir(DefaultContext, -1, "", desc, cmdName, args...) -} - -// ExecTimeout a command and use a specific timeout duration. -func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDir(DefaultContext, timeout, "", desc, cmdName, args...) -} - -// ExecDir a command and use the default timeout. -func (pm *Manager) ExecDir(ctx context.Context, timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) { - return pm.ExecDirEnv(ctx, timeout, dir, desc, nil, cmdName, args...) -} - -// ExecDirEnv runs a command in given path and environment variables, and waits for its completion -// up to the given timeout (or DefaultTimeout if -1 is given). -// Returns its complete stdout and stderr -// outputs and an error, if any (including timeout) -func (pm *Manager) ExecDirEnv(ctx context.Context, timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) { - return pm.ExecDirEnvStdIn(ctx, timeout, dir, desc, env, nil, cmdName, args...) -} - -// ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion -// up to the given timeout (or DefaultTimeout if -1 is given). -// Returns its complete stdout and stderr -// outputs and an error, if any (including timeout) -func (pm *Manager) ExecDirEnvStdIn(ctx context.Context, timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) { - if timeout == -1 { - timeout = 60 * time.Second - } - - stdOut := new(bytes.Buffer) - stdErr := new(bytes.Buffer) - - ctx, _, finished := pm.AddContextTimeout(ctx, timeout, desc) - defer finished() - - cmd := exec.CommandContext(ctx, cmdName, args...) - cmd.Dir = dir - cmd.Env = env - cmd.Stdout = stdOut - cmd.Stderr = stdErr - if stdIn != nil { - cmd.Stdin = stdIn - } - - if err := cmd.Start(); err != nil { - return "", "", err - } - - err := cmd.Wait() - if err != nil { - err = &Error{ - PID: GetPID(ctx), - Description: desc, - Err: err, - CtxErr: ctx.Err(), - Stdout: stdOut.String(), - Stderr: stdErr.String(), - } - } - - return stdOut.String(), stdErr.String(), err -} - -// Error is a wrapped error describing the error results of Process Execution -type Error struct { - PID IDType - Description string - Err error - CtxErr error - Stdout string - Stderr string -} - -func (err *Error) Error() string { - return fmt.Sprintf("exec(%s:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr) -} - -// Unwrap implements the unwrappable implicit interface for go1.13 Unwrap() -func (err *Error) Unwrap() error { - return err.Err -} diff --git a/modules/process/manager_exec.go b/modules/process/manager_exec.go new file mode 100644 index 000000000000..61ddae646f0e --- /dev/null +++ b/modules/process/manager_exec.go @@ -0,0 +1,79 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package process + +import ( + "bytes" + "context" + "io" + "os/exec" + "time" +) + +// Exec a command and use the default timeout. +func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) { + return pm.ExecDir(DefaultContext, -1, "", desc, cmdName, args...) +} + +// ExecTimeout a command and use a specific timeout duration. +func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) { + return pm.ExecDir(DefaultContext, timeout, "", desc, cmdName, args...) +} + +// ExecDir a command and use the default timeout. +func (pm *Manager) ExecDir(ctx context.Context, timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) { + return pm.ExecDirEnv(ctx, timeout, dir, desc, nil, cmdName, args...) +} + +// ExecDirEnv runs a command in given path and environment variables, and waits for its completion +// up to the given timeout (or DefaultTimeout if -1 is given). +// Returns its complete stdout and stderr +// outputs and an error, if any (including timeout) +func (pm *Manager) ExecDirEnv(ctx context.Context, timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) { + return pm.ExecDirEnvStdIn(ctx, timeout, dir, desc, env, nil, cmdName, args...) +} + +// ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion +// up to the given timeout (or DefaultTimeout if timeout <= 0 is given). +// Returns its complete stdout and stderr +// outputs and an error, if any (including timeout) +func (pm *Manager) ExecDirEnvStdIn(ctx context.Context, timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) { + if timeout <= 0 { + timeout = 60 * time.Second + } + + stdOut := new(bytes.Buffer) + stdErr := new(bytes.Buffer) + + ctx, _, finished := pm.AddContextTimeout(ctx, timeout, desc) + defer finished() + + cmd := exec.CommandContext(ctx, cmdName, args...) + cmd.Dir = dir + cmd.Env = env + cmd.Stdout = stdOut + cmd.Stderr = stdErr + if stdIn != nil { + cmd.Stdin = stdIn + } + + if err := cmd.Start(); err != nil { + return "", "", err + } + + err := cmd.Wait() + if err != nil { + err = &Error{ + PID: GetPID(ctx), + Description: desc, + Err: err, + CtxErr: ctx.Err(), + Stdout: stdOut.String(), + Stderr: stdErr.String(), + } + } + + return stdOut.String(), stdErr.String(), err +} diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go new file mode 100644 index 000000000000..fbe3374b87bb --- /dev/null +++ b/modules/process/manager_stacktraces.go @@ -0,0 +1,355 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package process + +import ( + "fmt" + "io" + "runtime/pprof" + "sort" + "time" + + "github.com/google/pprof/profile" +) + +// StackEntry is an entry on a stacktrace +type StackEntry struct { + Function string + File string + Line int +} + +// Label represents a pprof label assigned to goroutine stack +type Label struct { + Name string + Value string +} + +// Stack is a stacktrace relating to a goroutine. (Multiple goroutines may have the same stacktrace) +type Stack struct { + Count int64 // Number of goroutines with this stack trace + Description string + Labels []*Label `json:",omitempty"` + Entry []*StackEntry `json:",omitempty"` +} + +// A Process is a combined representation of a Process and a Stacktrace for the goroutines associated with it +type Process struct { + PID IDType + ParentPID IDType + Description string + Start time.Time + Type string + + Children []*Process `json:",omitempty"` + Stacks []*Stack `json:",omitempty"` +} + +// Processes gets the processes in a thread safe manner +func (pm *Manager) Processes(flat, noSystem bool) ([]*Process, int) { + pm.mutex.Lock() + processCount := len(pm.processMap) + processes := make([]*Process, 0, len(pm.processMap)) + if flat { + for _, process := range pm.processMap { + if noSystem && process.Type == SystemProcessType { + continue + } + processes = append(processes, process.toProcess()) + } + } else { + // We need our own processMap + processMap := map[IDType]*Process{} + for _, internalProcess := range pm.processMap { + process, ok := processMap[internalProcess.PID] + if !ok { + process = internalProcess.toProcess() + processMap[process.PID] = process + } + + // Check its parent + if process.ParentPID == "" { + processes = append(processes, process) + continue + } + + internalParentProcess, ok := pm.processMap[internalProcess.ParentPID] + if ok { + parentProcess, ok := processMap[process.ParentPID] + if !ok { + parentProcess = internalParentProcess.toProcess() + processMap[parentProcess.PID] = parentProcess + } + parentProcess.Children = append(parentProcess.Children, process) + continue + } + + processes = append(processes, process) + } + } + pm.mutex.Unlock() + + if !flat && noSystem { + for i := 0; i < len(processes); i++ { + process := processes[i] + if process.Type != SystemProcessType { + continue + } + processes[len(processes)-1], processes[i] = processes[i], processes[len(processes)-1] + processes = append(processes[:len(processes)-1], process.Children...) + i-- + } + } + + // Sort by process' start time. Oldest process appears first. + sort.Slice(processes, func(i, j int) bool { + left, right := processes[i], processes[j] + + return left.Start.Before(right.Start) + }) + + return processes, processCount +} + +// ProcessStacktraces gets the processes and stacktraces in a thread safe manner +func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int64, error) { + var stacks *profile.Profile + var err error + + // We cannot use the pm.ProcessMap here because we will release the mutex ... + processMap := map[IDType]*Process{} + processCount := 0 + + // Lock the manager + pm.mutex.Lock() + processCount = len(pm.processMap) + + // Add a defer to unlock in case there is a panic + unlocked := false + defer func() { + if !unlocked { + pm.mutex.Unlock() + } + }() + + processes := make([]*Process, 0, len(pm.processMap)) + if flat { + for _, internalProcess := range pm.processMap { + process := internalProcess.toProcess() + processMap[process.PID] = process + if noSystem && internalProcess.Type == SystemProcessType { + continue + } + processes = append(processes, process) + } + } else { + for _, internalProcess := range pm.processMap { + process, ok := processMap[internalProcess.PID] + if !ok { + process = internalProcess.toProcess() + processMap[process.PID] = process + } + + // Check its parent + if process.ParentPID == "" { + processes = append(processes, process) + continue + } + + internalParentProcess, ok := pm.processMap[internalProcess.ParentPID] + if ok { + parentProcess, ok := processMap[process.ParentPID] + if !ok { + parentProcess = internalParentProcess.toProcess() + processMap[parentProcess.PID] = parentProcess + } + parentProcess.Children = append(parentProcess.Children, process) + continue + } + + processes = append(processes, process) + } + } + + // Now from within the lock we need to get the goroutines. + // Why? If we release the lock then between between filling the above map and getting + // the stacktraces another process could be created which would then look like a dead process below + reader, writer := io.Pipe() + defer reader.Close() + go func() { + err := pprof.Lookup("goroutine").WriteTo(writer, 0) + _ = writer.CloseWithError(err) + }() + stacks, err = profile.Parse(reader) + if err != nil { + return nil, 0, 0, err + } + + // Unlock the mutex + pm.mutex.Unlock() + unlocked = true + + goroutineCount := int64(0) + + // Now walk through the "Sample" slice in the goroutines stack + for _, sample := range stacks.Sample { + // In the "goroutine" pprof profile each sample represents one or more goroutines + // with the same labels and stacktraces. + + // We will represent each goroutine by a `Stack` + stack := &Stack{} + + // Add the non-process associated labels from the goroutine sample to the Stack + for name, value := range sample.Label { + if name == DescriptionPProfLabel || name == PIDPProfLabel || (!flat && name == PPIDPProfLabel) || name == ProcessTypePProfLabel { + continue + } + + // Labels from the "goroutine" pprof profile only have one value. + // This is because the underlying representation is a map[string]string + if len(value) != 1 { + // Unexpected... + return nil, 0, 0, fmt.Errorf("label: %s in goroutine stack with unexpected number of values: %v", name, value) + } + + stack.Labels = append(stack.Labels, &Label{Name: name, Value: value[0]}) + } + + // The number of goroutines that this sample represents is the `stack.Value[0]` + stack.Count = sample.Value[0] + goroutineCount += stack.Count + + // Now we want to associate this Stack with a Process. + var process *Process + + // Try to get the PID from the goroutine labels + if pidvalue, ok := sample.Label[PIDPProfLabel]; ok && len(pidvalue) == 1 { + pid := IDType(pidvalue[0]) + + // Now try to get the process from our map + process, ok = processMap[pid] + if !ok && pid != "" { + // This means that no process has been found in the process map - but there was a process PID + // Therefore this goroutine belongs to a dead process and it has escaped control of the process as it + // should have died with the process context cancellation. + + // We need to create a dead process holder for this process and label it appropriately + + // get the parent PID + ppid := IDType("") + if value, ok := sample.Label[PPIDPProfLabel]; ok && len(value) == 1 { + ppid = IDType(value[0]) + } + + // format the description + description := "(dead process)" + if value, ok := sample.Label[DescriptionPProfLabel]; ok && len(value) == 1 { + description = value[0] + " " + description + } + + // override the type of the process to "code" but add the old type as a label on the first stack + ptype := NoneProcessType + if value, ok := sample.Label[ProcessTypePProfLabel]; ok && len(value) == 1 { + stack.Labels = append(stack.Labels, &Label{Name: ProcessTypePProfLabel, Value: value[0]}) + } + process = &Process{ + PID: pid, + ParentPID: ppid, + Description: description, + Type: ptype, + } + + // Now add the dead process back to the map and tree so we don't go back through this again. + processMap[process.PID] = process + added := false + if process.ParentPID != "" && !flat { + if parent, ok := processMap[process.ParentPID]; ok { + parent.Children = append(parent.Children, process) + added = true + } + } + if !added { + processes = append(processes, process) + } + } + } + + if process == nil { + // This means that the sample we're looking has no PID label + var ok bool + process, ok = processMap[""] + if !ok { + // this is the first time we've come acrross an unassociated goroutine so create a "process" to hold them + process = &Process{ + Description: "(unassociated)", + Type: NoneProcessType, + } + processMap[process.PID] = process + processes = append(processes, process) + } + } + + // The sample.Location represents a stack trace for this goroutine, + // however each Location can represent multiple lines (mostly due to inlining) + // so we need to walk the lines too + for _, location := range sample.Location { + for _, line := range location.Line { + entry := &StackEntry{ + Function: line.Function.Name, + File: line.Function.Filename, + Line: int(line.Line), + } + stack.Entry = append(stack.Entry, entry) + } + } + + // Now we need a short-descriptive name to call the stack trace if when it is folded and + // assuming the stack trace has some lines we'll choose the bottom of the stack (i.e. the + // initial function that started the stack trace.) The top of the stack is unlikely to + // be very helpful as a lot of the time it will be runtime.select or some other call into + // a std library. + stack.Description = "(unknown)" + if len(stack.Entry) > 0 { + stack.Description = stack.Entry[len(stack.Entry)-1].Function + } + + process.Stacks = append(process.Stacks, stack) + } + + // restrict to not show system processes + if noSystem { + for i := 0; i < len(processes); i++ { + process := processes[i] + if process.Type != SystemProcessType && process.Type != NoneProcessType { + continue + } + processes[len(processes)-1], processes[i] = processes[i], processes[len(processes)-1] + processes = append(processes[:len(processes)-1], process.Children...) + i-- + } + } + + // Now finally re-sort the processes. Newest process appears first + after := func(processes []*Process) func(i, j int) bool { + return func(i, j int) bool { + left, right := processes[i], processes[j] + return left.Start.After(right.Start) + } + } + sort.Slice(processes, after(processes)) + if !flat { + + var sortChildren func(process *Process) + + sortChildren = func(process *Process) { + sort.Slice(process.Children, after(process.Children)) + for _, child := range process.Children { + sortChildren(child) + } + } + } + + return processes, processCount, goroutineCount, err +} diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go index 152c7a92359c..30eabeb37a48 100644 --- a/modules/process/manager_test.go +++ b/modules/process/manager_test.go @@ -22,7 +22,7 @@ func TestGetManager(t *testing.T) { } func TestManager_AddContext(t *testing.T) { - pm := Manager{processes: make(map[IDType]*Process), next: 1} + pm := Manager{processMap: make(map[IDType]*process), next: 1} ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -41,7 +41,7 @@ func TestManager_AddContext(t *testing.T) { } func TestManager_Cancel(t *testing.T) { - pm := Manager{processes: make(map[IDType]*Process), next: 1} + pm := Manager{processMap: make(map[IDType]*process), next: 1} ctx, _, finished := pm.AddContext(context.Background(), "foo") defer finished() @@ -69,7 +69,7 @@ func TestManager_Cancel(t *testing.T) { } func TestManager_Remove(t *testing.T) { - pm := Manager{processes: make(map[IDType]*Process), next: 1} + pm := Manager{processMap: make(map[IDType]*process), next: 1} ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -85,7 +85,7 @@ func TestManager_Remove(t *testing.T) { pm.Remove(GetPID(p2Ctx)) - _, exists := pm.processes[GetPID(p2Ctx)] + _, exists := pm.processMap[GetPID(p2Ctx)] assert.False(t, exists, "PID %d is in the list but shouldn't", GetPID(p2Ctx)) } diff --git a/modules/process/process.go b/modules/process/process.go index 662f878d7f3d..2f7ea1837325 100644 --- a/modules/process/process.go +++ b/modules/process/process.go @@ -6,61 +6,34 @@ package process import ( "context" - "sync" "time" ) -// Process represents a working process inheriting from Gitea. -type Process struct { +var ( + SystemProcessType = "system" + RequestProcessType = "request" + NormalProcessType = "normal" + NoneProcessType = "none" +) + +// process represents a working process inheriting from Gitea. +type process struct { PID IDType // Process ID, not system one. ParentPID IDType Description string Start time.Time Cancel context.CancelFunc - - lock sync.Mutex - children []*Process -} - -// Children gets the children of the process -// Note: this function will behave nicely even if p is nil -func (p *Process) Children() (children []*Process) { - if p == nil { - return - } - - p.lock.Lock() - defer p.lock.Unlock() - children = make([]*Process, len(p.children)) - copy(children, p.children) - return children + Type string } -// AddChild adds a child process -// Note: this function will behave nicely even if p is nil -func (p *Process) AddChild(child *Process) { - if p == nil { - return - } - - p.lock.Lock() - defer p.lock.Unlock() - p.children = append(p.children, child) -} - -// RemoveChild removes a child process -// Note: this function will behave nicely even if p is nil -func (p *Process) RemoveChild(process *Process) { - if p == nil { - return - } - - p.lock.Lock() - defer p.lock.Unlock() - for i, child := range p.children { - if child == process { - p.children = append(p.children[:i], p.children[i+1:]...) - return - } +// ToProcess converts a process to a externally usable Process +func (p *process) toProcess() *Process { + process := &Process{ + PID: p.PID, + ParentPID: p.ParentPID, + Description: p.Description, + Start: p.Start, + Type: p.Type, } + return process } diff --git a/modules/queue/queue_bytefifo.go b/modules/queue/queue_bytefifo.go index ead3828f332b..99c6428abce7 100644 --- a/modules/queue/queue_bytefifo.go +++ b/modules/queue/queue_bytefifo.go @@ -7,6 +7,7 @@ package queue import ( "context" "fmt" + "runtime/pprof" "sync" "sync/atomic" "time" @@ -20,7 +21,6 @@ import ( type ByteFIFOQueueConfiguration struct { WorkerPoolConfiguration Workers int - Name string WaitOnEmpty bool } @@ -153,6 +153,7 @@ func (q *ByteFIFOQueue) Flush(timeout time.Duration) error { // Run runs the bytefifo queue func (q *ByteFIFOQueue) Run(atShutdown, atTerminate func(func())) { + pprof.SetGoroutineLabels(q.baseCtx) atShutdown(q.Shutdown) atTerminate(q.Terminate) log.Debug("%s: %s Starting", q.typ, q.name) @@ -355,6 +356,7 @@ func (q *ByteFIFOQueue) Terminate() { if err := q.byteFIFO.Close(); err != nil { log.Error("Error whilst closing internal byte fifo in %s: %s: %v", q.typ, q.name, err) } + q.baseCtxFinished() log.Debug("%s: %s Terminated", q.typ, q.name) } diff --git a/modules/queue/queue_channel.go b/modules/queue/queue_channel.go index 5469c0310076..028023d50032 100644 --- a/modules/queue/queue_channel.go +++ b/modules/queue/queue_channel.go @@ -7,6 +7,7 @@ package queue import ( "context" "fmt" + "runtime/pprof" "sync/atomic" "time" @@ -20,7 +21,6 @@ const ChannelQueueType Type = "channel" type ChannelQueueConfiguration struct { WorkerPoolConfiguration Workers int - Name string } // ChannelQueue implements Queue @@ -84,6 +84,7 @@ func NewChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, erro // Run starts to run the queue func (q *ChannelQueue) Run(atShutdown, atTerminate func(func())) { + pprof.SetGoroutineLabels(q.baseCtx) atShutdown(q.Shutdown) atTerminate(q.Terminate) log.Debug("ChannelQueue: %s Starting", q.name) @@ -169,6 +170,7 @@ func (q *ChannelQueue) Terminate() { default: } q.terminateCtxCancel() + q.baseCtxFinished() log.Debug("ChannelQueue: %s Terminated", q.name) } diff --git a/modules/queue/queue_channel_test.go b/modules/queue/queue_channel_test.go index 26a635b91897..d30b90886151 100644 --- a/modules/queue/queue_channel_test.go +++ b/modules/queue/queue_channel_test.go @@ -34,9 +34,9 @@ func TestChannelQueue(t *testing.T) { BlockTimeout: 1 * time.Second, BoostTimeout: 5 * time.Minute, BoostWorkers: 5, + Name: "TestChannelQueue", }, Workers: 0, - Name: "TestChannelQueue", }, &testData{}) assert.NoError(t, err) diff --git a/modules/queue/queue_disk_channel.go b/modules/queue/queue_disk_channel.go index 0494698e0ea2..014d93f5b5d5 100644 --- a/modules/queue/queue_disk_channel.go +++ b/modules/queue/queue_disk_channel.go @@ -7,6 +7,7 @@ package queue import ( "context" "fmt" + "runtime/pprof" "sync" "sync/atomic" "time" @@ -72,9 +73,9 @@ func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) ( BoostTimeout: config.BoostTimeout, BoostWorkers: config.BoostWorkers, MaxWorkers: config.MaxWorkers, + Name: config.Name + "-channel", }, Workers: config.Workers, - Name: config.Name + "-channel", }, exemplar) if err != nil { return nil, err @@ -90,9 +91,9 @@ func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) ( BoostTimeout: 5 * time.Minute, BoostWorkers: 1, MaxWorkers: 5, + Name: config.Name + "-level", }, Workers: 0, - Name: config.Name + "-level", }, DataDir: config.DataDir, } @@ -154,6 +155,7 @@ func (q *PersistableChannelQueue) PushBack(data Data) error { // Run starts to run the queue func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) { + pprof.SetGoroutineLabels(q.channelQueue.baseCtx) log.Debug("PersistableChannelQueue: %s Starting", q.delayedStarter.name) _ = q.channelQueue.AddWorkers(q.channelQueue.workers, 0) diff --git a/modules/queue/unique_queue_channel.go b/modules/queue/unique_queue_channel.go index b7282e6c6cad..6e8d37a20cc4 100644 --- a/modules/queue/unique_queue_channel.go +++ b/modules/queue/unique_queue_channel.go @@ -7,6 +7,7 @@ package queue import ( "context" "fmt" + "runtime/pprof" "sync" "sync/atomic" "time" @@ -97,6 +98,7 @@ func NewChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue // Run starts to run the queue func (q *ChannelUniqueQueue) Run(atShutdown, atTerminate func(func())) { + pprof.SetGoroutineLabels(q.baseCtx) atShutdown(q.Shutdown) atTerminate(q.Terminate) log.Debug("ChannelUniqueQueue: %s Starting", q.name) @@ -226,6 +228,7 @@ func (q *ChannelUniqueQueue) Terminate() { default: } q.terminateCtxCancel() + q.baseCtxFinished() log.Debug("ChannelUniqueQueue: %s Terminated", q.name) } diff --git a/modules/queue/unique_queue_channel_test.go b/modules/queue/unique_queue_channel_test.go index ef6752079e14..6daf3fc96ecb 100644 --- a/modules/queue/unique_queue_channel_test.go +++ b/modules/queue/unique_queue_channel_test.go @@ -32,9 +32,9 @@ func TestChannelUniqueQueue(t *testing.T) { BlockTimeout: 1 * time.Second, BoostTimeout: 5 * time.Minute, BoostWorkers: 5, + Name: "TestChannelQueue", }, Workers: 0, - Name: "TestChannelQueue", }, &testData{}) assert.NoError(t, err) diff --git a/modules/queue/unique_queue_disk_channel.go b/modules/queue/unique_queue_disk_channel.go index 5ee1c396fc6a..6ab03094ba78 100644 --- a/modules/queue/unique_queue_disk_channel.go +++ b/modules/queue/unique_queue_disk_channel.go @@ -6,6 +6,7 @@ package queue import ( "context" + "runtime/pprof" "sync" "time" @@ -72,9 +73,9 @@ func NewPersistableChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interfac BoostTimeout: config.BoostTimeout, BoostWorkers: config.BoostWorkers, MaxWorkers: config.MaxWorkers, + Name: config.Name + "-channel", }, Workers: config.Workers, - Name: config.Name + "-channel", }, exemplar) if err != nil { return nil, err @@ -90,9 +91,9 @@ func NewPersistableChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interfac BoostTimeout: 5 * time.Minute, BoostWorkers: 1, MaxWorkers: 5, + Name: config.Name + "-level", }, Workers: 0, - Name: config.Name + "-level", }, DataDir: config.DataDir, } @@ -183,6 +184,7 @@ func (q *PersistableChannelUniqueQueue) Has(data Data) (bool, error) { // Run starts to run the queue func (q *PersistableChannelUniqueQueue) Run(atShutdown, atTerminate func(func())) { + pprof.SetGoroutineLabels(q.channelQueue.baseCtx) log.Debug("PersistableChannelUniqueQueue: %s Starting", q.delayedStarter.name) q.lock.Lock() @@ -301,6 +303,7 @@ func (q *PersistableChannelUniqueQueue) Terminate() { if q.internal != nil { q.internal.(*LevelUniqueQueue).Terminate() } + q.channelQueue.baseCtxFinished() log.Debug("PersistableChannelUniqueQueue: %s Terminated", q.delayedStarter.name) } diff --git a/modules/queue/workerpool.go b/modules/queue/workerpool.go index 5f6ec1871019..2d8504598a1a 100644 --- a/modules/queue/workerpool.go +++ b/modules/queue/workerpool.go @@ -6,11 +6,14 @@ package queue import ( "context" + "fmt" + "runtime/pprof" "sync" "sync/atomic" "time" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/util" ) @@ -22,6 +25,7 @@ type WorkerPool struct { lock sync.Mutex baseCtx context.Context baseCtxCancel context.CancelFunc + baseCtxFinished process.FinishedFunc paused chan struct{} resumed chan struct{} cond *sync.Cond @@ -44,6 +48,7 @@ var ( // WorkerPoolConfiguration is the basic configuration for a WorkerPool type WorkerPoolConfiguration struct { + Name string QueueLength int BatchLength int BlockTimeout time.Duration @@ -54,12 +59,13 @@ type WorkerPoolConfiguration struct { // NewWorkerPool creates a new worker pool func NewWorkerPool(handle HandlerFunc, config WorkerPoolConfiguration) *WorkerPool { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel, finished := process.GetManager().AddTypedContext(context.Background(), fmt.Sprintf("Queue: %s", config.Name), process.SystemProcessType, false) dataChan := make(chan Data, config.QueueLength) pool := &WorkerPool{ baseCtx: ctx, baseCtxCancel: cancel, + baseCtxFinished: finished, batchLength: config.BatchLength, dataChan: dataChan, resumed: closedChan, @@ -299,6 +305,7 @@ func (p *WorkerPool) addWorkers(ctx context.Context, cancel context.CancelFunc, p.numberOfWorkers++ p.lock.Unlock() go func() { + pprof.SetGoroutineLabels(ctx) p.doWork(ctx) p.lock.Lock() @@ -476,6 +483,7 @@ func (p *WorkerPool) FlushWithContext(ctx context.Context) error { } func (p *WorkerPool) doWork(ctx context.Context) { + pprof.SetGoroutineLabels(ctx) delay := time.Millisecond * 300 // Create a common timer - we will use this elsewhere diff --git a/modules/repository/create.go b/modules/repository/create.go index d63f501ec4e2..33039d77c28c 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -111,9 +111,9 @@ func CreateRepository(doer, u *user_model.User, opts models.CreateRepoOptions) ( return fmt.Errorf("checkDaemonExportOK: %v", err) } - if stdout, err := git.NewCommand(ctx, "update-server-info"). + if stdout, _, err := git.NewCommand(ctx, "update-server-info"). SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { + RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) rollbackRepo = repo rollbackRepo.OwnerID = u.ID diff --git a/modules/repository/generate.go b/modules/repository/generate.go index 8f0b3c16fe10..1436d764f0dc 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -177,9 +177,9 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } repoPath := repo.RepoPath() - if stdout, err := git.NewCommand(ctx, "remote", "add", "origin", repoPath). + if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin", repoPath). SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)). - RunInDirWithEnv(tmpDir, env); err != nil { + RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git remote add: %v", err) } @@ -292,9 +292,9 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ return generateRepo, fmt.Errorf("checkDaemonExportOK: %v", err) } - if stdout, err := git.NewCommand(ctx, "update-server-info"). + if stdout, _, err := git.NewCommand(ctx, "update-server-info"). SetDescription(fmt.Sprintf("GenerateRepository(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { + RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %v", err) } diff --git a/modules/repository/init.go b/modules/repository/init.go index 3263beadcbee..d5a67df5d150 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -229,9 +229,9 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, ) // Clone to temporary path and do the init commit. - if stdout, err := git.NewCommand(ctx, "clone", repoPath, tmpDir). + if stdout, _, err := git.NewCommand(ctx, "clone", repoPath, tmpDir). SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)). - RunInDirWithEnv("", env); err != nil { + RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil { log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git clone: %v", err) } @@ -306,9 +306,9 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi committerName := sig.Name committerEmail := sig.Email - if stdout, err := git.NewCommand(ctx, "add", "--all"). + if stdout, _, err := git.NewCommand(ctx, "add", "--all"). SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). - RunInDir(tmpPath); err != nil { + RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil { log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git add --all: %v", err) } @@ -343,9 +343,9 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi "GIT_COMMITTER_EMAIL="+committerEmail, ) - if stdout, err := git.NewCommand(ctx, args...). + if stdout, _, err := git.NewCommand(ctx, args...). SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). - RunInDirWithEnv(tmpPath, env); err != nil { + RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil { log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err) return fmt.Errorf("git commit: %v", err) } @@ -354,9 +354,9 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi defaultBranch = setting.Repository.DefaultBranch } - if stdout, err := git.NewCommand(ctx, "push", "origin", "HEAD:"+defaultBranch). + if stdout, _, err := git.NewCommand(ctx, "push", "origin", "HEAD:"+defaultBranch). SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). - RunInDirWithEnv(tmpPath, models.InternalPushingEnvironment(u, repo)); err != nil { + RunStdString(&git.RunOpts{Dir: tmpPath, Env: models.InternalPushingEnvironment(u, repo)}); err != nil { log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git push: %v", err) } diff --git a/modules/repository/push.go b/modules/repository/push.go index aa94a3e2429e..e1dbd5d2fcfb 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -104,8 +104,8 @@ func IsForcePush(ctx context.Context, opts *PushUpdateOptions) (bool, error) { return false, nil } - output, err := git.NewCommand(ctx, "rev-list", "--max-count=1", opts.OldCommitID, "^"+opts.NewCommitID). - RunInDir(repo_model.RepoPath(opts.RepoUserName, opts.RepoName)) + output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1", opts.OldCommitID, "^"+opts.NewCommitID). + RunStdString(&git.RunOpts{Dir: repo_model.RepoPath(opts.RepoUserName, opts.RepoName)}) if err != nil { return false, err } else if len(output) > 0 { diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 3ed48134c3ca..0dffa322d056 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -119,9 +119,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, return repo, fmt.Errorf("checkDaemonExportOK: %v", err) } - if stdout, err := git.NewCommand(ctx, "update-server-info"). + if stdout, _, err := git.NewCommand(ctx, "update-server-info"). SetDescription(fmt.Sprintf("MigrateRepositoryGitData(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { + RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %v", err) } @@ -150,6 +150,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } if !opts.Releases { + // note: this will greatly improve release (tag) sync + // for pull-mirrors with many tags + repo.IsMirror = opts.Mirror if err = SyncReleasesWithTags(repo, gitRepo); err != nil { log.Error("Failed to synchronize tags to releases for repository: %v", err) } @@ -238,7 +241,7 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo } } - _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunInDir(repoPath) + _, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return repo, fmt.Errorf("CleanUpMigrateInfo: %v", err) } @@ -254,6 +257,14 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo // SyncReleasesWithTags synchronizes release table with repository tags func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) error { + log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) + + // optimized procedure for pull-mirrors which saves a lot of time (in + // particular for repos with many tags). + if repo.IsMirror { + return pullMirrorReleaseSync(repo, gitRepo) + } + existingRelTags := make(map[string]struct{}) opts := models.FindReleasesOptions{ IncludeDrafts: true, @@ -450,3 +461,52 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re return nil } + +// pullMirrorReleaseSync is a pull-mirror specific tag<->release table +// synchronization which overwrites all Releases from the repository tags. This +// can be relied on since a pull-mirror is always identical to its +// upstream. Hence, after each sync we want the pull-mirror release set to be +// identical to the upstream tag set. This is much more efficient for +// repositories like https://github.com/vim/vim (with over 13000 tags). +func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) error { + log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) + tags, numTags, err := gitRepo.GetTagInfos(0, 0) + if err != nil { + return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) + } + err = db.WithTx(func(ctx context.Context) error { + // + // clear out existing releases + // + if _, err := db.DeleteByBean(ctx, &models.Release{RepoID: repo.ID}); err != nil { + return fmt.Errorf("unable to clear releases for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) + } + // + // make release set identical to upstream tags + // + for _, tag := range tags { + release := models.Release{ + RepoID: repo.ID, + TagName: tag.Name, + LowerTagName: strings.ToLower(tag.Name), + Sha1: tag.Object.String(), + // NOTE: ignored, since NumCommits are unused + // for pull-mirrors (only relevant when + // displaying releases, IsTag: false) + NumCommits: -1, + CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()), + IsTag: true, + } + if err := db.Insert(ctx, release); err != nil { + return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) + } + } + return nil + }) + if err != nil { + return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) + } + + log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags) + return nil +} diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 1a92edb79514..44ed431c9309 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -23,7 +23,9 @@ import ( "syscall" asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -317,7 +319,11 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) { } } - go listen(&srv) + go func() { + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Service: Built-in SSH server", process.SystemProcessType, true) + defer finished() + listen(&srv) + }() } // GenKeyPair make a pair of public and private keys for SSH access. diff --git a/modules/web/routing/logger_manager.go b/modules/web/routing/logger_manager.go index cc434c338da5..7715b0b5d37f 100644 --- a/modules/web/routing/logger_manager.go +++ b/modules/web/routing/logger_manager.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/process" ) // Event indicates when the printer is triggered @@ -40,7 +41,9 @@ type requestRecordsManager struct { } func (manager *requestRecordsManager) startSlowQueryDetector(threshold time.Duration) { - go graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { + go graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) { + ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: SlowQueryDetector", process.SystemProcessType, true) + defer finished() // This go-routine checks all active requests every second. // If a request has been running for a long time (eg: /user/events), we also print a log with "still-executing" message // After the "still-executing" log is printed, the record will be removed from the map to prevent from duplicated logs in future @@ -49,7 +52,7 @@ func (manager *requestRecordsManager) startSlowQueryDetector(threshold time.Dura t := time.NewTicker(time.Second) for { select { - case <-baseCtx.Done(): + case <-ctx.Done(): return case <-t.C: now := time.Now() diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini index a2bf0d0062d3..2887a9b7f263 100644 --- a/options/locale/locale_bg-BG.ini +++ b/options/locale/locale_bg-BG.ini @@ -1080,6 +1080,7 @@ repos.size=Големина + auths.name=Име auths.type=Тип auths.enabled=Активно @@ -1258,3 +1259,5 @@ mark_all_as_read=Бележа всичко като прочетено [units] +[packages] + diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 7f93b49078f4..778326b205a1 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -2274,6 +2274,7 @@ repos.forks=Rozštěpení repos.issues=Úkoly repos.size=Velikost + defaulthooks=Výchozí webové háčky defaulthooks.desc=Webové háčky automaticky vytvářejí HTTP POST dotazy na server při určitých Gitea událostech. Webové háčky definované zde jsou výchozí a budou zkopírovány do všech nových repozitářů. Přečtěte si více v průvodci webovými háčky. defaulthooks.add_webhook=Přidat výchozí webový háček @@ -2661,3 +2662,5 @@ error.probable_bad_default_signature=VAROVÁNÍ! Ačkoli výchozí klíč má to error.no_unit_allowed_repo=Nejste oprávněni přistupovat k žádné části tohoto repozitáře. error.unit_not_allowed=Nejste oprávněni přistupovat k této části repozitáře. +[packages] + diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f64a3b83e140..a416aabc47a2 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -2513,6 +2513,7 @@ repos.forks=Forks repos.issues=Issues repos.size=Größe + defaulthooks=Standard-Webhooks defaulthooks.desc=Webhooks senden automatisch eine HTTP POST Anfrage an einen Server, wenn bestimmte Gitea Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der Webhooks Anleitung (auf englisch). defaulthooks.add_webhook=Standard-Webhook hinzufügen @@ -2937,3 +2938,5 @@ unit=Einheit error.no_unit_allowed_repo=Du hast keine Berechtigung, um auf irgendeinen Bereich dieses Repositories zuzugreifen. error.unit_not_allowed=Du hast keine Berechtigung, um auf diesen Repository-Bereich zuzugreifen. +[packages] + diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 9fa73e374b0f..4b906a290c1f 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -2515,6 +2515,7 @@ repos.forks=Forks repos.issues=Ζητήματα repos.size=Μέγεθος + defaulthooks=Προεπιλεγμένα Webhooks defaulthooks.desc=Τα Webhooks κάνουν αυτόματα αιτήσεις HTTP POST σε ένα διακομιστή όταν συμβαίνουν ορισμένα γεγονότα στο Gitea. Τα Webhooks που ορίζονται εδώ είναι προεπιλογή και θα αντιγραφούν σε όλα τα νέα αποθετήρια. Διαβάστε περισσότερα στον οδηγό webhooks. defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook @@ -2946,3 +2947,5 @@ unit=Μονάδα error.no_unit_allowed_repo=Δεν σας επιτρέπεται να έχετε πρόσβαση σε οποιαδήποτε ενότητα αυτού του αποθετηρίου. error.unit_not_allowed=Δεν σας επιτρέπεται να έχετε πρόσβαση σε αυτήν την ενότητα αποθετηρίου. +[packages] + diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cdf1688e75ff..f6b9d4700838 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2831,6 +2831,8 @@ monitor.next = Next Time monitor.previous = Previous Time monitor.execute_times = Executions monitor.process = Running Processes +monitor.stacktrace = Stacktraces +monitor.goroutines = %d Goroutines monitor.desc = Description monitor.start = Start Time monitor.execute_time = Execution Time diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 7ceac65e630a..72f37480d454 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -2493,6 +2493,7 @@ repos.forks=Forks repos.issues=Incidencias repos.size=Tamaño + defaulthooks=Webhooks por defecto defaulthooks.desc=Los Webhooks automáticamente hacen peticiones HTTP POST a un servidor cuando ciertos eventos de Gitea se activan. Los ganchos definidos aquí son predeterminados y serán copiados en todos los nuevos repositorios. Leer más en la guía webhooks. defaulthooks.add_webhook=Añadir Webhook por defecto @@ -2921,3 +2922,5 @@ unit=Unidad error.no_unit_allowed_repo=No tiene permisos para acceder a ninguna sección de este repositorio. error.unit_not_allowed=No tiene permisos para acceder a esta sección del repositorio. +[packages] + diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 2237efc0b8b6..38f24b1b23ea 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -2398,6 +2398,7 @@ repos.forks=انشعاب‌ها repos.issues=مسائل repos.size=اندازه + defaulthooks=وب هوک های پیش فرض defaulthooks.desc=هنگامی که برخی رویدادهای Gitea فعال می شوند، Webhook ها به طور خودکار درخواست های HTTP POST را به سرور ارسال می کنند. هوک های تعریف شده در اینجا پیش فرض هستند و در تمام مخازن جدید کپی می شوند. در راهنمای هوک‌های وب بیشتر بخوانید. defaulthooks.add_webhook=اضافه کردن Webhook پیش فرض @@ -2815,3 +2816,5 @@ error.probable_bad_default_signature=هشدار! اگرچه اینجا یک کل error.no_unit_allowed_repo=شما اجازه دسترسی به هیچ قسمت از این مخزن را ندارید. error.unit_not_allowed=شما اجازه دسترسی به این قسمت مخزن را ندارید. +[packages] + diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 3d57ddcb2ee1..4f624518cf63 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -1136,6 +1136,7 @@ repos.size=Koko + auths.new=Lisää todennuslähde auths.name=Nimi auths.type=Tyyppi @@ -1326,3 +1327,5 @@ mark_all_as_read=Merkitse kaikki luetuiksi [units] +[packages] + diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 487ae0e81f9f..b83924ae7be8 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -2216,6 +2216,7 @@ repos.issues=Tickets repos.size=Taille + systemhooks=Rappels système systemhooks.desc=Les Webhooks font automatiquement des requêtes HTTP POST à un serveur lorsque certains événements Gitea se déclenchent. Les Webhooks définis ici agiront sur tous les dépots du système, donc veuillez prendre en compte les implications en termes de performances que cela peut avoir. Lire la suite dans le guide des Webhooks. systemhooks.add_webhook=Ajouter un rappel système @@ -2588,3 +2589,5 @@ error.probable_bad_default_signature=AVERTISSEMENT ! Bien que la clé par défau error.no_unit_allowed_repo=Vous n'êtes pas autorisé à accéder à n'importe quelle section de ce dépôt. error.unit_not_allowed=Vous n'êtes pas autorisé à accéder à cette section du dépôt. +[packages] + diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 779d0be8cdc7..61c21c688d6a 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1494,6 +1494,7 @@ repos.size=Méret + auths.new=Hitelesítési forrás hozzáadása auths.name=Név auths.type=Típus @@ -1771,3 +1772,5 @@ error.not_signed_commit=Nem aláírt commit [units] +[packages] + diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 5a3780e3cb4e..b9f1e46e397c 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -1141,6 +1141,7 @@ repos.size=Ukuran + auths.name=Nama auths.type=Jenis auths.enabled=Aktif @@ -1394,3 +1395,5 @@ error.not_signed_commit=Bukan melakukan yang ditandatangani error.no_unit_allowed_repo=Anda tidak diijinkan untuk melihat semua unit dari repositori ini. error.unit_not_allowed=Anda tidak diizinkan untuk mengunjungi unit repositori ini. +[packages] + diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 91c4f4fa5b82..0550290fe34b 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -2034,6 +2034,7 @@ repos.issues=Problemi repos.size=Dimensione + systemhooks=Webhooks di Sistema systemhooks.add_webhook=Aggiungi Webhook di Sistema systemhooks.update_webhook=Aggiorna Webhook di Sistema @@ -2402,3 +2403,5 @@ error.probable_bad_default_signature=ATTENZIONE! Anche se la chiave predefinita error.no_unit_allowed_repo=Non possiedi il permesso di accedere ad alcuna sezione di questo repository. error.unit_not_allowed=Non possiedi il permesso di accedere a questa sezione di repository. +[packages] + diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index e8a018ed2438..400af5a7f47e 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1418,6 +1418,8 @@ issues.due_date_remove=が期日 %s を削除 %s issues.due_date_overdue=期日は過ぎています issues.due_date_invalid=期日が正しくないか範囲を超えています。 'yyyy-mm-dd' の形式で入力してください。 issues.dependency.title=依存関係 +issues.dependency.issue_no_dependencies=依存関係が設定されていません。 +issues.dependency.pr_no_dependencies=依存関係が設定されていません。 issues.dependency.add=依存関係を追加... issues.dependency.cancel=キャンセル issues.dependency.remove=削除 @@ -2461,6 +2463,7 @@ dashboard.gc_times=GC実行回数 dashboard.delete_old_actions=データベースから古い操作履歴をすべて削除 dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。 dashboard.update_checker=更新チェック +dashboard.delete_old_system_notices=データベースから古いシステム通知をすべて削除 users.user_manage_panel=ユーザーアカウント管理 users.new_account=ユーザーアカウントを作成 @@ -2543,6 +2546,7 @@ repos.forks=フォーク repos.issues=イシュー repos.size=サイズ + defaulthooks=デフォルトWebhook defaulthooks.desc=Webhookは、特定のGiteaイベントトリガーが発生した際に、自動的にHTTP POSTリクエストをサーバーへ送信するものです。 ここで定義されたWebhookはデフォルトとなり、全ての新規リポジトリにコピーされます。 詳しくはwebhooks guideをご覧下さい。 defaulthooks.add_webhook=デフォルトWebhookの追加 @@ -2813,6 +2817,7 @@ monitor.process=実行中のプロセス monitor.desc=説明 monitor.start=開始日時 monitor.execute_time=実行時間 +monitor.last_execution_result=結果 monitor.process.cancel=処理をキャンセル monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります monitor.process.cancel_notices=キャンセル: %s? @@ -2979,3 +2984,5 @@ unit=ユニット error.no_unit_allowed_repo=このリポジトリのどのセクションにもアクセスが許可されていません。 error.unit_not_allowed=このセクションへのアクセスが許可されていません。 +[packages] + diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index fac82ddf8d41..b9d878904fa8 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -1336,6 +1336,7 @@ repos.size=크기 + auths.auth_manage_panel=인증 소스 관리 auths.new=인증 소스 추가 auths.name=이름 @@ -1584,3 +1585,5 @@ error.not_signed_commit=서명되지 않은 커밋입니다. error.no_unit_allowed_repo=이 저장소의 어떤 섹션에도 접근할 수 없습니다. error.unit_not_allowed=이 저장소 섹션에 접근할 수 없습니다. +[packages] + diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 94a8b99131dc..6304ebab354f 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -2481,6 +2481,7 @@ repos.forks=Atdalītie repos.issues=Problēmas repos.size=Izmērs + defaulthooks=Noklusētie tīmekļa āķi defaulthooks.desc=Tīmekļa āķi ļauj paziņot ārējiem servisiem par noteiktiem notikumiem, kas notiek Gitea. Kad iestāsies kāds notikums, katram ārējā servisa URL tiks nosūtīts POST pieprasījums. Šeit izveidotie tīmekļa āķi tiks pievienoti visiem jaunajajiem repozitorijiem. Lai uzzinātu sīkāk skatieties tīmekļa āķu rokasgrāmatā. defaulthooks.add_webhook=Pievienot noklusēto tīmekļa āķi @@ -2906,3 +2907,5 @@ unit=Vienība error.no_unit_allowed_repo=Jums nav tiesību aplūkot nevienu šī repozitorija sadaļu. error.unit_not_allowed=Jums nav tiesību piekļūt šai repozitorija sadaļai. +[packages] + diff --git a/options/locale/locale_ml-IN.ini b/options/locale/locale_ml-IN.ini index c5392f15010c..6c42aff44895 100644 --- a/options/locale/locale_ml-IN.ini +++ b/options/locale/locale_ml-IN.ini @@ -782,6 +782,7 @@ repos.issues=ഇഷ്യൂകള്‍ + [action] @@ -796,3 +797,5 @@ repos.issues=ഇഷ്യൂകള്‍ [units] +[packages] + diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 9023af3038c2..3553b11e090f 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -2098,6 +2098,7 @@ repos.issues=Kwesties repos.size=Grootte + systemhooks=Systeem webhooks systemhooks.add_webhook=Systeem Webhook toevoegen systemhooks.update_webhook=Systeem-webhook bijwerken @@ -2450,3 +2451,5 @@ error.probable_bad_default_signature=WAARSCHUWING! Hoewel de standaard sleutel d error.no_unit_allowed_repo=U heeft geen toegang tot een enkele sectie van deze repository. error.unit_not_allowed=U heeft geen toegang tot deze sectie van de repository. +[packages] + diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 34dab43f936f..16a346ce6811 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -2331,6 +2331,7 @@ repos.forks=Forki repos.issues=Zgłoszenia repos.size=Rozmiar + defaulthooks=Domyślne Webhooki defaulthooks.desc=Webhooki automatycznie wysyłają zapytania HTTP POST na serwer, gdy niektóre zdarzenia Gitea je wyzwalają. Webhooki zdefiniowane tutaj są domyślne i zostaną skopiowane do wszystkich nowych repozytoriów. Przeczytaj więcej w przewodniku webhooków. defaulthooks.add_webhook=Dodaj domyślny Webhook @@ -2711,3 +2712,5 @@ error.probable_bad_default_signature=OSTRZEŻENIE! Pomimo, że domyślny klucz p error.no_unit_allowed_repo=Nie masz uprawnień do żadnej sekcji tego repozytorium. error.unit_not_allowed=Nie masz uprawnień do tej sekcji repozytorium. +[packages] + diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index ccb077e32f81..553c617ad66a 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -2108,6 +2108,7 @@ repos.size=Tamanho + auths.auth_manage_panel=Gerenciamento de fonte de autenticação auths.new=Adicionar fonte de autenticação auths.name=Nome @@ -2500,3 +2501,5 @@ error.probable_bad_default_signature=AVISO! Embora a chave padrão tenha este ID error.no_unit_allowed_repo=Você não tem permissão para acessar nenhuma seção deste repositório. error.unit_not_allowed=Você não tem permissão para acessar esta seção do repositório. +[packages] + diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index c2f7ef79aab5..1db1ea09cafa 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -2545,6 +2545,7 @@ repos.forks=Derivações repos.issues=Questões repos.size=Tamanho + defaulthooks=Automatismos web padrão defaulthooks.desc=Os automatismos web fazem pedidos HTTP POST automaticamente a um servidor quando são despoletados determinados eventos do Gitea. Os automatismos web definidos aqui são os padrões e serão copiados para todos os novos repositórios. Leia mais no guia de automatismos web. defaulthooks.add_webhook=Adicionar automatismo web padrão @@ -2982,3 +2983,5 @@ unit=Unidade error.no_unit_allowed_repo=Não tem permissão para aceder a nenhuma parte deste repositório. error.unit_not_allowed=Não tem permissão para aceder a esta parte do repositório. +[packages] + diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 3bee47c69000..7ca953d6a47b 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -2428,6 +2428,7 @@ repos.forks=Форки repos.issues=Задачи repos.size=Размер + defaulthooks=Стандартные Веб-хуки defaulthooks.desc=Вебхуки автоматически делают HTTP-POST запросы на сервер, когда вызываются определенные события Gitea. Вебхуки, определённые здесь, по умолчанию и будут скопированы во все новые репозитории. Подробнее читайте в руководстве по вебхукам. defaulthooks.add_webhook=Добавить стандартный Веб-хук @@ -2844,3 +2845,5 @@ error.probable_bad_default_signature=ПРЕДУПРЕЖДЕНИЕ! Хотя кл error.no_unit_allowed_repo=У вас нет доступа ни к одному разделу этого репозитория. error.unit_not_allowed=У вас нет доступа к этому разделу репозитория. +[packages] + diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 29f13ba209da..115b09fb3c3e 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -2333,6 +2333,7 @@ repos.forks=දෙබලක repos.issues=ගැටළු repos.size=ප්‍රමාණය + defaulthooks=පෙරනිමි වෙබ් කොකු defaulthooks.desc=ඇතැම් Gitea සිදුවීම් අවුලුවාලන විට වෙබ් හූක්ස් ස්වයංක්රීයව සේවාදායකයකට HTTP පෝස්ට් ඉල්ලීම් කරයි. මෙහි අර්ථ දක්වා ඇති වෙබ්කොකු පැහැර හැරීම් වන අතර සියලු නව ගබඩාවන් වෙත පිටපත් කරනු ලැබේ. තව දුරටත් කියවන්න වෙබ් කොකු මාර්ගෝපදේශය. defaulthooks.add_webhook=පෙරනිමි වෙබ් හූක් එකතු කරන්න @@ -2725,3 +2726,5 @@ no_read=කියවූ දැනුම්දීම් නැත. [units] +[packages] + diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index a7efb8e99a7e..fdca7da18d03 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -1842,6 +1842,7 @@ repos.issues=Ärenden repos.size=Storlek + systemhooks=Systemets webbhooks auths.auth_manage_panel=Hantering av autentiseringkälla @@ -2138,3 +2139,5 @@ error.probable_bad_default_signature=VARNING! Även om standard-nyckeln har dett error.no_unit_allowed_repo=Du tillåts inte åtkomst till någon del av denna utvecklingskatalog. error.unit_not_allowed=Du har inte åtkomst till denna del av utvecklingskatalogen. +[packages] + diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 298af27564de..9eb3022d2736 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -2333,6 +2333,7 @@ repos.forks=Çatallar repos.issues=Konular repos.size=Boyut + defaulthooks=Varsayılan Web İstemcileri defaulthooks.desc=Web İstemcileri, belirli Gitea olayları tetiklendiğinde otomatik olarak HTTP POST isteklerini sunucuya yapar. Burada tanımlanan Web İstemcileri varsayılandır ve tüm yeni depolara kopyalanır. web istemcileri kılavuzunda daha fazla bilgi edinin. defaulthooks.add_webhook=Varsayılan Web İstemcisi Ekle @@ -2726,3 +2727,5 @@ error.probable_bad_default_signature=UYARI! Varsayılan anahtarın bu kimliği o error.no_unit_allowed_repo=Bu deponun hiçbir bölümüne erişme izniniz yok. error.unit_not_allowed=Bu depo bölümüne erişme izniniz yok. +[packages] + diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index ac8890e66186..7c9a3b0ce620 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -2406,6 +2406,7 @@ repos.forks=Форки repos.issues=Задачі repos.size=Розмір + defaulthooks=Веб-хуки за замовчуванням defaulthooks.desc=Веб-хуки автоматично створюють HTTP POST-запити до сервера, коли виконуються певні події Gitea. Визначені тут веб-хуки є типовими і копіюються у всі нові сховища. Детальніше читайте в інструкції по використанню web-хуків. defaulthooks.add_webhook=Додати веб-хук за замовчуванням @@ -2821,3 +2822,5 @@ error.probable_bad_default_signature=УВАГА! Хоча типовий клю error.no_unit_allowed_repo=У вас немає доступу до жодного розділу цього репозитория. error.unit_not_allowed=У вас немає доступу до жодного розділу цього репозитория. +[packages] + diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 6034730f99eb..2b4936bacc41 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -2537,6 +2537,7 @@ repos.forks=派生数 repos.issues=工单数 repos.size=大小 + defaulthooks=默认Web钩子 defaulthooks.desc=当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子是默认配置,将被复制到所有新的仓库中。详情请访问 Web 钩子指南。 defaulthooks.add_webhook=添加默认Web 钩子 @@ -2973,3 +2974,5 @@ unit=单元 error.no_unit_allowed_repo=您没有被允许访问此仓库的任何单元。 error.unit_not_allowed=您没有权限访问此仓库单元 +[packages] + diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 643c0c386f82..c2e9cbe7c4f2 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -677,6 +677,7 @@ repos.size=大小 + auths.name=認證名稱 auths.type=認證類型 auths.enabled=已啟用 @@ -873,3 +874,5 @@ error.not_signed_commit=未簽名的提交 [units] +[packages] + diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 1882371ffce9..ffb0cf1fbc83 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -2546,6 +2546,7 @@ repos.forks=Fork 數 repos.issues=問題數 repos.size=大小 + defaulthooks=預設 Webhook defaulthooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。這裡所定義的 Webhook 是預設的,並且會複製到所有新儲存庫。在 Webhook 指南閱讀更多內容。 defaulthooks.add_webhook=新增預設 Webhook @@ -2983,3 +2984,5 @@ unit=單元 error.no_unit_allowed_repo=您未被允許存取此儲存庫的任何區域。 error.unit_not_allowed=您未被允許訪問此儲存庫區域 +[packages] + diff --git a/package-lock.json b/package-lock.json index 460062939862..30412800bfd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "license": "MIT", "dependencies": { "@claviska/jquery-minicolors": "2.3.6", - "@primer/octicons": "16.3.1", + "@primer/octicons": "17.0.0", "add-asset-webpack-plugin": "2.0.1", - "css-loader": "6.6.0", + "css-loader": "6.7.1", "dropzone": "6.0.0-beta.2", "easymde": "2.16.1", "esbuild-loader": "2.18.0", @@ -23,12 +23,12 @@ "less-loader": "10.2.0", "license-checker-webpack-plugin": "0.2.1", "mermaid": "8.14.0", - "mini-css-extract-plugin": "2.5.3", - "monaco-editor": "0.32.1", + "mini-css-extract-plugin": "2.6.0", + "monaco-editor": "0.33.0", "monaco-editor-webpack-plugin": "7.0.1", "pretty-ms": "7.0.1", - "sortablejs": "1.14.0", - "swagger-ui-dist": "4.5.2", + "sortablejs": "1.15.0", + "swagger-ui-dist": "4.10.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "2.6.14", @@ -36,27 +36,27 @@ "vue-calendar-heatmap": "0.8.4", "vue-loader": "15.9.8", "vue-template-compiler": "2.6.14", - "webpack": "5.69.1", + "webpack": "5.70.0", "webpack-cli": "4.9.2", - "workbox-routing": "6.5.0", - "workbox-strategies": "6.5.0", + "workbox-routing": "6.5.2", + "workbox-strategies": "6.5.2", "worker-loader": "3.0.8", "wrap-ansi": "8.0.1" }, "devDependencies": { - "eslint": "8.9.0", + "eslint": "8.12.0", "eslint-plugin-html": "6.2.0", "eslint-plugin-import": "2.25.4", - "eslint-plugin-unicorn": "41.0.0", + "eslint-plugin-unicorn": "41.0.1", "eslint-plugin-vue": "8.5.0", "jest": "27.5.1", "jest-extended": "2.0.0", "jest-raw-loader": "1.0.1", "postcss-less": "6.0.0", - "stylelint": "14.5.3", + "stylelint": "14.6.1", "stylelint-config-standard": "25.0.0", "svgo": "2.8.0", - "updates": "13.0.2" + "updates": "13.0.4" }, "engines": { "node": ">= 12.17.0" @@ -685,16 +685,16 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.3.1", "globals": "^13.9.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.0.4", @@ -704,15 +704,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", @@ -1157,9 +1148,9 @@ } }, "node_modules/@primer/octicons": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-16.3.1.tgz", - "integrity": "sha512-J3IlK0Ok88RQZVB//af7Lnl1Vw2buyyr5G3oEvK1wRSYTJi/E/HBm5JZUihmDAtm/unr85FC534DwA5e+4LR2w==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-17.0.0.tgz", + "integrity": "sha512-DiIjtous4XPuR2deTctD3/RVZy/vRzVYBgYYvHV313MmTfkbVP60qLH5txrT3/bYNvnb0poNDelLS6U0kqlvHA==", "dependencies": { "object-assign": "^4.1.1" } @@ -2434,12 +2425,12 @@ } }, "node_modules/css-loader": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.6.0.tgz", - "integrity": "sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.5", + "postcss": "^8.4.7", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -3247,9 +3238,9 @@ "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3539,9 +3530,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -4076,12 +4067,12 @@ } }, "node_modules/eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", + "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.1.0", + "@eslint/eslintrc": "^1.2.1", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -4232,9 +4223,9 @@ "dev": true }, "node_modules/eslint-plugin-unicorn": { - "version": "41.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-41.0.0.tgz", - "integrity": "sha512-xoJCaRc1uy5REg9DkVga1BkZV57jJxoqOcrU28QHZB89Lk5LdSqdVyTIt9JQVfHNKaiyJ7X+3iLlIn+VEHWEzA==", + "version": "41.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-41.0.1.tgz", + "integrity": "sha512-gF5vo2dIj0YdNMQ/IMegiBkQdQ22GBFFVpdkJP+0og3w7XD4ypea0xQVRv6iofkLVR2w0phAdikcnU01ybd4Ow==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.15.7", @@ -4787,9 +4778,9 @@ } }, "node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -6800,9 +6791,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", - "integrity": "sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz", + "integrity": "sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w==", "dependencies": { "schema-utils": "^4.0.0" }, @@ -6853,9 +6844,9 @@ "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" }, "node_modules/monaco-editor": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.32.1.tgz", - "integrity": "sha512-LUt2wsUvQmEi2tfTOK+tjAPvt7eQ+K5C4rZPr6SeuyzjAuAHrIvlUloTcOiGjZW3fn3a/jFQCONrEJbNOaCqbA==" + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "node_modules/monaco-editor-webpack-plugin": { "version": "7.0.1", @@ -6876,9 +6867,9 @@ "devOptional": true }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7364,20 +7355,26 @@ } }, "node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-less": { @@ -8119,9 +8116,9 @@ } }, "node_modules/sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==" }, "node_modules/source-list-map": { "version": "2.0.1", @@ -8368,16 +8365,16 @@ "dev": true }, "node_modules/stylelint": { - "version": "14.5.3", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.5.3.tgz", - "integrity": "sha512-omHETL+kGHR+fCXFK1SkZD/A+emCP9esggAdWEl8GPjTNeyRYj+H6uetRDcU+7E451zwWiUYGVAX+lApsAZgsQ==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.6.1.tgz", + "integrity": "sha512-FfNdvZUZdzh9KDQxDnO7Opp+prKh8OQVuSW8S13cBtxrooCbm6J6royhUeb++53WPMt04VB+ZbOz/QmzAijs6Q==", "dev": true, "dependencies": { "balanced-match": "^2.0.0", "colord": "^2.9.2", "cosmiconfig": "^7.0.1", "css-functions-list": "^3.0.1", - "debug": "^4.3.3", + "debug": "^4.3.4", "execall": "^2.0.0", "fast-glob": "^3.2.11", "fastest-levenshtein": "^1.0.12", @@ -8398,7 +8395,7 @@ "normalize-path": "^3.0.0", "normalize-selector": "^0.2.0", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.12", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", @@ -8549,9 +8546,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.5.2.tgz", - "integrity": "sha512-wV4w54eW9z+VKbYJBJfULfqO05otCbM9jwgRIkwRl9CrfTVKelDzyhhEvdUQkGUzro+Ir8TOZPiZgKIdIdolWQ==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.10.0.tgz", + "integrity": "sha512-+RBJA/beHLg0hO4rJZIhgUdxmZE7AaNfc11PCSzZdnzkmwSJv8Qg0HZbr7BQPQjkC6z4xVWq2h1itOPk1FQBrA==" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -8915,9 +8912,9 @@ } }, "node_modules/updates": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/updates/-/updates-13.0.2.tgz", - "integrity": "sha512-bTC+36YoHGzK8vdKHebToVYsa5XTHCBe7X41H39wUt1A9OK1GhoY7pGzkOfXgWVS6yvVK9BSWfqVg0VA98fahQ==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/updates/-/updates-13.0.4.tgz", + "integrity": "sha512-RgHZnmTlcoRdn2yA8FZUwlRj7ltEANZQvh3ISAoSZcxunIv2s5EpFnZh8jgU7DigtX4ogm4XSn0r5O4u+cF7sg==", "dev": true, "bin": { "updates": "bin/updates.js" @@ -9198,9 +9195,9 @@ } }, "node_modules/webpack": { - "version": "5.69.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", - "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -9211,7 +9208,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -9440,24 +9437,24 @@ } }, "node_modules/workbox-core": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.0.tgz", - "integrity": "sha512-5SPwNipUzYBhrneLVT02JFA0fw3LG82jFAN/G2NzxkIW10t4MVZuML2nU94bbkgjq25u0fkY8+4JXzMfHgxEWQ==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.2.tgz", + "integrity": "sha512-IlxLGQf+wJHCR+NM0UWqDh4xe/Gu6sg2i4tfZk6WIij34IVk9BdOQgi6WvqSHd879jbQIUgL2fBdJUJyAP5ypQ==" }, "node_modules/workbox-routing": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.0.tgz", - "integrity": "sha512-w1A9OVa/yYStu9ds0Dj+TC6zOAoskKlczf+wZI5mrM9nFCt/KOMQiFp1/41DMFPrrN/8KlZTS3Cel/Ttutw93Q==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.2.tgz", + "integrity": "sha512-nR1w5PjF6IVwo0SX3oE88LhmGFmTnqqU7zpGJQQPZiKJfEKgDENQIM9mh3L1ksdFd9Y3CZVkusopHfxQvit/BA==", "dependencies": { - "workbox-core": "6.5.0" + "workbox-core": "6.5.2" } }, "node_modules/workbox-strategies": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.0.tgz", - "integrity": "sha512-Ngnwo+tfGw4uKSlTz3h1fYKb/lCV7SDI/dtTb8VaJzRl0N9XssloDGYERBmF6BN/DV/x3bnRsshfobnKI/3z0g==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.2.tgz", + "integrity": "sha512-fgbwaUMxbG39BHjJIs2y2X21C0bmf1Oq3vMQxJ1hr6y5JMJIm8rvKCcf1EIdAr+PjKdSk4ddmgyBQ4oO8be4Uw==", "dependencies": { - "workbox-core": "6.5.0" + "workbox-core": "6.5.2" } }, "node_modules/worker-loader": { @@ -10145,28 +10142,20 @@ "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==" }, "@eslint/eslintrc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", - "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", + "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.3.1", "globals": "^13.9.0", - "ignore": "^4.0.6", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - } } }, "@humanwhocodes/config-array": { @@ -10518,9 +10507,9 @@ } }, "@primer/octicons": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-16.3.1.tgz", - "integrity": "sha512-J3IlK0Ok88RQZVB//af7Lnl1Vw2buyyr5G3oEvK1wRSYTJi/E/HBm5JZUihmDAtm/unr85FC534DwA5e+4LR2w==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-17.0.0.tgz", + "integrity": "sha512-DiIjtous4XPuR2deTctD3/RVZy/vRzVYBgYYvHV313MmTfkbVP60qLH5txrT3/bYNvnb0poNDelLS6U0kqlvHA==", "requires": { "object-assign": "^4.1.1" } @@ -11562,12 +11551,12 @@ "dev": true }, "css-loader": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.6.0.tgz", - "integrity": "sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.5", + "postcss": "^8.4.7", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -12221,9 +12210,9 @@ "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -12445,9 +12434,9 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -12743,12 +12732,12 @@ } }, "eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", + "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.1.0", + "@eslint/eslintrc": "^1.2.1", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -12884,9 +12873,9 @@ } }, "eslint-plugin-unicorn": { - "version": "41.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-41.0.0.tgz", - "integrity": "sha512-xoJCaRc1uy5REg9DkVga1BkZV57jJxoqOcrU28QHZB89Lk5LdSqdVyTIt9JQVfHNKaiyJ7X+3iLlIn+VEHWEzA==", + "version": "41.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-41.0.1.tgz", + "integrity": "sha512-gF5vo2dIj0YdNMQ/IMegiBkQdQ22GBFFVpdkJP+0og3w7XD4ypea0xQVRv6iofkLVR2w0phAdikcnU01ybd4Ow==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.15.7", @@ -13293,9 +13282,9 @@ } }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -14791,9 +14780,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz", - "integrity": "sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz", + "integrity": "sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w==", "requires": { "schema-utils": "^4.0.0" } @@ -14828,9 +14817,9 @@ "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==" }, "monaco-editor": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.32.1.tgz", - "integrity": "sha512-LUt2wsUvQmEi2tfTOK+tjAPvt7eQ+K5C4rZPr6SeuyzjAuAHrIvlUloTcOiGjZW3fn3a/jFQCONrEJbNOaCqbA==" + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.33.0.tgz", + "integrity": "sha512-VcRWPSLIUEgQJQIE0pVT8FcGBIgFoxz7jtqctE+IiCxWugD0DwgyQBcZBhdSrdMC84eumoqMZsGl2GTreOzwqw==" }, "monaco-editor-webpack-plugin": { "version": "7.0.1", @@ -14847,9 +14836,9 @@ "devOptional": true }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==" }, "natural-compare": { "version": "1.4.0", @@ -15204,11 +15193,11 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "requires": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -15732,9 +15721,9 @@ } }, "sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==" }, "source-list-map": { "version": "2.0.1", @@ -15938,16 +15927,16 @@ "dev": true }, "stylelint": { - "version": "14.5.3", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.5.3.tgz", - "integrity": "sha512-omHETL+kGHR+fCXFK1SkZD/A+emCP9esggAdWEl8GPjTNeyRYj+H6uetRDcU+7E451zwWiUYGVAX+lApsAZgsQ==", + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.6.1.tgz", + "integrity": "sha512-FfNdvZUZdzh9KDQxDnO7Opp+prKh8OQVuSW8S13cBtxrooCbm6J6royhUeb++53WPMt04VB+ZbOz/QmzAijs6Q==", "dev": true, "requires": { "balanced-match": "^2.0.0", "colord": "^2.9.2", "cosmiconfig": "^7.0.1", "css-functions-list": "^3.0.1", - "debug": "^4.3.3", + "debug": "^4.3.4", "execall": "^2.0.0", "fast-glob": "^3.2.11", "fastest-levenshtein": "^1.0.12", @@ -15968,7 +15957,7 @@ "normalize-path": "^3.0.0", "normalize-selector": "^0.2.0", "picocolors": "^1.0.0", - "postcss": "^8.4.6", + "postcss": "^8.4.12", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", @@ -16082,9 +16071,9 @@ } }, "swagger-ui-dist": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.5.2.tgz", - "integrity": "sha512-wV4w54eW9z+VKbYJBJfULfqO05otCbM9jwgRIkwRl9CrfTVKelDzyhhEvdUQkGUzro+Ir8TOZPiZgKIdIdolWQ==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.10.0.tgz", + "integrity": "sha512-+RBJA/beHLg0hO4rJZIhgUdxmZE7AaNfc11PCSzZdnzkmwSJv8Qg0HZbr7BQPQjkC6z4xVWq2h1itOPk1FQBrA==" }, "symbol-tree": { "version": "3.2.4", @@ -16353,9 +16342,9 @@ "dev": true }, "updates": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/updates/-/updates-13.0.2.tgz", - "integrity": "sha512-bTC+36YoHGzK8vdKHebToVYsa5XTHCBe7X41H39wUt1A9OK1GhoY7pGzkOfXgWVS6yvVK9BSWfqVg0VA98fahQ==", + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/updates/-/updates-13.0.4.tgz", + "integrity": "sha512-RgHZnmTlcoRdn2yA8FZUwlRj7ltEANZQvh3ISAoSZcxunIv2s5EpFnZh8jgU7DigtX4ogm4XSn0r5O4u+cF7sg==", "dev": true }, "uri-js": { @@ -16585,9 +16574,9 @@ "dev": true }, "webpack": { - "version": "5.69.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz", - "integrity": "sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", @@ -16598,7 +16587,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -16753,24 +16742,24 @@ "dev": true }, "workbox-core": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.0.tgz", - "integrity": "sha512-5SPwNipUzYBhrneLVT02JFA0fw3LG82jFAN/G2NzxkIW10t4MVZuML2nU94bbkgjq25u0fkY8+4JXzMfHgxEWQ==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.2.tgz", + "integrity": "sha512-IlxLGQf+wJHCR+NM0UWqDh4xe/Gu6sg2i4tfZk6WIij34IVk9BdOQgi6WvqSHd879jbQIUgL2fBdJUJyAP5ypQ==" }, "workbox-routing": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.0.tgz", - "integrity": "sha512-w1A9OVa/yYStu9ds0Dj+TC6zOAoskKlczf+wZI5mrM9nFCt/KOMQiFp1/41DMFPrrN/8KlZTS3Cel/Ttutw93Q==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.2.tgz", + "integrity": "sha512-nR1w5PjF6IVwo0SX3oE88LhmGFmTnqqU7zpGJQQPZiKJfEKgDENQIM9mh3L1ksdFd9Y3CZVkusopHfxQvit/BA==", "requires": { - "workbox-core": "6.5.0" + "workbox-core": "6.5.2" } }, "workbox-strategies": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.0.tgz", - "integrity": "sha512-Ngnwo+tfGw4uKSlTz3h1fYKb/lCV7SDI/dtTb8VaJzRl0N9XssloDGYERBmF6BN/DV/x3bnRsshfobnKI/3z0g==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.2.tgz", + "integrity": "sha512-fgbwaUMxbG39BHjJIs2y2X21C0bmf1Oq3vMQxJ1hr6y5JMJIm8rvKCcf1EIdAr+PjKdSk4ddmgyBQ4oO8be4Uw==", "requires": { - "workbox-core": "6.5.0" + "workbox-core": "6.5.2" } }, "worker-loader": { diff --git a/package.json b/package.json index b8ab231540e9..3848b7ec8f84 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ }, "dependencies": { "@claviska/jquery-minicolors": "2.3.6", - "@primer/octicons": "16.3.1", + "@primer/octicons": "17.0.0", "add-asset-webpack-plugin": "2.0.1", - "css-loader": "6.6.0", + "css-loader": "6.7.1", "dropzone": "6.0.0-beta.2", "easymde": "2.16.1", "esbuild-loader": "2.18.0", @@ -23,12 +23,12 @@ "less-loader": "10.2.0", "license-checker-webpack-plugin": "0.2.1", "mermaid": "8.14.0", - "mini-css-extract-plugin": "2.5.3", - "monaco-editor": "0.32.1", + "mini-css-extract-plugin": "2.6.0", + "monaco-editor": "0.33.0", "monaco-editor-webpack-plugin": "7.0.1", "pretty-ms": "7.0.1", - "sortablejs": "1.14.0", - "swagger-ui-dist": "4.5.2", + "sortablejs": "1.15.0", + "swagger-ui-dist": "4.10.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "2.6.14", @@ -36,27 +36,27 @@ "vue-calendar-heatmap": "0.8.4", "vue-loader": "15.9.8", "vue-template-compiler": "2.6.14", - "webpack": "5.69.1", + "webpack": "5.70.0", "webpack-cli": "4.9.2", - "workbox-routing": "6.5.0", - "workbox-strategies": "6.5.0", + "workbox-routing": "6.5.2", + "workbox-strategies": "6.5.2", "worker-loader": "3.0.8", "wrap-ansi": "8.0.1" }, "devDependencies": { - "eslint": "8.9.0", + "eslint": "8.12.0", "eslint-plugin-html": "6.2.0", "eslint-plugin-import": "2.25.4", - "eslint-plugin-unicorn": "41.0.0", + "eslint-plugin-unicorn": "41.0.1", "eslint-plugin-vue": "8.5.0", "jest": "27.5.1", "jest-extended": "2.0.0", "jest-raw-loader": "1.0.1", "postcss-less": "6.0.0", - "stylelint": "14.5.3", + "stylelint": "14.6.1", "stylelint-config-standard": "25.0.0", "svgo": "2.8.0", - "updates": "13.0.2" + "updates": "13.0.4" }, "browserslist": [ "defaults", diff --git a/public/img/svg/octicon-feed-forked.svg b/public/img/svg/octicon-feed-forked.svg new file mode 100644 index 000000000000..d93d48aaf76e --- /dev/null +++ b/public/img/svg/octicon-feed-forked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-feed-merged.svg b/public/img/svg/octicon-feed-merged.svg new file mode 100644 index 000000000000..93139310871e --- /dev/null +++ b/public/img/svg/octicon-feed-merged.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-feed-trophy.svg b/public/img/svg/octicon-feed-trophy.svg new file mode 100644 index 000000000000..b19b85afe51c --- /dev/null +++ b/public/img/svg/octicon-feed-trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-file-directory-fill.svg b/public/img/svg/octicon-file-directory-fill.svg new file mode 100644 index 000000000000..7ec313489bf6 --- /dev/null +++ b/public/img/svg/octicon-file-directory-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-file-directory.svg b/public/img/svg/octicon-file-directory.svg index d5fbf1efbeba..ca3345a4d303 100644 --- a/public/img/svg/octicon-file-directory.svg +++ b/public/img/svg/octicon-file-directory.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/img/svg/octicon-repo-locked.svg b/public/img/svg/octicon-repo-locked.svg new file mode 100644 index 000000000000..1da51110b21c --- /dev/null +++ b/public/img/svg/octicon-repo-locked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/octicon-trophy.svg b/public/img/svg/octicon-trophy.svg new file mode 100644 index 000000000000..57cf90ccb4c9 --- /dev/null +++ b/public/img/svg/octicon-trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index f0b1fafd26d3..08b6b421b084 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -367,7 +367,7 @@ func GetBlob(ctx *context.Context) { return } - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(blob.Blob.HashSHA256)) + s, _, err := packages_service.GetPackageFileStream(ctx, blob.File) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -506,7 +506,7 @@ func GetManifest(ctx *context.Context) { return } - s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(manifest.Blob.HashSHA256)) + s, _, err := packages_service.GetPackageFileStream(ctx, manifest.File) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index f3bc5861259c..3af7155fae6d 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -376,13 +376,7 @@ func DownloadSymbolFile(ctx *context.Context) { return } - pv, err := packages_model.GetVersionByID(ctx, pfs[0].VersionID) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - s, _, err := packages_service.GetPackageFileStream(ctx, pv, pfs[0]) + s, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) if err != nil { if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { apiError(ctx, http.StatusNotFound, err) diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index 38f4bc47523c..5aa73667968e 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -9,6 +9,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" @@ -67,12 +68,12 @@ func GetIssueCommentReactions(ctx *context.APIContext) { return } - reactions, _, err := models.FindCommentReactions(comment) + reactions, _, err := issues_model.FindCommentReactions(comment.IssueID, comment.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "FindCommentReactions", err) return } - _, err = reactions.LoadUsers(ctx.Repo.Repository) + _, err = reactions.LoadUsers(ctx, ctx.Repo.Repository) if err != nil { ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err) return @@ -197,11 +198,11 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp if isCreateType { // PostIssueCommentReaction part - reaction, err := models.CreateCommentReaction(ctx.Doer, comment.Issue, comment, form.Reaction) + reaction, err := issues_model.CreateCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction) if err != nil { - if models.IsErrForbiddenIssueReaction(err) { + if issues_model.IsErrForbiddenIssueReaction(err) { ctx.Error(http.StatusForbidden, err.Error(), err) - } else if models.IsErrReactionAlreadyExist(err) { + } else if issues_model.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ User: convert.ToUser(ctx.Doer, ctx.Doer), Reaction: reaction.Type, @@ -220,7 +221,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp }) } else { // DeleteIssueCommentReaction part - err = models.DeleteCommentReaction(ctx.Doer, comment.Issue, comment, form.Reaction) + err = issues_model.DeleteCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Reaction) if err != nil { ctx.Error(http.StatusInternalServerError, "DeleteCommentReaction", err) return @@ -285,12 +286,12 @@ func GetIssueReactions(ctx *context.APIContext) { return } - reactions, count, err := models.FindIssueReactions(issue, utils.GetListOptions(ctx)) + reactions, count, err := issues_model.FindIssueReactions(issue.ID, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err) return } - _, err = reactions.LoadUsers(ctx.Repo.Repository) + _, err = reactions.LoadUsers(ctx, ctx.Repo.Repository) if err != nil { ctx.Error(http.StatusInternalServerError, "ReactionList.LoadUsers()", err) return @@ -407,11 +408,11 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i if isCreateType { // PostIssueReaction part - reaction, err := models.CreateIssueReaction(ctx.Doer, issue, form.Reaction) + reaction, err := issues_model.CreateIssueReaction(ctx.Doer.ID, issue.ID, form.Reaction) if err != nil { - if models.IsErrForbiddenIssueReaction(err) { + if issues_model.IsErrForbiddenIssueReaction(err) { ctx.Error(http.StatusForbidden, err.Error(), err) - } else if models.IsErrReactionAlreadyExist(err) { + } else if issues_model.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ User: convert.ToUser(ctx.Doer, ctx.Doer), Reaction: reaction.Type, @@ -430,7 +431,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i }) } else { // DeleteIssueReaction part - err = models.DeleteIssueReaction(ctx.Doer, issue, form.Reaction) + err = issues_model.DeleteIssueReaction(ctx.Doer.ID, issue.ID, form.Reaction) if err != nil { ctx.Error(http.StatusInternalServerError, "DeleteIssueReaction", err) return diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 65544b80b3d4..bb922ddb23be 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -723,13 +723,12 @@ func MergePullRequest(ctx *context.APIContext) { return } - if err = pr.LoadHeadRepo(); err != nil { + if err := pr.LoadHeadRepo(); err != nil { ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) return } - err = pr.LoadIssue() - if err != nil { + if err := pr.LoadIssue(); err != nil { ctx.Error(http.StatusInternalServerError, "LoadIssue", err) return } @@ -743,29 +742,33 @@ func MergePullRequest(ctx *context.APIContext) { } } - if pr.Issue.IsClosed { - ctx.NotFound() - return - } + manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged + force := form.ForceMerge != nil && *form.ForceMerge - allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.Doer) - if err != nil { - ctx.Error(http.StatusInternalServerError, "IsUSerAllowedToMerge", err) - return - } - if !allowedMerge { - ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR") - return - } - - if pr.HasMerged { - ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "") + if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, force); err != nil { + if errors.Is(err, pull_service.ErrIsClosed) { + ctx.NotFound() + } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { + ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR") + } else if errors.Is(err, pull_service.ErrHasMerged) { + ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "") + } else if errors.Is(err, pull_service.ErrIsWorkInProgress) { + ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged") + } else if errors.Is(err, pull_service.ErrNotMergableState) { + ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later") + } else if models.IsErrDisallowedToMerge(err) { + ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err) + } else if asymkey_service.IsErrWontSign(err) { + ctx.Error(http.StatusMethodNotAllowed, fmt.Sprintf("Protected branch %s requires signed commits but this merge would not be signed", pr.BaseBranch), err) + } else { + ctx.InternalServerError(err) + } return } // handle manually-merged mark - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged { - if err = pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { + if manuallMerge { + if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) return @@ -781,63 +784,13 @@ func MergePullRequest(ctx *context.APIContext) { return } - if !pr.CanAutoMerge() { - ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later") + // set defaults to propagate needed fields + if err := form.SetDefaults(pr); err != nil { + ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) return } - if pr.IsWorkInProgress() { - ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged") - return - } - - if err := pull_service.CheckPRReadyToMerge(ctx, pr, false); err != nil { - if !models.IsErrNotAllowedToMerge(err) { - ctx.Error(http.StatusInternalServerError, "CheckPRReadyToMerge", err) - return - } - if form.ForceMerge != nil && *form.ForceMerge { - if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.Doer); err != nil { - ctx.Error(http.StatusInternalServerError, "IsUserRepoAdmin", err) - return - } else if !isRepoAdmin { - ctx.Error(http.StatusMethodNotAllowed, "Merge", "Only repository admin can merge if not all checks are ok (force merge)") - } - } else { - ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err) - return - } - } - - if _, err := pull_service.IsSignedIfRequired(ctx, pr, ctx.Doer); err != nil { - if !asymkey_service.IsErrWontSign(err) { - ctx.Error(http.StatusInternalServerError, "IsSignedIfRequired", err) - return - } - ctx.Error(http.StatusMethodNotAllowed, fmt.Sprintf("Protected branch %s requires signed commits but this merge would not be signed", pr.BaseBranch), err) - return - } - - if len(form.Do) == 0 { - form.Do = string(repo_model.MergeStyleMerge) - } - - message := strings.TrimSpace(form.MergeTitleField) - if len(message) == 0 { - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleMerge { - message = pr.GetDefaultMergeMessage() - } - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash { - message = pr.GetDefaultSquashMessage() - } - } - - form.MergeMessageField = strings.TrimSpace(form.MergeMessageField) - if len(form.MergeMessageField) > 0 { - message += "\n\n" + form.MergeMessageField - } - - if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil { + if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) return diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 591c4cf30e8e..6ea1e1dfbe5e 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -27,7 +27,7 @@ func Middlewares() []func(http.Handler) http.Handler { // First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL req.URL.RawPath = req.URL.EscapedPath() - ctx, _, finished := process.GetManager().AddContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI)) + ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true) defer finished() next.ServeHTTP(context.NewResponse(resp), req.WithContext(ctx)) }) diff --git a/routers/init.go b/routers/init.go index 489994789734..62a9e4002b90 100644 --- a/routers/init.go +++ b/routers/init.go @@ -141,7 +141,7 @@ func GlobalInitInstalled(ctx context.Context) { models.NewRepoContext() // Booting long running goroutines. - cron.NewContext() + cron.NewContext(ctx) issue_indexer.InitIssueIndexer(false) code_indexer.Init() mustInit(stats_indexer.Init) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index c6ea422287ed..763fe1cf1c55 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -179,7 +179,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN // 2. Disallow force pushes to protected branches if git.EmptySHA != oldCommitID { - output, err := git.NewCommand(ctx, "rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), ctx.env) + output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env}) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ @@ -340,7 +340,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN // Check all status checks and reviews are ok if err := pull_service.CheckPRReadyToMerge(ctx, pr, true); err != nil { - if models.IsErrNotAllowedToMerge(err) { + if models.IsErrDisallowedToMerge(err) { log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error()) ctx.JSON(http.StatusForbidden, private.Response{ Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()), diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index 683ed8d071c6..dfa6195b1971 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -45,11 +45,10 @@ func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env [] // This is safe as force pushes are already forbidden err = git.NewCommand(repo.Ctx, "rev-list", oldCommitID+"..."+newCommitID). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, + Run(&git.RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdoutWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) @@ -93,11 +92,10 @@ func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { hash := git.MustIDFromString(sha) return git.NewCommand(repo.Ctx, "cat-file", "commit", sha). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: repo.Path, - Stdout: stdoutWriter, + Run(&git.RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdoutWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { _ = stdoutWriter.Close() commit, err := git.CommitFromReader(repo, hash, stdoutReader) diff --git a/routers/private/internal.go b/routers/private/internal.go index 263180bd58e2..6ba87d67bf54 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -70,6 +70,7 @@ func Routes() *web.Route { r.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging) r.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger) r.Post("/manager/remove-logger/{group}/{name}", RemoveLogger) + r.Get("/manager/processes", Processes) r.Post("/mail/send", SendEmail) r.Post("/restore_repo", RestoreRepo) diff --git a/routers/private/manager_process.go b/routers/private/manager_process.go new file mode 100644 index 000000000000..f8932d61fae7 --- /dev/null +++ b/routers/private/manager_process.go @@ -0,0 +1,161 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package private + +import ( + "bytes" + "fmt" + "io" + "net/http" + "runtime" + "time" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + process_module "code.gitea.io/gitea/modules/process" +) + +// Processes prints out the processes +func Processes(ctx *context.PrivateContext) { + pid := ctx.FormString("cancel-pid") + if pid != "" { + process_module.GetManager().Cancel(process_module.IDType(pid)) + runtime.Gosched() + time.Sleep(100 * time.Millisecond) + } + + flat := ctx.FormBool("flat") + noSystem := ctx.FormBool("no-system") + stacktraces := ctx.FormBool("stacktraces") + json := ctx.FormBool("json") + + var processes []*process_module.Process + goroutineCount := int64(0) + processCount := 0 + var err error + if stacktraces { + processes, processCount, goroutineCount, err = process_module.GetManager().ProcessStacktraces(flat, noSystem) + if err != nil { + log.Error("Unable to get stacktrace: %v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Failed to get stacktraces: %v", err), + }) + return + } + } else { + processes, processCount = process_module.GetManager().Processes(flat, noSystem) + } + + if json { + ctx.JSON(http.StatusOK, map[string]interface{}{ + "TotalNumberOfGoroutines": goroutineCount, + "TotalNumberOfProcesses": processCount, + "Processes": processes, + }) + return + } + + ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") + ctx.Resp.WriteHeader(http.StatusOK) + + if err := writeProcesses(ctx.Resp, processes, processCount, goroutineCount, "", flat); err != nil { + log.Error("Unable to write out process stacktrace: %v", err) + if !ctx.Written() { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Failed to get stacktraces: %v", err), + }) + } + return + } +} + +func writeProcesses(out io.Writer, processes []*process_module.Process, processCount int, goroutineCount int64, indent string, flat bool) error { + if goroutineCount > 0 { + if _, err := fmt.Fprintf(out, "%sTotal Number of Goroutines: %d\n", indent, goroutineCount); err != nil { + return err + } + } + if _, err := fmt.Fprintf(out, "%sTotal Number of Processes: %d\n", indent, processCount); err != nil { + return err + } + if len(processes) > 0 { + if err := writeProcess(out, processes[0], " ", flat); err != nil { + return err + } + } + if len(processes) > 1 { + for _, process := range processes[1:] { + if _, err := fmt.Fprintf(out, "%s | \n", indent); err != nil { + return err + } + if err := writeProcess(out, process, " ", flat); err != nil { + return err + } + } + } + return nil +} + +func writeProcess(out io.Writer, process *process_module.Process, indent string, flat bool) error { + sb := &bytes.Buffer{} + if flat { + if process.ParentPID != "" { + _, _ = fmt.Fprintf(sb, "%s+ PID: %s\t\tType: %s\n", indent, process.PID, process.Type) + } else { + _, _ = fmt.Fprintf(sb, "%s+ PID: %s:%s\tType: %s\n", indent, process.ParentPID, process.PID, process.Type) + } + } else { + _, _ = fmt.Fprintf(sb, "%s+ PID: %s\tType: %s\n", indent, process.PID, process.Type) + } + indent += "| " + + _, _ = fmt.Fprintf(sb, "%sDescription: %s\n", indent, process.Description) + _, _ = fmt.Fprintf(sb, "%sStart: %s\n", indent, process.Start) + + if len(process.Stacks) > 0 { + _, _ = fmt.Fprintf(sb, "%sGoroutines:\n", indent) + for _, stack := range process.Stacks { + indent := indent + " " + _, _ = fmt.Fprintf(sb, "%s+ Description: %s", indent, stack.Description) + if stack.Count > 1 { + _, _ = fmt.Fprintf(sb, "* %d", stack.Count) + } + _, _ = fmt.Fprintf(sb, "\n") + indent += "| " + if len(stack.Labels) > 0 { + _, _ = fmt.Fprintf(sb, "%sLabels: %q:%q", indent, stack.Labels[0].Name, stack.Labels[0].Value) + + if len(stack.Labels) > 1 { + for _, label := range stack.Labels[1:] { + _, _ = fmt.Fprintf(sb, ", %q:%q", label.Name, label.Value) + } + } + _, _ = fmt.Fprintf(sb, "\n") + } + _, _ = fmt.Fprintf(sb, "%sStack:\n", indent) + indent += " " + for _, entry := range stack.Entry { + _, _ = fmt.Fprintf(sb, "%s+ %s\n", indent, entry.Function) + _, _ = fmt.Fprintf(sb, "%s| %s:%d\n", indent, entry.File, entry.Line) + } + } + } + if _, err := out.Write(sb.Bytes()); err != nil { + return err + } + sb.Reset() + if len(process.Children) > 0 { + if _, err := fmt.Fprintf(out, "%sChildren:\n", indent); err != nil { + return err + } + for _, child := range process.Children { + if err := writeProcess(out, child, indent+" ", flat); err != nil { + return err + } + } + } + return nil +} diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 4c700df35440..d4093f2049ac 100644 --- a/routers/web/admin/admin.go +++ b/routers/web/admin/admin.go @@ -35,10 +35,11 @@ import ( ) const ( - tplDashboard base.TplName = "admin/dashboard" - tplConfig base.TplName = "admin/config" - tplMonitor base.TplName = "admin/monitor" - tplQueue base.TplName = "admin/queue" + tplDashboard base.TplName = "admin/dashboard" + tplConfig base.TplName = "admin/config" + tplMonitor base.TplName = "admin/monitor" + tplStacktrace base.TplName = "admin/stacktrace" + tplQueue base.TplName = "admin/queue" ) var sysStatus struct { @@ -326,12 +327,33 @@ func Monitor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.monitor") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminMonitor"] = true - ctx.Data["Processes"] = process.GetManager().Processes(true) + ctx.Data["Processes"], ctx.Data["ProcessCount"] = process.GetManager().Processes(false, true) ctx.Data["Entries"] = cron.ListTasks() ctx.Data["Queues"] = queue.GetManager().ManagedQueues() + ctx.HTML(http.StatusOK, tplMonitor) } +// GoroutineStacktrace show admin monitor goroutines page +func GoroutineStacktrace(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminMonitor"] = true + + processStacks, processCount, goroutineCount, err := process.GetManager().ProcessStacktraces(false, false) + if err != nil { + ctx.ServerError("GoroutineStacktrace", err) + return + } + + ctx.Data["ProcessStacks"] = processStacks + + ctx.Data["GoroutineCount"] = goroutineCount + ctx.Data["ProcessCount"] = processCount + + ctx.HTML(http.StatusOK, tplStacktrace) +} + // MonitorCancel cancels a process func MonitorCancel(ctx *context.Context) { pid := ctx.Params("pid") diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index d149d4f89fa5..1306a5436945 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -313,7 +313,7 @@ func dummyInfoRefs(ctx *context.Context) { return } - refs, err := git.NewCommand(ctx, "receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunInDirBytes(tmpDir) + refs, _, err := git.NewCommand(ctx, "receive-pack", "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Dir: tmpDir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } @@ -397,7 +397,7 @@ func (h *serviceHandler) sendFile(contentType, file string) { var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`) func getGitConfig(ctx gocontext.Context, option, dir string) string { - out, err := git.NewCommand(ctx, "config", option).RunInDir(dir) + out, _, err := git.NewCommand(ctx, "config", option).RunStdString(&git.RunOpts{Dir: dir}) if err != nil { log.Error("%v - %s", err, out) } @@ -472,13 +472,12 @@ func serviceRPC(ctx gocontext.Context, h serviceHandler, service string) { var stderr bytes.Buffer cmd := git.NewCommand(h.r.Context(), service, "--stateless-rpc", h.dir) cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir)) - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: h.dir, - Env: append(os.Environ(), h.environ...), - Stdout: h.w, - Stdin: reqBody, - Stderr: &stderr, + if err := cmd.Run(&git.RunOpts{ + Dir: h.dir, + Env: append(os.Environ(), h.environ...), + Stdout: h.w, + Stdin: reqBody, + Stderr: &stderr, }); err != nil { if err.Error() != "signal: killed" { log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.dir, err, stderr.String()) @@ -512,7 +511,7 @@ func getServiceType(r *http.Request) string { } func updateServerInfo(ctx gocontext.Context, dir string) []byte { - out, err := git.NewCommand(ctx, "update-server-info").RunInDirBytes(dir) + out, _, err := git.NewCommand(ctx, "update-server-info").RunStdBytes(&git.RunOpts{Dir: dir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(out))) } @@ -542,7 +541,7 @@ func GetInfoRefs(ctx *context.Context) { } h.environ = append(os.Environ(), h.environ...) - refs, err := git.NewCommand(ctx, service, "--stateless-rpc", "--advertise-refs", ".").RunInDirTimeoutEnv(h.environ, -1, h.dir) + refs, _, err := git.NewCommand(ctx, service, "--stateless-rpc", "--advertise-refs", ".").RunStdBytes(&git.RunOpts{Env: h.environ, Dir: h.dir}) if err != nil { log.Error(fmt.Sprintf("%v - %s", err, string(refs))) } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index c50e773e99b3..486a63a9e105 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -2349,9 +2350,9 @@ func ChangeIssueReaction(ctx *context.Context) { switch ctx.Params(":action") { case "react": - reaction, err := models.CreateIssueReaction(ctx.Doer, issue, form.Content) + reaction, err := issues_model.CreateIssueReaction(ctx.Doer.ID, issue.ID, form.Content) if err != nil { - if models.IsErrForbiddenIssueReaction(err) { + if issues_model.IsErrForbiddenIssueReaction(err) { ctx.ServerError("ChangeIssueReaction", err) return } @@ -2367,7 +2368,7 @@ func ChangeIssueReaction(ctx *context.Context) { log.Trace("Reaction for issue created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, reaction.ID) case "unreact": - if err := models.DeleteIssueReaction(ctx.Doer, issue, form.Content); err != nil { + if err := issues_model.DeleteIssueReaction(ctx.Doer.ID, issue.ID, form.Content); err != nil { ctx.ServerError("DeleteIssueReaction", err) return } @@ -2451,9 +2452,9 @@ func ChangeCommentReaction(ctx *context.Context) { switch ctx.Params(":action") { case "react": - reaction, err := models.CreateCommentReaction(ctx.Doer, comment.Issue, comment, form.Content) + reaction, err := issues_model.CreateCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Content) if err != nil { - if models.IsErrForbiddenIssueReaction(err) { + if issues_model.IsErrForbiddenIssueReaction(err) { ctx.ServerError("ChangeIssueReaction", err) return } @@ -2469,7 +2470,7 @@ func ChangeCommentReaction(ctx *context.Context) { log.Trace("Reaction for comment created: %d/%d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID, reaction.ID) case "unreact": - if err := models.DeleteCommentReaction(ctx.Doer, comment.Issue, comment, form.Content); err != nil { + if err := issues_model.DeleteCommentReaction(ctx.Doer.ID, comment.Issue.ID, comment.ID, form.Content); err != nil { ctx.ServerError("DeleteCommentReaction", err) return } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 7b24e5dabb6e..b324ae4d2e5c 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -34,6 +34,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/utils" + asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/gitdiff" pull_service "code.gitea.io/gitea/services/pull" @@ -339,7 +340,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C } if commitSHA != "" { // Get immediate parent of the first commit in the patch, grab history back - parentCommit, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1", commitSHA).RunInDir(ctx.Repo.GitRepo.Path) + parentCommit, _, err = git.NewCommand(ctx, "rev-list", "-1", "--skip=1", commitSHA).RunStdString(&git.RunOpts{Dir: ctx.Repo.GitRepo.Path}) if err == nil { parentCommit = strings.TrimSpace(parentCommit) } @@ -858,39 +859,53 @@ func MergePullRequest(ctx *context.Context) { if ctx.Written() { return } - if issue.IsClosed { - if issue.IsPull { - ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) - ctx.Redirect(issue.Link()) - return - } - ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) - ctx.Redirect(issue.Link()) - return - } pr := issue.PullRequest + pr.Issue = issue + pr.Issue.Repo = ctx.Repo.Repository + manuallMerge := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged + forceMerge := form.ForceMerge != nil && *form.ForceMerge - allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.Doer) - if err != nil { - ctx.ServerError("IsUserAllowedToMerge", err) - return - } - if !allowedMerge { - ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) - ctx.Redirect(issue.Link()) - return - } + if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, manuallMerge, forceMerge); err != nil { + if errors.Is(err, pull_service.ErrIsClosed) { + if issue.IsPull { + ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) + ctx.Redirect(issue.Link()) + } else { + ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) + ctx.Redirect(issue.Link()) + } + } else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) { + ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) + ctx.Redirect(issue.Link()) + } else if errors.Is(err, pull_service.ErrHasMerged) { + ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) + ctx.Redirect(issue.Link()) + } else if errors.Is(err, pull_service.ErrIsWorkInProgress) { + ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) + ctx.Redirect(issue.Link()) + } else if errors.Is(err, pull_service.ErrNotMergableState) { + ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) + ctx.Redirect(issue.Link()) + } else if models.IsErrDisallowedToMerge(err) { + ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) + ctx.Redirect(issue.Link()) + } else if asymkey_service.IsErrWontSign(err) { + ctx.Flash.Error(err.Error()) // has not translation ... + ctx.Redirect(issue.Link()) + } else if errors.Is(err, pull_service.ErrDependenciesLeft) { + ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) + ctx.Redirect(issue.Link()) + } else { + ctx.ServerError("WebCheck", err) + } - if pr.HasMerged { - ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) - ctx.Redirect(issue.Link()) return } // handle manually-merged mark - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged { - if err = pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { + if manuallMerge { + if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Redirect(issue.Link()) @@ -909,72 +924,13 @@ func MergePullRequest(ctx *context.Context) { return } - if !pr.CanAutoMerge() { - ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) - ctx.Redirect(issue.Link()) - return - } - - if pr.IsWorkInProgress() { - ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) - ctx.Redirect(issue.Link()) - return - } - - if err := pull_service.CheckPRReadyToMerge(ctx, pr, false); err != nil { - if !models.IsErrNotAllowedToMerge(err) { - ctx.ServerError("Merge PR status", err) - return - } - if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.Doer); err != nil { - ctx.ServerError("IsUserRepoAdmin", err) - return - } else if !isRepoAdmin { - ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) - ctx.Redirect(issue.Link()) - return - } - } - - if ctx.HasError() { - ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) - ctx.Redirect(issue.Link()) - return - } - - message := strings.TrimSpace(form.MergeTitleField) - if len(message) == 0 { - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleMerge { - message = pr.GetDefaultMergeMessage() - } - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleRebaseMerge { - message = pr.GetDefaultMergeMessage() - } - if repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash { - message = pr.GetDefaultSquashMessage() - } - } - - form.MergeMessageField = strings.TrimSpace(form.MergeMessageField) - if len(form.MergeMessageField) > 0 { - message += "\n\n" + form.MergeMessageField - } - - pr.Issue = issue - pr.Issue.Repo = ctx.Repo.Repository - - noDeps, err := models.IssueNoDependenciesLeft(issue) - if err != nil { - return - } - - if !noDeps { - ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) - ctx.Redirect(issue.Link()) + // set defaults to propagate needed fields + if err := form.SetDefaults(pr); err != nil { + ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) return } - if err = pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil { + if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) ctx.Redirect(issue.Link()) diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 8a5294dce1ec..edbb4aadf6fd 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -331,7 +331,6 @@ func DownloadPackageFile(ctx *context.Context) { s, _, err := packages_service.GetPackageFileStream( ctx, - ctx.Package.Descriptor.Version, pf, ) if err != nil { diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index f62884037588..85870eddf5a3 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -280,6 +280,7 @@ func Profile(ctx *context.Context) { pager.AddParam(ctx, "language", "Language") } ctx.Data["Page"] = pager + ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["ShowUserEmail"] = len(ctx.ContextUser.Email) > 0 && ctx.IsSigned && (!ctx.ContextUser.KeepEmailPrivate || ctx.ContextUser.ID == ctx.Doer.ID) diff --git a/routers/web/web.go b/routers/web/web.go index 60e104ccf83b..3bdedab85460 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -436,6 +436,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/config/test_mail", admin.SendTestMail) m.Group("/monitor", func() { m.Get("", admin.Monitor) + m.Get("/stacktrace", admin.GoroutineStacktrace) m.Post("/cancel/{pid}", admin.MonitorCancel) m.Group("/queue/{qid}", func() { m.Get("", admin.Queue) @@ -472,10 +473,12 @@ func RegisterRoutes(m *web.Route) { m.Post("/delete", admin.DeleteRepo) }) - m.Group("/packages", func() { - m.Get("", admin.Packages) - m.Post("/delete", admin.DeletePackageVersion) - }) + if setting.Packages.Enabled { + m.Group("/packages", func() { + m.Get("", admin.Packages) + m.Post("/delete", admin.DeletePackageVersion) + }) + } m.Group("/hooks", func() { m.Get("", admin.DefaultOrSystemWebhooks) @@ -669,21 +672,23 @@ func RegisterRoutes(m *web.Route) { }, reqSignIn) m.Group("/{username}/-", func() { - m.Group("/packages", func() { - m.Get("", user.ListPackages) - m.Group("/{type}/{name}", func() { - m.Get("", user.RedirectToLastVersion) - m.Get("/versions", user.ListPackageVersions) - m.Group("/{version}", func() { - m.Get("", user.ViewPackageVersion) - m.Get("/files/{fileid}", user.DownloadPackageFile) - m.Group("/settings", func() { - m.Get("", user.PackageSettings) - m.Post("", bindIgnErr(forms.PackageSettingForm{}), user.PackageSettingsPost) - }, reqPackageAccess(perm.AccessModeWrite)) + if setting.Packages.Enabled { + m.Group("/packages", func() { + m.Get("", user.ListPackages) + m.Group("/{type}/{name}", func() { + m.Get("", user.RedirectToLastVersion) + m.Get("/versions", user.ListPackageVersions) + m.Group("/{version}", func() { + m.Get("", user.ViewPackageVersion) + m.Get("/files/{fileid}", user.DownloadPackageFile) + m.Group("/settings", func() { + m.Get("", user.PackageSettings) + m.Post("", bindIgnErr(forms.PackageSettingForm{}), user.PackageSettingsPost) + }, reqPackageAccess(perm.AccessModeWrite)) + }) }) - }) - }, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) + }, context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) + } }, context_service.UserAssignmentWeb()) // ***** Release Attachment Download without Signin @@ -972,7 +977,9 @@ func RegisterRoutes(m *web.Route) { m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones) }, context.RepoRef()) - m.Get("/packages", repo.Packages) + if setting.Packages.Enabled { + m.Get("/packages", repo.Packages) + } m.Group("/projects", func() { m.Get("", repo.Projects) diff --git a/services/agit/agit.go b/services/agit/agit.go index b2859c8a5b4a..5f8d16172da4 100644 --- a/services/agit/agit.go +++ b/services/agit/agit.go @@ -205,7 +205,7 @@ func ProcRecive(ctx *context.PrivateContext, opts *private.HookOptions) []privat } if !forcePush { - output, err := git.NewCommand(ctx, "rev-list", "--max-count=1", oldCommitID, "^"+opts.NewCommitIDs[i]).RunInDirWithEnv(repo.RepoPath(), os.Environ()) + output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1", oldCommitID, "^"+opts.NewCommitIDs[i]).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) if err != nil { log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, opts.NewCommitIDs[i], repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index 59f96352ded9..c3f093a80810 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -90,15 +90,15 @@ func SigningKey(ctx context.Context, repoPath string) (string, *git.Signature) { if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { // Can ignore the error here as it means that commit.gpgsign is not set - value, _ := git.NewCommand(ctx, "config", "--get", "commit.gpgsign").RunInDir(repoPath) + value, _, _ := git.NewCommand(ctx, "config", "--get", "commit.gpgsign").RunStdString(&git.RunOpts{Dir: repoPath}) sign, valid := git.ParseBool(strings.TrimSpace(value)) if !sign || !valid { return "", nil } - signingKey, _ := git.NewCommand(ctx, "config", "--get", "user.signingkey").RunInDir(repoPath) - signingName, _ := git.NewCommand(ctx, "config", "--get", "user.name").RunInDir(repoPath) - signingEmail, _ := git.NewCommand(ctx, "config", "--get", "user.email").RunInDir(repoPath) + signingKey, _, _ := git.NewCommand(ctx, "config", "--get", "user.signingkey").RunStdString(&git.RunOpts{Dir: repoPath}) + signingName, _, _ := git.NewCommand(ctx, "config", "--get", "user.name").RunStdString(&git.RunOpts{Dir: repoPath}) + signingEmail, _, _ := git.NewCommand(ctx, "config", "--get", "user.email").RunStdString(&git.RunOpts{Dir: repoPath}) return strings.TrimSpace(signingKey), &git.Signature{ Name: strings.TrimSpace(signingName), Email: strings.TrimSpace(signingEmail), diff --git a/services/auth/signin.go b/services/auth/signin.go index aa9a9660c039..3ccf68c3a7ea 100644 --- a/services/auth/signin.go +++ b/services/auth/signin.go @@ -23,19 +23,23 @@ import ( // UserSignIn validates user name and password. func UserSignIn(username, password string) (*user_model.User, *auth.Source, error) { var user *user_model.User + isEmail := false if strings.Contains(username, "@") { + isEmail = true emailAddress := user_model.EmailAddress{LowerEmail: strings.ToLower(strings.TrimSpace(username))} // check same email - has, err := db.GetEngine(db.DefaultContext).Where("is_activated=?", true).Get(&emailAddress) + has, err := db.GetEngine(db.DefaultContext).Get(&emailAddress) if err != nil { return nil, nil, err } - if !has { - return nil, nil, user_model.ErrEmailAddressNotExist{ - Email: username, + if has { + if !emailAddress.IsActivated { + return nil, nil, user_model.ErrEmailAddressNotExist{ + Email: username, + } } + user = &user_model.User{ID: emailAddress.UID} } - user = &user_model.User{ID: emailAddress.UID} } else { trimmedUsername := strings.TrimSpace(username) if len(trimmedUsername) == 0 { @@ -45,38 +49,40 @@ func UserSignIn(username, password string) (*user_model.User, *auth.Source, erro user = &user_model.User{LowerName: strings.ToLower(trimmedUsername)} } - hasUser, err := user_model.GetUser(user) - if err != nil { - return nil, nil, err - } - - if hasUser { - source, err := auth.GetSourceByID(user.LoginSource) + if user != nil { + hasUser, err := user_model.GetUser(user) if err != nil { return nil, nil, err } - if !source.IsActive { - return nil, nil, oauth2.ErrAuthSourceNotActived - } + if hasUser { + source, err := auth.GetSourceByID(user.LoginSource) + if err != nil { + return nil, nil, err + } - authenticator, ok := source.Cfg.(PasswordAuthenticator) - if !ok { - return nil, nil, smtp.ErrUnsupportedLoginType - } + if !source.IsActive { + return nil, nil, oauth2.ErrAuthSourceNotActived + } - user, err := authenticator.Authenticate(user, user.LoginName, password) - if err != nil { - return nil, nil, err - } + authenticator, ok := source.Cfg.(PasswordAuthenticator) + if !ok { + return nil, nil, smtp.ErrUnsupportedLoginType + } - // WARN: DON'T check user.IsActive, that will be checked on reqSign so that - // user could be hint to resend confirm email. - if user.ProhibitLogin { - return nil, nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name} - } + user, err := authenticator.Authenticate(user, user.LoginName, password) + if err != nil { + return nil, nil, err + } - return user, source, nil + // WARN: DON'T check user.IsActive, that will be checked on reqSign so that + // user could be hint to resend confirm email. + if user.ProhibitLogin { + return nil, nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name} + } + + return user, source, nil + } } sources, err := auth.AllActiveSources() @@ -111,5 +117,9 @@ func UserSignIn(username, password string) (*user_model.User, *auth.Source, erro } } + if isEmail { + return nil, nil, user_model.ErrEmailAddressNotExist{Email: username} + } + return nil, nil, user_model.ErrUserNotExist{Name: username} } diff --git a/services/cron/cron.go b/services/cron/cron.go index 9fe90d42308e..ebbcd75b6df6 100644 --- a/services/cron/cron.go +++ b/services/cron/cron.go @@ -7,9 +7,11 @@ package cron import ( "context" + "runtime/pprof" "time" "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/sync" "github.com/gogs/cron" @@ -23,7 +25,9 @@ var taskStatusTable = sync.NewStatusTable() // NewContext begins cron tasks // Each cron task is run within the shutdown context as a running server // AtShutdown the cron server is stopped -func NewContext() { +func NewContext(original context.Context) { + defer pprof.SetGoroutineLabels(original) + _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().ShutdownContext(), "Service: Cron", process.SystemProcessType, true) initBasicTasks() initExtendedTasks() @@ -42,6 +46,7 @@ func NewContext() { lock.Lock() started = false lock.Unlock() + finished() }) } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 33c765864058..80123e9af322 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -599,6 +599,31 @@ func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +// SetDefaults if not provided for mergestyle and commit message +func (f *MergePullRequestForm) SetDefaults(pr *models.PullRequest) (err error) { + if f.Do == "" { + f.Do = "merge" + } + + f.MergeTitleField = strings.TrimSpace(f.MergeTitleField) + if len(f.MergeTitleField) == 0 { + switch f.Do { + case "merge", "rebase-merge": + f.MergeTitleField, err = pr.GetDefaultMergeMessage() + case "squash": + f.MergeTitleField, err = pr.GetDefaultSquashMessage() + } + } + + f.MergeMessageField = strings.TrimSpace(f.MergeMessageField) + if len(f.MergeMessageField) > 0 { + f.MergeTitleField += "\n\n" + f.MergeMessageField + f.MergeMessageField = "" + } + + return +} + // CodeCommentForm form for adding code comments for PRs type CodeCommentForm struct { Origin string `binding:"Required;In(timeline,diff)"` diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 58c25ff98f82..1df16e50167c 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1378,7 +1378,7 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff go func(ctx context.Context, diffArgs []string, repoPath string, writer *io.PipeWriter) { cmd := git.NewCommand(ctx, diffArgs...) cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) - if err := cmd.RunWithContext(&git.RunContext{ + if err := cmd.Run(&git.RunOpts{ Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, Dir: repoPath, Stderr: os.Stderr, diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 9b0adc4a6832..6410aa1ee085 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -479,7 +479,7 @@ func (g *RepositoryDumper) CreatePullRequests(prs ...*base.PullRequest) error { } if ok { - _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunInDir(g.gitPath()) + _, _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.gitPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) } else { diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 82e57e296d28..9d2a7eb41bb2 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/foreignreference" + issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -392,7 +393,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error { } // add reactions for _, reaction := range issue.Reactions { - res := models.Reaction{ + res := issues_model.Reaction{ Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } @@ -448,7 +449,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error { // add reactions for _, reaction := range comment.Reactions { - res := models.Reaction{ + res := issues_model.Reaction{ Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } @@ -552,7 +553,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head } if ok { - _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunInDir(g.repo.RepoPath()) + _, _, err = git.NewCommand(g.ctx, "fetch", remote, pr.Head.Ref).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { log.Error("Fetch branch from %s failed: %v", pr.Head.CloneURL, err) } else { @@ -576,7 +577,7 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(pr *base.PullRequest) (head } else { head = pr.Head.Ref // Ensure the closed PR SHA still points to an existing ref - _, err = git.NewCommand(g.ctx, "rev-list", "--quiet", "-1", pr.Head.SHA).RunInDir(g.repo.RepoPath()) + _, _, err = git.NewCommand(g.ctx, "rev-list", "--quiet", "-1", pr.Head.SHA).RunStdString(&git.RunOpts{Dir: g.repo.RepoPath()}) if err != nil { if pr.Head.SHA != "" { // Git update-ref remove bad references with a relative path @@ -646,7 +647,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR // add reactions for _, reaction := range pr.Reactions { - res := models.Reaction{ + res := issues_model.Reaction{ Type: reaction.Content, CreatedUnix: timeutil.TimeStampNow(), } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 1d4ec13d1cbd..ad5caa4279e9 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -233,7 +233,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) baseRef := "master" assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false)) - _, err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+baseRef).RunInDir(fromRepo.RepoPath()) + err := git.NewCommand(git.DefaultContext, "symbolic-ref", "HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) @@ -257,7 +257,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { // fromRepo branch1 // headRef := "branch1" - _, err = git.NewCommand(git.DefaultContext, "checkout", "-b", headRef).RunInDir(fromRepo.RepoPath()) + _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b", headRef).RunStdString(&git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) @@ -281,7 +281,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, git.CloneWithArgs(git.DefaultContext, fromRepo.RepoPath(), forkRepo.RepoPath(), []string{}, git.CloneRepoOptions{ Branch: headRef, })) - _, err = git.NewCommand(git.DefaultContext, "checkout", "-b", forkHeadRef).RunInDir(forkRepo.RepoPath()) + _, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b", forkHeadRef).RunStdString(&git.RunOpts{Dir: forkRepo.RepoPath()}) assert.NoError(t, err) assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644)) assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true)) diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index aa9fb4cccbd7..ecd031b38714 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -33,7 +33,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error remoteName := m.GetRemoteName() repoPath := m.Repo.RepoPath() // Remove old remote - _, err := git.NewCommand(ctx, "remote", "rm", remoteName).RunInDir(repoPath) + _, _, err := git.NewCommand(ctx, "remote", "rm", remoteName).RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err } @@ -44,7 +44,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error } else { cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, addr, repoPath)) } - _, err = cmd.RunInDir(repoPath) + _, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err } @@ -53,7 +53,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error wikiPath := m.Repo.WikiPath() wikiRemotePath := repo_module.WikiRemoteURL(ctx, addr) // Remove old remote of wiki - _, err := git.NewCommand(ctx, "remote", "rm", remoteName).RunInDir(wikiPath) + _, _, err = git.NewCommand(ctx, "remote", "rm", remoteName).RunStdString(&git.RunOpts{Dir: wikiPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err } @@ -64,7 +64,7 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error } else { cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, wikiRemotePath, wikiPath)) } - _, err = cmd.RunInDir(wikiPath) + _, _, err = cmd.RunStdString(&git.RunOpts{Dir: wikiPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err } @@ -171,7 +171,7 @@ func pruneBrokenReferences(ctx context.Context, stdoutBuilder.Reset() pruneErr := git.NewCommand(ctx, "remote", "prune", m.GetRemoteName()). SetDescription(fmt.Sprintf("Mirror.runSync %ssPrune references: %s ", wiki, m.Repo.FullName())). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: timeout, Dir: repoPath, Stdout: stdoutBuilder, @@ -219,7 +219,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stderrBuilder := strings.Builder{} if err := git.NewCommand(ctx, gitArgs...). SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: timeout, Dir: repoPath, Stdout: &stdoutBuilder, @@ -245,7 +245,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder.Reset() if err = git.NewCommand(ctx, gitArgs...). SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: timeout, Dir: repoPath, Stdout: &stdoutBuilder, @@ -310,7 +310,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder.Reset() if err := git.NewCommand(ctx, "remote", "update", "--prune", m.GetRemoteName()). SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: timeout, Dir: wikiPath, Stdout: &stdoutBuilder, @@ -337,7 +337,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo if err = git.NewCommand(ctx, "remote", "update", "--prune", m.GetRemoteName()). SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: timeout, Dir: wikiPath, Stdout: &stdoutBuilder, diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index e5c4b39895aa..5c0c14c6271e 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -35,13 +35,13 @@ func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr str } else { cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=push %s [repo_path: %s]", m.RemoteName, addr, path)) } - if _, err := cmd.RunInDir(path); err != nil { + if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } - if _, err := git.NewCommand(ctx, "config", "--add", "remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunInDir(path); err != nil { + if _, _, err := git.NewCommand(ctx, "config", "--add", "remote."+m.RemoteName+".push", "+refs/heads/*:refs/heads/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } - if _, err := git.NewCommand(ctx, "config", "--add", "remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunInDir(path); err != nil { + if _, _, err := git.NewCommand(ctx, "config", "--add", "remote."+m.RemoteName+".push", "+refs/tags/*:refs/tags/*").RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } return nil @@ -67,12 +67,12 @@ func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr str func RemovePushMirrorRemote(ctx context.Context, m *repo_model.PushMirror) error { cmd := git.NewCommand(ctx, "remote", "rm", m.RemoteName) - if _, err := cmd.RunInDir(m.Repo.RepoPath()); err != nil { + if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: m.Repo.RepoPath()}); err != nil { return err } if m.Repo.HasWiki() { - if _, err := cmd.RunInDir(m.Repo.WikiPath()); err != nil { + if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: m.Repo.WikiPath()}); err != nil { // The wiki remote may not exist log.Warn("Wiki Remote[%d] could not be removed: %v", m.ID, err) } diff --git a/services/packages/packages.go b/services/packages/packages.go index b26e60c71185..7f90f80bafc2 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -426,7 +426,7 @@ func GetFileStreamByPackageVersionAndFileID(ctx context.Context, owner *user_mod return nil, nil, err } - return GetPackageFileStream(ctx, pv, pf) + return GetPackageFileStream(ctx, pf) } // GetFileStreamByPackageVersion returns the content of the specific package file @@ -436,11 +436,11 @@ func GetFileStreamByPackageVersion(ctx context.Context, pv *packages_model.Packa return nil, nil, err } - return GetPackageFileStream(ctx, pv, pf) + return GetPackageFileStream(ctx, pf) } // GetPackageFileStream returns the content of the specific package file -func GetPackageFileStream(ctx context.Context, pv *packages_model.PackageVersion, pf *packages_model.PackageFile) (io.ReadCloser, *packages_model.PackageFile, error) { +func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (io.ReadCloser, *packages_model.PackageFile, error) { pb, err := packages_model.GetBlobByID(ctx, pf.BlobID) if err != nil { return nil, nil, err @@ -449,7 +449,7 @@ func GetPackageFileStream(ctx context.Context, pv *packages_model.PackageVersion s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256)) if err == nil { if pf.IsLead { - if err := packages_model.IncrementDownloadCounter(ctx, pv.ID); err != nil { + if err := packages_model.IncrementDownloadCounter(ctx, pf.VersionID); err != nil { log.Error("Error incrementing download counter: %v", err) } } diff --git a/services/pull/check.go b/services/pull/check.go index f920688f5d9b..253417072ce0 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -7,6 +7,7 @@ package pull import ( "context" + "errors" "fmt" "os" "strconv" @@ -24,11 +25,21 @@ import ( "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + asymkey_service "code.gitea.io/gitea/services/asymkey" ) // prQueue represents a queue to handle update pull request tests var prQueue queue.UniqueQueue +var ( + ErrIsClosed = errors.New("pull is cosed") + ErrUserNotAllowedToMerge = errors.New("user not allowed to merge") + ErrHasMerged = errors.New("has already been merged") + ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") + ErrNotMergableState = errors.New("not in mergeable state") + ErrDependenciesLeft = errors.New("is blocked by an open dependency") +) + // AddToTaskQueue adds itself to pull request test task queue. func AddToTaskQueue(pr *models.PullRequest) { err := prQueue.PushFunc(strconv.FormatInt(pr.ID, 10), func() error { @@ -46,6 +57,79 @@ func AddToTaskQueue(pr *models.PullRequest) { } } +// CheckPullMergable check if the pull mergable based on all conditions (branch protection, merge options, ...) +func CheckPullMergable(ctx context.Context, doer *user_model.User, perm *models.Permission, pr *models.PullRequest, manuallMerge, force bool) error { + if pr.HasMerged { + return ErrHasMerged + } + + if err := pr.LoadIssue(); err != nil { + return err + } else if pr.Issue.IsClosed { + return ErrIsClosed + } + + if allowedMerge, err := IsUserAllowedToMerge(pr, *perm, doer); err != nil { + return err + } else if !allowedMerge { + return ErrUserNotAllowedToMerge + } + + if manuallMerge { + // don't check rules to "auto merge", doer is going to mark this pull as merged manually + return nil + } + + if pr.IsWorkInProgress() { + return ErrIsWorkInProgress + } + + if !pr.CanAutoMerge() { + return ErrNotMergableState + } + + if err := CheckPRReadyToMerge(ctx, pr, false); err != nil { + if models.IsErrDisallowedToMerge(err) { + if force { + if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, doer); err != nil { + return err + } else if !isRepoAdmin { + return ErrUserNotAllowedToMerge + } + } + } else { + return err + } + } + + if _, err := isSignedIfRequired(ctx, pr, doer); err != nil { + return err + } + + if noDeps, err := models.IssueNoDependenciesLeft(pr.Issue); err != nil { + return err + } else if !noDeps { + return ErrDependenciesLeft + } + + return nil +} + +// isSignedIfRequired check if merge will be signed if required +func isSignedIfRequired(ctx context.Context, pr *models.PullRequest, doer *user_model.User) (bool, error) { + if err := pr.LoadProtectedBranch(); err != nil { + return false, err + } + + if pr.ProtectedBranch == nil || !pr.ProtectedBranch.RequireSignedCommits { + return true, nil + } + + sign, _, _, err := asymkey_service.SignMerge(ctx, pr, doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) + + return sign, err +} + // checkAndUpdateStatus checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. func checkAndUpdateStatus(pr *models.PullRequest) { @@ -91,8 +175,8 @@ func getMergeCommit(ctx context.Context, pr *models.PullRequest) (*git.Commit, e headFile := pr.GetGitRefName() // Check if a pull request is merged into BaseBranch - _, err = git.NewCommand(ctx, "merge-base", "--is-ancestor", headFile, pr.BaseBranch). - RunInDirWithEnv(pr.BaseRepo.RepoPath(), []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}) + _, _, err = git.NewCommand(ctx, "merge-base", "--is-ancestor", headFile, pr.BaseBranch). + RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath(), Env: []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}}) if err != nil { // Errors are signaled by a non-zero status that is not 1 if strings.Contains(err.Error(), "exit status 1") { @@ -112,8 +196,8 @@ func getMergeCommit(ctx context.Context, pr *models.PullRequest) (*git.Commit, e cmd := commitID[:40] + ".." + pr.BaseBranch // Get the commit from BaseBranch where the pull request got merged - mergeCommit, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse", cmd). - RunInDirWithEnv("", []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}) + mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse", cmd). + RunStdString(&git.RunOpts{Dir: "", Env: []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}}) if err != nil { return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v", err) } else if len(mergeCommit) < 40 { diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 4cdd17cc7b5c..65bcb9c0e44d 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -32,9 +32,9 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { WorkerPoolConfiguration: queue.WorkerPoolConfiguration{ QueueLength: 10, BatchLength: 1, + Name: "temporary-queue", }, Workers: 1, - Name: "temporary-queue", }, "") assert.NoError(t, err) diff --git a/services/pull/merge.go b/services/pull/merge.go index 6108a7956e1d..6ecb3cf08e59 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -141,7 +141,7 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User stagingBranch := "staging" if expectedHeadCommitID != "" { - trackingCommitID, err := git.NewCommand(ctx, "show-ref", "--hash", git.BranchPrefix+trackingBranch).RunInDir(tmpBasePath) + trackingCommitID, _, err := git.NewCommand(ctx, "show-ref", "--hash", git.BranchPrefix+trackingBranch).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { log.Error("show-ref[%s] --hash refs/heads/trackingn: %v", tmpBasePath, git.BranchPrefix+trackingBranch, err) return "", fmt.Errorf("getDiffTree: %v", err) @@ -188,11 +188,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User // Switch off LFS process (set required, clean and smudge here also) if err := gitConfigCommand().AddArguments("filter.lfs.process", ""). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git config [filter.lfs.process -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git config [filter.lfs.process -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -201,11 +200,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User errbuf.Reset() if err := gitConfigCommand().AddArguments("filter.lfs.required", "false"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git config [filter.lfs.required -> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git config [filter.lfs.required -> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -214,11 +212,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User errbuf.Reset() if err := gitConfigCommand().AddArguments("filter.lfs.clean", ""). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git config [filter.lfs.clean -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git config [filter.lfs.clean -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -227,11 +224,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User errbuf.Reset() if err := gitConfigCommand().AddArguments("filter.lfs.smudge", ""). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git config [filter.lfs.smudge -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git config [filter.lfs.smudge -> <> ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -240,11 +236,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User errbuf.Reset() if err := gitConfigCommand().AddArguments("core.sparseCheckout", "true"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git config [core.sparseCheckout -> true ]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git config [core.sparsecheckout -> true]: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -254,11 +249,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User // Read base branch index if err := git.NewCommand(ctx, "read-tree", "HEAD"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git read-tree HEAD: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("Unable to read base branch in to the index: %v\n%s\n%s", err, outbuf.String(), errbuf.String()) @@ -315,11 +309,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User case repo_model.MergeStyleRebaseMerge: // Checkout head branch if err := git.NewCommand(ctx, "checkout", "-b", stagingBranch, trackingBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -329,11 +322,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User // Rebase before merging if err := git.NewCommand(ctx, "rebase", baseBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { // Rebase will leave a REBASE_HEAD file in .git if there is a conflict if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil { @@ -383,11 +375,10 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User // Checkout base branch again if err := git.NewCommand(ctx, "checkout", baseBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git checkout base prior to merge post staging rebase [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -429,12 +420,11 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User sig := pr.Issue.Poster.NewGitSig() if signArg == "" { if err := git.NewCommand(ctx, "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Env: env, + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -445,12 +435,11 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) } if err := git.NewCommand(ctx, "commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Env: env, + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -515,12 +504,11 @@ func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User } // Push back to upstream. - if err := pushCmd.RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + if err := pushCmd.Run(&git.RunOpts{ + Env: env, + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { if strings.Contains(errbuf.String(), "non-fast-forward") { return "", &git.ErrPushOutOfDate{ @@ -549,24 +537,22 @@ func commitAndSignNoAuthor(ctx context.Context, pr *models.PullRequest, message, var outbuf, errbuf strings.Builder if signArg == "" { if err := git.NewCommand(ctx, "commit", "-m", message). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Env: env, + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) } } else { if err := git.NewCommand(ctx, "commit", signArg, "-m", message). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Env: env, + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) @@ -577,11 +563,10 @@ func commitAndSignNoAuthor(ctx context.Context, pr *models.PullRequest, message, func runMergeCommand(pr *models.PullRequest, mergeStyle repo_model.MergeStyle, cmd *git.Command, tmpBasePath string) error { var outbuf, errbuf strings.Builder - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + if err := cmd.Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { @@ -616,11 +601,10 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) ( var outbuf, errbuf strings.Builder // Compute the diff-tree for sparse-checkout if err := git.NewCommand(ctx, "diff-tree", "--no-commit-id", "--name-only", "-r", "-z", "--root", baseBranch, headBranch, "--"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: repoPath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: repoPath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { return "", fmt.Errorf("git diff-tree [%s base:%s head:%s]: %s", repoPath, baseBranch, headBranch, errbuf.String()) } @@ -660,21 +644,6 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) ( return out.String(), nil } -// IsSignedIfRequired check if merge will be signed if required -func IsSignedIfRequired(ctx context.Context, pr *models.PullRequest, doer *user_model.User) (bool, error) { - if err := pr.LoadProtectedBranch(); err != nil { - return false, err - } - - if pr.ProtectedBranch == nil || !pr.ProtectedBranch.RequireSignedCommits { - return true, nil - } - - sign, _, _, err := asymkey_service.SignMerge(ctx, pr, doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) - - return sign, err -} - // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections func IsUserAllowedToMerge(pr *models.PullRequest, p models.Permission, user *user_model.User) (bool, error) { if user == nil { @@ -711,29 +680,29 @@ func CheckPRReadyToMerge(ctx context.Context, pr *models.PullRequest, skipProtec return err } if !isPass { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "Not all required status checks successful", } } if !pr.ProtectedBranch.HasEnoughApprovals(pr) { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "Does not have enough approvals", } } if pr.ProtectedBranch.MergeBlockedByRejectedReview(pr) { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "There are requested changes", } } if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(pr) { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "There are official review requests", } } if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "The head branch is behind the base branch", } } @@ -743,7 +712,7 @@ func CheckPRReadyToMerge(ctx context.Context, pr *models.PullRequest, skipProtec } if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr) { - return models.ErrNotAllowedToMerge{ + return models.ErrDisallowedToMerge{ Reason: "Changed protected files", } } diff --git a/services/pull/patch.go b/services/pull/patch.go index 4c0c91d96a45..f86141aa7aec 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -76,7 +76,7 @@ func TestPatch(pr *models.PullRequest) error { defer gitRepo.Close() // 1. update merge base - pr.MergeBase, err = git.NewCommand(ctx, "merge-base", "--", "base", "tracking").RunInDir(tmpBasePath) + pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base", "--", "base", "tracking").RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { var err2 error pr.MergeBase, err2 = gitRepo.GetRefCommitID(git.BranchPrefix + "base") @@ -166,7 +166,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g } // Need to get the objects from the object db to attempt to merge - root, err := git.NewCommand(ctx, "unpack-file", file.stage1.sha).RunInDir(tmpBasePath) + root, _, err := git.NewCommand(ctx, "unpack-file", file.stage1.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get root object: %s at path: %s for merging. Error: %w", file.stage1.sha, file.stage1.path, err) } @@ -175,7 +175,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g _ = util.Remove(filepath.Join(tmpBasePath, root)) }() - base, err := git.NewCommand(ctx, "unpack-file", file.stage2.sha).RunInDir(tmpBasePath) + base, _, err := git.NewCommand(ctx, "unpack-file", file.stage2.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get base object: %s at path: %s for merging. Error: %w", file.stage2.sha, file.stage2.path, err) } @@ -183,7 +183,7 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g defer func() { _ = util.Remove(base) }() - head, err := git.NewCommand(ctx, "unpack-file", file.stage3.sha).RunInDir(tmpBasePath) + head, _, err := git.NewCommand(ctx, "unpack-file", file.stage3.sha).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return fmt.Errorf("unable to get head object:%s at path: %s for merging. Error: %w", file.stage3.sha, file.stage3.path, err) } @@ -193,13 +193,13 @@ func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, g }() // now git merge-file annoyingly takes a different order to the merge-tree ... - _, conflictErr := git.NewCommand(ctx, "merge-file", base, root, head).RunInDir(tmpBasePath) + _, _, conflictErr := git.NewCommand(ctx, "merge-file", base, root, head).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if conflictErr != nil { return &errMergeConflict{file.stage2.path} } // base now contains the merged data - hash, err := git.NewCommand(ctx, "hash-object", "-w", "--path", file.stage2.path, base).RunInDir(tmpBasePath) + hash, _, err := git.NewCommand(ctx, "hash-object", "-w", "--path", file.stage2.path, base).RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return err } @@ -223,7 +223,7 @@ func AttemptThreeWayMerge(ctx context.Context, gitPath string, gitRepo *git.Repo defer cancel() // First we use read-tree to do a simple three-way merge - if _, err := git.NewCommand(ctx, "read-tree", "-m", base, ours, theirs).RunInDir(gitPath); err != nil { + if _, _, err := git.NewCommand(ctx, "read-tree", "-m", base, ours, theirs).RunStdString(&git.RunOpts{Dir: gitPath}); err != nil { log.Error("Unable to run read-tree -m! Error: %v", err) return false, nil, fmt.Errorf("unable to run read-tree -m! Error: %v", err) } @@ -282,7 +282,8 @@ func checkConflicts(ctx context.Context, pr *models.PullRequest, gitRepo *git.Re } if !conflict { - treeHash, err := git.NewCommand(ctx, "write-tree").RunInDir(tmpBasePath) + var treeHash string + treeHash, _, err = git.NewCommand(ctx, "write-tree").RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return false, err } @@ -334,7 +335,7 @@ func checkConflicts(ctx context.Context, pr *models.PullRequest, gitRepo *git.Re log.Trace("PullRequest[%d].testPatch (patchPath): %s", pr.ID, patchPath) // 4. Read the base branch in to the index of the temporary repository - _, err = git.NewCommand(gitRepo.Ctx, "read-tree", "base").RunInDir(tmpBasePath) + _, _, err = git.NewCommand(gitRepo.Ctx, "read-tree", "base").RunStdString(&git.RunOpts{Dir: tmpBasePath}) if err != nil { return false, fmt.Errorf("git read-tree %s: %v", pr.BaseBranch, err) } @@ -379,10 +380,9 @@ func checkConflicts(ctx context.Context, pr *models.PullRequest, gitRepo *git.Re // 8. Run the check command conflict = false err = git.NewCommand(gitRepo.Ctx, args...). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stderr: stderrWriter, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stderr: stderrWriter, PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { // Close the writer end of the pipe to begin processing _ = stderrWriter.Close() diff --git a/services/pull/patch_unmerged.go b/services/pull/patch_unmerged.go index abd54b07cf12..38394191429c 100644 --- a/services/pull/patch_unmerged.go +++ b/services/pull/patch_unmerged.go @@ -63,11 +63,10 @@ func readUnmergedLsFileLines(ctx context.Context, tmpBasePath string, outputChan stderr := &strings.Builder{} err = git.NewCommand(ctx, "ls-files", "-u", "-z"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: lsFilesWriter, - Stderr: stderr, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: lsFilesWriter, + Stderr: stderr, PipelineFunc: func(_ context.Context, _ context.CancelFunc) error { _ = lsFilesWriter.Close() defer func() { diff --git a/services/pull/pull.go b/services/pull/pull.go index 13e4773d8ab6..0537964b9de7 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -479,7 +479,7 @@ func UpdateRef(ctx context.Context, pr *models.PullRequest) (err error) { return err } - _, err = git.NewCommand(ctx, "update-ref", pr.GetGitRefName(), pr.HeadCommitID).RunInDir(pr.BaseRepo.RepoPath()) + _, _, err = git.NewCommand(ctx, "update-ref", pr.GetGitRefName(), pr.HeadCommitID).RunStdString(&git.RunOpts{Dir: pr.BaseRepo.RepoPath()}) if err != nil { log.Error("Unable to update ref in base repository for PR[%d] Error: %v", pr.ID, err) } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index c6c6d8b6e786..22ef53937d8a 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -93,11 +93,10 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e var outbuf, errbuf strings.Builder if err := git.NewCommand(ctx, "remote", "add", "-t", pr.BaseBranch, "-m", pr.BaseBranch, "origin", baseRepoPath). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("Unable to add base repository as origin [%s -> %s]: %v\n%s\n%s", pr.BaseRepo.FullName(), tmpBasePath, err, outbuf.String(), errbuf.String()) if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { @@ -109,11 +108,10 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e errbuf.Reset() if err := git.NewCommand(ctx, "fetch", "origin", "--no-tags", "--", pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("Unable to fetch origin base branch [%s:%s -> base, original_base in %s]: %v:\n%s\n%s", pr.BaseRepo.FullName(), pr.BaseBranch, tmpBasePath, err, outbuf.String(), errbuf.String()) if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { @@ -125,11 +123,10 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e errbuf.Reset() if err := git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+baseBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("Unable to set HEAD as base branch [%s]: %v\n%s\n%s", tmpBasePath, err, outbuf.String(), errbuf.String()) if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { @@ -149,11 +146,10 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e } if err := git.NewCommand(ctx, "remote", "add", remoteRepoName, headRepoPath). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { log.Error("Unable to add head repository as head_repo [%s -> %s]: %v\n%s\n%s", pr.HeadRepo.FullName(), tmpBasePath, err, outbuf.String(), errbuf.String()) if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { @@ -175,11 +171,10 @@ func createTemporaryRepo(ctx context.Context, pr *models.PullRequest) (string, e headBranch = pr.GetGitRefName() } if err := git.NewCommand(ctx, "fetch", "--no-tags", remoteRepoName, headBranch+":"+trackingBranch). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: tmpBasePath, - Stdout: &outbuf, - Stderr: &errbuf, + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, }); err != nil { if err := models.RemoveTemporaryPath(tmpBasePath); err != nil { log.Error("CreateTempRepo: RemoveTemporaryPath: %s", err) diff --git a/services/release/release.go b/services/release/release.go index 4d16e66aec98..0372e3a6906a 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -297,9 +297,9 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del } if delTag { - if stdout, err := git.NewCommand(ctx, "tag", "-d", rel.TagName). + if stdout, _, err := git.NewCommand(ctx, "tag", "-d", rel.TagName). SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)). - RunInDir(repo.RepoPath()); err != nil && !strings.Contains(err.Error(), "not found") { + RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err) return fmt.Errorf("git tag -d: %v", err) } diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 2523ca735087..b287d94f9dc6 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -84,9 +84,9 @@ func AdoptRepository(doer, u *user_model.User, opts models.CreateRepoOptions) (* } } - if stdout, err := git.NewCommand(ctx, "update-server-info"). + if stdout, _, err := git.NewCommand(ctx, "update-server-info"). SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). - RunInDir(repoPath); err != nil { + RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("CreateRepository(git update-server-info): %v", err) } diff --git a/services/repository/check.go b/services/repository/check.go index 6962090f8441..6fb86d0dc3c2 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -77,15 +77,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())) var stdout string var err error - if timeout > 0 { - var stdoutBytes []byte - stdoutBytes, err = command.RunInDirTimeout( - timeout, - repo.RepoPath()) - stdout = string(stdoutBytes) - } else { - stdout, err = command.RunInDir(repo.RepoPath()) - } + stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) if err != nil { log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 904512f3f3c3..240cb4fe2c60 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -145,12 +145,11 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user } cmd := git.NewCommand(ctx, args...) - if err := cmd.RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: t.basePath, - Stdout: stdout, - Stderr: stderr, - Stdin: strings.NewReader(opts.Content), + if err := cmd.Run(&git.RunOpts{ + Dir: t.basePath, + Stdout: stdout, + Stderr: stderr, + Stdin: strings.NewReader(opts.Content), }); err != nil { return nil, fmt.Errorf("Error: Stdout: %s\nStderr: %s\nErr: %v", stdout.String(), stderr.String(), err) } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 66c8f0936405..8ebf99138227 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -52,7 +52,7 @@ func (t *TemporaryUploadRepository) Close() { // Clone the base repository to our path and set branch as the HEAD func (t *TemporaryUploadRepository) Clone(branch string) error { - if _, err := git.NewCommand(t.ctx, "clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath).Run(); err != nil { + if _, _, err := git.NewCommand(t.ctx, "clone", "-s", "--bare", "-b", branch, t.repo.RepoPath(), t.basePath).RunStdString(nil); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched { return git.ErrBranchNotExist{ @@ -92,7 +92,7 @@ func (t *TemporaryUploadRepository) Init() error { // SetDefaultIndex sets the git index to our HEAD func (t *TemporaryUploadRepository) SetDefaultIndex() error { - if _, err := git.NewCommand(t.ctx, "read-tree", "HEAD").RunInDir(t.basePath); err != nil { + if _, _, err := git.NewCommand(t.ctx, "read-tree", "HEAD").RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { return fmt.Errorf("SetDefaultIndex: %v", err) } return nil @@ -111,11 +111,10 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro } if err := git.NewCommand(t.ctx, cmdArgs...). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: t.basePath, - Stdout: stdOut, - Stderr: stdErr, + Run(&git.RunOpts{ + Dir: t.basePath, + Stdout: stdOut, + Stderr: stdErr, }); err != nil { log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) @@ -144,12 +143,11 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) er } if err := git.NewCommand(t.ctx, "update-index", "--remove", "-z", "--index-info"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: t.basePath, - Stdin: stdIn, - Stdout: stdOut, - Stderr: stdErr, + Run(&git.RunOpts{ + Dir: t.basePath, + Stdin: stdIn, + Stdout: stdOut, + Stderr: stdErr, }); err != nil { log.Error("Unable to update-index for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) return fmt.Errorf("Unable to update-index for temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) @@ -163,12 +161,11 @@ func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error stdErr := new(bytes.Buffer) if err := git.NewCommand(t.ctx, "hash-object", "-w", "--stdin"). - RunWithContext(&git.RunContext{ - Timeout: -1, - Dir: t.basePath, - Stdin: content, - Stdout: stdOut, - Stderr: stdErr, + Run(&git.RunOpts{ + Dir: t.basePath, + Stdin: content, + Stdout: stdOut, + Stderr: stdErr, }); err != nil { log.Error("Unable to hash-object to temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) return "", fmt.Errorf("Unable to hash-object to temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) @@ -179,7 +176,7 @@ func (t *TemporaryUploadRepository) HashObject(content io.Reader) (string, error // AddObjectToIndex adds the provided object hash to the index with the provided mode and path func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPath string) error { - if _, err := git.NewCommand(t.ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, objectHash, objectPath).RunInDir(t.basePath); err != nil { + if _, _, err := git.NewCommand(t.ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, objectHash, objectPath).RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched { return models.ErrFilePathInvalid{ @@ -195,7 +192,7 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPat // WriteTree writes the current index as a tree to the object db and returns its hash func (t *TemporaryUploadRepository) WriteTree() (string, error) { - stdout, err := git.NewCommand(t.ctx, "write-tree").RunInDir(t.basePath) + stdout, _, err := git.NewCommand(t.ctx, "write-tree").RunStdString(&git.RunOpts{Dir: t.basePath}) if err != nil { log.Error("Unable to write tree in temporary repo: %s(%s): Error: %v", t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to write-tree in temporary repo for: %s Error: %v", t.repo.FullName(), err) @@ -213,7 +210,7 @@ func (t *TemporaryUploadRepository) GetLastCommitByRef(ref string) (string, erro if ref == "" { ref = "HEAD" } - stdout, err := git.NewCommand(t.ctx, "rev-parse", ref).RunInDir(t.basePath) + stdout, _, err := git.NewCommand(t.ctx, "rev-parse", ref).RunStdString(&git.RunOpts{Dir: t.basePath}) if err != nil { log.Error("Unable to get last ref for %s in temporary repo: %s(%s): Error: %v", ref, t.repo.FullName(), t.basePath, err) return "", fmt.Errorf("Unable to rev-parse %s in temporary repo for: %s Error: %v", ref, t.repo.FullName(), err) @@ -300,13 +297,12 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) if err := git.NewCommand(t.ctx, args...). - RunWithContext(&git.RunContext{ - Env: env, - Timeout: -1, - Dir: t.basePath, - Stdin: messageBytes, - Stdout: stdout, - Stderr: stderr, + Run(&git.RunOpts{ + Env: env, + Dir: t.basePath, + Stdin: messageBytes, + Stdout: stdout, + Stderr: stderr, }); err != nil { log.Error("Unable to commit-tree in temporary repo: %s (%s) Error: %v\nStdout: %s\nStderr: %s", t.repo.FullName(), t.basePath, err, stdout, stderr) @@ -357,7 +353,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) { var finalErr error if err := git.NewCommand(t.ctx, "diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD"). - RunWithContext(&git.RunContext{ + Run(&git.RunOpts{ Timeout: 30 * time.Second, Dir: t.basePath, Stdout: stdoutWriter, diff --git a/services/repository/fork.go b/services/repository/fork.go index 1a5d35840815..a2ef75bbd042 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -108,10 +108,10 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork needsRollback = true repoPath := repo_model.RepoPath(owner.Name, repo.Name) - if stdout, err := git.NewCommand(txCtx, + if stdout, _, err := git.NewCommand(txCtx, "clone", "--bare", oldRepoPath, repoPath). SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())). - RunInDirTimeout(10*time.Minute, ""); err != nil { + RunStdBytes(&git.RunOpts{Timeout: 10 * time.Minute}); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) return fmt.Errorf("git clone: %v", err) } @@ -120,9 +120,9 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork return fmt.Errorf("checkDaemonExportOK: %v", err) } - if stdout, err := git.NewCommand(txCtx, "update-server-info"). + if stdout, _, err := git.NewCommand(txCtx, "update-server-info"). SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). - RunInDir(repoPath); err != nil { + RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("git update-server-info: %v", err) } diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index f45e9d08d87b..7998be53c283 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/setting" @@ -31,7 +32,7 @@ import ( ) // Deliver deliver hook task -func Deliver(t *webhook_model.HookTask) error { +func Deliver(ctx context.Context, t *webhook_model.HookTask) error { w, err := webhook_model.GetWebhookByID(t.HookID) if err != nil { return err @@ -178,7 +179,7 @@ func Deliver(t *webhook_model.HookTask) error { return nil } - resp, err := webhookHTTPClient.Do(req.WithContext(graceful.GetManager().ShutdownContext())) + resp, err := webhookHTTPClient.Do(req.WithContext(ctx)) if err != nil { t.ResponseInfo.Body = fmt.Sprintf("Delivery: %v", err) return err @@ -210,6 +211,8 @@ func DeliverHooks(ctx context.Context) { return default: } + ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: DeliverHooks", process.SystemProcessType, true) + defer finished() tasks, err := webhook_model.FindUndeliveredHookTasks() if err != nil { log.Error("DeliverHooks: %v", err) @@ -223,7 +226,7 @@ func DeliverHooks(ctx context.Context) { return default: } - if err = Deliver(t); err != nil { + if err = Deliver(ctx, t); err != nil { log.Error("deliver: %v", err) } } @@ -255,7 +258,7 @@ func DeliverHooks(ctx context.Context) { return default: } - if err = Deliver(t); err != nil { + if err = Deliver(ctx, t); err != nil { log.Error("deliver: %v", err) } } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 9e14b0cefb74..454f54983c10 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -81,7 +81,7 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { return fmt.Errorf("InitRepository: %v", err) } else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { return fmt.Errorf("createDelegateHooks: %v", err) - } else if _, err = git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunInDir(repo.WikiPath()); err != nil { + } else if _, _, err = git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(&git.RunOpts{Dir: repo.WikiPath()}); err != nil { return fmt.Errorf("unable to set default wiki branch to master: %v", err) } return nil diff --git a/templates/admin/process-row.tmpl b/templates/admin/process-row.tmpl index 146ecc7b29b0..2191677a5cee 100644 --- a/templates/admin/process-row.tmpl +++ b/templates/admin/process-row.tmpl @@ -1,11 +1,14 @@
+
{{if eq .Process.Type "request"}}{{svg "octicon-globe" 16 }}{{else if eq .Process.Type "system"}}{{svg "octicon-cpu" 16 }}{{else}}{{svg "octicon-terminal" 16 }}{{end}}
{{.Process.Description}}
{{TimeSince .Process.Start .root.i18n.Lang}}
- {{svg "octicon-trash" 16 "text-red"}} + {{if ne .Process.Type "system"}} + {{svg "octicon-trash" 16 "text-red"}} + {{end}}
diff --git a/templates/admin/process.tmpl b/templates/admin/process.tmpl index 719c10cead3d..c44300dbb759 100644 --- a/templates/admin/process.tmpl +++ b/templates/admin/process.tmpl @@ -1,5 +1,8 @@

{{.i18n.Tr "admin.monitor.process"}} +

diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl index b1f172720ae8..345f59401a1c 100644 --- a/templates/admin/repo/unadopted.tmpl +++ b/templates/admin/repo/unadopted.tmpl @@ -25,7 +25,7 @@ {{range $dirI, $dir := .Dirs}}
- {{svg "octicon-file-directory"}} + {{svg "octicon-file-directory-fill"}} {{$dir}}
diff --git a/templates/admin/stacktrace-row.tmpl b/templates/admin/stacktrace-row.tmpl new file mode 100644 index 000000000000..a21ef72d6327 --- /dev/null +++ b/templates/admin/stacktrace-row.tmpl @@ -0,0 +1,66 @@ +
+
+
+ {{if eq .Process.Type "request"}} + {{svg "octicon-globe" 16 }} + {{else if eq .Process.Type "system"}} + {{svg "octicon-cpu" 16 }} + {{else if eq .Process.Type "normal"}} + {{svg "octicon-terminal" 16 }} + {{else}} + {{svg "octicon-code" 16 }} + {{end}} +
+
+
{{.Process.Description}}
+
{{if ne .Process.Type "none"}}{{TimeSince .Process.Start .root.i18n.Lang}}{{end}}
+
+
+ {{if or (eq .Process.Type "request") (eq .Process.Type "normal") }} + {{svg "octicon-trash" 16 "text-red"}} + {{end}} +
+
+ {{if .Process.Stacks}} +
+ {{range .Process.Stacks}} +
+
+ +
+
+ {{svg "octicon-code" 16 }}{{.Description}}{{if gt .Count 1}} * {{.Count}}{{end}} +
+
+ {{range .Labels}} +
{{.Name}}
{{.Value}}
+ {{end}} +
+
+
+
+ {{range .Entry}} +
+ {{svg "octicon-dot-fill" 16 }} +
+
{{.Function}}
+
{{.File}}:{{.Line}}
+
+
+ {{end}} +
+
+
+ {{end}} +
+ {{end}} + + {{if .Process.Children}} +
+ {{range .Process.Children}} + {{template "admin/stacktrace-row" dict "Process" . "root" $.root}} + {{end}} +
+ {{end}} + +
diff --git a/templates/admin/stacktrace.tmpl b/templates/admin/stacktrace.tmpl new file mode 100644 index 000000000000..68dfbe066d1f --- /dev/null +++ b/templates/admin/stacktrace.tmpl @@ -0,0 +1,33 @@ +{{template "base/head" .}} +
+ {{template "admin/navbar" .}} +
+ {{template "base/alert" .}} +

+ {{.i18n.Tr "admin.monitor.stacktrace"}}: {{.i18n.Tr "admin.monitor.goroutines" .GoroutineCount}} + +

+
+
+ {{range .ProcessStacks}} + {{template "admin/stacktrace-row" dict "Process" . "root" $}} + {{end}} +
+
+
+
+ + +{{template "base/footer" .}} diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl index 1f7b1216a626..ff1b8cadc5a8 100644 --- a/templates/org/menu.tmpl +++ b/templates/org/menu.tmpl @@ -3,9 +3,11 @@ {{svg "octicon-repo"}} {{.i18n.Tr "user.repositories"}} + {{if .IsPackageEnabled}} {{svg "octicon-package"}} {{.i18n.Tr "packages.title"}} + {{end}} {{if .IsOrganizationMember}} {{svg "octicon-organization"}} {{$.i18n.Tr "org.people"}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index bc56041c7d8d..ef0ab866f53e 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -62,7 +62,7 @@ {{if $entry.IsDir}} {{$subJumpablePathName := $entry.GetSubJumpablePathName}} {{$subJumpablePath := SubJumpablePath $subJumpablePathName}} - {{svg "octicon-file-directory"}} + {{svg "octicon-file-directory-fill"}} {{if eq (len $subJumpablePath) 2}} {{index $subJumpablePath 0}}{{index $subJumpablePath 1}} diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index d761b84d6d62..34ecf1afe24a 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -87,9 +87,11 @@ {{svg "octicon-repo"}} {{.i18n.Tr "user.repositories"}} + {{if .IsPackageEnabled}} {{svg "octicon-package"}} {{.i18n.Tr "packages.title"}} + {{end}} {{svg "octicon-rss"}} {{.i18n.Tr "user.activity"}} diff --git a/templates/user/settings/repos.tmpl b/templates/user/settings/repos.tmpl index 0e19b6e3f2bc..31f4ca6a17f2 100644 --- a/templates/user/settings/repos.tmpl +++ b/templates/user/settings/repos.tmpl @@ -33,7 +33,7 @@ {{$repo.BaseRepo.OwnerName}}/{{$repo.BaseRepo.Name}} {{end}} {{else}} - {{svg "octicon-file-directory"}} + {{svg "octicon-file-directory-fill"}} {{$.Owner.Name}}/{{$dir}}
{{if $.allowAdopt}} diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 4b17dd78029c..5801426d64f4 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -334,7 +334,7 @@ margin-right: 10px; } - &.octicon-file-directory, + &.octicon-file-directory-fill, &.octicon-file-submodule { color: var(--color-primary); }