Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

branch.go 13KB

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