0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-10-07 04:47:07 +02:00

Add rebase push display wrong comments bug (#35560) (#35580)

Backport #35560 by @lunny

Fix #35518

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot 2025-10-04 16:28:32 +08:00 committed by GitHub
parent d94faf6d7e
commit 006fe2a907
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 177 additions and 21 deletions

View File

@ -213,3 +213,15 @@
is_deleted: false is_deleted: false
deleted_by_id: 0 deleted_by_id: 0
deleted_unix: 0 deleted_unix: 0
-
id: 26
repo_id: 10
name: 'feature/1'
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
commit_message: 'Initial commit'
commit_time: 1489950479
pusher_id: 2
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

View File

@ -1583,7 +1583,16 @@ func UpdatePullRequestTarget(ctx *context.Context) {
} }
if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, targetBranch); err != nil { if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, targetBranch); err != nil {
if issues_model.IsErrPullRequestAlreadyExists(err) { switch {
case git_model.IsErrBranchNotExist(err):
errorMessage := ctx.Tr("form.target_branch_not_exist")
ctx.Flash.Error(errorMessage)
ctx.JSON(http.StatusBadRequest, map[string]any{
"error": err.Error(),
"user_error": errorMessage,
})
case issues_model.IsErrPullRequestAlreadyExists(err):
err := err.(issues_model.ErrPullRequestAlreadyExists) err := err.(issues_model.ErrPullRequestAlreadyExists)
RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
@ -1594,7 +1603,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(), "error": err.Error(),
"user_error": errorMessage, "user_error": errorMessage,
}) })
} else if issues_model.IsErrIssueIsClosed(err) { case issues_model.IsErrIssueIsClosed(err):
errorMessage := ctx.Tr("repo.pulls.is_closed") errorMessage := ctx.Tr("repo.pulls.is_closed")
ctx.Flash.Error(errorMessage) ctx.Flash.Error(errorMessage)
@ -1602,7 +1611,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(), "error": err.Error(),
"user_error": errorMessage, "user_error": errorMessage,
}) })
} else if pull_service.IsErrPullRequestHasMerged(err) { case pull_service.IsErrPullRequestHasMerged(err):
errorMessage := ctx.Tr("repo.pulls.has_merged") errorMessage := ctx.Tr("repo.pulls.has_merged")
ctx.Flash.Error(errorMessage) ctx.Flash.Error(errorMessage)
@ -1610,7 +1619,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(), "error": err.Error(),
"user_error": errorMessage, "user_error": errorMessage,
}) })
} else if git_model.IsErrBranchesEqual(err) { case git_model.IsErrBranchesEqual(err):
errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
ctx.Flash.Error(errorMessage) ctx.Flash.Error(errorMessage)
@ -1618,7 +1627,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(), "error": err.Error(),
"user_error": errorMessage, "user_error": errorMessage,
}) })
} else { default:
ctx.ServerError("UpdatePullRequestTarget", err) ctx.ServerError("UpdatePullRequestTarget", err)
} }
return return

View File

@ -63,6 +63,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *iss
var data issues_model.PushActionContent var data issues_model.PushActionContent
if opts.IsForcePush { if opts.IsForcePush {
data.CommitIDs = []string{oldCommitID, newCommitID} data.CommitIDs = []string{oldCommitID, newCommitID}
data.IsForcePush = true
} else { } else {
data.CommitIDs, err = getCommitIDsFromRepo(ctx, pr.BaseRepo, oldCommitID, newCommitID, pr.BaseBranch) data.CommitIDs, err = getCommitIDsFromRepo(ctx, pr.BaseRepo, oldCommitID, newCommitID, pr.BaseBranch)
if err != nil { if err != nil {

View File

@ -244,6 +244,17 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
} }
} }
exist, err := git_model.IsBranchExist(ctx, pr.BaseRepoID, targetBranch)
if err != nil {
return err
}
if !exist {
return git_model.ErrBranchNotExist{
RepoID: pr.BaseRepoID,
BranchName: targetBranch,
}
}
// Check if branches are equal // Check if branches are equal
branchesEqual, err := IsHeadEqualWithBranch(ctx, pr, targetBranch) branchesEqual, err := IsHeadEqualWithBranch(ctx, pr, targetBranch)
if err != nil { if err != nil {

View File

@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func withKeyFile(t *testing.T, keyname string, callback func(string)) { func withKeyFile(t *testing.T, keyname string, callback func(string)) {
@ -160,20 +161,27 @@ func doGitPushTestRepositoryFail(dstPath string, args ...string) func(*testing.T
} }
} }
func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { type localGitAddCommitOptions struct {
return func(t *testing.T) { LocalRepoPath string
doGitCheckoutBranch(dstPath, branch)(t) CheckoutBranch string
TreeFilePath string
TreeFileContent string
}
assert.NoError(t, os.WriteFile(filepath.Join(dstPath, fmt.Sprintf("file-%s.txt", branch)), []byte("file "+branch), 0o644)) func doGitCheckoutWriteFileCommit(opts localGitAddCommitOptions) func(*testing.T) {
assert.NoError(t, git.AddChanges(t.Context(), dstPath, true)) return func(t *testing.T) {
doGitCheckoutBranch(opts.LocalRepoPath, opts.CheckoutBranch)(t)
localFilePath := filepath.Join(opts.LocalRepoPath, opts.TreeFilePath)
require.NoError(t, os.WriteFile(localFilePath, []byte(opts.TreeFileContent), 0o644))
require.NoError(t, git.AddChanges(t.Context(), opts.LocalRepoPath, true))
signature := git.Signature{ signature := git.Signature{
Email: "test@test.test", Email: "test@test.test",
Name: "test", Name: "test",
} }
assert.NoError(t, git.CommitChanges(t.Context(), dstPath, git.CommitChangesOptions{ require.NoError(t, git.CommitChanges(t.Context(), opts.LocalRepoPath, git.CommitChangesOptions{
Committer: &signature, Committer: &signature,
Author: &signature, Author: &signature,
Message: "update " + branch, Message: fmt.Sprintf("update %s @ %s", opts.TreeFilePath, opts.CheckoutBranch),
})) }))
} }
} }

View File

@ -58,8 +58,12 @@ func testGitPush(t *testing.T, u *url.URL) {
for i := range 5 { for i := range 5 {
branchName := fmt.Sprintf("branch-%d", i) branchName := fmt.Sprintf("branch-%d", i)
pushed = append(pushed, branchName) pushed = append(pushed, branchName)
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
doGitAddSomeCommits(gitPath, branchName)(t) LocalRepoPath: gitPath,
CheckoutBranch: branchName,
TreeFilePath: fmt.Sprintf("file-%s.txt", branchName),
TreeFileContent: "file " + branchName,
})(t)
} }
for i := 5; i < 10; i++ { for i := 5; i < 10; i++ {

View File

@ -0,0 +1,109 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"os"
"testing"
"time"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
issues_service "code.gitea.io/gitea/services/issue"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testWaitForPullRequestStatus(t *testing.T, prIssue *issues_model.Issue, expectedStatus issues_model.PullRequestStatus) (retIssue *issues_model.Issue) {
require.Eventually(t, func() bool {
prIssueCond := *prIssue
retIssue = unittest.AssertExistsAndLoadBean(t, &prIssueCond)
require.NoError(t, retIssue.LoadPullRequest(t.Context()))
return retIssue.PullRequest.Status == expectedStatus
}, 5*time.Second, 20*time.Millisecond)
return retIssue
}
func testPullCommentRebase(t *testing.T, u *url.URL, session *TestSession) {
testPRTitle := "Test PR for rebase comment"
// make a change on forked branch
testEditFile(t, session, "user1", "repo1", "test-branch/rebase", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", "repo1", false, "test-branch/rebase", "test-branch/rebase", testPRTitle)
// create a conflict on base repo branch
testEditFile(t, session, "user2", "repo1", "test-branch/rebase", "README.md", "Hello, World (Edited Conflicted)\n")
// Now the pull request status should be conflicted
testWaitForPullRequestStatus(t, &issues_model.Issue{Title: testPRTitle}, issues_model.PullRequestStatusConflict)
dstPath := t.TempDir()
u.Path = "/user2/repo1.git"
doGitClone(dstPath, u)(t)
doGitCheckoutBranch(dstPath, "test-branch/rebase")(t)
doGitCreateBranch(dstPath, "local-branch/rebase")(t)
content, _ := os.ReadFile(dstPath + "/README.md")
require.Equal(t, "Hello, World (Edited Conflicted)\n", string(content))
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
LocalRepoPath: dstPath,
CheckoutBranch: "local-branch/rebase",
TreeFilePath: "README.md",
TreeFileContent: "Hello, World (Edited Conflict Resolved)\n",
})(t)
// do force push
u.Path = "/user1/repo1.git"
u.User = url.UserPassword("user1", userPassword)
doGitAddRemote(dstPath, "base-repo", u)(t)
doGitPushTestRepositoryFail(dstPath, "base-repo", "local-branch/rebase:test-branch/rebase")(t)
doGitPushTestRepository(dstPath, "--force", "base-repo", "local-branch/rebase:test-branch/rebase")(t)
// reload the pr
prIssue := testWaitForPullRequestStatus(t, &issues_model.Issue{Title: testPRTitle}, issues_model.PullRequestStatusMergeable)
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
IssueID: prIssue.ID,
Type: issues_model.CommentTypeUndefined, // get all comments type
})
require.NoError(t, err)
lastComment := comments[len(comments)-1]
assert.NoError(t, issues_service.LoadCommentPushCommits(t.Context(), lastComment))
assert.True(t, lastComment.IsForcePush)
}
func testPullCommentRetarget(t *testing.T, u *url.URL, session *TestSession) {
testPRTitle := "Test PR for retarget comment"
// keep a non-conflict branch
testCreateBranch(t, session, "user2", "repo1", "branch/test-branch/retarget", "test-branch/retarget-no-conflict", http.StatusSeeOther)
// make a change on forked branch
testEditFile(t, session, "user1", "repo1", "test-branch/retarget", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", "repo1", false, "test-branch/retarget", "test-branch/retarget", testPRTitle)
// create a conflict line on user2/repo1 README.md
testEditFile(t, session, "user2", "repo1", "test-branch/retarget", "README.md", "Hello, World (Edited Conflicted)\n")
// Now the pull request status should be conflicted
prIssue := testWaitForPullRequestStatus(t, &issues_model.Issue{Title: testPRTitle}, issues_model.PullRequestStatusConflict)
// do retarget
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/pull/%d/target_branch", prIssue.PullRequest.Index), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"target_branch": "test-branch/retarget-no-conflict",
})
session.MakeRequest(t, req, http.StatusOK)
testWaitForPullRequestStatus(t, &issues_model.Issue{Title: testPRTitle}, issues_model.PullRequestStatusMergeable)
}
func TestPullComment(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user1")
testCreateBranch(t, session, "user2", "repo1", "branch/master", "test-branch/rebase", http.StatusSeeOther)
testCreateBranch(t, session, "user2", "repo1", "branch/master", "test-branch/retarget", http.StatusSeeOther)
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
t.Run("RebaseComment", func(t *testing.T) { testPullCommentRebase(t, u, session) })
t.Run("RetargetComment", func(t *testing.T) { testPullCommentRetarget(t, u, session) })
})
}

View File

@ -260,14 +260,16 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) {
u.Path = "user2/repo1.git" u.Path = "user2/repo1.git"
u.User = url.UserPassword("user4", userPassword) u.User = url.UserPassword("user4", userPassword)
t.Run("Clone", doGitClone(dstPath, u)) doGitClone(dstPath, u)(t)
doGitCheckoutWriteFileCommit(localGitAddCommitOptions{
LocalRepoPath: dstPath,
CheckoutBranch: "master",
TreeFilePath: "new-file-for-agit.txt",
TreeFileContent: "temp content",
})(t)
t.Run("add commit", doGitAddSomeCommits(dstPath, "master")) err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
assert.NoError(t, err)
t.Run("do agit pull create", func(t *testing.T) {
err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o").AddDynamicArguments("topic="+"test-topic").Run(t.Context(), &gitcmd.RunOpts{Dir: dstPath})
assert.NoError(t, err)
})
}) })
} }