mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-05 13:36:08 +00:00
9dc04289aa
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
278 lines
9.5 KiB
Go
278 lines
9.5 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
git_model "gitea.dev/models/git"
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/models/unit"
|
|
"gitea.dev/modules/git"
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/optional"
|
|
repo_module "gitea.dev/modules/repository"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/templates"
|
|
"gitea.dev/modules/util"
|
|
"gitea.dev/modules/web"
|
|
"gitea.dev/routers/utils"
|
|
"gitea.dev/services/context"
|
|
"gitea.dev/services/forms"
|
|
pull_service "gitea.dev/services/pull"
|
|
release_service "gitea.dev/services/release"
|
|
repo_service "gitea.dev/services/repository"
|
|
)
|
|
|
|
const (
|
|
tplBranch templates.TplName = "repo/branch/list"
|
|
)
|
|
|
|
// Branches render repository branch page
|
|
func Branches(ctx *context.Context) {
|
|
ctx.Data["Title"] = "Branches"
|
|
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls(ctx)
|
|
ctx.Data["IsWriter"] = ctx.Repo.Permission.CanWrite(unit.TypeCode)
|
|
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
|
|
// TODO: Can be replaced by ctx.Repo.PullRequestCtx.CanCreateNewPull()
|
|
ctx.Data["CanPull"] = ctx.Repo.Permission.CanWrite(unit.TypeCode) ||
|
|
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
|
|
ctx.Data["PageIsViewCode"] = true
|
|
ctx.Data["PageIsBranches"] = true
|
|
|
|
page := max(ctx.FormInt("page"), 1)
|
|
pageSize := setting.Git.BranchesRangeSize
|
|
|
|
kw := ctx.FormString("q")
|
|
|
|
defaultBranchOptional, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, optional.None[bool](), kw, page, pageSize)
|
|
if err != nil {
|
|
ctx.ServerError("LoadBranches", err)
|
|
return
|
|
}
|
|
|
|
commitIDs := make([]string, 0, len(branches)+1)
|
|
if defaultBranchOptional != nil {
|
|
commitIDs = append(commitIDs, defaultBranchOptional.DBBranch.CommitID)
|
|
}
|
|
for _, branch := range branches {
|
|
commitIDs = append(commitIDs, branch.DBBranch.CommitID)
|
|
}
|
|
|
|
commitStatuses, err := git_model.GetLatestCommitStatusForRepoCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs)
|
|
if err != nil {
|
|
ctx.ServerError("LoadBranches", err)
|
|
return
|
|
}
|
|
if !ctx.Repo.Permission.CanRead(unit.TypeActions) {
|
|
for key := range commitStatuses {
|
|
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key])
|
|
}
|
|
}
|
|
|
|
commitStatus := make(map[string]*git_model.CommitStatus)
|
|
for commitID, cs := range commitStatuses {
|
|
commitStatus[commitID] = git_model.CalcCommitStatus(cs)
|
|
}
|
|
|
|
ctx.Data["Keyword"] = kw
|
|
ctx.Data["Branches"] = branches
|
|
ctx.Data["CommitStatus"] = commitStatus
|
|
ctx.Data["CommitStatuses"] = commitStatuses
|
|
ctx.Data["DefaultBranchBranch"] = defaultBranchOptional
|
|
pager := context.NewPagination(branchesCount, pageSize, page, 5)
|
|
pager.AddParamFromRequest(ctx.Req)
|
|
ctx.Data["Page"] = pager
|
|
ctx.HTML(http.StatusOK, tplBranch)
|
|
}
|
|
|
|
// DeleteBranchPost responses for delete merged branch
|
|
func DeleteBranchPost(ctx *context.Context) {
|
|
defer jsonRedirectBranches(ctx)
|
|
branchName := ctx.FormString("name")
|
|
|
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
|
switch {
|
|
case git.IsErrBranchNotExist(err):
|
|
log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
|
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
|
log.Debug("DeleteBranch: Can't delete default branch '%s'", branchName)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.default_deletion_failed", branchName))
|
|
case errors.Is(err, git_model.ErrBranchIsProtected):
|
|
log.Debug("DeleteBranch: Can't delete protected branch '%s'", branchName)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
|
|
default:
|
|
log.Error("DeleteBranch: %v", err)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName))
|
|
}
|
|
|
|
// RestoreBranchPost responses for delete merged branch
|
|
func RestoreBranchPost(ctx *context.Context) {
|
|
defer jsonRedirectBranches(ctx)
|
|
|
|
branchID := ctx.FormInt64("branch_id")
|
|
branchName := ctx.FormString("name")
|
|
|
|
deletedBranch, err := git_model.GetDeletedBranchByID(ctx, ctx.Repo.Repository.ID, branchID)
|
|
if err != nil {
|
|
log.Error("GetDeletedBranchByID: %v", err)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
|
|
return
|
|
} else if deletedBranch == nil {
|
|
log.Debug("RestoreBranch: Can't restore branch[%d] '%s', as it does not exist", branchID, branchName)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
|
|
return
|
|
}
|
|
|
|
if err := git.PushManaged(ctx, ctx.Repo.Repository, ctx.Repo.Repository, git.PushOptions{
|
|
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
|
|
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
|
|
}); err != nil {
|
|
if strings.Contains(err.Error(), "already exists") {
|
|
log.Debug("RestoreBranch: Can't restore branch '%s', since one with same name already exist", deletedBranch.Name)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.already_exists", deletedBranch.Name))
|
|
return
|
|
}
|
|
log.Error("RestoreBranch: CreateBranch: %v", err)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
|
|
return
|
|
}
|
|
|
|
objectFormat := git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName)
|
|
|
|
// Don't return error below this
|
|
if err := repo_service.PushUpdates(
|
|
&repo_module.PushUpdateOptions{
|
|
RefFullName: git.RefNameFromBranch(deletedBranch.Name),
|
|
OldCommitID: objectFormat.EmptyObjectID().String(),
|
|
NewCommitID: deletedBranch.CommitID,
|
|
PusherID: ctx.Doer.ID,
|
|
PusherName: ctx.Doer.Name,
|
|
RepoUserName: ctx.Repo.Owner.Name,
|
|
RepoName: ctx.Repo.Repository.Name,
|
|
}); err != nil {
|
|
log.Error("RestoreBranch: Update: %v", err)
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.branch.restore_success", deletedBranch.Name))
|
|
}
|
|
|
|
func jsonRedirectBranches(ctx *context.Context) {
|
|
ctx.JSONRedirect(ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")))
|
|
}
|
|
|
|
// CreateBranch creates new branch in repository
|
|
func CreateBranch(ctx *context.Context) {
|
|
form := web.GetForm(ctx).(*forms.NewBranchForm)
|
|
if !ctx.Repo.CanCreateBranch() {
|
|
ctx.NotFound(nil)
|
|
return
|
|
}
|
|
|
|
if ctx.HasError() {
|
|
ctx.Flash.Error(ctx.GetErrMsg())
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
|
|
var err error
|
|
|
|
if form.CreateTag {
|
|
target := ctx.Repo.CommitID
|
|
if ctx.Repo.RefFullName.IsBranch() {
|
|
target = ctx.Repo.BranchName
|
|
}
|
|
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
|
|
} else if ctx.Repo.RefFullName.IsBranch() {
|
|
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName)
|
|
} else {
|
|
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName)
|
|
}
|
|
if err != nil {
|
|
if release_service.IsErrProtectedTagName(err) {
|
|
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
|
|
if release_service.IsErrTagAlreadyExists(err) {
|
|
e := err.(release_service.ErrTagAlreadyExists)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
if git_model.IsErrBranchNameConflict(err) {
|
|
e := err.(git_model.ErrBranchNameConflict)
|
|
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
if git.IsErrPushRejected(err) {
|
|
e := err.(*git.ErrPushRejected)
|
|
if len(e.Message) == 0 {
|
|
ctx.Flash.Error(ctx.Tr("repo.editor.push_rejected_no_message"))
|
|
} else {
|
|
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
|
|
"Message": ctx.Tr("repo.editor.push_rejected"),
|
|
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
|
|
"Details": utils.EscapeFlashErrorString(e.Message),
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("UpdatePullRequest.HTMLString", err)
|
|
return
|
|
}
|
|
ctx.Flash.Error(flashError)
|
|
}
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
|
|
return
|
|
}
|
|
|
|
ctx.ServerError("CreateNewBranch", err)
|
|
return
|
|
}
|
|
|
|
if form.CreateTag {
|
|
ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.NewBranchName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.NewBranchName))
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName) + "/" + util.PathEscapeSegments(form.CurrentPath))
|
|
}
|
|
|
|
func MergeUpstream(ctx *context.Context) {
|
|
branchName := ctx.FormString("branch")
|
|
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName, false)
|
|
if err != nil {
|
|
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrPermissionDenied) {
|
|
ctx.JSONErrorNotFound()
|
|
return
|
|
} else if pull_service.IsErrMergeConflicts(err) {
|
|
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
|
return
|
|
}
|
|
ctx.ServerError("MergeUpstream", err)
|
|
return
|
|
}
|
|
ctx.JSONRedirect("")
|
|
}
|