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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  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. "code.gitea.io/gitea/models/unit"
  16. "code.gitea.io/gitea/modules/base"
  17. "code.gitea.io/gitea/modules/context"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/log"
  20. repo_module "code.gitea.io/gitea/modules/repository"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/util"
  23. "code.gitea.io/gitea/modules/web"
  24. "code.gitea.io/gitea/routers/utils"
  25. "code.gitea.io/gitea/services/forms"
  26. release_service "code.gitea.io/gitea/services/release"
  27. repo_service "code.gitea.io/gitea/services/repository"
  28. files_service "code.gitea.io/gitea/services/repository/files"
  29. )
  30. const (
  31. tplBranch base.TplName = "repo/branch/list"
  32. )
  33. // Branch contains the branch information
  34. type Branch struct {
  35. Name string
  36. Commit *git.Commit
  37. IsProtected bool
  38. IsDeleted bool
  39. IsIncluded bool
  40. DeletedBranch *git_model.DeletedBranch
  41. CommitsAhead int
  42. CommitsBehind int
  43. LatestPullRequest *issues_model.PullRequest
  44. MergeMovedOn bool
  45. }
  46. // Branches render repository branch page
  47. func Branches(ctx *context.Context) {
  48. ctx.Data["Title"] = "Branches"
  49. ctx.Data["IsRepoToolbarBranches"] = true
  50. ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
  51. ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
  52. ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
  53. ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
  54. ctx.Data["CanPull"] = ctx.Repo.CanWrite(unit.TypeCode) ||
  55. (ctx.IsSigned && repo_model.HasForkedRepo(ctx.Doer.ID, ctx.Repo.Repository.ID))
  56. ctx.Data["PageIsViewCode"] = true
  57. ctx.Data["PageIsBranches"] = true
  58. page := ctx.FormInt("page")
  59. if page <= 1 {
  60. page = 1
  61. }
  62. pageSize := setting.Git.BranchesRangeSize
  63. skip := (page - 1) * pageSize
  64. log.Debug("Branches: skip: %d limit: %d", skip, pageSize)
  65. defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize)
  66. if ctx.Written() {
  67. return
  68. }
  69. ctx.Data["Branches"] = branches
  70. ctx.Data["DefaultBranchBranch"] = defaultBranchBranch
  71. pager := context.NewPagination(branchesCount, pageSize, page, 5)
  72. pager.SetDefaultParams(ctx)
  73. ctx.Data["Page"] = pager
  74. ctx.HTML(http.StatusOK, tplBranch)
  75. }
  76. // DeleteBranchPost responses for delete merged branch
  77. func DeleteBranchPost(ctx *context.Context) {
  78. defer redirect(ctx)
  79. branchName := ctx.FormString("name")
  80. if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
  81. switch {
  82. case git.IsErrBranchNotExist(err):
  83. log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName)
  84. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
  85. case errors.Is(err, repo_service.ErrBranchIsDefault):
  86. log.Debug("DeleteBranch: Can't delete default branch '%s'", branchName)
  87. ctx.Flash.Error(ctx.Tr("repo.branch.default_deletion_failed", branchName))
  88. case errors.Is(err, git_model.ErrBranchIsProtected):
  89. log.Debug("DeleteBranch: Can't delete protected branch '%s'", branchName)
  90. ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
  91. default:
  92. log.Error("DeleteBranch: %v", err)
  93. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
  94. }
  95. return
  96. }
  97. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName))
  98. }
  99. // RestoreBranchPost responses for delete merged branch
  100. func RestoreBranchPost(ctx *context.Context) {
  101. defer redirect(ctx)
  102. branchID := ctx.FormInt64("branch_id")
  103. branchName := ctx.FormString("name")
  104. deletedBranch, err := git_model.GetDeletedBranchByID(ctx, ctx.Repo.Repository.ID, branchID)
  105. if err != nil {
  106. log.Error("GetDeletedBranchByID: %v", err)
  107. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
  108. return
  109. } else if deletedBranch == nil {
  110. log.Debug("RestoreBranch: Can't restore branch[%d] '%s', as it does not exist", branchID, branchName)
  111. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
  112. return
  113. }
  114. if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
  115. Remote: ctx.Repo.Repository.RepoPath(),
  116. Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
  117. Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
  118. }); err != nil {
  119. if strings.Contains(err.Error(), "already exists") {
  120. log.Debug("RestoreBranch: Can't restore branch '%s', since one with same name already exist", deletedBranch.Name)
  121. ctx.Flash.Error(ctx.Tr("repo.branch.already_exists", deletedBranch.Name))
  122. return
  123. }
  124. log.Error("RestoreBranch: CreateBranch: %v", err)
  125. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
  126. return
  127. }
  128. // Don't return error below this
  129. if err := repo_service.PushUpdate(
  130. &repo_module.PushUpdateOptions{
  131. RefFullName: git.RefNameFromBranch(deletedBranch.Name),
  132. OldCommitID: git.EmptySHA,
  133. NewCommitID: deletedBranch.Commit,
  134. PusherID: ctx.Doer.ID,
  135. PusherName: ctx.Doer.Name,
  136. RepoUserName: ctx.Repo.Owner.Name,
  137. RepoName: ctx.Repo.Repository.Name,
  138. }); err != nil {
  139. log.Error("RestoreBranch: Update: %v", err)
  140. }
  141. ctx.Flash.Success(ctx.Tr("repo.branch.restore_success", deletedBranch.Name))
  142. }
  143. func redirect(ctx *context.Context) {
  144. ctx.JSON(http.StatusOK, map[string]any{
  145. "redirect": ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")),
  146. })
  147. }
  148. // loadBranches loads branches from the repository limited by page & pageSize.
  149. // NOTE: May write to context on error.
  150. func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) {
  151. defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch)
  152. if err != nil {
  153. if !git.IsErrBranchNotExist(err) {
  154. log.Error("loadBranches: get default branch: %v", err)
  155. ctx.ServerError("GetDefaultBranch", err)
  156. return nil, nil, 0
  157. }
  158. log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository)
  159. }
  160. rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit)
  161. if err != nil {
  162. log.Error("GetBranches: %v", err)
  163. ctx.ServerError("GetBranches", err)
  164. return nil, nil, 0
  165. }
  166. rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
  167. if err != nil {
  168. ctx.ServerError("FindRepoProtectedBranchRules", err)
  169. return nil, nil, 0
  170. }
  171. repoIDToRepo := map[int64]*repo_model.Repository{}
  172. repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
  173. repoIDToGitRepo := map[int64]*git.Repository{}
  174. repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
  175. var branches []*Branch
  176. for i := range rawBranches {
  177. if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name {
  178. // Skip default branch
  179. continue
  180. }
  181. branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
  182. if branch == nil {
  183. return nil, nil, 0
  184. }
  185. branches = append(branches, branch)
  186. }
  187. var defaultBranchBranch *Branch
  188. if defaultBranch != nil {
  189. // Always add the default branch
  190. log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
  191. defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
  192. branches = append(branches, defaultBranchBranch)
  193. }
  194. if ctx.Repo.CanWrite(unit.TypeCode) {
  195. deletedBranches, err := getDeletedBranches(ctx)
  196. if err != nil {
  197. ctx.ServerError("getDeletedBranches", err)
  198. return nil, nil, 0
  199. }
  200. branches = append(branches, deletedBranches...)
  201. }
  202. return defaultBranchBranch, branches, totalNumOfBranches
  203. }
  204. func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules,
  205. repoIDToRepo map[int64]*repo_model.Repository,
  206. repoIDToGitRepo map[int64]*git.Repository,
  207. ) *Branch {
  208. log.Trace("loadOneBranch: '%s'", rawBranch.Name)
  209. commit, err := rawBranch.GetCommit()
  210. if err != nil {
  211. ctx.ServerError("GetCommit", err)
  212. return nil
  213. }
  214. branchName := rawBranch.Name
  215. p := protectedBranches.GetFirstMatched(branchName)
  216. isProtected := p != nil
  217. divergence := &git.DivergeObject{
  218. Ahead: -1,
  219. Behind: -1,
  220. }
  221. if defaultBranch != nil {
  222. divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName)
  223. if err != nil {
  224. log.Error("CountDivergingCommits", err)
  225. }
  226. }
  227. pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
  228. if err != nil {
  229. ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
  230. return nil
  231. }
  232. headCommit := commit.ID.String()
  233. mergeMovedOn := false
  234. if pr != nil {
  235. pr.HeadRepo = ctx.Repo.Repository
  236. if err := pr.LoadIssue(ctx); err != nil {
  237. ctx.ServerError("LoadIssue", err)
  238. return nil
  239. }
  240. if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
  241. pr.BaseRepo = repo
  242. } else if err := pr.LoadBaseRepo(ctx); err != nil {
  243. ctx.ServerError("LoadBaseRepo", err)
  244. return nil
  245. } else {
  246. repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
  247. }
  248. pr.Issue.Repo = pr.BaseRepo
  249. if pr.HasMerged {
  250. baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
  251. if !ok {
  252. baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
  253. if err != nil {
  254. ctx.ServerError("OpenRepository", err)
  255. return nil
  256. }
  257. defer baseGitRepo.Close()
  258. repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
  259. }
  260. pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
  261. if err != nil && !git.IsErrNotExist(err) {
  262. ctx.ServerError("GetBranchCommitID", err)
  263. return nil
  264. }
  265. if err == nil && headCommit != pullCommit {
  266. // the head has moved on from the merge - we shouldn't delete
  267. mergeMovedOn = true
  268. }
  269. }
  270. }
  271. isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
  272. return &Branch{
  273. Name: branchName,
  274. Commit: commit,
  275. IsProtected: isProtected,
  276. IsIncluded: isIncluded,
  277. CommitsAhead: divergence.Ahead,
  278. CommitsBehind: divergence.Behind,
  279. LatestPullRequest: pr,
  280. MergeMovedOn: mergeMovedOn,
  281. }
  282. }
  283. func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
  284. branches := []*Branch{}
  285. deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID)
  286. if err != nil {
  287. return branches, err
  288. }
  289. for i := range deletedBranches {
  290. deletedBranches[i].LoadUser(ctx)
  291. branches = append(branches, &Branch{
  292. Name: deletedBranches[i].Name,
  293. IsDeleted: true,
  294. DeletedBranch: deletedBranches[i],
  295. })
  296. }
  297. return branches, nil
  298. }
  299. // CreateBranch creates new branch in repository
  300. func CreateBranch(ctx *context.Context) {
  301. form := web.GetForm(ctx).(*forms.NewBranchForm)
  302. if !ctx.Repo.CanCreateBranch() {
  303. ctx.NotFound("CreateBranch", nil)
  304. return
  305. }
  306. if ctx.HasError() {
  307. ctx.Flash.Error(ctx.GetErrMsg())
  308. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  309. return
  310. }
  311. var err error
  312. if form.CreateTag {
  313. target := ctx.Repo.CommitID
  314. if ctx.Repo.IsViewBranch {
  315. target = ctx.Repo.BranchName
  316. }
  317. err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
  318. } else if ctx.Repo.IsViewBranch {
  319. err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
  320. } else {
  321. err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
  322. }
  323. if err != nil {
  324. if models.IsErrProtectedTagName(err) {
  325. ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
  326. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  327. return
  328. }
  329. if models.IsErrTagAlreadyExists(err) {
  330. e := err.(models.ErrTagAlreadyExists)
  331. ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
  332. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  333. return
  334. }
  335. if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
  336. ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
  337. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  338. return
  339. }
  340. if models.IsErrBranchNameConflict(err) {
  341. e := err.(models.ErrBranchNameConflict)
  342. ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
  343. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  344. return
  345. }
  346. if git.IsErrPushRejected(err) {
  347. e := err.(*git.ErrPushRejected)
  348. if len(e.Message) == 0 {
  349. ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
  350. } else {
  351. flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
  352. "Message": ctx.Tr("repo.editor.push_rejected"),
  353. "Summary": ctx.Tr("repo.editor.push_rejected_summary"),
  354. "Details": utils.SanitizeFlashErrorString(e.Message),
  355. })
  356. if err != nil {
  357. ctx.ServerError("UpdatePullRequest.HTMLString", err)
  358. return
  359. }
  360. ctx.Flash.Error(flashError)
  361. }
  362. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  363. return
  364. }
  365. ctx.ServerError("CreateNewBranch", err)
  366. return
  367. }
  368. if form.CreateTag {
  369. ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.NewBranchName))
  370. ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.NewBranchName))
  371. return
  372. }
  373. ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
  374. ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName) + "/" + util.PathEscapeSegments(form.CurrentPath))
  375. }