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.3 kB

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