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.

branch.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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. "code.gitea.io/gitea/models"
  10. actions_model "code.gitea.io/gitea/models/actions"
  11. "code.gitea.io/gitea/models/db"
  12. git_model "code.gitea.io/gitea/models/git"
  13. issues_model "code.gitea.io/gitea/models/issues"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/graceful"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/queue"
  20. repo_module "code.gitea.io/gitea/modules/repository"
  21. "code.gitea.io/gitea/modules/timeutil"
  22. "code.gitea.io/gitea/modules/util"
  23. webhook_module "code.gitea.io/gitea/modules/webhook"
  24. notify_service "code.gitea.io/gitea/services/notify"
  25. files_service "code.gitea.io/gitea/services/repository/files"
  26. "xorm.io/builder"
  27. )
  28. // CreateNewBranch creates a new repository branch
  29. func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) {
  30. branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
  31. if err != nil {
  32. return err
  33. }
  34. return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName)
  35. }
  36. // Branch contains the branch information
  37. type Branch struct {
  38. DBBranch *git_model.Branch
  39. IsProtected bool
  40. IsIncluded bool
  41. CommitsAhead int
  42. CommitsBehind int
  43. LatestPullRequest *issues_model.PullRequest
  44. MergeMovedOn bool
  45. }
  46. // LoadBranches loads branches from the repository limited by page & pageSize.
  47. func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
  48. defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
  49. if err != nil {
  50. return nil, nil, 0, err
  51. }
  52. branchOpts := git_model.FindBranchOptions{
  53. RepoID: repo.ID,
  54. IsDeletedBranch: isDeletedBranch,
  55. ListOptions: db.ListOptions{
  56. Page: page,
  57. PageSize: pageSize,
  58. },
  59. Keyword: keyword,
  60. }
  61. totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts)
  62. if err != nil {
  63. return nil, nil, 0, err
  64. }
  65. branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch}
  66. dbBranches, err := git_model.FindBranches(ctx, branchOpts)
  67. if err != nil {
  68. return nil, nil, 0, err
  69. }
  70. if err := dbBranches.LoadDeletedBy(ctx); err != nil {
  71. return nil, nil, 0, err
  72. }
  73. if err := dbBranches.LoadPusher(ctx); err != nil {
  74. return nil, nil, 0, err
  75. }
  76. rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
  77. if err != nil {
  78. return nil, nil, 0, err
  79. }
  80. repoIDToRepo := map[int64]*repo_model.Repository{}
  81. repoIDToRepo[repo.ID] = repo
  82. repoIDToGitRepo := map[int64]*git.Repository{}
  83. repoIDToGitRepo[repo.ID] = gitRepo
  84. branches := make([]*Branch, 0, len(dbBranches))
  85. for i := range dbBranches {
  86. branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
  87. if err != nil {
  88. return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
  89. }
  90. branches = append(branches, branch)
  91. }
  92. // Always add the default branch
  93. log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
  94. defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
  95. if err != nil {
  96. return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
  97. }
  98. return defaultBranch, branches, totalNumOfBranches, nil
  99. }
  100. func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
  101. repoIDToRepo map[int64]*repo_model.Repository,
  102. repoIDToGitRepo map[int64]*git.Repository,
  103. ) (*Branch, error) {
  104. log.Trace("loadOneBranch: '%s'", dbBranch.Name)
  105. branchName := dbBranch.Name
  106. p := protectedBranches.GetFirstMatched(branchName)
  107. isProtected := p != nil
  108. divergence := &git.DivergeObject{
  109. Ahead: -1,
  110. Behind: -1,
  111. }
  112. // it's not default branch
  113. if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
  114. var err error
  115. divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
  116. if err != nil {
  117. log.Error("CountDivergingCommits: %v", err)
  118. }
  119. }
  120. pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName)
  121. if err != nil {
  122. return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
  123. }
  124. headCommit := dbBranch.CommitID
  125. mergeMovedOn := false
  126. if pr != nil {
  127. pr.HeadRepo = repo
  128. if err := pr.LoadIssue(ctx); err != nil {
  129. return nil, fmt.Errorf("LoadIssue: %v", err)
  130. }
  131. if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
  132. pr.BaseRepo = repo
  133. } else if err := pr.LoadBaseRepo(ctx); err != nil {
  134. return nil, fmt.Errorf("LoadBaseRepo: %v", err)
  135. } else {
  136. repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
  137. }
  138. pr.Issue.Repo = pr.BaseRepo
  139. if pr.HasMerged {
  140. baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
  141. if !ok {
  142. baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
  143. if err != nil {
  144. return nil, fmt.Errorf("OpenRepository: %v", err)
  145. }
  146. defer baseGitRepo.Close()
  147. repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
  148. }
  149. pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
  150. if err != nil && !git.IsErrNotExist(err) {
  151. return nil, fmt.Errorf("GetBranchCommitID: %v", err)
  152. }
  153. if err == nil && headCommit != pullCommit {
  154. // the head has moved on from the merge - we shouldn't delete
  155. mergeMovedOn = true
  156. }
  157. }
  158. }
  159. isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
  160. return &Branch{
  161. DBBranch: dbBranch,
  162. IsProtected: isProtected,
  163. IsIncluded: isIncluded,
  164. CommitsAhead: divergence.Ahead,
  165. CommitsBehind: divergence.Behind,
  166. LatestPullRequest: pr,
  167. MergeMovedOn: mergeMovedOn,
  168. }, nil
  169. }
  170. func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
  171. return git.GetBranchCommitID(ctx, repo.RepoPath(), branch)
  172. }
  173. // checkBranchName validates branch name with existing repository branches
  174. func checkBranchName(ctx context.Context, repo *repo_model.Repository, name string) error {
  175. _, err := git.WalkReferences(ctx, repo.RepoPath(), func(_, refName string) error {
  176. branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
  177. switch {
  178. case branchRefName == name:
  179. return git_model.ErrBranchAlreadyExists{
  180. BranchName: name,
  181. }
  182. // If branchRefName like a/b but we want to create a branch named a then we have a conflict
  183. case strings.HasPrefix(branchRefName, name+"/"):
  184. return git_model.ErrBranchNameConflict{
  185. BranchName: branchRefName,
  186. }
  187. // Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
  188. case strings.HasPrefix(name, branchRefName+"/"):
  189. return git_model.ErrBranchNameConflict{
  190. BranchName: branchRefName,
  191. }
  192. case refName == git.TagPrefix+name:
  193. return models.ErrTagAlreadyExists{
  194. TagName: name,
  195. }
  196. }
  197. return nil
  198. })
  199. return err
  200. }
  201. // syncBranchToDB sync the branch information in the database. It will try to update the branch first,
  202. // if updated success with affect records > 0, then all are done. Because that means the branch has been in the database.
  203. // If no record is affected, that means the branch does not exist in database. So there are two possibilities.
  204. // One is this is a new branch, then we just need to insert the record. Another is the branches haven't been synced,
  205. // then we need to sync all the branches into database.
  206. func syncBranchToDB(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
  207. cnt, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit)
  208. if err != nil {
  209. return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
  210. }
  211. if cnt > 0 { // This means branch does exist, so it's a normal update. It also means the branch has been synced.
  212. return nil
  213. }
  214. // if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
  215. // we cannot simply insert the branch but need to check we have branches or not
  216. hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
  217. RepoID: repoID,
  218. IsDeletedBranch: util.OptionalBoolFalse,
  219. }.ToConds())
  220. if err != nil {
  221. return err
  222. }
  223. if !hasBranch {
  224. if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
  225. return fmt.Errorf("repo_module.SyncRepoBranches %d:%s failed: %v", repoID, branchName, err)
  226. }
  227. return nil
  228. }
  229. // if database have branches but not this branch, it means this is a new branch
  230. return db.Insert(ctx, &git_model.Branch{
  231. RepoID: repoID,
  232. Name: branchName,
  233. CommitID: commit.ID.String(),
  234. CommitMessage: commit.Summary(),
  235. PusherID: pusherID,
  236. CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
  237. })
  238. }
  239. // CreateNewBranchFromCommit creates a new repository branch
  240. func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) {
  241. err = repo.MustNotBeArchived()
  242. if err != nil {
  243. return err
  244. }
  245. // Check if branch name can be used
  246. if err := checkBranchName(ctx, repo, branchName); err != nil {
  247. return err
  248. }
  249. if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
  250. Remote: repo.RepoPath(),
  251. Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
  252. Env: repo_module.PushingEnvironment(doer, repo),
  253. }); err != nil {
  254. if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
  255. return err
  256. }
  257. return fmt.Errorf("push: %w", err)
  258. }
  259. return nil
  260. }
  261. // RenameBranch rename a branch
  262. func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) {
  263. if from == to {
  264. return "target_exist", nil
  265. }
  266. if gitRepo.IsBranchExist(to) {
  267. return "target_exist", nil
  268. }
  269. if !gitRepo.IsBranchExist(from) {
  270. return "from_not_exist", nil
  271. }
  272. if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
  273. err2 := gitRepo.RenameBranch(from, to)
  274. if err2 != nil {
  275. return err2
  276. }
  277. if isDefault {
  278. // if default branch changed, we need to delete all schedules and cron jobs
  279. if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
  280. log.Error("DeleteCronTaskByRepo: %v", err)
  281. }
  282. // cancel running cron jobs of this repository and delete old schedules
  283. if err := actions_model.CancelRunningJobs(
  284. ctx,
  285. repo.ID,
  286. from,
  287. "",
  288. webhook_module.HookEventSchedule,
  289. ); err != nil {
  290. log.Error("CancelRunningJobs: %v", err)
  291. }
  292. err2 = gitRepo.SetDefaultBranch(to)
  293. if err2 != nil {
  294. return err2
  295. }
  296. }
  297. return nil
  298. }); err != nil {
  299. return "", err
  300. }
  301. refNameTo := git.RefNameFromBranch(to)
  302. refID, err := gitRepo.GetRefCommitID(refNameTo.String())
  303. if err != nil {
  304. return "", err
  305. }
  306. notify_service.DeleteRef(ctx, doer, repo, git.RefNameFromBranch(from))
  307. notify_service.CreateRef(ctx, doer, repo, refNameTo, refID)
  308. return "", nil
  309. }
  310. // enmuerates all branch related errors
  311. var (
  312. ErrBranchIsDefault = errors.New("branch is default")
  313. )
  314. // DeleteBranch delete branch
  315. func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
  316. if branchName == repo.DefaultBranch {
  317. return ErrBranchIsDefault
  318. }
  319. isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName)
  320. if err != nil {
  321. return err
  322. }
  323. if isProtected {
  324. return git_model.ErrBranchIsProtected
  325. }
  326. rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
  327. if err != nil {
  328. return fmt.Errorf("GetBranch: %vc", err)
  329. }
  330. if rawBranch.IsDeleted {
  331. return nil
  332. }
  333. commit, err := gitRepo.GetBranchCommit(branchName)
  334. if err != nil {
  335. return err
  336. }
  337. if err := db.WithTx(ctx, func(ctx context.Context) error {
  338. if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
  339. return err
  340. }
  341. return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
  342. Force: true,
  343. })
  344. }); err != nil {
  345. return err
  346. }
  347. // Don't return error below this
  348. if err := PushUpdate(
  349. &repo_module.PushUpdateOptions{
  350. RefFullName: git.RefNameFromBranch(branchName),
  351. OldCommitID: commit.ID.String(),
  352. NewCommitID: git.EmptySHA,
  353. PusherID: doer.ID,
  354. PusherName: doer.Name,
  355. RepoUserName: repo.OwnerName,
  356. RepoName: repo.Name,
  357. }); err != nil {
  358. log.Error("Update: %v", err)
  359. }
  360. return nil
  361. }
  362. type BranchSyncOptions struct {
  363. RepoID int64
  364. }
  365. // branchSyncQueue represents a queue to handle branch sync jobs.
  366. var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
  367. func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
  368. for _, opts := range items {
  369. _, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
  370. if err != nil {
  371. log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
  372. }
  373. }
  374. return nil
  375. }
  376. func addRepoToBranchSyncQueue(repoID, doerID int64) error {
  377. return branchSyncQueue.Push(&BranchSyncOptions{
  378. RepoID: repoID,
  379. })
  380. }
  381. func initBranchSyncQueue(ctx context.Context) error {
  382. branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
  383. if branchSyncQueue == nil {
  384. return errors.New("unable to create branch_sync queue")
  385. }
  386. go graceful.GetManager().RunWithCancel(branchSyncQueue)
  387. return nil
  388. }
  389. func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
  390. if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
  391. return addRepoToBranchSyncQueue(repo.ID, doerID)
  392. }); err != nil {
  393. return fmt.Errorf("run sync all branches failed: %v", err)
  394. }
  395. return nil
  396. }
  397. func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, newBranchName string) error {
  398. if repo.DefaultBranch == newBranchName {
  399. return nil
  400. }
  401. if !gitRepo.IsBranchExist(newBranchName) {
  402. return git_model.ErrBranchNotExist{
  403. BranchName: newBranchName,
  404. }
  405. }
  406. oldDefaultBranchName := repo.DefaultBranch
  407. repo.DefaultBranch = newBranchName
  408. if err := db.WithTx(ctx, func(ctx context.Context) error {
  409. if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
  410. return err
  411. }
  412. if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
  413. log.Error("DeleteCronTaskByRepo: %v", err)
  414. }
  415. // cancel running cron jobs of this repository and delete old schedules
  416. if err := actions_model.CancelRunningJobs(
  417. ctx,
  418. repo.ID,
  419. oldDefaultBranchName,
  420. "",
  421. webhook_module.HookEventSchedule,
  422. ); err != nil {
  423. log.Error("CancelRunningJobs: %v", err)
  424. }
  425. if err := gitRepo.SetDefaultBranch(newBranchName); err != nil {
  426. if !git.IsErrUnsupportedVersion(err) {
  427. return err
  428. }
  429. }
  430. return nil
  431. }); err != nil {
  432. return err
  433. }
  434. notify_service.ChangeDefaultBranch(ctx, repo)
  435. return nil
  436. }