mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-22 17:02:41 +00:00
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
319 lines
12 KiB
Go
319 lines
12 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"
|
|
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)
|
|
})
|
|
}
|
|
|
|
// TestAPIPullUpdatePublicOnlyToken verifies that a public-only API token cannot
|
|
// update (push into) a PR whose head repo is private, even when the base repo
|
|
// named in the route is public.
|
|
func TestAPIPullUpdatePublicOnlyToken(t *testing.T) {
|
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
|
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()))
|
|
|
|
// make the head repo private while the base repo stays public
|
|
require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(),
|
|
&repo_model.Repository{ID: pr.HeadRepo.ID, IsPrivate: true}, "is_private"))
|
|
|
|
// a public-only write token must be refused (404), not perform the push
|
|
publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopePublicOnly)
|
|
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index).
|
|
AddTokenAuth(publicOnlyToken)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
|
|
// the head branch must still be outdated (no push happened)
|
|
diffCount, err := git.GetDivergingCommits(t.Context(), pr.BaseRepo, pr.BaseBranch, pr.GetGitHeadRefName())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, diffCount.Behind)
|
|
|
|
// a normal write token still works, proving the guard is scope-specific
|
|
token := getUserToken(t, user.Name, 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)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
})
|
|
}
|
|
|
|
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}
|
|
err = pull_service.NewPullRequest(t.Context(), 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
|
|
}
|