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

hook.go 6.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. "fmt"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. "code.gitea.io/git"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/private"
  18. "code.gitea.io/gitea/modules/setting"
  19. "github.com/urfave/cli"
  20. )
  21. var (
  22. // CmdHook represents the available hooks sub-command.
  23. CmdHook = cli.Command{
  24. Name: "hook",
  25. Usage: "Delegate commands to corresponding Git hooks",
  26. Description: "This should only be called by Git",
  27. Flags: []cli.Flag{
  28. cli.StringFlag{
  29. Name: "config, c",
  30. Value: "custom/conf/app.ini",
  31. Usage: "Custom configuration file path",
  32. },
  33. },
  34. Subcommands: []cli.Command{
  35. subcmdHookPreReceive,
  36. subcmdHookUpdate,
  37. subcmdHookPostReceive,
  38. },
  39. }
  40. subcmdHookPreReceive = cli.Command{
  41. Name: "pre-receive",
  42. Usage: "Delegate pre-receive Git hook",
  43. Description: "This command should only be called by Git",
  44. Action: runHookPreReceive,
  45. }
  46. subcmdHookUpdate = cli.Command{
  47. Name: "update",
  48. Usage: "Delegate update Git hook",
  49. Description: "This command should only be called by Git",
  50. Action: runHookUpdate,
  51. }
  52. subcmdHookPostReceive = cli.Command{
  53. Name: "post-receive",
  54. Usage: "Delegate post-receive Git hook",
  55. Description: "This command should only be called by Git",
  56. Action: runHookPostReceive,
  57. }
  58. )
  59. func hookSetup(logPath string) {
  60. setting.NewContext()
  61. log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
  62. models.LoadConfigs()
  63. }
  64. func runHookPreReceive(c *cli.Context) error {
  65. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  66. return nil
  67. }
  68. if c.IsSet("config") {
  69. setting.CustomConf = c.String("config")
  70. } else if c.GlobalIsSet("config") {
  71. setting.CustomConf = c.GlobalString("config")
  72. }
  73. hookSetup("hooks/pre-receive.log")
  74. // the environment setted on serv command
  75. repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
  76. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  77. username := os.Getenv(models.EnvRepoUsername)
  78. reponame := os.Getenv(models.EnvRepoName)
  79. userIDStr := os.Getenv(models.EnvPusherID)
  80. repoPath := models.RepoPath(username, reponame)
  81. buf := bytes.NewBuffer(nil)
  82. scanner := bufio.NewScanner(os.Stdin)
  83. for scanner.Scan() {
  84. buf.Write(scanner.Bytes())
  85. buf.WriteByte('\n')
  86. // TODO: support news feeds for wiki
  87. if isWiki {
  88. continue
  89. }
  90. fields := bytes.Fields(scanner.Bytes())
  91. if len(fields) != 3 {
  92. continue
  93. }
  94. oldCommitID := string(fields[0])
  95. newCommitID := string(fields[1])
  96. refFullName := string(fields[2])
  97. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  98. protectBranch, err := private.GetProtectedBranchBy(repoID, branchName)
  99. if err != nil {
  100. log.GitLogger.Fatal(2, "retrieve protected branches information failed")
  101. }
  102. if protectBranch != nil && protectBranch.IsProtected() {
  103. // detect force push
  104. if git.EmptySHA != oldCommitID {
  105. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDir(repoPath)
  106. if err != nil {
  107. fail("Internal error", "Fail to detect force push: %v", err)
  108. } else if len(output) > 0 {
  109. fail(fmt.Sprintf("branch %s is protected from force push", branchName), "")
  110. }
  111. }
  112. // check and deletion
  113. if newCommitID == git.EmptySHA {
  114. fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
  115. } else {
  116. userID, _ := strconv.ParseInt(userIDStr, 10, 64)
  117. canPush, err := private.CanUserPush(protectBranch.ID, userID)
  118. if err != nil {
  119. fail("Internal error", "Fail to detect user can push: %v", err)
  120. } else if !canPush {
  121. fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
  122. }
  123. }
  124. }
  125. }
  126. return nil
  127. }
  128. func runHookUpdate(c *cli.Context) error {
  129. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  130. return nil
  131. }
  132. if c.IsSet("config") {
  133. setting.CustomConf = c.String("config")
  134. } else if c.GlobalIsSet("config") {
  135. setting.CustomConf = c.GlobalString("config")
  136. }
  137. hookSetup("hooks/update.log")
  138. return nil
  139. }
  140. func runHookPostReceive(c *cli.Context) error {
  141. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  142. return nil
  143. }
  144. if c.IsSet("config") {
  145. setting.CustomConf = c.String("config")
  146. } else if c.GlobalIsSet("config") {
  147. setting.CustomConf = c.GlobalString("config")
  148. }
  149. hookSetup("hooks/post-receive.log")
  150. // the environment setted on serv command
  151. repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
  152. repoUser := os.Getenv(models.EnvRepoUsername)
  153. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  154. repoName := os.Getenv(models.EnvRepoName)
  155. pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
  156. pusherName := os.Getenv(models.EnvPusherName)
  157. buf := bytes.NewBuffer(nil)
  158. scanner := bufio.NewScanner(os.Stdin)
  159. for scanner.Scan() {
  160. buf.Write(scanner.Bytes())
  161. buf.WriteByte('\n')
  162. // TODO: support news feeds for wiki
  163. if isWiki {
  164. continue
  165. }
  166. fields := bytes.Fields(scanner.Bytes())
  167. if len(fields) != 3 {
  168. continue
  169. }
  170. oldCommitID := string(fields[0])
  171. newCommitID := string(fields[1])
  172. refFullName := string(fields[2])
  173. if err := private.PushUpdate(models.PushUpdateOptions{
  174. RefFullName: refFullName,
  175. OldCommitID: oldCommitID,
  176. NewCommitID: newCommitID,
  177. PusherID: pusherID,
  178. PusherName: pusherName,
  179. RepoUserName: repoUser,
  180. RepoName: repoName,
  181. }); err != nil {
  182. log.GitLogger.Error(2, "Update: %v", err)
  183. }
  184. if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
  185. branch := strings.TrimPrefix(refFullName, git.BranchPrefix)
  186. repo, pullRequestAllowed, err := private.GetRepository(repoID)
  187. if err != nil {
  188. log.GitLogger.Error(2, "get repo: %v", err)
  189. break
  190. }
  191. if !pullRequestAllowed {
  192. break
  193. }
  194. baseRepo := repo
  195. if repo.IsFork {
  196. baseRepo = repo.BaseRepo
  197. }
  198. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  199. break
  200. }
  201. pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch)
  202. if err != nil {
  203. log.GitLogger.Error(2, "get active pr: %v", err)
  204. break
  205. }
  206. fmt.Fprintln(os.Stderr, "")
  207. if pr == nil {
  208. if repo.IsFork {
  209. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  210. }
  211. fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
  212. fmt.Fprintf(os.Stderr, " %s/compare/%s...%s\n", baseRepo.HTMLURL(), url.QueryEscape(baseRepo.DefaultBranch), url.QueryEscape(branch))
  213. } else {
  214. fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
  215. fmt.Fprintf(os.Stderr, " %s/pulls/%d\n", baseRepo.HTMLURL(), pr.Index)
  216. }
  217. fmt.Fprintln(os.Stderr, "")
  218. }
  219. }
  220. return nil
  221. }