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.

push.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "time"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/cache"
  15. "code.gitea.io/gitea/modules/git"
  16. "code.gitea.io/gitea/modules/graceful"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/notification"
  19. "code.gitea.io/gitea/modules/process"
  20. "code.gitea.io/gitea/modules/queue"
  21. repo_module "code.gitea.io/gitea/modules/repository"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/timeutil"
  24. issue_service "code.gitea.io/gitea/services/issue"
  25. pull_service "code.gitea.io/gitea/services/pull"
  26. )
  27. // pushQueue represents a queue to handle update pull request tests
  28. var pushQueue queue.Queue
  29. // handle passed PR IDs and test the PRs
  30. func handle(data ...queue.Data) []queue.Data {
  31. for _, datum := range data {
  32. opts := datum.([]*repo_module.PushUpdateOptions)
  33. if err := pushUpdates(opts); err != nil {
  34. log.Error("pushUpdate failed: %v", err)
  35. }
  36. }
  37. return nil
  38. }
  39. func initPushQueue() error {
  40. pushQueue = queue.CreateQueue("push_update", handle, []*repo_module.PushUpdateOptions{})
  41. if pushQueue == nil {
  42. return errors.New("unable to create push_update Queue")
  43. }
  44. go graceful.GetManager().RunWithShutdownFns(pushQueue.Run)
  45. return nil
  46. }
  47. // PushUpdate is an alias of PushUpdates for single push update options
  48. func PushUpdate(opts *repo_module.PushUpdateOptions) error {
  49. return PushUpdates([]*repo_module.PushUpdateOptions{opts})
  50. }
  51. // PushUpdates adds a push update to push queue
  52. func PushUpdates(opts []*repo_module.PushUpdateOptions) error {
  53. if len(opts) == 0 {
  54. return nil
  55. }
  56. for _, opt := range opts {
  57. if opt.IsNewRef() && opt.IsDelRef() {
  58. return fmt.Errorf("Old and new revisions are both %s", git.EmptySHA)
  59. }
  60. }
  61. return pushQueue.Push(opts)
  62. }
  63. // pushUpdates generates push action history feeds for push updating multiple refs
  64. func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
  65. if len(optsList) == 0 {
  66. return nil
  67. }
  68. ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PushUpdates: %s/%s", optsList[0].RepoUserName, optsList[0].RepoName))
  69. defer finished()
  70. repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, optsList[0].RepoUserName, optsList[0].RepoName)
  71. if err != nil {
  72. return fmt.Errorf("GetRepositoryByOwnerAndName failed: %w", err)
  73. }
  74. repoPath := repo.RepoPath()
  75. gitRepo, err := git.OpenRepository(ctx, repoPath)
  76. if err != nil {
  77. return fmt.Errorf("OpenRepository[%s]: %w", repoPath, err)
  78. }
  79. defer gitRepo.Close()
  80. if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
  81. log.Error("Failed to update size for repository: %v", err)
  82. }
  83. addTags := make([]string, 0, len(optsList))
  84. delTags := make([]string, 0, len(optsList))
  85. var pusher *user_model.User
  86. for _, opts := range optsList {
  87. log.Trace("pushUpdates: %-v %s %s %s", repo, opts.OldCommitID, opts.NewCommitID, opts.RefFullName)
  88. if opts.IsNewRef() && opts.IsDelRef() {
  89. return fmt.Errorf("old and new revisions are both %s", git.EmptySHA)
  90. }
  91. if opts.IsTag() { // If is tag reference
  92. if pusher == nil || pusher.ID != opts.PusherID {
  93. if opts.PusherID == user_model.ActionsUserID {
  94. pusher = user_model.NewActionsUser()
  95. } else {
  96. var err error
  97. if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil {
  98. return err
  99. }
  100. }
  101. }
  102. tagName := opts.TagName()
  103. if opts.IsDelRef() {
  104. notification.NotifyPushCommits(
  105. db.DefaultContext, pusher, repo,
  106. &repo_module.PushUpdateOptions{
  107. RefFullName: git.TagPrefix + tagName,
  108. OldCommitID: opts.OldCommitID,
  109. NewCommitID: git.EmptySHA,
  110. }, repo_module.NewPushCommits())
  111. delTags = append(delTags, tagName)
  112. notification.NotifyDeleteRef(db.DefaultContext, pusher, repo, "tag", opts.RefFullName)
  113. } else { // is new tag
  114. newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
  115. if err != nil {
  116. return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
  117. }
  118. commits := repo_module.NewPushCommits()
  119. commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
  120. commits.CompareURL = repo.ComposeCompareURL(git.EmptySHA, opts.NewCommitID)
  121. notification.NotifyPushCommits(
  122. db.DefaultContext, pusher, repo,
  123. &repo_module.PushUpdateOptions{
  124. RefFullName: git.TagPrefix + tagName,
  125. OldCommitID: git.EmptySHA,
  126. NewCommitID: opts.NewCommitID,
  127. }, commits)
  128. addTags = append(addTags, tagName)
  129. notification.NotifyCreateRef(db.DefaultContext, pusher, repo, "tag", opts.RefFullName, opts.NewCommitID)
  130. }
  131. } else if opts.IsBranch() { // If is branch reference
  132. if pusher == nil || pusher.ID != opts.PusherID {
  133. if opts.PusherID == user_model.ActionsUserID {
  134. pusher = user_model.NewActionsUser()
  135. } else {
  136. var err error
  137. if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil {
  138. return err
  139. }
  140. }
  141. }
  142. branch := opts.BranchName()
  143. if !opts.IsDelRef() {
  144. log.Trace("TriggerTask '%s/%s' by %s", repo.Name, branch, pusher.Name)
  145. go pull_service.AddTestPullRequestTask(pusher, repo.ID, branch, true, opts.OldCommitID, opts.NewCommitID)
  146. newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
  147. if err != nil {
  148. return fmt.Errorf("gitRepo.GetCommit(%s) in %s/%s[%d]: %w", opts.NewCommitID, repo.OwnerName, repo.Name, repo.ID, err)
  149. }
  150. refName := opts.RefName()
  151. // Push new branch.
  152. var l []*git.Commit
  153. if opts.IsNewRef() {
  154. if repo.IsEmpty { // Change default branch and empty status only if pushed ref is non-empty branch.
  155. repo.DefaultBranch = refName
  156. repo.IsEmpty = false
  157. if repo.DefaultBranch != setting.Repository.DefaultBranch {
  158. if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
  159. if !git.IsErrUnsupportedVersion(err) {
  160. return err
  161. }
  162. }
  163. }
  164. // Update the is empty and default_branch columns
  165. if err := repo_model.UpdateRepositoryCols(db.DefaultContext, repo, "default_branch", "is_empty"); err != nil {
  166. return fmt.Errorf("UpdateRepositoryCols: %w", err)
  167. }
  168. }
  169. l, err = newCommit.CommitsBeforeLimit(10)
  170. if err != nil {
  171. return fmt.Errorf("newCommit.CommitsBeforeLimit: %w", err)
  172. }
  173. notification.NotifyCreateRef(db.DefaultContext, pusher, repo, "branch", opts.RefFullName, opts.NewCommitID)
  174. } else {
  175. l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
  176. if err != nil {
  177. return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err)
  178. }
  179. isForce, err := repo_module.IsForcePush(ctx, opts)
  180. if err != nil {
  181. log.Error("isForcePush %s:%s failed: %v", repo.FullName(), branch, err)
  182. }
  183. if isForce {
  184. log.Trace("Push %s is a force push", opts.NewCommitID)
  185. cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
  186. } else {
  187. // TODO: increment update the commit count cache but not remove
  188. cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))
  189. }
  190. }
  191. commits := repo_module.GitToPushCommits(l)
  192. commits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
  193. if err := issue_service.UpdateIssuesCommit(pusher, repo, commits.Commits, refName); err != nil {
  194. log.Error("updateIssuesCommit: %v", err)
  195. }
  196. oldCommitID := opts.OldCommitID
  197. if oldCommitID == git.EmptySHA && len(commits.Commits) > 0 {
  198. oldCommit, err := gitRepo.GetCommit(commits.Commits[len(commits.Commits)-1].Sha1)
  199. if err != nil && !git.IsErrNotExist(err) {
  200. log.Error("unable to GetCommit %s from %-v: %v", oldCommitID, repo, err)
  201. }
  202. if oldCommit != nil {
  203. for i := 0; i < oldCommit.ParentCount(); i++ {
  204. commitID, _ := oldCommit.ParentID(i)
  205. if !commitID.IsZero() {
  206. oldCommitID = commitID.String()
  207. break
  208. }
  209. }
  210. }
  211. }
  212. if oldCommitID == git.EmptySHA && repo.DefaultBranch != branch {
  213. oldCommitID = repo.DefaultBranch
  214. }
  215. if oldCommitID != git.EmptySHA {
  216. commits.CompareURL = repo.ComposeCompareURL(oldCommitID, opts.NewCommitID)
  217. } else {
  218. commits.CompareURL = ""
  219. }
  220. if len(commits.Commits) > setting.UI.FeedMaxCommitNum {
  221. commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
  222. }
  223. notification.NotifyPushCommits(db.DefaultContext, pusher, repo, opts, commits)
  224. if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil {
  225. log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
  226. }
  227. // Cache for big repository
  228. if err := CacheRef(graceful.GetManager().HammerContext(), repo, gitRepo, opts.RefFullName); err != nil {
  229. log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
  230. }
  231. } else {
  232. notification.NotifyDeleteRef(db.DefaultContext, pusher, repo, "branch", opts.RefFullName)
  233. if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil {
  234. // close all related pulls
  235. log.Error("close related pull request failed: %v", err)
  236. }
  237. }
  238. // Even if user delete a branch on a repository which he didn't watch, he will be watch that.
  239. if err = repo_model.WatchIfAuto(db.DefaultContext, opts.PusherID, repo.ID, true); err != nil {
  240. log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
  241. }
  242. } else {
  243. log.Trace("Non-tag and non-branch commits pushed.")
  244. }
  245. }
  246. if err := PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil {
  247. return fmt.Errorf("PushUpdateAddDeleteTags: %w", err)
  248. }
  249. // Change repository last updated time.
  250. if err := repo_model.UpdateRepositoryUpdatedTime(repo.ID, time.Now()); err != nil {
  251. return fmt.Errorf("UpdateRepositoryUpdatedTime: %w", err)
  252. }
  253. return nil
  254. }
  255. // PushUpdateAddDeleteTags updates a number of added and delete tags
  256. func PushUpdateAddDeleteTags(repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
  257. return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
  258. if err := repo_model.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil {
  259. return err
  260. }
  261. return pushUpdateAddTags(ctx, repo, gitRepo, addTags)
  262. })
  263. }
  264. // pushUpdateAddTags updates a number of add tags
  265. func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, tags []string) error {
  266. if len(tags) == 0 {
  267. return nil
  268. }
  269. lowerTags := make([]string, 0, len(tags))
  270. for _, tag := range tags {
  271. lowerTags = append(lowerTags, strings.ToLower(tag))
  272. }
  273. releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, repo.ID, lowerTags)
  274. if err != nil {
  275. return fmt.Errorf("GetReleasesByRepoIDAndNames: %w", err)
  276. }
  277. relMap := make(map[string]*repo_model.Release)
  278. for _, rel := range releases {
  279. relMap[rel.LowerTagName] = rel
  280. }
  281. newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap))
  282. emailToUser := make(map[string]*user_model.User)
  283. for i, lowerTag := range lowerTags {
  284. tag, err := gitRepo.GetTag(tags[i])
  285. if err != nil {
  286. return fmt.Errorf("GetTag: %w", err)
  287. }
  288. commit, err := tag.Commit(gitRepo)
  289. if err != nil {
  290. return fmt.Errorf("Commit: %w", err)
  291. }
  292. sig := tag.Tagger
  293. if sig == nil {
  294. sig = commit.Author
  295. }
  296. if sig == nil {
  297. sig = commit.Committer
  298. }
  299. var author *user_model.User
  300. createdAt := time.Unix(1, 0)
  301. if sig != nil {
  302. var ok bool
  303. author, ok = emailToUser[sig.Email]
  304. if !ok {
  305. author, err = user_model.GetUserByEmailContext(ctx, sig.Email)
  306. if err != nil && !user_model.IsErrUserNotExist(err) {
  307. return fmt.Errorf("GetUserByEmail: %w", err)
  308. }
  309. if author != nil {
  310. emailToUser[sig.Email] = author
  311. }
  312. }
  313. createdAt = sig.When
  314. }
  315. commitsCount, err := commit.CommitsCount()
  316. if err != nil {
  317. return fmt.Errorf("CommitsCount: %w", err)
  318. }
  319. rel, has := relMap[lowerTag]
  320. if !has {
  321. rel = &repo_model.Release{
  322. RepoID: repo.ID,
  323. Title: "",
  324. TagName: tags[i],
  325. LowerTagName: lowerTag,
  326. Target: "",
  327. Sha1: commit.ID.String(),
  328. NumCommits: commitsCount,
  329. Note: "",
  330. IsDraft: false,
  331. IsPrerelease: false,
  332. IsTag: true,
  333. CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
  334. }
  335. if author != nil {
  336. rel.PublisherID = author.ID
  337. }
  338. newReleases = append(newReleases, rel)
  339. } else {
  340. rel.Sha1 = commit.ID.String()
  341. rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
  342. rel.NumCommits = commitsCount
  343. rel.IsDraft = false
  344. if rel.IsTag && author != nil {
  345. rel.PublisherID = author.ID
  346. }
  347. if err = repo_model.UpdateRelease(ctx, rel); err != nil {
  348. return fmt.Errorf("Update: %w", err)
  349. }
  350. }
  351. }
  352. if len(newReleases) > 0 {
  353. if err = db.Insert(ctx, newReleases); err != nil {
  354. return fmt.Errorf("Insert: %w", err)
  355. }
  356. }
  357. return nil
  358. }