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.

233 lines
6.2 KiB

  1. // Copyright 2017 The Gitea 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 cmd
  5. import (
  6. "bufio"
  7. "bytes"
  8. "crypto/tls"
  9. "fmt"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "code.gitea.io/git"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/modules/base"
  16. "code.gitea.io/gitea/modules/httplib"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "github.com/Unknwon/com"
  20. "github.com/urfave/cli"
  21. )
  22. var (
  23. // CmdHook represents the available hooks sub-command.
  24. CmdHook = cli.Command{
  25. Name: "hook",
  26. Usage: "Delegate commands to corresponding Git hooks",
  27. Description: "This should only be called by Git",
  28. Flags: []cli.Flag{
  29. cli.StringFlag{
  30. Name: "config, c",
  31. Value: "custom/conf/app.ini",
  32. Usage: "Custom configuration file path",
  33. },
  34. },
  35. Subcommands: []cli.Command{
  36. subcmdHookPreReceive,
  37. subcmdHookUpadte,
  38. subcmdHookPostReceive,
  39. },
  40. }
  41. subcmdHookPreReceive = cli.Command{
  42. Name: "pre-receive",
  43. Usage: "Delegate pre-receive Git hook",
  44. Description: "This command should only be called by Git",
  45. Action: runHookPreReceive,
  46. }
  47. subcmdHookUpadte = cli.Command{
  48. Name: "update",
  49. Usage: "Delegate update Git hook",
  50. Description: "This command should only be called by Git",
  51. Action: runHookUpdate,
  52. }
  53. subcmdHookPostReceive = cli.Command{
  54. Name: "post-receive",
  55. Usage: "Delegate post-receive Git hook",
  56. Description: "This command should only be called by Git",
  57. Action: runHookPostReceive,
  58. }
  59. )
  60. func runHookPreReceive(c *cli.Context) error {
  61. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  62. return nil
  63. }
  64. if c.IsSet("config") {
  65. setting.CustomConf = c.String("config")
  66. } else if c.GlobalIsSet("config") {
  67. setting.CustomConf = c.GlobalString("config")
  68. }
  69. if err := setup("hooks/pre-receive.log"); err != nil {
  70. fail("Hook pre-receive init failed", fmt.Sprintf("setup: %v", err))
  71. }
  72. // the environment setted on serv command
  73. repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
  74. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  75. //username := os.Getenv(models.EnvRepoUsername)
  76. //reponame := os.Getenv(models.EnvRepoName)
  77. //repoPath := models.RepoPath(username, reponame)
  78. buf := bytes.NewBuffer(nil)
  79. scanner := bufio.NewScanner(os.Stdin)
  80. for scanner.Scan() {
  81. buf.Write(scanner.Bytes())
  82. buf.WriteByte('\n')
  83. // TODO: support news feeds for wiki
  84. if isWiki {
  85. continue
  86. }
  87. fields := bytes.Fields(scanner.Bytes())
  88. if len(fields) != 3 {
  89. continue
  90. }
  91. //oldCommitID := string(fields[0])
  92. newCommitID := string(fields[1])
  93. refFullName := string(fields[2])
  94. // FIXME: when we add feature to protected branch to deny force push, then uncomment below
  95. /*var isForce bool
  96. // detect force push
  97. if git.EmptySHA != oldCommitID {
  98. output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).RunInDir(repoPath)
  99. if err != nil {
  100. fail("Internal error", "Fail to detect force push: %v", err)
  101. } else if len(output) > 0 {
  102. isForce = true
  103. }
  104. }*/
  105. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  106. protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
  107. if err != nil {
  108. log.GitLogger.Fatal(2, "retrieve protected branches information failed")
  109. }
  110. if protectBranch != nil {
  111. // check and deletion
  112. if newCommitID == git.EmptySHA {
  113. fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
  114. } else {
  115. fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
  116. //fail(fmt.Sprintf("branch %s is protected from force push", branchName), "")
  117. }
  118. }
  119. }
  120. return nil
  121. }
  122. func runHookUpdate(c *cli.Context) error {
  123. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  124. return nil
  125. }
  126. if c.IsSet("config") {
  127. setting.CustomConf = c.String("config")
  128. } else if c.GlobalIsSet("config") {
  129. setting.CustomConf = c.GlobalString("config")
  130. }
  131. if err := setup("hooks/update.log"); err != nil {
  132. fail("Hook update init failed", fmt.Sprintf("setup: %v", err))
  133. }
  134. return nil
  135. }
  136. func runHookPostReceive(c *cli.Context) error {
  137. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  138. return nil
  139. }
  140. if c.IsSet("config") {
  141. setting.CustomConf = c.String("config")
  142. } else if c.GlobalIsSet("config") {
  143. setting.CustomConf = c.GlobalString("config")
  144. }
  145. if err := setup("hooks/post-receive.log"); err != nil {
  146. fail("Hook post-receive init failed", fmt.Sprintf("setup: %v", err))
  147. }
  148. // the environment setted on serv command
  149. repoUser := os.Getenv(models.EnvRepoUsername)
  150. repoUserSalt := os.Getenv(models.EnvRepoUserSalt)
  151. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  152. repoName := os.Getenv(models.EnvRepoName)
  153. pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
  154. pusherName := os.Getenv(models.EnvPusherName)
  155. buf := bytes.NewBuffer(nil)
  156. scanner := bufio.NewScanner(os.Stdin)
  157. for scanner.Scan() {
  158. buf.Write(scanner.Bytes())
  159. buf.WriteByte('\n')
  160. // TODO: support news feeds for wiki
  161. if isWiki {
  162. continue
  163. }
  164. fields := bytes.Fields(scanner.Bytes())
  165. if len(fields) != 3 {
  166. continue
  167. }
  168. oldCommitID := string(fields[0])
  169. newCommitID := string(fields[1])
  170. refFullName := string(fields[2])
  171. if err := models.PushUpdate(models.PushUpdateOptions{
  172. RefFullName: refFullName,
  173. OldCommitID: oldCommitID,
  174. NewCommitID: newCommitID,
  175. PusherID: pusherID,
  176. PusherName: pusherName,
  177. RepoUserName: repoUser,
  178. RepoName: repoName,
  179. }); err != nil {
  180. log.GitLogger.Error(2, "Update: %v", err)
  181. }
  182. // Ask for running deliver hook and test pull request tasks.
  183. reqURL := setting.LocalURL + repoUser + "/" + repoName + "/tasks/trigger?branch=" +
  184. strings.TrimPrefix(refFullName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUserSalt) + "&pusher=" + com.ToStr(pusherID)
  185. log.GitLogger.Trace("Trigger task: %s", reqURL)
  186. resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
  187. InsecureSkipVerify: true,
  188. }).Response()
  189. if err == nil {
  190. resp.Body.Close()
  191. if resp.StatusCode/100 != 2 {
  192. log.GitLogger.Error(2, "Failed to trigger task: not 2xx response code")
  193. }
  194. } else {
  195. log.GitLogger.Error(2, "Failed to trigger task: %v", err)
  196. }
  197. }
  198. return nil
  199. }