Compare commits
2 Commits
t-mergemod
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
| 7ec663db27 | |||
| 3b83ba96e4 |
@@ -39,10 +39,6 @@ const (
|
|||||||
|
|
||||||
Permission_ForceMerge = "force-merge"
|
Permission_ForceMerge = "force-merge"
|
||||||
Permission_Group = "release-engineering"
|
Permission_Group = "release-engineering"
|
||||||
|
|
||||||
MergeModeFF = "ff-only"
|
|
||||||
MergeModeReplace = "replace"
|
|
||||||
MergeModeDevel = "devel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
@@ -56,9 +52,9 @@ type ReviewGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type QAConfig struct {
|
type QAConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Origin string
|
Origin string
|
||||||
Label string // requires this gitea lable to be set or skipped
|
Label string // requires this gitea lable to be set or skipped
|
||||||
BuildDisableRepos []string // which repos to build disable in the new project
|
BuildDisableRepos []string // which repos to build disable in the new project
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,8 +89,7 @@ type AutogitConfig struct {
|
|||||||
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
||||||
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
||||||
|
|
||||||
Labels map[string]string // list of tags, if not default, to apply
|
Labels map[string]string // list of tags, if not default, to apply
|
||||||
MergeMode string // project merge mode
|
|
||||||
|
|
||||||
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
|
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
|
||||||
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
||||||
@@ -189,17 +184,6 @@ func ReadWorkflowConfig(gitea GiteaFileContentAndRepoFetcher, git_project string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.GitProjectName = config.GitProjectName + "#" + branch
|
config.GitProjectName = config.GitProjectName + "#" + branch
|
||||||
|
|
||||||
// verify merge modes
|
|
||||||
switch config.MergeMode {
|
|
||||||
case MergeModeFF, MergeModeDevel, MergeModeReplace:
|
|
||||||
break // good results
|
|
||||||
case "":
|
|
||||||
config.MergeMode = MergeModeFF
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unsupported merge mode in %s: %s", git_project, config.MergeMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -341,67 +341,3 @@ func TestConfigPermissions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigMergeModeParser(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
json string
|
|
||||||
mergeMode string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty",
|
|
||||||
json: "{}",
|
|
||||||
mergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ff-only",
|
|
||||||
json: `{"MergeMode": "ff-only"}`,
|
|
||||||
mergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "replace",
|
|
||||||
json: `{"MergeMode": "replace"}`,
|
|
||||||
mergeMode: common.MergeModeReplace,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "devel",
|
|
||||||
json: `{"MergeMode": "devel"}`,
|
|
||||||
mergeMode: common.MergeModeDevel,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unsupported",
|
|
||||||
json: `{"MergeMode": "invalid"}`,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
repo := models.Repository{
|
|
||||||
DefaultBranch: "master",
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl := gomock.NewController(t)
|
|
||||||
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
|
|
||||||
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
|
|
||||||
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
|
|
||||||
|
|
||||||
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
|
|
||||||
if test.wantErr {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected error, got nil")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.MergeMode != test.mergeMode {
|
|
||||||
t.Errorf("Expected MergeMode %s, got %s", test.mergeMode, config.MergeMode)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
155
common/pr.go
155
common/pr.go
@@ -554,144 +554,6 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
|||||||
return is_manually_reviewed_ok
|
return is_manually_reviewed_ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *PRSet) AddMergeCommit(git Git, remote string, pr int) bool {
|
|
||||||
prinfo := rs.PRs[pr]
|
|
||||||
|
|
||||||
LogDebug("Adding merge commit for %s", PRtoString(prinfo.PR))
|
|
||||||
if !prinfo.PR.AllowMaintainerEdit {
|
|
||||||
LogError(" PR is not editable by maintainer")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := prinfo.PR.Base.Repo
|
|
||||||
head := prinfo.PR.Head
|
|
||||||
br := rs.Config.Branch
|
|
||||||
if len(br) == 0 {
|
|
||||||
br = prinfo.PR.Base.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("Merge branch '%s' into %s", br, head.Name)
|
|
||||||
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "-X", "theirs", head.Sha); err != nil {
|
|
||||||
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "--allow-unrelated-histories", "-X", "theirs", head.Sha); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
LogError("WARNING: Merging unrelated histories")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure only files that are in head.Sha are kept
|
|
||||||
git.GitExecOrPanic(repo.Name, "read-tree", "--reset", "-u", head.Sha)
|
|
||||||
git.GitExecOrPanic(repo.Name, "commit", "-m", msg)
|
|
||||||
|
|
||||||
if !IsDryRun {
|
|
||||||
git.GitExecOrPanic(repo.Name, "push", remote, "HEAD:"+head.Name)
|
|
||||||
prinfo.PR.Head.Sha = strings.TrimSpace(git.GitExecWithOutputOrPanic(repo.Name, "rev-list", "-1", "HEAD")) // need to update as it's pushed but pr not refetched
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *PRSet) HasMerge(git Git, pr int) bool {
|
|
||||||
prinfo := rs.PRs[pr]
|
|
||||||
|
|
||||||
repo := prinfo.PR.Base.Repo
|
|
||||||
head := prinfo.PR.Head
|
|
||||||
br := rs.Config.Branch
|
|
||||||
if len(br) == 0 {
|
|
||||||
br = prinfo.PR.Base.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
parents, err := git.GitExecWithOutput(repo.Name, "show", "-s", "--format=%P", head.Sha)
|
|
||||||
if err == nil {
|
|
||||||
p := strings.Fields(strings.TrimSpace(parents))
|
|
||||||
if len(p) == 2 {
|
|
||||||
targetHead, _ := git.GitExecWithOutput(repo.Name, "rev-parse", "HEAD")
|
|
||||||
targetHead = strings.TrimSpace(targetHead)
|
|
||||||
if p[0] == targetHead || p[1] == targetHead {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *PRSet) PrepareForMerge(git Git) bool {
|
|
||||||
// verify that package can merge here. Checkout current target branch of each PRSet, make a temporary branch
|
|
||||||
// PR_#_mergetest and perform the merge based
|
|
||||||
|
|
||||||
if rs.Config.MergeMode == MergeModeDevel {
|
|
||||||
return true // always can merge as we set branch here, not merge anything
|
|
||||||
} else {
|
|
||||||
// make sure that all the package PRs are in mergeable state
|
|
||||||
for idx, prinfo := range rs.PRs {
|
|
||||||
if rs.IsPrjGitPR(prinfo.PR) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := prinfo.PR.Base.Repo
|
|
||||||
head := prinfo.PR.Head
|
|
||||||
br := rs.Config.Branch
|
|
||||||
if len(br) == 0 {
|
|
||||||
br = prinfo.PR.Base.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
remote, err := git.GitClone(repo.Name, br, repo.SSHURL)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", remote, head.Sha)
|
|
||||||
switch rs.Config.MergeMode {
|
|
||||||
case MergeModeFF:
|
|
||||||
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case MergeModeReplace:
|
|
||||||
Verify:
|
|
||||||
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
|
|
||||||
if !rs.HasMerge(git, idx) {
|
|
||||||
forkRemote, err := git.GitClone(repo.Name, head.Name, head.Repo.SSHURL)
|
|
||||||
if err != nil {
|
|
||||||
LogError("Failed to clone head repo:", head.Name, head.Repo.SSHURL)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
LogDebug("Merge commit is missing and this is not FF merge possibility")
|
|
||||||
git.GitExecOrPanic(repo.Name, "checkout", remote+"/"+br)
|
|
||||||
if !rs.AddMergeCommit(git, forkRemote, idx) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !IsDryRun {
|
|
||||||
goto Verify
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we check project git if mergeable
|
|
||||||
prjgit_info, err := rs.GetPrjGitPR()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
prjgit := prjgit_info.PR
|
|
||||||
|
|
||||||
_, _, prjgitBranch := rs.Config.GetPrjGit()
|
|
||||||
remote, err := git.GitClone(DefaultGitPrj, prjgitBranch, prjgit.Base.Repo.SSHURL)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
testBranch := fmt.Sprintf("PR_%d_mergetest", prjgit.Index)
|
|
||||||
git.GitExecOrPanic(DefaultGitPrj, "fetch", remote, prjgit.Head.Sha)
|
|
||||||
if err := git.GitExec(DefaultGitPrj, "checkout", "-B", testBranch, prjgit.Base.Sha); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if err := git.GitExec(DefaultGitPrj, "merge", "--no-ff", "--no-commit", prjgit.Head.Sha); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||||
prjgit_info, err := rs.GetPrjGitPR()
|
prjgit_info, err := rs.GetPrjGitPR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -856,10 +718,11 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
||||||
PanicOnError(err)
|
PanicOnError(err)
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
||||||
if rs.Config.MergeMode == MergeModeDevel || isNewRepo {
|
|
||||||
git.GitExecOrPanic(repo.Name, "checkout", "-B", br, head.Sha)
|
if isNewRepo {
|
||||||
|
LogInfo("Force-pushing new repository branch", br, "to", head.Sha)
|
||||||
|
// we don't merge, we just set the branch to this commit
|
||||||
} else {
|
} else {
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
|
||||||
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,15 +748,11 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !IsDryRun {
|
if !IsDryRun {
|
||||||
params := []string{"push"}
|
|
||||||
if rs.Config.MergeMode == MergeModeDevel || isNewRepo {
|
|
||||||
params = append(params, "-f")
|
|
||||||
}
|
|
||||||
params = append(params, prinfo.RemoteName)
|
|
||||||
if isNewRepo {
|
if isNewRepo {
|
||||||
params = append(params, prinfo.PR.Head.Sha+":"+prinfo.PR.Base.Name)
|
git.GitExecOrPanic(repo.Name, "push", "-f", prinfo.RemoteName, prinfo.PR.Head.Sha+":"+prinfo.PR.Base.Name)
|
||||||
|
} else {
|
||||||
|
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
|
||||||
}
|
}
|
||||||
git.GitExecOrPanic(repo.Name, params...)
|
|
||||||
} else {
|
} else {
|
||||||
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
|
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package common_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -1268,7 +1267,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
SSHURL: "file://" + path.Join(repoDir, "prjgit"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1290,7 +1289,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
SSHURL: "file://" + path.Join(cmd.Dir, "prjgit"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1400,344 +1399,3 @@ func TestPRChanges(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRPrepareForMerge(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest)
|
|
||||||
config *common.AutogitConfig
|
|
||||||
expected bool
|
|
||||||
editable bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Success Devel",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeDevel,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Success FF",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Success Replace MergeCommit",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeReplace,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
|
||||||
// merge-base fails initially
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
|
||||||
// HasMerge returns true
|
|
||||||
m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1 target_head", nil)
|
|
||||||
m.EXPECT().GitExecWithOutput("pkg", "rev-parse", "HEAD").Return("target_head", nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Merge Conflict in PrjGit",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(fmt.Errorf("conflict"))
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Not FF in PkgGit",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Success Replace with AddMergeCommit",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeReplace,
|
|
||||||
},
|
|
||||||
editable: true,
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha)
|
|
||||||
// First merge-base fails
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor"))
|
|
||||||
// HasMerge returns false
|
|
||||||
m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1", nil)
|
|
||||||
m.EXPECT().GitClone("pkg", pkgPR.Head.Name, pkgPR.Base.Repo.SSHURL).Return("origin_fork", nil)
|
|
||||||
// AddMergeCommit is called
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "checkout", "origin/master")
|
|
||||||
m.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "read-tree", "--reset", "-u", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "push", "origin_fork", "HEAD:"+pkgPR.Head.Name)
|
|
||||||
m.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_pkg_head_sha")
|
|
||||||
// Second merge-base succeeds (after goto Verify)
|
|
||||||
m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", "new_pkg_head_sha").Return(nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil)
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
prjPR := &models.PullRequest{
|
|
||||||
Index: 1,
|
|
||||||
Base: &models.PRBranchInfo{
|
|
||||||
Name: "master",
|
|
||||||
Sha: "base_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "_ObsPrj",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Head: &models.PRBranchInfo{
|
|
||||||
Sha: "head_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "_ObsPrj",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgPR := &models.PullRequest{
|
|
||||||
Index: 2,
|
|
||||||
Base: &models.PRBranchInfo{
|
|
||||||
Name: "master",
|
|
||||||
Sha: "pkg_base_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "pkg",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Head: &models.PRBranchInfo{
|
|
||||||
Name: "branch_name",
|
|
||||||
Sha: "pkg_head_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "pkg",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AllowMaintainerEdit: test.editable,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl := gomock.NewController(t)
|
|
||||||
git := mock_common.NewMockGit(ctl)
|
|
||||||
test.setup(git, prjPR, pkgPR)
|
|
||||||
|
|
||||||
prset := &common.PRSet{
|
|
||||||
Config: test.config,
|
|
||||||
PRs: []*common.PRInfo{
|
|
||||||
{PR: prjPR},
|
|
||||||
{PR: pkgPR},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := prset.PrepareForMerge(git); res != test.expected {
|
|
||||||
t.Errorf("Expected %v, got %v", test.expected, res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPRMergeMock(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest)
|
|
||||||
config *common.AutogitConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Success FF",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeFF,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin_pkg", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "merge", "--ff", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "push", "origin_pkg")
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Success Devel",
|
|
||||||
config: &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeDevel,
|
|
||||||
},
|
|
||||||
setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {
|
|
||||||
m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil)
|
|
||||||
|
|
||||||
m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "checkout", "-B", "master", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "push", "-f", "origin_pkg")
|
|
||||||
m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
prjPR := &models.PullRequest{
|
|
||||||
Index: 1,
|
|
||||||
Base: &models.PRBranchInfo{
|
|
||||||
Name: "master",
|
|
||||||
Sha: "prj_base_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "_ObsPrj",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Head: &models.PRBranchInfo{
|
|
||||||
Sha: "prj_head_sha",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pkgPR := &models.PullRequest{
|
|
||||||
Index: 2,
|
|
||||||
Base: &models.PRBranchInfo{
|
|
||||||
Name: "master",
|
|
||||||
Sha: "pkg_base_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "pkg",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Head: &models.PRBranchInfo{
|
|
||||||
Sha: "pkg_head_sha",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl := gomock.NewController(t)
|
|
||||||
git := mock_common.NewMockGit(ctl)
|
|
||||||
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
|
||||||
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
|
|
||||||
|
|
||||||
test.setup(git, prjPR, pkgPR)
|
|
||||||
|
|
||||||
prset := &common.PRSet{
|
|
||||||
Config: test.config,
|
|
||||||
PRs: []*common.PRInfo{
|
|
||||||
{PR: prjPR},
|
|
||||||
{PR: pkgPR},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := prset.Merge(reviewUnrequestMock, git); err != nil {
|
|
||||||
t.Errorf("Unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPRAddMergeCommit(t *testing.T) {
|
|
||||||
pkgPR := &models.PullRequest{
|
|
||||||
Index: 2,
|
|
||||||
Base: &models.PRBranchInfo{
|
|
||||||
Name: "master",
|
|
||||||
Sha: "pkg_base_sha",
|
|
||||||
Repo: &models.Repository{
|
|
||||||
Owner: &models.User{UserName: "org"},
|
|
||||||
Name: "pkg",
|
|
||||||
SSHURL: "ssh://git@src.opensuse.org/org/pkg.git",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Head: &models.PRBranchInfo{
|
|
||||||
Name: "branch_name",
|
|
||||||
Sha: "pkg_head_sha",
|
|
||||||
},
|
|
||||||
AllowMaintainerEdit: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &common.AutogitConfig{
|
|
||||||
Organization: "org",
|
|
||||||
GitProjectName: "org/_ObsPrj#master",
|
|
||||||
MergeMode: common.MergeModeReplace,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl := gomock.NewController(t)
|
|
||||||
git := mock_common.NewMockGit(ctl)
|
|
||||||
|
|
||||||
git.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil)
|
|
||||||
git.EXPECT().GitExecOrPanic("pkg", "read-tree", "--reset", "-u", pkgPR.Head.Sha)
|
|
||||||
git.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
|
||||||
git.EXPECT().GitExecOrPanic("pkg", "push", "origin", "HEAD:branch_name")
|
|
||||||
git.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_head_sha")
|
|
||||||
|
|
||||||
prset := &common.PRSet{
|
|
||||||
Config: config,
|
|
||||||
PRs: []*common.PRInfo{
|
|
||||||
{PR: &models.PullRequest{}}, // prjgit at index 0
|
|
||||||
{PR: pkgPR}, // pkg at index 1
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := prset.AddMergeCommit(git, "origin", 1); !res {
|
|
||||||
t.Errorf("Expected true, got %v", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,4 @@ markers =
|
|||||||
t005: Test case 005
|
t005: Test case 005
|
||||||
t006: Test case 006
|
t006: Test case 006
|
||||||
t007: Test case 007
|
t007: Test case 007
|
||||||
t008: Test case 008
|
|
||||||
t009: Test case 009
|
|
||||||
t010: Test case 010
|
|
||||||
t011: Test case 011
|
|
||||||
dependency: pytest-dependency marker
|
dependency: pytest-dependency marker
|
||||||
|
|||||||
@@ -76,10 +76,6 @@ The testing will be conducted in a dedicated test environment that mimics the pr
|
|||||||
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||||
| **TC-MERGE-006** | - | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
| **TC-MERGE-006** | - | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||||
| **TC-MERGE-007** | - | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High |
|
| **TC-MERGE-007** | - | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High |
|
||||||
| **TC-MERGE-008** | P | **MergeMode: ff-only (Success)** | 1. Set `MergeMode = "ff-only"`.<br>2. Create a FF-mergeable PackageGit PR.<br>3. Approve reviews on both PRs. | 1. Both PRs are automatically merged successfully. | High |
|
|
||||||
| **TC-MERGE-009** | P | **MergeMode: ff-only (Failure)** | 1. Set `MergeMode = "ff-only"`.<br>2. Create a PackageGit PR that adds a new file.<br>3. Commit the same file with different content to the base branch to create a content conflict.<br>4. Approve reviews and trigger a sync by pushing another change. | 1. The bot detects it is not FF-mergeable.<br>2. The PR is NOT merged. | High |
|
|
||||||
| **TC-MERGE-010** | P | **MergeMode: devel (Force-push)** | 1. Set `MergeMode = "devel"`.<br>2. Create a PackageGit PR that adds a new file.<br>3. Commit the same file with different content to the base branch to create a content conflict.<br>4. Approve reviews. | 1. Both PRs are merged.<br>2. The `pkgA` submodule points to the PR's head SHA. | High |
|
|
||||||
| **TC-MERGE-011** | P | **MergeMode: replace (Merge-commit)** | 1. Set `MergeMode = "replace"`.<br>2. Create a PackageGit PR that adds a new file.<br>3. Enable "Allow edits from maintainers" on the PR.<br>4. Commit the same file with different content to the base branch to create a content conflict.<br>5. Approve reviews. | 1. Both PRs are merged.<br>2. The project branch HEAD is a merge commit (has >1 parent).<br>3. The `pkgA` submodule points to the PR's head SHA. | High |
|
|
||||||
| **TC-CONFIG-001** | - | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High |
|
| **TC-CONFIG-001** | - | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High |
|
||||||
| **TC-LABEL-001** | P | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High |
|
| **TC-LABEL-001** | P | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High |
|
||||||
| **TC-LABEL-002** | x | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium |
|
| **TC-LABEL-002** | x | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium |
|
||||||
|
|||||||
@@ -71,21 +71,6 @@ BRANCH_CONFIG_CUSTOM = {
|
|||||||
"ReviewPending": "review/Pending"
|
"ReviewPending": "review/Pending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"merge-ff": {
|
|
||||||
"workflow.config": {
|
|
||||||
"MergeMode": "ff-only"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"merge-replace": {
|
|
||||||
"workflow.config": {
|
|
||||||
"MergeMode": "replace"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"merge-devel": {
|
|
||||||
"workflow.config": {
|
|
||||||
"MergeMode": "devel"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,18 +275,6 @@ def no_project_git_pr_env(gitea_env):
|
|||||||
def label_env(gitea_env):
|
def label_env(gitea_env):
|
||||||
return gitea_env, "myproducts/mySLFO", "label-test"
|
return gitea_env, "myproducts/mySLFO", "label-test"
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def merge_ff_env(gitea_env):
|
|
||||||
return gitea_env, "myproducts/mySLFO", "merge-ff"
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def merge_replace_env(gitea_env):
|
|
||||||
return gitea_env, "myproducts/mySLFO", "merge-replace"
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def merge_devel_env(gitea_env):
|
|
||||||
return gitea_env, "myproducts/mySLFO", "merge-devel"
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def usera_client(gitea_env):
|
def usera_client(gitea_env):
|
||||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="usera")
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="usera")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import time
|
|||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import base64
|
import base64
|
||||||
@@ -531,45 +530,3 @@ index 0000000..{pkg_b_sha}
|
|||||||
print(f"Error restarting service {service_name}: {e}")
|
print(f"Error restarting service {service_name}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def wait_for_project_pr(self, package_pr_repo, package_pr_number, project_pr_repo="myproducts/mySLFO", timeout=60):
|
|
||||||
print(f"Polling {package_pr_repo} PR #{package_pr_number} timeline for forwarded PR event in {project_pr_repo}...")
|
|
||||||
for _ in range(timeout):
|
|
||||||
time.sleep(1)
|
|
||||||
timeline_events = self.get_timeline_events(package_pr_repo, package_pr_number)
|
|
||||||
for event in timeline_events:
|
|
||||||
if event.get("type") == "pull_ref":
|
|
||||||
if not (ref_issue := event.get("ref_issue")):
|
|
||||||
continue
|
|
||||||
url_to_check = ref_issue.get("html_url", "")
|
|
||||||
match = re.search(fr"{project_pr_repo}/pulls/(\d+)", url_to_check)
|
|
||||||
if match:
|
|
||||||
return int(match.group(1))
|
|
||||||
return None
|
|
||||||
|
|
||||||
def approve_and_wait_merge(self, package_pr_repo, package_pr_number, project_pr_number, project_pr_repo="myproducts/mySLFO", timeout=30):
|
|
||||||
print(f"Approving reviews and verifying both PRs are merged ({package_pr_repo}#{package_pr_number} and {project_pr_repo}#{project_pr_number})...")
|
|
||||||
package_merged = False
|
|
||||||
project_merged = False
|
|
||||||
|
|
||||||
for i in range(timeout):
|
|
||||||
self.approve_requested_reviews(package_pr_repo, package_pr_number)
|
|
||||||
self.approve_requested_reviews(project_pr_repo, project_pr_number)
|
|
||||||
|
|
||||||
if not package_merged:
|
|
||||||
pkg_details = self.get_pr_details(package_pr_repo, package_pr_number)
|
|
||||||
if pkg_details.get("merged"):
|
|
||||||
package_merged = True
|
|
||||||
print(f"Package PR {package_pr_repo}#{package_pr_number} merged.")
|
|
||||||
|
|
||||||
if not project_merged:
|
|
||||||
prj_details = self.get_pr_details(project_pr_repo, project_pr_number)
|
|
||||||
if prj_details.get("merged"):
|
|
||||||
project_merged = True
|
|
||||||
print(f"Project PR {project_pr_repo}#{project_pr_number} merged.")
|
|
||||||
|
|
||||||
if package_merged and project_merged:
|
|
||||||
return True, True
|
|
||||||
|
|
||||||
time.sleep(1)
|
|
||||||
return package_merged, project_merged
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,27 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result):
|
|||||||
|
|
||||||
compose_dir = Path(__file__).parent.parent
|
compose_dir = Path(__file__).parent.parent
|
||||||
|
|
||||||
forwarded_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", initial_pr_number)
|
forwarded_pr_number = None
|
||||||
|
print(
|
||||||
|
f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
|
||||||
|
)
|
||||||
|
for _ in range(20):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
forwarded_pr_number = match.group(1)
|
||||||
|
break
|
||||||
|
if forwarded_pr_number:
|
||||||
|
break
|
||||||
assert (
|
assert (
|
||||||
forwarded_pr_number is not None
|
forwarded_pr_number is not None
|
||||||
), "Workflow bot did not create a project PR."
|
), "Workflow bot did not create a pull_ref event on the timeline."
|
||||||
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
|
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
|
||||||
|
|
||||||
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
|
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
|
||||||
@@ -77,10 +94,27 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result):
|
|||||||
|
|
||||||
compose_dir = Path(__file__).parent.parent
|
compose_dir = Path(__file__).parent.parent
|
||||||
|
|
||||||
forwarded_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", initial_pr_number)
|
forwarded_pr_number = None
|
||||||
|
print(
|
||||||
|
f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..."
|
||||||
|
)
|
||||||
|
for _ in range(20):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
forwarded_pr_number = match.group(1)
|
||||||
|
break
|
||||||
|
if forwarded_pr_number:
|
||||||
|
break
|
||||||
assert (
|
assert (
|
||||||
forwarded_pr_number is not None
|
forwarded_pr_number is not None
|
||||||
), "Workflow bot did not create a project PR."
|
), "Workflow bot did not create a pull_ref event on the timeline."
|
||||||
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
|
print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}")
|
||||||
|
|
||||||
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
|
print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...")
|
||||||
|
|||||||
@@ -35,7 +35,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,55 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
# 3. Approve reviews and verify merged
|
# 3. Approve reviews and verify merged
|
||||||
pkg_merged, prj_merged = gitea_env.approve_and_wait_merge("mypool/pkgA", package_pr_number, project_pr_number)
|
print("Approving reviews and verifying both PRs are merged...")
|
||||||
assert pkg_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically."
|
package_merged = False
|
||||||
assert prj_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically."
|
project_merged = False
|
||||||
|
|
||||||
|
for i in range(20): # Poll for up to 20 seconds
|
||||||
|
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
|
||||||
|
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
|
||||||
|
|
||||||
|
if not package_merged:
|
||||||
|
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
||||||
|
if pkg_details.get("merged"):
|
||||||
|
package_merged = True
|
||||||
|
print(f"Package PR mypool/pkgA#{package_pr_number} merged.")
|
||||||
|
|
||||||
|
# Project PR
|
||||||
|
if not project_merged:
|
||||||
|
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
|
||||||
|
if prj_details.get("merged"):
|
||||||
|
project_merged = True
|
||||||
|
print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.")
|
||||||
|
|
||||||
|
if package_merged and project_merged:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically."
|
||||||
|
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically."
|
||||||
print("Both PRs merged successfully.")
|
print("Both PRs merged successfully.")
|
||||||
|
|
||||||
@pytest.mark.t002
|
@pytest.mark.t002
|
||||||
@@ -57,7 +98,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
@@ -136,172 +193,3 @@ index 0000000..e69de29
|
|||||||
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged after 'merge ok'."
|
assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged after 'merge ok'."
|
||||||
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged after 'merge ok'."
|
assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged after 'merge ok'."
|
||||||
print("Both PRs merged successfully after 'merge ok'.")
|
print("Both PRs merged successfully after 'merge ok'.")
|
||||||
|
|
||||||
@pytest.mark.t008
|
|
||||||
def test_008_merge_mode_ff_only_success(merge_ff_env, test_user_client):
|
|
||||||
"""
|
|
||||||
Test MergeMode "ff-only" - Success case (FF-mergeable)
|
|
||||||
"""
|
|
||||||
gitea_env, test_full_repo_name, merge_branch_name = merge_ff_env
|
|
||||||
|
|
||||||
# 1. Create a package PR (this will be FF-mergeable by default)
|
|
||||||
diff = """diff --git a/ff_test.txt b/ff_test.txt
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..e69de29
|
|
||||||
"""
|
|
||||||
package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test FF Merge", False, base_branch=merge_branch_name)
|
|
||||||
package_pr_number = package_pr["number"]
|
|
||||||
|
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
|
||||||
assert project_pr_number is not None
|
|
||||||
|
|
||||||
pkg_merged, prj_merged = gitea_env.approve_and_wait_merge("mypool/pkgA", package_pr_number, project_pr_number)
|
|
||||||
assert pkg_merged and prj_merged
|
|
||||||
|
|
||||||
@pytest.mark.t009
|
|
||||||
def test_009_merge_mode_ff_only_failure(merge_ff_env, ownerA_client):
|
|
||||||
"""
|
|
||||||
Test MergeMode "ff-only" - Failure case (Content Conflict, should NOT merge)
|
|
||||||
"""
|
|
||||||
gitea_env, test_full_repo_name, merge_branch_name = merge_ff_env
|
|
||||||
|
|
||||||
ts = time.strftime("%H%M%S")
|
|
||||||
filename = f"ff_fail_test_{ts}.txt"
|
|
||||||
|
|
||||||
# 1. Create a package PR that adds a file
|
|
||||||
diff = f"""diff --git a/{filename} b/{filename}
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..e69de29
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/{filename}
|
|
||||||
@@ -0,0 +1 @@
|
|
||||||
+PR content
|
|
||||||
"""
|
|
||||||
package_pr = ownerA_client.create_gitea_pr("mypool/pkgA", diff, "Test FF Merge Failure (Conflict)", False, base_branch=merge_branch_name)
|
|
||||||
package_pr_number = package_pr["number"]
|
|
||||||
|
|
||||||
# 2. Wait for project PR to be created
|
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
|
||||||
assert project_pr_number is not None
|
|
||||||
|
|
||||||
print("Making PR non-FF by creating a content conflict in the base branch...")
|
|
||||||
gitea_env.create_file("mypool", "pkgA", filename, "Conflicting base content\n", branch=merge_branch_name)
|
|
||||||
|
|
||||||
print("Approving reviews initially...")
|
|
||||||
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
|
|
||||||
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
|
|
||||||
|
|
||||||
print("Pushing another change to PR branch to trigger sync...")
|
|
||||||
gitea_env.modify_gitea_pr("mypool/pkgA", package_pr_number,
|
|
||||||
"diff --git a/sync_test.txt b/sync_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n",
|
|
||||||
"Trigger Sync")
|
|
||||||
|
|
||||||
# The bot should detect it's not FF and NOT merge, and re-request reviews because of the new commit
|
|
||||||
print("Waiting for reviews to be re-requested and approving again...")
|
|
||||||
time.sleep(10) # Wait for bot to process sync
|
|
||||||
|
|
||||||
# Approve again and verify it is NOT merged
|
|
||||||
print("Approving again and verifying PR is NOT merged (because it's not FF)...")
|
|
||||||
for i in range(15):
|
|
||||||
gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number)
|
|
||||||
gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
|
||||||
assert not pkg_details.get("merged"), "Package PR merged despite NOT being FF-mergeable!"
|
|
||||||
|
|
||||||
print("FF-only failure (not merged after sync) verified.")
|
|
||||||
|
|
||||||
@pytest.mark.t010
|
|
||||||
def test_010_merge_mode_devel_success(merge_devel_env, ownerA_client):
|
|
||||||
"""
|
|
||||||
Test MergeMode "devel" - Success case (Content Conflict, should still merge via force-push)
|
|
||||||
"""
|
|
||||||
gitea_env, test_full_repo_name, merge_branch_name = merge_devel_env
|
|
||||||
|
|
||||||
ts = time.strftime("%H%M%S")
|
|
||||||
filename = f"devel_test_{ts}.txt"
|
|
||||||
|
|
||||||
# 1. Create a package PR that adds a file
|
|
||||||
diff = f"""diff --git a/{filename} b/{filename}
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..e69de29
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/{filename}
|
|
||||||
@@ -0,0 +1 @@
|
|
||||||
+PR content
|
|
||||||
"""
|
|
||||||
package_pr = ownerA_client.create_gitea_pr("mypool/pkgA", diff, "Test Devel Merge (Conflict)", False, base_branch=merge_branch_name)
|
|
||||||
package_pr_number = package_pr["number"]
|
|
||||||
|
|
||||||
# 2. Create a content conflict by committing the same file to the base branch
|
|
||||||
gitea_env.create_file("mypool", "pkgA", filename, "Conflicting base content\n", branch=merge_branch_name)
|
|
||||||
|
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
|
||||||
assert project_pr_number is not None
|
|
||||||
|
|
||||||
# Before merge, get the head sha of the package pr and project pr
|
|
||||||
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
|
||||||
pkg_head_sha = pkg_details["head"]["sha"]
|
|
||||||
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
|
|
||||||
prj_head_sha = prj_details["head"]["sha"]
|
|
||||||
|
|
||||||
pkg_merged, prj_merged = gitea_env.approve_and_wait_merge("mypool/pkgA", package_pr_number, project_pr_number)
|
|
||||||
assert pkg_merged and prj_merged
|
|
||||||
print("Devel merge (force-push) successful.")
|
|
||||||
|
|
||||||
# Verify that pkgA submodule points to the correct SHA
|
|
||||||
pkgA_submodule_info = gitea_env.get_file_info("myproducts", "mySLFO", "pkgA", branch=merge_branch_name)
|
|
||||||
assert pkgA_submodule_info["sha"] == pkg_head_sha, f"Submodule pkgA should point to {pkg_head_sha} but points to {pkgA_submodule_info['sha']}"
|
|
||||||
|
|
||||||
@pytest.mark.t011
|
|
||||||
def test_011_merge_mode_replace_success(merge_replace_env, ownerA_client):
|
|
||||||
"""
|
|
||||||
Test MergeMode "replace" - Success case (Content Conflict, bot should add merge commit)
|
|
||||||
"""
|
|
||||||
gitea_env, test_full_repo_name, merge_branch_name = merge_replace_env
|
|
||||||
|
|
||||||
ts = time.strftime("%H%M%S")
|
|
||||||
filename = f"replace_test_{ts}.txt"
|
|
||||||
|
|
||||||
# 1. Create a package PR that adds a file
|
|
||||||
diff = f"""diff --git a/{filename} b/{filename}
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..e69de29
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/{filename}
|
|
||||||
@@ -0,0 +1 @@
|
|
||||||
+PR content
|
|
||||||
"""
|
|
||||||
package_pr = ownerA_client.create_gitea_pr("mypool/pkgA", diff, "Test Replace Merge (Conflict)", False, base_branch=merge_branch_name)
|
|
||||||
package_pr_number = package_pr["number"]
|
|
||||||
|
|
||||||
# Enable "Allow edits from maintainers"
|
|
||||||
ownerA_client.update_gitea_pr_properties("mypool/pkgA", package_pr_number, allow_maintainer_edit=True)
|
|
||||||
|
|
||||||
# 2. Create a content conflict by committing the same file to the base branch
|
|
||||||
gitea_env.create_file("mypool", "pkgA", filename, "Conflicting base content\n", branch=merge_branch_name)
|
|
||||||
|
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
|
||||||
assert project_pr_number is not None
|
|
||||||
|
|
||||||
# Before merge, get the head sha of the package pr and project pr
|
|
||||||
pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number)
|
|
||||||
pkg_head_sha = pkg_details["head"]["sha"]
|
|
||||||
prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number)
|
|
||||||
prj_head_sha = prj_details["head"]["sha"]
|
|
||||||
|
|
||||||
pkg_merged, prj_merged = gitea_env.approve_and_wait_merge("mypool/pkgA", package_pr_number, project_pr_number, timeout=60)
|
|
||||||
assert pkg_merged and prj_merged
|
|
||||||
print("Replace merge successful.")
|
|
||||||
|
|
||||||
# Verify that the project branch HEAD is a merge commit
|
|
||||||
branch_info = gitea_env._request("GET", f"repos/myproducts/mySLFO/branches/{merge_branch_name}").json()
|
|
||||||
new_head_sha = branch_info["commit"]["id"]
|
|
||||||
|
|
||||||
commit_details = gitea_env._request("GET", f"repos/myproducts/mySLFO/git/commits/{new_head_sha}").json()
|
|
||||||
assert len(commit_details["parents"]) > 1, f"Project branch {merge_branch_name} HEAD should be a merge commit but has {len(commit_details['parents'])} parents"
|
|
||||||
|
|
||||||
# Verify that pkgA submodule points to the correct SHA
|
|
||||||
pkgA_submodule_info = gitea_env.get_file_info("myproducts", "mySLFO", "pkgA", branch=merge_branch_name)
|
|
||||||
assert pkgA_submodule_info["sha"] == pkg_head_sha, f"Submodule pkgA should point to {pkg_head_sha} but points to {pkgA_submodule_info['sha']}"
|
|
||||||
|
|||||||
@@ -94,7 +94,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
print(f"Created package PR mypool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
@@ -163,7 +179,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgB#{package_pr_number}")
|
print(f"Created package PR mypool/pkgB#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgB", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
@@ -285,7 +317,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created package PR mypool/pkgB#{package_pr_number}")
|
print(f"Created package PR mypool/pkgB#{package_pr_number}")
|
||||||
|
|
||||||
# 2. Make sure the workflow-pr service created related project PR
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgB", package_pr_number)
|
project_pr_number = None
|
||||||
|
print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_number = int(match.group(1))
|
||||||
|
break
|
||||||
|
if project_pr_number:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||||
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
print(f"Found project PR: myproducts/mySLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,27 @@ def test_001_project_pr(gitea_env):
|
|||||||
pytest.initial_pr_number = pytest.pr["number"]
|
pytest.initial_pr_number = pytest.pr["number"]
|
||||||
time.sleep(5) # Give Gitea some time to process the PR and make the timeline available
|
time.sleep(5) # Give Gitea some time to process the PR and make the timeline available
|
||||||
|
|
||||||
pytest.forwarded_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", pytest.initial_pr_number)
|
compose_dir = Path(__file__).parent.parent
|
||||||
|
|
||||||
|
pytest.forwarded_pr_number = None
|
||||||
|
print(
|
||||||
|
f"Polling mypool/pkgA PR #{pytest.initial_pr_number} timeline for forwarded PR event..."
|
||||||
|
)
|
||||||
|
# Instead of polling timeline, check if forwarded PR exists directly
|
||||||
|
for _ in range(20):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", pytest.initial_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
pytest.forwarded_pr_number = match.group(1)
|
||||||
|
break
|
||||||
|
if pytest.forwarded_pr_number:
|
||||||
|
break
|
||||||
assert (
|
assert (
|
||||||
pytest.forwarded_pr_number is not None
|
pytest.forwarded_pr_number is not None
|
||||||
), "Workflow bot did not create a forwarded PR."
|
), "Workflow bot did not create a forwarded PR."
|
||||||
@@ -125,9 +144,23 @@ index 0000000..e69de29
|
|||||||
print(f"Created Package PR #{package_pr_number}")
|
print(f"Created Package PR #{package_pr_number}")
|
||||||
|
|
||||||
# 2. Verify that the workflow-pr bot did not create a Project PR
|
# 2. Verify that the workflow-pr bot did not create a Project PR
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number, timeout=10)
|
project_pr_created = False
|
||||||
|
for i in range(10): # Poll for some time
|
||||||
|
time.sleep(2)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_created = True
|
||||||
|
break
|
||||||
|
if project_pr_created:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is None, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
|
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
|
||||||
print("Verification complete: No Project PR was created by the bot.")
|
print("Verification complete: No Project PR was created by the bot.")
|
||||||
|
|
||||||
# 3. Manually create the Project PR
|
# 3. Manually create the Project PR
|
||||||
@@ -219,9 +252,24 @@ index 0000000..e69de29
|
|||||||
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
||||||
|
|
||||||
# 3. Assert that the workflow-pr bot did not create a Project PR in the myproducts/mySLFO repository
|
# 3. Assert that the workflow-pr bot did not create a Project PR in the myproducts/mySLFO repository
|
||||||
project_pr_number = gitea_env.wait_for_project_pr("mypool/pkgA", package_pr_number, timeout=10)
|
project_pr_created = False
|
||||||
|
for i in range(20): # Poll for a reasonable time
|
||||||
|
time.sleep(2) # Wait a bit longer to be sure
|
||||||
|
timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number)
|
||||||
|
for event in timeline_events:
|
||||||
|
if event.get("type") == "pull_ref":
|
||||||
|
if not (ref_issue := event.get("ref_issue")):
|
||||||
|
continue
|
||||||
|
url_to_check = ref_issue.get("html_url", "")
|
||||||
|
# Regex now searches for myproducts/mySLFO/pulls/(\d+)
|
||||||
|
match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_created = True
|
||||||
|
break
|
||||||
|
if project_pr_created:
|
||||||
|
break
|
||||||
|
|
||||||
assert project_pr_number is None, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
|
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO."
|
||||||
print("Verification complete: No Project PR was created in myproducts/mySLFO as expected.")
|
print("Verification complete: No Project PR was created in myproducts/mySLFO as expected.")
|
||||||
|
|
||||||
# 1. Create that Project PR from the test code.
|
# 1. Create that Project PR from the test code.
|
||||||
@@ -274,3 +322,5 @@ index 0000000..f587a12
|
|||||||
|
|
||||||
assert project_pr_updated, "Manually created Project PR was not updated by the bot."
|
assert project_pr_updated, "Manually created Project PR was not updated by the bot."
|
||||||
print("Verification complete: Manually created Project PR was updated by the bot as expected.")
|
print("Verification complete: Manually created Project PR was updated by the bot as expected.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,5 @@
|
|||||||
"myproducts/mySLFO#maintainer-merge",
|
"myproducts/mySLFO#maintainer-merge",
|
||||||
"myproducts/mySLFO#review-required",
|
"myproducts/mySLFO#review-required",
|
||||||
"myproducts/mySLFO#label-test",
|
"myproducts/mySLFO#label-test",
|
||||||
"myproducts/mySLFO#manual-merge",
|
"myproducts/mySLFO#manual-merge"
|
||||||
"myproducts/mySLFO#merge-ff",
|
|
||||||
"myproducts/mySLFO#merge-replace",
|
|
||||||
"myproducts/mySLFO#merge-devel"
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -96,19 +96,6 @@ Package Deletion Requests
|
|||||||
If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool.
|
If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool.
|
||||||
|
|
||||||
|
|
||||||
Merge Modes
|
|
||||||
-----------
|
|
||||||
|
|
||||||
| Merge Mode | Description
|
|
||||||
|------------|--------------------------------------------------------------------------------
|
|
||||||
| ff-only | Only allow --ff-only merges in the package branch. This is best suited for
|
|
||||||
| | devel projects and openSUSE Tumbleweed development, where history should be linear
|
|
||||||
| replace | Merge is done via `-X theirs` strategy and old files are removed in the merge.
|
|
||||||
| | This works well for downstream codestreams, like Leap, that would update their branch
|
|
||||||
| | using latest version.
|
|
||||||
| devel | No merge, just set the project branch to PR HEAD. This is suitable for downstream
|
|
||||||
| | projects like Leap during development cycle, where keeping maintenance history is not important
|
|
||||||
|
|
||||||
Labels
|
Labels
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@@ -417,12 +417,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
}
|
}
|
||||||
common.LogInfo("fetched PRSet of size:", len(prset.PRs))
|
common.LogInfo("fetched PRSet of size:", len(prset.PRs))
|
||||||
|
|
||||||
if !prset.PrepareForMerge(git) {
|
|
||||||
common.LogError("PRs are NOT mergeable.")
|
|
||||||
} else {
|
|
||||||
common.LogInfo("PRs are in mergeable state.")
|
|
||||||
}
|
|
||||||
|
|
||||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||||
prjGitPR, err := prset.GetPrjGitPR()
|
prjGitPR, err := prset.GetPrjGitPR()
|
||||||
if err == common.PRSet_PrjGitMissing {
|
if err == common.PRSet_PrjGitMissing {
|
||||||
@@ -476,7 +470,8 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
if pr.PR.State == "open" {
|
if pr.PR.State == "open" {
|
||||||
org, repo, idx := pr.PRComponents()
|
org, repo, idx := pr.PRComponents()
|
||||||
if prjGitPR.PR.HasMerged {
|
if prjGitPR.PR.HasMerged {
|
||||||
Gitea.AddComment(pr.PR, "This PR is merged via the associated Project PR.")
|
// TODO: use timeline here because this can spam if ManualMergePR fails
|
||||||
|
// Gitea.AddComment(pr.PR, "This PR is merged via the associated Project PR.")
|
||||||
err = Gitea.ManualMergePR(org, repo, idx, pr.PR.Head.Sha, false)
|
err = Gitea.ManualMergePR(org, repo, idx, pr.PR.Head.Sha, false)
|
||||||
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
|
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user