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

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