You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.9 KiB

  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package git
  5. import (
  6. "bytes"
  7. "context"
  8. "fmt"
  9. "io"
  10. "os/exec"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/modules/process"
  14. )
  15. var (
  16. // GlobalCommandArgs global command args for external package setting
  17. GlobalCommandArgs []string
  18. // DefaultCommandExecutionTimeout default command execution timeout duration
  19. DefaultCommandExecutionTimeout = 60 * time.Second
  20. )
  21. // Command represents a command with its subcommands or arguments.
  22. type Command struct {
  23. name string
  24. args []string
  25. }
  26. func (c *Command) String() string {
  27. if len(c.args) == 0 {
  28. return c.name
  29. }
  30. return fmt.Sprintf("%s %s", c.name, strings.Join(c.args, " "))
  31. }
  32. // NewCommand creates and returns a new Git Command based on given command and arguments.
  33. func NewCommand(args ...string) *Command {
  34. // Make an explicit copy of GlobalCommandArgs, otherwise append might overwrite it
  35. cargs := make([]string, len(GlobalCommandArgs))
  36. copy(cargs, GlobalCommandArgs)
  37. return &Command{
  38. name: GitExecutable,
  39. args: append(cargs, args...),
  40. }
  41. }
  42. // AddArguments adds new argument(s) to the command.
  43. func (c *Command) AddArguments(args ...string) *Command {
  44. c.args = append(c.args, args...)
  45. return c
  46. }
  47. // RunInDirTimeoutEnvPipeline executes the command in given directory with given timeout,
  48. // it pipes stdout and stderr to given io.Writer.
  49. func (c *Command) RunInDirTimeoutEnvPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer) error {
  50. return c.RunInDirTimeoutEnvFullPipeline(env, timeout, dir, stdout, stderr, nil)
  51. }
  52. // RunInDirTimeoutEnvFullPipeline executes the command in given directory with given timeout,
  53. // it pipes stdout and stderr to given io.Writer and passes in an io.Reader as stdin.
  54. func (c *Command) RunInDirTimeoutEnvFullPipeline(env []string, timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error {
  55. if timeout == -1 {
  56. timeout = DefaultCommandExecutionTimeout
  57. }
  58. if len(dir) == 0 {
  59. log(c.String())
  60. } else {
  61. log("%s: %v", dir, c)
  62. }
  63. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  64. defer cancel()
  65. cmd := exec.CommandContext(ctx, c.name, c.args...)
  66. cmd.Env = env
  67. cmd.Dir = dir
  68. cmd.Stdout = stdout
  69. cmd.Stderr = stderr
  70. cmd.Stdin = stdin
  71. if err := cmd.Start(); err != nil {
  72. return err
  73. }
  74. pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), dir), cmd)
  75. defer process.GetManager().Remove(pid)
  76. if err := cmd.Wait(); err != nil {
  77. return err
  78. }
  79. return ctx.Err()
  80. }
  81. // RunInDirTimeoutPipeline executes the command in given directory with given timeout,
  82. // it pipes stdout and stderr to given io.Writer.
  83. func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error {
  84. return c.RunInDirTimeoutEnvPipeline(nil, timeout, dir, stdout, stderr)
  85. }
  86. // RunInDirTimeoutFullPipeline executes the command in given directory with given timeout,
  87. // it pipes stdout and stderr to given io.Writer, and stdin from the given io.Reader
  88. func (c *Command) RunInDirTimeoutFullPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer, stdin io.Reader) error {
  89. return c.RunInDirTimeoutEnvFullPipeline(nil, timeout, dir, stdout, stderr, stdin)
  90. }
  91. // RunInDirTimeout executes the command in given directory with given timeout,
  92. // and returns stdout in []byte and error (combined with stderr).
  93. func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) {
  94. return c.RunInDirTimeoutEnv(nil, timeout, dir)
  95. }
  96. // RunInDirTimeoutEnv executes the command in given directory with given timeout,
  97. // and returns stdout in []byte and error (combined with stderr).
  98. func (c *Command) RunInDirTimeoutEnv(env []string, timeout time.Duration, dir string) ([]byte, error) {
  99. stdout := new(bytes.Buffer)
  100. stderr := new(bytes.Buffer)
  101. if err := c.RunInDirTimeoutEnvPipeline(env, timeout, dir, stdout, stderr); err != nil {
  102. return nil, concatenateError(err, stderr.String())
  103. }
  104. if stdout.Len() > 0 {
  105. log("stdout:\n%s", stdout.Bytes()[:1024])
  106. }
  107. return stdout.Bytes(), nil
  108. }
  109. // RunInDirPipeline executes the command in given directory,
  110. // it pipes stdout and stderr to given io.Writer.
  111. func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error {
  112. return c.RunInDirFullPipeline(dir, stdout, stderr, nil)
  113. }
  114. // RunInDirFullPipeline executes the command in given directory,
  115. // it pipes stdout and stderr to given io.Writer.
  116. func (c *Command) RunInDirFullPipeline(dir string, stdout, stderr io.Writer, stdin io.Reader) error {
  117. return c.RunInDirTimeoutFullPipeline(-1, dir, stdout, stderr, stdin)
  118. }
  119. // RunInDirBytes executes the command in given directory
  120. // and returns stdout in []byte and error (combined with stderr).
  121. func (c *Command) RunInDirBytes(dir string) ([]byte, error) {
  122. return c.RunInDirTimeout(-1, dir)
  123. }
  124. // RunInDir executes the command in given directory
  125. // and returns stdout in string and error (combined with stderr).
  126. func (c *Command) RunInDir(dir string) (string, error) {
  127. return c.RunInDirWithEnv(dir, nil)
  128. }
  129. // RunInDirWithEnv executes the command in given directory
  130. // and returns stdout in string and error (combined with stderr).
  131. func (c *Command) RunInDirWithEnv(dir string, env []string) (string, error) {
  132. stdout, err := c.RunInDirTimeoutEnv(env, -1, dir)
  133. if err != nil {
  134. return "", err
  135. }
  136. return string(stdout), nil
  137. }
  138. // RunTimeout executes the command in default working directory with given timeout,
  139. // and returns stdout in string and error (combined with stderr).
  140. func (c *Command) RunTimeout(timeout time.Duration) (string, error) {
  141. stdout, err := c.RunInDirTimeout(timeout, "")
  142. if err != nil {
  143. return "", err
  144. }
  145. return string(stdout), nil
  146. }
  147. // Run executes the command in default working directory
  148. // and returns stdout in string and error (combined with stderr).
  149. func (c *Command) Run() (string, error) {
  150. return c.RunTimeout(-1)
  151. }