gitea_hat/routers/hat/repo/branch.go

254 lines
7.8 KiB
Go

package repo
import (
"fmt"
"net/http"
"sort"
"strings"
"time"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/context"
gitea_git "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
hat_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/convert"
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
hat_branch_convert "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/convert"
"code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
"code.gitea.io/gitea/routers/api/v1/utils"
)
func ListBranches(ctx *context.APIContext) {
var totalNumOfBranches int64
var apiBranches []*hat_api.Branch
searchName := ctx.FormString("name")
listOptions := utils.GetListOptions(ctx)
if !ctx.Repo.Repository.IsEmpty {
if ctx.Repo.GitRepo == nil {
ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
return
}
var isDeleted util.OptionalBool
switch ctx.FormString("state") {
case "all":
isDeleted = util.OptionalBoolNone
case "deleted":
isDeleted = util.OptionalBoolTrue
default:
isDeleted = util.OptionalBoolFalse
}
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: isDeleted,
Keyword: searchName,
}
var err error
totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches < 2 { // sync branches immediately because non-empty repository should have at least 1 branch
_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
return
}
branches, err := git_model.FindBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
}
apiBranches = make([]*hat_api.Branch, 0, len(branches))
for i := range branches {
err := branches[i].LoadDeletedBy(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadDeletedBy", err)
return
}
c, err := ctx.Repo.GitRepo.GetCommit(branches[i].CommitID)
if err != nil {
// Skip if this branch doesn't exist anymore.
if gitea_git.IsErrNotExist(err) {
totalNumOfBranches--
continue
}
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
apiBranch, err := hat_branch_convert.ToBranch(ctx, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
apiBranches = append(apiBranches, apiBranch)
}
}
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.SetTotalCountHeader(totalNumOfBranches)
ctx.RespHeader().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
ctx.JSON(http.StatusOK, &apiBranches)
}
func ListBranchesSlice(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
branches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(0, 0)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
}
apiBranches := make([]*git.Branch, len(branches))
for i, branch := range branches {
c, err := branch.GetCommit()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
branchProtection, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, branch.Name)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
return
}
sBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
gitBranch := &git.Branch{}
gitBranch.Branch = sBranch
gitBranch.CommitID = c.ID.String()
gitBranch.CommitTime = c.Author.When.Format(time.RFC3339)
gitBranch.DefaultBranch = ctx.Repo.Repository.DefaultBranch
gitBranch.BranchKind = int(git.OtherBranch)
apiBranches[i] = gitBranch
if branchProtection != nil {
apiBranches[i].BranchKind = int(git.ProtectedBranch)
}
if branch.Name == ctx.Repo.Repository.DefaultBranch {
apiBranches[i].BranchKind = int(git.DefaultBranch)
}
}
sort.Sort(git.SortBranch(apiBranches))
BranchesSlice := BranchesSliceByProtection(ctx, apiBranches)
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.RespHeader().Set("X-Total-Count", fmt.Sprintf("%d", totalNumOfBranches))
ctx.RespHeader().Set("Access-Control-Expose-Headers", "X-Total-Count, Link")
ctx.JSON(http.StatusOK, &BranchesSlice)
}
func paginate(branchSlice []*git.Branch, skip, pageSize int) []*git.Branch {
limit := func() int {
if skip+pageSize > len(branchSlice) {
return len(branchSlice)
} else {
return skip + pageSize
}
}
start := func() int {
if skip > len(branchSlice) {
return len(branchSlice)
} else {
return skip
}
}
return branchSlice[start():limit()]
}
func BranchesSliceByProtection(ctx *context.APIContext, branchList []*git.Branch) []git.BranchesSlice {
// group by protection
sort.Sort(git.SortBranch(branchList))
branchSlice := make([]git.BranchesSlice, 0)
i := 0
var j int
for {
if i >= len(branchList) {
break
}
for j = i + 1; j < len(branchList) && (branchList[i].BranchKind == branchList[j].BranchKind); j++ {
}
// get the same branches
sameBranchSlice := branchList[i:j]
// sort by time
sort.Sort(git.SortBranchTime(sameBranchSlice))
branchSlice = append(branchSlice, git.BranchesSlice{
BranchName: hat_convert.ToBranchType(branchList[i].BranchKind).Name(),
Branches: sameBranchSlice,
})
i = j
}
return branchSlice
}
func RestoreBranch(ctx *context.APIContext) {
form := web.GetForm(ctx).(*hat_api.RestoreBranchOption)
branchID := form.BranchID
branchName := form.BranchName
deletedBranch, err := git_model.GetDeletedBranchByID(ctx, ctx.Repo.Repository.ID, branchID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetDeletedBranchByID: %v", err)
return
} else if deletedBranch == nil {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', as it does not exist", branchName)
return
}
if err := gitea_git.Push(ctx, ctx.Repo.Repository.RepoPath(), gitea_git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, gitea_git.BranchPrefix, deletedBranch.Name),
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
}); err != nil {
if strings.Contains(err.Error(), "already exists") {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Can't restore branch '%s', since one with same name already exist", deletedBranch.Name)
return
}
ctx.Error(http.StatusInternalServerError, "RestoreBranch: CreateBranch: %v", err)
return
}
if err := repo_service.PushUpdate(
&repo_module.PushUpdateOptions{
RefFullName: gitea_git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: gitea_git.EmptySHA,
NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
RepoUserName: ctx.Repo.Owner.Name,
RepoName: ctx.Repo.Repository.Name,
}); err != nil {
ctx.Error(http.StatusInternalServerError, "RestoreBranch: Update: %v", err)
return
}
ctx.Status(http.StatusCreated)
}