本站源代码
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.

298 lines
8.2KB

  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "strings"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/auth"
  10. "code.gitea.io/gitea/modules/base"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/repofiles"
  15. "code.gitea.io/gitea/modules/util"
  16. )
  17. const (
  18. tplBranch base.TplName = "repo/branch/list"
  19. )
  20. // Branch contains the branch information
  21. type Branch struct {
  22. Name string
  23. Commit *git.Commit
  24. IsProtected bool
  25. IsDeleted bool
  26. IsIncluded bool
  27. DeletedBranch *models.DeletedBranch
  28. CommitsAhead int
  29. CommitsBehind int
  30. LatestPullRequest *models.PullRequest
  31. }
  32. // Branches render repository branch page
  33. func Branches(ctx *context.Context) {
  34. ctx.Data["Title"] = "Branches"
  35. ctx.Data["IsRepoToolbarBranches"] = true
  36. ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
  37. ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
  38. ctx.Data["IsWriter"] = ctx.Repo.CanWrite(models.UnitTypeCode)
  39. ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
  40. ctx.Data["PageIsViewCode"] = true
  41. ctx.Data["PageIsBranches"] = true
  42. ctx.Data["Branches"] = loadBranches(ctx)
  43. ctx.HTML(200, tplBranch)
  44. }
  45. // DeleteBranchPost responses for delete merged branch
  46. func DeleteBranchPost(ctx *context.Context) {
  47. defer redirect(ctx)
  48. branchName := ctx.Query("name")
  49. isProtected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.User)
  50. if err != nil {
  51. log.Error("DeleteBranch: %v", err)
  52. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
  53. return
  54. }
  55. if isProtected {
  56. ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
  57. return
  58. }
  59. if !ctx.Repo.GitRepo.IsBranchExist(branchName) || branchName == ctx.Repo.Repository.DefaultBranch {
  60. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
  61. return
  62. }
  63. if err := deleteBranch(ctx, branchName); err != nil {
  64. ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
  65. return
  66. }
  67. ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName))
  68. }
  69. // RestoreBranchPost responses for delete merged branch
  70. func RestoreBranchPost(ctx *context.Context) {
  71. defer redirect(ctx)
  72. branchID := ctx.QueryInt64("branch_id")
  73. branchName := ctx.Query("name")
  74. deletedBranch, err := ctx.Repo.Repository.GetDeletedBranchByID(branchID)
  75. if err != nil {
  76. log.Error("GetDeletedBranchByID: %v", err)
  77. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
  78. return
  79. }
  80. if err := ctx.Repo.GitRepo.CreateBranch(deletedBranch.Name, deletedBranch.Commit); err != nil {
  81. if strings.Contains(err.Error(), "already exists") {
  82. ctx.Flash.Error(ctx.Tr("repo.branch.already_exists", deletedBranch.Name))
  83. return
  84. }
  85. log.Error("CreateBranch: %v", err)
  86. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
  87. return
  88. }
  89. if err := ctx.Repo.Repository.RemoveDeletedBranch(deletedBranch.ID); err != nil {
  90. log.Error("RemoveDeletedBranch: %v", err)
  91. ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
  92. return
  93. }
  94. ctx.Flash.Success(ctx.Tr("repo.branch.restore_success", deletedBranch.Name))
  95. }
  96. func redirect(ctx *context.Context) {
  97. ctx.JSON(200, map[string]interface{}{
  98. "redirect": ctx.Repo.RepoLink + "/branches",
  99. })
  100. }
  101. func deleteBranch(ctx *context.Context, branchName string) error {
  102. commit, err := ctx.Repo.GitRepo.GetBranchCommit(branchName)
  103. if err != nil {
  104. log.Error("GetBranchCommit: %v", err)
  105. return err
  106. }
  107. if err := ctx.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
  108. Force: true,
  109. }); err != nil {
  110. log.Error("DeleteBranch: %v", err)
  111. return err
  112. }
  113. // Don't return error below this
  114. if err := repofiles.PushUpdate(
  115. ctx.Repo.Repository,
  116. branchName,
  117. models.PushUpdateOptions{
  118. RefFullName: git.BranchPrefix + branchName,
  119. OldCommitID: commit.ID.String(),
  120. NewCommitID: git.EmptySHA,
  121. PusherID: ctx.User.ID,
  122. PusherName: ctx.User.Name,
  123. RepoUserName: ctx.Repo.Owner.Name,
  124. RepoName: ctx.Repo.Repository.Name,
  125. }); err != nil {
  126. log.Error("Update: %v", err)
  127. }
  128. if err := ctx.Repo.Repository.AddDeletedBranch(branchName, commit.ID.String(), ctx.User.ID); err != nil {
  129. log.Warn("AddDeletedBranch: %v", err)
  130. }
  131. return nil
  132. }
  133. func loadBranches(ctx *context.Context) []*Branch {
  134. rawBranches, err := ctx.Repo.Repository.GetBranches()
  135. if err != nil {
  136. ctx.ServerError("GetBranches", err)
  137. return nil
  138. }
  139. protectedBranches, err := ctx.Repo.Repository.GetProtectedBranches()
  140. if err != nil {
  141. ctx.ServerError("GetProtectedBranches", err)
  142. return nil
  143. }
  144. branches := make([]*Branch, len(rawBranches))
  145. for i := range rawBranches {
  146. commit, err := rawBranches[i].GetCommit()
  147. if err != nil {
  148. ctx.ServerError("GetCommit", err)
  149. return nil
  150. }
  151. var isProtected bool
  152. branchName := rawBranches[i].Name
  153. for _, b := range protectedBranches {
  154. if b.BranchName == branchName {
  155. isProtected = true
  156. break
  157. }
  158. }
  159. divergence, divergenceError := repofiles.CountDivergingCommits(ctx.Repo.Repository, branchName)
  160. if divergenceError != nil {
  161. ctx.ServerError("CountDivergingCommits", divergenceError)
  162. return nil
  163. }
  164. pr, err := models.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
  165. if err != nil {
  166. ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
  167. return nil
  168. }
  169. if pr != nil {
  170. if err := pr.LoadIssue(); err != nil {
  171. ctx.ServerError("pr.LoadIssue", err)
  172. return nil
  173. }
  174. }
  175. isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
  176. branches[i] = &Branch{
  177. Name: branchName,
  178. Commit: commit,
  179. IsProtected: isProtected,
  180. IsIncluded: isIncluded,
  181. CommitsAhead: divergence.Ahead,
  182. CommitsBehind: divergence.Behind,
  183. LatestPullRequest: pr,
  184. }
  185. }
  186. if ctx.Repo.CanWrite(models.UnitTypeCode) {
  187. deletedBranches, err := getDeletedBranches(ctx)
  188. if err != nil {
  189. ctx.ServerError("getDeletedBranches", err)
  190. return nil
  191. }
  192. branches = append(branches, deletedBranches...)
  193. }
  194. return branches
  195. }
  196. func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
  197. branches := []*Branch{}
  198. deletedBranches, err := ctx.Repo.Repository.GetDeletedBranches()
  199. if err != nil {
  200. return branches, err
  201. }
  202. for i := range deletedBranches {
  203. deletedBranches[i].LoadUser()
  204. branches = append(branches, &Branch{
  205. Name: deletedBranches[i].Name,
  206. IsDeleted: true,
  207. DeletedBranch: deletedBranches[i],
  208. })
  209. }
  210. return branches, nil
  211. }
  212. // CreateBranch creates new branch in repository
  213. func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
  214. if !ctx.Repo.CanCreateBranch() {
  215. ctx.NotFound("CreateBranch", nil)
  216. return
  217. }
  218. if ctx.HasError() {
  219. ctx.Flash.Error(ctx.GetErrMsg())
  220. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  221. return
  222. }
  223. var err error
  224. if ctx.Repo.IsViewBranch {
  225. err = ctx.Repo.Repository.CreateNewBranch(ctx.User, ctx.Repo.BranchName, form.NewBranchName)
  226. } else {
  227. err = ctx.Repo.Repository.CreateNewBranchFromCommit(ctx.User, ctx.Repo.BranchName, form.NewBranchName)
  228. }
  229. if err != nil {
  230. if models.IsErrTagAlreadyExists(err) {
  231. e := err.(models.ErrTagAlreadyExists)
  232. ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
  233. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  234. return
  235. }
  236. if models.IsErrBranchAlreadyExists(err) {
  237. e := err.(models.ErrBranchAlreadyExists)
  238. ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", e.BranchName))
  239. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  240. return
  241. }
  242. if models.IsErrBranchNameConflict(err) {
  243. e := err.(models.ErrBranchNameConflict)
  244. ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
  245. ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
  246. return
  247. }
  248. ctx.ServerError("CreateNewBranch", err)
  249. return
  250. }
  251. ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
  252. ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName))
  253. }
上海开阖软件有限公司 沪ICP备12045867号-1