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.

git.go 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package git
  6. import (
  7. "context"
  8. "fmt"
  9. "os/exec"
  10. "runtime"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/modules/process"
  14. "code.gitea.io/gitea/modules/setting"
  15. "github.com/hashicorp/go-version"
  16. )
  17. var (
  18. // Prefix the log prefix
  19. Prefix = "[git-module] "
  20. // GitVersionRequired is the minimum Git version required
  21. GitVersionRequired = "1.7.2"
  22. // GitExecutable is the command name of git
  23. // Could be updated to an absolute path while initialization
  24. GitExecutable = "git"
  25. // DefaultContext is the default context to run git commands in
  26. // will be overwritten by Init with HammerContext
  27. DefaultContext = context.Background()
  28. gitVersion *version.Version
  29. // will be checked on Init
  30. goVersionLessThan115 = true
  31. )
  32. // LocalVersion returns current Git version from shell.
  33. func LocalVersion() (*version.Version, error) {
  34. if err := LoadGitVersion(); err != nil {
  35. return nil, err
  36. }
  37. return gitVersion, nil
  38. }
  39. // LoadGitVersion returns current Git version from shell.
  40. func LoadGitVersion() error {
  41. // doesn't need RWMutex because its exec by Init()
  42. if gitVersion != nil {
  43. return nil
  44. }
  45. stdout, err := NewCommand("version").Run()
  46. if err != nil {
  47. return err
  48. }
  49. fields := strings.Fields(stdout)
  50. if len(fields) < 3 {
  51. return fmt.Errorf("not enough output: %s", stdout)
  52. }
  53. var versionString string
  54. // Handle special case on Windows.
  55. i := strings.Index(fields[2], "windows")
  56. if i >= 1 {
  57. versionString = fields[2][:i-1]
  58. } else {
  59. versionString = fields[2]
  60. }
  61. gitVersion, err = version.NewVersion(versionString)
  62. return err
  63. }
  64. // SetExecutablePath changes the path of git executable and checks the file permission and version.
  65. func SetExecutablePath(path string) error {
  66. // If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
  67. if path != "" {
  68. GitExecutable = path
  69. }
  70. absPath, err := exec.LookPath(GitExecutable)
  71. if err != nil {
  72. return fmt.Errorf("Git not found: %v", err)
  73. }
  74. GitExecutable = absPath
  75. err = LoadGitVersion()
  76. if err != nil {
  77. return fmt.Errorf("Git version missing: %v", err)
  78. }
  79. versionRequired, err := version.NewVersion(GitVersionRequired)
  80. if err != nil {
  81. return err
  82. }
  83. if gitVersion.LessThan(versionRequired) {
  84. return fmt.Errorf("Git version not supported. Requires version > %v", GitVersionRequired)
  85. }
  86. return nil
  87. }
  88. // VersionInfo returns git version information
  89. func VersionInfo() string {
  90. var format = "Git Version: %s"
  91. var args = []interface{}{gitVersion.Original()}
  92. // Since git wire protocol has been released from git v2.18
  93. if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
  94. format += ", Wire Protocol %s Enabled"
  95. args = append(args, "Version 2") // for focus color
  96. }
  97. return fmt.Sprintf(format, args...)
  98. }
  99. // Init initializes git module
  100. func Init(ctx context.Context) error {
  101. DefaultContext = ctx
  102. defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
  103. if err := SetExecutablePath(setting.Git.Path); err != nil {
  104. return err
  105. }
  106. // force cleanup args
  107. GlobalCommandArgs = []string{}
  108. if CheckGitVersionAtLeast("2.9") == nil {
  109. // Explicitly disable credential helper, otherwise Git credentials might leak
  110. GlobalCommandArgs = append(GlobalCommandArgs, "-c", "credential.helper=")
  111. }
  112. // Since git wire protocol has been released from git v2.18
  113. if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
  114. GlobalCommandArgs = append(GlobalCommandArgs, "-c", "protocol.version=2")
  115. }
  116. // Save current git version on init to gitVersion otherwise it would require an RWMutex
  117. if err := LoadGitVersion(); err != nil {
  118. return err
  119. }
  120. // Save if the go version used to compile gitea is greater or equal 1.15
  121. runtimeVersion, err := version.NewVersion(strings.TrimPrefix(runtime.Version(), "go"))
  122. if err != nil {
  123. return err
  124. }
  125. version115, _ := version.NewVersion("1.15")
  126. goVersionLessThan115 = runtimeVersion.LessThan(version115)
  127. // Git requires setting user.name and user.email in order to commit changes - if they're not set just add some defaults
  128. for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} {
  129. if err := checkAndSetConfig(configKey, defaultValue, false); err != nil {
  130. return err
  131. }
  132. }
  133. // Set git some configurations - these must be set to these values for gitea to work correctly
  134. if err := checkAndSetConfig("core.quotePath", "false", true); err != nil {
  135. return err
  136. }
  137. if CheckGitVersionAtLeast("2.10") == nil {
  138. if err := checkAndSetConfig("receive.advertisePushOptions", "true", true); err != nil {
  139. return err
  140. }
  141. }
  142. if CheckGitVersionAtLeast("2.18") == nil {
  143. if err := checkAndSetConfig("core.commitGraph", "true", true); err != nil {
  144. return err
  145. }
  146. if err := checkAndSetConfig("gc.writeCommitGraph", "true", true); err != nil {
  147. return err
  148. }
  149. }
  150. if runtime.GOOS == "windows" {
  151. if err := checkAndSetConfig("core.longpaths", "true", true); err != nil {
  152. return err
  153. }
  154. }
  155. return nil
  156. }
  157. // CheckGitVersionAtLeast check git version is at least the constraint version
  158. func CheckGitVersionAtLeast(atLeast string) error {
  159. if err := LoadGitVersion(); err != nil {
  160. return err
  161. }
  162. atLeastVersion, err := version.NewVersion(atLeast)
  163. if err != nil {
  164. return err
  165. }
  166. if gitVersion.Compare(atLeastVersion) < 0 {
  167. return fmt.Errorf("installed git binary version %s is not at least %s", gitVersion.Original(), atLeast)
  168. }
  169. return nil
  170. }
  171. func checkAndSetConfig(key, defaultValue string, forceToDefault bool) error {
  172. stdout, stderr, err := process.GetManager().Exec("git.Init(get setting)", GitExecutable, "config", "--get", key)
  173. if err != nil {
  174. perr, ok := err.(*process.Error)
  175. if !ok {
  176. return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
  177. }
  178. eerr, ok := perr.Err.(*exec.ExitError)
  179. if !ok || eerr.ExitCode() != 1 {
  180. return fmt.Errorf("Failed to get git %s(%v) errType %T: %s", key, err, err, stderr)
  181. }
  182. }
  183. currValue := strings.TrimSpace(stdout)
  184. if currValue == defaultValue || (!forceToDefault && len(currValue) > 0) {
  185. return nil
  186. }
  187. if _, stderr, err = process.GetManager().Exec(fmt.Sprintf("git.Init(set %s)", key), "git", "config", "--global", key, defaultValue); err != nil {
  188. return fmt.Errorf("Failed to set git %s(%s): %s", key, err, stderr)
  189. }
  190. return nil
  191. }
  192. // Fsck verifies the connectivity and validity of the objects in the database
  193. func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
  194. // Make sure timeout makes sense.
  195. if timeout <= 0 {
  196. timeout = -1
  197. }
  198. _, err := NewCommandContext(ctx, "fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
  199. return err
  200. }