Compare commits

...

3 commits

Author SHA1 Message Date
8981e11426
add first integration function test
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/push/build Pipeline failed
testing / backend-checks (pull_request) Has been cancelled
testing / frontend-checks (pull_request) Has been cancelled
testing / test-unit (pull_request) Has been cancelled
testing / test-e2e (pull_request) Has been cancelled
testing / test-remote-cacher (map[image:docker.io/bitnami/redis:7.2 port:6379]) (pull_request) Has been cancelled
testing / test-remote-cacher (map[image:docker.io/bitnami/valkey:7.2 port:6379]) (pull_request) Has been cancelled
testing / test-remote-cacher (map[image:ghcr.io/microsoft/garnet-alpine:1.0.14 port:6379]) (pull_request) Has been cancelled
testing / test-remote-cacher (map[image:registry.redict.io/redict:7.3.0-scratch port:6379]) (pull_request) Has been cancelled
testing / test-mysql (pull_request) Has been cancelled
testing / test-pgsql (pull_request) Has been cancelled
testing / test-sqlite (pull_request) Has been cancelled
testing / security-check (pull_request) Has been cancelled
/ release-notes (pull_request_target) Has been cancelled
2024-10-26 21:58:44 +02:00
2cf271321a
Add woodpecker ci (temp) 2024-10-26 21:05:16 +02:00
61a47fe832
Add branch auto deletion for scheduled PRs 2024-10-26 21:05:08 +02:00
13 changed files with 402 additions and 170 deletions

38
.woodpecker/.build.yaml Normal file
View file

@ -0,0 +1,38 @@
---
steps:
- name: build (prod)
image: woodpeckerci/plugin-docker-buildx
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/tom/forkjo
platforms:
- linux/amd64
tags:
- latest
- ${CI_PIPELINE_NUMBER}
when:
- branch: forgejo
event: push
- name: build (experimental)
image: woodpeckerci/plugin-docker-buildx
settings:
registry: git.ar21.de
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
repo: git.ar21.de/tom/forkjo
platforms:
- linux/amd64
tags:
- experimental
- experimental-${CI_PIPELINE_NUMBER}
when:
- branch:
exclude: forgejo
event: push

View file

@ -82,6 +82,8 @@ var migrations = []*Migration{
NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror),
// v22 -> v23
NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential),
// v23 -> v24
NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge),
}
// GetCurrentDBVersion returns the current Forgejo database version.

View file

@ -0,0 +1,16 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package forgejo_migrations //nolint:revive
import "xorm.io/xorm"
// AddDeleteBranchAfterMergeToAutoMerge: add DeleteBranchAfterMerge column, setting existing rows to false
func AddDeleteBranchAfterMergeToAutoMerge(x *xorm.Engine) error {
type AutoMerge struct {
ID int64 `xorm:"pk autoincr"`
DeleteBranchAfterMerge bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(&AutoMerge{})
}

View file

@ -15,13 +15,14 @@ import (
// AutoMerge represents a pull request scheduled for merging when checks succeed
type AutoMerge struct {
ID int64 `xorm:"pk autoincr"`
PullID int64 `xorm:"UNIQUE"`
DoerID int64 `xorm:"INDEX NOT NULL"`
Doer *user_model.User `xorm:"-"`
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
Message string `xorm:"LONGTEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
ID int64 `xorm:"pk autoincr"`
PullID int64 `xorm:"UNIQUE"`
DoerID int64 `xorm:"INDEX NOT NULL"`
Doer *user_model.User `xorm:"-"`
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
Message string `xorm:"LONGTEXT"`
DeleteBranchAfterMerge bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
// TableName return database table name for xorm
@ -49,7 +50,7 @@ func IsErrAlreadyScheduledToAutoMerge(err error) bool {
}
// ScheduleAutoMerge schedules a pull request to be merged when all checks succeed
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string) error {
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string, deleteBranch bool) error {
// Check if we already have a merge scheduled for that pull request
if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil {
return err
@ -58,10 +59,11 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
}
_, err := db.GetEngine(ctx).Insert(&AutoMerge{
DoerID: doer.ID,
PullID: pullID,
MergeStyle: style,
Message: message,
DoerID: doer.ID,
PullID: pullID,
MergeStyle: style,
Message: message,
DeleteBranchAfterMerge: deleteBranch,
})
return err
}

2
package-lock.json generated
View file

@ -1,5 +1,5 @@
{
"name": "forgejo",
"name": "forkjo",
"lockfileVersion": 3,
"requires": true,
"packages": {

View file

@ -988,7 +988,7 @@ func MergePullRequest(ctx *context.APIContext) {
}
if form.MergeWhenChecksSucceed {
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
if err != nil {
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)

View file

@ -28,7 +28,7 @@ func TestHandlePullRequestMerging(t *testing.T) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr")
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr", false)
require.NoError(t, err)
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})

View file

@ -1302,7 +1302,7 @@ func MergePullRequest(ctx *context.Context) {
// delete all scheduled auto merges
_ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
// schedule auto merge
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
if err != nil {
ctx.ServerError("ScheduleAutoMerge", err)
return

View file

@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/queue"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
shared_automerge "code.gitea.io/gitea/services/shared/automerge"
)
@ -52,9 +53,9 @@ func handler(items ...string) []string {
}
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string, deleteBranch bool) (scheduled bool, err error) {
err = db.WithTx(ctx, func(ctx context.Context) error {
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message, deleteBranch); err != nil {
return err
}
scheduled = true
@ -210,4 +211,15 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
return
}
if scheduledPRM.DeleteBranchAfterMerge {
deleted, err := repo_service.DeleteBranchIfUnused(ctx, pr, headGitRepo, doer)
if err != nil {
if deleted {
log.Info("%d branch deleted but got error during comment creation: %v", pr.ID, err)
} else {
log.Error("%d repo_service.DeleteBranchIfUnused: %v", pr.ID, err)
}
}
}
}

View file

@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
files_service "code.gitea.io/gitea/services/repository/files"
"xorm.io/builder"
@ -475,6 +476,36 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return nil
}
func DeleteBranchIfUnused(ctx context.Context, pr *issues_model.PullRequest, repo *git.Repository, doer *user_model.User) (bool, error) {
// Don't cleanup when there are other PR's that use this branch as head branch.
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
if err != nil {
log.Error("HasUnmergedPullRequestsByHeadInfo", err)
return false, err
}
if exist {
log.Info("%d was scheduled to automerge with branch deletion but branch is still used", pr.ID)
return false, nil
}
if err := pull_service.RetargetChildrenOnMerge(ctx, doer, pr); err != nil {
log.Error("RetargetChildrenOnMerge", err)
return false, err
}
if err := DeleteBranch(ctx, doer, pr.HeadRepo, repo, pr.HeadBranch); err != nil {
log.Error("DeleteBranch: %v", err)
return false, err
}
if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
log.Error("AddDeletePRBranchComment: %v", err)
return true, err
}
return true, nil
}
type BranchSyncOptions struct {
RepoID int64
}

View file

@ -34,7 +34,7 @@ func TestActionsAutomerge(t *testing.T) {
assert.False(t, pr.HasMerged, "PR should not be merged")
assert.Equal(t, issues_model.PullRequestStatusMergeable, pr.Status, "PR should be mergeable")
scheduled, err := automerge.ScheduleAutoMerge(ctx, user, pr, repo_model.MergeStyleMerge, "Dummy")
scheduled, err := automerge.ScheduleAutoMerge(ctx, user, pr, repo_model.MergeStyleMerge, "Dummy", false)
require.NoError(t, err, "PR should be scheduled for automerge")
assert.True(t, scheduled, "PR should be scheduled for automerge")

View file

@ -845,160 +845,99 @@ func TestPullMergeBranchProtect(t *testing.T) {
})
}
func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// create a pull request
session := loginUser(t, "user1")
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
forkedName := "repo1-1"
testRepoFork(t, session, "user2", "repo1", "user1", forkedName)
defer func() {
testDeleteRepository(t, session, "user1", forkedName)
}()
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
func testPullAutoMergeAfterCommitStatusSucceed(t *testing.T, forkName string, approval bool, deleteBranch bool) {
// create a pull request
session := loginUser(t, "user1")
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
testRepoFork(t, session, "user2", "repo1", "user1", forkName)
defer func() {
testDeleteRepository(t, session, "user1", forkName)
}()
if deleteBranch {
testEditFileToNewBranch(t, session, "user1", forkName, "master", "new_branch_1", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", forkName, false, "master", "new_branch_1", "Indexer notifier test pull")
} else {
testEditFile(t, session, "user1", forkName, "master", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", forkName, false, "master", "master", "Indexer notifier test pull")
}
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
BaseRepoID: baseRepo.ID,
BaseBranch: "master",
HeadRepoID: forkedRepo.ID,
HeadBranch: "master",
})
// add protected branch for commit status
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
// Change master branch to protected
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
"_csrf": csrf,
"rule_name": "master",
"enable_push": "true",
"enable_status_check": "true",
"status_check_contexts": "gitea/actions",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// first time insert automerge record, return true
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
require.NoError(t, err)
assert.True(t, scheduled)
// second time insert automerge record, return false because it does exist
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
require.Error(t, err)
assert.False(t, scheduled)
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
assert.Empty(t, pr.MergedCommitID)
// update commit status to success, then it should be merged automatically
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
require.NoError(t, err)
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
require.NoError(t, err)
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
require.NoError(t, err)
branches, _, err := baseGitRepo.GetBranchNames(0, 100)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"sub-home-md-img-check", "home-md-img-check", "pr-to-update", "branch2", "DefaultBranch", "develop", "feature/1", "master"}, branches)
baseGitRepo.Close()
defer func() {
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
State: api.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
require.NoError(t, err)
time.Sleep(2 * time.Second)
// realod pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.True(t, pr.HasMerged)
assert.NotEmpty(t, pr.MergedCommitID)
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkName})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
BaseRepoID: baseRepo.ID,
BaseBranch: "master",
HeadRepoID: forkedRepo.ID,
HeadBranch: "master",
})
}
func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
// create a pull request
session := loginUser(t, "user1")
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
forkedName := "repo1-2"
testRepoFork(t, session, "user2", "repo1", "user1", forkedName)
defer func() {
testDeleteRepository(t, session, "user1", forkedName)
}()
testEditFile(t, session, "user1", forkedName, "master", "README.md", "Hello, World (Edited)\n")
testPullCreate(t, session, "user1", forkedName, false, "master", "master", "Indexer notifier test pull")
// add protected branch for commit status
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: forkedName})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
BaseRepoID: baseRepo.ID,
BaseBranch: "master",
HeadRepoID: forkedRepo.ID,
HeadBranch: "master",
})
// Change master branch to protected
reqParams := map[string]string{
"_csrf": csrf,
"rule_name": "master",
"enable_push": "true",
"enable_status_check": "true",
"status_check_contexts": "gitea/actions",
}
if approval {
reqParams["required_approvals"] = "1"
}
// add protected branch for commit status
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
// Change master branch to protected
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
"_csrf": csrf,
"rule_name": "master",
"enable_push": "true",
"enable_status_check": "true",
"status_check_contexts": "gitea/actions",
"required_approvals": "1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", reqParams)
session.MakeRequest(t, req, http.StatusSeeOther)
// first time insert automerge record, return true
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
if deleteBranch {
// check if new branch exists
forkedGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, forkedRepo)
require.NoError(t, err)
assert.True(t, scheduled)
// second time insert automerge record, return false because it does exist
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
require.Error(t, err)
assert.False(t, scheduled)
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
assert.Empty(t, pr.MergedCommitID)
// update commit status to success, then it should be merged automatically
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
newBranch, err := forkedGitRepo.GetBranch("new_branch_1")
require.NoError(t, err)
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
require.NoError(t, err)
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
require.NoError(t, err)
baseGitRepo.Close()
defer func() {
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
}()
assert.NotNil(t, newBranch)
}
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
State: api.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
require.NoError(t, err)
// first time insert automerge record, return true
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
require.NoError(t, err)
assert.True(t, scheduled)
time.Sleep(2 * time.Second)
// second time insert automerge record, return false because it does exist
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
require.Error(t, err)
assert.False(t, scheduled)
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
assert.Empty(t, pr.MergedCommitID)
// update commit status to success, then it should be merged automatically
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
require.NoError(t, err)
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
require.NoError(t, err)
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
require.NoError(t, err)
branches, _, err := baseGitRepo.GetBranchNames(0, 100)
require.NoError(t, err)
assert.ElementsMatch(t, []string{"sub-home-md-img-check", "home-md-img-check", "pr-to-update", "branch2", "DefaultBranch", "develop", "feature/1", "master"}, branches)
baseGitRepo.Close()
defer func() {
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
State: api.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
require.NoError(t, err)
time.Sleep(2 * time.Second)
if approval {
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
@ -1012,13 +951,62 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
time.Sleep(2 * time.Second)
}
// realod pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.True(t, pr.HasMerged)
assert.NotEmpty(t, pr.MergedCommitID)
// realod pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.True(t, pr.HasMerged)
assert.NotEmpty(t, pr.MergedCommitID)
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
if deleteBranch {
// check if new branch got removed
forkedGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, forkedRepo)
require.NoError(t, err)
_, err = forkedGitRepo.GetBranch("new_branch_1")
require.Error(t, err)
assert.True(t, git.IsErrBranchNotExist(err))
}
}
func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
for _, testCase := range []struct {
name string
forkName string
approval bool
deleteBranch bool
}{
{
name: "TestPullAutoMergeAfterCommitStatusSucceed",
forkName: "repo1-1",
approval: false,
deleteBranch: false,
},
{
name: "TestPullAutoMergeAfterCommitStatusSucceedWithBranchDeletion",
forkName: "repo1-2",
approval: false,
deleteBranch: true,
},
{
name: "TestPullAutoMergeAfterCommitStatusSucceedAndApproval",
forkName: "repo2-1",
approval: true,
deleteBranch: false,
},
{
name: "TestPullAutoMergeAfterCommitStatusSucceedAndApprovalWithBranchDeletion",
forkName: "repo2-2",
approval: true,
deleteBranch: true,
},
} {
t.Run(testCase.name, func(t *testing.T) {
testPullAutoMergeAfterCommitStatusSucceed(t, testCase.forkName, testCase.approval, testCase.deleteBranch)
})
}
})
}
@ -1093,12 +1081,12 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
// first time insert automerge record, return true
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
require.NoError(t, err)
assert.True(t, scheduled)
// second time insert automerge record, return false because it does exist
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
require.Error(t, err)
assert.False(t, scheduled)
@ -1150,3 +1138,146 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
})
}
func TestPullAutoMergeWithBranchDeletionAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
// create a pull request
baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
dstPath := t.TempDir()
u.Path = baseAPITestContext.GitPath()
u.User = url.UserPassword("user2", userPassword)
t.Run("Clone", doGitClone(dstPath, u))
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
require.NoError(t, err)
err = git.AddChanges(dstPath, true)
require.NoError(t, err)
err = git.CommitChanges(dstPath, git.CommitChangesOptions{
Committer: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Author: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Message: "Testing commit 1",
})
require.NoError(t, err)
stderrBuf := &bytes.Buffer{}
err = git.NewCommand(git.DefaultContext, "push", "origin", "HEAD:refs/for/master", "-o").
AddDynamicArguments(`topic=test/head2`).
AddArguments("-o").
AddDynamicArguments(`title="create a test pull request with agit"`).
AddArguments("-o").
AddDynamicArguments(`description="This PR is a test pull request which created with agit"`).
Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf})
require.NoError(t, err)
assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6")
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
Flow: issues_model.PullRequestFlowAGit,
BaseRepoID: baseRepo.ID,
BaseBranch: "master",
HeadRepoID: baseRepo.ID,
HeadBranch: "user2/test/head2",
})
session := loginUser(t, "user1")
// add protected branch for commit status
csrf := GetCSRF(t, session, "/user2/repo1/settings/branches")
// Change master branch to protected
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
"_csrf": csrf,
"rule_name": "master",
"enable_push": "true",
"enable_status_check": "true",
"status_check_contexts": "gitea/actions",
"required_approvals": "1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// check if new branch exists
baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
require.NoError(t, err)
newBranch, err := baseGitRepo.GetBranch("user2/test/head2")
require.NoError(t, err)
assert.NotNil(t, newBranch)
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
// first time insert automerge record, return true
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", true)
require.NoError(t, err)
assert.True(t, scheduled)
// second time insert automerge record, return false because it does exist
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", true)
require.Error(t, err)
assert.False(t, scheduled)
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
assert.Empty(t, pr.MergedCommitID)
// update commit status to success, then it should be merged automatically
baseGitRepo, err = gitrepo.OpenRepository(db.DefaultContext, baseRepo)
require.NoError(t, err)
sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
require.NoError(t, err)
masterCommitID, err := baseGitRepo.GetBranchCommitID("master")
require.NoError(t, err)
baseGitRepo.Close()
defer func() {
testResetRepo(t, baseRepo.RepoPath(), "master", masterCommitID)
}()
err = commitstatus_service.CreateCommitStatus(db.DefaultContext, baseRepo, user1, sha, &git_model.CommitStatus{
State: api.CommitStatusSuccess,
TargetURL: "https://gitea.com",
Context: "gitea/actions",
})
require.NoError(t, err)
time.Sleep(2 * time.Second)
// reload pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.False(t, pr.HasMerged)
assert.Empty(t, pr.MergedCommitID)
// approve the PR from non-author
approveSession := loginUser(t, "user1")
req = NewRequest(t, "GET", fmt.Sprintf("/user2/repo1/pulls/%d", pr.Index))
resp := approveSession.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
testSubmitReview(t, approveSession, htmlDoc.GetCSRF(), "user2", "repo1", strconv.Itoa(int(pr.Index)), sha, "approve", http.StatusOK)
time.Sleep(2 * time.Second)
// realod pr again
pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
assert.True(t, pr.HasMerged)
assert.NotEmpty(t, pr.MergedCommitID)
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{PullID: pr.ID})
// check if new branch got removed
baseGitRepo, err = gitrepo.OpenRepository(db.DefaultContext, baseRepo)
require.NoError(t, err)
_, err = baseGitRepo.GetBranch("user2/test/head2")
require.Error(t, err)
assert.True(t, git.IsErrBranchNotExist(err))
})
}

View file

@ -130,7 +130,7 @@ export default {
{{ mergeForm.textCancel }}
</button>
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable">
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
</div>