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.

377 lines
14 KiB

  1. // Copyright 2019 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
  5. package private
  6. import (
  7. "fmt"
  8. "net/http"
  9. "os"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/private"
  15. "code.gitea.io/gitea/modules/repofiles"
  16. "code.gitea.io/gitea/modules/util"
  17. pull_service "code.gitea.io/gitea/services/pull"
  18. "gitea.com/macaron/macaron"
  19. )
  20. // HookPreReceive checks whether a individual commit is acceptable
  21. func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) {
  22. ownerName := ctx.Params(":owner")
  23. repoName := ctx.Params(":repo")
  24. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  25. if err != nil {
  26. log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
  27. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  28. "err": err.Error(),
  29. })
  30. return
  31. }
  32. repo.OwnerName = ownerName
  33. for i := range opts.OldCommitIDs {
  34. oldCommitID := opts.OldCommitIDs[i]
  35. newCommitID := opts.NewCommitIDs[i]
  36. refFullName := opts.RefFullNames[i]
  37. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  38. protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
  39. if err != nil {
  40. log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
  41. ctx.JSON(500, map[string]interface{}{
  42. "err": err.Error(),
  43. })
  44. return
  45. }
  46. if protectBranch != nil && protectBranch.IsProtected() {
  47. // check and deletion
  48. if newCommitID == git.EmptySHA {
  49. log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
  50. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  51. "err": fmt.Sprintf("branch %s is protected from deletion", branchName),
  52. })
  53. return
  54. }
  55. // detect force push
  56. if git.EmptySHA != oldCommitID {
  57. env := os.Environ()
  58. if opts.GitAlternativeObjectDirectories != "" {
  59. env = append(env,
  60. private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories)
  61. }
  62. if opts.GitObjectDirectory != "" {
  63. env = append(env,
  64. private.GitObjectDirectory+"="+opts.GitObjectDirectory)
  65. }
  66. if opts.GitQuarantinePath != "" {
  67. env = append(env,
  68. private.GitQuarantinePath+"="+opts.GitQuarantinePath)
  69. }
  70. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
  71. if err != nil {
  72. log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
  73. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  74. "err": fmt.Sprintf("Fail to detect force push: %v", err),
  75. })
  76. return
  77. } else if len(output) > 0 {
  78. log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
  79. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  80. "err": fmt.Sprintf("branch %s is protected from force push", branchName),
  81. })
  82. return
  83. }
  84. }
  85. canPush := false
  86. if opts.IsDeployKey {
  87. canPush = protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
  88. } else {
  89. canPush = protectBranch.CanUserPush(opts.UserID)
  90. }
  91. if !canPush && opts.ProtectedBranchID > 0 {
  92. // Manual merge
  93. pr, err := models.GetPullRequestByID(opts.ProtectedBranchID)
  94. if err != nil {
  95. log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err)
  96. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  97. "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err),
  98. })
  99. return
  100. }
  101. user, err := models.GetUserByID(opts.UserID)
  102. if err != nil {
  103. log.Error("Unable to get User id %d Error: %v", opts.UserID, err)
  104. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  105. "err": fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err),
  106. })
  107. return
  108. }
  109. perm, err := models.GetUserRepoPermission(repo, user)
  110. if err != nil {
  111. log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err)
  112. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  113. "err": fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err),
  114. })
  115. return
  116. }
  117. allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user)
  118. if err != nil {
  119. log.Error("Error calculating if allowed to merge: %v", err)
  120. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  121. "err": fmt.Sprintf("Error calculating if allowed to merge: %v", err),
  122. })
  123. return
  124. }
  125. if !allowedMerge {
  126. log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index)
  127. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  128. "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
  129. })
  130. return
  131. }
  132. // Manual merge only allowed if PR is ready (even if admin)
  133. if err := pull_service.CheckPRReadyToMerge(pr); err != nil {
  134. if models.IsErrNotAllowedToMerge(err) {
  135. log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error())
  136. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  137. "err": fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.ProtectedBranchID, err.Error()),
  138. })
  139. return
  140. }
  141. log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err)
  142. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  143. "err": fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.ProtectedBranchID, err),
  144. })
  145. }
  146. } else if !canPush {
  147. log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo)
  148. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  149. "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
  150. })
  151. return
  152. }
  153. }
  154. }
  155. ctx.PlainText(http.StatusOK, []byte("ok"))
  156. }
  157. // HookPostReceive updates services and users
  158. func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) {
  159. ownerName := ctx.Params(":owner")
  160. repoName := ctx.Params(":repo")
  161. var repo *models.Repository
  162. updates := make([]*repofiles.PushUpdateOptions, 0, len(opts.OldCommitIDs))
  163. wasEmpty := false
  164. for i := range opts.OldCommitIDs {
  165. refFullName := opts.RefFullNames[i]
  166. branch := opts.RefFullNames[i]
  167. if strings.HasPrefix(branch, git.BranchPrefix) {
  168. branch = strings.TrimPrefix(branch, git.BranchPrefix)
  169. } else {
  170. branch = strings.TrimPrefix(branch, git.TagPrefix)
  171. }
  172. // Only trigger activity updates for changes to branches or
  173. // tags. Updates to other refs (eg, refs/notes, refs/changes,
  174. // or other less-standard refs spaces are ignored since there
  175. // may be a very large number of them).
  176. if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
  177. if repo == nil {
  178. var err error
  179. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  180. if err != nil {
  181. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  182. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  183. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  184. })
  185. return
  186. }
  187. if repo.OwnerName == "" {
  188. repo.OwnerName = ownerName
  189. }
  190. wasEmpty = repo.IsEmpty
  191. }
  192. option := repofiles.PushUpdateOptions{
  193. RefFullName: refFullName,
  194. OldCommitID: opts.OldCommitIDs[i],
  195. NewCommitID: opts.NewCommitIDs[i],
  196. Branch: branch,
  197. PusherID: opts.UserID,
  198. PusherName: opts.UserName,
  199. RepoUserName: ownerName,
  200. RepoName: repoName,
  201. }
  202. updates = append(updates, &option)
  203. if repo.IsEmpty && branch == "master" && strings.HasPrefix(refFullName, git.BranchPrefix) {
  204. // put the master branch first
  205. copy(updates[1:], updates)
  206. updates[0] = &option
  207. }
  208. }
  209. }
  210. if repo != nil && len(updates) > 0 {
  211. if err := repofiles.PushUpdates(repo, updates); err != nil {
  212. log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates))
  213. for i, update := range updates {
  214. log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.Branch)
  215. }
  216. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  217. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  218. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  219. })
  220. return
  221. }
  222. }
  223. results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs))
  224. // We have to reload the repo in case its state is changed above
  225. repo = nil
  226. var baseRepo *models.Repository
  227. for i := range opts.OldCommitIDs {
  228. refFullName := opts.RefFullNames[i]
  229. newCommitID := opts.NewCommitIDs[i]
  230. branch := git.RefEndName(opts.RefFullNames[i])
  231. if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
  232. if repo == nil {
  233. var err error
  234. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  235. if err != nil {
  236. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  237. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  238. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  239. RepoWasEmpty: wasEmpty,
  240. })
  241. return
  242. }
  243. if repo.OwnerName == "" {
  244. repo.OwnerName = ownerName
  245. }
  246. if !repo.AllowsPulls() {
  247. // We can stop there's no need to go any further
  248. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  249. RepoWasEmpty: wasEmpty,
  250. })
  251. return
  252. }
  253. baseRepo = repo
  254. if repo.IsFork {
  255. if err := repo.GetBaseRepo(); err != nil {
  256. log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
  257. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  258. Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
  259. RepoWasEmpty: wasEmpty,
  260. })
  261. return
  262. }
  263. baseRepo = repo.BaseRepo
  264. }
  265. }
  266. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  267. results = append(results, private.HookPostReceiveBranchResult{})
  268. continue
  269. }
  270. pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
  271. if err != nil && !models.IsErrPullRequestNotExist(err) {
  272. log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
  273. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  274. Err: fmt.Sprintf(
  275. "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
  276. RepoWasEmpty: wasEmpty,
  277. })
  278. return
  279. }
  280. if pr == nil {
  281. if repo.IsFork {
  282. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  283. }
  284. results = append(results, private.HookPostReceiveBranchResult{
  285. Message: true,
  286. Create: true,
  287. Branch: branch,
  288. URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
  289. })
  290. } else {
  291. results = append(results, private.HookPostReceiveBranchResult{
  292. Message: true,
  293. Create: false,
  294. Branch: branch,
  295. URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
  296. })
  297. }
  298. }
  299. }
  300. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  301. Results: results,
  302. RepoWasEmpty: wasEmpty,
  303. })
  304. }
  305. // SetDefaultBranch updates the default branch
  306. func SetDefaultBranch(ctx *macaron.Context) {
  307. ownerName := ctx.Params(":owner")
  308. repoName := ctx.Params(":repo")
  309. branch := ctx.Params(":branch")
  310. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  311. if err != nil {
  312. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  313. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  314. "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  315. })
  316. return
  317. }
  318. if repo.OwnerName == "" {
  319. repo.OwnerName = ownerName
  320. }
  321. repo.DefaultBranch = branch
  322. gitRepo, err := git.OpenRepository(repo.RepoPath())
  323. if err != nil {
  324. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  325. "Err": fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err),
  326. })
  327. return
  328. }
  329. if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
  330. if !git.IsErrUnsupportedVersion(err) {
  331. gitRepo.Close()
  332. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  333. "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err),
  334. })
  335. return
  336. }
  337. }
  338. gitRepo.Close()
  339. if err := repo.UpdateDefaultBranch(); err != nil {
  340. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  341. "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err),
  342. })
  343. return
  344. }
  345. ctx.PlainText(200, []byte("success"))
  346. }