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.

139 lines
3.5 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. "fmt"
  8. "io"
  9. "os/exec"
  10. "strings"
  11. "time"
  12. )
  13. // Command represents a command with its subcommands or arguments.
  14. type Command struct {
  15. name string
  16. args []string
  17. }
  18. func (c *Command) String() string {
  19. if len(c.args) == 0 {
  20. return c.name
  21. }
  22. return fmt.Sprintf("%s %s", c.name, strings.Join(c.args, " "))
  23. }
  24. // NewCommand creates and returns a new Git Command based on given command and arguments.
  25. func NewCommand(args ...string) *Command {
  26. return &Command{
  27. name: "git",
  28. args: args,
  29. }
  30. }
  31. // AddArguments adds new argument(s) to the command.
  32. func (c *Command) AddArguments(args ...string) *Command {
  33. c.args = append(c.args, args...)
  34. return c
  35. }
  36. const DEFAULT_TIMEOUT = 60 * time.Second
  37. // RunInDirTimeoutPipeline executes the command in given directory with given timeout,
  38. // it pipes stdout and stderr to given io.Writer.
  39. func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error {
  40. if timeout == -1 {
  41. timeout = DEFAULT_TIMEOUT
  42. }
  43. if len(dir) == 0 {
  44. log(c.String())
  45. } else {
  46. log("%s: %v", dir, c)
  47. }
  48. cmd := exec.Command(c.name, c.args...)
  49. cmd.Dir = dir
  50. cmd.Stdout = stdout
  51. cmd.Stderr = stderr
  52. if err := cmd.Start(); err != nil {
  53. return err
  54. }
  55. done := make(chan error)
  56. go func() {
  57. done <- cmd.Wait()
  58. }()
  59. var err error
  60. select {
  61. case <-time.After(timeout):
  62. if cmd.Process != nil && cmd.ProcessState != nil && !cmd.ProcessState.Exited() {
  63. if err := cmd.Process.Kill(); err != nil {
  64. return fmt.Errorf("fail to kill process: %v", err)
  65. }
  66. }
  67. <-done
  68. return ErrExecTimeout{timeout}
  69. case err = <-done:
  70. }
  71. return err
  72. }
  73. // RunInDirTimeout executes the command in given directory with given timeout,
  74. // and returns stdout in []byte and error (combined with stderr).
  75. func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) {
  76. stdout := new(bytes.Buffer)
  77. stderr := new(bytes.Buffer)
  78. if err := c.RunInDirTimeoutPipeline(timeout, dir, stdout, stderr); err != nil {
  79. return nil, concatenateError(err, stderr.String())
  80. }
  81. if stdout.Len() > 0 {
  82. log("stdout:\n%s", stdout.Bytes()[:1024])
  83. }
  84. return stdout.Bytes(), nil
  85. }
  86. // RunInDirPipeline executes the command in given directory,
  87. // it pipes stdout and stderr to given io.Writer.
  88. func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error {
  89. return c.RunInDirTimeoutPipeline(-1, dir, stdout, stderr)
  90. }
  91. // RunInDir executes the command in given directory
  92. // and returns stdout in []byte and error (combined with stderr).
  93. func (c *Command) RunInDirBytes(dir string) ([]byte, error) {
  94. return c.RunInDirTimeout(-1, dir)
  95. }
  96. // RunInDir executes the command in given directory
  97. // and returns stdout in string and error (combined with stderr).
  98. func (c *Command) RunInDir(dir string) (string, error) {
  99. stdout, err := c.RunInDirTimeout(-1, dir)
  100. if err != nil {
  101. return "", err
  102. }
  103. return string(stdout), nil
  104. }
  105. // RunTimeout executes the command in defualt working directory with given timeout,
  106. // and returns stdout in string and error (combined with stderr).
  107. func (c *Command) RunTimeout(timeout time.Duration) (string, error) {
  108. stdout, err := c.RunInDirTimeout(timeout, "")
  109. if err != nil {
  110. return "", err
  111. }
  112. return string(stdout), nil
  113. }
  114. // Run executes the command in defualt working directory
  115. // and returns stdout in string and error (combined with stderr).
  116. func (c *Command) Run() (string, error) {
  117. return c.RunTimeout(-1)
  118. }