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.

hook.go 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  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. "bufio"
  8. "context"
  9. "fmt"
  10. "io"
  11. "net/http"
  12. "os"
  13. "strings"
  14. "code.gitea.io/gitea/models"
  15. gitea_context "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/private"
  19. repo_module "code.gitea.io/gitea/modules/repository"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/util"
  22. "code.gitea.io/gitea/modules/web"
  23. "code.gitea.io/gitea/services/agit"
  24. pull_service "code.gitea.io/gitea/services/pull"
  25. repo_service "code.gitea.io/gitea/services/repository"
  26. )
  27. func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error {
  28. stdoutReader, stdoutWriter, err := os.Pipe()
  29. if err != nil {
  30. log.Error("Unable to create os.Pipe for %s", repo.Path)
  31. return err
  32. }
  33. defer func() {
  34. _ = stdoutReader.Close()
  35. _ = stdoutWriter.Close()
  36. }()
  37. // This is safe as force pushes are already forbidden
  38. err = git.NewCommand("rev-list", oldCommitID+"..."+newCommitID).
  39. RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
  40. stdoutWriter, nil, nil,
  41. func(ctx context.Context, cancel context.CancelFunc) error {
  42. _ = stdoutWriter.Close()
  43. err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
  44. if err != nil {
  45. log.Error("%v", err)
  46. cancel()
  47. }
  48. _ = stdoutReader.Close()
  49. return err
  50. })
  51. if err != nil && !isErrUnverifiedCommit(err) {
  52. log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
  53. }
  54. return err
  55. }
  56. func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error {
  57. scanner := bufio.NewScanner(input)
  58. for scanner.Scan() {
  59. line := scanner.Text()
  60. err := readAndVerifyCommit(line, repo, env)
  61. if err != nil {
  62. log.Error("%v", err)
  63. return err
  64. }
  65. }
  66. return scanner.Err()
  67. }
  68. func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
  69. stdoutReader, stdoutWriter, err := os.Pipe()
  70. if err != nil {
  71. log.Error("Unable to create pipe for %s: %v", repo.Path, err)
  72. return err
  73. }
  74. defer func() {
  75. _ = stdoutReader.Close()
  76. _ = stdoutWriter.Close()
  77. }()
  78. hash := git.MustIDFromString(sha)
  79. return git.NewCommand("cat-file", "commit", sha).
  80. RunInDirTimeoutEnvFullPipelineFunc(env, -1, repo.Path,
  81. stdoutWriter, nil, nil,
  82. func(ctx context.Context, cancel context.CancelFunc) error {
  83. _ = stdoutWriter.Close()
  84. commit, err := git.CommitFromReader(repo, hash, stdoutReader)
  85. if err != nil {
  86. return err
  87. }
  88. verification := models.ParseCommitWithSignature(commit)
  89. if !verification.Verified {
  90. cancel()
  91. return &errUnverifiedCommit{
  92. commit.ID.String(),
  93. }
  94. }
  95. return nil
  96. })
  97. }
  98. type errUnverifiedCommit struct {
  99. sha string
  100. }
  101. func (e *errUnverifiedCommit) Error() string {
  102. return fmt.Sprintf("Unverified commit: %s", e.sha)
  103. }
  104. func isErrUnverifiedCommit(err error) bool {
  105. _, ok := err.(*errUnverifiedCommit)
  106. return ok
  107. }
  108. // HookPreReceive checks whether a individual commit is acceptable
  109. func HookPreReceive(ctx *gitea_context.PrivateContext) {
  110. opts := web.GetForm(ctx).(*private.HookOptions)
  111. ownerName := ctx.Params(":owner")
  112. repoName := ctx.Params(":repo")
  113. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  114. if err != nil {
  115. log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
  116. ctx.JSON(http.StatusInternalServerError, private.Response{
  117. Err: err.Error(),
  118. })
  119. return
  120. }
  121. repo.OwnerName = ownerName
  122. gitRepo, err := git.OpenRepository(repo.RepoPath())
  123. if err != nil {
  124. log.Error("Unable to get git repository for: %s/%s Error: %v", ownerName, repoName, err)
  125. ctx.JSON(http.StatusInternalServerError, private.Response{
  126. Err: err.Error(),
  127. })
  128. return
  129. }
  130. defer gitRepo.Close()
  131. // Generate git environment for checking commits
  132. env := os.Environ()
  133. if opts.GitAlternativeObjectDirectories != "" {
  134. env = append(env,
  135. private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories)
  136. }
  137. if opts.GitObjectDirectory != "" {
  138. env = append(env,
  139. private.GitObjectDirectory+"="+opts.GitObjectDirectory)
  140. }
  141. if opts.GitQuarantinePath != "" {
  142. env = append(env,
  143. private.GitQuarantinePath+"="+opts.GitQuarantinePath)
  144. }
  145. if git.SupportProcReceive {
  146. pusher, err := models.GetUserByID(opts.UserID)
  147. if err != nil {
  148. log.Error("models.GetUserByID:%v", err)
  149. ctx.Error(http.StatusInternalServerError, "")
  150. return
  151. }
  152. perm, err := models.GetUserRepoPermission(repo, pusher)
  153. if err != nil {
  154. log.Error("models.GetUserRepoPermission:%v", err)
  155. ctx.Error(http.StatusInternalServerError, "")
  156. return
  157. }
  158. canCreatePullRequest := perm.CanRead(models.UnitTypePullRequests)
  159. for _, refFullName := range opts.RefFullNames {
  160. // if user want update other refs (branch or tag),
  161. // should check code write permission because
  162. // this check was delayed.
  163. if !strings.HasPrefix(refFullName, git.PullRequestPrefix) {
  164. if !perm.CanWrite(models.UnitTypeCode) {
  165. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  166. "err": "User permission denied.",
  167. })
  168. return
  169. }
  170. break
  171. } else if repo.IsEmpty {
  172. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  173. "err": "Can't create pull request for an empty repository.",
  174. })
  175. return
  176. } else if !canCreatePullRequest {
  177. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  178. "err": "User permission denied.",
  179. })
  180. return
  181. } else if opts.IsWiki {
  182. // TODO: maybe can do it ...
  183. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  184. "err": "not support send pull request to wiki.",
  185. })
  186. return
  187. }
  188. }
  189. }
  190. protectedTags, err := repo.GetProtectedTags()
  191. if err != nil {
  192. log.Error("Unable to get protected tags for %-v Error: %v", repo, err)
  193. ctx.JSON(http.StatusInternalServerError, private.Response{
  194. Err: err.Error(),
  195. })
  196. return
  197. }
  198. // Iterate across the provided old commit IDs
  199. for i := range opts.OldCommitIDs {
  200. oldCommitID := opts.OldCommitIDs[i]
  201. newCommitID := opts.NewCommitIDs[i]
  202. refFullName := opts.RefFullNames[i]
  203. if strings.HasPrefix(refFullName, git.BranchPrefix) {
  204. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  205. if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
  206. log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
  207. ctx.JSON(http.StatusForbidden, private.Response{
  208. Err: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName),
  209. })
  210. return
  211. }
  212. protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
  213. if err != nil {
  214. log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
  215. ctx.JSON(http.StatusInternalServerError, private.Response{
  216. Err: err.Error(),
  217. })
  218. return
  219. }
  220. // Allow pushes to non-protected branches
  221. if protectBranch == nil || !protectBranch.IsProtected() {
  222. continue
  223. }
  224. // This ref is a protected branch.
  225. //
  226. // First of all we need to enforce absolutely:
  227. //
  228. // 1. Detect and prevent deletion of the branch
  229. if newCommitID == git.EmptySHA {
  230. log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
  231. ctx.JSON(http.StatusForbidden, private.Response{
  232. Err: fmt.Sprintf("branch %s is protected from deletion", branchName),
  233. })
  234. return
  235. }
  236. // 2. Disallow force pushes to protected branches
  237. if git.EmptySHA != oldCommitID {
  238. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
  239. if err != nil {
  240. log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
  241. ctx.JSON(http.StatusInternalServerError, private.Response{
  242. Err: fmt.Sprintf("Fail to detect force push: %v", err),
  243. })
  244. return
  245. } else if len(output) > 0 {
  246. log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
  247. ctx.JSON(http.StatusForbidden, private.Response{
  248. Err: fmt.Sprintf("branch %s is protected from force push", branchName),
  249. })
  250. return
  251. }
  252. }
  253. // 3. Enforce require signed commits
  254. if protectBranch.RequireSignedCommits {
  255. err := verifyCommits(oldCommitID, newCommitID, gitRepo, env)
  256. if err != nil {
  257. if !isErrUnverifiedCommit(err) {
  258. log.Error("Unable to check commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
  259. ctx.JSON(http.StatusInternalServerError, private.Response{
  260. Err: fmt.Sprintf("Unable to check commits from %s to %s: %v", oldCommitID, newCommitID, err),
  261. })
  262. return
  263. }
  264. unverifiedCommit := err.(*errUnverifiedCommit).sha
  265. log.Warn("Forbidden: Branch: %s in %-v is protected from unverified commit %s", branchName, repo, unverifiedCommit)
  266. ctx.JSON(http.StatusForbidden, private.Response{
  267. Err: fmt.Sprintf("branch %s is protected from unverified commit %s", branchName, unverifiedCommit),
  268. })
  269. return
  270. }
  271. }
  272. // Now there are several tests which can be overridden:
  273. //
  274. // 4. Check protected file patterns - this is overridable from the UI
  275. changedProtectedfiles := false
  276. protectedFilePath := ""
  277. globs := protectBranch.GetProtectedFilePatterns()
  278. if len(globs) > 0 {
  279. _, err := pull_service.CheckFileProtection(oldCommitID, newCommitID, globs, 1, env, gitRepo)
  280. if err != nil {
  281. if !models.IsErrFilePathProtected(err) {
  282. log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
  283. ctx.JSON(http.StatusInternalServerError, private.Response{
  284. Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
  285. })
  286. return
  287. }
  288. changedProtectedfiles = true
  289. protectedFilePath = err.(models.ErrFilePathProtected).Path
  290. }
  291. }
  292. // 5. Check if the doer is allowed to push
  293. canPush := false
  294. if opts.IsDeployKey {
  295. canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
  296. } else {
  297. canPush = !changedProtectedfiles && protectBranch.CanUserPush(opts.UserID)
  298. }
  299. // 6. If we're not allowed to push directly
  300. if !canPush {
  301. // Is this is a merge from the UI/API?
  302. if opts.PullRequestID == 0 {
  303. // 6a. If we're not merging from the UI/API then there are two ways we got here:
  304. //
  305. // We are changing a protected file and we're not allowed to do that
  306. if changedProtectedfiles {
  307. log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
  308. ctx.JSON(http.StatusForbidden, private.Response{
  309. Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
  310. })
  311. return
  312. }
  313. // Allow commits that only touch unprotected files
  314. globs := protectBranch.GetUnprotectedFilePatterns()
  315. if len(globs) > 0 {
  316. unprotectedFilesOnly, err := pull_service.CheckUnprotectedFiles(oldCommitID, newCommitID, globs, env, gitRepo)
  317. if err != nil {
  318. log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err)
  319. ctx.JSON(http.StatusInternalServerError, private.Response{
  320. Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err),
  321. })
  322. return
  323. }
  324. if unprotectedFilesOnly {
  325. // Commit only touches unprotected files, this is allowed
  326. continue
  327. }
  328. }
  329. // Or we're simply not able to push to this protected branch
  330. log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo)
  331. ctx.JSON(http.StatusForbidden, private.Response{
  332. Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
  333. })
  334. return
  335. }
  336. // 6b. Merge (from UI or API)
  337. // Get the PR, user and permissions for the user in the repository
  338. pr, err := models.GetPullRequestByID(opts.PullRequestID)
  339. if err != nil {
  340. log.Error("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err)
  341. ctx.JSON(http.StatusInternalServerError, private.Response{
  342. Err: fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.PullRequestID, err),
  343. })
  344. return
  345. }
  346. user, err := models.GetUserByID(opts.UserID)
  347. if err != nil {
  348. log.Error("Unable to get User id %d Error: %v", opts.UserID, err)
  349. ctx.JSON(http.StatusInternalServerError, private.Response{
  350. Err: fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err),
  351. })
  352. return
  353. }
  354. perm, err := models.GetUserRepoPermission(repo, user)
  355. if err != nil {
  356. log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err)
  357. ctx.JSON(http.StatusInternalServerError, private.Response{
  358. Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err),
  359. })
  360. return
  361. }
  362. // Now check if the user is allowed to merge PRs for this repository
  363. allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user)
  364. if err != nil {
  365. log.Error("Error calculating if allowed to merge: %v", err)
  366. ctx.JSON(http.StatusInternalServerError, private.Response{
  367. Err: fmt.Sprintf("Error calculating if allowed to merge: %v", err),
  368. })
  369. return
  370. }
  371. if !allowedMerge {
  372. 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)
  373. ctx.JSON(http.StatusForbidden, private.Response{
  374. Err: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
  375. })
  376. return
  377. }
  378. // If we're an admin for the repository we can ignore status checks, reviews and override protected files
  379. if perm.IsAdmin() {
  380. continue
  381. }
  382. // Now if we're not an admin - we can't overwrite protected files so fail now
  383. if changedProtectedfiles {
  384. log.Warn("Forbidden: Branch: %s in %-v is protected from changing file %s", branchName, repo, protectedFilePath)
  385. ctx.JSON(http.StatusForbidden, private.Response{
  386. Err: fmt.Sprintf("branch %s is protected from changing file %s", branchName, protectedFilePath),
  387. })
  388. return
  389. }
  390. // Check all status checks and reviews are ok
  391. if err := pull_service.CheckPRReadyToMerge(pr, true); err != nil {
  392. if models.IsErrNotAllowedToMerge(err) {
  393. 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())
  394. ctx.JSON(http.StatusForbidden, private.Response{
  395. Err: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.PullRequestID, err.Error()),
  396. })
  397. return
  398. }
  399. log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err)
  400. ctx.JSON(http.StatusInternalServerError, private.Response{
  401. Err: fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.PullRequestID, err),
  402. })
  403. return
  404. }
  405. }
  406. } else if strings.HasPrefix(refFullName, git.TagPrefix) {
  407. tagName := strings.TrimPrefix(refFullName, git.TagPrefix)
  408. isAllowed, err := models.IsUserAllowedToControlTag(protectedTags, tagName, opts.UserID)
  409. if err != nil {
  410. ctx.JSON(http.StatusInternalServerError, private.Response{
  411. Err: err.Error(),
  412. })
  413. return
  414. }
  415. if !isAllowed {
  416. log.Warn("Forbidden: Tag %s in %-v is protected", tagName, repo)
  417. ctx.JSON(http.StatusForbidden, private.Response{
  418. Err: fmt.Sprintf("Tag %s is protected", tagName),
  419. })
  420. return
  421. }
  422. } else if git.SupportProcReceive && strings.HasPrefix(refFullName, git.PullRequestPrefix) {
  423. baseBranchName := opts.RefFullNames[i][len(git.PullRequestPrefix):]
  424. baseBranchExist := false
  425. if gitRepo.IsBranchExist(baseBranchName) {
  426. baseBranchExist = true
  427. }
  428. if !baseBranchExist {
  429. for p, v := range baseBranchName {
  430. if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
  431. baseBranchExist = true
  432. break
  433. }
  434. }
  435. }
  436. if !baseBranchExist {
  437. ctx.JSON(http.StatusForbidden, private.Response{
  438. Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
  439. })
  440. return
  441. }
  442. } else {
  443. log.Error("Unexpected ref: %s", refFullName)
  444. ctx.JSON(http.StatusInternalServerError, private.Response{
  445. Err: fmt.Sprintf("Unexpected ref: %s", refFullName),
  446. })
  447. return
  448. }
  449. }
  450. ctx.PlainText(http.StatusOK, []byte("ok"))
  451. }
  452. // HookPostReceive updates services and users
  453. func HookPostReceive(ctx *gitea_context.PrivateContext) {
  454. opts := web.GetForm(ctx).(*private.HookOptions)
  455. ownerName := ctx.Params(":owner")
  456. repoName := ctx.Params(":repo")
  457. var repo *models.Repository
  458. updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs))
  459. wasEmpty := false
  460. for i := range opts.OldCommitIDs {
  461. refFullName := opts.RefFullNames[i]
  462. // Only trigger activity updates for changes to branches or
  463. // tags. Updates to other refs (eg, refs/notes, refs/changes,
  464. // or other less-standard refs spaces are ignored since there
  465. // may be a very large number of them).
  466. if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
  467. if repo == nil {
  468. var err error
  469. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  470. if err != nil {
  471. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  472. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  473. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  474. })
  475. return
  476. }
  477. if repo.OwnerName == "" {
  478. repo.OwnerName = ownerName
  479. }
  480. wasEmpty = repo.IsEmpty
  481. }
  482. option := repo_module.PushUpdateOptions{
  483. RefFullName: refFullName,
  484. OldCommitID: opts.OldCommitIDs[i],
  485. NewCommitID: opts.NewCommitIDs[i],
  486. PusherID: opts.UserID,
  487. PusherName: opts.UserName,
  488. RepoUserName: ownerName,
  489. RepoName: repoName,
  490. }
  491. updates = append(updates, &option)
  492. if repo.IsEmpty && option.IsBranch() && (option.BranchName() == "master" || option.BranchName() == "main") {
  493. // put the master/main branch first
  494. copy(updates[1:], updates)
  495. updates[0] = &option
  496. }
  497. }
  498. }
  499. if repo != nil && len(updates) > 0 {
  500. if err := repo_service.PushUpdates(updates); err != nil {
  501. log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates))
  502. for i, update := range updates {
  503. log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.BranchName())
  504. }
  505. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  506. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  507. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  508. })
  509. return
  510. }
  511. }
  512. // Push Options
  513. if repo != nil && len(opts.GitPushOptions) > 0 {
  514. repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate)
  515. repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate)
  516. if err := models.UpdateRepositoryCols(repo, "is_private", "is_template"); err != nil {
  517. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  518. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  519. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  520. })
  521. }
  522. }
  523. results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs))
  524. // We have to reload the repo in case its state is changed above
  525. repo = nil
  526. var baseRepo *models.Repository
  527. for i := range opts.OldCommitIDs {
  528. refFullName := opts.RefFullNames[i]
  529. newCommitID := opts.NewCommitIDs[i]
  530. branch := git.RefEndName(opts.RefFullNames[i])
  531. if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
  532. if repo == nil {
  533. var err error
  534. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  535. if err != nil {
  536. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  537. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  538. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  539. RepoWasEmpty: wasEmpty,
  540. })
  541. return
  542. }
  543. if repo.OwnerName == "" {
  544. repo.OwnerName = ownerName
  545. }
  546. if !repo.AllowsPulls() {
  547. // We can stop there's no need to go any further
  548. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  549. RepoWasEmpty: wasEmpty,
  550. })
  551. return
  552. }
  553. baseRepo = repo
  554. if repo.IsFork {
  555. if err := repo.GetBaseRepo(); err != nil {
  556. log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
  557. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  558. Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
  559. RepoWasEmpty: wasEmpty,
  560. })
  561. return
  562. }
  563. baseRepo = repo.BaseRepo
  564. }
  565. }
  566. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  567. results = append(results, private.HookPostReceiveBranchResult{})
  568. continue
  569. }
  570. pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, models.PullRequestFlowGithub)
  571. if err != nil && !models.IsErrPullRequestNotExist(err) {
  572. log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
  573. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  574. Err: fmt.Sprintf(
  575. "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
  576. RepoWasEmpty: wasEmpty,
  577. })
  578. return
  579. }
  580. if pr == nil {
  581. if repo.IsFork {
  582. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  583. }
  584. results = append(results, private.HookPostReceiveBranchResult{
  585. Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(),
  586. Create: true,
  587. Branch: branch,
  588. URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
  589. })
  590. } else {
  591. results = append(results, private.HookPostReceiveBranchResult{
  592. Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(),
  593. Create: false,
  594. Branch: branch,
  595. URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
  596. })
  597. }
  598. }
  599. }
  600. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  601. Results: results,
  602. RepoWasEmpty: wasEmpty,
  603. })
  604. }
  605. // HookProcReceive proc-receive hook
  606. func HookProcReceive(ctx *gitea_context.PrivateContext) {
  607. opts := web.GetForm(ctx).(*private.HookOptions)
  608. if !git.SupportProcReceive {
  609. ctx.Status(http.StatusNotFound)
  610. return
  611. }
  612. cancel := loadRepositoryAndGitRepoByParams(ctx)
  613. if ctx.Written() {
  614. return
  615. }
  616. defer cancel()
  617. results := agit.ProcRecive(ctx, opts)
  618. if ctx.Written() {
  619. return
  620. }
  621. ctx.JSON(http.StatusOK, private.HookProcReceiveResult{
  622. Results: results,
  623. })
  624. }
  625. // SetDefaultBranch updates the default branch
  626. func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
  627. ownerName := ctx.Params(":owner")
  628. repoName := ctx.Params(":repo")
  629. branch := ctx.Params(":branch")
  630. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  631. if err != nil {
  632. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  633. ctx.JSON(http.StatusInternalServerError, private.Response{
  634. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  635. })
  636. return
  637. }
  638. if repo.OwnerName == "" {
  639. repo.OwnerName = ownerName
  640. }
  641. repo.DefaultBranch = branch
  642. gitRepo, err := git.OpenRepository(repo.RepoPath())
  643. if err != nil {
  644. ctx.JSON(http.StatusInternalServerError, private.Response{
  645. Err: fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err),
  646. })
  647. return
  648. }
  649. if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
  650. if !git.IsErrUnsupportedVersion(err) {
  651. gitRepo.Close()
  652. ctx.JSON(http.StatusInternalServerError, private.Response{
  653. Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
  654. })
  655. return
  656. }
  657. }
  658. gitRepo.Close()
  659. if err := repo.UpdateDefaultBranch(); err != nil {
  660. ctx.JSON(http.StatusInternalServerError, private.Response{
  661. Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
  662. })
  663. return
  664. }
  665. ctx.PlainText(http.StatusOK, []byte("success"))
  666. }
  667. func loadRepositoryAndGitRepoByParams(ctx *gitea_context.PrivateContext) context.CancelFunc {
  668. ownerName := ctx.Params(":owner")
  669. repoName := ctx.Params(":repo")
  670. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  671. if err != nil {
  672. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  673. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  674. "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  675. })
  676. return nil
  677. }
  678. if repo.OwnerName == "" {
  679. repo.OwnerName = ownerName
  680. }
  681. gitRepo, err := git.OpenRepository(repo.RepoPath())
  682. if err != nil {
  683. log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
  684. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  685. "Err": fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
  686. })
  687. return nil
  688. }
  689. ctx.Repo = &gitea_context.Repository{
  690. Repository: repo,
  691. GitRepo: gitRepo,
  692. }
  693. // We opened it, we should close it
  694. cancel := func() {
  695. // If it's been set to nil then assume someone else has closed it.
  696. if ctx.Repo.GitRepo != nil {
  697. ctx.Repo.GitRepo.Close()
  698. }
  699. }
  700. return cancel
  701. }