gitea源码

branch.go 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. // Copyright 2021 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. actions_model "code.gitea.io/gitea/models/actions"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. issues_model "code.gitea.io/gitea/models/issues"
  13. access_model "code.gitea.io/gitea/models/perm/access"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. "code.gitea.io/gitea/models/unit"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/cache"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/gitrepo"
  20. "code.gitea.io/gitea/modules/graceful"
  21. "code.gitea.io/gitea/modules/json"
  22. "code.gitea.io/gitea/modules/log"
  23. "code.gitea.io/gitea/modules/optional"
  24. "code.gitea.io/gitea/modules/queue"
  25. repo_module "code.gitea.io/gitea/modules/repository"
  26. "code.gitea.io/gitea/modules/reqctx"
  27. "code.gitea.io/gitea/modules/timeutil"
  28. "code.gitea.io/gitea/modules/util"
  29. webhook_module "code.gitea.io/gitea/modules/webhook"
  30. actions_service "code.gitea.io/gitea/services/actions"
  31. notify_service "code.gitea.io/gitea/services/notify"
  32. release_service "code.gitea.io/gitea/services/release"
  33. files_service "code.gitea.io/gitea/services/repository/files"
  34. "xorm.io/builder"
  35. )
  36. // CreateNewBranch creates a new repository branch
  37. func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) {
  38. branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
  39. if err != nil {
  40. return err
  41. }
  42. return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName)
  43. }
  44. // Branch contains the branch information
  45. type Branch struct {
  46. DBBranch *git_model.Branch
  47. IsProtected bool
  48. IsIncluded bool
  49. CommitsAhead int
  50. CommitsBehind int
  51. LatestPullRequest *issues_model.PullRequest
  52. MergeMovedOn bool
  53. }
  54. // LoadBranches loads branches from the repository limited by page & pageSize.
  55. func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch optional.Option[bool], keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
  56. defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
  57. if err != nil {
  58. return nil, nil, 0, err
  59. }
  60. branchOpts := git_model.FindBranchOptions{
  61. RepoID: repo.ID,
  62. IsDeletedBranch: isDeletedBranch,
  63. ListOptions: db.ListOptions{
  64. Page: page,
  65. PageSize: pageSize,
  66. },
  67. Keyword: keyword,
  68. ExcludeBranchNames: []string{repo.DefaultBranch},
  69. }
  70. dbBranches, totalNumOfBranches, err := db.FindAndCount[git_model.Branch](ctx, branchOpts)
  71. if err != nil {
  72. return nil, nil, 0, err
  73. }
  74. if err := git_model.BranchList(dbBranches).LoadDeletedBy(ctx); err != nil {
  75. return nil, nil, 0, err
  76. }
  77. if err := git_model.BranchList(dbBranches).LoadPusher(ctx); err != nil {
  78. return nil, nil, 0, err
  79. }
  80. rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
  81. if err != nil {
  82. return nil, nil, 0, err
  83. }
  84. repoIDToRepo := map[int64]*repo_model.Repository{}
  85. repoIDToRepo[repo.ID] = repo
  86. repoIDToGitRepo := map[int64]*git.Repository{}
  87. repoIDToGitRepo[repo.ID] = gitRepo
  88. branches := make([]*Branch, 0, len(dbBranches))
  89. for i := range dbBranches {
  90. branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
  91. if err != nil {
  92. return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
  93. }
  94. branches = append(branches, branch)
  95. }
  96. // Always add the default branch
  97. log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
  98. defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
  99. if err != nil {
  100. return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
  101. }
  102. return defaultBranch, branches, totalNumOfBranches, nil
  103. }
  104. func getDivergenceCacheKey(repoID int64, branchName string) string {
  105. return fmt.Sprintf("%d-%s", repoID, branchName)
  106. }
  107. // getDivergenceFromCache gets the divergence from cache
  108. func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
  109. data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
  110. res := git.DivergeObject{
  111. Ahead: -1,
  112. Behind: -1,
  113. }
  114. if !ok || data == "" {
  115. return &res, false
  116. }
  117. if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil {
  118. log.Error("json.UnMarshal failed: %v", err)
  119. return &res, false
  120. }
  121. return &res, true
  122. }
  123. func putDivergenceFromCache(repoID int64, branchName string, divergence *git.DivergeObject) error {
  124. bs, err := json.Marshal(divergence)
  125. if err != nil {
  126. return err
  127. }
  128. return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60)
  129. }
  130. func DelDivergenceFromCache(repoID int64, branchName string) error {
  131. return cache.GetCache().Delete(getDivergenceCacheKey(repoID, branchName))
  132. }
  133. // DelRepoDivergenceFromCache deletes all divergence caches of a repository
  134. func DelRepoDivergenceFromCache(ctx context.Context, repoID int64) error {
  135. dbBranches, err := db.Find[git_model.Branch](ctx, git_model.FindBranchOptions{
  136. RepoID: repoID,
  137. ListOptions: db.ListOptionsAll,
  138. })
  139. if err != nil {
  140. return err
  141. }
  142. for i := range dbBranches {
  143. if err := DelDivergenceFromCache(repoID, dbBranches[i].Name); err != nil {
  144. log.Error("DelDivergenceFromCache: %v", err)
  145. }
  146. }
  147. return nil
  148. }
  149. func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
  150. repoIDToRepo map[int64]*repo_model.Repository,
  151. repoIDToGitRepo map[int64]*git.Repository,
  152. ) (*Branch, error) {
  153. log.Trace("loadOneBranch: '%s'", dbBranch.Name)
  154. branchName := dbBranch.Name
  155. p := protectedBranches.GetFirstMatched(branchName)
  156. isProtected := p != nil
  157. var divergence *git.DivergeObject
  158. // it's not default branch
  159. if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
  160. var cached bool
  161. divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name)
  162. if !cached {
  163. var err error
  164. divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
  165. if err != nil {
  166. log.Error("CountDivergingCommits: %v", err)
  167. } else {
  168. if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil {
  169. log.Error("putDivergenceFromCache: %v", err)
  170. }
  171. }
  172. }
  173. }
  174. if divergence == nil {
  175. // tolerate the error that we cannot get divergence
  176. divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
  177. }
  178. pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
  179. if err != nil {
  180. return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
  181. }
  182. headCommit := dbBranch.CommitID
  183. mergeMovedOn := false
  184. if pr != nil {
  185. pr.HeadRepo = repo
  186. if err := pr.LoadIssue(ctx); err != nil {
  187. return nil, fmt.Errorf("LoadIssue: %v", err)
  188. }
  189. if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
  190. pr.BaseRepo = repo
  191. } else if err := pr.LoadBaseRepo(ctx); err != nil {
  192. return nil, fmt.Errorf("LoadBaseRepo: %v", err)
  193. } else {
  194. repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
  195. }
  196. pr.Issue.Repo = pr.BaseRepo
  197. if pr.HasMerged {
  198. baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
  199. if !ok {
  200. baseGitRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
  201. if err != nil {
  202. return nil, fmt.Errorf("OpenRepository: %v", err)
  203. }
  204. defer baseGitRepo.Close()
  205. repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
  206. }
  207. pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitHeadRefName())
  208. if err != nil && !git.IsErrNotExist(err) {
  209. return nil, fmt.Errorf("GetBranchCommitID: %v", err)
  210. }
  211. if err == nil && headCommit != pullCommit {
  212. // the head has moved on from the merge - we shouldn't delete
  213. mergeMovedOn = true
  214. }
  215. }
  216. }
  217. isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
  218. return &Branch{
  219. DBBranch: dbBranch,
  220. IsProtected: isProtected,
  221. IsIncluded: isIncluded,
  222. CommitsAhead: divergence.Ahead,
  223. CommitsBehind: divergence.Behind,
  224. LatestPullRequest: pr,
  225. MergeMovedOn: mergeMovedOn,
  226. }, nil
  227. }
  228. // checkBranchName validates branch name with existing repository branches
  229. func checkBranchName(ctx context.Context, repo *repo_model.Repository, name string) error {
  230. _, err := gitrepo.WalkReferences(ctx, repo, func(_, refName string) error {
  231. branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
  232. switch {
  233. case branchRefName == name:
  234. return git_model.ErrBranchAlreadyExists{
  235. BranchName: name,
  236. }
  237. // If branchRefName like a/b but we want to create a branch named a then we have a conflict
  238. case strings.HasPrefix(branchRefName, name+"/"):
  239. return git_model.ErrBranchNameConflict{
  240. BranchName: branchRefName,
  241. }
  242. // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
  243. case strings.HasPrefix(name, branchRefName+"/"):
  244. return git_model.ErrBranchNameConflict{
  245. BranchName: branchRefName,
  246. }
  247. case refName == git.TagPrefix+name:
  248. return release_service.ErrTagAlreadyExists{
  249. TagName: name,
  250. }
  251. }
  252. return nil
  253. })
  254. return err
  255. }
  256. // SyncBranchesToDB sync the branch information in the database.
  257. // It will check whether the branches of the repository have never been synced before.
  258. // If so, it will sync all branches of the repository.
  259. // Otherwise, it will sync the branches that need to be updated.
  260. func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, commitIDs []string, getCommit func(commitID string) (*git.Commit, error)) error {
  261. // Some designs that make the code look strange but are made for performance optimization purposes:
  262. // 1. Sync branches in a batch to reduce the number of DB queries.
  263. // 2. Lazy load commit information since it may be not necessary.
  264. // 3. Exit early if synced all branches of git repo when there's no branch in DB.
  265. // 4. Check the branches in DB if they are already synced.
  266. //
  267. // If the user pushes many branches at once, the Git hook will call the internal API in batches, rather than all at once.
  268. // See https://github.com/go-gitea/gitea/blob/cb52b17f92e2d2293f7c003649743464492bca48/cmd/hook.go#L27
  269. // For the first batch, it will hit optimization 3.
  270. // For other batches, it will hit optimization 4.
  271. if len(branchNames) != len(commitIDs) {
  272. return errors.New("branchNames and commitIDs length not match")
  273. }
  274. return db.WithTx(ctx, func(ctx context.Context) error {
  275. branches, err := git_model.GetBranches(ctx, repoID, branchNames, true)
  276. if err != nil {
  277. return fmt.Errorf("git_model.GetBranches: %v", err)
  278. }
  279. if len(branches) == 0 {
  280. // if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
  281. // we cannot simply insert the branch but need to check we have branches or not
  282. hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
  283. RepoID: repoID,
  284. IsDeletedBranch: optional.Some(false),
  285. }.ToConds())
  286. if err != nil {
  287. return err
  288. }
  289. if !hasBranch {
  290. if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
  291. return fmt.Errorf("repo_module.SyncRepoBranches %d failed: %v", repoID, err)
  292. }
  293. return nil
  294. }
  295. }
  296. branchMap := make(map[string]*git_model.Branch, len(branches))
  297. for _, branch := range branches {
  298. branchMap[branch.Name] = branch
  299. }
  300. newBranches := make([]*git_model.Branch, 0, len(branchNames))
  301. for i, branchName := range branchNames {
  302. commitID := commitIDs[i]
  303. branch, exist := branchMap[branchName]
  304. if exist && branch.CommitID == commitID && !branch.IsDeleted {
  305. continue
  306. }
  307. commit, err := getCommit(commitID)
  308. if err != nil {
  309. return fmt.Errorf("get commit of %s failed: %v", branchName, err)
  310. }
  311. if exist {
  312. if _, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit); err != nil {
  313. return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
  314. }
  315. continue
  316. }
  317. // if database have branches but not this branch, it means this is a new branch
  318. newBranches = append(newBranches, &git_model.Branch{
  319. RepoID: repoID,
  320. Name: branchName,
  321. CommitID: commit.ID.String(),
  322. CommitMessage: commit.Summary(),
  323. PusherID: pusherID,
  324. CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
  325. })
  326. }
  327. if len(newBranches) > 0 {
  328. return db.Insert(ctx, newBranches)
  329. }
  330. return nil
  331. })
  332. }
  333. // CreateNewBranchFromCommit creates a new repository branch
  334. func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) {
  335. err = repo.MustNotBeArchived()
  336. if err != nil {
  337. return err
  338. }
  339. // Check if branch name can be used
  340. if err := checkBranchName(ctx, repo, branchName); err != nil {
  341. return err
  342. }
  343. if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
  344. Remote: repo.RepoPath(),
  345. Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
  346. Env: repo_module.PushingEnvironment(doer, repo),
  347. }); err != nil {
  348. if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
  349. return err
  350. }
  351. return fmt.Errorf("push: %w", err)
  352. }
  353. return nil
  354. }
  355. // RenameBranch rename a branch
  356. func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) {
  357. err := repo.MustNotBeArchived()
  358. if err != nil {
  359. return "", err
  360. }
  361. if from == to {
  362. return "target_exist", nil
  363. }
  364. if gitrepo.IsBranchExist(ctx, repo, to) {
  365. return "target_exist", nil
  366. }
  367. if !gitrepo.IsBranchExist(ctx, repo, from) {
  368. return "from_not_exist", nil
  369. }
  370. perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
  371. if err != nil {
  372. return "", err
  373. }
  374. isDefault := from == repo.DefaultBranch
  375. if isDefault && !perm.IsAdmin() {
  376. return "", repo_model.ErrUserDoesNotHaveAccessToRepo{
  377. UserID: doer.ID,
  378. RepoName: repo.LowerName,
  379. }
  380. }
  381. // If from == rule name, admins are allowed to modify them.
  382. if protectedBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, from); err != nil {
  383. return "", err
  384. } else if protectedBranch != nil && !perm.IsAdmin() {
  385. return "", repo_model.ErrUserDoesNotHaveAccessToRepo{
  386. UserID: doer.ID,
  387. RepoName: repo.LowerName,
  388. }
  389. }
  390. if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
  391. err2 := gitRepo.RenameBranch(from, to)
  392. if err2 != nil {
  393. return err2
  394. }
  395. if isDefault {
  396. // if default branch changed, we need to delete all schedules and cron jobs
  397. if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
  398. log.Error("DeleteCronTaskByRepo: %v", err)
  399. }
  400. // cancel running cron jobs of this repository and delete old schedules
  401. if err := actions_service.CancelPreviousJobs(
  402. ctx,
  403. repo.ID,
  404. from,
  405. "",
  406. webhook_module.HookEventSchedule,
  407. ); err != nil {
  408. log.Error("CancelPreviousJobs: %v", err)
  409. }
  410. err2 = gitrepo.SetDefaultBranch(ctx, repo, to)
  411. if err2 != nil {
  412. return err2
  413. }
  414. }
  415. return nil
  416. }); err != nil {
  417. return "", err
  418. }
  419. refNameTo := git.RefNameFromBranch(to)
  420. refID, err := gitRepo.GetRefCommitID(refNameTo.String())
  421. if err != nil {
  422. return "", err
  423. }
  424. notify_service.DeleteRef(ctx, doer, repo, git.RefNameFromBranch(from))
  425. notify_service.CreateRef(ctx, doer, repo, refNameTo, refID)
  426. return "", nil
  427. }
  428. // enmuerates all branch related errors
  429. var (
  430. ErrBranchIsDefault = errors.New("branch is default")
  431. )
  432. func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
  433. if branchName == repo.DefaultBranch {
  434. return ErrBranchIsDefault
  435. }
  436. perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
  437. if err != nil {
  438. return err
  439. }
  440. if !perm.CanWrite(unit.TypeCode) {
  441. return util.NewPermissionDeniedErrorf("permission denied to access repo %d unit %s", repo.ID, unit.TypeCode.LogString())
  442. }
  443. isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName)
  444. if err != nil {
  445. return err
  446. }
  447. if isProtected {
  448. return git_model.ErrBranchIsProtected
  449. }
  450. return nil
  451. }
  452. // DeleteBranch delete branch
  453. func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error {
  454. err := repo.MustNotBeArchived()
  455. if err != nil {
  456. return err
  457. }
  458. if err := CanDeleteBranch(ctx, repo, branchName, doer); err != nil {
  459. return err
  460. }
  461. rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
  462. if err != nil && !git_model.IsErrBranchNotExist(err) {
  463. return fmt.Errorf("GetBranch: %vc", err)
  464. }
  465. // database branch record not exist or it's a deleted branch
  466. notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
  467. branchCommit, err := gitRepo.GetBranchCommit(branchName)
  468. if err != nil && !errors.Is(err, util.ErrNotExist) {
  469. return err
  470. }
  471. if err := db.WithTx(ctx, func(ctx context.Context) error {
  472. if !notExist {
  473. if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
  474. return err
  475. }
  476. }
  477. if pr != nil {
  478. if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
  479. return fmt.Errorf("DeleteBranch: %v", err)
  480. }
  481. }
  482. if branchCommit == nil {
  483. return nil
  484. }
  485. return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
  486. Force: true,
  487. })
  488. }); err != nil {
  489. return err
  490. }
  491. if branchCommit == nil {
  492. return nil
  493. }
  494. // Don't return error below this
  495. objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
  496. if err := PushUpdate(
  497. &repo_module.PushUpdateOptions{
  498. RefFullName: git.RefNameFromBranch(branchName),
  499. OldCommitID: branchCommit.ID.String(),
  500. NewCommitID: objectFormat.EmptyObjectID().String(),
  501. PusherID: doer.ID,
  502. PusherName: doer.Name,
  503. RepoUserName: repo.OwnerName,
  504. RepoName: repo.Name,
  505. }); err != nil {
  506. log.Error("PushUpdateOptions: %v", err)
  507. }
  508. return nil
  509. }
  510. type BranchSyncOptions struct {
  511. RepoID int64
  512. }
  513. // branchSyncQueue represents a queue to handle branch sync jobs.
  514. var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
  515. func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
  516. for _, opts := range items {
  517. _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
  518. if err != nil {
  519. log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
  520. }
  521. }
  522. return nil
  523. }
  524. func addRepoToBranchSyncQueue(repoID int64) error {
  525. return branchSyncQueue.Push(&BranchSyncOptions{
  526. RepoID: repoID,
  527. })
  528. }
  529. func initBranchSyncQueue(ctx context.Context) error {
  530. branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
  531. if branchSyncQueue == nil {
  532. return errors.New("unable to create branch_sync queue")
  533. }
  534. go graceful.GetManager().RunWithCancel(branchSyncQueue)
  535. return nil
  536. }
  537. func AddAllRepoBranchesToSyncQueue(ctx context.Context) error {
  538. if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
  539. return addRepoToBranchSyncQueue(repo.ID)
  540. }); err != nil {
  541. return fmt.Errorf("run sync all branches failed: %v", err)
  542. }
  543. return nil
  544. }
  545. func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, newBranchName string) error {
  546. if repo.DefaultBranch == newBranchName {
  547. return nil
  548. }
  549. if !gitrepo.IsBranchExist(ctx, repo, newBranchName) {
  550. return git_model.ErrBranchNotExist{
  551. BranchName: newBranchName,
  552. }
  553. }
  554. oldDefaultBranchName := repo.DefaultBranch
  555. repo.DefaultBranch = newBranchName
  556. if err := db.WithTx(ctx, func(ctx context.Context) error {
  557. if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
  558. return err
  559. }
  560. if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
  561. log.Error("DeleteCronTaskByRepo: %v", err)
  562. }
  563. // cancel running cron jobs of this repository and delete old schedules
  564. if err := actions_service.CancelPreviousJobs(
  565. ctx,
  566. repo.ID,
  567. oldDefaultBranchName,
  568. "",
  569. webhook_module.HookEventSchedule,
  570. ); err != nil {
  571. log.Error("CancelPreviousJobs: %v", err)
  572. }
  573. return gitrepo.SetDefaultBranch(ctx, repo, newBranchName)
  574. }); err != nil {
  575. return err
  576. }
  577. if !repo.IsEmpty {
  578. if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{
  579. RepoID: repo.ID,
  580. }); err != nil {
  581. log.Error("AddRepoToLicenseUpdaterQueue: %v", err)
  582. }
  583. }
  584. // clear divergence cache
  585. if err := DelRepoDivergenceFromCache(ctx, repo.ID); err != nil {
  586. log.Error("DelRepoDivergenceFromCache: %v", err)
  587. }
  588. notify_service.ChangeDefaultBranch(ctx, repo)
  589. return nil
  590. }
  591. // BranchDivergingInfo contains the information about the divergence of a head branch to the base branch.
  592. type BranchDivergingInfo struct {
  593. // whether the base branch contains new commits which are not in the head branch
  594. BaseHasNewCommits bool
  595. // behind/after are number of commits that the head branch is behind/after the base branch, it's 0 if it's unable to calculate.
  596. // there could be a case that BaseHasNewCommits=true while the behind/after are both 0 (unable to calculate).
  597. HeadCommitsBehind int
  598. HeadCommitsAhead int
  599. }
  600. // GetBranchDivergingInfo returns the information about the divergence of a patch branch to the base branch.
  601. func GetBranchDivergingInfo(ctx reqctx.RequestContext, baseRepo *repo_model.Repository, baseBranch string, headRepo *repo_model.Repository, headBranch string) (*BranchDivergingInfo, error) {
  602. headGitBranch, err := git_model.GetBranch(ctx, headRepo.ID, headBranch)
  603. if err != nil {
  604. return nil, err
  605. }
  606. if headGitBranch.IsDeleted {
  607. return nil, git_model.ErrBranchNotExist{
  608. BranchName: headBranch,
  609. }
  610. }
  611. baseGitBranch, err := git_model.GetBranch(ctx, baseRepo.ID, baseBranch)
  612. if err != nil {
  613. return nil, err
  614. }
  615. if baseGitBranch.IsDeleted {
  616. return nil, git_model.ErrBranchNotExist{
  617. BranchName: baseBranch,
  618. }
  619. }
  620. info := &BranchDivergingInfo{}
  621. if headGitBranch.CommitID == baseGitBranch.CommitID {
  622. return info, nil
  623. }
  624. // if the fork repo has new commits, this call will fail because they are not in the base repo
  625. // exit status 128 - fatal: Invalid symmetric difference expression aaaaaaaaaaaa...bbbbbbbbbbbb
  626. // so at the moment, we first check the update time, then check whether the fork branch has base's head
  627. diff, err := git.GetDivergingCommits(ctx, baseRepo.RepoPath(), baseGitBranch.CommitID, headGitBranch.CommitID)
  628. if err != nil {
  629. info.BaseHasNewCommits = baseGitBranch.UpdatedUnix > headGitBranch.UpdatedUnix
  630. if headRepo.IsFork && info.BaseHasNewCommits {
  631. return info, nil
  632. }
  633. // if the base's update time is before the fork, check whether the base's head is in the fork
  634. headGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, headRepo)
  635. if err != nil {
  636. return nil, err
  637. }
  638. headCommit, err := headGitRepo.GetCommit(headGitBranch.CommitID)
  639. if err != nil {
  640. return nil, err
  641. }
  642. baseCommitID, err := git.NewIDFromString(baseGitBranch.CommitID)
  643. if err != nil {
  644. return nil, err
  645. }
  646. hasPreviousCommit, _ := headCommit.HasPreviousCommit(baseCommitID)
  647. info.BaseHasNewCommits = !hasPreviousCommit
  648. return info, nil
  649. }
  650. info.HeadCommitsBehind, info.HeadCommitsAhead = diff.Behind, diff.Ahead
  651. info.BaseHasNewCommits = info.HeadCommitsBehind > 0
  652. return info, nil
  653. }