mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-07 14:25:11 +01:00
This adds a per-repository default PR base branch and wires it through PR entry points. It updates compare links and recently pushed branch prompts to respect the configured base branch, and prevents auto-merge cleanup from deleting the configured base branch on same-repo PRs. ## Behavior changes - New PR compare links on repo home/issue list and branch list honor the configured default PR base branch. - The "recently pushed new branches" prompt now compares against the configured base branch. - Auto-merge branch cleanup skips deleting the configured base branch (same-repo PRs only). --------- Signed-off-by: Louis <116039387+tototomate123@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
parent
0dacd956fb
commit
e2104a1dd5
@ -490,12 +490,25 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
opts.CommitAfterUnix = time.Now().Add(-time.Hour * 2).Unix()
|
||||
}
|
||||
|
||||
baseBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
var ignoredCommitIDs []string
|
||||
baseDefaultBranch, err := GetBranch(ctx, opts.BaseRepo.ID, opts.BaseRepo.DefaultBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Warn("GetBranch:DefaultBranch: %v", err)
|
||||
} else {
|
||||
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultBranch.CommitID)
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already created PRs, we will check later
|
||||
baseDefaultTargetBranchName := opts.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultTargetBranch
|
||||
if baseDefaultTargetBranchName != "" && baseDefaultTargetBranchName != opts.BaseRepo.DefaultBranch {
|
||||
baseDefaultTargetBranch, err := GetBranch(ctx, opts.BaseRepo.ID, baseDefaultTargetBranchName)
|
||||
if err != nil {
|
||||
log.Warn("GetBranch:DefaultTargetBranch: %v", err)
|
||||
} else {
|
||||
ignoredCommitIDs = append(ignoredCommitIDs, baseDefaultTargetBranch.CommitID)
|
||||
}
|
||||
}
|
||||
|
||||
// find all related branches, these branches may already have PRs, we will check later
|
||||
var branches []*Branch
|
||||
if err := db.GetEngine(ctx).
|
||||
Where(builder.And(
|
||||
@ -506,7 +519,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
builder.Gte{"commit_time": opts.CommitAfterUnix},
|
||||
builder.In("repo_id", repoIDs),
|
||||
// newly created branch have no changes, so skip them
|
||||
builder.Neq{"commit_id": baseBranch.CommitID},
|
||||
builder.NotIn("commit_id", ignoredCommitIDs),
|
||||
)).
|
||||
OrderBy(db.SearchOrderByRecentUpdated.String()).
|
||||
Find(&branches); err != nil {
|
||||
@ -514,10 +527,8 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
}
|
||||
|
||||
newBranches := make([]*RecentlyPushedNewBranch, 0, len(branches))
|
||||
if opts.MaxCount == 0 {
|
||||
// by default we display 2 recently pushed new branch
|
||||
opts.MaxCount = 2
|
||||
}
|
||||
opts.MaxCount = util.IfZero(opts.MaxCount, 2) // by default, we display 2 recently pushed new branch
|
||||
baseTargetBranchName := opts.BaseRepo.GetPullRequestTargetBranch(ctx)
|
||||
for _, branch := range branches {
|
||||
// whether the branch is protected
|
||||
protected, err := IsBranchProtected(ctx, branch.RepoID, branch.Name)
|
||||
@ -555,7 +566,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
BranchDisplayName: branchDisplayName,
|
||||
BranchName: branch.Name,
|
||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, baseTargetBranchName, branch.Name),
|
||||
CommitTime: branch.CommitTime,
|
||||
})
|
||||
}
|
||||
|
||||
16
models/repo/pull_request_default.go
Normal file
16
models/repo/pull_request_default.go
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func (repo *Repository) GetPullRequestTargetBranch(ctx context.Context) string {
|
||||
unitPRConfig := repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
return util.IfZero(unitPRConfig.DefaultTargetBranch, repo.DefaultBranch)
|
||||
}
|
||||
32
models/repo/pull_request_default_test.go
Normal file
32
models/repo/pull_request_default_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefaultTargetBranchSelection(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx := t.Context()
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1})
|
||||
|
||||
assert.Equal(t, repo.DefaultBranch, repo.GetPullRequestTargetBranch(ctx))
|
||||
|
||||
repo.Units = nil
|
||||
prUnit, err := repo.GetUnit(ctx, unit.TypePullRequests)
|
||||
assert.NoError(t, err)
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
prConfig.DefaultTargetBranch = "branch2"
|
||||
prUnit.Config = prConfig
|
||||
assert.NoError(t, UpdateRepoUnit(ctx, prUnit))
|
||||
repo.Units = nil
|
||||
assert.Equal(t, "branch2", repo.GetPullRequestTargetBranch(ctx))
|
||||
}
|
||||
@ -613,16 +613,13 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
|
||||
return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
|
||||
}
|
||||
|
||||
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, branchName string) string {
|
||||
if baseRepo == nil {
|
||||
baseRepo = repo
|
||||
}
|
||||
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, baseBranch, branchName string) string {
|
||||
var cmpBranchEscaped string
|
||||
if repo.ID != baseRepo.ID {
|
||||
cmpBranchEscaped = fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
|
||||
}
|
||||
cmpBranchEscaped = fmt.Sprintf("%s%s", cmpBranchEscaped, util.PathEscapeSegments(branchName))
|
||||
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseRepo.DefaultBranch), cmpBranchEscaped)
|
||||
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseBranch), cmpBranchEscaped)
|
||||
}
|
||||
|
||||
// IsOwnedBy returns true when user owns this repository
|
||||
|
||||
@ -131,6 +131,7 @@ type PullRequestsConfig struct {
|
||||
DefaultDeleteBranchAfterMerge bool
|
||||
DefaultMergeStyle MergeStyle
|
||||
DefaultAllowMaintainerEdit bool
|
||||
DefaultTargetBranch string
|
||||
}
|
||||
|
||||
// FromDB fills up a PullRequestsConfig from serialized format.
|
||||
|
||||
@ -58,26 +58,27 @@ type Repository struct {
|
||||
Fork bool `json:"fork"`
|
||||
Template bool `json:"template"`
|
||||
// the original repository if this repository is a fork, otherwise null
|
||||
Parent *Repository `json:"parent,omitempty"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Size int `json:"size"`
|
||||
Language string `json:"language"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
URL string `json:"url"`
|
||||
Link string `json:"link"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
OriginalURL string `json:"original_url"`
|
||||
Website string `json:"website"`
|
||||
Stars int `json:"stars_count"`
|
||||
Forks int `json:"forks_count"`
|
||||
Watchers int `json:"watchers_count"`
|
||||
OpenIssues int `json:"open_issues_count"`
|
||||
OpenPulls int `json:"open_pr_counter"`
|
||||
Releases int `json:"release_counter"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Archived bool `json:"archived"`
|
||||
Parent *Repository `json:"parent,omitempty"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Size int `json:"size"`
|
||||
Language string `json:"language"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
URL string `json:"url"`
|
||||
Link string `json:"link"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
OriginalURL string `json:"original_url"`
|
||||
Website string `json:"website"`
|
||||
Stars int `json:"stars_count"`
|
||||
Forks int `json:"forks_count"`
|
||||
Watchers int `json:"watchers_count"`
|
||||
OpenIssues int `json:"open_issues_count"`
|
||||
OpenPulls int `json:"open_pr_counter"`
|
||||
Releases int `json:"release_counter"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
DefaultTargetBranch string `json:"default_target_branch,omitempty"`
|
||||
Archived bool `json:"archived"`
|
||||
// swagger:strfmt date-time
|
||||
Created time.Time `json:"created_at"`
|
||||
// swagger:strfmt date-time
|
||||
|
||||
@ -2124,6 +2124,8 @@
|
||||
"repo.settings.pulls.ignore_whitespace": "Ignore Whitespace for Conflicts",
|
||||
"repo.settings.pulls.enable_autodetect_manual_merge": "Enable autodetect manual merge (Note: In some special cases, misjudgments can occur)",
|
||||
"repo.settings.pulls.allow_rebase_update": "Enable updating pull request branch by rebase",
|
||||
"repo.settings.pulls.default_target_branch": "Default target branch for new pull requests",
|
||||
"repo.settings.pulls.default_target_branch_default": "Default branch (%s)",
|
||||
"repo.settings.pulls.default_delete_branch_after_merge": "Delete pull request branch after merge by default",
|
||||
"repo.settings.pulls.default_allow_edits_from_maintainers": "Allow edits from maintainers by default",
|
||||
"repo.settings.releases_desc": "Enable Repository Releases",
|
||||
@ -2436,9 +2438,10 @@
|
||||
"repo.settings.block_outdated_branch_desc": "Merging will not be possible when head branch is behind base branch.",
|
||||
"repo.settings.block_admin_merge_override": "Administrators must follow branch protection rules",
|
||||
"repo.settings.block_admin_merge_override_desc": "Administrators must follow branch protection rules and cannot circumvent it.",
|
||||
"repo.settings.default_branch_desc": "Select a default repository branch for pull requests and code commits:",
|
||||
"repo.settings.default_branch_desc": "Select a default branch for code commits.",
|
||||
"repo.settings.default_target_branch_desc": "Pull requests can use different default target branch if it is set in the Pull Requests section of Repository Advance Settings.",
|
||||
"repo.settings.merge_style_desc": "Merge Styles",
|
||||
"repo.settings.default_merge_style_desc": "Default Merge Style",
|
||||
"repo.settings.default_merge_style_desc": "Default merge style",
|
||||
"repo.settings.choose_branch": "Choose a branch…",
|
||||
"repo.settings.no_protected_branch": "There are no protected branches.",
|
||||
"repo.settings.edit_protected_branch": "Edit",
|
||||
@ -2650,7 +2653,7 @@
|
||||
"repo.branch.restore_success": "Branch \"%s\" has been restored.",
|
||||
"repo.branch.restore_failed": "Failed to restore branch \"%s\".",
|
||||
"repo.branch.protected_deletion_failed": "Branch \"%s\" is protected. It cannot be deleted.",
|
||||
"repo.branch.default_deletion_failed": "Branch \"%s\" is the default branch. It cannot be deleted.",
|
||||
"repo.branch.default_deletion_failed": "Branch \"%s\" is the default or pull request target branch. It cannot be deleted.",
|
||||
"repo.branch.default_branch_not_exist": "Default branch \"%s\" does not exist.",
|
||||
"repo.branch.restore": "Restore Branch \"%s\"",
|
||||
"repo.branch.download": "Download Branch \"%s\"",
|
||||
|
||||
@ -155,7 +155,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
case git.IsErrBranchNotExist(err):
|
||||
ctx.APIErrorNotFound(err)
|
||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
|
||||
ctx.APIError(http.StatusForbidden, errors.New("can not delete default or pull request target branch"))
|
||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
|
||||
default:
|
||||
|
||||
@ -1138,7 +1138,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.DefaultBranch))
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx)))
|
||||
headRef := headGitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch))
|
||||
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), compareReq.BaseOriRef, baseRef, compareReq.HeadOriRef, headRef)
|
||||
|
||||
@ -41,6 +41,7 @@ func Branches(ctx *context.Context) {
|
||||
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls(ctx)
|
||||
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
|
||||
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
|
||||
// TODO: Can be replaced by ctx.Repo.PullRequestCtx.CanCreateNewPull()
|
||||
ctx.Data["CanPull"] = ctx.Repo.CanWrite(unit.TypeCode) ||
|
||||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
|
||||
@ -247,7 +247,7 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo {
|
||||
}
|
||||
|
||||
// 4 get base and head refs
|
||||
baseRefName := util.IfZero(compareReq.BaseOriRef, baseRepo.DefaultBranch)
|
||||
baseRefName := util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx))
|
||||
headRefName := util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch)
|
||||
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefName)
|
||||
@ -276,10 +276,10 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo {
|
||||
ctx.Data["BaseBranch"] = baseRef.ShortName() // for legacy templates
|
||||
ctx.Data["HeadUser"] = headOwner
|
||||
ctx.Data["HeadBranch"] = headRef.ShortName() // for legacy templates
|
||||
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
||||
|
||||
ctx.Data["IsPull"] = true
|
||||
|
||||
context.InitRepoPullRequestCtx(ctx, baseRepo, headRepo)
|
||||
|
||||
// The current base and head repositories and branches may not
|
||||
// actually be the intended branches that the user wants to
|
||||
// create a pull-request from - but also determining the head
|
||||
|
||||
@ -109,11 +109,6 @@ func MustAllowPulls(ctx *context.Context) {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// User can send pull request if owns a forked repository.
|
||||
if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.Repo.PullRequest.Allowed = true
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository) (open, closed []*project_model.Project) {
|
||||
|
||||
@ -28,6 +28,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
repo_router "code.gitea.io/gitea/routers/web/repo"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
@ -88,6 +89,11 @@ func SettingsCtxData(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["PushMirrors"] = pushMirrors
|
||||
|
||||
repo_router.PrepareBranchList(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Settings show a repository's settings page
|
||||
@ -622,6 +628,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
||||
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
|
||||
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
|
||||
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
|
||||
DefaultTargetBranch: strings.TrimSpace(form.DefaultTargetBranch),
|
||||
}))
|
||||
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
|
||||
|
||||
@ -221,7 +221,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
||||
ctx := &APIContext{
|
||||
Base: base,
|
||||
Cache: cache.GetCache(),
|
||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||
Repo: &Repository{},
|
||||
Org: &APIOrganization{},
|
||||
}
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
|
||||
|
||||
Cache: cache.GetCache(),
|
||||
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||
Repo: &Repository{},
|
||||
Org: &Organization{},
|
||||
}
|
||||
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
|
||||
|
||||
@ -37,11 +37,46 @@ import (
|
||||
"github.com/editorconfig/editorconfig-core-go/v2"
|
||||
)
|
||||
|
||||
// PullRequest contains information to make a pull request
|
||||
type PullRequest struct {
|
||||
BaseRepo *repo_model.Repository
|
||||
Allowed bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
|
||||
SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
|
||||
// PullRequestContext contains context information for making a new pull request
|
||||
type PullRequestContext struct {
|
||||
ctx *Context
|
||||
|
||||
baseRepo, headRepo *repo_model.Repository
|
||||
|
||||
canCreateNewPull *bool
|
||||
defaultTargetBranch *string
|
||||
}
|
||||
|
||||
func (prc *PullRequestContext) SameRepo() bool {
|
||||
return prc.baseRepo != nil && prc.headRepo != nil && prc.baseRepo.ID == prc.headRepo.ID
|
||||
}
|
||||
|
||||
func (prc *PullRequestContext) CanCreateNewPull() bool {
|
||||
if prc.canCreateNewPull != nil {
|
||||
return *prc.canCreateNewPull
|
||||
}
|
||||
ctx := prc.ctx
|
||||
// People who have push access or have forked repository can propose a new pull request.
|
||||
can := prc.baseRepo.CanContentChange() &&
|
||||
(ctx.Repo.CanWrite(unit_model.TypeCode) || (ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)))
|
||||
prc.canCreateNewPull = &can
|
||||
return can
|
||||
}
|
||||
|
||||
func (prc *PullRequestContext) MakeDefaultCompareLink(headBranch string) string {
|
||||
return prc.baseRepo.Link() + "/compare/" +
|
||||
util.PathEscapeSegments(prc.DefaultTargetBranch()) + "..." +
|
||||
util.Iif(prc.SameRepo(), "", util.PathEscapeSegments(prc.headRepo.OwnerName)+":") +
|
||||
util.PathEscapeSegments(headBranch)
|
||||
}
|
||||
|
||||
func (prc *PullRequestContext) DefaultTargetBranch() string {
|
||||
if prc.defaultTargetBranch != nil {
|
||||
return *prc.defaultTargetBranch
|
||||
}
|
||||
branchName := prc.baseRepo.GetPullRequestTargetBranch(prc.ctx)
|
||||
prc.defaultTargetBranch = &branchName
|
||||
return branchName
|
||||
}
|
||||
|
||||
// Repository contains information to operate a repository
|
||||
@ -64,7 +99,7 @@ type Repository struct {
|
||||
CommitID string
|
||||
CommitsCount int64
|
||||
|
||||
PullRequest *PullRequest
|
||||
PullRequestCtx *PullRequestContext
|
||||
}
|
||||
|
||||
// CanWriteToBranch checks if the branch is writable by the user
|
||||
@ -418,6 +453,12 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
|
||||
}
|
||||
|
||||
func InitRepoPullRequestCtx(ctx *Context, base, head *repo_model.Repository) {
|
||||
ctx.Repo.PullRequestCtx = &PullRequestContext{ctx: ctx}
|
||||
ctx.Repo.PullRequestCtx.baseRepo, ctx.Repo.PullRequestCtx.headRepo = base, head
|
||||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequestCtx
|
||||
}
|
||||
|
||||
// RepoAssignment returns a middleware to handle repository assignment
|
||||
func RepoAssignment(ctx *Context) {
|
||||
if ctx.Data["Repository"] != nil {
|
||||
@ -666,28 +707,16 @@ func RepoAssignment(ctx *Context) {
|
||||
|
||||
ctx.Data["BranchesCount"] = branchesTotal
|
||||
|
||||
// People who have push access or have forked repository can propose a new pull request.
|
||||
canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
|
||||
(ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID))
|
||||
canCompare := false
|
||||
|
||||
// Pull request is allowed if this is a fork repository
|
||||
// and base repository accepts pull requests.
|
||||
// Pull request is allowed if this is a fork repository, and base repository accepts pull requests.
|
||||
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls(ctx) {
|
||||
canCompare = true
|
||||
// TODO: this (and below) "BaseRepo" var is not clear and should be removed in the future
|
||||
ctx.Data["BaseRepo"] = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
InitRepoPullRequestCtx(ctx, repo.BaseRepo, repo)
|
||||
} else if repo.AllowsPulls(ctx) {
|
||||
// Or, this is repository accepts pull requests between branches.
|
||||
canCompare = true
|
||||
ctx.Data["BaseRepo"] = repo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.SameRepo = true
|
||||
InitRepoPullRequestCtx(ctx, repo, repo)
|
||||
}
|
||||
ctx.Data["CanCompareOrPull"] = canCompare
|
||||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
|
||||
|
||||
if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
|
||||
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
|
||||
|
||||
@ -103,6 +103,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
defaultDeleteBranchAfterMerge := false
|
||||
defaultMergeStyle := repo_model.MergeStyleMerge
|
||||
defaultAllowMaintainerEdit := false
|
||||
defaultTargetBranch := ""
|
||||
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
|
||||
config := unit.PullRequestsConfig()
|
||||
hasPullRequests = true
|
||||
@ -118,6 +119,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
|
||||
defaultMergeStyle = config.GetDefaultMergeStyle()
|
||||
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
|
||||
defaultTargetBranch = config.DefaultTargetBranch
|
||||
}
|
||||
hasProjects := false
|
||||
projectsMode := repo_model.ProjectsModeAll
|
||||
@ -235,6 +237,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
||||
DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
|
||||
DefaultMergeStyle: string(defaultMergeStyle),
|
||||
DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit,
|
||||
DefaultTargetBranch: defaultTargetBranch,
|
||||
AvatarURL: repo.AvatarLink(ctx),
|
||||
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
|
||||
MirrorInterval: mirrorInterval,
|
||||
|
||||
@ -143,6 +143,7 @@ type RepoSettingForm struct {
|
||||
PullsAllowRebaseUpdate bool
|
||||
DefaultDeleteBranchAfterMerge bool
|
||||
DefaultAllowMaintainerEdit bool
|
||||
DefaultTargetBranch string
|
||||
EnableTimetracker bool
|
||||
AllowOnlyContributorsToTrackTime bool
|
||||
EnableIssueDependencies bool
|
||||
|
||||
@ -547,10 +547,11 @@ func UpdateBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git
|
||||
return gitrepo.Push(ctx, repo, repo, pushOpts)
|
||||
}
|
||||
|
||||
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default")
|
||||
var ErrBranchIsDefault = util.ErrorWrap(util.ErrPermissionDenied, "branch is default or pull request target")
|
||||
|
||||
func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
|
||||
if branchName == repo.DefaultBranch {
|
||||
unitPRConfig := repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
if branchName == repo.DefaultBranch || branchName == unitPRConfig.DefaultTargetBranch {
|
||||
return ErrBranchIsDefault
|
||||
}
|
||||
|
||||
|
||||
@ -133,14 +133,14 @@
|
||||
<span class="ui orange large label" data-tooltip-content="{{ctx.Locale.Tr "repo.branch.included_desc"}}">
|
||||
{{svg "octicon-git-pull-request"}} {{ctx.Locale.Tr "repo.branch.included"}}
|
||||
</span>
|
||||
{{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
|
||||
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}?expand=1">
|
||||
{{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
|
||||
<a href="{{$.PullRequestCtx.MakeDefaultCompareLink .DBBranch.Name}}?expand=1">
|
||||
<button id="new-pull-request" class="ui compact basic button tw-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
|
||||
</a>
|
||||
{{end}}
|
||||
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
|
||||
{{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
|
||||
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}?expand=1">
|
||||
<a href="{{$.PullRequestCtx.MakeDefaultCompareLink .DBBranch.Name}}?expand=1">
|
||||
<button id="new-pull-request" class="ui compact basic button tw-mr-0">{{if $.CanPull}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}</button>
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
@ -24,11 +24,12 @@
|
||||
{{if .PageIsIssueList}}
|
||||
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||
{{else}}
|
||||
<a class="ui small primary button new-pr-button issue-list-new{{if not .PullRequestCtx.Allowed}} disabled{{end}}" href="{{if .PullRequestCtx.Allowed}}{{.Repository.Link}}/compare/{{.Repository.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">{{ctx.Locale.Tr "repo.pulls.new"}}</a>
|
||||
<a class="ui small primary button new-pr-button issue-list-new {{if not .PullRequestCtx.CanCreateNewPull}}disabled{{end}}" href="{{.PullRequestCtx.MakeDefaultCompareLink .Repository.DefaultBranch}}">{{ctx.Locale.Tr "repo.pulls.new"}}</a>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{/* archived, view compare page only */}}
|
||||
{{if not .PageIsIssueList}}
|
||||
<a class="ui small primary small button issue-list-new{{if not .PullRequestCtx.Allowed}} disabled{{end}}" href="{{if .PullRequestCtx.Allowed}}{{.PullRequestCtx.BaseRepo.Link}}/compare/{{.PullRequestCtx.BaseRepo.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">{{ctx.Locale.Tr "action.compare_commits_general"}}</a>
|
||||
<a class="ui small primary small button issue-list-new" href="{{.PullRequestCtx.MakeDefaultCompareLink .Repository.DefaultBranch}}">{{ctx.Locale.Tr "action.compare_commits_general"}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@ -9,22 +9,23 @@
|
||||
{{ctx.Locale.Tr "repo.default_branch"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>
|
||||
{{ctx.Locale.Tr "repo.settings.default_branch_desc"}}
|
||||
</p>
|
||||
<form class="tw-flex" action="{{.Link}}" method="post">
|
||||
<p>{{ctx.Locale.Tr "repo.settings.default_branch_desc"}}</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="default_branch">
|
||||
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
<div class="flex-text-block">
|
||||
<div class="ui dropdown selection search tw-flex-1 tw-mr-2 tw-max-w-96">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="hidden" name="branch" value="{{.Repository.DefaultBranch}}">
|
||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||
<div class="menu">
|
||||
{{range .Branches}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button"{{if .Repository.IsEmpty}} disabled{{end}}>{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
</div>
|
||||
<button class="ui primary button"{{if .Repository.IsEmpty}} disabled{{end}}>{{ctx.Locale.Tr "repo.settings.branches.update_default_branch"}}</button>
|
||||
<div class="help tw-mt-4 tw-p-0">{{ctx.Locale.Tr "repo.settings.default_target_branch_desc"}}</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@ -287,6 +287,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* FIXME: need to split the "Advance Settings" by units, there are too many options here */}}
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "repo.settings.advanced_settings"}}
|
||||
</h4>
|
||||
@ -594,6 +595,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.pulls.default_target_branch"}}</label>
|
||||
<div class="ui search selection dropdown">
|
||||
<input type="hidden" name="default_target_branch" value="{{$prUnit.PullRequestsConfig.DefaultTargetBranch}}">
|
||||
<div class="default text"></div>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<div class="item" data-value="">{{ctx.Locale.Tr "repo.settings.pulls.default_target_branch_default" $.Repository.DefaultBranch}}</div>
|
||||
{{range $branchName := $.Branches}}
|
||||
<div class="item" data-value="{{$branchName}}">{{$branchName}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="default_allow_maintainer_edit" type="checkbox" {{if or (not $pullRequestEnabled) ($prUnit.PullRequestsConfig.DefaultAllowMaintainerEdit)}}checked{{end}}>
|
||||
|
||||
@ -20,15 +20,10 @@
|
||||
"ShowViewAllRefsEntry" true
|
||||
}}
|
||||
|
||||
{{if and .CanCompareOrPull .RefFullName.IsBranch (not .Repository.IsArchived)}}
|
||||
{{$cmpBranch := ""}}
|
||||
{{if ne .Repository.ID .BaseRepo.ID}}
|
||||
{{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
|
||||
{{end}}
|
||||
{{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
|
||||
{{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
|
||||
{{if and .PullRequestCtx.CanCreateNewPull .RefFullName.IsBranch}}
|
||||
{{$compareLink := .PullRequestCtx.MakeDefaultCompareLink .BranchName}}
|
||||
<a id="new-pull-request" role="button" class="ui compact basic button" href="{{QueryBuild $compareLink "expand" 1}}"
|
||||
data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}">
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.compare_changes"}}">
|
||||
{{svg "octicon-git-pull-request"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
4
templates/swagger/v1_json.tmpl
generated
4
templates/swagger/v1_json.tmpl
generated
@ -28147,6 +28147,10 @@
|
||||
"type": "string",
|
||||
"x-go-name": "DefaultMergeStyle"
|
||||
},
|
||||
"default_target_branch": {
|
||||
"type": "string",
|
||||
"x-go-name": "DefaultTargetBranch"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"x-go-name": "Description"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user