forked from adamm/autogits
Compare commits
68 Commits
proxy
...
status_ser
Author | SHA256 | Date | |
---|---|---|---|
e8b6066bae | |||
42e2713cd8 | |||
3d24dce5c0 | |||
0cefb45d8a | |||
ddbb824006 | |||
69dac4ec31 | |||
b7e03ab465 | |||
76aec3aabb | |||
19fb7e277b | |||
51261f1bc1 | |||
949810709d | |||
c012570e89 | |||
44a3b15a7d | |||
c5db1c83a7 | |||
9f0909621b | |||
b3914b04bd | |||
b43a19189e | |||
01b665230e | |||
1a07d4c541 | |||
22e44dff47 | |||
f9021d08b9 | |||
7a0394e51b | |||
518bc15696 | |||
51873eb048 | |||
4f33ce979c | |||
7cc4db2283 | |||
4d9e2f8cab | |||
ed4f27a19e | |||
e438b5b064 | |||
885bb7e537 | |||
977d75f6e9 | |||
42a9ee48e0 | |||
9333e5c3da | |||
5e29c88dc8 | |||
4f0f101620 | |||
253f009da3 | |||
5e66a14fa9 | |||
e79122e494 | |||
0b4b1a4e21 | |||
0019546e30 | |||
6438a8625a | |||
3928fa6429 | |||
e92ac4a592 | |||
a1520ebfb0 | |||
c8d65a3ae5 | |||
b849a72f31 | |||
568a2f3df8 | |||
30c8b2fe57 | |||
69b0f9a5ed | |||
a283d4f26f | |||
af898a6b8d | |||
b89cdb7664 | |||
d37bfaa9d3 | |||
90cca05b31 | |||
7c229500c1 | |||
290424c4a7 | |||
703fa101a4 | |||
66e4982e2d | |||
09b1c415dd | |||
629b941558 | |||
aa50481c00 | |||
bc714ee22d | |||
b8cc0357a7 | |||
aed0ac3ee9 | |||
cca3575596 | |||
69dcebcf74 | |||
7da9daddd5 | |||
cd0c3bc759 |
@@ -59,6 +59,10 @@ type AutogitConfig struct {
|
||||
Reviewers []string // only used by `pr` workflow
|
||||
ReviewGroups []ReviewGroup
|
||||
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
|
||||
|
||||
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
||||
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
|
||||
}
|
||||
|
||||
type AutogitConfigs []*AutogitConfig
|
||||
|
@@ -10,6 +10,62 @@ import (
|
||||
mock_common "src.opensuse.org/autogits/common/mock"
|
||||
)
|
||||
|
||||
func TestProjectConfigMatcher(t *testing.T) {
|
||||
configs := common.AutogitConfigs{
|
||||
{
|
||||
Organization: "test",
|
||||
GitProjectName: "test/prjgit#main",
|
||||
},
|
||||
{
|
||||
Organization: "test",
|
||||
Branch: "main",
|
||||
GitProjectName: "test/prjgit#main",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
org string
|
||||
repo string
|
||||
branch string
|
||||
config int
|
||||
}{
|
||||
{
|
||||
name: "invalid match",
|
||||
org: "foo",
|
||||
repo: "bar",
|
||||
config: -1,
|
||||
},
|
||||
{
|
||||
name: "default branch",
|
||||
org: "test",
|
||||
repo: "foo",
|
||||
branch: "",
|
||||
config: 0,
|
||||
},
|
||||
{
|
||||
name: "main branch",
|
||||
org: "test",
|
||||
repo: "foo",
|
||||
branch: "main",
|
||||
config: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
c := configs.GetPrjGitConfig(test.org, test.repo, test.branch)
|
||||
if test.config < 0 {
|
||||
if c != nil {
|
||||
t.Fatal("Expected nil. Got:", *c)
|
||||
}
|
||||
} else if config := configs[test.config]; c != config {
|
||||
t.Fatal("Expected", *config, "got", *c)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigWorkflowParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -41,10 +97,14 @@ func TestConfigWorkflowParser(t *testing.T) {
|
||||
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.config_json), "abc", nil)
|
||||
gitea.EXPECT().GetRepository("foo", "bar").Return(&test.repo, nil)
|
||||
|
||||
_, err := common.ReadWorkflowConfig(gitea, "foo/bar")
|
||||
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if config.ManualMergeOnly != false {
|
||||
t.Fatal("This should be false")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -82,7 +142,7 @@ func TestProjectGitParser(t *testing.T) {
|
||||
res: [3]string{"oorg", "foo.bar", "point"},
|
||||
},
|
||||
{
|
||||
name: "whitespace shouldn't matter",
|
||||
name: "whitespace shouldn't matter",
|
||||
prjgit: " oorg / \nfoo.bar\t # point ",
|
||||
res: [3]string{"oorg", "foo.bar", "point"},
|
||||
},
|
||||
|
@@ -44,6 +44,10 @@ type GitStatusLister interface {
|
||||
GitStatus(cwd string) ([]GitStatusData, error)
|
||||
}
|
||||
|
||||
type GitDiffLister interface {
|
||||
GitDiff(cwd, base, head string) (string, error)
|
||||
}
|
||||
|
||||
type Git interface {
|
||||
// error if git, but wrong remote
|
||||
GitClone(repo, branch, remoteUrl string) (string, error) // clone, or check if path is already checked out remote and force pulls, error otherwise. Return remotename, errror
|
||||
@@ -63,6 +67,8 @@ type Git interface {
|
||||
GitExecOrPanic(cwd string, params ...string)
|
||||
GitExec(cwd string, params ...string) error
|
||||
GitExecWithOutput(cwd string, params ...string) (string, error)
|
||||
|
||||
GitDiffLister
|
||||
}
|
||||
|
||||
type GitHandlerImpl struct {
|
||||
@@ -133,6 +139,7 @@ func (s *gitHandlerGeneratorImpl) CreateGitHandler(org string) (Git, error) {
|
||||
}
|
||||
|
||||
func (s *gitHandlerGeneratorImpl) ReadExistingPath(org string) (Git, error) {
|
||||
LogDebug("Locking git org:", org)
|
||||
s.lock_lock.Lock()
|
||||
defer s.lock_lock.Unlock()
|
||||
|
||||
@@ -154,6 +161,7 @@ func (s *gitHandlerGeneratorImpl) ReadExistingPath(org string) (Git, error) {
|
||||
func (s *gitHandlerGeneratorImpl) ReleaseLock(org string) {
|
||||
m, ok := s.lock[org]
|
||||
if ok {
|
||||
LogDebug("Unlocking git org:", org)
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -235,7 +243,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
|
||||
e.GitExecOrPanic(repo, "submodule", "deinit", "--all", "--force")
|
||||
}
|
||||
|
||||
e.GitExecOrPanic(repo, "fetch", remoteName, remoteBranch)
|
||||
e.GitExecOrPanic(repo, "fetch", "--prune", remoteName, remoteBranch)
|
||||
}
|
||||
|
||||
refsBytes, err := os.ReadFile(path.Join(e.GitPath, repo, ".git/refs/remotes", remoteName, "HEAD"))
|
||||
@@ -257,7 +265,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
|
||||
LogDebug("branch", branch)
|
||||
}
|
||||
|
||||
args := []string{"fetch", remoteName, branch}
|
||||
args := []string{"fetch", "--prune", remoteName, branch}
|
||||
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
|
||||
args = slices.Insert(args, 1, "--unshallow")
|
||||
}
|
||||
@@ -284,6 +292,7 @@ func (e *GitHandlerImpl) GitRemoteHead(gitDir, remote, branchName string) (strin
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) Close() error {
|
||||
LogDebug("Unlocking git lock")
|
||||
e.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -760,6 +769,8 @@ func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleLi
|
||||
done.Lock()
|
||||
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
|
||||
|
||||
LogDebug("Getting submodules for:", commitId)
|
||||
|
||||
go func() {
|
||||
defer done.Unlock()
|
||||
defer close(data_out.ch)
|
||||
@@ -837,7 +848,7 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
||||
go func() {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
subCommitId = "wrong"
|
||||
subCommitId = ""
|
||||
commitId = "ok"
|
||||
valid = false
|
||||
}
|
||||
@@ -892,7 +903,7 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return subCommitId, len(subCommitId) == len(commitId)
|
||||
return subCommitId, len(subCommitId) > 0
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -907,6 +918,16 @@ type GitStatusData struct {
|
||||
Path string
|
||||
Status int
|
||||
States [3]string
|
||||
|
||||
/*
|
||||
<sub> A 4 character field describing the submodule state.
|
||||
"N..." when the entry is not a submodule.
|
||||
"S<c><m><u>" when the entry is a submodule.
|
||||
<c> is "C" if the commit changed; otherwise ".".
|
||||
<m> is "M" if it has tracked changes; otherwise ".".
|
||||
<u> is "U" if there are untracked changes; otherwise ".".
|
||||
*/
|
||||
SubmoduleChanges string
|
||||
}
|
||||
|
||||
func parseGitStatusHexString(data io.ByteReader) (string, error) {
|
||||
@@ -929,6 +950,20 @@ func parseGitStatusHexString(data io.ByteReader) (string, error) {
|
||||
}
|
||||
}
|
||||
func parseGitStatusString(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
||||
}
|
||||
if c == 0 || c == ' ' {
|
||||
return string(str), nil
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
|
||||
func parseGitStatusStringWithSpace(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
@@ -969,7 +1004,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Modified
|
||||
ret.Path, err = parseGitStatusString(data)
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -979,11 +1014,11 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Renamed
|
||||
ret.Path, err = parseGitStatusString(data)
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.States[0], err = parseGitStatusString(data)
|
||||
ret.States[0], err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -993,7 +1028,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Untracked
|
||||
ret.Path, err = parseGitStatusString(data)
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1003,15 +1038,22 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Ignored
|
||||
ret.Path, err = parseGitStatusString(data)
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 'u':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 7); err != nil {
|
||||
if err = skipGitStatusEntry(data, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.SubmoduleChanges, err = parseGitStatusString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = skipGitStatusEntry(data, 4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ret.States[0], err = parseGitStatusHexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1022,7 +1064,7 @@ func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Unmerged
|
||||
ret.Path, err = parseGitStatusString(data)
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1069,3 +1111,26 @@ func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error)
|
||||
|
||||
return parseGitStatusData(bufio.NewReader(bytes.NewReader(out)))
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
||||
LogDebug("getting diff from", base, "..", head)
|
||||
|
||||
cmd := exec.Command("/usr/bin/git", "diff", base+".."+head)
|
||||
cmd.Env = []string{
|
||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||
"GIT_LFS_SKIP_SMUDGE=1",
|
||||
"GIT_CONFIG_GLOBAL=/dev/null",
|
||||
}
|
||||
cmd.Dir = filepath.Join(e.GitPath, cwd)
|
||||
cmd.Stderr = writeFunc(func(data []byte) (int, error) {
|
||||
LogError(string(data))
|
||||
return len(data), nil
|
||||
})
|
||||
LogDebug("command run:", cmd.Args)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
LogError("Error running command", cmd.Args, err)
|
||||
}
|
||||
|
||||
return string(out), nil
|
||||
}
|
||||
|
@@ -555,6 +555,8 @@ func TestGitStatusParse(t *testing.T) {
|
||||
Path: ".gitmodules",
|
||||
Status: GitStatus_Unmerged,
|
||||
States: [3]string{"587ec403f01113f2629da538f6e14b84781f70ac59c41aeedd978ea8b1253a76", "d23eb05d9ca92883ab9f4d28f3ec90c05f667f3a5c8c8e291bd65e03bac9ae3c", "087b1d5f22dbf0aa4a879fff27fff03568b334c90daa5f2653f4a7961e24ea33"},
|
||||
|
||||
SubmoduleChanges: "N...",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -29,7 +29,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
transport "github.com/go-openapi/runtime/client"
|
||||
@@ -86,7 +85,15 @@ type GiteaMaintainershipReader interface {
|
||||
|
||||
type GiteaPRFetcher interface {
|
||||
GetPullRequest(org, project string, num int64) (*models.PullRequest, error)
|
||||
GetAssociatedPrjGitPR(prjGitOrg, prjGitRepo, refOrg, refRepo string, Index int64) (*models.PullRequest, error)
|
||||
}
|
||||
|
||||
type GiteaPRUpdater interface {
|
||||
UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error)
|
||||
}
|
||||
|
||||
type GiteaPRTimelineFetcher interface {
|
||||
GiteaPRFetcher
|
||||
GiteaTimelineFetcher
|
||||
}
|
||||
|
||||
type GiteaCommitFetcher interface {
|
||||
@@ -101,14 +108,19 @@ type GiteaCommentFetcher interface {
|
||||
GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error)
|
||||
}
|
||||
|
||||
type GiteaPRChecker interface {
|
||||
type GiteaReviewTimelineFetcher interface {
|
||||
GiteaReviewFetcher
|
||||
GiteaTimelineFetcher
|
||||
}
|
||||
|
||||
type GiteaPRChecker interface {
|
||||
GiteaReviewTimelineFetcher
|
||||
GiteaCommentFetcher
|
||||
GiteaMaintainershipReader
|
||||
}
|
||||
|
||||
type GiteaReviewFetcherAndRequester interface {
|
||||
GiteaReviewFetcher
|
||||
GiteaReviewTimelineFetcher
|
||||
GiteaCommentFetcher
|
||||
GiteaReviewRequester
|
||||
}
|
||||
@@ -155,6 +167,7 @@ type Gitea interface {
|
||||
GiteaReviewUnrequester
|
||||
GiteaReviewer
|
||||
GiteaPRFetcher
|
||||
GiteaPRUpdater
|
||||
GiteaCommitFetcher
|
||||
GiteaReviewFetcher
|
||||
GiteaCommentFetcher
|
||||
@@ -164,18 +177,19 @@ type Gitea interface {
|
||||
GiteaCommitStatusGetter
|
||||
GiteaCommitStatusSetter
|
||||
GiteaSetRepoOptions
|
||||
GiteaTimelineFetcher
|
||||
|
||||
GetPullNotifications(since *time.Time) ([]*models.NotificationThread, error)
|
||||
GetDonePullNotifications(page int64) ([]*models.NotificationThread, error)
|
||||
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
||||
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
||||
SetNotificationRead(notificationId int64) error
|
||||
GetOrganization(orgName string) (*models.Organization, error)
|
||||
GetOrganizationRepositories(orgName string) ([]*models.Repository, error)
|
||||
CreateRepositoryIfNotExist(git Git, org, repoName string) (*models.Repository, error)
|
||||
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error)
|
||||
GetAssociatedPrjGitPR(prjGitOrg, prjGitRepo, refOrg, refRepo string, Index int64) (*models.PullRequest, error)
|
||||
GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, string, error)
|
||||
GetRecentPullRequests(org, repo, branch string) ([]*models.PullRequest, error)
|
||||
GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error)
|
||||
GetPullRequests(org, project string) ([]*models.PullRequest, error)
|
||||
|
||||
GetCurrentUser() (*models.User, error)
|
||||
}
|
||||
@@ -222,6 +236,52 @@ func (gitea *GiteaTransport) GetPullRequest(org, project string, num int64) (*mo
|
||||
return pr.Payload, err
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error) {
|
||||
pr, err := gitea.client.Repository.RepoEditPullRequest(
|
||||
repository.NewRepoEditPullRequestParams().
|
||||
WithOwner(org).
|
||||
WithRepo(repo).
|
||||
WithIndex(num).
|
||||
WithBody(options),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
)
|
||||
|
||||
return pr.Payload, err
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRequest, error) {
|
||||
var page, limit int64
|
||||
|
||||
prs := make([]*models.PullRequest, 0)
|
||||
limit = 20
|
||||
state := "open"
|
||||
|
||||
for {
|
||||
page++
|
||||
req, err := gitea.client.Repository.RepoListPullRequests(
|
||||
repository.
|
||||
NewRepoListPullRequestsParams().
|
||||
WithDefaults().
|
||||
WithOwner(org).
|
||||
WithRepo(repo).
|
||||
WithState(&state).
|
||||
WithPage(&page).
|
||||
WithLimit(&limit),
|
||||
gitea.transport.DefaultAuthentication)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", org, repo, err)
|
||||
}
|
||||
|
||||
prs = slices.Concat(prs, req.Payload)
|
||||
if len(req.Payload) < int(limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return prs, nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetCommitStatus(org, repo, hash string) ([]*models.CommitStatus, error) {
|
||||
page := int64(1)
|
||||
limit := int64(10)
|
||||
@@ -367,29 +427,42 @@ func (gitea *GiteaTransport) SetRepoOptions(owner, repo string, manual_merge boo
|
||||
return ok.Payload, err
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetPullNotifications(since *time.Time) ([]*models.NotificationThread, error) {
|
||||
bigLimit := int64(100000)
|
||||
const (
|
||||
GiteaNotificationType_Pull = "Pull"
|
||||
)
|
||||
|
||||
params := notification.NewNotifyGetListParams().
|
||||
WithDefaults().
|
||||
WithSubjectType([]string{"Pull"}).
|
||||
WithStatusTypes([]string{"unread"}).
|
||||
WithLimit(&bigLimit)
|
||||
func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
|
||||
bigLimit := int64(20)
|
||||
ret := make([]*models.NotificationThread, 0, 100)
|
||||
|
||||
if since != nil {
|
||||
s := strfmt.DateTime(*since)
|
||||
params.SetSince(&s)
|
||||
for page := int64(1); ; page++ {
|
||||
params := notification.NewNotifyGetListParams().
|
||||
WithDefaults().
|
||||
WithSubjectType([]string{Type}).
|
||||
WithStatusTypes([]string{"unread"}).
|
||||
WithLimit(&bigLimit).
|
||||
WithPage(&page)
|
||||
|
||||
if since != nil {
|
||||
s := strfmt.DateTime(*since)
|
||||
params.SetSince(&s)
|
||||
}
|
||||
|
||||
list, err := gitea.client.Notification.NotifyGetList(params, gitea.transport.DefaultAuthentication)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret = slices.Concat(ret, list.Payload)
|
||||
if len(list.Payload) < int(bigLimit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
list, err := gitea.client.Notification.NotifyGetList(params, gitea.transport.DefaultAuthentication)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list.Payload, nil
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetDonePullNotifications(page int64) ([]*models.NotificationThread, error) {
|
||||
func (gitea *GiteaTransport) GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error) {
|
||||
limit := int64(20)
|
||||
t := true
|
||||
|
||||
@@ -399,7 +472,7 @@ func (gitea *GiteaTransport) GetDonePullNotifications(page int64) ([]*models.Not
|
||||
list, err := gitea.client.Notification.NotifyGetList(
|
||||
notification.NewNotifyGetListParams().
|
||||
WithAll(&t).
|
||||
WithSubjectType([]string{"Pull"}).
|
||||
WithSubjectType([]string{Type}).
|
||||
WithStatusTypes([]string{"read"}).
|
||||
WithLimit(&limit).
|
||||
WithPage(&page),
|
||||
@@ -530,14 +603,14 @@ func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org, repoName s
|
||||
|
||||
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
||||
prOptions := models.CreatePullRequestOption{
|
||||
Base: repo.DefaultBranch,
|
||||
Base: targetId,
|
||||
Head: srcId,
|
||||
Title: title,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
if pr, err := gitea.client.Repository.RepoGetPullRequestByBaseHead(
|
||||
repository.NewRepoGetPullRequestByBaseHeadParams().WithOwner(repo.Owner.UserName).WithRepo(repo.Name).WithBase(repo.DefaultBranch).WithHead(srcId),
|
||||
repository.NewRepoGetPullRequestByBaseHeadParams().WithOwner(repo.Owner.UserName).WithRepo(repo.Name).WithBase(targetId).WithHead(srcId),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
); err == nil {
|
||||
return pr.Payload, nil
|
||||
@@ -560,48 +633,6 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
|
||||
return pr.GetPayload(), nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetAssociatedPrjGitPR(prjGitOrg, prjGitRepo, refOrg, refRepo string, Index int64) (*models.PullRequest, error) {
|
||||
var page int64
|
||||
state := "open"
|
||||
|
||||
prLine := fmt.Sprintf(PrPattern, refOrg, refRepo, Index)
|
||||
LogDebug("Finding PrjGitPR for", prLine, " Looking in", prjGitOrg, "/", prjGitRepo)
|
||||
for {
|
||||
page++
|
||||
prs, err := gitea.client.Repository.RepoListPullRequests(
|
||||
repository.
|
||||
NewRepoListPullRequestsParams().
|
||||
WithDefaults().
|
||||
WithOwner(prjGitOrg).
|
||||
WithRepo(prjGitRepo).
|
||||
WithState(&state).
|
||||
WithPage(&page),
|
||||
gitea.transport.DefaultAuthentication)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", prjGitOrg, prjGitRepo, err)
|
||||
}
|
||||
|
||||
// payload_processing:
|
||||
for _, pr := range prs.Payload {
|
||||
lines := strings.Split(pr.Body, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == prLine {
|
||||
LogDebug("Found PR:", pr.Index)
|
||||
return pr, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(prs.Payload) < 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) {
|
||||
reviewOptions := models.PullReviewRequestOptions{
|
||||
Reviewers: reviewers,
|
||||
@@ -687,20 +718,18 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string)
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
limit := int64(20)
|
||||
page := int64(1)
|
||||
resCount := limit
|
||||
resCount := 1
|
||||
|
||||
retData := []*models.TimelineComment{}
|
||||
|
||||
for resCount == limit {
|
||||
for resCount > 0 {
|
||||
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(
|
||||
issue.NewIssueGetCommentsAndTimelineParams().
|
||||
WithOwner(org).
|
||||
WithRepo(repo).
|
||||
WithIndex(idx).
|
||||
WithPage(&page).
|
||||
WithLimit(&limit),
|
||||
WithPage(&page),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
)
|
||||
|
||||
@@ -708,14 +737,16 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resCount = int64(len(res.Payload))
|
||||
resCount = len(res.Payload)
|
||||
LogDebug("page:", page, "len:", resCount)
|
||||
page++
|
||||
|
||||
retData = append(retData, res.Payload...)
|
||||
}
|
||||
LogDebug("total results:", len(retData))
|
||||
|
||||
slices.SortFunc(retData, func(a, b *models.TimelineComment) int {
|
||||
return time.Time(a.Created).Compare(time.Time(b.Created))
|
||||
return time.Time(b.Created).Compare(time.Time(a.Created))
|
||||
})
|
||||
|
||||
return retData, nil
|
||||
|
@@ -63,7 +63,7 @@ func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, org, prjGit
|
||||
if m != nil {
|
||||
m.IsDir = dir
|
||||
m.FetchPackage = func(pkg string) ([]byte, error) {
|
||||
data , _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, pkg)
|
||||
data, _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, pkg)
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
@@ -127,18 +127,11 @@ prjMaintainer:
|
||||
}
|
||||
|
||||
func (data *MaintainershipMap) IsApproved(pkg string, reviews []*models.PullReview, submitter string) bool {
|
||||
reviewers, found := data.Data[pkg]
|
||||
if !found {
|
||||
if pkg != ProjectKey && data.IsDir {
|
||||
r, err := data.FetchPackage(pkg)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
reviewers = parsePkgDirData(pkg, r)
|
||||
data.Data[pkg] = reviewers
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
var reviewers []string
|
||||
if pkg != ProjectKey {
|
||||
reviewers = data.ListPackageMaintainers(pkg)
|
||||
} else {
|
||||
reviewers = data.ListProjectMaintainers()
|
||||
}
|
||||
|
||||
if len(reviewers) == 0 {
|
||||
@@ -146,11 +139,12 @@ func (data *MaintainershipMap) IsApproved(pkg string, reviews []*models.PullRevi
|
||||
}
|
||||
|
||||
LogDebug("Looking for review by:", reviewers)
|
||||
if slices.Contains(reviewers, submitter) {
|
||||
LogDebug("Submitter is maintainer. Approving.")
|
||||
return true
|
||||
}
|
||||
|
||||
for _, review := range reviews {
|
||||
if slices.Contains(reviewers, submitter) {
|
||||
LogDebug("Submitter is maintainer. Approving.")
|
||||
return true
|
||||
}
|
||||
if !review.Stale && review.State == ReviewStateApproved && slices.Contains(reviewers, review.User.UserName) {
|
||||
LogDebug("Reviewed by", review.User.UserName)
|
||||
return true
|
||||
@@ -190,7 +184,7 @@ func (data *MaintainershipMap) WriteMaintainershipFile(writer io.StringWriter) e
|
||||
keys = slices.Delete(keys, i, len(keys))
|
||||
}
|
||||
slices.Sort(keys)
|
||||
for i, pkg := range(keys) {
|
||||
for i, pkg := range keys {
|
||||
eol := ","
|
||||
if i == len(keys)-1 {
|
||||
eol = ""
|
||||
|
56
common/manifest.go
Normal file
56
common/manifest.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Manifest struct {
|
||||
Subdirectories []string
|
||||
}
|
||||
|
||||
func (m *Manifest) SubdirForPackage(pkg string) string {
|
||||
if m == nil {
|
||||
return pkg
|
||||
}
|
||||
|
||||
idx := -1
|
||||
matchLen := 0
|
||||
basePkg := path.Base(pkg)
|
||||
lowercasePkg := strings.ToLower(basePkg)
|
||||
|
||||
for i, sub := range m.Subdirectories {
|
||||
basename := strings.ToLower(path.Base(sub))
|
||||
if strings.HasPrefix(lowercasePkg, basename) && matchLen < len(basename) {
|
||||
idx = i
|
||||
matchLen = len(basename)
|
||||
}
|
||||
}
|
||||
|
||||
if idx > -1 {
|
||||
return path.Join(m.Subdirectories[idx], basePkg)
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
func ReadManifestFile(filename string) (*Manifest, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseManifestFile(data)
|
||||
}
|
||||
|
||||
func ParseManifestFile(data []byte) (*Manifest, error) {
|
||||
ret := &Manifest{}
|
||||
err := yaml.Unmarshal(data, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
56
common/manifest_test.go
Normal file
56
common/manifest_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
)
|
||||
|
||||
func TestManifestSubdirAssignments(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
ManifestContent string
|
||||
Packages []string
|
||||
ManifestLocations []string
|
||||
}{
|
||||
{
|
||||
Name: "empty manifest",
|
||||
Packages: []string{"atom", "blarg", "Foobar", "X-Ray", "boost", "NodeJS"},
|
||||
ManifestLocations: []string{"atom", "blarg", "Foobar", "X-Ray", "boost", "NodeJS"},
|
||||
},
|
||||
{
|
||||
Name: "only few subdirs manifest",
|
||||
ManifestContent: "subdirectories:\n - a\n - b",
|
||||
Packages: []string{"atom", "blarg", "Foobar", "X-Ray", "Boost", "NodeJS"},
|
||||
ManifestLocations: []string{"a/atom", "b/blarg", "Foobar", "X-Ray", "b/Boost", "NodeJS"},
|
||||
},
|
||||
{
|
||||
Name: "multilayer subdirs manifest",
|
||||
ManifestContent: "subdirectories:\n - a\n - b\n - libs/boo",
|
||||
Packages: []string{"atom", "blarg", "Foobar", "X-Ray", "Boost", "NodeJS"},
|
||||
ManifestLocations: []string{"a/atom", "b/blarg", "Foobar", "X-Ray", "libs/boo/Boost", "NodeJS"},
|
||||
},
|
||||
{
|
||||
Name: "multilayer subdirs manifest with trailing /",
|
||||
ManifestContent: "subdirectories:\n - a\n - b\n - libs/boo/\n - somedir/Node/",
|
||||
Packages: []string{"atom", "blarg", "Foobar", "X-Ray", "Boost", "NodeJS", "foobar/node2"},
|
||||
ManifestLocations: []string{"a/atom", "b/blarg", "Foobar", "X-Ray", "libs/boo/Boost", "somedir/Node/NodeJS", "somedir/Node/node2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
m, err := common.ParseManifestFile([]byte(test.ManifestContent))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, pkg := range test.Packages {
|
||||
expected := test.ManifestLocations[i]
|
||||
if l := m.SubdirForPackage(pkg); l != expected {
|
||||
t.Error("Expected:", expected, "but got:", l)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -156,6 +156,34 @@ type GroupMeta struct {
|
||||
Persons PersonGroup `xml:"person"`
|
||||
}
|
||||
|
||||
type RequestStateMeta struct {
|
||||
XMLName xml.Name `xml:"state"`
|
||||
State string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
type RequestActionTarget struct {
|
||||
XMLName xml.Name
|
||||
Project string `xml:"project,attr"`
|
||||
Package string `xml:"package,attr"`
|
||||
Revision *string `xml:"rev,attr,optional"`
|
||||
}
|
||||
|
||||
type RequestActionMeta struct {
|
||||
XMLName xml.Name `xml:"action"`
|
||||
Type string `xml:"type,attr"`
|
||||
Source *RequestActionTarget `xml:"source,optional"`
|
||||
Target *RequestActionTarget `xml:"target,optional"`
|
||||
}
|
||||
|
||||
type RequestMeta struct {
|
||||
XMLName xml.Name `xml:"request"`
|
||||
Id int `xml:"id,attr"`
|
||||
|
||||
Creator string `xml:"creator,attr"`
|
||||
Action *RequestActionMeta `xml:"action"`
|
||||
State RequestStateMeta `xml:"state"`
|
||||
}
|
||||
|
||||
func parseProjectMeta(data []byte) (*ProjectMeta, error) {
|
||||
var meta ProjectMeta
|
||||
err := xml.Unmarshal(data, &meta)
|
||||
@@ -166,8 +194,83 @@ func parseProjectMeta(data []byte) (*ProjectMeta, error) {
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
const (
|
||||
RequestStatus_Unknown = "unknown"
|
||||
RequestStatus_Accepted = "accepted"
|
||||
RequestStatus_Superseded = "superseded"
|
||||
RequestStatus_Declined = "declined"
|
||||
RequestStatus_Revoked = "revoked"
|
||||
RequestStatus_New = "new"
|
||||
RequestStatus_Review = "review"
|
||||
)
|
||||
|
||||
func (status *RequestStateMeta) IsFinal() bool {
|
||||
switch status.State {
|
||||
case RequestStatus_Declined, RequestStatus_Revoked, RequestStatus_Accepted, RequestStatus_Superseded:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseRequestXml(data []byte) (*RequestMeta, error) {
|
||||
ret := RequestMeta{}
|
||||
LogDebug("parsing: ", string(data))
|
||||
if err := xml.Unmarshal(data, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (c *ObsClient) CreateSubmitRequest(sourcePrj, sourcePkg, targetPrj string) (*RequestMeta, error) {
|
||||
url := c.baseUrl.JoinPath("request")
|
||||
query := url.Query()
|
||||
query.Add("cmd", "create")
|
||||
url.RawQuery = query.Encode()
|
||||
request := `<request>
|
||||
<action type="submit">
|
||||
<source project="` + sourcePrj + `" package="` + sourcePkg + `">
|
||||
</source>
|
||||
<target project="` + targetPrj + `" package="` + sourcePkg + `">
|
||||
</target>
|
||||
</action>
|
||||
</request>`
|
||||
res, err := c.ObsRequestRaw("POST", url.String(), strings.NewReader(request))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if res.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("Unexpected return code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parseRequestXml(data)
|
||||
}
|
||||
|
||||
func (c *ObsClient) RequestStatus(requestID int) (*RequestMeta, error) {
|
||||
res, err := c.ObsRequest("GET", []string{"request", fmt.Sprint(requestID)}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("Unexpected return code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parseRequestXml(data)
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetGroupMeta(gid string) (*GroupMeta, error) {
|
||||
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("group", gid).String(), nil)
|
||||
res, err := c.ObsRequest("GET", []string{"group", gid}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -197,7 +300,7 @@ func (c *ObsClient) GetGroupMeta(gid string) (*GroupMeta, error) {
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetUserMeta(uid string) (*UserMeta, error) {
|
||||
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("person", uid).String(), nil)
|
||||
res, err := c.ObsRequest("GET", []string{"person", uid}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -226,7 +329,11 @@ func (c *ObsClient) GetUserMeta(uid string) (*UserMeta, error) {
|
||||
return &meta, nil
|
||||
}
|
||||
|
||||
func (c *ObsClient) ObsRequest(method string, url string, body io.Reader) (*http.Response, error) {
|
||||
func (c *ObsClient) ObsRequest(method string, url_path []string, body io.Reader) (*http.Response, error) {
|
||||
return c.ObsRequestRaw(method, c.baseUrl.JoinPath(url_path...).String(), body)
|
||||
}
|
||||
|
||||
func (c *ObsClient) ObsRequestRaw(method string, url string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
|
||||
if err != nil {
|
||||
@@ -322,7 +429,7 @@ func (c *ObsClient) ObsRequest(method string, url string, body io.Reader) (*http
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) {
|
||||
req := c.baseUrl.JoinPath("source", project, "_meta").String()
|
||||
req := []string{"source", project, "_meta"}
|
||||
res, err := c.ObsRequest("GET", req, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -348,7 +455,7 @@ func (c *ObsClient) GetProjectMeta(project string) (*ProjectMeta, error) {
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetPackageMeta(project, pkg string) (*PackageMeta, error) {
|
||||
res, err := c.ObsRequest("GET", c.baseUrl.JoinPath("source", project, pkg, "_meta").String(), nil)
|
||||
res, err := c.ObsRequest("GET", []string{"source", project, pkg, "_meta"}, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -422,7 +529,7 @@ func (c *ObsClient) SetProjectMeta(meta *ProjectMeta) error {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.ObsRequest("PUT", c.baseUrl.JoinPath("source", meta.Name, "_meta").String(), io.NopCloser(bytes.NewReader(xml)))
|
||||
res, err := c.ObsRequest("PUT", []string{"source", meta.Name, "_meta"}, io.NopCloser(bytes.NewReader(xml)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -443,7 +550,7 @@ func (c *ObsClient) DeleteProject(project string) error {
|
||||
query := url.Query()
|
||||
query.Add("force", "1")
|
||||
url.RawQuery = query.Encode()
|
||||
res, err := c.ObsRequest("DELETE", url.String(), nil)
|
||||
res, err := c.ObsRequestRaw("DELETE", url.String(), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -455,25 +562,44 @@ func (c *ObsClient) DeleteProject(project string) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ObsClient) BuildLog(prj, pkg, repo, arch string) (io.ReadCloser, error) {
|
||||
url := c.baseUrl.JoinPath("build", prj, repo, arch, pkg, "_log")
|
||||
query := url.Query()
|
||||
query.Add("nostream", "1")
|
||||
query.Add("start", "0")
|
||||
url.RawQuery = query.Encode()
|
||||
res, err := c.ObsRequestRaw("GET", url.String(), nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Body, nil
|
||||
}
|
||||
|
||||
type PackageBuildStatus struct {
|
||||
Package string `xml:"package,attr"`
|
||||
Code string `xml:"code,attr"`
|
||||
Details string `xml:"details"`
|
||||
|
||||
LastUpdate int64
|
||||
}
|
||||
|
||||
type BuildResult struct {
|
||||
Project string `xml:"project,attr"`
|
||||
Repository string `xml:"repository,attr"`
|
||||
Arch string `xml:"arch,attr"`
|
||||
Code string `xml:"code,attr"`
|
||||
Dirty bool `xml:"dirty,attr"`
|
||||
ScmSync string `xml:"scmsync"`
|
||||
ScmInfo string `xml:"scminfo"`
|
||||
Status []PackageBuildStatus `xml:"status"`
|
||||
Binaries []BinaryList `xml:"binarylist"`
|
||||
Project string `xml:"project,attr"`
|
||||
Repository string `xml:"repository,attr"`
|
||||
Arch string `xml:"arch,attr"`
|
||||
|
||||
Code string `xml:"code,attr"`
|
||||
Dirty bool `xml:"dirty,attr"`
|
||||
ScmSync string `xml:"scmsync"`
|
||||
ScmInfo string `xml:"scminfo"`
|
||||
Status []PackageBuildStatus `xml:"status"`
|
||||
Binaries []BinaryList `xml:"binarylist"`
|
||||
|
||||
LastUpdate int64
|
||||
}
|
||||
|
||||
type Binary struct {
|
||||
@@ -493,6 +619,7 @@ type BuildResultList struct {
|
||||
Result []BuildResult `xml:"result"`
|
||||
|
||||
isLastBuild bool
|
||||
LastUpdate int64
|
||||
}
|
||||
|
||||
func (r *BuildResultList) GetPackageList() []string {
|
||||
@@ -515,6 +642,48 @@ func (r *BuildResultList) GetPackageList() []string {
|
||||
return pkgList
|
||||
}
|
||||
|
||||
func packageSort(A, B PackageBuildStatus) int {
|
||||
return strings.Compare(A.Package, B.Package)
|
||||
}
|
||||
|
||||
func repoSort(A, B BuildResult) int {
|
||||
eq := strings.Compare(A.Project, B.Project)
|
||||
if eq == 0 {
|
||||
eq = strings.Compare(A.Repository, B.Repository)
|
||||
if eq == 0 {
|
||||
eq = strings.Compare(A.Arch, B.Arch)
|
||||
}
|
||||
}
|
||||
return eq
|
||||
}
|
||||
|
||||
func (r *BuildResultList) MergePackageState(now int64, pkgState *BuildResultList) {
|
||||
for _, nr := range pkgState.Result {
|
||||
idx, found := slices.BinarySearchFunc(r.Result, nr, repoSort)
|
||||
// not found, new repo?
|
||||
if !found {
|
||||
nr.LastUpdate = now
|
||||
r.Result = slices.Insert(r.Result, idx, nr)
|
||||
continue
|
||||
}
|
||||
|
||||
// update current repo
|
||||
repo := &r.Result[idx]
|
||||
|
||||
// update all the packages in the repo
|
||||
for _, p := range nr.Status {
|
||||
p.LastUpdate = now
|
||||
idx, found := slices.BinarySearchFunc(repo.Status, p, packageSort)
|
||||
if !found {
|
||||
repo.Status = slices.Insert(repo.Status, idx, p)
|
||||
continue
|
||||
}
|
||||
|
||||
repo.Status[idx] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BuildResultList) BuildResultSummary() (success, finished bool) {
|
||||
if r == nil {
|
||||
return false, false
|
||||
@@ -718,9 +887,7 @@ func (obs ObsProjectNotFound) Error() string {
|
||||
}
|
||||
|
||||
func (c *ObsClient) ProjectConfig(project string) (string, error) {
|
||||
u := c.baseUrl.JoinPath("source", project, "_config")
|
||||
|
||||
res, err := c.ObsRequest("GET", u.String(), nil)
|
||||
res, err := c.ObsRequest("GET", []string{"source", project, "_config"}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -761,7 +928,7 @@ func (c *ObsClient) BuildStatusWithState(project string, opts *BuildResultOption
|
||||
}
|
||||
}
|
||||
u.RawQuery = query.Encode()
|
||||
res, err := c.ObsRequest("GET", u.String(), nil)
|
||||
res, err := c.ObsRequestRaw("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -784,5 +951,11 @@ func (c *ObsClient) BuildStatusWithState(project string, opts *BuildResultOption
|
||||
if ret != nil {
|
||||
ret.isLastBuild = opts.LastBuild
|
||||
}
|
||||
|
||||
slices.SortFunc(ret.Result, repoSort)
|
||||
for _, r := range ret.Result {
|
||||
slices.SortFunc(r.Status, packageSort)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
@@ -55,6 +55,52 @@ func TestParsingOfBuildResults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingRequestResults(t *testing.T) {
|
||||
res, err := parseRequestXml([]byte(metaRequestData))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.Id != 42 ||
|
||||
res.Action.Source.Project != "home:foo-user" ||
|
||||
res.Action.Source.Package != "obs-server" ||
|
||||
*res.Action.Source.Revision != "521e" ||
|
||||
res.Action.Target.Project != "OBS:Unstable" ||
|
||||
res.Action.Target.Revision != nil {
|
||||
|
||||
t.Fatal(res)
|
||||
}
|
||||
}
|
||||
|
||||
const metaRequestData = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<request id="42" creator="foo-user">
|
||||
<action type="submit">
|
||||
<source project="home:foo-user" package="obs-server" rev="521e">
|
||||
</source>
|
||||
<target project="OBS:Unstable" package="obs-server">
|
||||
</target>
|
||||
<options>
|
||||
<sourceupdate>cleanup</sourceupdate>
|
||||
</options>
|
||||
</action>
|
||||
<state name="accepted" who="bar-user" when="2021-01-15T13:39:43">
|
||||
<comment>allright</comment>
|
||||
</state>
|
||||
<review state="accepted" when="2021-01-15T15:49:32" who="obs-maintainer" by_user="obs-maintainer">
|
||||
</review>
|
||||
<review state="accepted" when="2021-01-15T15:49:32" who="obs-maintainer" by_group="obs-group">
|
||||
</review>
|
||||
<review state="accepted" when="2021-01-15T15:49:32" who="obs-maintainer" by_project="OBS:Unstable">
|
||||
</review>
|
||||
<review state="accepted" when="2021-01-15T15:49:32" who="obs-maintainer" by_package="obs-server">
|
||||
</review>
|
||||
<history who="foo" when="2021-01-15T13:39:43">
|
||||
<description>Request created</description>
|
||||
<comment>Please review sources</comment>
|
||||
</history>
|
||||
<description>A little version update</description>
|
||||
</request>`
|
||||
|
||||
const metaPrjData = `
|
||||
<project name="home:adamm">
|
||||
<title>Adam's Home Projects</title>
|
||||
|
194
common/pr.go
194
common/pr.go
@@ -25,6 +25,13 @@ type PRSet struct {
|
||||
BotUser string
|
||||
}
|
||||
|
||||
func (prinfo *PRInfo) PRComponents() (org string, repo string, idx int64) {
|
||||
org = prinfo.PR.Base.Repo.Owner.UserName
|
||||
repo = prinfo.PR.Base.Repo.Name
|
||||
idx = prinfo.PR.Index
|
||||
return
|
||||
}
|
||||
|
||||
func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRInfo, config *AutogitConfig) ([]*PRInfo, error) {
|
||||
for _, p := range currentSet {
|
||||
if pr.Index == p.PR.Index && pr.Base.Repo.Name == p.PR.Base.Repo.Name && pr.Base.Repo.Owner.UserName == p.PR.Base.Repo.Owner.UserName {
|
||||
@@ -53,7 +60,42 @@ func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRIn
|
||||
return retSet, nil
|
||||
}
|
||||
|
||||
func FetchPRSet(user string, gitea GiteaPRFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
|
||||
var Timeline_RefIssueNotFound error = errors.New("RefIssue not found on the timeline")
|
||||
|
||||
func LastPrjGitRefOnTimeline(gitea GiteaPRTimelineFetcher, org, repo string, num int64, prjGitOrg, prjGitRepo string) (*models.PullRequest, error) {
|
||||
prRefLine := fmt.Sprintf(PrPattern, org, repo, num)
|
||||
timeline, err := gitea.GetTimeline(org, repo, num)
|
||||
if err != nil {
|
||||
LogError("Failed to fetch timeline for", org, repo, "#", num, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for idx := len(timeline) - 1; idx >= 0; idx-- {
|
||||
item := timeline[idx]
|
||||
issue := item.RefIssue
|
||||
if item.Type == TimelineCommentType_PullRequestRef &&
|
||||
issue != nil &&
|
||||
issue.Repository != nil &&
|
||||
issue.Repository.Owner == prjGitOrg &&
|
||||
issue.Repository.Name == prjGitRepo {
|
||||
|
||||
lines := SplitLines(item.RefIssue.Body)
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == prRefLine {
|
||||
LogDebug("Found PrjGit PR in Timeline:", issue.Index)
|
||||
|
||||
// found prjgit PR in timeline. Return it
|
||||
return gitea.GetPullRequest(prjGitOrg, prjGitRepo, issue.Index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug("PrjGit RefIssue not found on timeline in", org, repo, num)
|
||||
return nil, Timeline_RefIssueNotFound
|
||||
}
|
||||
|
||||
func FetchPRSet(user string, gitea GiteaPRTimelineFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
|
||||
var pr *models.PullRequest
|
||||
var err error
|
||||
|
||||
@@ -63,7 +105,7 @@ func FetchPRSet(user string, gitea GiteaPRFetcher, org, repo string, num int64,
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if pr, err = gitea.GetAssociatedPrjGitPR(prjGitOrg, prjGitRepo, org, repo, num); err != nil {
|
||||
if pr, err = LastPrjGitRefOnTimeline(gitea, org, repo, num, prjGitOrg, prjGitRepo); err != nil && err != Timeline_RefIssueNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -80,26 +122,53 @@ func FetchPRSet(user string, gitea GiteaPRFetcher, org, repo string, num int64,
|
||||
}
|
||||
|
||||
return &PRSet{
|
||||
PRs: prs,
|
||||
Config: config,
|
||||
PRs: prs,
|
||||
Config: config,
|
||||
BotUser: user,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rs *PRSet) IsPrjGitPR(pr *models.PullRequest) bool {
|
||||
org, repo, _ := rs.Config.GetPrjGit()
|
||||
return pr.Base.Repo.Name == repo && pr.Base.Repo.Owner.UserName == org
|
||||
func (rs *PRSet) Find(pr *models.PullRequest) (*PRInfo, bool) {
|
||||
for _, p := range rs.PRs {
|
||||
if p.PR.Base.RepoID == pr.Base.RepoID &&
|
||||
p.PR.Head.Sha == pr.Head.Sha &&
|
||||
p.PR.Base.Name == pr.Base.Name {
|
||||
return p, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (rs *PRSet) GetPrjGitPR() (*models.PullRequest, error) {
|
||||
var ret *models.PullRequest
|
||||
func (rs *PRSet) AddPR(pr *models.PullRequest) *PRInfo {
|
||||
if pr, found := rs.Find(pr); found {
|
||||
return pr
|
||||
}
|
||||
|
||||
prinfo := &PRInfo{
|
||||
PR: pr,
|
||||
}
|
||||
rs.PRs = append(rs.PRs, prinfo)
|
||||
return prinfo
|
||||
}
|
||||
|
||||
func (rs *PRSet) IsPrjGitPR(pr *models.PullRequest) bool {
|
||||
org, repo, branch := rs.Config.GetPrjGit()
|
||||
return pr.Base.Name == branch && pr.Base.Repo.Name == repo && pr.Base.Repo.Owner.UserName == org
|
||||
}
|
||||
|
||||
var PRSet_PrjGitMissing error = errors.New("No PrjGit PR found")
|
||||
var PRSet_MultiplePrjGit error = errors.New("Multiple PrjGit PRs in one review set")
|
||||
|
||||
func (rs *PRSet) GetPrjGitPR() (*PRInfo, error) {
|
||||
var ret *PRInfo
|
||||
|
||||
for _, prinfo := range rs.PRs {
|
||||
if rs.IsPrjGitPR(prinfo.PR) {
|
||||
if ret == nil {
|
||||
ret = prinfo.PR
|
||||
ret = prinfo
|
||||
} else {
|
||||
return nil, errors.New("Multiple PrjGit PRs in one review set")
|
||||
return nil, PRSet_MultiplePrjGit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,21 +177,37 @@ func (rs *PRSet) GetPrjGitPR() (*models.PullRequest, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("No PrjGit PR found")
|
||||
return nil, PRSet_PrjGitMissing
|
||||
}
|
||||
|
||||
func (rs *PRSet) NeedRecreatingPrjGit(currentBranchHash string) bool {
|
||||
pr, err := rs.GetPrjGitPR()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return pr.PR.Base.Sha == currentBranchHash
|
||||
}
|
||||
|
||||
func (rs *PRSet) IsConsistent() bool {
|
||||
prjpr, err := rs.GetPrjGitPR()
|
||||
prjpr_info, err := rs.GetPrjGitPR()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
prjpr := prjpr_info.PR
|
||||
|
||||
_, prjpr_set := ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(prjpr.Body)))
|
||||
if len(prjpr_set) != len(rs.PRs)-1 { // 1 to many mapping
|
||||
LogDebug("Number of PR from links:", len(prjpr_set), "is not what's expected", len(rs.PRs)-1)
|
||||
return false
|
||||
}
|
||||
|
||||
next_rs:
|
||||
for _, prinfo := range rs.PRs {
|
||||
if prinfo.PR.State != "open" {
|
||||
return false
|
||||
}
|
||||
|
||||
if prjpr == prinfo.PR {
|
||||
continue
|
||||
}
|
||||
@@ -142,14 +227,16 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
|
||||
for _, pr := range rs.PRs {
|
||||
reviewers := []string{}
|
||||
|
||||
if rs.IsPrjGitPR(pr.PR) {
|
||||
reviewers = configReviewers.Prj
|
||||
reviewers = slices.Concat(configReviewers.Prj, configReviewers.PrjOptional)
|
||||
LogDebug("PrjGit submitter:", pr.PR.User.UserName)
|
||||
if len(rs.PRs) == 1 {
|
||||
reviewers = slices.Concat(reviewers, maintainers.ListProjectMaintainers())
|
||||
}
|
||||
} else {
|
||||
pkg := pr.PR.Base.Repo.Name
|
||||
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(), maintainers.ListPackageMaintainers(pkg))
|
||||
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(), maintainers.ListPackageMaintainers(pkg), configReviewers.PkgOptional)
|
||||
}
|
||||
|
||||
slices.Sort(reviewers)
|
||||
@@ -160,9 +247,13 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
reviewers = slices.Delete(reviewers, idx, idx+1)
|
||||
}
|
||||
|
||||
LogDebug("PR: ", pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
LogDebug("reviewers for PR:", reviewers)
|
||||
|
||||
// remove reviewers that were already requested and are not stale
|
||||
reviews, err := FetchGiteaReviews(gitea, reviewers, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Error fetching reviews:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -170,6 +261,7 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
user := reviewers[idx]
|
||||
if reviews.HasPendingReviewBy(user) || reviews.IsReviewedBy(user) {
|
||||
reviewers = slices.Delete(reviewers, idx, idx+1)
|
||||
LogDebug("removing reviewer:", user)
|
||||
} else {
|
||||
idx++
|
||||
}
|
||||
@@ -177,8 +269,13 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
|
||||
// get maintainers associated with the PR too
|
||||
if len(reviewers) > 0 {
|
||||
if _, err := gitea.RequestReviews(pr.PR, reviewers...); err != nil {
|
||||
return fmt.Errorf("Cannot create reviews on %s/%s#%d for [%s]: %w", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index, strings.Join(reviewers, ", "), err)
|
||||
LogDebug("Requesting reviews from:", reviewers)
|
||||
if !IsDryRun {
|
||||
for _, r := range reviewers {
|
||||
if _, err := gitea.RequestReviews(pr.PR, r); err != nil {
|
||||
LogError("Cannot create reviews on", fmt.Sprintf("%s/%s#%d for [%s]", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index, strings.Join(reviewers, ", ")), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +285,54 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData) bool {
|
||||
configReviewers := ParseReviewers(rs.Config.Reviewers)
|
||||
|
||||
is_reviewed := false
|
||||
is_manually_reviewed_ok := false
|
||||
|
||||
if need_manual_review := rs.Config.ManualMergeOnly || rs.Config.ManualMergeProject; need_manual_review {
|
||||
prjgit, err := rs.GetPrjGitPR()
|
||||
if err == nil && prjgit != nil {
|
||||
reviewers := slices.Concat(configReviewers.Prj, maintainers.ListProjectMaintainers())
|
||||
LogDebug("Fetching reviews for", prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, reviewers, prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
prjgit.Reviews = r
|
||||
if prjgit.Reviews.IsManualMergeOK() {
|
||||
is_manually_reviewed_ok = true
|
||||
}
|
||||
}
|
||||
|
||||
if !is_manually_reviewed_ok && !rs.Config.ManualMergeProject {
|
||||
for _, pr := range rs.PRs {
|
||||
if rs.IsPrjGitPR(pr.PR) {
|
||||
continue
|
||||
}
|
||||
|
||||
pkg := pr.PR.Base.Repo.Name
|
||||
reviewers := slices.Concat(configReviewers.Pkg, maintainers.ListPackageMaintainers(pkg))
|
||||
LogDebug("Fetching reviews for", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, reviewers, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
pr.Reviews = r
|
||||
if !pr.Reviews.IsManualMergeOK() {
|
||||
LogInfo("Not approved manual merge. PR:", pr.PR.URL)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
is_manually_reviewed_ok = true
|
||||
}
|
||||
|
||||
if !is_manually_reviewed_ok {
|
||||
LogInfo("manual merge not ok")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, pr := range rs.PRs {
|
||||
var reviewers []string
|
||||
var pkg string
|
||||
@@ -210,14 +354,15 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
is_reviewed = r.IsApproved()
|
||||
LogDebug(pr.PR.Base.Repo.Name, is_reviewed)
|
||||
if !is_reviewed {
|
||||
|
||||
is_manually_reviewed_ok = r.IsApproved()
|
||||
LogDebug(pr.PR.Base.Repo.Name, is_manually_reviewed_ok)
|
||||
if !is_manually_reviewed_ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if need_maintainer_review := !rs.IsPrjGitPR(pr.PR) || pr.PR.User.UserName != rs.BotUser; need_maintainer_review {
|
||||
if is_reviewed = maintainers.IsApproved(pkg, r.reviews, pr.PR.User.UserName); !is_reviewed {
|
||||
if is_manually_reviewed_ok = maintainers.IsApproved(pkg, r.reviews, pr.PR.User.UserName); !is_manually_reviewed_ok {
|
||||
LogDebug(" not approved?", pkg)
|
||||
return false
|
||||
}
|
||||
@@ -225,14 +370,15 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
LogDebug("PrjGit PR -- bot created, no need for review")
|
||||
}
|
||||
}
|
||||
return is_reviewed
|
||||
return is_manually_reviewed_ok
|
||||
}
|
||||
|
||||
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||
prjgit, err := rs.GetPrjGitPR()
|
||||
prjgit_info, err := rs.GetPrjGitPR()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prjgit := prjgit_info.PR
|
||||
|
||||
remote, err := git.GitClone(DefaultGitPrj, rs.Config.Branch, prjgit.Base.Repo.SSHURL)
|
||||
PanicOnError(err)
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -14,6 +15,37 @@ import (
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
mock_common "src.opensuse.org/autogits/common/mock"
|
||||
)
|
||||
/*
|
||||
func TestCockpit(t *testing.T) {
|
||||
common.SetLoggingLevel(common.LogLevelDebug)
|
||||
gitea := common.AllocateGiteaTransport("https://src.opensuse.org")
|
||||
tl, err := gitea.GetTimeline("cockpit", "cockpit", 29)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to timeline", err)
|
||||
}
|
||||
t.Log(tl)
|
||||
r, err := common.FetchGiteaReviews(gitea, []string{}, "cockpit", "cockpit", 29)
|
||||
if err != nil {
|
||||
t.Fatal("Error:", err)
|
||||
}
|
||||
|
||||
t.Error(r)
|
||||
}
|
||||
*/
|
||||
func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
|
||||
timeline := make([]*models.TimelineComment, len(reviews))
|
||||
for idx, review := range reviews {
|
||||
if review.ID == 0 {
|
||||
review.ID = int64(idx) + 100
|
||||
}
|
||||
|
||||
timeline[idx] = &models.TimelineComment{
|
||||
Type: common.TimelineCommentType_Review,
|
||||
ReviewID: review.ID,
|
||||
}
|
||||
}
|
||||
return timeline
|
||||
}
|
||||
|
||||
func TestPR(t *testing.T) {
|
||||
baseConfig := common.AutogitConfig{
|
||||
@@ -27,6 +59,7 @@ func TestPR(t *testing.T) {
|
||||
pr *models.PullRequest
|
||||
pr_err error
|
||||
reviews []*models.PullReview
|
||||
timeline []*models.TimelineComment
|
||||
review_error error
|
||||
}
|
||||
|
||||
@@ -40,26 +73,26 @@ func TestPR(t *testing.T) {
|
||||
consistentSet bool
|
||||
prjGitPRIndex int
|
||||
|
||||
reviewSetFetcher func(*mock_common.MockGiteaPRFetcher) (*common.PRSet, error)
|
||||
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error)
|
||||
}{
|
||||
{
|
||||
name: "Error fetching PullRequest",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}, pr_err: errors.New("Missing PR")},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("Missing PR")},
|
||||
},
|
||||
prjGitPRIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "Error fetching PullRequest in PrjGit",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}, pr_err: errors.New("missing PR")},
|
||||
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("missing PR")},
|
||||
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error fetching prjgit",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 1,
|
||||
prjGitPRIndex: -1,
|
||||
@@ -67,8 +100,8 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -78,8 +111,8 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent: 1pkg",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -88,9 +121,9 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent: 2pkg",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "some desc", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/fer#33", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "some desc", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/fer#33", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -100,7 +133,7 @@ func TestPR(t *testing.T) {
|
||||
name: "Review set of prjgit PR is consistent",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}},
|
||||
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -112,38 +145,385 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet(mock, "foo", "barPrj", 42, &baseConfig)
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Review set is consistent: 2pkg",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 2,
|
||||
consistentSet: true,
|
||||
},
|
||||
{
|
||||
name: "WIP PR is not approved",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "", Title: "WIP: some title", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 1,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is missing",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is done, via PrjGit",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is done, via PrjGit",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is not done, via PrjGit",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is done via PackageGit",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review done via PkgGits",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review done via PkgGits not allowed",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is is missing on one PR",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PR is approved with negative optional review",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "bot"}, State: common.ReviewStateRequestChanges},
|
||||
},
|
||||
},
|
||||
},
|
||||
resLen: 1,
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
config := common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj",
|
||||
}
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRFetcher(ctl)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaPRChecker(ctl)
|
||||
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
|
||||
|
||||
if test.reviewSetFetcher == nil { // if we are fetching the prjgit directly, the these mocks are not called
|
||||
if test.prjGitPRIndex >= 0 {
|
||||
pr_mock.EXPECT().GetAssociatedPrjGitPR(baseConfig.Organization, baseConfig.GitProjectName, test.data[0].pr.Base.Repo.Owner.UserName, test.data[0].pr.Base.Repo.Name, test.data[0].pr.Index).
|
||||
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, test.prjGitPRIndex).
|
||||
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err)
|
||||
} else if test.prjGitPRIndex < 0 {
|
||||
// no prjgit PR
|
||||
pr_mock.EXPECT().GetAssociatedPrjGitPR(baseConfig.Organization, baseConfig.GitProjectName, test.data[0].pr.Base.Repo.Owner.UserName, test.data[0].pr.Base.Repo.Name, test.data[0].pr.Index).
|
||||
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, gomock.Any()).
|
||||
Return(nil, nil)
|
||||
}
|
||||
}
|
||||
@@ -155,6 +535,10 @@ func TestPR(t *testing.T) {
|
||||
test_err = data.pr_err
|
||||
}
|
||||
review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
|
||||
if data.timeline == nil {
|
||||
data.timeline = reviewsToTimeline(data.reviews)
|
||||
}
|
||||
review_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
|
||||
}
|
||||
|
||||
var res *common.PRSet
|
||||
@@ -163,7 +547,7 @@ func TestPR(t *testing.T) {
|
||||
if test.reviewSetFetcher != nil {
|
||||
res, err = test.reviewSetFetcher(pr_mock)
|
||||
} else {
|
||||
res, err = common.FetchPRSet(pr_mock, "test", "repo", 42, &baseConfig)
|
||||
res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -198,7 +582,7 @@ func TestPR(t *testing.T) {
|
||||
pr_found := false
|
||||
if test.prjGitPRIndex >= 0 {
|
||||
for i := range test.data {
|
||||
if PrjGitPR == test.data[i].pr && i == test.prjGitPRIndex {
|
||||
if PrjGitPR.PR == test.data[i].pr && i == test.prjGitPRIndex {
|
||||
t.Log("found at index", i)
|
||||
pr_found = true
|
||||
}
|
||||
@@ -222,6 +606,8 @@ func TestPR(t *testing.T) {
|
||||
*/
|
||||
|
||||
maintainers := mock_common.NewMockMaintainershipData(ctl)
|
||||
maintainers.EXPECT().ListPackageMaintainers(gomock.Any()).Return([]string{}).AnyTimes()
|
||||
maintainers.EXPECT().ListProjectMaintainers().Return([]string{}).AnyTimes()
|
||||
maintainers.EXPECT().IsApproved(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes()
|
||||
|
||||
if isApproved := res.IsApproved(review_mock, maintainers); isApproved != test.reviewed {
|
||||
@@ -242,8 +628,10 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
reviewer string
|
||||
}
|
||||
|
||||
pkgReviews []*models.PullReview
|
||||
prjReviews []*models.PullReview
|
||||
pkgReviews []*models.PullReview
|
||||
pkgTimeline []*models.TimelineComment
|
||||
prjReviews []*models.PullReview
|
||||
prjTimeline []*models.TimelineComment
|
||||
|
||||
expectedReviewerCall [2][]string
|
||||
}{
|
||||
@@ -331,8 +719,8 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
},
|
||||
pkgReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
},
|
||||
{
|
||||
State: common.ReviewStatePending,
|
||||
@@ -353,15 +741,59 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "Stale optional review is not done, re-request it",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2", "~bot"},
|
||||
},
|
||||
pkgReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "bot"},
|
||||
Stale: true,
|
||||
},
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
},
|
||||
{
|
||||
State: common.ReviewStatePending,
|
||||
User: &models.User{UserName: "prjmaintainer"},
|
||||
},
|
||||
},
|
||||
prjReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateRequestChanges,
|
||||
User: &models.User{UserName: "user1"},
|
||||
Stale: true,
|
||||
},
|
||||
{
|
||||
State: common.ReviewStateRequestReview,
|
||||
Stale: true,
|
||||
User: &models.User{UserName: "autogits_obs_staging_bot"},
|
||||
},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer", "bot"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRFetcher(ctl)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
|
||||
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
|
||||
|
||||
if test.pkgTimeline == nil {
|
||||
test.pkgTimeline = reviewsToTimeline(test.pkgReviews)
|
||||
}
|
||||
if test.prjTimeline == nil {
|
||||
test.prjTimeline = reviewsToTimeline(test.prjReviews)
|
||||
}
|
||||
|
||||
pr_mock.EXPECT().GetPullRequest("other", "pkgrepo", int64(1)).Return(&models.PullRequest{
|
||||
Body: "Some description is here",
|
||||
User: &models.User{UserName: "submitter"},
|
||||
@@ -371,7 +803,8 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
Index: 1,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("other", "pkgrepo", int64(1)).Return(test.pkgReviews, nil)
|
||||
pr_mock.EXPECT().GetAssociatedPrjGitPR("org", "repo", "other", "pkgrepo", int64(1)).Return(&models.PullRequest{
|
||||
review_mock.EXPECT().GetTimeline("other", "pkgrepo", int64(1)).Return(test.pkgTimeline, nil)
|
||||
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
|
||||
Body: fmt.Sprintf(common.PrPattern, "other", "pkgrepo", 1),
|
||||
User: &models.User{UserName: "bot1"},
|
||||
RequestedReviewers: []*models.User{{UserName: "main_reviewer"}},
|
||||
@@ -380,11 +813,12 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
Index: 42,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(42)).Return(test.prjReviews, nil)
|
||||
review_mock.EXPECT().GetTimeline("org", "repo", int64(42)).Return(test.prjTimeline, nil)
|
||||
|
||||
maintainership_mock.EXPECT().ListProjectMaintainers().Return([]string{"prjmaintainer"}).AnyTimes()
|
||||
maintainership_mock.EXPECT().ListPackageMaintainers("pkgrepo").Return([]string{"pkgmaintainer"}).AnyTimes()
|
||||
|
||||
prs, _ := common.FetchPRSet(pr_mock, "other", "pkgrepo", int64(1), &test.config)
|
||||
prs, _ := common.FetchPRSet("test", pr_mock, "other", "pkgrepo", int64(1), &test.config)
|
||||
if len(prs.PRs) != 2 {
|
||||
t.Fatal("PRs not fetched")
|
||||
}
|
||||
@@ -393,8 +827,9 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
if !prs.IsPrjGitPR(pr.PR) {
|
||||
r = test.expectedReviewerCall[1]
|
||||
}
|
||||
if len(r) > 0 {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, r).Return(nil, nil)
|
||||
slices.Sort(r)
|
||||
for _, reviewer := range r {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
|
||||
}
|
||||
}
|
||||
prs.AssignReviewers(review_mock, maintainership_mock)
|
||||
@@ -428,7 +863,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
for _, test := range prjgit_tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRFetcher(ctl)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
|
||||
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
|
||||
|
||||
@@ -441,10 +876,11 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
Index: 1,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(1)).Return(test.prjReviews, nil)
|
||||
review_mock.EXPECT().GetTimeline("org", "repo", int64(1)).Return(nil, nil)
|
||||
|
||||
maintainership_mock.EXPECT().ListProjectMaintainers().Return([]string{"prjmaintainer"}).AnyTimes()
|
||||
|
||||
prs, _ := common.FetchPRSet(pr_mock, "org", "repo", int64(1), &test.config)
|
||||
prs, _ := common.FetchPRSet("test", pr_mock, "org", "repo", int64(1), &test.config)
|
||||
if len(prs.PRs) != 1 {
|
||||
t.Fatal("PRs not fetched")
|
||||
}
|
||||
@@ -453,8 +889,8 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
if !prs.IsPrjGitPR(pr.PR) {
|
||||
t.Fatal("only prjgit pr here")
|
||||
}
|
||||
if len(r) > 0 {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, r).Return(nil, nil)
|
||||
for _, reviewer := range r {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
|
||||
}
|
||||
}
|
||||
prs.AssignReviewers(review_mock, maintainership_mock)
|
||||
@@ -516,7 +952,7 @@ func TestPRMerge(t *testing.T) {
|
||||
mergeError: "Aborting merge",
|
||||
},
|
||||
{
|
||||
name: "Merge conflict in modules",
|
||||
name: "Merge conflict in modules, auto-resolved",
|
||||
|
||||
pr: &models.PullRequest{
|
||||
Base: &models.PRBranchInfo{
|
||||
@@ -539,19 +975,23 @@ func TestPRMerge(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock := mock_common.NewMockGiteaPRFetcher(ctl)
|
||||
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
||||
|
||||
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
|
||||
testDir := t.TempDir()
|
||||
t.Log("dir:", testDir)
|
||||
mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)
|
||||
|
||||
set, err := common.FetchPRSet(mock, "org", "prj", 1, config)
|
||||
set, err := common.FetchPRSet("test", mock, "org", "prj", 1, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gh, _ := common.AllocateGitWorkTree(testDir, "", "")
|
||||
err = set.Merge(gh)
|
||||
git, err := gh.CreateGitHandler("org")
|
||||
err = set.Merge(reviewUnrequestMock, git)
|
||||
|
||||
if err != nil && (test.mergeError == "" || (len(test.mergeError) > 0 && !strings.Contains(err.Error(), test.mergeError))) {
|
||||
os.CopyFS("/tmp/upstream", os.DirFS(repoDir))
|
||||
@@ -561,3 +1001,53 @@ func TestPRMerge(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRChanges(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
PRs []*models.PullRequest
|
||||
PrjPRs *models.PullRequest
|
||||
}{
|
||||
{
|
||||
name: "Pkg PR is closed",
|
||||
PRs: []*models.PullRequest{
|
||||
{
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Owner: &models.User{UserName: "org"}, Name: "repo"}},
|
||||
Index: 42,
|
||||
State: "merged",
|
||||
},
|
||||
},
|
||||
PrjPRs: &models.PullRequest{
|
||||
Title: "some PR",
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
|
||||
Body: "PR: org/repo#42",
|
||||
State: "opened",
|
||||
},
|
||||
},
|
||||
}
|
||||
config := common.AutogitConfig{
|
||||
Branch: "main",
|
||||
GitProjectName: "org/prjgit#branch",
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock_fetcher := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
|
||||
for _, pr := range test.PRs {
|
||||
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
|
||||
}
|
||||
|
||||
PRs, err := common.FetchPRSet("user", mock_fetcher, "org", "repo", 42, &config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if PRs.IsConsistent() {
|
||||
t.Fatal("Inconsistent set!")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -22,55 +22,32 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
rabbitmq "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
const RequestType_CreateBrachTag = "create"
|
||||
const RequestType_DeleteBranchTag = "delete"
|
||||
const RequestType_Fork = "fork"
|
||||
const RequestType_Issue = "issues"
|
||||
const RequestType_IssueAssign = "issue_assign"
|
||||
const RequestType_IssueComment = "issue_comment"
|
||||
const RequestType_IssueLabel = "issue_label"
|
||||
const RequestType_IssueMilestone = "issue_milestone"
|
||||
const RequestType_Push = "push"
|
||||
const RequestType_Repository = "repository"
|
||||
const RequestType_Release = "release"
|
||||
const RequestType_PR = "pull_request"
|
||||
const RequestType_PRAssign = "pull_request_assign"
|
||||
const RequestType_PRLabel = "pull_request_label"
|
||||
const RequestType_PRComment = "pull_request_comment"
|
||||
const RequestType_PRMilestone = "pull_request_milestone"
|
||||
const RequestType_PRSync = "pull_request_sync"
|
||||
const RequestType_PRReviewAccepted = "pull_request_review_approved"
|
||||
const RequestType_PRReviewRejected = "pull_request_review_rejected"
|
||||
const RequestType_PRReviewRequest = "pull_request_review_request"
|
||||
const RequestType_PRReviewComment = "pull_request_review_comment"
|
||||
const RequestType_Wiki = "wiki"
|
||||
|
||||
type RequestProcessor interface {
|
||||
ProcessFunc(*Request) error
|
||||
}
|
||||
|
||||
type ListenDefinitions struct {
|
||||
type RabbitConnection struct {
|
||||
RabbitURL *url.URL // amqps://user:password@host/queue
|
||||
|
||||
GitAuthor string
|
||||
Handlers map[string]RequestProcessor
|
||||
Orgs []string
|
||||
queueName string
|
||||
ch *rabbitmq.Channel
|
||||
|
||||
topics []string
|
||||
topicSubChanges chan string // +topic = subscribe, -topic = unsubscribe
|
||||
}
|
||||
|
||||
type RabbitProcessor interface {
|
||||
GenerateTopics() []string
|
||||
|
||||
Connection() *RabbitConnection
|
||||
ProcessRabbitMessage(msg RabbitMessage) error
|
||||
}
|
||||
|
||||
type RabbitMessage rabbitmq.Delivery
|
||||
|
||||
func (l *ListenDefinitions) processTopicChanges(ch *rabbitmq.Channel, queueName string) {
|
||||
func (l *RabbitConnection) ProcessTopicChanges() {
|
||||
for {
|
||||
topic, ok := <-l.topicSubChanges
|
||||
if !ok {
|
||||
@@ -80,11 +57,11 @@ func (l *ListenDefinitions) processTopicChanges(ch *rabbitmq.Channel, queueName
|
||||
LogDebug(" topic change:", topic)
|
||||
switch topic[0] {
|
||||
case '+':
|
||||
if err := ch.QueueBind(queueName, topic[1:], "pubsub", false, nil); err != nil {
|
||||
if err := l.ch.QueueBind(l.queueName, topic[1:], "pubsub", false, nil); err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
case '-':
|
||||
if err := ch.QueueUnbind(queueName, topic[1:], "pubsub", nil); err != nil {
|
||||
if err := l.ch.QueueUnbind(l.queueName, topic[1:], "pubsub", nil); err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
default:
|
||||
@@ -93,7 +70,7 @@ func (l *ListenDefinitions) processTopicChanges(ch *rabbitmq.Channel, queueName
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) processRabbitMQ(msgCh chan<- RabbitMessage) error {
|
||||
func (l *RabbitConnection) ProcessRabbitMQ(msgCh chan<- RabbitMessage) error {
|
||||
queueName := l.RabbitURL.Path
|
||||
l.RabbitURL.Path = ""
|
||||
|
||||
@@ -152,7 +129,7 @@ func (l *ListenDefinitions) processRabbitMQ(msgCh chan<- RabbitMessage) error {
|
||||
LogDebug(" -- listening to topics:")
|
||||
l.topicSubChanges = make(chan string)
|
||||
defer close(l.topicSubChanges)
|
||||
go l.processTopicChanges(ch, q.Name)
|
||||
go l.ProcessTopicChanges()
|
||||
|
||||
for _, topic := range l.topics {
|
||||
l.topicSubChanges <- "+" + topic
|
||||
@@ -174,18 +151,18 @@ func (l *ListenDefinitions) processRabbitMQ(msgCh chan<- RabbitMessage) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) connectAndProcessRabbitMQ(ch chan<- RabbitMessage) {
|
||||
func (l *RabbitConnection) ConnectAndProcessRabbitMQ(ch chan<- RabbitMessage) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
LogError(r)
|
||||
LogError("'crash' RabbitMQ worker. Recovering... reconnecting...")
|
||||
time.Sleep(5 * time.Second)
|
||||
go l.connectAndProcessRabbitMQ(ch)
|
||||
go l.ConnectAndProcessRabbitMQ(ch)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
err := l.processRabbitMQ(ch)
|
||||
err := l.ProcessRabbitMQ(ch)
|
||||
if err != nil {
|
||||
LogError("Error in RabbitMQ connection. %#v", err)
|
||||
LogInfo("Reconnecting in 2 seconds...")
|
||||
@@ -194,49 +171,20 @@ func (l *ListenDefinitions) connectAndProcessRabbitMQ(ch chan<- RabbitMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) connectToRabbitMQ() chan RabbitMessage {
|
||||
func (l *RabbitConnection) ConnectToRabbitMQ(processor RabbitProcessor) <-chan RabbitMessage {
|
||||
LogInfo("RabbitMQ connection:", l.RabbitURL.String())
|
||||
|
||||
l.RabbitURL.User = url.UserPassword(rabbitUser, rabbitPassword)
|
||||
l.topics = processor.GenerateTopics()
|
||||
|
||||
ch := make(chan RabbitMessage, 100)
|
||||
go l.connectAndProcessRabbitMQ(ch)
|
||||
go l.ConnectAndProcessRabbitMQ(ch)
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func ProcessEvent(f RequestProcessor, request *Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
LogError("panic caught")
|
||||
if err, ok := r.(error); !ok {
|
||||
LogError(err)
|
||||
}
|
||||
LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := f.ProcessFunc(request); err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) generateTopics() []string {
|
||||
topics := make([]string, 0, len(l.Handlers)*len(l.Orgs))
|
||||
scope := "suse"
|
||||
if l.RabbitURL.Hostname() == "rabbit.opensuse.org" {
|
||||
scope = "opensuse"
|
||||
}
|
||||
|
||||
for _, org := range l.Orgs {
|
||||
for requestType, _ := range l.Handlers {
|
||||
topics = append(topics, fmt.Sprintf("%s.src.%s.%s.#", scope, org, requestType))
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(topics)
|
||||
return slices.Compact(topics)
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) UpdateTopics() {
|
||||
newTopics := l.generateTopics()
|
||||
func (l *RabbitConnection) UpdateTopics(processor RabbitProcessor) {
|
||||
newTopics := processor.GenerateTopics()
|
||||
|
||||
j := 0
|
||||
next_new_topic:
|
||||
@@ -273,14 +221,8 @@ next_new_topic:
|
||||
l.topics = newTopics
|
||||
}
|
||||
|
||||
func (l *ListenDefinitions) ProcessRabbitMQEvents() error {
|
||||
LogInfo("RabbitMQ connection:", l.RabbitURL.String())
|
||||
LogDebug("# Handlers:", len(l.Handlers))
|
||||
LogDebug("# Orgs:", len(l.Orgs))
|
||||
|
||||
l.RabbitURL.User = url.UserPassword(rabbitUser, rabbitPassword)
|
||||
l.topics = l.generateTopics()
|
||||
ch := l.connectToRabbitMQ()
|
||||
func ProcessRabbitMQEvents(processor RabbitProcessor) error {
|
||||
ch := processor.Connection().ConnectToRabbitMQ(processor)
|
||||
|
||||
for {
|
||||
msg, ok := <-ch
|
||||
@@ -289,36 +231,8 @@ func (l *ListenDefinitions) ProcessRabbitMQEvents() error {
|
||||
}
|
||||
|
||||
LogDebug("event:", msg.RoutingKey)
|
||||
|
||||
route := strings.Split(msg.RoutingKey, ".")
|
||||
if len(route) > 3 {
|
||||
reqType := route[3]
|
||||
org := route[2]
|
||||
|
||||
if !slices.Contains(l.Orgs, org) {
|
||||
LogInfo("Got event for unhandeled org:", org)
|
||||
continue
|
||||
}
|
||||
|
||||
LogDebug("org:", org, "type:", reqType)
|
||||
if handler, found := l.Handlers[reqType]; found {
|
||||
/* h, err := CreateRequestHandler()
|
||||
if err != nil {
|
||||
log.Println("Cannot create request handler", err)
|
||||
continue
|
||||
}
|
||||
*/
|
||||
req, err := ParseRequestJSON(reqType, msg.Body)
|
||||
if err != nil {
|
||||
LogError("Error parsing request JSON:", err)
|
||||
continue
|
||||
} else {
|
||||
LogDebug("processing req", req.Type)
|
||||
// h.Request = req
|
||||
ProcessEvent(handler, req)
|
||||
|
||||
}
|
||||
}
|
||||
if err := processor.ProcessRabbitMessage(msg); err != nil {
|
||||
LogError("Error processing", msg.RoutingKey, err)
|
||||
}
|
||||
}
|
||||
}
|
130
common/rabbitmq_gitea.go
Normal file
130
common/rabbitmq_gitea.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package common
|
||||
|
||||
/*
|
||||
* This file is part of Autogits.
|
||||
*
|
||||
* Copyright © 2024 SUSE LLC
|
||||
*
|
||||
* Autogits is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 2 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* Foobar. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestType_CreateBrachTag = "create"
|
||||
RequestType_DeleteBranchTag = "delete"
|
||||
RequestType_Fork = "fork"
|
||||
RequestType_Issue = "issues"
|
||||
RequestType_IssueAssign = "issue_assign"
|
||||
RequestType_IssueComment = "issue_comment"
|
||||
RequestType_IssueLabel = "issue_label"
|
||||
RequestType_IssueMilestone = "issue_milestone"
|
||||
RequestType_Push = "push"
|
||||
RequestType_Repository = "repository"
|
||||
RequestType_Release = "release"
|
||||
RequestType_PR = "pull_request"
|
||||
RequestType_PRAssign = "pull_request_assign"
|
||||
RequestType_PRLabel = "pull_request_label"
|
||||
RequestType_PRComment = "pull_request_comment"
|
||||
RequestType_PRMilestone = "pull_request_milestone"
|
||||
RequestType_PRSync = "pull_request_sync"
|
||||
RequestType_PRReviewAccepted = "pull_request_review_approved"
|
||||
RequestType_PRReviewRejected = "pull_request_review_rejected"
|
||||
RequestType_PRReviewRequest = "pull_request_review_request"
|
||||
RequestType_PRReviewComment = "pull_request_review_comment"
|
||||
RequestType_Wiki = "wiki"
|
||||
)
|
||||
|
||||
type RequestProcessor interface {
|
||||
ProcessFunc(*Request) error
|
||||
}
|
||||
|
||||
type RabbitMQGiteaEventsProcessor struct {
|
||||
Handlers map[string]RequestProcessor
|
||||
Orgs []string
|
||||
|
||||
c *RabbitConnection
|
||||
}
|
||||
|
||||
func (gitea *RabbitMQGiteaEventsProcessor) Connection() *RabbitConnection {
|
||||
if gitea.c == nil {
|
||||
gitea.c = &RabbitConnection{}
|
||||
}
|
||||
return gitea.c
|
||||
}
|
||||
|
||||
func (gitea *RabbitMQGiteaEventsProcessor) GenerateTopics() []string {
|
||||
topics := make([]string, 0, len(gitea.Handlers)*len(gitea.Orgs))
|
||||
scope := "suse"
|
||||
if gitea.c.RabbitURL.Hostname() == "rabbit.opensuse.org" {
|
||||
scope = "opensuse"
|
||||
}
|
||||
|
||||
for _, org := range gitea.Orgs {
|
||||
for requestType, _ := range gitea.Handlers {
|
||||
topics = append(topics, fmt.Sprintf("%s.src.%s.%s.#", scope, org, requestType))
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(topics)
|
||||
return slices.Compact(topics)
|
||||
}
|
||||
|
||||
func (gitea *RabbitMQGiteaEventsProcessor) ProcessRabbitMessage(msg RabbitMessage) error {
|
||||
route := strings.Split(msg.RoutingKey, ".")
|
||||
if len(route) > 3 {
|
||||
reqType := route[3]
|
||||
org := route[2]
|
||||
|
||||
if !slices.Contains(gitea.Orgs, org) {
|
||||
LogInfo("Got event for unhandeled org:", org)
|
||||
return nil
|
||||
}
|
||||
|
||||
LogDebug("org:", org, "type:", reqType)
|
||||
if handler, found := gitea.Handlers[reqType]; found {
|
||||
req, err := ParseRequestJSON(reqType, msg.Body)
|
||||
if err != nil {
|
||||
LogError("Error parsing request JSON:", err)
|
||||
return nil
|
||||
} else {
|
||||
LogDebug("processing req", req.Type)
|
||||
// h.Request = req
|
||||
ProcessEvent(handler, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Invalid routing key: %s", route)
|
||||
}
|
||||
|
||||
func ProcessEvent(f RequestProcessor, request *Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
LogError("panic caught")
|
||||
if err, ok := r.(error); !ok {
|
||||
LogError(err)
|
||||
}
|
||||
LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if err := f.ProcessFunc(request); err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
}
|
115
common/rabbitmq_obs.go
Normal file
115
common/rabbitmq_obs.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ObsMessageType_PackageBuildFail = "package.build_fail"
|
||||
ObsMessageType_PackageBuildSuccess = "package.build_success"
|
||||
ObsMessageType_PackageBuildUnchanged = "package.build_unchanged"
|
||||
|
||||
ObsMessageType_RepoBuildFinished = "repo.build_finished"
|
||||
ObsMessageType_RepoBuildStarted = "repo.build_started"
|
||||
)
|
||||
|
||||
type BuildResultMsg struct {
|
||||
Status string
|
||||
Project string `json:"project"`
|
||||
Package string `json:"package"`
|
||||
Repo string `json:"repository"`
|
||||
Arch string `json:"arch"`
|
||||
|
||||
StartTime int32 `json:"starttime"`
|
||||
EndTime int32 `json:"endtime"`
|
||||
WorkerID string `json:"workerid"`
|
||||
Version string `json:"versrel"`
|
||||
Build string `json:"buildtype"`
|
||||
}
|
||||
|
||||
type RepoBuildMsg struct {
|
||||
Status string
|
||||
Project string `json:"project"`
|
||||
Repo string `json:"repo"`
|
||||
Arch string `json:"arch"`
|
||||
BuildId string `json:"buildid"`
|
||||
}
|
||||
|
||||
var ObsRabbitMessageError_UnknownMessageType error = errors.New("Unknown message type")
|
||||
var ObsRabbitMessageError_ParseError error = errors.New("JSON parsing error")
|
||||
|
||||
func ParseObsRabbitMessaege(ObsMessageType string, data []byte) (interface{}, error) {
|
||||
unmarshall := func(data []byte, v any) (interface{}, error) {
|
||||
if err := json.Unmarshal(data, v); err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", ObsRabbitMessageError_ParseError, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
switch ObsMessageType {
|
||||
case ObsMessageType_PackageBuildSuccess, ObsMessageType_PackageBuildUnchanged:
|
||||
ret := &BuildResultMsg{Status: "succeeded"}
|
||||
return unmarshall(data, ret)
|
||||
case ObsMessageType_PackageBuildFail:
|
||||
ret := &BuildResultMsg{Status: "failed"}
|
||||
return unmarshall(data, ret)
|
||||
case ObsMessageType_RepoBuildFinished:
|
||||
ret := &RepoBuildMsg{Status: "finished"}
|
||||
return unmarshall(data, ret)
|
||||
case ObsMessageType_RepoBuildStarted:
|
||||
ret := &RepoBuildMsg{Status: "building"}
|
||||
return unmarshall(data, ret)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: %s", ObsRabbitMessageError_UnknownMessageType, ObsMessageType)
|
||||
}
|
||||
|
||||
type ObsMessageProcessor func(topic string, data []byte) error
|
||||
|
||||
type RabbitMQObsBuildStatusProcessor struct {
|
||||
Handlers map[string]ObsMessageProcessor
|
||||
|
||||
c *RabbitConnection
|
||||
}
|
||||
|
||||
func (o *RabbitMQObsBuildStatusProcessor) routingKeyPrefix() string {
|
||||
if strings.HasSuffix(o.c.RabbitURL.Hostname(), "opensuse.org") {
|
||||
return "opensuse"
|
||||
}
|
||||
|
||||
return "suse"
|
||||
}
|
||||
|
||||
func (o *RabbitMQObsBuildStatusProcessor) GenerateTopics() []string {
|
||||
prefix := o.routingKeyPrefix()
|
||||
msgs := make([]string, len(o.Handlers))
|
||||
idx := 0
|
||||
for k, _ := range o.Handlers {
|
||||
msgs[idx] = prefix + ".obs." + k
|
||||
idx++
|
||||
}
|
||||
slices.Sort(msgs)
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (o *RabbitMQObsBuildStatusProcessor) Connection() *RabbitConnection {
|
||||
if o.c == nil {
|
||||
o.c = &RabbitConnection{}
|
||||
}
|
||||
|
||||
return o.c
|
||||
}
|
||||
|
||||
func (o *RabbitMQObsBuildStatusProcessor) ProcessRabbitMessage(msg RabbitMessage) error {
|
||||
prefix := o.routingKeyPrefix() + ".obs."
|
||||
topic := strings.TrimPrefix(msg.RoutingKey, prefix)
|
||||
if h, ok := o.Handlers[topic]; ok {
|
||||
return h(topic, msg.Body)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unhandled message received: %s", msg.RoutingKey)
|
||||
}
|
@@ -50,11 +50,13 @@ func TestListenDefinitionsTopicUpdate(t *testing.T) {
|
||||
u, _ := url.Parse("amqps://rabbit.example.com")
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := ListenDefinitions{
|
||||
Orgs: test.orgs1,
|
||||
Handlers: make(map[string]RequestProcessor),
|
||||
topicSubChanges: make(chan string, len(test.topicDelta)*10),
|
||||
RabbitURL: u,
|
||||
l := &RabbitMQGiteaEventsProcessor{
|
||||
Orgs: test.orgs1,
|
||||
Handlers: make(map[string]RequestProcessor),
|
||||
c: &RabbitConnection{
|
||||
RabbitURL: u,
|
||||
topicSubChanges: make(chan string, len(test.topicDelta)*10),
|
||||
},
|
||||
}
|
||||
|
||||
slices.Sort(test.topicDelta)
|
||||
@@ -64,11 +66,11 @@ func TestListenDefinitionsTopicUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
changes := []string{}
|
||||
l.UpdateTopics()
|
||||
l.c.UpdateTopics(l)
|
||||
a:
|
||||
for {
|
||||
select {
|
||||
case c := <-l.topicSubChanges:
|
||||
case c := <-l.c.topicSubChanges:
|
||||
changes = append(changes, c)
|
||||
default:
|
||||
changes = []string{}
|
||||
@@ -78,13 +80,13 @@ func TestListenDefinitionsTopicUpdate(t *testing.T) {
|
||||
|
||||
l.Orgs = test.orgs2
|
||||
|
||||
l.UpdateTopics()
|
||||
l.c.UpdateTopics(l)
|
||||
changes = []string{}
|
||||
|
||||
b:
|
||||
for {
|
||||
select {
|
||||
case c := <-l.topicSubChanges:
|
||||
case c := <-l.c.topicSubChanges:
|
||||
changes = append(changes, c)
|
||||
default:
|
||||
slices.Sort(changes)
|
@@ -7,21 +7,33 @@ import (
|
||||
type Reviewers struct {
|
||||
Prj []string
|
||||
Pkg []string
|
||||
|
||||
PrjOptional []string
|
||||
PkgOptional []string
|
||||
}
|
||||
|
||||
func ParseReviewers(input []string) *Reviewers {
|
||||
r := &Reviewers{}
|
||||
for _, reviewer := range input {
|
||||
pkg := &r.Pkg
|
||||
prj := &r.Prj
|
||||
|
||||
if reviewer[0] == '~' {
|
||||
pkg = &r.PkgOptional
|
||||
prj = &r.PrjOptional
|
||||
reviewer = reviewer[1:]
|
||||
}
|
||||
|
||||
switch reviewer[0] {
|
||||
case '*':
|
||||
r.Prj = append(r.Prj, reviewer[1:])
|
||||
r.Pkg = append(r.Pkg, reviewer[1:])
|
||||
*prj = append(*prj, reviewer[1:])
|
||||
*pkg = append(*pkg, reviewer[1:])
|
||||
case '-':
|
||||
r.Prj = append(r.Prj, reviewer[1:])
|
||||
*prj = append(*prj, reviewer[1:])
|
||||
case '+':
|
||||
r.Pkg = append(r.Pkg, reviewer[1:])
|
||||
*pkg = append(*pkg, reviewer[1:])
|
||||
default:
|
||||
r.Pkg = append(r.Pkg, reviewer)
|
||||
*pkg = append(*pkg, reviewer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,10 @@ func TestReviewers(t *testing.T) {
|
||||
name string
|
||||
input []string
|
||||
|
||||
prj []string
|
||||
pkg []string
|
||||
prj []string
|
||||
pkg []string
|
||||
pkg_optional []string
|
||||
prj_optional []string
|
||||
}{
|
||||
{
|
||||
name: "project and package reviewers",
|
||||
@@ -22,6 +24,15 @@ func TestReviewers(t *testing.T) {
|
||||
prj: []string{"5", "7", common.Bot_BuildReview},
|
||||
pkg: []string{"1", "2", "3", "5", "6"},
|
||||
},
|
||||
{
|
||||
name: "optional project and package reviewers",
|
||||
input: []string{"~1", "2", "3", "~*5", "+6", "-7"},
|
||||
|
||||
prj: []string{"7", common.Bot_BuildReview},
|
||||
pkg: []string{"2", "3", "6"},
|
||||
prj_optional: []string{"5"},
|
||||
pkg_optional: []string{"1", "5"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -31,7 +42,13 @@ func TestReviewers(t *testing.T) {
|
||||
t.Error("unexpected return of ForProject():", reviewers.Prj)
|
||||
}
|
||||
if !slices.Equal(reviewers.Pkg, test.pkg) {
|
||||
t.Error("unexpected return of ForProject():", reviewers.Pkg)
|
||||
t.Error("unexpected return of ForPackage():", reviewers.Pkg)
|
||||
}
|
||||
if !slices.Equal(reviewers.PrjOptional, test.prj_optional) {
|
||||
t.Error("unexpected return of ForProjectOptional():", reviewers.Prj)
|
||||
}
|
||||
if !slices.Equal(reviewers.PkgOptional, test.pkg_optional) {
|
||||
t.Error("unexpected return of ForPackageOptional():", reviewers.Pkg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
@@ -9,28 +11,110 @@ import (
|
||||
type PRReviews struct {
|
||||
reviews []*models.PullReview
|
||||
reviewers []string
|
||||
comments []*models.TimelineComment
|
||||
}
|
||||
|
||||
func FetchGiteaReviews(rf GiteaReviewFetcher, reviewers []string, org, repo string, no int64) (*PRReviews, error) {
|
||||
reviews, err := rf.GetPullRequestReviews(org, repo, no)
|
||||
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, reviewers []string, org, repo string, no int64) (*PRReviews, error) {
|
||||
timeline, err := rf.GetTimeline(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawReviews, err := rf.GetPullRequestReviews(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]*models.PullReview, 0, len(reviewers))
|
||||
var comments []*models.TimelineComment
|
||||
|
||||
alreadyHaveUserReview := func(user string) bool {
|
||||
for _, r := range reviews {
|
||||
if r.User != nil && r.User.UserName == user {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for idx, item := range timeline {
|
||||
if item.Type == TimelineCommentType_Review {
|
||||
for _, r := range rawReviews {
|
||||
if r.ID == item.ReviewID {
|
||||
if !alreadyHaveUserReview(r.User.UserName) {
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if item.Type == TimelineCommentType_Comment {
|
||||
comments = append(comments, item)
|
||||
} else if item.Type == TimelineCommentType_PushPull {
|
||||
LogDebug("cut-off", item.Created)
|
||||
timeline = timeline[0:idx]
|
||||
break
|
||||
} else {
|
||||
LogDebug("Unhandled timeline type:", item.Type)
|
||||
}
|
||||
}
|
||||
LogDebug("num comments:", len(comments), "reviews:", len(reviews), len(timeline))
|
||||
|
||||
return &PRReviews{
|
||||
reviews: reviews,
|
||||
reviewers: reviewers,
|
||||
comments: comments,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const ManualMergeOK = "^merge\\s+ok(\\W|$)"
|
||||
|
||||
var merge_ok_regex *regexp.Regexp = regexp.MustCompile(ManualMergeOK)
|
||||
|
||||
func bodyCommandManualMergeOK(body string) bool {
|
||||
lines := SplitLines(body)
|
||||
for _, line := range lines {
|
||||
if merge_ok_regex.MatchString(strings.ToLower(line)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsManualMergeOK() bool {
|
||||
for _, c := range r.comments {
|
||||
if c.Updated != c.Created {
|
||||
continue
|
||||
}
|
||||
LogDebug("comment:", c.User.UserName, c.Body)
|
||||
if slices.Contains(r.reviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range r.reviews {
|
||||
if c.Updated != c.Submitted {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(r.reviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsApproved() bool {
|
||||
goodReview := true
|
||||
|
||||
LogDebug("reviewers:", r.reviewers)
|
||||
for _, reviewer := range r.reviewers {
|
||||
goodReview = false
|
||||
for _, review := range r.reviews {
|
||||
if review.User.UserName == reviewer && review.State == ReviewStateApproved && !review.Stale && !review.Dismissed {
|
||||
LogDebug(" -- found review: ", review.User.UserName)
|
||||
goodReview = true
|
||||
break
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ func TestReviews(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reviews []*models.PullReview
|
||||
timeline []*models.TimelineComment
|
||||
reviewers []string
|
||||
fetchErr error
|
||||
isApproved bool
|
||||
@@ -25,17 +26,17 @@ func TestReviews(t *testing.T) {
|
||||
isApproved: true,
|
||||
},
|
||||
{
|
||||
name: "Single reviewer done",
|
||||
reviews: []*models.PullReview{&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}}},
|
||||
reviewers: []string{"user1"},
|
||||
isApproved: true,
|
||||
name: "Single reviewer done",
|
||||
reviews: []*models.PullReview{&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}}},
|
||||
reviewers: []string{"user1"},
|
||||
isApproved: true,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "Two reviewer, one not approved",
|
||||
reviews: []*models.PullReview{&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}}},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
name: "Two reviewer, one not approved",
|
||||
reviews: []*models.PullReview{&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}}},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -44,8 +45,8 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}, Stale: true},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -54,8 +55,8 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isPendingByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -63,9 +64,9 @@ func TestReviews(t *testing.T) {
|
||||
reviews: []*models.PullReview{
|
||||
&models.PullReview{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}, Stale: true},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isPendingByTest1: false,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isPendingByTest1: false,
|
||||
isReviewedByTest1: false,
|
||||
},
|
||||
{
|
||||
@@ -74,8 +75,8 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: true,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: true,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -84,8 +85,8 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}, Dismissed: true},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -94,9 +95,9 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
fetchErr: errors.New("System error fetching reviews."),
|
||||
isApproved: true,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
fetchErr: errors.New("System error fetching reviews."),
|
||||
isApproved: true,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
@@ -106,8 +107,23 @@ func TestReviews(t *testing.T) {
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user4"}},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: true,
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: true,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "Review ignored before push",
|
||||
reviews: []*models.PullReview{
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user1"}, ID: 1001},
|
||||
&models.PullReview{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}, ID: 1000},
|
||||
},
|
||||
timeline: []*models.TimelineComment{
|
||||
&models.TimelineComment{Type: common.TimelineCommentType_Review, ReviewID: 1001},
|
||||
&models.TimelineComment{Type: common.TimelineCommentType_PushPull},
|
||||
&models.TimelineComment{Type: common.TimelineCommentType_Review, ReviewID: 1000},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
}
|
||||
@@ -115,8 +131,12 @@ func TestReviews(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
rf := mock_common.NewMockGiteaReviewFetcher(ctl)
|
||||
rf := mock_common.NewMockGiteaReviewTimelineFetcher(ctl)
|
||||
|
||||
if test.timeline == nil {
|
||||
test.timeline = reviewsToTimeline(test.reviews)
|
||||
}
|
||||
rf.EXPECT().GetTimeline("test", "pr", int64(1)).Return(test.timeline, nil)
|
||||
rf.EXPECT().GetPullRequestReviews("test", "pr", int64(1)).Return(test.reviews, test.fetchErr)
|
||||
|
||||
reviews, err := common.FetchGiteaReviews(rf, test.reviewers, "test", "pr", 1)
|
||||
|
@@ -113,6 +113,10 @@ func (s *Submodule) parseKeyValue(line string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Submodule) ManifestSubmodulePath(manifest *Manifest) string {
|
||||
return manifest.SubdirForPackage(s.Path)
|
||||
}
|
||||
|
||||
func ParseSubmodulesFile(reader io.Reader) ([]Submodule, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
|
@@ -8,20 +8,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CommentType_ReviewRequested = "review_request"
|
||||
CommentType_Review = "review"
|
||||
CommentType_PushPull = "pull_push"
|
||||
CommentType_DismissReview = "dismiss_review"
|
||||
TimelineCommentType_ReviewRequested = "review_request"
|
||||
TimelineCommentType_Review = "review"
|
||||
TimelineCommentType_PushPull = "pull_push"
|
||||
TimelineCommentType_PullRequestRef = "pull_ref"
|
||||
TimelineCommentType_DismissReview = "dismiss_review"
|
||||
TimelineCommentType_Comment = "comment"
|
||||
)
|
||||
|
||||
func FetchTimelineSinceReviewRequestOrPush(gitea GiteaTimelineFetcher, groupName, headSha, org, repo string, id int64) ([]*models.TimelineComment, error) {
|
||||
func FetchTimelineSinceLastPush(gitea GiteaTimelineFetcher, headSha, org, repo string, id int64) ([]*models.TimelineComment, error) {
|
||||
timeline, err := gitea.GetTimeline(org, repo, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx := len(timeline) - 1
|
||||
|
||||
//{"is_force_push":true,"commit_ids":["36e43509be1b13a1a8fc63a4361405de04cc621ab16935f88968c46193221bb6","732246a48fbc6bac9df16c0b0ca23ce0f6fbabd9990795863b6d1f0ef3f242c8"]}
|
||||
type PullPushData struct {
|
||||
IsForcePush bool `json:"is_force_push"`
|
||||
@@ -29,24 +29,35 @@ func FetchTimelineSinceReviewRequestOrPush(gitea GiteaTimelineFetcher, groupName
|
||||
}
|
||||
|
||||
// trim timeline to last push update or last time review request was requested
|
||||
for ; idx > 0; idx-- {
|
||||
e := timeline[idx]
|
||||
if e.Type == CommentType_PushPull {
|
||||
for i, e := range timeline {
|
||||
if e.Type == TimelineCommentType_PushPull {
|
||||
var push PullPushData
|
||||
if err := json.Unmarshal([]byte(e.Body), &push); err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
|
||||
if slices.Contains(push.CommitIds, headSha) {
|
||||
break
|
||||
return timeline[0:i], nil
|
||||
}
|
||||
} else if e.Type == CommentType_ReviewRequested && e.Assignee != nil && e.Assignee.UserName == groupName {
|
||||
// review request is cut-off for reviews too
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
timeline = timeline[idx:]
|
||||
return timeline, nil
|
||||
}
|
||||
|
||||
func FetchTimelineSinceReviewRequestOrPush(gitea GiteaTimelineFetcher, groupName, headSha, org, repo string, id int64) ([]*models.TimelineComment, error) {
|
||||
timeline, err := FetchTimelineSinceLastPush(gitea, headSha, org, repo, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// trim timeline to last push update or last time review request was requested
|
||||
for i, e := range timeline {
|
||||
if e.Type == TimelineCommentType_ReviewRequested && e.Assignee != nil && e.Assignee.UserName == groupName {
|
||||
// review request is cut-off for reviews too
|
||||
return timeline[0:i], nil
|
||||
}
|
||||
}
|
||||
|
||||
return timeline, nil
|
||||
}
|
||||
|
@@ -19,11 +19,16 @@ package common
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
func SplitLines(str string) []string {
|
||||
@@ -121,3 +126,50 @@ func (giturl *GitUrl) RemoteName() string {
|
||||
|
||||
return strings.ToLower(giturl.Org) + "_" + strings.ToLower(giturl.Repo)
|
||||
}
|
||||
|
||||
func PRtoString(pr *models.PullRequest) string {
|
||||
if pr == nil {
|
||||
return "(null)"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s#%d", pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index)
|
||||
}
|
||||
|
||||
type DevelProject struct {
|
||||
Project, Package string
|
||||
}
|
||||
|
||||
type DevelProjects []*DevelProject
|
||||
|
||||
func FetchDevelProjects() (DevelProjects, error) {
|
||||
res, err := http.Get("https://src.opensuse.org/openSUSE/Factory/raw/branch/main/pkgs/_meta/devel_packages")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
scanner := bufio.NewScanner(res.Body)
|
||||
ret := []*DevelProject{}
|
||||
for scanner.Scan() {
|
||||
d := SplitStringNoEmpty(scanner.Text(), " ")
|
||||
if len(d) == 2 {
|
||||
ret = append(ret, &DevelProject{
|
||||
Project: d[1],
|
||||
Package: d[0],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
var DevelProjectNotFound = errors.New("Devel project not found")
|
||||
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
||||
for _, item := range d {
|
||||
if item.Package == pkg {
|
||||
return item.Project, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", DevelProjectNotFound
|
||||
}
|
||||
|
@@ -73,6 +73,10 @@ func runObsCommand(args ...string) ([]string, error) {
|
||||
|
||||
var DebugMode bool
|
||||
|
||||
func giteaPackage(pkg string) string {
|
||||
return strings.ReplaceAll(pkg, "+", "_")
|
||||
}
|
||||
|
||||
func projectMaintainer(obs *common.ObsClient, prj string) ([]string, []string) { // users, groups
|
||||
meta, err := obs.GetProjectMeta(prj)
|
||||
if err != nil {
|
||||
@@ -168,13 +172,14 @@ func gitImporter(prj, pkg string) error {
|
||||
|
||||
func cloneDevel(git common.Git, gitDir, outName, urlString string) error {
|
||||
url, err := url.Parse(urlString)
|
||||
branch := url.Fragment
|
||||
// branch := url.Fragment
|
||||
url.Fragment = ""
|
||||
|
||||
params := []string{"clone"}
|
||||
if len(branch) > 0 {
|
||||
params = append(params, "-b", branch)
|
||||
}
|
||||
/* if len(branch) > 0 {
|
||||
params = append(params, "-b", branch)
|
||||
}
|
||||
*/
|
||||
params = append(params, url.String(), outName)
|
||||
|
||||
if err != nil {
|
||||
@@ -185,13 +190,16 @@ func cloneDevel(git common.Git, gitDir, outName, urlString string) error {
|
||||
}
|
||||
|
||||
func importRepos(packages []string) {
|
||||
RepoToObsName := make(map[string]string)
|
||||
|
||||
factoryRepos := make([]*models.Repository, 0, len(packages)*2)
|
||||
develProjectPackages := make([]string, 0, len(packages))
|
||||
for _, pkg := range packages {
|
||||
src_pkg_name := strings.Split(pkg, ":")
|
||||
RepoToObsName[giteaPackage(src_pkg_name[0])] = src_pkg_name[0]
|
||||
repo, err := client.Repository.RepoGet(
|
||||
repository.NewRepoGetParams().
|
||||
WithDefaults().WithOwner("pool").WithRepo(src_pkg_name[0]),
|
||||
WithDefaults().WithOwner("pool").WithRepo(giteaPackage(src_pkg_name[0])),
|
||||
r.DefaultAuthentication)
|
||||
|
||||
if err != nil {
|
||||
@@ -206,10 +214,19 @@ func importRepos(packages []string) {
|
||||
}
|
||||
}
|
||||
log.Println("Num repos found:", len(factoryRepos))
|
||||
if len(develProjectPackages) > 0 {
|
||||
log.Println("Num of repos that need to create:", len(develProjectPackages))
|
||||
log.Println("Create the following packages in pool to continue:", strings.Join(develProjectPackages, " "))
|
||||
if forceNonPoolPackages {
|
||||
log.Println(" IGNORING and will create these as non-pool packages!")
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
oldPackageNames := make([]string, 0, len(factoryRepos))
|
||||
for _, repo := range factoryRepos {
|
||||
oldPackageNames = append(oldPackageNames, repo.Name)
|
||||
oldPackageNames = append(oldPackageNames, RepoToObsName[repo.Name])
|
||||
}
|
||||
|
||||
// fork packags from pool
|
||||
@@ -231,48 +248,60 @@ func importRepos(packages []string) {
|
||||
log.Println("adding remotes...")
|
||||
for i := 0; i < len(factoryRepos); i++ {
|
||||
pkg := factoryRepos[i]
|
||||
pkgName := RepoToObsName[pkg.Name]
|
||||
gitName := pkg.Name
|
||||
|
||||
// verify that package was created by `git-importer`, or it's scmsync package and clone it
|
||||
fi, err := os.Stat(filepath.Join(git.GetPath(), pkg.Name))
|
||||
fi, err := os.Stat(filepath.Join(git.GetPath(), gitName))
|
||||
if os.IsNotExist(err) {
|
||||
if slices.Contains(develProjectPackages, pkg.Name) {
|
||||
if slices.Contains(develProjectPackages, pkgName) {
|
||||
// failed import of former factory package
|
||||
log.Println("Failed to import former factory pkg:", pkgName)
|
||||
continue
|
||||
}
|
||||
|
||||
// scmsync?
|
||||
devel_project, err := runObsCommand("develproject", "openSUSE:Factory", pkg.Name)
|
||||
if err != nil || len(devel_project) != 1 {
|
||||
log.Panicln("devel project len:", len(devel_project), "for", pkg.Name, "err:", err)
|
||||
devel_project, err := devel_projects.GetDevelProject(pkgName)
|
||||
if err != nil {
|
||||
log.Panicln("devel project not found for", RepoToObsName[pkg.Name], "err:", err)
|
||||
}
|
||||
d := strings.Split(devel_project[0], "/")
|
||||
if len(d) != 2 {
|
||||
log.Panicln("expected devel project/package. got:", d)
|
||||
}
|
||||
meta, _ := obs.GetPackageMeta(d[0], d[1])
|
||||
meta, _ := obs.GetPackageMeta(devel_project, pkgName)
|
||||
if len(meta.ScmSync) > 0 {
|
||||
if err2 := cloneDevel(git, "", pkg.Name, meta.ScmSync); err != nil {
|
||||
if err2 := cloneDevel(git, "", gitName, meta.ScmSync); err != nil {
|
||||
log.Panicln(err2)
|
||||
}
|
||||
git.GitExecOrPanic(pkg.Name, "checkout", "-B", "main")
|
||||
if err2 := git.GitExec(gitName, "checkout", "-B", "main"); err2 != nil {
|
||||
git.GitExecOrPanic(gitName, "checkout", "-B", "master")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// try again, should now exist
|
||||
if fi, err = os.Stat(filepath.Join(git.GetPath(), pkg.Name)); err != nil {
|
||||
if fi, err = os.Stat(filepath.Join(git.GetPath(), gitName)); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Panicln(err)
|
||||
} else {
|
||||
// verify that we do not have scmsync for imported packages
|
||||
meta, err := obs.GetPackageMeta(prj, pkg.Name)
|
||||
meta, err := obs.GetPackageMeta(prj, pkgName)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if len(meta.ScmSync) > 0 {
|
||||
log.Panicln("importing an scmsync package??:", prj, pkg.Name)
|
||||
u, err := url.Parse(meta.ScmSync)
|
||||
if err != nil {
|
||||
log.Println("Invlid scmsync in", pkg, meta.ScmSync, err)
|
||||
}
|
||||
o, err := url.Parse(strings.TrimSpace(git.GitExecWithOutputOrPanic(gitName, "remote", "get-url", "origin")))
|
||||
log.Println("Invlid scmsync in git repo", pkg, meta.ScmSync, err)
|
||||
if u.Host != o.Host || u.Path != u.Path {
|
||||
log.Panicln("importing an scmsync package??:", prj, gitName)
|
||||
} else {
|
||||
log.Println("previous SCMSYNC package. Pull.")
|
||||
git.GitExecOrPanic(gitName, "pull", "origin", "HEAD:main")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,11 +310,11 @@ func importRepos(packages []string) {
|
||||
}
|
||||
|
||||
// add remote repos
|
||||
out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show", "-n")
|
||||
out := git.GitExecWithOutputOrPanic(gitName, "remote", "show", "-n")
|
||||
switch pkg.Owner.UserName {
|
||||
case "pool":
|
||||
if !slices.Contains(strings.Split(out, "\n"), "pool") {
|
||||
out := git.GitExecWithOutputOrPanic(pkg.Name, "remote", "add", "pool", pkg.CloneURL)
|
||||
out := git.GitExecWithOutputOrPanic(gitName, "remote", "add", "pool", pkg.CloneURL)
|
||||
if len(strings.TrimSpace(out)) > 1 {
|
||||
log.Println(out)
|
||||
}
|
||||
@@ -329,7 +358,7 @@ func importRepos(packages []string) {
|
||||
break
|
||||
} else {
|
||||
log.Panicln(" *** factory has no branches", branches)
|
||||
}
|
||||
}
|
||||
}
|
||||
pool_branch := "factory"
|
||||
|
||||
@@ -392,12 +421,22 @@ func importRepos(packages []string) {
|
||||
|
||||
for i := 0; i < len(develProjectPackages); i++ {
|
||||
pkg := develProjectPackages[i]
|
||||
meta, _ := obs.GetPackageMeta(prj, pkg)
|
||||
if len(meta.ScmSync) > 0 {
|
||||
if err2 := cloneDevel(git, "", pkg, meta.ScmSync); err2 != nil {
|
||||
log.Panicln(err2)
|
||||
meta, err := obs.GetPackageMeta(prj, pkg)
|
||||
if err != nil {
|
||||
meta, err = obs.GetPackageMeta(prj, pkg)
|
||||
if err != nil {
|
||||
log.Println("Error fetching pkg meta for:", prj, pkg, err)
|
||||
}
|
||||
}
|
||||
if meta == nil {
|
||||
log.Println(" **** pkg meta is nil? ****")
|
||||
} else if len(meta.ScmSync) > 0 {
|
||||
if _, err := os.Stat(path.Join(git.GetPath(), pkg)); os.IsNotExist(err) {
|
||||
if err2 := cloneDevel(git, "", pkg, meta.ScmSync); err2 != nil {
|
||||
log.Panicln(err2)
|
||||
}
|
||||
git.GitExecOrPanic(pkg, "checkout", "-B", "main")
|
||||
}
|
||||
git.GitExecOrPanic(pkg, "checkout", "-B", "main")
|
||||
continue
|
||||
} else {
|
||||
common.PanicOnError(gitImporter(prj, pkg))
|
||||
@@ -459,7 +498,7 @@ func importRepos(packages []string) {
|
||||
remotes := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkg.Name, "remote", "show"), "\n")
|
||||
if !slices.Contains(remotes, "develorigin") {
|
||||
git.GitExecOrPanic(pkg.Name, "remote", "add", "develorigin", repo.SSHURL)
|
||||
// git.GitExecOrPanic(pkg.Name, "fetch", "devel")
|
||||
// git.GitExecOrPanic(pkgName, "fetch", "devel")
|
||||
}
|
||||
if slices.Contains(remotes, "origin") {
|
||||
git.GitExecOrPanic(pkg.Name, "lfs", "fetch", "--all")
|
||||
@@ -467,8 +506,8 @@ func importRepos(packages []string) {
|
||||
}
|
||||
git.GitExecOrPanic(pkg.Name, "push", "develorigin", "main", "-f")
|
||||
git.GitExec(pkg.Name, "push", "develorigin", "--delete", "factory", "devel")
|
||||
// git.GitExecOrPanic(pkg.Name, "checkout", "-B", "main", "devel/main")
|
||||
_, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(repo.Name).WithBody(&models.EditRepoOption{
|
||||
// git.GitExecOrPanic(pkg.ame, "checkout", "-B", "main", "devel/main")
|
||||
_, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(giteaPackage(repo.Name)).WithBody(&models.EditRepoOption{
|
||||
DefaultBranch: "main",
|
||||
DefaultMergeStyle: "fast-forward-only",
|
||||
HasPullRequests: true,
|
||||
@@ -493,12 +532,13 @@ func importRepos(packages []string) {
|
||||
|
||||
for _, pkg := range develProjectPackages {
|
||||
var repo *models.Repository
|
||||
if repoData, err := client.Repository.RepoGet(repository.NewRepoGetParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication); err != nil {
|
||||
if repoData, err := client.Repository.RepoGet(repository.NewRepoGetParams().WithOwner(org).WithRepo(giteaPackage(pkg)), r.DefaultAuthentication); err != nil {
|
||||
giteaPkg := giteaPackage(pkg)
|
||||
_, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody(
|
||||
&models.CreateRepoOption{
|
||||
ObjectFormatName: "sha256",
|
||||
AutoInit: false,
|
||||
Name: &pkg,
|
||||
Name: &giteaPkg,
|
||||
DefaultBranch: "main",
|
||||
}),
|
||||
r.DefaultAuthentication,
|
||||
@@ -508,7 +548,7 @@ func importRepos(packages []string) {
|
||||
log.Panicln("Error creating new package repository:", pkg, err)
|
||||
}
|
||||
|
||||
ret, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(pkg).WithBody(
|
||||
ret, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(giteaPkg).WithBody(
|
||||
&models.EditRepoOption{
|
||||
HasPullRequests: true,
|
||||
HasPackages: false,
|
||||
@@ -548,7 +588,7 @@ func importRepos(packages []string) {
|
||||
git.GitExecOrPanic(pkg, "push", "develorigin", "main", "-f")
|
||||
git.GitExec(pkg, "push", "develorigin", "--delete", "factory", "devel")
|
||||
|
||||
_, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(pkg).WithBody(&models.EditRepoOption{
|
||||
_, err := client.Repository.RepoEdit(repository.NewRepoEditParams().WithOwner(org).WithRepo(giteaPackage(pkg)).WithBody(&models.EditRepoOption{
|
||||
DefaultBranch: "main",
|
||||
DefaultMergeStyle: "fast-forward-only",
|
||||
}), r.DefaultAuthentication)
|
||||
@@ -647,7 +687,7 @@ func syncPackageCollaborators(pkg string, orig_uids []common.PersonRepoMeta) []s
|
||||
missing := []string{}
|
||||
uids := make([]common.PersonRepoMeta, len(orig_uids))
|
||||
copy(uids, orig_uids)
|
||||
collab, err := client.Repository.RepoListCollaborators(repository.NewRepoListCollaboratorsParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication)
|
||||
collab, err := client.Repository.RepoListCollaborators(repository.NewRepoListCollaboratorsParams().WithOwner(org).WithRepo(giteaPackage(pkg)), r.DefaultAuthentication)
|
||||
if err != nil {
|
||||
if errors.Is(err, &repository.RepoListCollaboratorsNotFound{}) {
|
||||
return missing
|
||||
@@ -668,7 +708,7 @@ func syncPackageCollaborators(pkg string, orig_uids []common.PersonRepoMeta) []s
|
||||
log.Println("missing collabs for", pkg, ":", uids)
|
||||
}
|
||||
for _, u := range uids {
|
||||
_, err := client.Repository.RepoAddCollaborator(repository.NewRepoAddCollaboratorParams().WithOwner(org).WithRepo(pkg).WithBody(&models.AddCollaboratorOption{
|
||||
_, err := client.Repository.RepoAddCollaborator(repository.NewRepoAddCollaboratorParams().WithOwner(org).WithRepo(giteaPackage(pkg)).WithBody(&models.AddCollaboratorOption{
|
||||
Permission: "write",
|
||||
}).WithCollaborator(u.UserID), r.DefaultAuthentication)
|
||||
|
||||
@@ -700,6 +740,10 @@ func syncMaintainersToGitea(pkgs []string) {
|
||||
devs := []string{}
|
||||
|
||||
for _, group := range prjMeta.Groups {
|
||||
if group.GroupID == "factory-maintainers" {
|
||||
log.Println("Ignoring factory-maintainers")
|
||||
continue
|
||||
}
|
||||
teamMembers, err := obs.GetGroupMeta(group.GroupID)
|
||||
if err != nil {
|
||||
log.Panicln("failed to get group", err)
|
||||
@@ -795,13 +839,20 @@ func createPrjGit() {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "add", "_config")
|
||||
}
|
||||
|
||||
file, err = os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "project.build"))
|
||||
file, err = os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "staging.config"))
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
file.Write([]byte(prj))
|
||||
file.WriteString("{\n // Reference build project\n \"ObsProject\": \"" + prj + "\",\n}\n")
|
||||
file.Close()
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "add", "project.build")
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "add", "staging.config")
|
||||
|
||||
if file, err = os.Create(path.Join(git.GetPath(), common.DefaultGitPrj, "workflow.config")); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
file.WriteString("{\n \"Workflows\": [\"direct\", \"pr\"],\n \"Organization\": \"" + org + "\",\n}\n")
|
||||
file.Close()
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "add", "workflow.config")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -812,6 +863,8 @@ var git common.Git
|
||||
var obs *common.ObsClient
|
||||
var prj, org string
|
||||
var forceBadPool bool
|
||||
var forceNonPoolPackages bool
|
||||
var devel_projects common.DevelProjects
|
||||
|
||||
func main() {
|
||||
if err := common.RequireGiteaSecretToken(); err != nil {
|
||||
@@ -828,6 +881,7 @@ func main() {
|
||||
flags.SetOutput(helpString)
|
||||
//workflowConfig := flag.String("config", "", "Repository and workflow definition file")
|
||||
giteaHost := flags.String("gitea", "src.opensuse.org", "Gitea instance")
|
||||
obsUrl := flags.String("obs-url", "https://api.opensuse.org", "OBS API Url")
|
||||
//rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
|
||||
flags.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
|
||||
// revNew := flag.Int("nrevs", 20, "Number of new revisions in factory branch. Indicator of broken history import")
|
||||
@@ -836,6 +890,8 @@ func main() {
|
||||
getMaintainers := flags.Bool("maintainers-only", false, "Get maintainers only and exit")
|
||||
syncMaintainers := flags.Bool("sync-maintainers-only", false, "Sync maintainers to Gitea and exit")
|
||||
flags.BoolVar(&forceBadPool, "bad-pool", false, "Force packages if pool has no branches due to bad import")
|
||||
flags.BoolVar(&forceNonPoolPackages, "non-pool", false, "Allow packages that are not in pool to be created. WARNING: Can't add to factory later!")
|
||||
specificPackage := flags.String("package", "", "Process specific package only, ignoring the others")
|
||||
|
||||
if help := flags.Parse(os.Args[1:]); help == flag.ErrHelp || flags.NArg() != 2 {
|
||||
printHelp(helpString.String())
|
||||
@@ -847,25 +903,30 @@ func main() {
|
||||
// r.SetDebug(true)
|
||||
client = apiclient.New(r, nil)
|
||||
|
||||
obs, _ = common.NewObsClient("api.opensuse.org")
|
||||
obs, _ = common.NewObsClient(*obsUrl)
|
||||
|
||||
gh := common.GitHandlerGeneratorImpl{}
|
||||
var gh common.GitHandlerGenerator
|
||||
var err error
|
||||
git, err = gh.CreateGitHandler("Autogits - Devel Importer", "not.exist", "devel-importer")
|
||||
|
||||
devel_projects, err = common.FetchDevelProjects()
|
||||
if err != nil {
|
||||
log.Panicln("Failed to allocate git handler. Err:", err)
|
||||
log.Panic("Cannot load devel projects:", err)
|
||||
}
|
||||
log.Println("# devel projects loaded:", len(devel_projects))
|
||||
|
||||
if DebugMode {
|
||||
if len(*debugGitPath) > 0 {
|
||||
git.Close()
|
||||
git, err = gh.ReadExistingPath("Autogits - Devel Importer", "not.exist", *debugGitPath)
|
||||
gh, err = common.AllocateGitWorkTree(*debugGitPath, "Autogits - Devel Importer", "not.exist")
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
log.Println(" - working directory:" + git.GetPath())
|
||||
} else {
|
||||
defer git.Close()
|
||||
dir, _ := os.MkdirTemp(os.TempDir(), "devel-importer")
|
||||
gh, err = common.AllocateGitWorkTree(dir, "Autogits - Devel Importer", "not.exist")
|
||||
if err != nil {
|
||||
log.Panicln("Failed to allocate git handler", err)
|
||||
}
|
||||
}
|
||||
|
||||
prj = flags.Arg(0)
|
||||
@@ -879,6 +940,13 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
git, err = gh.CreateGitHandler(org)
|
||||
if err != nil {
|
||||
log.Panicln("Cannot create git", err)
|
||||
}
|
||||
defer git.Close()
|
||||
log.Println(" - working directory:" + git.GetPath())
|
||||
|
||||
/*
|
||||
for _, pkg := range packages {
|
||||
if _, err := client.Organization.CreateOrgRepo(organization.NewCreateOrgRepoParams().WithOrg(org).WithBody(
|
||||
@@ -920,11 +988,15 @@ func main() {
|
||||
if *purgeOnly {
|
||||
log.Println("Purging repositories...")
|
||||
for _, pkg := range packages {
|
||||
client.Repository.RepoDelete(repository.NewRepoDeleteParams().WithOwner(org).WithRepo(pkg), r.DefaultAuthentication)
|
||||
client.Repository.RepoDelete(repository.NewRepoDeleteParams().WithOwner(org).WithRepo(giteaPackage(pkg)), r.DefaultAuthentication)
|
||||
}
|
||||
os.Exit(10)
|
||||
}
|
||||
|
||||
if len(*specificPackage) != 0 {
|
||||
importRepos([]string{*specificPackage})
|
||||
return
|
||||
}
|
||||
importRepos(packages)
|
||||
syncMaintainersToGitea(packages)
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
@@ -21,19 +22,32 @@ var acceptRx *regexp.Regexp
|
||||
var rejectRx *regexp.Regexp
|
||||
var groupName string
|
||||
|
||||
func InitRegex(groupName string) {
|
||||
acceptRx = regexp.MustCompile("\\s*:\\s*LGTM")
|
||||
rejectRx = regexp.MustCompile("\\s*:\\s*")
|
||||
func InitRegex(newGroupName string) {
|
||||
groupName = newGroupName
|
||||
acceptRx = regexp.MustCompile("^:\\s*(LGTM|approved?)")
|
||||
rejectRx = regexp.MustCompile("^:\\s*")
|
||||
}
|
||||
|
||||
func ParseReviewLine(reviewText string) (bool, string) {
|
||||
line := strings.TrimSpace(reviewText)
|
||||
glen := len(groupName)
|
||||
if len(line) < glen || line[0:glen] != groupName {
|
||||
groupTextName := "@" + groupName
|
||||
glen := len(groupTextName)
|
||||
if len(line) < glen || line[0:glen] != groupTextName {
|
||||
return false, line
|
||||
}
|
||||
|
||||
return true, line[glen:]
|
||||
l := line[glen:]
|
||||
for idx, r := range l {
|
||||
if unicode.IsSpace(r) {
|
||||
continue
|
||||
} else if r == ':' {
|
||||
return true, l[idx:]
|
||||
} else {
|
||||
return false, line
|
||||
}
|
||||
}
|
||||
|
||||
return false, line
|
||||
}
|
||||
|
||||
func ReviewAccepted(reviewText string) bool {
|
||||
@@ -99,23 +113,22 @@ var commentStrings = []string{
|
||||
"change_time_estimate",
|
||||
}*/
|
||||
|
||||
func FindAcceptableReviewInTimeline(user string, timeline []*models.TimelineComment, reviews []*models.PullReview) *models.PullReview {
|
||||
var good_review *models.PullReview
|
||||
|
||||
func FindAcceptableReviewInTimeline(user string, timeline []*models.TimelineComment, reviews []*models.PullReview) *models.TimelineComment {
|
||||
for _, t := range timeline {
|
||||
if t.Type == common.CommentType_Review && t.User != nil && t.User.UserName == user && t.Created == t.Updated {
|
||||
for _, r := range reviews {
|
||||
if r.ID == t.ReviewID && (ReviewAccepted(r.Body) || ReviewRejected(r.Body)) {
|
||||
good_review = r
|
||||
break
|
||||
}
|
||||
if t.Type == common.TimelineCommentType_Comment && t.User.UserName == user && t.Created == t.Updated {
|
||||
if ReviewAccepted(t.Body) || ReviewRejected(t.Body) {
|
||||
return t
|
||||
}
|
||||
} else if (t.Type == common.CommentType_DismissReview || t.Type == common.CommentType_ReviewRequested) && t.Assignee != nil && t.Assignee.UserName == user {
|
||||
good_review = nil
|
||||
}
|
||||
}
|
||||
|
||||
return good_review
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnrequestReviews(gitea common.Gitea, org, repo string, id int64, users []string) {
|
||||
if err := gitea.UnrequestReview(org, repo, id, users...); err != nil {
|
||||
common.LogError("Can't remove reviewrs after a review:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessNotifications(notification *models.NotificationThread, gitea common.Gitea) {
|
||||
@@ -167,6 +180,10 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
}
|
||||
|
||||
config := configs.GetPrjGitConfig(org, repo, pr.Base.Name)
|
||||
if config == nil {
|
||||
common.LogError("Cannot find config for:", fmt.Sprintf("%s/%s#%s", org, repo, pr.Base.Name))
|
||||
return
|
||||
}
|
||||
if pr.State == "closed" {
|
||||
// dismiss the review
|
||||
common.LogInfo(" -- closed request, so nothing to review")
|
||||
@@ -200,9 +217,10 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
|
||||
for _, reviewer := range requestReviewers {
|
||||
if review := FindAcceptableReviewInTimeline(reviewer, timeline, reviews); review != nil {
|
||||
if review.State == common.ReviewStateApproved && ReviewAccepted(review.Body) {
|
||||
if ReviewAccepted(review.Body) {
|
||||
if !common.IsDryRun {
|
||||
gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+reviewer)
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
if !common.IsDryRun {
|
||||
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
||||
common.LogDebug(" Cannot set notification as read", err)
|
||||
@@ -210,11 +228,12 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
}
|
||||
}
|
||||
common.LogInfo(" -> approved by", reviewer)
|
||||
common.LogInfo(" review at", review.Submitted)
|
||||
common.LogInfo(" review at", review.Created)
|
||||
return
|
||||
} else if review.State == common.ReviewStateRequestChanges && ReviewRejected(review.Body) {
|
||||
} else if ReviewRejected(review.Body) {
|
||||
if !common.IsDryRun {
|
||||
gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Request changes. See review by: "+reviewer)
|
||||
gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer)
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
||||
common.LogDebug(" Cannot set notification as read", err)
|
||||
}
|
||||
@@ -239,10 +258,24 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
} else {
|
||||
common.LogDebug(" Not requesting additional reviewers")
|
||||
}
|
||||
|
||||
// add a helpful comment, if not yet added
|
||||
found_help_comment := false
|
||||
for _, t := range timeline {
|
||||
if t.Type == common.TimelineCommentType_Comment && t.User != nil && t.User.UserName == groupName {
|
||||
found_help_comment = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found_help_comment && !common.IsDryRun {
|
||||
helpComment := fmt.Sprintln("Review by", groupName, "represents a group of reviewers:", strings.Join(requestReviewers, ", "), ". To review as part of this group, create a comment with contents @"+groupName+": LGTM on a separate line to accept a review. To request changes, write @"+groupName+": followed by reason for rejection. Do not use reviews to review as a group. Editing a comment invalidates that comment.")
|
||||
gitea.AddComment(pr, helpComment)
|
||||
}
|
||||
}
|
||||
|
||||
func PeriodReviewCheck(gitea common.Gitea) {
|
||||
notifications, err := gitea.GetPullNotifications(nil)
|
||||
notifications, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
if err != nil {
|
||||
common.LogError(" Error fetching unread notifications: %w", err)
|
||||
return
|
||||
|
@@ -2,6 +2,76 @@ package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestReviews(t *testing.T) {
|
||||
func TestReviewApprovalCheck(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
GroupName string
|
||||
InString string
|
||||
Approved bool
|
||||
Rejected bool
|
||||
}{
|
||||
{
|
||||
Name: "Empty String",
|
||||
GroupName: "group",
|
||||
InString: "",
|
||||
},
|
||||
{
|
||||
Name: "Random Text",
|
||||
GroupName: "group",
|
||||
InString: "some things LGTM",
|
||||
},
|
||||
{
|
||||
Name: "Group name with Random Text means disapproval",
|
||||
GroupName: "group",
|
||||
InString: "@group: some things LGTM",
|
||||
Rejected: true,
|
||||
},
|
||||
{
|
||||
Name: "Bad name with Approval",
|
||||
GroupName: "group2",
|
||||
InString: "@group: LGTM",
|
||||
},
|
||||
{
|
||||
Name: "Bad name with Approval",
|
||||
GroupName: "group2",
|
||||
InString: "@group: LGTM",
|
||||
},
|
||||
{
|
||||
Name: "LGTM approval",
|
||||
GroupName: "group2",
|
||||
InString: "@group2: LGTM",
|
||||
Approved: true,
|
||||
},
|
||||
{
|
||||
Name: "approval",
|
||||
GroupName: "group2",
|
||||
InString: "@group2: approved",
|
||||
Approved: true,
|
||||
},
|
||||
{
|
||||
Name: "approval",
|
||||
GroupName: "group2",
|
||||
InString: "@group2: approve",
|
||||
Approved: true,
|
||||
},
|
||||
{
|
||||
Name: "disapproval",
|
||||
GroupName: "group2",
|
||||
InString: "@group2: disapprove",
|
||||
Rejected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
InitRegex(test.GroupName)
|
||||
|
||||
if r := ReviewAccepted(test.InString); r != test.Approved {
|
||||
t.Error("ReviewAccepted() returned", r, "expecting", test.Approved)
|
||||
}
|
||||
if r := ReviewRejected(test.InString); r != test.Rejected {
|
||||
t.Error("ReviewRejected() returned", r, "expecting", test.Rejected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
1
obs-forward-bot/.gitignore
vendored
Normal file
1
obs-forward-bot/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
forward-bot
|
293
obs-forward-bot/main.go
Normal file
293
obs-forward-bot/main.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
var LastDevelProjectUpdate *time.Time
|
||||
|
||||
var Git common.GitHandlerGenerator
|
||||
|
||||
func DevelProjectForPR(pr *models.PullRequest) (*common.DevelProject, error) {
|
||||
devels, err := common.FetchDevelProjects()
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch devel projects:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
org := pr.Head.Repo.Owner.UserName
|
||||
pkg := pr.Head.Repo.Name
|
||||
|
||||
common.LogDebug("Looking for devel package", org, pkg)
|
||||
|
||||
for _, devel_project := range devels {
|
||||
if devel_project.Package == pkg {
|
||||
common.LogDebug("Fetching prject meta for", devel_project.Project)
|
||||
meta, err := Obs.GetProjectMeta(devel_project.Project)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch devel project OBS meta", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u, err := url.Parse(meta.ScmSync)
|
||||
if err != nil {
|
||||
common.LogError("Failed to parse project scm", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Hostname() != "src.opensuse.org" || strings.TrimSuffix(u.Path[1:], ".git") != org+"/_ObsPrj" {
|
||||
common.LogError("Invalid ScmSync format for devel project", meta.ScmSync, "Expected:", u.Path, "!=", org+"/_ObsPrj")
|
||||
return nil, fmt.Errorf("Invalid ScmSync format for devel project %s", meta.ScmSync)
|
||||
}
|
||||
|
||||
g, err := Git.CreateGitHandler(org)
|
||||
if err != nil {
|
||||
common.LogError("Failed to alloate git:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer g.Close()
|
||||
|
||||
branch := u.Fragment
|
||||
u.Fragment = ""
|
||||
_, err = g.GitClone(common.DefaultGitPrj, branch, u.String())
|
||||
common.PanicOnError(err)
|
||||
expectedSha, ok := g.GitSubmoduleCommitId(common.DefaultGitPrj, pkg, branch)
|
||||
if !ok {
|
||||
common.LogError("Failed to find", pkg, "in projectgit")
|
||||
return nil, fmt.Errorf("failed to find %s in projectgit", pkg)
|
||||
}
|
||||
|
||||
if expectedSha == pr.Head.Sha {
|
||||
// found a match back to the devel project
|
||||
return devel_project, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Failed to match submission to devel project")
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Failed to find PR in a devel project. Ignoring")
|
||||
}
|
||||
|
||||
func ProcessNotification(notification *models.NotificationThread) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.LogInfo("panic cought --- recovered")
|
||||
common.LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
rx := regexp.MustCompile(`^/?api/v\d+/repos/(?<org>[_a-zA-Z0-9-]+)/(?<project>[_a-zA-Z0-9-]+)/(?:issues|pulls)/(?<num>[0-9]+)$`)
|
||||
subject := notification.Subject
|
||||
u, err := url.Parse(notification.Subject.URL)
|
||||
if err != nil {
|
||||
common.LogError("Invalid format of notification:", subject.URL, err)
|
||||
return
|
||||
}
|
||||
|
||||
match := rx.FindStringSubmatch(u.Path)
|
||||
if match == nil {
|
||||
common.LogError("** Unexpected format of notification:", subject.URL)
|
||||
return
|
||||
}
|
||||
|
||||
org := match[1]
|
||||
repo := match[2]
|
||||
id, _ := strconv.ParseInt(match[3], 10, 64)
|
||||
|
||||
common.LogInfo("processing:", fmt.Sprintf("%s/%s#%d", org, repo, id))
|
||||
pr, err := Gitea.GetPullRequest(org, repo, id)
|
||||
if err != nil {
|
||||
common.LogError(" ** Cannot fetch PR associated with review:", subject.URL, "Error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
repository := notification.Repository
|
||||
repoorg := repository.Owner.UserName
|
||||
reponame := repository.Name
|
||||
|
||||
if repoorg != org || reponame != repo {
|
||||
common.LogError(" *** failed to parse org notification. Expected", repoorg, reponame)
|
||||
return
|
||||
}
|
||||
|
||||
headSha := pr.Head.Sha
|
||||
timeline, err := common.FetchTimelineSinceLastPush(Gitea, headSha, org, repo, id)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch comments:", err)
|
||||
return
|
||||
}
|
||||
|
||||
ObsSrFormat := "OBS SR#%d\n"
|
||||
ExtractSR := func(body string) int {
|
||||
rx := regexp.MustCompile("^OBS SR#(\\d+)$")
|
||||
for _, line := range common.SplitLines(body) {
|
||||
if m := rx.FindStringSubmatch(line); m != nil && len(m) == 2 {
|
||||
id, _ := strconv.ParseInt(m[1], 10, 32)
|
||||
return int(id)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
common.LogDebug("notification", org, repo, id)
|
||||
superseed := false
|
||||
for _, timeline := range timeline {
|
||||
if timeline.Type == common.TimelineCommentType_Comment && timeline.User.UserName == GiteaUser {
|
||||
// check if SR comment referenced here
|
||||
if sr := ExtractSR(timeline.Body); sr > 0 {
|
||||
status, err := Obs.RequestStatus(sr)
|
||||
if err != nil {
|
||||
common.LogError("Failed to request OBS request status", err)
|
||||
return
|
||||
}
|
||||
|
||||
if superseed {
|
||||
break
|
||||
}
|
||||
|
||||
common.LogInfo("Found status:", status.State.State)
|
||||
if !common.IsDryRun {
|
||||
if status.State.State == common.RequestStatus_Accepted {
|
||||
if _, err := Gitea.AddReviewComment(pr, common.ReviewStateApproved, "SR was accepted in OBS. Approving."); err != nil {
|
||||
common.LogError("Failed to add review comment to PR:", err)
|
||||
return
|
||||
}
|
||||
} else if status.State.State == common.RequestStatus_Declined || status.State.State == common.RequestStatus_Revoked {
|
||||
if _, err := Gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "SR was rejected in OBS. Rejecting."); err != nil {
|
||||
common.LogError("Failed to add review comment to PR:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
common.LogDebug("Request is in state:", status.State.State, "Waiting.")
|
||||
return
|
||||
}
|
||||
Gitea.SetNotificationRead(notification.ID)
|
||||
} else {
|
||||
}
|
||||
return
|
||||
}
|
||||
} else if timeline.Type == common.TimelineCommentType_PushPull {
|
||||
superseed = true
|
||||
}
|
||||
}
|
||||
|
||||
// no current SR running, create one
|
||||
dp, err := DevelProjectForPR(pr)
|
||||
if err != nil {
|
||||
common.LogDebug("Failed to process PR:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !common.IsDryRun {
|
||||
meta, err := Obs.CreateSubmitRequest(dp.Project, dp.Package, ObsTarget)
|
||||
if err != nil {
|
||||
common.LogError("Failed to create OBS SR: ", dp.Project, dp.Package, "=>", ObsTarget, err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
// make sure we leave comment here
|
||||
err = Gitea.AddComment(pr, "Created OBS submit request to "+ObsTarget+"\n\n"+fmt.Sprintf(ObsSrFormat, meta.Id))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
common.LogError("Failed to create Gitea comment:", err)
|
||||
common.LogInfo("Waiting 1 minute and retrying to leave comment...")
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
} else {
|
||||
common.LogInfo("Would create a SR from", dp.Project, "/", dp.Package, "=>", ObsTarget)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ProcessNotifications() {
|
||||
// process PRs and issues
|
||||
notifications, err := Gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
if err != nil {
|
||||
common.LogError("Failed to get notifications.", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, notification := range notifications {
|
||||
ProcessNotification(notification)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var GiteaUser string
|
||||
var Gitea common.Gitea
|
||||
var Obs *common.ObsClient
|
||||
|
||||
var ObsTarget, GiteaTargetBranch, GiteaOrg string
|
||||
|
||||
func main() {
|
||||
GiteaHost := flag.String("gitea-host", "https://src.opensuse.org", "Gitea host")
|
||||
ObsHost := flag.String("obs-host", "https://api.opensuse.org", "OBS instance")
|
||||
flag.StringVar(&ObsTarget, "obs-target", "openSUSE:Factory", "")
|
||||
flag.StringVar(&GiteaTargetBranch, "gitea-target", "factory", "")
|
||||
flag.StringVar(&GiteaOrg, "gitea-org", "pool", "")
|
||||
debug := flag.Bool("debug", false, "Debug logging")
|
||||
GitRepoPath := flag.String("git-path", "", "Git repo path")
|
||||
flag.BoolVar(&common.IsDryRun, "dry", false, "no-op operation")
|
||||
flag.Parse()
|
||||
|
||||
if *debug {
|
||||
common.SetLoggingLevel(common.LogLevelDebug)
|
||||
}
|
||||
|
||||
var err error
|
||||
if err = common.RequireGiteaSecretToken(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
if err = common.RequireObsSecretToken(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
if Obs, err = common.NewObsClient(*ObsHost); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
Gitea = common.AllocateGiteaTransport(*GiteaHost)
|
||||
if user, err := Gitea.GetCurrentUser(); err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
GiteaUser = user.UserName
|
||||
}
|
||||
common.LogInfo("Current user:", GiteaUser)
|
||||
|
||||
if len(*GitRepoPath) == 0 {
|
||||
*GitRepoPath, err = os.MkdirTemp(os.TempDir(), "forward-bot")
|
||||
if err != nil {
|
||||
common.LogError("Failed to create tempdir:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Git, err = common.AllocateGitWorkTree(*GitRepoPath, "bot", "nothing")
|
||||
if err != nil {
|
||||
common.LogError("Failed to allocate git tree", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
common.LogDebug("--- Starting processing notifications ---")
|
||||
ProcessNotifications()
|
||||
common.LogDebug("--- End processing notifications ---")
|
||||
time.Sleep(time.Minute * 5)
|
||||
}
|
||||
}
|
@@ -315,7 +315,9 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
|
||||
}
|
||||
meta.ScmSync = pr.Head.Repo.CloneURL + "?" + strings.Join(urlPkg, "&") + "#" + pr.Head.Sha
|
||||
meta.Title = fmt.Sprintf("PR#%d to %s", pr.Index, pr.Base.Name)
|
||||
meta.PublicFlags = common.Flags{Contents: "<disable/>"}
|
||||
// QE wants it published ... also we should not hardcode it here, since
|
||||
// it is configurable via the :PullRequest project
|
||||
// meta.PublicFlags = common.Flags{Contents: "<disable/>"}
|
||||
|
||||
meta.Groups = nil
|
||||
meta.Persons = nil
|
||||
@@ -459,7 +461,7 @@ func FetchOurLatestActionableReview(gitea common.Gitea, org, repo string, id int
|
||||
|
||||
for idx := len(reviews) - 1; idx >= 0; idx-- {
|
||||
review := reviews[idx]
|
||||
if review.User != nil || review.User.UserName == Username {
|
||||
if review.User == nil || review.User.UserName == Username {
|
||||
if IsDryRun {
|
||||
// for purposes of moving forward a no-op check
|
||||
return review, nil
|
||||
@@ -547,7 +549,7 @@ func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThre
|
||||
}
|
||||
|
||||
if pr.State != "closed" {
|
||||
common.LogInfo(" ignoring peding PR", thread.Subject.HTMLURL, " state:", pr.State)
|
||||
common.LogInfo(" ignoring pending PR", thread.Subject.HTMLURL, " state:", pr.State)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -888,7 +890,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
|
||||
func PollWorkNotifications(giteaUrl string) {
|
||||
gitea := common.AllocateGiteaTransport(giteaUrl)
|
||||
data, err := gitea.GetPullNotifications(nil)
|
||||
data, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
@@ -918,7 +920,7 @@ func PollWorkNotifications(giteaUrl string) {
|
||||
cleanupFinished := false
|
||||
for page := int64(1); !cleanupFinished; page++ {
|
||||
cleanupFinished = true
|
||||
if data, err := gitea.GetDonePullNotifications(page); data != nil {
|
||||
if data, err := gitea.GetDoneNotifications(common.GiteaNotificationType_Pull, page); data != nil {
|
||||
for _, n := range data {
|
||||
if n.Unread {
|
||||
common.LogError("Done notification is unread or pinned?", *n.Subject)
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@@ -78,7 +79,7 @@ func ProjectStatusSummarySvg(project string) []byte {
|
||||
return ret.Bytes()
|
||||
}
|
||||
|
||||
func PackageStatusSummarySvg(status common.PackageBuildStatus) []byte {
|
||||
func PackageStatusSummarySvg(status *common.PackageBuildStatus) []byte {
|
||||
buildStatus, ok := common.ObsBuildStatusDetails[status.Code]
|
||||
if !ok {
|
||||
buildStatus = common.ObsBuildStatusDetails["error"]
|
||||
@@ -108,8 +109,10 @@ func main() {
|
||||
key := flag.String("key-file", "", "Private key for the TLS certificate")
|
||||
listen := flag.String("listen", "[::1]:8080", "Listening string")
|
||||
disableTls := flag.Bool("no-tls", false, "Disable TLS")
|
||||
obsHost := flag.String("obs-host", "api.opensuse.org", "OBS API endpoint for package status information")
|
||||
obsHost := flag.String("obs-host", "https://api.opensuse.org", "OBS API endpoint for package status information")
|
||||
flag.BoolVar(&debug, "debug", false, "Enable debug logging")
|
||||
RabbitMQHost := flag.String("rabbit-mq", "amqps://rabbit.opensuse.org", "RabbitMQ message bus server")
|
||||
Topic := flag.String("topic", "opensuse.obs", "RabbitMQ topic prefix")
|
||||
flag.Parse()
|
||||
|
||||
common.PanicOnError(common.RequireObsSecretToken())
|
||||
@@ -143,21 +146,25 @@ func main() {
|
||||
|
||||
res.Header().Add("content-type", "image/svg+xml")
|
||||
|
||||
prjStatus := GetCurrentStatus(prj)
|
||||
if prjStatus == nil {
|
||||
status := GetDetailedBuildStatus(prj, pkg, repo, arch)
|
||||
res.Write(PackageStatusSummarySvg(status))
|
||||
})
|
||||
http.HandleFunc("GET /{Project}/{Package}/{Repository}/{Arch}/buildlog", func(res http.ResponseWriter, req *http.Request) {
|
||||
prj := req.PathValue("Project")
|
||||
pkg := req.PathValue("Package")
|
||||
repo := req.PathValue("Repository")
|
||||
arch := req.PathValue("Arch")
|
||||
|
||||
// status := GetDetailedBuildStatus(prj, pkg, repo, arch)
|
||||
data, err := obs.BuildLog(prj, pkg, repo, arch)
|
||||
if err != nil {
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
common.LogError("Failed to fetch build log for:", prj, pkg, repo, arch, err)
|
||||
return
|
||||
}
|
||||
defer data.Close()
|
||||
|
||||
for _, r := range prjStatus.Result {
|
||||
if r.Arch == arch && r.Repository == repo {
|
||||
for _, status := range r.Status {
|
||||
if status.Package == pkg {
|
||||
res.Write(PackageStatusSummarySvg(status))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
io.Copy(res, data)
|
||||
})
|
||||
|
||||
go ProcessUpdates()
|
||||
|
3
obs-status-service/rabbit.go
Normal file
3
obs-status-service/rabbit.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -24,14 +24,86 @@ type StatusUpdateMsg struct {
|
||||
|
||||
func GetCurrentStatus(project string) *common.BuildResultList {
|
||||
statusMutex.RLock()
|
||||
defer statusMutex.RUnlock()
|
||||
|
||||
if ret, found := CurrentStatus[project]; found {
|
||||
statusMutex.RUnlock()
|
||||
return ret
|
||||
} else {
|
||||
go WatchObsProject(obs, project)
|
||||
}
|
||||
|
||||
res, err := obs.BuildStatus(project)
|
||||
statusMutex.RUnlock()
|
||||
statusMutex.Lock()
|
||||
defer statusMutex.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return res
|
||||
}
|
||||
CurrentStatus[project] = res
|
||||
|
||||
now := time.Now().Unix()
|
||||
CurrentStatus[project].LastUpdate = now
|
||||
for _, r := range res.Result {
|
||||
r.LastUpdate = now
|
||||
for _, p := range r.Status {
|
||||
p.LastUpdate = now
|
||||
}
|
||||
slices.SortFunc(r.Status, packageSort)
|
||||
}
|
||||
slices.SortFunc(res.Result, repoSort)
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
func updatePrjPackage(prjState *common.BuildResultList, pkg string, now int64, pkgState *common.BuildResultList) {
|
||||
for prjState.
|
||||
Result[0].Status[0].Package
|
||||
}
|
||||
|
||||
func extractPackageBuildStatus(prjState *common.BuildResultList, pkg string) []*common.PackageBuildStatus {
|
||||
|
||||
}
|
||||
|
||||
func GetDetailedPackageBuildStatus(prj, pkg string) []*common.PackageBuildStatus {
|
||||
statusMutex.RLock()
|
||||
now := time.Now().Unix()
|
||||
|
||||
cachedPrj, found := CurrentStatus[prj]
|
||||
if found {
|
||||
statusMutex.Unlock()
|
||||
if now-cachedPrj.LastUpdate < 60 {
|
||||
return extractPackageBuildStatus(cachedPrj, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
ret, err := obs.BuildStatus(prj, pkg)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
statusMutex.Lock()
|
||||
defer statusMutex.Unlock()
|
||||
|
||||
updatePrjPackage(cachedPrj, pkg, now, ret)
|
||||
return extractPackageBuildStatus(cachedPrj, pkg)
|
||||
}
|
||||
|
||||
func GetDetailedBuildStatus(prj, pkg, repo, arch string) *common.PackageBuildStatus {
|
||||
prjStatus := GetCurrentStatus(prj)
|
||||
if prjStatus == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range prjStatus.Result {
|
||||
if r.Arch == arch && r.Repository == repo {
|
||||
for _, status := range r.Status {
|
||||
if status.Package == pkg {
|
||||
return &status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProcessUpdates() {
|
||||
@@ -53,30 +125,3 @@ func ProcessUpdates() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WatchObsProject(obs common.ObsStatusFetcherWithState, ObsProject string) {
|
||||
old_state := ""
|
||||
|
||||
mutex.Lock()
|
||||
if pos, found := slices.BinarySearch(WatchedRepos, ObsProject); found {
|
||||
mutex.Unlock()
|
||||
return
|
||||
} else {
|
||||
WatchedRepos = slices.Insert(WatchedRepos, pos, ObsProject)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
LogDebug("+ watching", ObsProject)
|
||||
opts := common.BuildResultOptions{}
|
||||
for {
|
||||
state, err := obs.BuildStatusWithState(ObsProject, &opts)
|
||||
if err != nil {
|
||||
log.Println(" *** Error fetching build for", ObsProject, err)
|
||||
time.Sleep(time.Minute)
|
||||
} else {
|
||||
opts.OldState = state.State
|
||||
LogDebug(" --> update", ObsProject, " => ", old_state)
|
||||
StatusUpdateCh <- StatusUpdateMsg{ObsProject: ObsProject, Result: state}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -103,7 +103,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
||||
|
||||
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%d err: %w", gitOrg, gitPrj, gitBranch, err)
|
||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%s err: %w", gitOrg, gitPrj, gitBranch, err)
|
||||
}
|
||||
|
||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||
@@ -526,7 +526,7 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var defs common.ListenDefinitions
|
||||
defs := &common.RabbitMQGiteaEventsProcessor{}
|
||||
var err error
|
||||
|
||||
if len(*basePath) == 0 {
|
||||
@@ -557,7 +557,7 @@ func main() {
|
||||
}
|
||||
log.Println("*** Reconfiguring ***")
|
||||
updateConfiguration(*configFilename, &defs.Orgs)
|
||||
defs.UpdateTopics()
|
||||
defs.Connection().UpdateTopics(defs)
|
||||
}
|
||||
}()
|
||||
signal.Notify(signalChannel, syscall.SIGHUP)
|
||||
@@ -573,18 +573,17 @@ func main() {
|
||||
|
||||
updateConfiguration(*configFilename, &defs.Orgs)
|
||||
|
||||
defs.GitAuthor = GitAuthor
|
||||
defs.RabbitURL, err = url.Parse(*rabbitUrl)
|
||||
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
||||
if err != nil {
|
||||
log.Panicf("cannot parse server URL. Err: %#v\n", err)
|
||||
}
|
||||
|
||||
go consistencyCheckProcess()
|
||||
log.Println("defs:", defs)
|
||||
log.Println("defs:", *defs)
|
||||
|
||||
defs.Handlers = make(map[string]common.RequestProcessor)
|
||||
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
||||
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
||||
|
||||
log.Fatal(defs.ProcessRabbitMQEvents())
|
||||
log.Fatal(common.ProcessRabbitMQEvents(defs))
|
||||
}
|
||||
|
@@ -29,7 +29,9 @@ JSON
|
||||
* _GitProjectName_: package in above org, or `org/package` for PrjGit
|
||||
* _Reviewers_: accounts associated with mandatory reviews for PrjGit. Can trigger additional
|
||||
review requests for PrjGit or associated PkgGit repos. Only when all reviews are
|
||||
satisfied, will the PrjGit PR be merged.
|
||||
satisfied, will the PrjGit PR be merged. See Reviewers below.
|
||||
* _ManualMergeOnly_: (true, false) only merge if "merge ok" comment/review by package or project maintainers or reviewers
|
||||
* _ManualMergeProject_: (true, false) only merge if "merge ok" by project maintainers or reviewers
|
||||
|
||||
example:
|
||||
|
||||
@@ -44,6 +46,29 @@ example:
|
||||
...
|
||||
]
|
||||
|
||||
Reviewers
|
||||
---------
|
||||
|
||||
Reviews is a list of accounts that need to review package and/or project. They have specific syntax
|
||||
|
||||
[~][*|-|+]username
|
||||
|
||||
General prefix of ~ indicates advisory reviewer. They will be requested, but ignored otherwise.
|
||||
|
||||
Other prefixes indicate project or package association of the reviewer:
|
||||
* `*` indicates project *and* package
|
||||
* `-` indicates project-only reviewer
|
||||
* `+` indicates package-only reviewer
|
||||
|
||||
`+` is implied. For example
|
||||
|
||||
`[foo, -bar, ~*moo]`
|
||||
|
||||
results in
|
||||
* foo -> package reviews
|
||||
* bar -> project reviews
|
||||
* moo -> package and project reviews, but ignored
|
||||
|
||||
|
||||
Maintainership
|
||||
--------------
|
||||
|
18
workflow-pr/interfaces/state_checker.go
Normal file
18
workflow-pr/interfaces/state_checker.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package interfaces
|
||||
|
||||
import "src.opensuse.org/autogits/common"
|
||||
|
||||
//go:generate mockgen -source=state_checker.go -destination=../mock/state_checker.go -typed -package mock_main
|
||||
|
||||
|
||||
type StateChecker interface {
|
||||
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
|
||||
CheckRepos() error
|
||||
ConsistencyCheckProcess() error
|
||||
}
|
||||
|
||||
type PRToProcess struct {
|
||||
Org, Repo, Branch string
|
||||
}
|
||||
|
||||
|
@@ -162,9 +162,9 @@ func main() {
|
||||
checker := CreateDefaultStateChecker(*checkOnStart, req, Gitea, time.Duration(*checkIntervalHours)*time.Hour)
|
||||
go checker.ConsistencyCheckProcess()
|
||||
|
||||
listenDefs := common.ListenDefinitions{
|
||||
listenDefs := &common.RabbitMQGiteaEventsProcessor{
|
||||
Orgs: orgs,
|
||||
GitAuthor: GitAuthor,
|
||||
// GitAuthor: GitAuthor,
|
||||
Handlers: map[string]common.RequestProcessor{
|
||||
common.RequestType_PR: req,
|
||||
common.RequestType_PRSync: req,
|
||||
@@ -172,7 +172,7 @@ func main() {
|
||||
common.RequestType_PRReviewRejected: req,
|
||||
},
|
||||
}
|
||||
listenDefs.RabbitURL, _ = url.Parse(*rabbitUrl)
|
||||
listenDefs.Connection().RabbitURL, _ = url.Parse(*rabbitUrl)
|
||||
|
||||
common.PanicOnError(listenDefs.ProcessRabbitMQEvents())
|
||||
common.PanicOnError(common.ProcessRabbitMQEvents(listenDefs))
|
||||
}
|
||||
|
@@ -16,32 +16,12 @@ import (
|
||||
)
|
||||
|
||||
func TestProjectBranchName(t *testing.T) {
|
||||
req := common.PullRequestWebhookEvent{
|
||||
Repository: &common.Repository{
|
||||
Name: "testingRepo",
|
||||
},
|
||||
Pull_Request: &common.PullRequest{
|
||||
Number: 10,
|
||||
},
|
||||
}
|
||||
|
||||
branchName := prGitBranchNameForPR(&req)
|
||||
branchName := prGitBranchNameForPR("testingRepo", 10)
|
||||
if branchName != "PR_testingRepo#10" {
|
||||
t.Error("Unexpected branch name:", branchName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjctGitSync(t *testing.T) {
|
||||
req := common.PullRequestWebhookEvent{
|
||||
Action: "pull",
|
||||
Number: 0,
|
||||
}
|
||||
|
||||
if err := processPrjGitPullRequestSync(&req); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const LocalCMD = "---"
|
||||
|
||||
func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) {
|
||||
@@ -133,7 +113,6 @@ func TestUpdatePrBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
git := &common.GitHandlerImpl{
|
||||
DebugLogger: true,
|
||||
GitCommiter: "TestCommiter",
|
||||
GitEmail: "test@testing",
|
||||
GitPath: t.TempDir(),
|
||||
@@ -146,7 +125,7 @@ func TestUpdatePrBranch(t *testing.T) {
|
||||
req.Pull_Request.Base.Sha = strings.TrimSpace(revs[1])
|
||||
req.Pull_Request.Head.Sha = strings.TrimSpace(revs[0])
|
||||
|
||||
updateSubmoduleInPR(req, git)
|
||||
updateSubmoduleInPR("mainRepo", revs[0], git)
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", "created commit"))
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", "origin", "+HEAD:+testing"))
|
||||
git.GitExecOrPanic("prj", "reset", "--hard", "testing")
|
||||
@@ -171,7 +150,6 @@ func TestCreatePrBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
git := &common.GitHandlerImpl{
|
||||
DebugLogger: true,
|
||||
GitCommiter: "TestCommiter",
|
||||
GitEmail: "test@testing",
|
||||
GitPath: t.TempDir(),
|
||||
@@ -184,7 +162,7 @@ func TestCreatePrBranch(t *testing.T) {
|
||||
req.Pull_Request.Base.Sha = strings.TrimSpace(revs[1])
|
||||
req.Pull_Request.Head.Sha = strings.TrimSpace(revs[0])
|
||||
|
||||
updateSubmoduleInPR(req, git)
|
||||
updateSubmoduleInPR("testRepo", revs[0], git)
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", "created commit"))
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", "origin", "+HEAD:testingCreated"))
|
||||
|
||||
|
@@ -5,14 +5,44 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"src.opensuse.org/autogits/common"
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
func prGitBranchNameForPR(req *common.PullRequestWebhookEvent) string {
|
||||
return fmt.Sprintf("PR_%s#%d", req.Pull_Request.Base.Repo.Name, req.Pull_Request.Number)
|
||||
func prGitBranchNameForPR(repo string, prNo int) string {
|
||||
return fmt.Sprintf("PR_%s#%d", repo, prNo)
|
||||
}
|
||||
|
||||
func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
|
||||
title_refs := make([]string, 0, len(prset.PRs)-1)
|
||||
refs := make([]string, 0, len(prset.PRs)-1)
|
||||
|
||||
for _, pr := range prset.PRs {
|
||||
org, repo, idx := pr.PRComponents()
|
||||
|
||||
title_refs = append(title_refs, repo)
|
||||
ref := fmt.Sprintf(common.PrPattern, org, repo, idx)
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
|
||||
title = "Forwarded PRs: " + strings.Join(title_refs, ", ")
|
||||
desc = fmt.Sprintf("This is a forwarded pull request by %s\nreferencing the following pull request(s):\n\n", GitAuthor) + strings.Join(refs, ",\n")
|
||||
|
||||
if prset.Config.ManualMergeOnly {
|
||||
desc = desc + "\n\nManualMergeOnly enabled. To merge, 'merge ok' is required in either the project PR or every package PR."
|
||||
}
|
||||
if prset.Config.ManualMergeProject {
|
||||
desc = desc + "\nManualMergeProject enabled. To merge, 'merge ok' is required by project maintainer in the project PR."
|
||||
}
|
||||
if !prset.Config.ManualMergeOnly && !prset.Config.ManualMergeProject {
|
||||
desc = desc + "\nAutomatic merge enabled. This will merge when all review requirements are satisfied."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func verifyRepositoryConfiguration(repo *models.Repository) error {
|
||||
@@ -29,13 +59,13 @@ func verifyRepositoryConfiguration(repo *models.Repository) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func updateSubmoduleInPR(req *common.PullRequestWebhookEvent, git common.Git) {
|
||||
common.LogDebug(req, git)
|
||||
submoduleName := req.Pull_Request.Base.Repo.Name
|
||||
commitID := req.Pull_Request.Head.Sha
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", submoduleName))
|
||||
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submoduleName), "fetch", "--depth", "1", "origin", commitID))
|
||||
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submoduleName), "checkout", commitID))
|
||||
func updateSubmoduleInPR(submodule, headSha string, git common.Git) {
|
||||
common.LogDebug("updating submodule", submodule, "to HEAD", headSha)
|
||||
// NOTE: this can fail if current PrjGit is pointing to outdated, GC'ed commit
|
||||
// as long as we can update to newer one later, we are still OK
|
||||
git.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", submodule)
|
||||
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "fetch", "--depth", "1", "origin", headSha))
|
||||
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "checkout", headSha))
|
||||
}
|
||||
|
||||
type PRProcessor struct {
|
||||
@@ -49,121 +79,229 @@ func AllocatePRProcessor(req *common.PullRequestWebhookEvent, configs common.Aut
|
||||
id := req.Pull_Request.Number
|
||||
|
||||
branch := req.Pull_Request.Base.Ref
|
||||
assumed_git_project_name := org + "/" + repo + "#" + branch
|
||||
|
||||
PRstr := fmt.Sprintf("%s/%s#%d", org, repo, id)
|
||||
common.LogInfo("*** Starting processing PR:", PRstr)
|
||||
common.LogInfo("*** Starting processing PR:", PRstr, "branch:", branch)
|
||||
|
||||
c := configs.GetPrjGitConfig(org, repo, branch)
|
||||
if c == nil {
|
||||
config := configs.GetPrjGitConfig(org, repo, branch)
|
||||
if config == nil {
|
||||
if req.Pull_Request.Base.Repo.Default_Branch == branch {
|
||||
c = configs.GetPrjGitConfig(org, repo, "")
|
||||
common.LogDebug("Default branch submission...", org, repo)
|
||||
config = configs.GetPrjGitConfig(org, repo, "")
|
||||
}
|
||||
}
|
||||
if c == nil {
|
||||
if config == nil {
|
||||
common.LogError("Cannot find config for PR.")
|
||||
return nil, fmt.Errorf("Cannot find config for PR")
|
||||
}
|
||||
|
||||
var config *common.AutogitConfig
|
||||
for _, c := range configs {
|
||||
if c.GitProjectName == assumed_git_project_name {
|
||||
config = c
|
||||
break
|
||||
}
|
||||
|
||||
if c.Organization == org {
|
||||
// default branch *or* match branch
|
||||
if (c.Branch == "" && branch == req.Pull_Request.Base.Repo.Default_Branch) ||
|
||||
(c.Branch != "" && c.Branch == branch) {
|
||||
config = c
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
common.LogDebug("found config", config)
|
||||
if config == nil {
|
||||
common.LogError("Cannot find config for branch '%s'", req.Pull_Request.Base.Ref)
|
||||
return nil, fmt.Errorf("Cannot find config for branch '%s'", req.Pull_Request.Base.Ref)
|
||||
}
|
||||
|
||||
git, err := GitHandler.CreateGitHandler(branch)
|
||||
git, err := GitHandler.CreateGitHandler(config.Organization)
|
||||
if err != nil {
|
||||
common.LogError("Cannot allocate GitHandler:", err)
|
||||
return nil, fmt.Errorf("Error allocating GitHandler. Err: %w", err)
|
||||
}
|
||||
common.LogDebug("git path:", git.GetPath())
|
||||
|
||||
// git.GitExecOrPanic("", "config", "set", "--global", "advice.submoduleMergeConflict", "false")
|
||||
// git.GitExecOrPanic("", "config", "set", "--global", "advice.mergeConflict", "false")
|
||||
|
||||
return &PRProcessor{
|
||||
config: config,
|
||||
git: git,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pr *PRProcessor) CreateOrUpdatePrjGitPR(req *common.PullRequestWebhookEvent) error {
|
||||
config := pr.config
|
||||
func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
|
||||
git := pr.git
|
||||
|
||||
branchName := prGitBranchNameForPR(req)
|
||||
|
||||
org, prj, _ := config.GetPrjGit()
|
||||
prOrg := req.Pull_Request.Base.Repo.Owner.Username
|
||||
prRepo := req.Pull_Request.Base.Repo.Name
|
||||
|
||||
if org == prOrg && prj == prRepo {
|
||||
common.LogDebug("PrjGit PR. No need to update it...")
|
||||
return nil
|
||||
}
|
||||
|
||||
prjGit, err := Gitea.CreateRepositoryIfNotExist(git, org, prj)
|
||||
common.PanicOnErrorWithMsg(err, "Error creating a prjgitrepo:", err)
|
||||
|
||||
common.PanicOnError(verifyRepositoryConfiguration(prjGit))
|
||||
|
||||
remoteName, err := git.GitClone(common.DefaultGitPrj, config.Branch, prjGit.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
|
||||
// check if branch already there, and check that out if available
|
||||
if err := git.GitExec(common.DefaultGitPrj, "fetch", remoteName, branchName); err == nil {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "checkout", "-B", branchName, remoteName+"/"+branchName)
|
||||
}
|
||||
|
||||
commitMsg := fmt.Sprintf(`auto-created for %s
|
||||
|
||||
This commit was autocreated by %s
|
||||
referencing
|
||||
|
||||
`+common.PrPattern,
|
||||
prRepo,
|
||||
GitAuthor,
|
||||
prOrg,
|
||||
prRepo,
|
||||
req.Pull_Request.Number,
|
||||
)
|
||||
|
||||
subList, err := git.GitSubmoduleList(common.DefaultGitPrj, "HEAD")
|
||||
common.PanicOnError(err)
|
||||
|
||||
if id := subList[prRepo]; id != req.Pull_Request.Head.Sha {
|
||||
updateSubmoduleInPR(req, git)
|
||||
status, err := git.GitStatus(common.DefaultGitPrj)
|
||||
common.LogDebug("status:", status)
|
||||
common.LogDebug("submodule", prRepo, " hash:", id, " -> ", req.Pull_Request.Head.Sha)
|
||||
common.PanicOnError(err)
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", remoteName, "+HEAD:"+branchName))
|
||||
if err != nil {
|
||||
common.LogError("Error fetching submodule list for PrjGit", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = Gitea.CreatePullRequestIfNotExist(prjGit, branchName, prjGit.DefaultBranch,
|
||||
fmt.Sprintf("Forwarded PR: %s", prRepo),
|
||||
fmt.Sprintf(`This is a forwarded pull request by %s
|
||||
referencing the following pull request:
|
||||
for _, pr := range prset.PRs {
|
||||
if prset.IsPrjGitPR(pr.PR) {
|
||||
continue
|
||||
}
|
||||
|
||||
`+common.PrPattern,
|
||||
GitAuthor, prOrg, prRepo, req.Pull_Request.Number),
|
||||
)
|
||||
org, repo, idx := pr.PRComponents()
|
||||
prHead := pr.PR.Head.Sha
|
||||
revert := false
|
||||
|
||||
return err
|
||||
if pr.PR.State != "open" {
|
||||
prjGitPR, err := prset.GetPrjGitPR()
|
||||
if prjGitPR != nil {
|
||||
// remove PR from PrjGit
|
||||
var valid bool
|
||||
if prHead, valid = git.GitSubmoduleCommitId(common.DefaultGitPrj, repo, prjGitPR.PR.MergeBase); !valid {
|
||||
common.LogError("Failed fetching original submodule commit id for repo")
|
||||
return err
|
||||
}
|
||||
}
|
||||
revert = true
|
||||
}
|
||||
|
||||
// find 'repo' in the submodule list
|
||||
submodule_found := false
|
||||
for submodulePath, id := range subList {
|
||||
if path.Base(submodulePath) == repo {
|
||||
submodule_found = true
|
||||
if id != prHead {
|
||||
ref := fmt.Sprintf(common.PrPattern, org, repo, idx)
|
||||
commitMsg := fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "referencing\n", ref)
|
||||
|
||||
if revert {
|
||||
commitMsg = fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "removing\n", ref)
|
||||
}
|
||||
|
||||
updateSubmoduleInPR(submodulePath, prHead, git)
|
||||
status, err := git.GitStatus(common.DefaultGitPrj)
|
||||
common.LogDebug("status:", status)
|
||||
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
|
||||
common.PanicOnError(err)
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
|
||||
}
|
||||
submodule_found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !submodule_found {
|
||||
common.LogError("Failed to find expected repo:", repo)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet) error {
|
||||
git := pr.git
|
||||
PrjGitOrg, PrjGitRepo, PrjGitBranch := prset.Config.GetPrjGit()
|
||||
PrjGit, err := Gitea.GetRepository(PrjGitOrg, PrjGitRepo)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch PrjGit repository data.", PrjGitOrg, PrjGitRepo, err)
|
||||
return err
|
||||
}
|
||||
RemoteName, err := git.GitClone(common.DefaultGitPrj, PrjGitBranch, PrjGit.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "checkout", "-B", prjGitPRbranch, RemoteName+"/"+PrjGitBranch)
|
||||
|
||||
headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch PrjGit branch", prjGitPRbranch, err)
|
||||
return err
|
||||
}
|
||||
if err := pr.SetSubmodulesToMatchPRSet(prset); err != nil {
|
||||
return err
|
||||
}
|
||||
newHeadCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch updated PrjGit branch", prjGitPRbranch, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.IsDryRun && headCommit != newHeadCommit {
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", RemoteName, "+HEAD:"+prjGitPRbranch))
|
||||
title, desc := PrjGitDescription(prset)
|
||||
pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
|
||||
if err != nil {
|
||||
common.LogError("Error creating PrjGit PR:", err)
|
||||
return err
|
||||
}
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, pr.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
})
|
||||
|
||||
prinfo := prset.AddPR(pr)
|
||||
prinfo.RemoteName = RemoteName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PRProcessor) RebaseAndSkipSubmoduleCommits(prset *common.PRSet, branch string) error {
|
||||
git := pr.git
|
||||
PrjGitPR, err := prset.GetPrjGitPR()
|
||||
common.PanicOnError(err)
|
||||
|
||||
remoteBranch := PrjGitPR.RemoteName + "/" + branch
|
||||
|
||||
common.LogDebug("Rebasing on top of", remoteBranch)
|
||||
for conflict := git.GitExec(common.DefaultGitPrj, "rebase", remoteBranch); conflict != nil; {
|
||||
statuses, err := git.GitStatus(common.DefaultGitPrj)
|
||||
if err != nil {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort")
|
||||
common.PanicOnError(err)
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if s.SubmoduleChanges != "S..." {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort")
|
||||
return fmt.Errorf("Unexpected conflict in rebase. %s", s)
|
||||
}
|
||||
}
|
||||
conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
_, _, PrjGitBranch := prset.Config.GetPrjGit()
|
||||
PrjGitPR, err := prset.GetPrjGitPR()
|
||||
if err != nil {
|
||||
common.LogError("Updating PrjGitPR but not found?", err)
|
||||
return err
|
||||
}
|
||||
|
||||
git := pr.git
|
||||
PrjGit := PrjGitPR.PR.Base.Repo
|
||||
prjGitPRbranch := PrjGitPR.PR.Head.Name
|
||||
|
||||
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch)
|
||||
|
||||
forcePush := false
|
||||
// trust Gitea here on mergeability
|
||||
if !PrjGitPR.PR.Mergeable {
|
||||
common.PanicOnError(pr.RebaseAndSkipSubmoduleCommits(prset, PrjGitBranch))
|
||||
forcePush = true
|
||||
}
|
||||
|
||||
headCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch PrjGit branch", prjGitPRbranch, err)
|
||||
return err
|
||||
}
|
||||
if err := pr.SetSubmodulesToMatchPRSet(prset); err != nil {
|
||||
return err
|
||||
}
|
||||
newHeadCommit, err := git.GitBranchHead(common.DefaultGitPrj, prjGitPRbranch)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch updated PrjGit branch", prjGitPRbranch, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.IsDryRun && headCommit != newHeadCommit {
|
||||
params := []string{"push", PrjGitPR.RemoteName, "+HEAD:" + prjGitPRbranch}
|
||||
if forcePush {
|
||||
params = slices.Insert(params, 1, "-f")
|
||||
}
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, params...))
|
||||
|
||||
// update PR
|
||||
PrjGitTitle, PrjGitBody := PrjGitDescription(prset)
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
Title: PrjGitTitle,
|
||||
Body: PrjGitBody,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
|
||||
@@ -174,16 +312,10 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
|
||||
common.LogInfo("processing opened PR:", req.Pull_Request.Url)
|
||||
prOrg := req.Pull_Request.Base.Repo.Owner.Username
|
||||
prRepo := req.Pull_Request.Base.Repo.Name
|
||||
prNo := int(req.Pull_Request.Number)
|
||||
|
||||
common.LogError(req)
|
||||
|
||||
prjGitOrg, prjGitRepo, prjGitBranch := config.GetPrjGit()
|
||||
if prOrg != prjGitOrg || prRepo != prjGitRepo {
|
||||
if err := pr.CreateOrUpdatePrjGitPR(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
prset, err := common.FetchPRSet(CurrentUser.UserName, Gitea, prOrg, prRepo, req.Number, config)
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch PRSet:", err)
|
||||
@@ -191,26 +323,69 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
|
||||
}
|
||||
common.LogInfo("fetched PRSet of size:", len(prset.PRs))
|
||||
|
||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||
prjGitPR, err := prset.GetPrjGitPR()
|
||||
if err == common.PRSet_PrjGitMissing {
|
||||
common.LogDebug("Missing PrjGit. Need to create one...")
|
||||
|
||||
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err == nil {
|
||||
common.LogDebug("Found PrjGit PR:", common.PRtoString(prjGitPR.PR))
|
||||
prjGitPRbranch = prjGitPR.PR.Head.Name
|
||||
|
||||
if prjGitPR.PR.State != "open" {
|
||||
// close entire prset
|
||||
common.LogInfo("PR State is closed:", prjGitPR.PR.State)
|
||||
for _, pr := range prset.PRs {
|
||||
if pr.PR.State == "open" {
|
||||
org, repo, idx := pr.PRComponents()
|
||||
Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
|
||||
State: "closed",
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(prset.PRs) > 1 {
|
||||
for _, pr := range prset.PRs {
|
||||
if prset.IsPrjGitPR(pr.PR) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = pr.UpdatePrjGitPR(prset); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if prjGitPR == nil {
|
||||
prjGitPR, err = prset.GetPrjGitPR()
|
||||
if err != nil {
|
||||
common.LogError("Error fetching PrjGitPR:", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
common.LogDebug("Updated PR")
|
||||
|
||||
// make sure that prjgit is consistent and only submodules that are to be *updated*
|
||||
// reset anything that changed that is not part of the prset
|
||||
// package removals/additions are *not* counted here
|
||||
org, repo, branch := config.GetPrjGit()
|
||||
if pr, err := prset.GetPrjGitPR(); err == nil {
|
||||
remote, err := git.GitClone(common.DefaultGitPrj, prjGitBranch, pr.Base.Repo.CloneURL)
|
||||
common.LogDebug("Submodule parse begin")
|
||||
orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.RemoteName+"/"+branch) // merge base must remote branch, checked in prjgit udate
|
||||
common.PanicOnError(err)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", remote, pr.Base.Ref, pr.Head.Ref)
|
||||
|
||||
common.LogDebug("Fetch done")
|
||||
orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.Base.Sha)
|
||||
common.PanicOnError(err)
|
||||
new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.Head.Sha)
|
||||
new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.Head.Sha)
|
||||
common.PanicOnError(err)
|
||||
common.LogDebug("Submodule parse done")
|
||||
|
||||
reset_submodule := func(submodule, sha string) {
|
||||
spath := path.Join(common.DefaultGitPrj, submodule)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "submodule", "update", "--init", "--depth", "1", submodule)
|
||||
git.GitExecOrPanic(spath, "fetch", "origin", sha)
|
||||
git.GitExecOrPanic(spath, "checkout", sha)
|
||||
updateSubmoduleInPR(submodule, sha, git)
|
||||
}
|
||||
|
||||
for path, commit := range new_subs {
|
||||
@@ -233,25 +408,42 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
|
||||
if len(stats) > 0 {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "commit", "-a", "-m", "Sync submodule updates with PR-set")
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "submodule", "deinit", "--all", "--force")
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "push")
|
||||
if !common.IsDryRun {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "push")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// request build review
|
||||
PR, err := prset.GetPrjGitPR()
|
||||
if err != nil {
|
||||
common.LogError("Error fetching PrjGitPR:", err)
|
||||
return nil
|
||||
}
|
||||
common.LogDebug(" num of reviewers:", len(PR.RequestedReviewers))
|
||||
org, repo, branch := config.GetPrjGit()
|
||||
common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
|
||||
maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = prset.AssignReviewers(Gitea, maintainers)
|
||||
// handle case where PrjGit PR is only one left and there are no changes, then we can just close the PR
|
||||
if len(prset.PRs) == 1 && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser {
|
||||
common.LogDebug(" --> checking if superflous PR")
|
||||
diff, err := git.GitDiff(common.DefaultGitPrj, prjGitPR.PR.MergeBase, prjGitPR.PR.Head.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(diff) == 0 {
|
||||
common.LogInfo("PR is no-op and can be closed. Closing.")
|
||||
if !common.IsDryRun {
|
||||
Gitea.AddComment(prjGitPR.PR, "Pull request no longer contains any changes. Closing.")
|
||||
_, err = Gitea.UpdatePullRequest(prjGitPR.PR.Base.Repo.Owner.UserName, prjGitPR.PR.Base.Repo.Name, prjGitPR.PR.Index, &models.EditPullRequestOption{
|
||||
State: "closed",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
common.LogDebug(" --> NOT superflous PR")
|
||||
}
|
||||
|
||||
prset.AssignReviewers(Gitea, maintainers)
|
||||
for _, pr := range prset.PRs {
|
||||
if err := verifyRepositoryConfiguration(pr.PR.Base.Repo); err != nil {
|
||||
common.LogError("Cannot set manual merge... aborting processing")
|
||||
@@ -271,25 +463,23 @@ func (pr *PRProcessor) Process(req *common.PullRequestWebhookEvent) error {
|
||||
return err
|
||||
}
|
||||
|
||||
type PullRequestProcessor interface {
|
||||
Process(req *common.PullRequestWebhookEvent, git common.Git, config *common.AutogitConfig) error
|
||||
}
|
||||
|
||||
type RequestProcessor struct {
|
||||
configuredRepos map[string][]*common.AutogitConfig
|
||||
}
|
||||
|
||||
func ProcesPullRequest(req *common.PullRequestWebhookEvent, configs []*common.AutogitConfig) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.LogInfo("panic cought --- recovered")
|
||||
common.LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if len(configs) < 1 {
|
||||
// ignoring pull request against unconfigured project (could be just regular sources?)
|
||||
return nil
|
||||
}
|
||||
|
||||
if req.Pull_Request.State != "open" {
|
||||
common.LogError("Can only deal with open PRs. This one is", req.Pull_Request.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
pr, err := AllocatePRProcessor(req, configs)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@@ -1,84 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"src.opensuse.org/autogits/common"
|
||||
mock_common "src.opensuse.org/autogits/common/mock"
|
||||
)
|
||||
|
||||
func TestClosePR(t *testing.T) {
|
||||
pr := PullRequestClosed{}
|
||||
|
||||
config := &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "branch",
|
||||
Organization: "test",
|
||||
GitProjectName: "prj",
|
||||
}
|
||||
|
||||
event := &common.PullRequestWebhookEvent{
|
||||
Action: "closed",
|
||||
Number: 1,
|
||||
Pull_Request: &common.PullRequest{
|
||||
Id: 1,
|
||||
Base: common.Head{
|
||||
Ref: "branch",
|
||||
Sha: "testing",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Default_Branch: "main1",
|
||||
},
|
||||
},
|
||||
Head: common.Head{
|
||||
Ref: "branch",
|
||||
Sha: "testing",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Default_Branch: "main1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Repository: &common.Repository{
|
||||
Owner: &common.Organization{
|
||||
Username: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
git := &common.GitHandlerImpl{
|
||||
GitCommiter: "tester",
|
||||
GitEmail: "test@suse.com",
|
||||
}
|
||||
|
||||
t.Run("PR git closed request against PrjGit == no action", func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr.gitea = mock_common.NewMockGitea(ctl)
|
||||
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "testRepo"
|
||||
event.Repository.Name = "testRepo"
|
||||
|
||||
if err := pr.Process(event, git, config); err != nil {
|
||||
t.Error("Error PrjGit closed request. Should be no error.", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("PR git closed", func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr.gitea = mock_common.NewMockGitea(ctl)
|
||||
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "prjGit"
|
||||
event.Repository.Name = "tester"
|
||||
|
||||
if err := pr.Process(event, git, config); err != nil {
|
||||
t.Error("Error PrjGit closed request. Should be no error.", err)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
@@ -12,13 +12,13 @@ import (
|
||||
)
|
||||
|
||||
func TestOpenPR(t *testing.T) {
|
||||
pr := PullRequestOpened{}
|
||||
|
||||
config := &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "branch",
|
||||
Organization: "test",
|
||||
GitProjectName: "prj",
|
||||
pr := PRProcessor{
|
||||
config: &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "branch",
|
||||
Organization: "test",
|
||||
GitProjectName: "prj",
|
||||
},
|
||||
}
|
||||
|
||||
event := &common.PullRequestWebhookEvent{
|
||||
@@ -60,14 +60,14 @@ func TestOpenPR(t *testing.T) {
|
||||
|
||||
t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr.gitea = mock_common.NewMockGitea(ctl)
|
||||
Gitea = mock_common.NewMockGitea(ctl)
|
||||
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "testRepo"
|
||||
pr.config.GitProjectName = "testRepo"
|
||||
event.Repository.Name = "testRepo"
|
||||
|
||||
if err := pr.Process(event, git, config); err != nil {
|
||||
if err := pr.Process(event); err != nil {
|
||||
t.Error("Error PrjGit opened request. Should be no error.", err)
|
||||
}
|
||||
})
|
||||
@@ -76,10 +76,10 @@ func TestOpenPR(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
pr.gitea = gitea
|
||||
Gitea = gitea
|
||||
|
||||
event.Repository.Name = "testRepo"
|
||||
config.GitProjectName = "prjcopy"
|
||||
pr.config.GitProjectName = "prjcopy"
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
setupGitForTests(t, git)
|
||||
@@ -101,7 +101,7 @@ func TestOpenPR(t *testing.T) {
|
||||
UserName: "test",
|
||||
},
|
||||
}
|
||||
gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil)
|
||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||
@@ -111,7 +111,7 @@ func TestOpenPR(t *testing.T) {
|
||||
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||
gitea.EXPECT().FetchMaintainershipFile("test", "prjcopy", "branch").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
if err != nil {
|
||||
t.Error("error:", err)
|
||||
}
|
||||
@@ -121,17 +121,17 @@ func TestOpenPR(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
pr.gitea = gitea
|
||||
Gitea = gitea
|
||||
|
||||
event.Repository.Name = "testRepo"
|
||||
config.GitProjectName = "prjcopy"
|
||||
pr.config.GitProjectName = "prjcopy"
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
setupGitForTests(t, git)
|
||||
failedErr := errors.New("Returned error here")
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(nil, failedErr)
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
if err != failedErr {
|
||||
t.Error("error:", err)
|
||||
}
|
||||
@@ -140,10 +140,10 @@ func TestOpenPR(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
pr.gitea = gitea
|
||||
Gitea = gitea
|
||||
|
||||
event.Repository.Name = "testRepo"
|
||||
config.GitProjectName = "prjcopy"
|
||||
pr.config.GitProjectName = "prjcopy"
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
setupGitForTests(t, git)
|
||||
@@ -155,7 +155,7 @@ func TestOpenPR(t *testing.T) {
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr)
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
if err != failedErr {
|
||||
t.Error("error:", err)
|
||||
}
|
||||
@@ -164,10 +164,10 @@ func TestOpenPR(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
pr.gitea = gitea
|
||||
Gitea = gitea
|
||||
|
||||
event.Repository.Name = "testRepo"
|
||||
config.GitProjectName = "prjcopy"
|
||||
pr.config.GitProjectName = "prjcopy"
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
setupGitForTests(t, git)
|
||||
@@ -189,7 +189,7 @@ func TestOpenPR(t *testing.T) {
|
||||
},
|
||||
}
|
||||
failedErr := errors.New("Returned error here")
|
||||
gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||
gitea.EXPECT().GetPullRequestReviews("org", "SomeRepo", int64(13)).Return([]*models.PullReview{}, nil)
|
||||
@@ -199,7 +199,7 @@ func TestOpenPR(t *testing.T) {
|
||||
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||
gitea.EXPECT().FetchMaintainershipFile("test", "prjcopy", "branch").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
if errors.Unwrap(err) != failedErr {
|
||||
t.Error("error:", err)
|
||||
}
|
||||
|
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
)
|
||||
/*
|
||||
func TestPRReviewed(t *testing.T) {
|
||||
testData := []struct {
|
||||
title string
|
||||
error error
|
||||
}{
|
||||
{
|
||||
title: "forward project review",
|
||||
},
|
||||
}
|
||||
|
||||
event := &common.PullRequestWebhookEvent{
|
||||
Action: "reviewed",
|
||||
Number: 1,
|
||||
Pull_Request: &common.PullRequest{
|
||||
Id: 1,
|
||||
Base: common.Head{
|
||||
Ref: "branch",
|
||||
Sha: "testing",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Default_Branch: "main1",
|
||||
},
|
||||
},
|
||||
Head: common.Head{
|
||||
Ref: "branch",
|
||||
Sha: "testing",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Default_Branch: "main1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Repository: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Owner: &common.Organization{
|
||||
Username: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testData {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock := mock_common.NewMockGitea(ctl)
|
||||
|
||||
s := PullRequestReviewed{
|
||||
gitea: mock,
|
||||
}
|
||||
|
||||
mock.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(nil, nil)
|
||||
|
||||
if err := s.Process(event, nil, nil); err != test.error {
|
||||
t.Error("unexected error:", err, "Expected:", test.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
@@ -1,5 +1,5 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
)
|
||||
|
||||
func TestSyncPR(t *testing.T) {
|
||||
pr := PullRequestSynced{}
|
||||
|
||||
config := &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "testing",
|
||||
Organization: "test",
|
||||
GitProjectName: "prj",
|
||||
pr := PRProcessor{
|
||||
config: &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "testing",
|
||||
Organization: "test",
|
||||
GitProjectName: "prj",
|
||||
},
|
||||
}
|
||||
|
||||
event := &common.PullRequestWebhookEvent{
|
||||
@@ -36,7 +36,7 @@ func TestSyncPR(t *testing.T) {
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
Owner: &common.Organization{
|
||||
Username: config.Organization,
|
||||
Username: pr.config.Organization,
|
||||
},
|
||||
Default_Branch: "main1",
|
||||
},
|
||||
@@ -52,7 +52,7 @@ func TestSyncPR(t *testing.T) {
|
||||
},
|
||||
Repository: &common.Repository{
|
||||
Owner: &common.Organization{
|
||||
Username: config.Organization,
|
||||
Username: pr.config.Organization,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -104,14 +104,14 @@ func TestSyncPR(t *testing.T) {
|
||||
|
||||
t.Run("PR sync request against PrjGit == no action", func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr.gitea = mock_common.NewMockGitea(ctl)
|
||||
Gitea = mock_common.NewMockGitea(ctl)
|
||||
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "testRepo"
|
||||
pr.config.GitProjectName = "testRepo"
|
||||
event.Repository.Name = "testRepo"
|
||||
|
||||
if err := pr.Process(event, git, config); err != nil {
|
||||
if err := pr.Process(event); err != nil {
|
||||
t.Error("Error PrjGit sync request. Should be no error.", err)
|
||||
}
|
||||
})
|
||||
@@ -123,7 +123,7 @@ func TestSyncPR(t *testing.T) {
|
||||
pr.gitea = mock
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "prjGit"
|
||||
pr.config.GitProjectName = "prjGit"
|
||||
event.Repository.Name = "testRepo"
|
||||
|
||||
setupGitForTests(t, git)
|
||||
@@ -132,10 +132,10 @@ func TestSyncPR(t *testing.T) {
|
||||
defer func() { PrjGitPR.Head.Sha = oldSha }()
|
||||
PrjGitPR.Head.Sha = "ab8adab91edb476b9762097d10c6379aa71efd6b60933a1c0e355ddacf419a95"
|
||||
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
|
||||
if err == nil || err.Error() != "Cannot fetch submodule commit id in prjgit for 'testRepo'" {
|
||||
t.Error("Invalid error received.", err)
|
||||
@@ -149,7 +149,7 @@ func TestSyncPR(t *testing.T) {
|
||||
pr.gitea = mock
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "prjGit"
|
||||
pr.config.GitProjectName = "prjGit"
|
||||
event.Repository.Name = "tester"
|
||||
|
||||
setupGitForTests(t, git)
|
||||
@@ -173,19 +173,19 @@ func TestSyncPR(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock := mock_common.NewMockGitea(ctl)
|
||||
|
||||
pr.gitea = mock
|
||||
Gitea = mock
|
||||
git.GitPath = t.TempDir()
|
||||
|
||||
config.GitProjectName = "prjGit"
|
||||
pr.config.GitProjectName = "prjGit"
|
||||
event.Repository.Name = "testRepo"
|
||||
|
||||
setupGitForTests(t, git)
|
||||
|
||||
// mock.EXPECT().GetAssociatedPrjGitPR(event).Return(PrjGitPR, nil)
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
|
||||
err := pr.Process(event, git, config)
|
||||
err := pr.Process(event)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Invalid error received.", err)
|
||||
@@ -199,14 +199,12 @@ func TestSyncPR(t *testing.T) {
|
||||
t.Error(b.String())
|
||||
}
|
||||
|
||||
/*
|
||||
* does nothing on next sync of already synced data -- PR is updated
|
||||
*/
|
||||
// does nothing on next sync of already synced data -- PR is updated
|
||||
os.RemoveAll(path.Join(git.GitPath, common.DefaultGitPrj))
|
||||
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
err = pr.Process(event, git, config)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
|
||||
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
|
||||
err = pr.Process(event)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Invalid error received.", err)
|
||||
@@ -231,3 +229,4 @@ func TestSyncPR(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
@@ -1,208 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"src.opensuse.org/autogits/common"
|
||||
|
||||
mock_common "src.opensuse.org/autogits/common/mock"
|
||||
mock_main "src.opensuse.org/autogits/workflow-pr/mock"
|
||||
)
|
||||
|
||||
func TestPRProcessor(t *testing.T) {
|
||||
tests := []struct {
|
||||
title string
|
||||
action string
|
||||
req func(req *RequestProcessor, mock PullRequestProcessor)
|
||||
}{
|
||||
{
|
||||
title: "Open routine called for PR opening",
|
||||
action: "opened",
|
||||
req: func(req *RequestProcessor, mock PullRequestProcessor) {
|
||||
req.Opened = mock
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Re-Open routine called for PR reopening",
|
||||
action: "reopened",
|
||||
req: func(req *RequestProcessor, mock PullRequestProcessor) {
|
||||
req.Opened = mock
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Sync routine called for PR sync requests",
|
||||
action: "synchronized",
|
||||
req: func(req *RequestProcessor, mock PullRequestProcessor) {
|
||||
req.Synced = mock
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Close routine called for PR closing",
|
||||
action: "closed",
|
||||
req: func(req *RequestProcessor, mock PullRequestProcessor) {
|
||||
req.Closed = mock
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Close routine called for PR closing",
|
||||
action: "reviewed",
|
||||
req: func(req *RequestProcessor, mock PullRequestProcessor) {
|
||||
req.Review = mock
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var logBuf bytes.Buffer
|
||||
oldOut := log.Writer()
|
||||
log.SetOutput(&logBuf)
|
||||
defer log.SetOutput(oldOut)
|
||||
|
||||
testConfiguration := make(map[string][]*common.AutogitConfig)
|
||||
|
||||
testConfiguration["test"] = make([]*common.AutogitConfig, 1, 1)
|
||||
testConfiguration["test"][0] = &common.AutogitConfig{
|
||||
Branch: "branch",
|
||||
}
|
||||
|
||||
event := &common.PullRequestWebhookEvent{
|
||||
// Action: "opened",
|
||||
Number: 1,
|
||||
Pull_Request: &common.PullRequest{
|
||||
Id: 1,
|
||||
Base: common.Head{
|
||||
Ref: "branch",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
},
|
||||
},
|
||||
Head: common.Head{
|
||||
Ref: "branch",
|
||||
Repo: &common.Repository{
|
||||
Name: "testRepo",
|
||||
},
|
||||
},
|
||||
},
|
||||
Repository: &common.Repository{
|
||||
Owner: &common.Organization{
|
||||
Username: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock := mock_main.NewMockPullRequestProcessor(ctl)
|
||||
mock.EXPECT().Process(event, gomock.Any(), testConfiguration["test"][0]).Return(nil)
|
||||
|
||||
req := &RequestProcessor{
|
||||
configuredRepos: testConfiguration,
|
||||
}
|
||||
test.req(req, mock)
|
||||
|
||||
event.Action = test.action
|
||||
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Error("Error processing open PR:", err)
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
req := &RequestProcessor{
|
||||
configuredRepos: testConfiguration,
|
||||
}
|
||||
|
||||
t.Run("Edit PR handling", func(t *testing.T) {
|
||||
/* ctl := gomock.NewController(t)
|
||||
closedMock := mock_main.NewMockPullRequestProcessor(ctl)
|
||||
closedMock.EXPECT().Process(event, gomock.Any(), testConfiguration["test"][0]).Return(nil)
|
||||
*/
|
||||
|
||||
// req.Closed = closedMock
|
||||
event.Action = "edited"
|
||||
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Error("Error processing edit PR:", err)
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Unknown PR-type handling", func(t *testing.T) {
|
||||
event.Action = "not existing action"
|
||||
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Missing branch in config present in PR", func(t *testing.T) {
|
||||
baseRef := event.Pull_Request.Base.Ref
|
||||
event.Pull_Request.Base.Ref = "not present"
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
event.Pull_Request.Base.Ref = baseRef
|
||||
|
||||
if err == nil {
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Invalid data present in PR", func(t *testing.T) {
|
||||
baseConfig := req.configuredRepos
|
||||
req.configuredRepos = make(map[string][]*common.AutogitConfig)
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: nil,
|
||||
})
|
||||
req.configuredRepos = baseConfig
|
||||
|
||||
if err == nil {
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Ignoring requests against unconfigured repos", func(t *testing.T) {
|
||||
baseConfig := req.configuredRepos
|
||||
req.configuredRepos = make(map[string][]*common.AutogitConfig)
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
req.configuredRepos = baseConfig
|
||||
|
||||
if err != nil {
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Failures of git handler creation", func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
gitHandler := mock_common.NewMockGitHandlerGenerator(ctl)
|
||||
|
||||
gitHandler.EXPECT().CreateGitHandler(gomock.Any()).Return(nil, fmt.Errorf("some error"))
|
||||
|
||||
err := req.ProcessFunc(&common.Request{
|
||||
Data: event,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error(logBuf.String())
|
||||
}
|
||||
})
|
||||
}
|
@@ -11,23 +11,16 @@ import (
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
"src.opensuse.org/autogits/workflow-pr/interfaces"
|
||||
)
|
||||
|
||||
//go:generate mockgen -source=repo_check.go -destination=mock/repo_check.go -typed
|
||||
|
||||
type StateChecker interface {
|
||||
VerifyProjectState(configs *common.AutogitConfig) error
|
||||
CheckRepos() error
|
||||
ConsistencyCheckProcess() error
|
||||
}
|
||||
|
||||
type DefaultStateChecker struct {
|
||||
exitCheckLoop bool
|
||||
checkOnStart bool
|
||||
checkInterval time.Duration
|
||||
|
||||
processor *RequestProcessor
|
||||
i StateChecker
|
||||
i interfaces.StateChecker
|
||||
}
|
||||
|
||||
func CreateDefaultStateChecker(checkOnStart bool, processor *RequestProcessor, gitea common.Gitea, interval time.Duration) *DefaultStateChecker {
|
||||
@@ -40,11 +33,20 @@ func CreateDefaultStateChecker(checkOnStart bool, processor *RequestProcessor, g
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) ProcessPR(git common.Git, pr *models.PullRequest, config *common.AutogitConfig) error {
|
||||
func pullRequestToEventState(state models.StateType) string {
|
||||
switch state {
|
||||
case "open":
|
||||
return "opened"
|
||||
}
|
||||
|
||||
return string(state)
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.AutogitConfig) error {
|
||||
var event common.PullRequestWebhookEvent
|
||||
|
||||
event.Pull_Request = common.PullRequestFromModel(pr)
|
||||
event.Action = string(pr.State) + "ed"
|
||||
event.Action = pullRequestToEventState(pr.State)
|
||||
event.Number = pr.Index
|
||||
event.Repository = common.RepositoryFromModel(pr.Base.Repo)
|
||||
event.Sender = *common.UserFromModel(pr.User)
|
||||
@@ -53,7 +55,7 @@ func (s *DefaultStateChecker) ProcessPR(git common.Git, pr *models.PullRequest,
|
||||
return ProcesPullRequest(&event, common.AutogitConfigs{config})
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) error {
|
||||
func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) ([]*interfaces.PRToProcess, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.LogError("panic caught")
|
||||
@@ -64,33 +66,31 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) e
|
||||
}
|
||||
}()
|
||||
|
||||
prsToProcess := []*interfaces.PRToProcess{}
|
||||
|
||||
prjGitOrg, prjGitRepo, prjGitBranch := config.GetPrjGit()
|
||||
common.LogInfo(" checking", prjGitOrg+"/"+prjGitRepo+"#"+prjGitBranch)
|
||||
|
||||
git, err := GitHandler.CreateGitHandler(config.Organization)
|
||||
common.LogDebug("Git Path:", git.GetPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot create git handler: %w", err)
|
||||
return nil, fmt.Errorf("Cannot create git handler: %w", err)
|
||||
}
|
||||
defer git.Close()
|
||||
|
||||
repo, err := Gitea.CreateRepositoryIfNotExist(git, prjGitOrg, prjGitRepo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error fetching or creating '%s/%s#%s' -- aborting verifyProjectState(). Err: %w", prjGitBranch, prjGitRepo, prjGitBranch, err)
|
||||
return nil, fmt.Errorf("Error fetching or creating '%s/%s#%s' -- aborting verifyProjectState(). Err: %w", prjGitBranch, prjGitRepo, prjGitBranch, err)
|
||||
}
|
||||
|
||||
_, err = git.GitClone(prjGitRepo, prjGitBranch, repo.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
prs, err := Gitea.GetRecentPullRequests(prjGitOrg, prjGitRepo, prjGitBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error fetching PrjGit Prs for %s/%s#%s: %w", prjGitOrg, prjGitRepo, prjGitBranch, err)
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
s.ProcessPR(git, pr, config)
|
||||
}
|
||||
|
||||
common.LogDebug(" - # of PRs to check in PrjGit:", len(prs))
|
||||
prsToProcess = append(prsToProcess, &interfaces.PRToProcess{
|
||||
Org: prjGitOrg,
|
||||
Repo: prjGitRepo,
|
||||
Branch: prjGitBranch,
|
||||
})
|
||||
submodules, err := git.GitSubmoduleList(prjGitRepo, "HEAD")
|
||||
|
||||
nextSubmodule:
|
||||
@@ -112,20 +112,16 @@ nextSubmodule:
|
||||
|
||||
branch = repo.DefaultBranch
|
||||
}
|
||||
prs, err := Gitea.GetRecentPullRequests(config.Organization, submoduleName, branch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error fetching pull requests for %s/%s#%s. Err: %w", config.Organization, submoduleName, branch, err)
|
||||
}
|
||||
common.LogDebug(" - # of PRs to check:", len(prs))
|
||||
|
||||
for _, pr := range prs {
|
||||
s.ProcessPR(git, pr, config)
|
||||
}
|
||||
prsToProcess = append(prsToProcess, &interfaces.PRToProcess{
|
||||
Org: config.Organization,
|
||||
Repo: submoduleName,
|
||||
Branch: branch,
|
||||
})
|
||||
|
||||
// check if the commited changes are syned with branches
|
||||
commits, err := Gitea.GetRecentCommits(config.Organization, submoduleName, branch, 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error fetching recent commits for %s/%s. Err: %w", config.Organization, submoduleName, err)
|
||||
return nil, fmt.Errorf("Error fetching recent commits for %s/%s. Err: %w", config.Organization, submoduleName, err)
|
||||
}
|
||||
|
||||
for idx, commit := range commits {
|
||||
@@ -149,7 +145,7 @@ nextSubmodule:
|
||||
url := git.GitExecWithOutputOrPanic(subDir, "remote", "get-url", "origin", "--push")
|
||||
sshUrl, err := common.TranslateHttpsToSshUrl(strings.TrimSpace(url))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot traslate HTTPS git URL to SSH_URL. %w", err)
|
||||
return prsToProcess, fmt.Errorf("Cannot traslate HTTPS git URL to SSH_URL. %w", err)
|
||||
}
|
||||
git.GitExecOrPanic(subDir, "remote", "set-url", "origin", "--push", sshUrl)
|
||||
git.GitExecOrPanic(subDir, "push", "origin", branch)
|
||||
@@ -157,7 +153,7 @@ nextSubmodule:
|
||||
}
|
||||
|
||||
// forward any package-gits referred by the project git, but don't go back
|
||||
return nil
|
||||
return prsToProcess, nil
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) CheckRepos() error {
|
||||
@@ -172,10 +168,25 @@ func (s *DefaultStateChecker) CheckRepos() error {
|
||||
}
|
||||
|
||||
common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName)
|
||||
if err := s.i.VerifyProjectState(config); err != nil {
|
||||
prs, err := s.i.VerifyProjectState(config)
|
||||
if err != nil {
|
||||
common.LogError(" *** verification failed, org:", org, err)
|
||||
errorList = append(errorList, err)
|
||||
}
|
||||
for _, pr := range prs {
|
||||
prs, err := Gitea.GetRecentPullRequests(pr.Org, pr.Repo, pr.Branch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error fetching pull requests for %s/%s#%s. Err: %w", pr.Org, pr.Repo, pr.Branch, err)
|
||||
}
|
||||
if len(prs) > 0 {
|
||||
common.LogDebug(fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), " - # of PRs to check:", len(prs))
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
s.ProcessPR(pr, config)
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName)
|
||||
}
|
||||
}
|
||||
|
@@ -106,7 +106,6 @@ func TestRepoCheck(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
state := mock_main.NewMockStateChecker(ctl)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
git := mock_common.NewMockGitHandlerGenerator(ctl)
|
||||
|
||||
config1 := &common.AutogitConfig{
|
||||
GitProjectName: "git_repo1",
|
||||
@@ -124,7 +123,7 @@ func TestRepoCheck(t *testing.T) {
|
||||
c.i = state
|
||||
|
||||
err := errors.New("test error")
|
||||
state.EXPECT().VerifyProjectState(configs.configuredRepos["repo1_org"][0]).Return(err)
|
||||
state.EXPECT().VerifyProjectState(configs.configuredRepos["repo1_org"][0]).Return(nil, err)
|
||||
|
||||
r := c.CheckRepos()
|
||||
|
||||
@@ -157,7 +156,6 @@ func TestVerifyProjectState(t *testing.T) {
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
git := &common.GitHandlerImpl{
|
||||
DebugLogger: true,
|
||||
GitCommiter: "TestCommiter",
|
||||
GitEmail: "test@testing",
|
||||
GitPath: t.TempDir(),
|
||||
@@ -186,11 +184,12 @@ func TestVerifyProjectState(t *testing.T) {
|
||||
gitea.EXPECT().GetRecentCommits(org, "testRepo", "testing", gomock.Any())
|
||||
|
||||
c := CreateDefaultStateChecker(false, configs, gitea, 0)
|
||||
/*
|
||||
c.git = &testGit{
|
||||
git: git,
|
||||
}
|
||||
}*/
|
||||
|
||||
err := c.VerifyProjectState(configs.configuredRepos[org][0])
|
||||
_, err := c.VerifyProjectState(configs.configuredRepos[org][0])
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -203,7 +202,6 @@ func TestVerifyProjectState(t *testing.T) {
|
||||
process := mock_main.NewMockPullRequestProcessor(ctl)
|
||||
|
||||
git := &common.GitHandlerImpl{
|
||||
DebugLogger: true,
|
||||
GitCommiter: "TestCommiter",
|
||||
GitEmail: "test@testing",
|
||||
GitPath: t.TempDir(),
|
||||
@@ -266,13 +264,14 @@ func TestVerifyProjectState(t *testing.T) {
|
||||
gitea.EXPECT().GetRecentCommits(org, "testRepo", "testing", gomock.Any())
|
||||
|
||||
c := CreateDefaultStateChecker(false, configs, gitea, 0)
|
||||
/*
|
||||
c.git = &testGit{
|
||||
git: git,
|
||||
}
|
||||
}*/
|
||||
process.EXPECT().Process(gomock.Any(), gomock.Any(), gomock.Any())
|
||||
c.processor.Opened = process
|
||||
// c.processor.Opened = process
|
||||
|
||||
err := c.VerifyProjectState(configs.configuredRepos[org][0])
|
||||
_, err := c.VerifyProjectState(configs.configuredRepos[org][0])
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
Reference in New Issue
Block a user