Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
10047b6
Add default PR base branch support to repositories
tototomate123 Jan 21, 2026
e52f187
fmt: update repo struct
tototomate123 Jan 21, 2026
9d5b6b4
swagger: regenerate spec
tototomate123 Jan 21, 2026
668575e
gitcmd: enforce timeout cancellation
tototomate123 Jan 21, 2026
c026345
move default PR base branch setting into repo_unit config
tototomate123 Jan 22, 2026
8b7728b
Merge branch 'main' into main
tototomate123 Jan 22, 2026
32d1eee
gitcmt: fix timout test stdin setup
tototomate123 Jan 22, 2026
e7363d5
gitcmt: revert timeout test change and drop Wait goroutine
tototomate123 Jan 22, 2026
3647385
Merge branch 'go-gitea:main' into main
tototomate123 Jan 23, 2026
9ecc21c
fix: align PR base branch config naming and errors
tototomate123 Jan 23, 2026
79b1ac5
fix: update default PR base branch naming in templates
tototomate123 Jan 23, 2026
c035282
chore: regenerate swagger spec
tototomate123 Jan 23, 2026
1e7b108
refactor: rename PR base branch to target branch and switch API compa…
tototomate123 Jan 24, 2026
4712e38
Merge branch 'main' into main
tototomate123 Jan 24, 2026
c3dd9c0
chore: regenerate swagger spec
tototomate123 Jan 24, 2026
3046628
repo: drop default target branch existence validation
tototomate123 Jan 27, 2026
2399fcd
Merge branch 'main' into pr-target-branch
wxiaoguang Jan 28, 2026
331e0e4
fix
wxiaoguang Jan 28, 2026
01e9145
fix
wxiaoguang Jan 28, 2026
76e26d7
fix
wxiaoguang Jan 28, 2026
5c57ca2
fix
wxiaoguang Jan 28, 2026
07a5dfb
Merge branch 'main' into main
wxiaoguang Jan 30, 2026
de1425e
add check for default target branch & default branch
wxiaoguang Jan 30, 2026
db7d65c
fix
wxiaoguang Jan 30, 2026
a774f28
fix text
wxiaoguang Jan 30, 2026
6cb1f0f
Merge branch 'main' into main
wxiaoguang Feb 3, 2026
3951856
update locale
silverwind Feb 3, 2026
92f8ad3
improve dropdown
wxiaoguang Feb 4, 2026
6de9452
add helper message on Branches page
wxiaoguang Feb 4, 2026
85b2722
fix ui
wxiaoguang Feb 4, 2026
1f3d282
Merge branch 'go-gitea:main' into main
tototomate123 Feb 4, 2026
d6ce3ee
Merge branch 'main' into main
wxiaoguang Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions models/git/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

baseDefaultTargetBranchName := opts.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultTargetBranch
Comment thread
wxiaoguang marked this conversation as resolved.
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 created PRs, we will check later
// 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(
Expand All @@ -506,18 +519,16 @@ 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 {
return nil, err
}

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)
Expand Down Expand Up @@ -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,
})
}
Expand Down
16 changes: 16 additions & 0 deletions models/repo/pull_request_default.go
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions models/repo/pull_request_default_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
7 changes: 2 additions & 5 deletions models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions models/repo/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ type PullRequestsConfig struct {
DefaultDeleteBranchAfterMerge bool
DefaultMergeStyle MergeStyle
DefaultAllowMaintainerEdit bool
DefaultTargetBranch string
}

// FromDB fills up a PullRequestsConfig from serialized format.
Expand Down
41 changes: 21 additions & 20 deletions modules/structs/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions options/locale/locale_en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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\"",
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions routers/web/repo/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions routers/web/repo/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions routers/web/repo/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion services/context/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
}

Expand Down
2 changes: 1 addition & 1 deletion services/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading