Compare commits
3 Commits
mergemodes
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
3e1b3c5c84 | ||
|
|
fc4899b75a | ||
| 0b479bcbfa |
@@ -181,8 +181,10 @@ install -D -m0755 obs-status-service/obs-status-service
|
|||||||
install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service
|
install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service
|
||||||
install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
|
install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
|
||||||
install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
|
install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
|
||||||
|
install -D -m0644 systemd/workflow-direct.target %{buildroot}%{_unitdir}/workflow-direct.target
|
||||||
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
||||||
install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service
|
install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service
|
||||||
|
install -D -m0644 systemd/workflow-pr.target %{buildroot}%{_unitdir}/workflow-pr.target
|
||||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||||
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
|
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
|
||||||
|
|
||||||
@@ -235,28 +237,28 @@ install -D -m0755 utils/maintainer-update/maintainer-update
|
|||||||
%service_del_postun obs-status-service.service
|
%service_del_postun obs-status-service.service
|
||||||
|
|
||||||
%pre workflow-direct
|
%pre workflow-direct
|
||||||
%service_add_pre workflow-direct.service
|
%service_add_pre workflow-direct.target
|
||||||
|
|
||||||
%post workflow-direct
|
%post workflow-direct
|
||||||
%service_add_post workflow-direct.service
|
%service_add_post workflow-direct.target
|
||||||
|
|
||||||
%preun workflow-direct
|
%preun workflow-direct
|
||||||
%service_del_preun workflow-direct.service
|
%service_del_preun workflow-direct.target
|
||||||
|
|
||||||
%postun workflow-direct
|
%postun workflow-direct
|
||||||
%service_del_postun workflow-direct.service
|
%service_del_postun workflow-direct.target
|
||||||
|
|
||||||
%pre workflow-pr
|
%pre workflow-pr
|
||||||
%service_add_pre workflow-pr.service
|
%service_add_pre workflow-pr.target
|
||||||
|
|
||||||
%post workflow-pr
|
%post workflow-pr
|
||||||
%service_add_post workflow-pr.service
|
%service_add_post workflow-pr.target
|
||||||
|
|
||||||
%preun workflow-pr
|
%preun workflow-pr
|
||||||
%service_del_preun workflow-pr.service
|
%service_del_preun workflow-pr.target
|
||||||
|
|
||||||
%postun workflow-pr
|
%postun workflow-pr
|
||||||
%service_del_postun workflow-pr.service
|
%service_del_postun workflow-pr.target
|
||||||
|
|
||||||
%files devel-importer
|
%files devel-importer
|
||||||
%license COPYING
|
%license COPYING
|
||||||
@@ -310,10 +312,12 @@ install -D -m0755 utils/maintainer-update/maintainer-update
|
|||||||
%doc workflow-direct/README.md
|
%doc workflow-direct/README.md
|
||||||
%{_bindir}/workflow-direct
|
%{_bindir}/workflow-direct
|
||||||
%{_unitdir}/workflow-direct@.service
|
%{_unitdir}/workflow-direct@.service
|
||||||
|
%{_unitdir}/workflow-direct.target
|
||||||
|
|
||||||
%files workflow-pr
|
%files workflow-pr
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-pr/README.md
|
%doc workflow-pr/README.md
|
||||||
%{_bindir}/workflow-pr
|
%{_bindir}/workflow-pr
|
||||||
%{_unitdir}/workflow-pr@.service
|
%{_unitdir}/workflow-pr@.service
|
||||||
|
%{_unitdir}/workflow-pr.target
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +88,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
|
||||||
@@ -188,17 +183,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,67 +342,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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
154
common/pr.go
154
common/pr.go
@@ -552,145 +552,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", "-m", head.Sha)
|
|
||||||
git.GitExecOrPanic(repo.Name, "commit", "-m", msg)
|
|
||||||
git.GitExecOrPanic(repo.Name, "clean", "-fxd")
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -829,12 +690,8 @@ 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)
|
||||||
if rs.Config.MergeMode == MergeModeDevel {
|
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
||||||
git.GitExecOrPanic(repo.Name, "checkout", "-B", br, head.Sha)
|
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
||||||
} else {
|
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
|
||||||
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,12 +708,7 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
repo := prinfo.PR.Base.Repo
|
repo := prinfo.PR.Base.Repo
|
||||||
|
|
||||||
if !IsDryRun {
|
if !IsDryRun {
|
||||||
params := []string{"push"}
|
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
|
||||||
if rs.Config.MergeMode == MergeModeDevel {
|
|
||||||
params = append(params, "-f")
|
|
||||||
}
|
|
||||||
params = append(params, 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"
|
||||||
@@ -1229,7 +1228,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{
|
||||||
@@ -1249,7 +1248,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{
|
||||||
@@ -1339,346 +1338,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", "-m", pkgPR.Head.Sha)
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
|
||||||
m.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd")
|
|
||||||
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", "-m", pkgPR.Head.Sha)
|
|
||||||
git.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
|
|
||||||
git.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd")
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
# 3. 'pytest -v tests/*' - run tests
|
# 3. 'pytest -v tests/*' - run tests
|
||||||
# 4. 'make down' - once the containers are not needed
|
# 4. 'make down' - once the containers are not needed
|
||||||
# B2: (make sure the go binaries in the parent folder are built)
|
# B2: (make sure the go binaries in the parent folder are built)
|
||||||
# 4. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`)
|
# 1. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`)
|
||||||
# 5. 'make up' - spawns podman-compose
|
# 2. 'make up' - spawns podman-compose
|
||||||
# 6. 'pytest -v tests/*' - run tests
|
# 3. 'pytest -v tests/*' - run tests
|
||||||
# 7. 'make down' - once the containers are not needed
|
# 4. 'make down' - once the containers are not needed
|
||||||
|
|
||||||
|
|
||||||
AUTO_DETECT_MODE := $(shell if test -e ../workflow-pr/workflow-pr; then echo .local; else echo .package; fi)
|
AUTO_DETECT_MODE := $(shell if test -e ../workflow-pr/workflow-pr; then echo .local; else echo .package; fi)
|
||||||
|
|||||||
57
integration/Makefile.txt
Normal file
57
integration/Makefile.txt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
|
| Makefile Targets |
|
||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| [Default Test Workflow] |
|
||||||
|
| test (Auto-detects mode: .local or .package) |
|
||||||
|
| └─> build_container |
|
||||||
|
| └─> test_container |
|
||||||
|
| |
|
||||||
|
| [Specific Test Workflows - Topology 1: Privileged Container] |
|
||||||
|
| test_package (Mode A1: Bots from official packages) |
|
||||||
|
| └─> build_container |
|
||||||
|
| └─> test_container |
|
||||||
|
| |
|
||||||
|
| test_local (Mode B1: Bots from local binaries) |
|
||||||
|
| └─> build_container |
|
||||||
|
| └─> test_container |
|
||||||
|
| |
|
||||||
|
| build_container |
|
||||||
|
| - Action: Builds the `autogits_integration` privileged container image. |
|
||||||
|
| - Purpose: Prepares an environment for running tests within a single container. |
|
||||||
|
| |
|
||||||
|
| test_container |
|
||||||
|
| - Action: Runs `autogits_integration` container, executes `make build`, `make up`, and |
|
||||||
|
| `pytest -v tests/*` inside it. |
|
||||||
|
| - Purpose: Executes the full test suite in Topology 1 (privileged container). |
|
||||||
|
| |
|
||||||
|
| [Build & Orchestration Workflows - Topology 2: podman-compose] |
|
||||||
|
| |
|
||||||
|
| build_package (Mode A: Builds service images from official packages) |
|
||||||
|
| └─> build |
|
||||||
|
| |
|
||||||
|
| build_local (Mode B: Builds service images from local binaries) |
|
||||||
|
| └─> build |
|
||||||
|
| |
|
||||||
|
| build |
|
||||||
|
| - Action: Pulls `rabbitmq` image and iterates through `podman-compose.yml` services |
|
||||||
|
| to build each one. |
|
||||||
|
| - Purpose: Prepares all necessary service images for Topology 2 deployment. |
|
||||||
|
| |
|
||||||
|
| up |
|
||||||
|
| - Action: Starts all services defined in `podman-compose.yml` in detached mode. |
|
||||||
|
| - Purpose: Deploys the application topology (containers) for testing or development. |
|
||||||
|
| |
|
||||||
|
| down |
|
||||||
|
| - Action: Stops and removes all services started by `up`. |
|
||||||
|
| - Purpose: Cleans up the deployed application topology. |
|
||||||
|
| |
|
||||||
|
| up-bots-package (Mode A: Spawns Topology 2 with official package bots) |
|
||||||
|
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.package`. |
|
||||||
|
| - Purpose: Specifically brings up the environment using official package bots. |
|
||||||
|
| |
|
||||||
|
| up-bots-local (Mode B: Spawns Topology 2 with local binaries) |
|
||||||
|
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.local`. |
|
||||||
|
| - Purpose: Specifically brings up the environment using local binaries. |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
77
integration/podman-compose.txt
Normal file
77
integration/podman-compose.txt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
|
| Podman-Compose Services Diagram |
|
||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| [Network] |
|
||||||
|
| gitea-network (Bridge network for inter-service communication) |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: gitea] |
|
||||||
|
| Description: Self-hosted Git service, central hub for repositories and code management. |
|
||||||
|
| Container Name: gitea-test |
|
||||||
|
| Image: Built from ./gitea Dockerfile |
|
||||||
|
| Ports: 3000 (HTTP), 3022 (SSH) |
|
||||||
|
| Volumes: ./gitea-data (for persistent data), ./gitea-logs (for logs) |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: rabbitmq] |
|
||||||
|
| Description: Message broker for asynchronous communication between services. |
|
||||||
|
| Container Name: rabbitmq-test |
|
||||||
|
| Image: rabbitmq:3.13.7-management |
|
||||||
|
| Ports: 5671 (AMQP), 15672 (Management UI) |
|
||||||
|
| Volumes: ./rabbitmq-data (for persistent data), ./rabbitmq-config/certs (TLS certs), |
|
||||||
|
| ./rabbitmq-config/rabbitmq.conf (config), ./rabbitmq-config/definitions.json (exchanges)|
|
||||||
|
| Healthcheck: Ensures RabbitMQ is running and healthy. |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: gitea-publisher] |
|
||||||
|
| Description: Publishes events from Gitea to the RabbitMQ message queue. |
|
||||||
|
| Container Name: gitea-publisher |
|
||||||
|
| Image: Built from ../gitea-events-rabbitmq-publisher/Dockerfile (local/package) |
|
||||||
|
| Dependencies: gitea (started), rabbitmq (healthy) |
|
||||||
|
| Environment: RABBITMQ_HOST, RABBITMQ_USERNAME, RABBITMQ_PASSWORD, SSL_CERT_FILE |
|
||||||
|
| Command: Listens for Gitea events, publishes to 'suse' topic, debug enabled. |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: workflow-pr] |
|
||||||
|
| Description: Manages pull request workflows, likely consuming events from RabbitMQ and |
|
||||||
|
| interacting with Gitea. |
|
||||||
|
| Container Name: workflow-pr |
|
||||||
|
| Image: Built from ../workflow-pr/Dockerfile (local/package) |
|
||||||
|
| Dependencies: gitea (started), rabbitmq (healthy) |
|
||||||
|
| Environment: AMQP_USERNAME, AMQP_PASSWORD, SSL_CERT_FILE |
|
||||||
|
| Volumes: ./gitea-data (read-only), ./workflow-pr/workflow-pr.json (config), |
|
||||||
|
| ./workflow-pr-repos (for repositories) |
|
||||||
|
| Command: Configures Gitea/RabbitMQ URLs, enables debug, manages repositories. |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: mock-obs] |
|
||||||
|
| Description: A mock (simulated) service for the Open Build Service (OBS) for testing. |
|
||||||
|
| Container Name: mock-obs |
|
||||||
|
| Image: Built from ./mock-obs Dockerfile |
|
||||||
|
| Ports: 8080 |
|
||||||
|
| Volumes: ./mock-obs/responses (for mock API responses) |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
|-------------------------------------------------------------------------------------------------|
|
||||||
|
| |
|
||||||
|
| [Service: obs-staging-bot] |
|
||||||
|
| Description: A bot that interacts with Gitea and the mock OBS, likely for staging processes. |
|
||||||
|
| Container Name: obs-staging-bot |
|
||||||
|
| Image: Built from ../obs-staging-bot/Dockerfile (local/package) |
|
||||||
|
| Dependencies: gitea (started), mock-obs (started) |
|
||||||
|
| Environment: OBS_USER, OBS_PASSWORD |
|
||||||
|
| Volumes: ./gitea-data (read-only) |
|
||||||
|
| Command: Configures Gitea/OBS URLs, enables debug. |
|
||||||
|
| Network: gitea-network |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------------------------------------------------+
|
||||||
10
integration/pytest.ini
Normal file
10
integration/pytest.ini
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[pytest]
|
||||||
|
markers =
|
||||||
|
t001: Test case 001
|
||||||
|
t002: Test case 002
|
||||||
|
t003: Test case 003
|
||||||
|
t004: Test case 004
|
||||||
|
t005: Test case 005
|
||||||
|
t006: Test case 006
|
||||||
|
t007: Test case 007
|
||||||
|
dependency: pytest-dependency marker
|
||||||
@@ -31,7 +31,7 @@ def gitea_env():
|
|||||||
|
|
||||||
# Wait for Gitea to be available
|
# Wait for Gitea to be available
|
||||||
print(f"Waiting for Gitea at {gitea_url}...")
|
print(f"Waiting for Gitea at {gitea_url}...")
|
||||||
max_retries = 30
|
max_retries = 5
|
||||||
for i in range(max_retries):
|
for i in range(max_retries):
|
||||||
try:
|
try:
|
||||||
# Check a specific API endpoint that indicates readiness
|
# Check a specific API endpoint that indicates readiness
|
||||||
@@ -41,8 +41,8 @@ def gitea_env():
|
|||||||
break
|
break
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
pass
|
pass
|
||||||
print(f"Gitea not ready ({response.status_code if 'response' in locals() else 'ConnectionError'}), retrying in 5 seconds... ({i+1}/{max_retries})")
|
print(f"Gitea not ready ({response.status_code if 'response' in locals() else 'ConnectionError'}), retrying in 1 seconds... ({i+1}/{max_retries})")
|
||||||
time.sleep(5)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
raise Exception("Gitea did not become available within the expected time.")
|
raise Exception("Gitea did not become available within the expected time.")
|
||||||
|
|
||||||
@@ -59,6 +59,23 @@ def gitea_env():
|
|||||||
|
|
||||||
# The add_submodules method also creates workflow.config and staging.config
|
# The add_submodules method also creates workflow.config and staging.config
|
||||||
client.add_submodules("products", "SLFO")
|
client.add_submodules("products", "SLFO")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
workflow_config_content = """{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "products/SLFO#main",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "main",
|
||||||
|
"ManualMergeProject": true,
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||||
|
}"""
|
||||||
|
client.create_file("products", "SLFO", "workflow.config", workflow_config_content)
|
||||||
|
|
||||||
|
staging_config_content = """{
|
||||||
|
"ObsProject": "openSUSE:Leap:16.0",
|
||||||
|
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||||
|
}"""
|
||||||
|
client.create_file("products", "SLFO", "staging.config", staging_config_content)
|
||||||
|
|
||||||
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
||||||
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
|
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
|
||||||
@@ -69,10 +86,635 @@ def gitea_env():
|
|||||||
client.update_repo_settings("pool", "pkgA")
|
client.update_repo_settings("pool", "pkgA")
|
||||||
client.update_repo_settings("pool", "pkgB")
|
client.update_repo_settings("pool", "pkgB")
|
||||||
print("--- Gitea Dummy Data Setup Complete ---")
|
print("--- Gitea Dummy Data Setup Complete ---")
|
||||||
time.sleep(5) # Add a small delay for Gitea to fully process changes
|
time.sleep(1) # Give workflow-pr bot time to become fully active
|
||||||
|
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def configured_dev_branch_env(gitea_env: GiteaAPIClient, request):
|
||||||
|
"""
|
||||||
|
Fixture to set up a 'dev' branch in products/SLFO and pool/pkgA,
|
||||||
|
and configure workflow.config in products/SLFO#dev with specific content.
|
||||||
|
Yields (gitea_env, test_full_repo_name, dev_branch_name).
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
dev_branch_name = "dev"
|
||||||
|
|
||||||
|
workflow_config_content = request.param # Get config content from parametrization
|
||||||
|
|
||||||
|
print(f"--- Setting up 'dev' branch and workflow.config in {test_full_repo_name}#{dev_branch_name} ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create 'dev' branch from 'main' in products/SLFO
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgA as well
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgB as well
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||||
|
|
||||||
|
# Create/update workflow.config with the provided content
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||||
|
print(f"Created workflow.config with specific content in {test_full_repo_name}#{dev_branch_name}")
|
||||||
|
|
||||||
|
# Restart workflow-pr service to pick up new project config
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1) # Give the service time to restart and re-initialize
|
||||||
|
|
||||||
|
yield gitea_env, test_full_repo_name, dev_branch_name
|
||||||
|
|
||||||
|
|
||||||
# Teardown (optional, depending on test strategy)
|
# Teardown (optional, depending on test strategy)
|
||||||
# For now, we'll leave resources for inspection. If a clean slate is needed for each test,
|
# For now, we'll leave resources for inspection. If a clean slate is needed for each test,
|
||||||
# this fixture's scope would be 'function' and teardown logic would be added here.
|
# this fixture's scope would be 'function' and teardown logic would be added here.
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def no_project_git_pr_env(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Sets up 'dev' branch in products/SLFO and pool/pkgA,
|
||||||
|
and configures workflow.config in products/SLFO#dev with NoProjectGitPR: true.
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
dev_branch_name = "dev"
|
||||||
|
|
||||||
|
print(f"--- Setting up workflow.config in {test_full_repo_name}#{dev_branch_name} for No Project PR ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create 'dev' branch from 'main' in products/SLFO
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgA as well
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgB as well
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Setup workflow.config to have "NoProjectGitPR": true
|
||||||
|
workflow_config_content_no_project_pr = f"""{{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "{test_full_repo_name}#{dev_branch_name}",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "dev",
|
||||||
|
"ManualMergeProject": true,
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||||
|
"NoProjectGitPR": true
|
||||||
|
}}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content_no_project_pr, branch=dev_branch_name)
|
||||||
|
print(f"Created workflow.config with NoProjectGitPR: true in {test_full_repo_name}#{dev_branch_name}")
|
||||||
|
|
||||||
|
# Restart workflow-pr service
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1) # Give the service time to restart and re-initialize
|
||||||
|
|
||||||
|
return gitea_env, test_full_repo_name, dev_branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def test_user_client(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Creates a new unique user and returns a GiteaAPIClient instance for them using sudo.
|
||||||
|
This user should not have write permissions to the test repositories by default.
|
||||||
|
"""
|
||||||
|
username = f"user-{int(time.time())}"
|
||||||
|
password = "password123"
|
||||||
|
email = f"{username}@example.com"
|
||||||
|
|
||||||
|
gitea_env.create_user(username, password, email)
|
||||||
|
|
||||||
|
# Grant write access to pool/pkgA
|
||||||
|
gitea_env.add_collaborator("pool", "pkgA", username, "write")
|
||||||
|
|
||||||
|
# Use admin token with Sudo header
|
||||||
|
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||||
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo=username)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_users_from_config(client: GiteaAPIClient, workflow_config: str, maintainership_config: str):
|
||||||
|
"""
|
||||||
|
Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
wf = json.loads(workflow_config)
|
||||||
|
mt = json.loads(maintainership_config)
|
||||||
|
|
||||||
|
all_users = set()
|
||||||
|
|
||||||
|
# Extract from workflow.config Reviewers
|
||||||
|
reviewers = wf.get("Reviewers", [])
|
||||||
|
for r in reviewers:
|
||||||
|
# Strip +, - prefixes
|
||||||
|
username = r.lstrip("+-")
|
||||||
|
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
|
||||||
|
all_users.add(username)
|
||||||
|
|
||||||
|
# Extract from maintainership
|
||||||
|
for pkg, users in mt.items():
|
||||||
|
for username in users:
|
||||||
|
all_users.add(username)
|
||||||
|
|
||||||
|
# Create all users
|
||||||
|
for username in all_users:
|
||||||
|
client.create_user(username, "password123", f"{username}@example.com")
|
||||||
|
# Global maintainers (empty key) get write access to everything
|
||||||
|
# Actually, let's just make them collaborators on SLFO, pkgA, pkgB for simplicity in tests
|
||||||
|
client.add_collaborator("products", "SLFO", username, "write")
|
||||||
|
|
||||||
|
# Set specific repository permissions based on maintainership
|
||||||
|
for pkg, users in mt.items():
|
||||||
|
repo_name = pkg if pkg else None
|
||||||
|
for username in users:
|
||||||
|
if not repo_name:
|
||||||
|
# Global maintainer - already added to SLFO, add to pkgA/pkgB
|
||||||
|
client.add_collaborator("pool", "pkgA", username, "write")
|
||||||
|
client.add_collaborator("pool", "pkgB", username, "write")
|
||||||
|
else:
|
||||||
|
client.add_collaborator("pool", repo_name, username, "write")
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def gitea_env():
|
||||||
|
"""
|
||||||
|
Sets up the Gitea environment with dummy data and provides a GiteaAPIClient instance.
|
||||||
|
"""
|
||||||
|
gitea_url = "http://127.0.0.1:3000"
|
||||||
|
|
||||||
|
# Read admin token
|
||||||
|
admin_token_path = "./gitea-data/admin.token" # Corrected path
|
||||||
|
admin_token = None
|
||||||
|
try:
|
||||||
|
with open(admin_token_path, "r") as f:
|
||||||
|
admin_token = f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(f"Admin token file not found at {admin_token_path}. Ensure it's generated and accessible.")
|
||||||
|
|
||||||
|
# Headers for authenticated requests
|
||||||
|
auth_headers = {"Authorization": f"token {admin_token}", "Content-Type": "application/json"}
|
||||||
|
|
||||||
|
# Wait for Gitea to be available
|
||||||
|
print(f"Waiting for Gitea at {gitea_url}...")
|
||||||
|
max_retries = 5
|
||||||
|
for i in range(max_retries):
|
||||||
|
try:
|
||||||
|
# Check a specific API endpoint that indicates readiness
|
||||||
|
response = requests.get(f"{gitea_url}/api/v1/version", headers=auth_headers, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("Gitea API is available.")
|
||||||
|
break
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
pass
|
||||||
|
print(f"Gitea not ready ({response.status_code if 'response' in locals() else 'ConnectionError'}), retrying in 1 seconds... ({i+1}/{max_retries})")
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
raise Exception("Gitea did not become available within the expected time.")
|
||||||
|
|
||||||
|
client = GiteaAPIClient(base_url=gitea_url, token=admin_token)
|
||||||
|
|
||||||
|
# Setup dummy data
|
||||||
|
print("--- Starting Gitea Dummy Data Setup from Pytest Fixture ---")
|
||||||
|
client.create_org("products")
|
||||||
|
client.create_org("pool")
|
||||||
|
|
||||||
|
client.create_repo("products", "SLFO")
|
||||||
|
client.create_repo("pool", "pkgA")
|
||||||
|
client.create_repo("pool", "pkgB")
|
||||||
|
|
||||||
|
# The add_submodules method also creates workflow.config and staging.config
|
||||||
|
client.add_submodules("products", "SLFO")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
workflow_config_content = """{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "products/SLFO#main",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "main",
|
||||||
|
"ManualMergeProject": true,
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||||
|
}"""
|
||||||
|
client.create_file("products", "SLFO", "workflow.config", workflow_config_content)
|
||||||
|
|
||||||
|
staging_config_content = """{
|
||||||
|
"ObsProject": "openSUSE:Leap:16.0",
|
||||||
|
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||||
|
}"""
|
||||||
|
client.create_file("products", "SLFO", "staging.config", staging_config_content)
|
||||||
|
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
# Create users from default main config
|
||||||
|
setup_users_from_config(client, workflow_config_content, maintainership_content)
|
||||||
|
|
||||||
|
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
||||||
|
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
|
||||||
|
client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
|
||||||
|
client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
|
||||||
|
|
||||||
|
client.update_repo_settings("products", "SLFO")
|
||||||
|
client.update_repo_settings("pool", "pkgA")
|
||||||
|
client.update_repo_settings("pool", "pkgB")
|
||||||
|
print("--- Gitea Dummy Data Setup Complete ---")
|
||||||
|
time.sleep(1) # Give workflow-pr bot time to become fully active
|
||||||
|
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def configured_dev_branch_env(gitea_env: GiteaAPIClient, request):
|
||||||
|
"""
|
||||||
|
Fixture to set up a 'dev' branch in products/SLFO and pool/pkgA,
|
||||||
|
and configure workflow.config in products/SLFO#dev with specific content.
|
||||||
|
Yields (gitea_env, test_full_repo_name, dev_branch_name).
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
dev_branch_name = "dev"
|
||||||
|
|
||||||
|
workflow_config_content = request.param # Get config content from parametrization
|
||||||
|
|
||||||
|
print(f"--- Setting up 'dev' branch and workflow.config in {test_full_repo_name}#{dev_branch_name} ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create 'dev' branch from 'main' in products/SLFO
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgA as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgB as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||||
|
|
||||||
|
# Create/update workflow.config with the provided content
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||||
|
|
||||||
|
# For this fixture, we use default maintainership as we don't receive it in request.param
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
setup_users_from_config(gitea_env, workflow_config_content, maintainership_content)
|
||||||
|
|
||||||
|
print(f"Created workflow.config with specific content in {test_full_repo_name}#{dev_branch_name}")
|
||||||
|
|
||||||
|
# Restart workflow-pr service to pick up new project config
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1) # Give the service time to restart and re-initialize
|
||||||
|
|
||||||
|
yield gitea_env, test_full_repo_name, dev_branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def no_project_git_pr_env(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Sets up 'dev' branch in products/SLFO and pool/pkgA,
|
||||||
|
and configures workflow.config in products/SLFO#dev with NoProjectGitPR: true.
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
dev_branch_name = "dev"
|
||||||
|
|
||||||
|
print(f"--- Setting up workflow.config in {test_full_repo_name}#{dev_branch_name} for No Project PR ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create 'dev' branch from 'main' in products/SLFO
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgA as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'dev' branch in pool/pkgB as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Setup workflow.config to have "NoProjectGitPR": true
|
||||||
|
workflow_config_content = f"""{{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "{test_full_repo_name}#{dev_branch_name}",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "dev",
|
||||||
|
"ManualMergeProject": true,
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||||
|
"NoProjectGitPR": true
|
||||||
|
}}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||||
|
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
setup_users_from_config(gitea_env, workflow_config_content, maintainership_content)
|
||||||
|
|
||||||
|
print(f"Created workflow.config with NoProjectGitPR: true in {test_full_repo_name}#{dev_branch_name}")
|
||||||
|
|
||||||
|
# Restart workflow-pr service
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1) # Give the service time to restart and re-initialize
|
||||||
|
|
||||||
|
return gitea_env, test_full_repo_name, dev_branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def test_user_client(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Creates a new unique user and returns a GiteaAPIClient instance for them using sudo.
|
||||||
|
This user should not have write permissions to the test repositories by default.
|
||||||
|
"""
|
||||||
|
username = f"user-{int(time.time())}"
|
||||||
|
password = "password123"
|
||||||
|
email = f"{username}@example.com"
|
||||||
|
|
||||||
|
gitea_env.create_user(username, password, email)
|
||||||
|
|
||||||
|
# Grant write access to pool/pkgA
|
||||||
|
gitea_env.add_collaborator("pool", "pkgA", username, "write")
|
||||||
|
|
||||||
|
# Use admin token with Sudo header
|
||||||
|
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||||
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo=username)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def automerge_env(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Sets up 'merge' branch and custom workflow.config for automerge tests.
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
merge_branch_name = "merge"
|
||||||
|
|
||||||
|
print(f"--- Setting up '{merge_branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create 'merge' branch from 'main' in products/SLFO
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, merge_branch_name, main_branch_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'merge' branch in pool/pkgA as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgA", merge_branch_name, pool_pkga_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create 'merge' branch in pool/pkgB as well
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgB", merge_branch_name, pool_pkgb_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
custom_workflow_config = f"""{{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "{test_full_repo_name}#{merge_branch_name}",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "{merge_branch_name}",
|
||||||
|
"Reviewers": [ "+usera", "+userb", "-autogits_obs_staging_bot" ]
|
||||||
|
}}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=merge_branch_name)
|
||||||
|
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=merge_branch_name)
|
||||||
|
|
||||||
|
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||||
|
|
||||||
|
# Restart workflow-pr service
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return gitea_env, test_full_repo_name, merge_branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def maintainer_env(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Sets up 'maintainer-merge' branch and workflow.config without mandatory reviewers.
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
branch_name = "maintainer-merge"
|
||||||
|
|
||||||
|
print(f"--- Setting up '{branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create branch in products/SLFO
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, branch_name, main_branch_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create branch in pool/pkgA
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgA", branch_name, pool_pkga_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create branch in pool/pkgB
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgB", branch_name, pool_pkgb_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
custom_workflow_config = f"""{{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "{test_full_repo_name}#{branch_name}",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "{branch_name}",
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||||
|
}}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=branch_name)
|
||||||
|
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=branch_name)
|
||||||
|
|
||||||
|
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||||
|
|
||||||
|
gitea_env.add_collaborator(test_org_name, test_repo_name, "autogits_obs_staging_bot", "write")
|
||||||
|
|
||||||
|
# Restart workflow-pr service
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return gitea_env, test_full_repo_name, branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def review_required_env(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Sets up 'review-required' branch and workflow.config with ReviewRequired: true.
|
||||||
|
"""
|
||||||
|
test_org_name = "products"
|
||||||
|
test_repo_name = "SLFO"
|
||||||
|
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||||
|
branch_name = "review-required"
|
||||||
|
|
||||||
|
print(f"--- Setting up '{branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||||
|
|
||||||
|
# Get the latest commit SHA of the main branch
|
||||||
|
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||||
|
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Create branch in products/SLFO
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch(test_org_name, test_repo_name, branch_name, main_branch_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create branch in pool/pkgA
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||||
|
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgA", branch_name, pool_pkga_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create branch in pool/pkgB
|
||||||
|
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||||
|
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||||
|
try:
|
||||||
|
gitea_env.create_branch("pool", "pkgB", branch_name, pool_pkgb_main_sha)
|
||||||
|
except Exception as e:
|
||||||
|
if "already exists" not in str(e).lower():
|
||||||
|
raise
|
||||||
|
|
||||||
|
custom_workflow_config = f"""{{
|
||||||
|
"Workflows": ["pr"],
|
||||||
|
"GitProjectName": "{test_full_repo_name}#{branch_name}",
|
||||||
|
"Organization": "pool",
|
||||||
|
"Branch": "{branch_name}",
|
||||||
|
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||||
|
"ReviewRequired": true
|
||||||
|
}}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=branch_name)
|
||||||
|
|
||||||
|
maintainership_content = """{
|
||||||
|
"": ["ownerX","ownerY"],
|
||||||
|
"pkgA": ["ownerA"],
|
||||||
|
"pkgB": ["ownerB","ownerBB"]
|
||||||
|
}"""
|
||||||
|
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=branch_name)
|
||||||
|
|
||||||
|
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||||
|
|
||||||
|
gitea_env.add_collaborator(test_org_name, test_repo_name, "autogits_obs_staging_bot", "write")
|
||||||
|
|
||||||
|
# Restart workflow-pr service
|
||||||
|
gitea_env.restart_service("workflow-pr")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return gitea_env, test_full_repo_name, branch_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def ownerA_client(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Returns a GiteaAPIClient instance for ownerA.
|
||||||
|
"""
|
||||||
|
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||||
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerA")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def ownerB_client(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Returns a GiteaAPIClient instance for ownerB.
|
||||||
|
"""
|
||||||
|
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||||
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerB")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def ownerBB_client(gitea_env: GiteaAPIClient):
|
||||||
|
"""
|
||||||
|
Returns a GiteaAPIClient instance for ownerBB.
|
||||||
|
"""
|
||||||
|
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||||
|
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerBB")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import base64
|
import base64
|
||||||
|
import subprocess
|
||||||
|
|
||||||
TEST_DATA_DIR = Path(__file__).parent.parent / "data"
|
TEST_DATA_DIR = Path(__file__).parent.parent / "data"
|
||||||
BUILD_RESULT_TEMPLATE = TEST_DATA_DIR / "build_result.xml.template"
|
BUILD_RESULT_TEMPLATE = TEST_DATA_DIR / "build_result.xml.template"
|
||||||
@@ -43,9 +44,11 @@ def mock_build_result():
|
|||||||
|
|
||||||
|
|
||||||
class GiteaAPIClient:
|
class GiteaAPIClient:
|
||||||
def __init__(self, base_url, token):
|
def __init__(self, base_url, token, sudo=None):
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
self.headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
||||||
|
if sudo:
|
||||||
|
self.headers["Sudo"] = sudo
|
||||||
|
|
||||||
def _request(self, method, path, **kwargs):
|
def _request(self, method, path, **kwargs):
|
||||||
url = f"{self.base_url}/api/v1/{path}"
|
url = f"{self.base_url}/api/v1/{path}"
|
||||||
@@ -58,6 +61,48 @@ class GiteaAPIClient:
|
|||||||
raise
|
raise
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def get_file_info(self, owner: str, repo: str, file_path: str, branch: str = "main"):
|
||||||
|
url = f"repos/{owner}/{repo}/contents/{file_path}"
|
||||||
|
if branch and branch != "main":
|
||||||
|
url += f"?ref={branch}"
|
||||||
|
try:
|
||||||
|
response = self._request("GET", url)
|
||||||
|
return response.json()
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
|
||||||
|
def create_user(self, username, password, email):
|
||||||
|
print(f"--- Creating user: {username} ---")
|
||||||
|
data = {
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
"email": email,
|
||||||
|
"must_change_password": False,
|
||||||
|
"send_notify": False
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
self._request("POST", "admin/users", json=data)
|
||||||
|
print(f"User '{username}' created.")
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 422: # Already exists
|
||||||
|
print(f"User '{username}' already exists. Updating password...")
|
||||||
|
# Update password to be sure it matches our expectation
|
||||||
|
self._request("PATCH", f"admin/users/{username}", json={"password": password, "login_name": username})
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_user_token(self, username, password, token_name="test-token"):
|
||||||
|
print(f"--- Getting token for user: {username} ---")
|
||||||
|
url = f"{self.base_url}/api/v1/users/{username}/tokens"
|
||||||
|
|
||||||
|
# Create new token using Basic Auth
|
||||||
|
response = requests.post(url, auth=(username, password), json={"name": token_name})
|
||||||
|
if response.status_code == 201:
|
||||||
|
return response.json()["sha1"]
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
def create_org(self, org_name):
|
def create_org(self, org_name):
|
||||||
print(f"--- Checking organization: {org_name} ---")
|
print(f"--- Checking organization: {org_name} ---")
|
||||||
try:
|
try:
|
||||||
@@ -71,6 +116,18 @@ class GiteaAPIClient:
|
|||||||
print(f"Organization '{org_name}' created.")
|
print(f"Organization '{org_name}' created.")
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
print(f"--- Checking organization: {org_name} ---")
|
||||||
|
try:
|
||||||
|
self._request("GET", f"orgs/{org_name}")
|
||||||
|
print(f"Organization '{org_name}' already exists.")
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
print(f"Creating organization '{org_name}'...")
|
||||||
|
data = {"username": org_name, "full_name": org_name}
|
||||||
|
self._request("POST", "orgs", json=data)
|
||||||
|
print(f"Organization '{org_name}' created.")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def create_repo(self, org_name, repo_name):
|
def create_repo(self, org_name, repo_name):
|
||||||
print(f"--- Checking repository: {org_name}/{repo_name} ---")
|
print(f"--- Checking repository: {org_name}/{repo_name} ---")
|
||||||
@@ -91,7 +148,7 @@ class GiteaAPIClient:
|
|||||||
}
|
}
|
||||||
self._request("POST", f"orgs/{org_name}/repos", json=data)
|
self._request("POST", f"orgs/{org_name}/repos", json=data)
|
||||||
print(f"Repository '{org_name}/{repo_name}' created with a README.")
|
print(f"Repository '{org_name}/{repo_name}' created with a README.")
|
||||||
time.sleep(1) # Added delay to allow Git operations to become available
|
time.sleep(0.1) # Added delay to allow Git operations to become available
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -147,30 +204,8 @@ index 0000000..{pkg_b_sha}
|
|||||||
+++ b/pkgB
|
+++ b/pkgB
|
||||||
@@ -0,0 +1 @@
|
@@ -0,0 +1 @@
|
||||||
+Subproject commit {pkg_b_sha}
|
+Subproject commit {pkg_b_sha}
|
||||||
diff --git a/workflow.config b/workflow.config
|
|
||||||
new file mode 100644
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/workflow.config
|
|
||||||
@@ -0,0 +7 @@
|
|
||||||
+{{
|
|
||||||
+ "Workflows": ["pr"],
|
|
||||||
+ "GitProjectName": "products/SLFO#main",
|
|
||||||
+ "Organization": "pool",
|
|
||||||
+ "Branch": "main",
|
|
||||||
+ "ManualMergeProject": true,
|
|
||||||
+ "Reviewers": [ "-autogits_obs_staging_bot" ]
|
|
||||||
+}}
|
|
||||||
diff --git a/staging.config b/staging.config
|
|
||||||
new file mode 100644
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/staging.config
|
|
||||||
@@ -0,0 +3 @@
|
|
||||||
+{{
|
|
||||||
+ "ObsProject": "openSUSE:Leap:16.0",
|
|
||||||
+ "StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
|
||||||
+}}
|
|
||||||
"""
|
"""
|
||||||
message = "Add pkgA and pkgB as submodules and config files"
|
message = "Add pkgA and pkgB as submodules"
|
||||||
data = {
|
data = {
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"content": diff_content,
|
"content": diff_content,
|
||||||
@@ -192,56 +227,145 @@ new file mode 100644
|
|||||||
print(f"Repository settings for '{org_name}/{repo_name}' updated.")
|
print(f"Repository settings for '{org_name}/{repo_name}' updated.")
|
||||||
|
|
||||||
|
|
||||||
def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str):
|
def create_file(self, owner: str, repo: str, file_path: str, content: str, branch: str = "main", message: str = "Add file"):
|
||||||
owner, repo = repo_full_name.split("/")
|
file_info = self.get_file_info(owner, repo, file_path, branch=branch)
|
||||||
url = f"repos/{owner}/{repo}/pulls"
|
|
||||||
base_branch = "main"
|
|
||||||
|
|
||||||
# Create a new branch for the PR
|
|
||||||
new_branch_name = f"pr-branch-{int(time.time())}"
|
|
||||||
|
|
||||||
# Get the latest commit SHA of the base branch
|
|
||||||
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
|
||||||
|
|
||||||
# Create the new branch
|
|
||||||
self._request("POST", f"repos/{owner}/{repo}/branches", json={
|
|
||||||
"new_branch_name": new_branch_name,
|
|
||||||
"old_ref": base_commit_sha # Use the commit SHA directly
|
|
||||||
})
|
|
||||||
|
|
||||||
# Create a new file or modify an existing one in the new branch
|
|
||||||
file_path = f"test-file-{int(time.time())}.txt"
|
|
||||||
file_content = "This is a test file for the PR."
|
|
||||||
self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
|
|
||||||
"content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
|
|
||||||
"message": "Add test file",
|
|
||||||
"branch": new_branch_name
|
|
||||||
})
|
|
||||||
|
|
||||||
# Now create the PR
|
|
||||||
data = {
|
data = {
|
||||||
"head": new_branch_name, # Use the newly created branch as head
|
"content": base64.b64encode(content.encode('utf-8')).decode('ascii'),
|
||||||
|
"branch": branch,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
|
||||||
|
if file_info:
|
||||||
|
print(f"--- Updating file {file_path} in {owner}/{repo} ---")
|
||||||
|
# Re-fetch file_info to get the latest SHA right before update
|
||||||
|
latest_file_info = self.get_file_info(owner, repo, file_path, branch=branch)
|
||||||
|
if not latest_file_info:
|
||||||
|
raise Exception(f"File {file_path} disappeared during update attempt.")
|
||||||
|
data["sha"] = latest_file_info["sha"]
|
||||||
|
data["message"] = f"Update {file_path}"
|
||||||
|
method = "PUT"
|
||||||
|
else:
|
||||||
|
print(f"--- Creating file {file_path} in {owner}/{repo} ---")
|
||||||
|
method = "POST"
|
||||||
|
|
||||||
|
url = f"repos/{owner}/{repo}/contents/{file_path}"
|
||||||
|
self._request(method, url, json=data)
|
||||||
|
print(f"File {file_path} {'updated' if file_info else 'created'} in {owner}/{repo}.")
|
||||||
|
|
||||||
|
def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str, use_fork: bool, base_branch: str = "main", body: str = ""):
|
||||||
|
owner, repo = repo_full_name.split("/")
|
||||||
|
|
||||||
|
head_owner, head_repo = owner, repo
|
||||||
|
|
||||||
|
if use_fork:
|
||||||
|
sudo_user = self.headers.get("Sudo")
|
||||||
|
head_owner = sudo_user
|
||||||
|
head_repo = repo
|
||||||
|
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
|
||||||
|
|
||||||
|
print(f"--- Forking {repo_full_name} ---")
|
||||||
|
try:
|
||||||
|
self._request("POST", f"repos/{owner}/{repo}/forks", json={})
|
||||||
|
print(f"--- Forked to {head_owner}/{head_repo} ---")
|
||||||
|
time.sleep(0.5) # Give more time for fork to be ready
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 409: # Already forked
|
||||||
|
print(f"--- Already forked to {head_owner}/{head_repo} ---")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create a unique branch in the FORK
|
||||||
|
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
||||||
|
print(f"--- Creating branch {new_branch_name} in {head_owner}/{head_repo} from {base_branch} ({base_commit_sha}) ---")
|
||||||
|
self._request("POST", f"repos/{head_owner}/{head_repo}/branches", json={
|
||||||
|
"new_branch_name": new_branch_name,
|
||||||
|
"old_ref": base_commit_sha
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
|
||||||
|
# Get the latest commit SHA of the base branch from the ORIGINAL repo
|
||||||
|
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
||||||
|
|
||||||
|
# Try to create the branch in the ORIGINAL repo
|
||||||
|
print(f"--- Creating branch {new_branch_name} in {repo_full_name} ---")
|
||||||
|
self._request("POST", f"repos/{owner}/{repo}/branches", json={
|
||||||
|
"new_branch_name": new_branch_name,
|
||||||
|
"old_ref": base_commit_sha
|
||||||
|
})
|
||||||
|
|
||||||
|
# Apply the diff using diffpatch in the branch (wherever it is)
|
||||||
|
print(f"--- Applying diff to {head_owner}/{head_repo} branch {new_branch_name} ---")
|
||||||
|
self._request("POST", f"repos/{head_owner}/{head_repo}/diffpatch", json={
|
||||||
|
"branch": new_branch_name,
|
||||||
|
"content": diff_content,
|
||||||
|
"message": title
|
||||||
|
})
|
||||||
|
|
||||||
|
# Now create the PR in the ORIGINAL repo
|
||||||
|
data = {
|
||||||
|
"head": f"{head_owner}:{new_branch_name}" if head_owner != owner else new_branch_name,
|
||||||
"base": base_branch,
|
"base": base_branch,
|
||||||
"title": title,
|
"title": title,
|
||||||
"body": "Test Pull Request"
|
"body": body,
|
||||||
|
"allow_maintainer_edit": True
|
||||||
}
|
}
|
||||||
response = self._request("POST", url, json=data)
|
print(f"--- Creating PR in {repo_full_name} from {data['head']} ---")
|
||||||
|
response = self._request("POST", f"repos/{owner}/{repo}/pulls", json=data)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
def create_branch(self, owner: str, repo: str, new_branch_name: str, old_ref: str):
|
||||||
|
print(f"--- Checking branch '{new_branch_name}' in {owner}/{repo} ---")
|
||||||
|
try:
|
||||||
|
self._request("GET", f"repos/{owner}/{repo}/branches/{new_branch_name}")
|
||||||
|
print(f"Branch '{new_branch_name}' already exists.")
|
||||||
|
return
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code != 404:
|
||||||
|
raise # Re-raise other HTTP errors
|
||||||
|
|
||||||
|
print(f"--- Creating branch '{new_branch_name}' in {owner}/{repo} from {old_ref} ---")
|
||||||
|
url = f"repos/{owner}/{repo}/branches"
|
||||||
|
data = {
|
||||||
|
"new_branch_name": new_branch_name,
|
||||||
|
"old_ref": old_ref
|
||||||
|
}
|
||||||
|
self._request("POST", url, json=data)
|
||||||
|
print(f"Branch '{new_branch_name}' created in {owner}/{repo}.")
|
||||||
|
|
||||||
|
def ensure_branch_exists(self, owner: str, repo: str, branch: str = "main", timeout: int = 10):
|
||||||
|
print(f"--- Ensuring branch '{branch}' exists in {owner}/{repo} ---")
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
try:
|
||||||
|
self._request("GET", f"repos/{owner}/{repo}/branches/{branch}")
|
||||||
|
print(f"Branch '{branch}' confirmed in {owner}/{repo}.")
|
||||||
|
return
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
print(f"Branch '{branch}' not found yet in {owner}/{repo}. Retrying...")
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
raise Exception(f"Timeout waiting for branch {branch} in {owner}/{repo}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def modify_gitea_pr(self, repo_full_name: str, pr_number: int, diff_content: str, message: str):
|
def modify_gitea_pr(self, repo_full_name: str, pr_number: int, diff_content: str, message: str):
|
||||||
owner, repo = repo_full_name.split("/")
|
owner, repo = repo_full_name.split("/")
|
||||||
|
|
||||||
# Get PR details to find the head branch
|
# Get PR details to find the head branch AND head repo
|
||||||
pr_details = self._request("GET", f"repos/{owner}/{repo}/pulls/{pr_number}").json()
|
pr_details = self._request("GET", f"repos/{owner}/{repo}/pulls/{pr_number}").json()
|
||||||
head_branch = pr_details["head"]["ref"]
|
head_branch = pr_details["head"]["ref"]
|
||||||
|
head_repo_owner = pr_details["head"]["repo"]["owner"]["login"]
|
||||||
|
head_repo_name = pr_details["head"]["repo"]["name"]
|
||||||
|
|
||||||
file_path = f"modified-file-{int(time.time())}.txt"
|
# Apply the diff using diffpatch
|
||||||
file_content = "This is a modified test file for the PR."
|
print(f"--- Modifying PR #{pr_number} in {head_repo_owner}/{head_repo_name} branch {head_branch} ---")
|
||||||
|
self._request("POST", f"repos/{head_repo_owner}/{head_repo_name}/diffpatch", json={
|
||||||
self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
|
"branch": head_branch,
|
||||||
"content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
|
"content": diff_content,
|
||||||
"message": message,
|
"message": message
|
||||||
"branch": head_branch
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def update_gitea_pr_properties(self, repo_full_name: str, pr_number: int, **kwargs):
|
def update_gitea_pr_properties(self, repo_full_name: str, pr_number: int, **kwargs):
|
||||||
@@ -261,12 +385,12 @@ new file mode 100644
|
|||||||
timeline_events = response.json()
|
timeline_events = response.json()
|
||||||
if timeline_events: # Check if timeline_events list is not empty
|
if timeline_events: # Check if timeline_events list is not empty
|
||||||
return timeline_events
|
return timeline_events
|
||||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 3 seconds...")
|
print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 1 seconds...")
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
if e.response.status_code == 404:
|
if e.response.status_code == 404:
|
||||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 3 seconds...")
|
print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 1 seconds...")
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
raise # Re-raise other HTTP errors
|
raise # Re-raise other HTTP errors
|
||||||
raise Exception(f"Failed to retrieve timeline for PR {pr_number} after multiple retries.")
|
raise Exception(f"Failed to retrieve timeline for PR {pr_number} after multiple retries.")
|
||||||
@@ -283,12 +407,12 @@ new file mode 100644
|
|||||||
print(f"Attempt {i+1}: Comments for PR {pr_number} received: {comments}") # Added debug print
|
print(f"Attempt {i+1}: Comments for PR {pr_number} received: {comments}") # Added debug print
|
||||||
if comments: # Check if comments list is not empty
|
if comments: # Check if comments list is not empty
|
||||||
return comments
|
return comments
|
||||||
print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 3 seconds...")
|
print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 1 seconds...")
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
if e.response.status_code == 404:
|
if e.response.status_code == 404:
|
||||||
print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 3 seconds...")
|
print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 1 seconds...")
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
raise # Re-raise other HTTP errors
|
raise # Re-raise other HTTP errors
|
||||||
raise Exception(f"Failed to retrieve comments for PR {pr_number} after multiple retries.")
|
raise Exception(f"Failed to retrieve comments for PR {pr_number} after multiple retries.")
|
||||||
@@ -299,3 +423,87 @@ new file mode 100644
|
|||||||
response = self._request("GET", url)
|
response = self._request("GET", url)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
def create_review(self, repo_full_name: str, pr_number: int, event: str = "APPROVED", body: str = "LGTM"):
|
||||||
|
owner, repo = repo_full_name.split("/")
|
||||||
|
|
||||||
|
# Check if this user already has an APPROVED review to avoid 422
|
||||||
|
current_user = self.headers.get("Sudo") or "admin" # simplified
|
||||||
|
existing_reviews = self.list_reviews(repo_full_name, pr_number)
|
||||||
|
for r in existing_reviews:
|
||||||
|
if r["user"]["login"] == current_user and r["state"] == "APPROVED" and event == "APPROVED":
|
||||||
|
print(f"User {current_user} already has an APPROVED review for {repo_full_name} PR #{pr_number}")
|
||||||
|
return r
|
||||||
|
|
||||||
|
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
|
||||||
|
data = {
|
||||||
|
"event": event,
|
||||||
|
"body": body
|
||||||
|
}
|
||||||
|
print(f"--- Creating and submitting review ({event}) for {repo_full_name} PR #{pr_number} as {current_user} ---")
|
||||||
|
try:
|
||||||
|
response = self._request("POST", url, json=data)
|
||||||
|
review = response.json()
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
# If it fails with 422, it might be because a review is already pending or something else
|
||||||
|
print(f"Failed to create review: {e.response.text}")
|
||||||
|
# Try to find a pending review to submit
|
||||||
|
existing_reviews = self.list_reviews(repo_full_name, pr_number)
|
||||||
|
pending_review = next((r for r in existing_reviews if r["user"]["login"] == current_user and r["state"] == "PENDING"), None)
|
||||||
|
if pending_review:
|
||||||
|
review = pending_review
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# If the state is PENDING, we submit it.
|
||||||
|
if review.get("state") == "PENDING":
|
||||||
|
review_id = review["id"]
|
||||||
|
submit_url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}"
|
||||||
|
submit_data = {
|
||||||
|
"event": event,
|
||||||
|
"body": body
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
self._request("POST", submit_url, json=submit_data)
|
||||||
|
print(f"--- Review {review_id} submitted ---")
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if "already" in e.response.text.lower() or "stay pending" in e.response.text.lower():
|
||||||
|
print(f"Review {review_id} could not be submitted further: {e.response.text}")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
return review
|
||||||
|
|
||||||
|
def list_reviews(self, repo_full_name: str, pr_number: int):
|
||||||
|
owner, repo = repo_full_name.split("/")
|
||||||
|
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
|
||||||
|
response = self._request("GET", url)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def approve_requested_reviews(self, repo_full_name: str, pr_number: int):
|
||||||
|
print(f"--- Checking for REQUEST_REVIEW state in {repo_full_name} PR #{pr_number} ---")
|
||||||
|
reviews = self.list_reviews(repo_full_name, pr_number)
|
||||||
|
|
||||||
|
requested_reviews = [r for r in reviews if r["state"] == "REQUEST_REVIEW"]
|
||||||
|
if not requested_reviews:
|
||||||
|
print(f"No reviews in REQUEST_REVIEW state found for {repo_full_name} PR #{pr_number}")
|
||||||
|
return
|
||||||
|
|
||||||
|
admin_token = self.headers["Authorization"].split(" ")[1]
|
||||||
|
for r in requested_reviews:
|
||||||
|
reviewer_username = r["user"]["login"]
|
||||||
|
print(f"Reacting on REQUEST_REVIEW for user {reviewer_username} by approving...")
|
||||||
|
|
||||||
|
reviewer_client = GiteaAPIClient(base_url=self.base_url, token=admin_token, sudo=reviewer_username)
|
||||||
|
time.sleep(1) # give a chance to avoid possible concurrency issues with reviews request/approval
|
||||||
|
reviewer_client.create_review(repo_full_name, pr_number, event="APPROVED", body="Approving requested review")
|
||||||
|
|
||||||
|
def restart_service(self, service_name: str):
|
||||||
|
print(f"--- Restarting service: {service_name} ---")
|
||||||
|
try:
|
||||||
|
# Assumes podman-compose.yml is in the parent directory of tests/lib
|
||||||
|
subprocess.run(["podman-compose", "restart", service_name], check=True, cwd=os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)))
|
||||||
|
print(f"Service {service_name} restarted successfully.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error restarting service {service_name}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from tests.lib.common_test_utils import (
|
|||||||
def test_pr_workflow_succeeded(gitea_env, mock_build_result):
|
def test_pr_workflow_succeeded(gitea_env, mock_build_result):
|
||||||
"""End-to-end test for a successful PR workflow."""
|
"""End-to-end test for a successful PR workflow."""
|
||||||
diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed")
|
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False)
|
||||||
initial_pr_number = pr["number"]
|
initial_pr_number = pr["number"]
|
||||||
|
|
||||||
compose_dir = Path(__file__).parent.parent
|
compose_dir = Path(__file__).parent.parent
|
||||||
@@ -87,7 +87,7 @@ def test_pr_workflow_succeeded(gitea_env, mock_build_result):
|
|||||||
def test_pr_workflow_failed(gitea_env, mock_build_result):
|
def test_pr_workflow_failed(gitea_env, mock_build_result):
|
||||||
"""End-to-end test for a failed PR workflow."""
|
"""End-to-end test for a failed PR workflow."""
|
||||||
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail")
|
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False)
|
||||||
initial_pr_number = pr["number"]
|
initial_pr_number = pr["number"]
|
||||||
|
|
||||||
compose_dir = Path(__file__).parent.parent
|
compose_dir = Path(__file__).parent.parent
|
||||||
|
|||||||
82
integration/tests/workflow_pr_merge_test.py
Normal file
82
integration/tests/workflow_pr_merge_test.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import pytest
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from tests.lib.common_test_utils import GiteaAPIClient
|
||||||
|
|
||||||
|
@pytest.mark.t001
|
||||||
|
@pytest.mark.xfail(reason="The bot sometimes re-request reviews despite having all the approvals")
|
||||||
|
def test_001_automerge(automerge_env, test_user_client):
|
||||||
|
"""
|
||||||
|
Test scenario:
|
||||||
|
1. Setup custom workflow.config with mandatory reviewers (+usera, +userb).
|
||||||
|
2. Create a package PR in 'merge' branch.
|
||||||
|
3. Make sure the workflow-pr service created related project PR in 'merge' branch.
|
||||||
|
4. React on 'requested' reviews by approving them.
|
||||||
|
5. Make sure both PRs are merged automatically by the workflow-pr service.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, merge_branch_name = automerge_env
|
||||||
|
|
||||||
|
# 1. Create a package PR
|
||||||
|
diff = """diff --git a/merge_test_fixture.txt b/merge_test_fixture.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
"""
|
||||||
|
print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---")
|
||||||
|
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created package PR pool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
|
project_pr_number = None
|
||||||
|
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("pool/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"products/SLFO/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."
|
||||||
|
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
# 4. Make sure both PRs are merged automatically by the workflow-pr service
|
||||||
|
print("Polling for PR merge status and reacting on REQUEST_REVIEW...")
|
||||||
|
package_merged = False
|
||||||
|
project_merged = False
|
||||||
|
|
||||||
|
for i in range(15): # Poll for up to 15 seconds
|
||||||
|
# Package PR
|
||||||
|
if not package_merged:
|
||||||
|
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||||
|
if pkg_details.get("merged"):
|
||||||
|
package_merged = True
|
||||||
|
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
|
||||||
|
else:
|
||||||
|
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
|
||||||
|
|
||||||
|
# Project PR
|
||||||
|
if not project_merged:
|
||||||
|
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||||
|
if prj_details.get("merged"):
|
||||||
|
project_merged = True
|
||||||
|
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||||
|
else:
|
||||||
|
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||||
|
|
||||||
|
if package_merged and project_merged:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
|
||||||
|
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||||
|
print("Both PRs merged successfully.")
|
||||||
346
integration/tests/workflow_pr_review_test.py
Normal file
346
integration/tests/workflow_pr_review_test.py
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
import pytest
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
|
from tests.lib.common_test_utils import GiteaAPIClient
|
||||||
|
|
||||||
|
@pytest.mark.t004
|
||||||
|
@pytest.mark.xfail(reason="the bot sometimes re-requests review from autogits_obs_staging_bot despite having the approval")
|
||||||
|
def test_004_maintainer(maintainer_env, ownerA_client):
|
||||||
|
"""
|
||||||
|
Test scenario:
|
||||||
|
1. workflow.config will not have users with '+' sign.
|
||||||
|
2. The package PR is opened by the package maintainer (ownerA for pkgA).
|
||||||
|
3. Do not submit any review approval.
|
||||||
|
4. Check that both PRs are automatically merged anyway.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||||
|
|
||||||
|
# 0. Smoke test ownerA_client
|
||||||
|
print(f"--- Smoke testing ownerA_client ---")
|
||||||
|
ownerA_client._request("GET", "users/admin")
|
||||||
|
print(f"ownerA_client smoke test passed")
|
||||||
|
|
||||||
|
# 0.1 Verify all users from config exist
|
||||||
|
print("--- Verifying all users from config exist ---")
|
||||||
|
import json
|
||||||
|
wf_file = gitea_env.get_file_info("products", "SLFO", "workflow.config", branch=branch_name)
|
||||||
|
wf = json.loads(base64.b64decode(wf_file["content"]).decode("utf-8"))
|
||||||
|
mt_file = gitea_env.get_file_info("products", "SLFO", "_maintainership.json", branch=branch_name)
|
||||||
|
mt = json.loads(base64.b64decode(mt_file["content"]).decode("utf-8"))
|
||||||
|
|
||||||
|
expected_users = set()
|
||||||
|
for r in wf.get("Reviewers", []):
|
||||||
|
username = r.lstrip("+-")
|
||||||
|
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
|
||||||
|
expected_users.add(username)
|
||||||
|
for pkg_users in mt.values():
|
||||||
|
for username in pkg_users:
|
||||||
|
expected_users.add(username)
|
||||||
|
|
||||||
|
for username in expected_users:
|
||||||
|
gitea_env._request("GET", f"users/{username}")
|
||||||
|
print(f"Verified user exists: {username}")
|
||||||
|
|
||||||
|
# 1. Create a package PR as ownerA
|
||||||
|
diff = """diff --git a/maintainer_test_fixture.txt b/maintainer_test_fixture.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
"""
|
||||||
|
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} as ownerA ---")
|
||||||
|
package_pr = ownerA_client.create_gitea_pr("pool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created package PR pool/pkgA#{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
|
project_pr_number = None
|
||||||
|
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("pool/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"products/SLFO/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."
|
||||||
|
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
# 3. Make sure both PRs are merged automatically WITHOUT manual approvals
|
||||||
|
print("Polling for PR merge status (only bot approval allowed)...")
|
||||||
|
package_merged = False
|
||||||
|
project_merged = False
|
||||||
|
|
||||||
|
for i in range(15): # Poll for up to 15 seconds
|
||||||
|
# Package PR
|
||||||
|
if not package_merged:
|
||||||
|
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||||
|
if pkg_details.get("merged"):
|
||||||
|
package_merged = True
|
||||||
|
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
|
||||||
|
else:
|
||||||
|
# Approve ONLY bot if requested
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number)
|
||||||
|
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||||
|
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
|
||||||
|
|
||||||
|
# Project PR
|
||||||
|
if not project_merged:
|
||||||
|
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||||
|
if prj_details.get("merged"):
|
||||||
|
project_merged = True
|
||||||
|
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||||
|
else:
|
||||||
|
# Approve ONLY bot if requested
|
||||||
|
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
|
||||||
|
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||||
|
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||||
|
|
||||||
|
if package_merged and project_merged:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
|
||||||
|
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||||
|
print("Both PRs merged successfully by maintainer rule.")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t005
|
||||||
|
# @pytest.mark.xfail(reason="TBD troubleshoot")
|
||||||
|
def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, ownerBB_client):
|
||||||
|
"""
|
||||||
|
Test scenario:
|
||||||
|
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
|
||||||
|
2. Check that review request comes to both ownerB and ownerBB.
|
||||||
|
3. ownerB doesn't leave review.
|
||||||
|
4. check that review from ownerBB was enough to get both PRs merged.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||||
|
|
||||||
|
# 1. Create a package PR for pool/pkgB as ownerA
|
||||||
|
diff = """diff --git a/pkgB_test_fixture.txt b/pkgB_test_fixture.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
"""
|
||||||
|
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||||
|
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
|
project_pr_number = None
|
||||||
|
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("pool/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"products/SLFO/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."
|
||||||
|
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
# 3. Check that review requests came to ownerB and ownerBB
|
||||||
|
print("Checking for review requests from ownerB and ownerBB...")
|
||||||
|
reviewers_requested = set()
|
||||||
|
for _ in range(20):
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert "ownerB" in reviewers_requested, f"ownerB was not requested for review. Requested: {reviewers_requested}"
|
||||||
|
assert "ownerBB" in reviewers_requested, f"ownerBB was not requested for review. Requested: {reviewers_requested}"
|
||||||
|
print(f"Confirmed: ownerB and ownerBB were requested for review.")
|
||||||
|
|
||||||
|
# 4. ownerBB leaves review, ownerB does not.
|
||||||
|
print("ownerBB approving the PR...")
|
||||||
|
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
|
||||||
|
|
||||||
|
# 5. Check that both PRs are merged automatically
|
||||||
|
print("Polling for PR merge status (only bot approval allowed for project PR)...")
|
||||||
|
package_merged = False
|
||||||
|
project_merged = False
|
||||||
|
|
||||||
|
for i in range(15): # Poll for up to 15 seconds
|
||||||
|
# Package PR
|
||||||
|
if not package_merged:
|
||||||
|
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||||
|
if pkg_details.get("merged"):
|
||||||
|
package_merged = True
|
||||||
|
print(f"Package PR pool/pkgB#{package_pr_number} merged.")
|
||||||
|
|
||||||
|
# Project PR
|
||||||
|
if not project_merged:
|
||||||
|
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||||
|
if prj_details.get("merged"):
|
||||||
|
project_merged = True
|
||||||
|
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||||
|
else:
|
||||||
|
# Approve ONLY bot if requested
|
||||||
|
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
|
||||||
|
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||||
|
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||||
|
|
||||||
|
if package_merged and project_merged:
|
||||||
|
break
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
assert package_merged, f"Package PR pool/pkgB#{package_pr_number} was not merged automatically."
|
||||||
|
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||||
|
print("Both PRs merged successfully with only one maintainer approval.")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t006
|
||||||
|
def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_client, ownerBB_client):
|
||||||
|
"""
|
||||||
|
Test scenario:
|
||||||
|
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
|
||||||
|
2. Check that review request comes to both ownerB and ownerBB.
|
||||||
|
3. ownerBB rejects the PR (REQUEST_CHANGES).
|
||||||
|
4. Check that review request for ownerB is removed.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||||
|
|
||||||
|
# 1. Create a package PR for pool/pkgB as ownerA
|
||||||
|
diff = """diff --git a/pkgB_rejection_test.txt b/pkgB_rejection_test.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
"""
|
||||||
|
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||||
|
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Check that review requests came to ownerB and ownerBB
|
||||||
|
print("Checking for review requests from ownerB and ownerBB...")
|
||||||
|
for _ in range(20):
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
|
||||||
|
|
||||||
|
# 3. ownerBB rejects the PR
|
||||||
|
print("ownerBB rejecting the PR...")
|
||||||
|
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB")
|
||||||
|
|
||||||
|
# 4. Check that review request for ownerB is removed
|
||||||
|
print("Checking if ownerB's review request is removed...")
|
||||||
|
for _ in range(20):
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
if "ownerB" not in reviewers_requested:
|
||||||
|
print("Confirmed: ownerB's review request was removed.")
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
pytest.fail("ownerB's review request was not removed after ownerBB rejection.")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t007
|
||||||
|
@pytest.mark.xfail(reason="TBD troubleshoot")
|
||||||
|
def test_007_review_required_needs_all_approvals(review_required_env, ownerA_client, ownerBB_client):
|
||||||
|
"""
|
||||||
|
Test scenario:
|
||||||
|
1. it uses new fixture with "ReviewRequired = true" in the workflow.config.
|
||||||
|
2. Package PR for pkgB opened by ownerA.
|
||||||
|
3. Check review request comes to both ownerB and ownerBB.
|
||||||
|
4. ownerBB approves.
|
||||||
|
5. make sure that review is not merged automatically and the request for ownerB is not removed.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, branch_name = review_required_env
|
||||||
|
|
||||||
|
# 0. Smoke test ownerA_client
|
||||||
|
print(f"--- Smoke testing ownerA_client ---")
|
||||||
|
ownerA_client._request("GET", "users/admin")
|
||||||
|
print(f"ownerA_client smoke test passed")
|
||||||
|
|
||||||
|
# 1. Create a package PR for pool/pkgB as ownerA
|
||||||
|
diff = """diff --git a/pkgB_review_required_test.txt b/pkgB_review_required_test.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
"""
|
||||||
|
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||||
|
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Required", True, base_branch=branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Make sure the workflow-pr service created related project PR
|
||||||
|
project_pr_number = None
|
||||||
|
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(1)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("pool/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"products/SLFO/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."
|
||||||
|
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||||
|
|
||||||
|
# 3. Check that review requests came to ownerB and ownerBB
|
||||||
|
print("Checking for review requests from ownerB and ownerBB...")
|
||||||
|
for _ in range(20):
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
|
||||||
|
|
||||||
|
# 4. ownerBB leaves review, ownerB does not.
|
||||||
|
print("ownerBB approving the PR...")
|
||||||
|
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
|
||||||
|
|
||||||
|
# 5. Check that the PR is NOT merged automatically and ownerB request remains
|
||||||
|
print("Waiting to ensure PR is NOT merged and ownerB request remains...")
|
||||||
|
for i in range(10):
|
||||||
|
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
review_states = [(r["user"]["login"], r["state"]) for r in reviews]
|
||||||
|
print(f"Attempt {i+1}: Merged={pkg_details.get('merged')}, Reviews={review_states}")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||||
|
assert not pkg_details.get("merged"), "Package PR was merged automatically but it should NOT have been (ReviewRequired=true)."
|
||||||
|
|
||||||
|
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||||
|
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||||
|
assert "ownerB" in reviewers_requested, f"ownerB's review request was removed, but it should have remained. All reviews: {[(r['user']['login'], r['state']) for r in reviews]}"
|
||||||
|
|
||||||
|
print("Confirmed: PR not merged and ownerB review request remains as expected.")
|
||||||
@@ -18,11 +18,12 @@ pytest.initial_pr_number = None
|
|||||||
pytest.forwarded_pr_number = None
|
pytest.forwarded_pr_number = None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t001
|
||||||
@pytest.mark.dependency()
|
@pytest.mark.dependency()
|
||||||
def test_001_project_pr(gitea_env):
|
def test_001_project_pr(gitea_env):
|
||||||
"""Forwarded PR correct title"""
|
"""Forwarded PR correct title"""
|
||||||
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||||
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR")
|
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR", False)
|
||||||
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
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ def test_001_project_pr(gitea_env):
|
|||||||
), "Forwarded PR correct title"
|
), "Forwarded PR correct title"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t002
|
||||||
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
||||||
def test_002_updated_project_pr(gitea_env):
|
def test_002_updated_project_pr(gitea_env):
|
||||||
"""Forwarded PR head is updated"""
|
"""Forwarded PR head is updated"""
|
||||||
@@ -76,6 +78,7 @@ def test_002_updated_project_pr(gitea_env):
|
|||||||
assert sha_changed, "Forwarded PR has sha updated"
|
assert sha_changed, "Forwarded PR has sha updated"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t003
|
||||||
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
||||||
def test_003_wip(gitea_env):
|
def test_003_wip(gitea_env):
|
||||||
"""WIP flag set for PR"""
|
"""WIP flag set for PR"""
|
||||||
@@ -115,3 +118,209 @@ def test_003_wip(gitea_env):
|
|||||||
wip_flag_removed = True
|
wip_flag_removed = True
|
||||||
break
|
break
|
||||||
assert wip_flag_removed, "WIP flag was not removed from the forwarded PR."
|
assert wip_flag_removed, "WIP flag was not removed from the forwarded PR."
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t005
|
||||||
|
@pytest.mark.xfail(reason="works only in ibs_state branch?")
|
||||||
|
@pytest.mark.dependency()
|
||||||
|
def test_005_NoProjectGitPR_edits_disabled(no_project_git_pr_env, test_user_client):
|
||||||
|
"""
|
||||||
|
Reworked test: Sets workflow.config with NoProjectGitPR: true and creates a Package PR.
|
||||||
|
Verifies that no Project PR is created, then manually creates one and checks for bot warning.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
|
||||||
|
|
||||||
|
# 1. Create a Package PR (without "Allow edits from maintainers" enabled)
|
||||||
|
initial_diff = """diff --git a/first_file.txt b/first_file.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/first_file.txt
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Initial content
|
||||||
|
"""
|
||||||
|
package_pr = test_user_client.create_gitea_pr("pool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
print(f"Created Package PR #{package_pr_number}")
|
||||||
|
|
||||||
|
# 2. Verify that the workflow-pr bot did not create a Project PR
|
||||||
|
project_pr_created = False
|
||||||
|
for i in range(10): # Poll for some time
|
||||||
|
time.sleep(2)
|
||||||
|
timeline_events = gitea_env.get_timeline_events("pool/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"products/SLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_created = True
|
||||||
|
break
|
||||||
|
if project_pr_created:
|
||||||
|
break
|
||||||
|
|
||||||
|
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
|
||||||
|
print("Verification complete: No Project PR was created by the bot.")
|
||||||
|
|
||||||
|
# 3. Manually create the Project PR
|
||||||
|
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
|
||||||
|
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||||
|
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
||||||
|
|
||||||
|
project_pr_title = "Forwarded PRs: pkgA (Manual)"
|
||||||
|
project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
|
||||||
|
project_pr_diff = f"""diff --git a/pkgA b/pkgA
|
||||||
|
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
|
||||||
|
--- a/pkgA
|
||||||
|
+++ b/pkgA
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-Subproject commit {pkgA_main_sha}
|
||||||
|
+Subproject commit {pkgA_pr_head_sha}
|
||||||
|
"""
|
||||||
|
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
|
||||||
|
manual_project_pr_number = manual_project_pr["number"]
|
||||||
|
|
||||||
|
# Verify and set allow_maintainer_edit to False
|
||||||
|
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=False)
|
||||||
|
|
||||||
|
# Verify that allow_maintainer_edit is now disabled
|
||||||
|
updated_pr = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||||
|
assert updated_pr.get("allow_maintainer_edit") is False, "Expected allow_maintainer_edit to be False after update"
|
||||||
|
|
||||||
|
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
|
||||||
|
|
||||||
|
# 4. Trigger an update on the Package PR to prompt the bot to react to the manual Project PR
|
||||||
|
new_diff_content = """diff --git a/trigger_bot.txt b/trigger_bot.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/trigger_bot.txt
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Trigger content
|
||||||
|
"""
|
||||||
|
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Trigger bot update")
|
||||||
|
|
||||||
|
# 5. Verify that the bot adds a warning comment because it cannot update the manual PR (edits disabled)
|
||||||
|
warning_found = False
|
||||||
|
print(f"Polling Package PR #{package_pr_number} for warning comment...")
|
||||||
|
for _ in range(20):
|
||||||
|
time.sleep(3)
|
||||||
|
comments = gitea_env.get_comments("pool/pkgA", package_pr_number)
|
||||||
|
for comment in comments:
|
||||||
|
# According to test-plan.md, the warning explains that it cannot update the PR.
|
||||||
|
if "cannot update" in comment.get("body", "").lower():
|
||||||
|
warning_found = True
|
||||||
|
print(f"Warning comment found: {comment.get('body')}")
|
||||||
|
break
|
||||||
|
if warning_found:
|
||||||
|
break
|
||||||
|
|
||||||
|
# assert warning_found, "Bot did not post the expected warning comment on the Package PR."
|
||||||
|
# print("Verification complete: Bot posted a warning comment as expected.")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.t006
|
||||||
|
@pytest.mark.xfail(reason="works only in ibs_state branch?")
|
||||||
|
@pytest.mark.dependency()
|
||||||
|
def test_006_NoProjectGitPR_edits_enabled(no_project_git_pr_env, test_user_client):
|
||||||
|
"""
|
||||||
|
Verify that no project PR is created when "NoProjectGitPR" is true
|
||||||
|
and "Allow edits from maintainers" is enabled, using a dev branch.
|
||||||
|
"""
|
||||||
|
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
|
||||||
|
|
||||||
|
# 2. Create a Package PR with "Allow edits from maintainers" enabled
|
||||||
|
diff = """diff --git a/new_feature.txt b/new_feature.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..e69de29
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/new_feature.txt
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+New feature content
|
||||||
|
"""
|
||||||
|
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name)
|
||||||
|
package_pr_number = package_pr["number"]
|
||||||
|
|
||||||
|
# Enable "Allow edits from maintainers"
|
||||||
|
test_user_client.update_gitea_pr_properties("pool/pkgA", package_pr_number, allow_maintainer_edit=True)
|
||||||
|
print(f"Created Package PR #{package_pr_number} and enabled 'Allow edits from maintainers'.")
|
||||||
|
|
||||||
|
# Get SHAs needed for the manual Project PR diff
|
||||||
|
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
|
||||||
|
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||||
|
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
||||||
|
|
||||||
|
# 3. Assert that the workflow-pr bot did not create a Project PR in the products/SLFO repository
|
||||||
|
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("pool/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 products/SLFO/pulls/(\d+)
|
||||||
|
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||||
|
if match:
|
||||||
|
project_pr_created = True
|
||||||
|
break
|
||||||
|
if project_pr_created:
|
||||||
|
break
|
||||||
|
|
||||||
|
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
|
||||||
|
print("Verification complete: No Project PR was created in products/SLFO as expected.")
|
||||||
|
|
||||||
|
# 1. Create that Project PR from the test code.
|
||||||
|
project_pr_title = "Forwarded PRs: pkgA"
|
||||||
|
project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
|
||||||
|
project_pr_diff = f"""diff --git a/pkgA b/pkgA
|
||||||
|
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
|
||||||
|
--- a/pkgA
|
||||||
|
+++ b/pkgA
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-Subproject commit {pkgA_main_sha}
|
||||||
|
+Subproject commit {pkgA_pr_head_sha}
|
||||||
|
"""
|
||||||
|
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
|
||||||
|
manual_project_pr_number = manual_project_pr["number"]
|
||||||
|
# Explicitly ensure allow_maintainer_edit is True (it should be by default now, but just in case)
|
||||||
|
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=True)
|
||||||
|
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
|
||||||
|
time.sleep(5) # Give the bot time to potentially react or for the PR to settle
|
||||||
|
|
||||||
|
# Get initial SHA of the manually created Project PR
|
||||||
|
initial_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||||
|
initial_head_sha = initial_project_pr_details["head"]["sha"]
|
||||||
|
print(f"Manually created Project PR initial head SHA: {initial_head_sha}")
|
||||||
|
|
||||||
|
# 2. Add new commit to the package PR.
|
||||||
|
new_diff_content = """diff --git a/another_file.txt b/another_file.txt
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..f587a12
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/another_file.txt
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Another file content
|
||||||
|
"""
|
||||||
|
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR")
|
||||||
|
print(f"Added new commit to Package PR #{package_pr_number}.")
|
||||||
|
time.sleep(5) # Give the bot time to react
|
||||||
|
|
||||||
|
# 3. Make sure the project PR is properly updated by the bot
|
||||||
|
project_pr_updated = False
|
||||||
|
print(f"Polling manually created Project PR #{manual_project_pr_number} for update...")
|
||||||
|
for _ in range(20): # Poll for a reasonable time
|
||||||
|
time.sleep(2) # Wait a bit longer to be sure
|
||||||
|
current_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||||
|
current_head_sha = current_project_pr_details["head"]["sha"]
|
||||||
|
if current_head_sha != initial_head_sha:
|
||||||
|
project_pr_updated = True
|
||||||
|
print(f"Manually created Project PR updated. New head SHA: {current_head_sha}")
|
||||||
|
break
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/git
|
|||||||
RUN update-ca-certificates
|
RUN update-ca-certificates
|
||||||
|
|
||||||
# Install git and ssh
|
# Install git and ssh
|
||||||
RUN zypper -n in git-core openssh-clients binutils
|
RUN zypper -n in git-core openssh-clients binutils git-lfs
|
||||||
|
|
||||||
# Copy the pre-built binary into the container
|
# Copy the pre-built binary into the container
|
||||||
COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr
|
COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
FROM registry.suse.com/bci/bci-base:15.7
|
FROM registry.suse.com/bci/bci-base:15.7
|
||||||
|
|
||||||
# Add the custom CA to the trust store
|
# Add the custom CA to the trust store
|
||||||
COPY rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt
|
COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt
|
||||||
RUN update-ca-certificates
|
RUN update-ca-certificates
|
||||||
|
|
||||||
RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-workflow/15.7/devel:Factory:git-workflow.repo
|
RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-workflow/15.7/devel:Factory:git-workflow.repo
|
||||||
RUN zypper --gpg-auto-import-keys ref
|
RUN zypper --gpg-auto-import-keys ref
|
||||||
|
|
||||||
# Install git and ssh
|
# Install git and ssh
|
||||||
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils
|
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils git-lfs
|
||||||
|
|
||||||
COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN chmod +4755 /usr/local/bin/entrypoint.sh
|
RUN chmod +4755 /usr/local/bin/entrypoint.sh
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
[
|
[
|
||||||
"products/SLFO#main"
|
"products/SLFO#main",
|
||||||
|
"products/SLFO#dev",
|
||||||
|
"products/SLFO#merge",
|
||||||
|
"products/SLFO#maintainer-merge",
|
||||||
|
"products/SLFO#review-required"
|
||||||
]
|
]
|
||||||
|
|||||||
4
systemd/workflow-direct.target
Normal file
4
systemd/workflow-direct.target
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Autogits Workflow Direct instances
|
||||||
|
Documentation=https://src.opensuse.org/git-workflow/autogits
|
||||||
|
|
||||||
@@ -20,4 +20,5 @@ PrivateTmp=yes
|
|||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
WantedBy=workflow-direct.target
|
||||||
|
|
||||||
|
|||||||
5
systemd/workflow-pr.target
Normal file
5
systemd/workflow-pr.target
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Autogits Workflow PR instances
|
||||||
|
Documentation=https://src.opensuse.org/git-workflow/autogits
|
||||||
|
|
||||||
|
|
||||||
@@ -20,4 +20,5 @@ PrivateTmp=yes
|
|||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
WantedBy=workflow-pr.target
|
||||||
|
|
||||||
|
|||||||
@@ -94,19 +94,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
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@@ -406,12 +406,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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user