mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-12 03:50:32 +00:00
Adds a repository-level **"Restrict to collaborators"** option under the Issues and Pull Request unit settings. When enabled, only users with collaborator access to the base repository (owner, team members, or explicit collaborators) can open issues or pull requests against it. Public repositories often need to be visible/cloneable by everyone, but owners may want to limit who can open pull requests (e.g. restrict to an internal team while still keeping the code public) * Fixes: https://github.com/go-gitea/gitea/issues/40015 * Fixes: https://github.com/go-gitea/gitea/issues/31890 * Fixes: https://github.com/go-gitea/gitea/issues/28021 * Fixes: https://github.com/go-gitea/gitea/issues/14801 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
287 lines
11 KiB
Go
287 lines
11 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
auth_model "gitea.dev/models/auth"
|
|
issues_model "gitea.dev/models/issues"
|
|
"gitea.dev/models/perm"
|
|
access_model "gitea.dev/models/perm/access"
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/models/unit"
|
|
"gitea.dev/models/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/git"
|
|
pull_service "gitea.dev/services/pull"
|
|
repo_service "gitea.dev/services/repository"
|
|
files_service "gitea.dev/services/repository/files"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAPIPullUpdate(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
|
// Create PR to test
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
|
pr := createOutdatedPR(t, user, org26)
|
|
require.NoError(t, pr.LoadBaseRepo(t.Context()))
|
|
require.NoError(t, pr.LoadIssue(t.Context()))
|
|
|
|
// Test GetDiverging
|
|
diffCount, err := git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, diffCount.Behind)
|
|
assert.Equal(t, 1, diffCount.Ahead)
|
|
assert.Equal(t, diffCount.Behind, pr.CommitsBehind)
|
|
assert.Equal(t, diffCount.Ahead, pr.CommitsAhead)
|
|
|
|
session := loginUser(t, "user2")
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
|
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Test GetDiverging after update
|
|
diffCount, err = git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, diffCount.Behind)
|
|
assert.Equal(t, 2, diffCount.Ahead)
|
|
assert.Eventually(t, func() bool {
|
|
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
|
|
return diffCount.Behind == pr.CommitsBehind && diffCount.Ahead == pr.CommitsAhead
|
|
}, 5*time.Second, 20*time.Millisecond)
|
|
})
|
|
}
|
|
|
|
func updateRepoPullRequestConfig(t *testing.T, repoID int64, update func(*repo_model.PullRequestsConfig)) {
|
|
t.Helper()
|
|
|
|
repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypePullRequests})
|
|
update(repoUnit.PullRequestsConfig())
|
|
assert.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoUnit))
|
|
}
|
|
|
|
func enableRepoAllowUpdateWithRebase(t *testing.T, repoID int64, allow bool) {
|
|
updateRepoPullRequestConfig(t, repoID, func(c *repo_model.PullRequestsConfig) { c.AllowRebaseUpdate = allow })
|
|
}
|
|
|
|
// setupOutdatedPRWithConfig creates an outdated PR (user2 base, org26 fork), loads its
|
|
// base repo, head repo, and issue, then applies the supplied PullRequestsConfig mutation.
|
|
func setupOutdatedPRWithConfig(t *testing.T, mutate func(*repo_model.PullRequestsConfig)) *issues_model.PullRequest {
|
|
t.Helper()
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
|
pr := createOutdatedPR(t, user, org26)
|
|
require.NoError(t, pr.LoadBaseRepo(t.Context()))
|
|
require.NoError(t, pr.LoadHeadRepo(t.Context()))
|
|
require.NoError(t, pr.LoadIssue(t.Context()))
|
|
if mutate != nil {
|
|
updateRepoPullRequestConfig(t, pr.BaseRepo.ID, mutate)
|
|
}
|
|
return pr
|
|
}
|
|
|
|
func TestAPIPullUpdateByRebase(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
|
// Create PR to test
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26})
|
|
pr := createOutdatedPR(t, user, org26)
|
|
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
|
|
|
// Test GetDiverging
|
|
diffCount, err := git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, diffCount.Behind)
|
|
assert.Equal(t, 1, diffCount.Ahead)
|
|
assert.NoError(t, pr.LoadIssue(t.Context()))
|
|
|
|
enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, false)
|
|
|
|
session := loginUser(t, "user2")
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
|
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusForbidden)
|
|
|
|
enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, true)
|
|
assert.NoError(t, pr.LoadHeadRepo(t.Context()))
|
|
|
|
// use a user which have write access to the pr but not write permission to the head repository to do the rebase
|
|
user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40})
|
|
err = repo_service.AddOrUpdateCollaborator(t.Context(), pr.BaseRepo, user40, perm.AccessModeWrite)
|
|
assert.NoError(t, err)
|
|
token40 := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteRepository)
|
|
|
|
req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token40)
|
|
session.MakeRequest(t, req, http.StatusForbidden)
|
|
|
|
err = repo_service.AddOrUpdateCollaborator(t.Context(), pr.HeadRepo, user40, perm.AccessModeWrite)
|
|
assert.NoError(t, err)
|
|
|
|
req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token40)
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Test GetDiverging after update
|
|
diffCount, err = git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 0, diffCount.Behind)
|
|
assert.Equal(t, 1, diffCount.Ahead)
|
|
})
|
|
}
|
|
|
|
// TestAPIPullUpdateStyleSettings first checks that a disabled explicit style is
|
|
// forbidden, then guards back-compat: even when the repo's DefaultUpdateStyle is
|
|
// rebase, an API call with no `style` parameter must still perform a merge
|
|
// update so existing API clients don't silently flip behavior.
|
|
func TestAPIPullUpdateStyleSettings(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
|
pr := setupOutdatedPRWithConfig(t, func(c *repo_model.PullRequestsConfig) {
|
|
c.AllowMergeUpdate = false
|
|
c.AllowRebaseUpdate = true
|
|
c.DefaultUpdateStyle = repo_model.UpdateStyleRebase
|
|
})
|
|
|
|
user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40})
|
|
require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), pr.BaseRepo, user40, perm.AccessModeWrite))
|
|
require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), pr.HeadRepo, user40, perm.AccessModeWrite))
|
|
|
|
session := loginUser(t, "user40")
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
|
|
|
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=merge", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusForbidden)
|
|
|
|
updateRepoPullRequestConfig(t, pr.BaseRepo.ID, func(c *repo_model.PullRequestsConfig) {
|
|
c.AllowMergeUpdate = true
|
|
})
|
|
|
|
req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
// merge update produces a merge commit on top of the head commit (Ahead=2),
|
|
// rebase update would replay the head commit alone (Ahead=1).
|
|
diffCount, err := git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, diffCount.Behind)
|
|
assert.Equal(t, 2, diffCount.Ahead)
|
|
})
|
|
}
|
|
|
|
func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest {
|
|
baseRepo, err := repo_service.CreateRepository(t.Context(), actor, actor, repo_service.CreateRepoOptions{
|
|
Name: "repo-pr-update",
|
|
Description: "repo-tmp-pr-update description",
|
|
AutoInit: true,
|
|
Gitignores: "C,C++",
|
|
License: "MIT",
|
|
Readme: "Default",
|
|
IsPrivate: false,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, baseRepo)
|
|
|
|
headRepo, err := repo_service.ForkRepository(t.Context(), actor, forkOrg, repo_service.ForkRepoOptions{
|
|
BaseRepo: baseRepo,
|
|
Name: "repo-pr-update",
|
|
Description: "desc",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, headRepo)
|
|
|
|
// create a commit on base Repo
|
|
_, err = files_service.ChangeRepoFiles(t.Context(), baseRepo, actor, &files_service.ChangeRepoFilesOptions{
|
|
Files: []*files_service.ChangeRepoFile{
|
|
{
|
|
Operation: "create",
|
|
TreePath: "File_A",
|
|
ContentReader: strings.NewReader("File A"),
|
|
},
|
|
},
|
|
Message: "Add File A",
|
|
OldBranch: "master",
|
|
NewBranch: "master",
|
|
Author: &files_service.IdentityOptions{
|
|
GitUserName: actor.Name,
|
|
GitUserEmail: actor.Email,
|
|
},
|
|
Committer: &files_service.IdentityOptions{
|
|
GitUserName: actor.Name,
|
|
GitUserEmail: actor.Email,
|
|
},
|
|
Dates: &files_service.CommitDateOptions{
|
|
Author: time.Now(),
|
|
Committer: time.Now(),
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// create a commit on head Repo
|
|
_, err = files_service.ChangeRepoFiles(t.Context(), headRepo, actor, &files_service.ChangeRepoFilesOptions{
|
|
Files: []*files_service.ChangeRepoFile{
|
|
{
|
|
Operation: "create",
|
|
TreePath: "File_B",
|
|
ContentReader: strings.NewReader("File B"),
|
|
},
|
|
},
|
|
Message: "Add File on PR branch",
|
|
OldBranch: "master",
|
|
NewBranch: "newBranch",
|
|
Author: &files_service.IdentityOptions{
|
|
GitUserName: actor.Name,
|
|
GitUserEmail: actor.Email,
|
|
},
|
|
Committer: &files_service.IdentityOptions{
|
|
GitUserName: actor.Name,
|
|
GitUserEmail: actor.Email,
|
|
},
|
|
Dates: &files_service.CommitDateOptions{
|
|
Author: time.Now(),
|
|
Committer: time.Now(),
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// create Pull
|
|
pullIssue := &issues_model.Issue{
|
|
RepoID: baseRepo.ID,
|
|
Title: "Test Pull -to-update-",
|
|
PosterID: actor.ID,
|
|
Poster: actor,
|
|
IsPull: true,
|
|
}
|
|
pullRequest := &issues_model.PullRequest{
|
|
HeadRepoID: headRepo.ID,
|
|
BaseRepoID: baseRepo.ID,
|
|
HeadBranch: "newBranch",
|
|
BaseBranch: "master",
|
|
HeadRepo: headRepo,
|
|
BaseRepo: baseRepo,
|
|
Type: issues_model.PullRequestGitea,
|
|
}
|
|
prOpts := &pull_service.NewPullRequestOptions{Repo: baseRepo, Issue: pullIssue, PullRequest: pullRequest}
|
|
basePerm, err := access_model.GetDoerRepoPermission(t.Context(), prOpts.Repo, prOpts.Issue.Poster)
|
|
require.NoError(t, err)
|
|
err = pull_service.NewPullRequest(t.Context(), basePerm, prOpts)
|
|
assert.NoError(t, err)
|
|
|
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: "Test Pull -to-update-"})
|
|
assert.NoError(t, issue.LoadPullRequest(t.Context()))
|
|
|
|
return issue.PullRequest
|
|
}
|