Compare commits
2 Commits
submodulem
...
mergemodes
| Author | SHA256 | Date | |
|---|---|---|---|
| db70452cbc | |||
| 53eebb75f7 |
@@ -39,6 +39,10 @@ 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 {
|
||||||
@@ -52,9 +56,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +92,8 @@ 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
|
||||||
@@ -183,6 +188,17 @@ 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,3 +342,67 @@ 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,6 +552,145 @@ 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 {
|
||||||
@@ -690,8 +829,12 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
}
|
}
|
||||||
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
|
||||||
PanicOnError(err)
|
PanicOnError(err)
|
||||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
if rs.Config.MergeMode == MergeModeDevel {
|
||||||
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
git.GitExecOrPanic(repo.Name, "checkout", "-B", br, head.Sha)
|
||||||
|
} else {
|
||||||
|
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
||||||
|
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +851,12 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
repo := prinfo.PR.Base.Repo
|
repo := prinfo.PR.Base.Repo
|
||||||
|
|
||||||
if !IsDryRun {
|
if !IsDryRun {
|
||||||
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
|
params := []string{"push"}
|
||||||
|
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,6 +2,7 @@ package common_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -1228,7 +1229,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "file://" + path.Join(repoDir, "prjgit"),
|
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1248,7 +1249,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
Owner: &models.User{
|
Owner: &models.User{
|
||||||
UserName: "org",
|
UserName: "org",
|
||||||
},
|
},
|
||||||
SSHURL: "file://" + path.Join(cmd.Dir, "prjgit"),
|
SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Head: &models.PRBranchInfo{
|
Head: &models.PRBranchInfo{
|
||||||
@@ -1338,3 +1339,346 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,19 @@ 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,6 +406,12 @@ 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