gitea源码

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