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 9.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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/http"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/private"
  16. "code.gitea.io/gitea/modules/setting"
  17. "github.com/urfave/cli"
  18. )
  19. const (
  20. hookBatchSize = 30
  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. Subcommands: []cli.Command{
  29. subcmdHookPreReceive,
  30. subcmdHookUpdate,
  31. subcmdHookPostReceive,
  32. },
  33. }
  34. subcmdHookPreReceive = cli.Command{
  35. Name: "pre-receive",
  36. Usage: "Delegate pre-receive Git hook",
  37. Description: "This command should only be called by Git",
  38. Action: runHookPreReceive,
  39. }
  40. subcmdHookUpdate = cli.Command{
  41. Name: "update",
  42. Usage: "Delegate update Git hook",
  43. Description: "This command should only be called by Git",
  44. Action: runHookUpdate,
  45. }
  46. subcmdHookPostReceive = cli.Command{
  47. Name: "post-receive",
  48. Usage: "Delegate post-receive Git hook",
  49. Description: "This command should only be called by Git",
  50. Action: runHookPostReceive,
  51. }
  52. )
  53. func runHookPreReceive(c *cli.Context) error {
  54. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  55. if setting.OnlyAllowPushIfGiteaEnvironmentSet {
  56. fail(`Rejecting changes as Gitea environment not set.
  57. If you are pushing over SSH you must push with a key managed by
  58. Gitea or set your environment appropriately.`, "")
  59. } else {
  60. return nil
  61. }
  62. }
  63. setup("hooks/pre-receive.log", false)
  64. // the environment setted on serv command
  65. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  66. username := os.Getenv(models.EnvRepoUsername)
  67. reponame := os.Getenv(models.EnvRepoName)
  68. userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
  69. prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64)
  70. isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey))
  71. hookOptions := private.HookOptions{
  72. UserID: userID,
  73. GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
  74. GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
  75. GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
  76. ProtectedBranchID: prID,
  77. IsDeployKey: isDeployKey,
  78. }
  79. scanner := bufio.NewScanner(os.Stdin)
  80. oldCommitIDs := make([]string, hookBatchSize)
  81. newCommitIDs := make([]string, hookBatchSize)
  82. refFullNames := make([]string, hookBatchSize)
  83. count := 0
  84. total := 0
  85. lastline := 0
  86. for scanner.Scan() {
  87. // TODO: support news feeds for wiki
  88. if isWiki {
  89. continue
  90. }
  91. fields := bytes.Fields(scanner.Bytes())
  92. if len(fields) != 3 {
  93. continue
  94. }
  95. oldCommitID := string(fields[0])
  96. newCommitID := string(fields[1])
  97. refFullName := string(fields[2])
  98. total++
  99. lastline++
  100. // If the ref is a branch, check if it's protected
  101. if strings.HasPrefix(refFullName, git.BranchPrefix) {
  102. oldCommitIDs[count] = oldCommitID
  103. newCommitIDs[count] = newCommitID
  104. refFullNames[count] = refFullName
  105. count++
  106. fmt.Fprintf(os.Stdout, "*")
  107. os.Stdout.Sync()
  108. if count >= hookBatchSize {
  109. fmt.Fprintf(os.Stdout, " Checking %d branches\n", count)
  110. os.Stdout.Sync()
  111. hookOptions.OldCommitIDs = oldCommitIDs
  112. hookOptions.NewCommitIDs = newCommitIDs
  113. hookOptions.RefFullNames = refFullNames
  114. statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
  115. switch statusCode {
  116. case http.StatusOK:
  117. // no-op
  118. case http.StatusInternalServerError:
  119. fail("Internal Server Error", msg)
  120. default:
  121. fail(msg, "")
  122. }
  123. count = 0
  124. lastline = 0
  125. }
  126. } else {
  127. fmt.Fprintf(os.Stdout, ".")
  128. os.Stdout.Sync()
  129. }
  130. if lastline >= hookBatchSize {
  131. fmt.Fprintf(os.Stdout, "\n")
  132. os.Stdout.Sync()
  133. lastline = 0
  134. }
  135. }
  136. if count > 0 {
  137. hookOptions.OldCommitIDs = oldCommitIDs[:count]
  138. hookOptions.NewCommitIDs = newCommitIDs[:count]
  139. hookOptions.RefFullNames = refFullNames[:count]
  140. fmt.Fprintf(os.Stdout, " Checking %d branches\n", count)
  141. os.Stdout.Sync()
  142. statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
  143. switch statusCode {
  144. case http.StatusInternalServerError:
  145. fail("Internal Server Error", msg)
  146. case http.StatusForbidden:
  147. fail(msg, "")
  148. }
  149. } else if lastline > 0 {
  150. fmt.Fprintf(os.Stdout, "\n")
  151. os.Stdout.Sync()
  152. lastline = 0
  153. }
  154. fmt.Fprintf(os.Stdout, "Checked %d references in total\n", total)
  155. os.Stdout.Sync()
  156. return nil
  157. }
  158. func runHookUpdate(c *cli.Context) error {
  159. // Update is empty and is kept only for backwards compatibility
  160. return nil
  161. }
  162. func runHookPostReceive(c *cli.Context) error {
  163. if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
  164. if setting.OnlyAllowPushIfGiteaEnvironmentSet {
  165. fail(`Rejecting changes as Gitea environment not set.
  166. If you are pushing over SSH you must push with a key managed by
  167. Gitea or set your environment appropriately.`, "")
  168. } else {
  169. return nil
  170. }
  171. }
  172. setup("hooks/post-receive.log", false)
  173. // the environment setted on serv command
  174. repoUser := os.Getenv(models.EnvRepoUsername)
  175. isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
  176. repoName := os.Getenv(models.EnvRepoName)
  177. pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
  178. pusherName := os.Getenv(models.EnvPusherName)
  179. hookOptions := private.HookOptions{
  180. UserName: pusherName,
  181. UserID: pusherID,
  182. GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
  183. GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
  184. GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
  185. }
  186. oldCommitIDs := make([]string, hookBatchSize)
  187. newCommitIDs := make([]string, hookBatchSize)
  188. refFullNames := make([]string, hookBatchSize)
  189. count := 0
  190. total := 0
  191. wasEmpty := false
  192. masterPushed := false
  193. results := make([]private.HookPostReceiveBranchResult, 0)
  194. scanner := bufio.NewScanner(os.Stdin)
  195. for scanner.Scan() {
  196. // TODO: support news feeds for wiki
  197. if isWiki {
  198. continue
  199. }
  200. fields := bytes.Fields(scanner.Bytes())
  201. if len(fields) != 3 {
  202. continue
  203. }
  204. fmt.Fprintf(os.Stdout, ".")
  205. oldCommitIDs[count] = string(fields[0])
  206. newCommitIDs[count] = string(fields[1])
  207. refFullNames[count] = string(fields[2])
  208. if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
  209. masterPushed = true
  210. }
  211. count++
  212. total++
  213. os.Stdout.Sync()
  214. if count >= hookBatchSize {
  215. fmt.Fprintf(os.Stdout, " Processing %d references\n", count)
  216. os.Stdout.Sync()
  217. hookOptions.OldCommitIDs = oldCommitIDs
  218. hookOptions.NewCommitIDs = newCommitIDs
  219. hookOptions.RefFullNames = refFullNames
  220. resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
  221. if resp == nil {
  222. hookPrintResults(results)
  223. fail("Internal Server Error", err)
  224. }
  225. wasEmpty = wasEmpty || resp.RepoWasEmpty
  226. results = append(results, resp.Results...)
  227. count = 0
  228. }
  229. }
  230. if count == 0 {
  231. if wasEmpty && masterPushed {
  232. // We need to tell the repo to reset the default branch to master
  233. err := private.SetDefaultBranch(repoUser, repoName, "master")
  234. if err != nil {
  235. fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
  236. }
  237. }
  238. fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total)
  239. os.Stdout.Sync()
  240. hookPrintResults(results)
  241. return nil
  242. }
  243. hookOptions.OldCommitIDs = oldCommitIDs[:count]
  244. hookOptions.NewCommitIDs = newCommitIDs[:count]
  245. hookOptions.RefFullNames = refFullNames[:count]
  246. fmt.Fprintf(os.Stdout, " Processing %d references\n", count)
  247. os.Stdout.Sync()
  248. resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
  249. if resp == nil {
  250. hookPrintResults(results)
  251. fail("Internal Server Error", err)
  252. }
  253. wasEmpty = wasEmpty || resp.RepoWasEmpty
  254. results = append(results, resp.Results...)
  255. fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total)
  256. os.Stdout.Sync()
  257. if wasEmpty && masterPushed {
  258. // We need to tell the repo to reset the default branch to master
  259. err := private.SetDefaultBranch(repoUser, repoName, "master")
  260. if err != nil {
  261. fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
  262. }
  263. }
  264. hookPrintResults(results)
  265. return nil
  266. }
  267. func hookPrintResults(results []private.HookPostReceiveBranchResult) {
  268. for _, res := range results {
  269. if !res.Message {
  270. continue
  271. }
  272. fmt.Fprintln(os.Stderr, "")
  273. if res.Create {
  274. fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
  275. fmt.Fprintf(os.Stderr, " %s\n", res.URL)
  276. } else {
  277. fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
  278. fmt.Fprintf(os.Stderr, " %s\n", res.URL)
  279. }
  280. fmt.Fprintln(os.Stderr, "")
  281. os.Stderr.Sync()
  282. }
  283. }