diff --git a/services/pull/update.go b/services/pull/update.go index 462bbec55afa4..34b2210c009cc 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -102,7 +102,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model. // IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections // update PR means send new commits to PR head branch from base branch -func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) { +func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (pushAllowed, rebaseAllowed bool, err error) { if pull.Flow == issues_model.PullRequestFlowAGit { return false, false, nil } @@ -116,6 +116,7 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, } return false, false, err } + pushAllowed = headRepoPerm.CanWrite(unit.TypeCode) if err := pull.LoadBaseRepo(ctx); err != nil { return false, false, err @@ -143,20 +144,16 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, if pb != nil { pb.Repo = pull.HeadRepo rebaseAllowed = rebaseAllowed && pb.CanUserForcePush(ctx, user) + pushAllowed = pb.CanUserPush(ctx, user) } } - // 3. check whether user has write access to head branch + // 3. check whether user has write access to base branch baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user) if err != nil { return false, false, err } - mergeAllowed, err = isUserAllowedToMergeInRepoBranch(ctx, pull.HeadRepoID, pull.HeadBranch, headRepoPerm, user) - if err != nil { - return false, false, err - } - // 4. if the pull creator allows maintainer to edit, it means the write permissions of the head branch has been // granted to the user with write permission of the base repository if pull.AllowMaintainerEdit { @@ -165,13 +162,13 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, return false, false, err } - mergeAllowed = mergeAllowed || mergeAllowedMaintainer + pushAllowed = pushAllowed || mergeAllowedMaintainer } - // if merge is not allowed, rebase is also not allowed - rebaseAllowed = rebaseAllowed && mergeAllowed + // if push is not allowed, rebase is also not allowed + rebaseAllowed = rebaseAllowed && pushAllowed - return mergeAllowed, rebaseAllowed, nil + return pushAllowed, rebaseAllowed, nil } func syncCommitDivergence(ctx context.Context, pr *issues_model.PullRequest) error { diff --git a/services/pull/update_test.go b/services/pull/update_test.go new file mode 100644 index 0000000000000..9f36cbd279664 --- /dev/null +++ b/services/pull/update_test.go @@ -0,0 +1,39 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pull + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestIsUserAllowedToUpdateRespectsProtectedBranch(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + assert.NoError(t, pr.LoadHeadRepo(t.Context())) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr.HeadRepoID, + RuleName: pr.HeadBranch, + CanPush: false, + CanForcePush: false, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + assert.NoError(t, err) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr, user) + assert.NoError(t, err) + assert.False(t, pushAllowed) + assert.False(t, rebaseAllowed) +}