SHA256
1
0

1 Commits

Author SHA256 Message Date
d24b8446c0 WIP: Add TestStatusBarSvg 2025-11-11 09:00:20 +01:00
31 changed files with 598 additions and 1907 deletions

View File

@@ -61,20 +61,6 @@ type Permissions struct {
Members []string
}
const (
Label_StagingAuto = "staging/Auto"
Label_ReviewPending = "review/Pending"
Label_ReviewDone = "review/Done"
)
func LabelKey(tag_value string) string {
// capitalize first letter and remove /
if len(tag_value) == 0 {
return ""
}
return strings.ToUpper(tag_value[0:1]) + strings.ReplaceAll(tag_value[1:], "/", "")
}
type AutogitConfig struct {
Workflows []string // [pr, direct, test]
Organization string
@@ -86,8 +72,6 @@ type AutogitConfig struct {
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
Labels map[string]string // list of tags, if not default, to apply
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
@@ -204,8 +188,6 @@ func (configs AutogitConfigs) GetPrjGitConfig(org, repo, branch string) *Autogit
if c.GitProjectName == prjgit {
return c
}
}
for _, c := range configs {
if c.Organization == org && c.Branch == branch {
return c
}
@@ -291,14 +273,6 @@ func (config *AutogitConfig) GetRemoteBranch() string {
return "origin_" + config.Branch
}
func (config *AutogitConfig) Label(label string) string {
if t, found := config.Labels[LabelKey(label)]; found {
return t
}
return label
}
type StagingConfig struct {
ObsProject string
RebuildAll bool

View File

@@ -10,67 +10,6 @@ import (
mock_common "src.opensuse.org/autogits/common/mock"
)
func TestLabelKey(t *testing.T) {
tests := map[string]string{
"": "",
"foo": "Foo",
"foo/bar": "Foobar",
"foo/Bar": "FooBar",
}
for k, v := range tests {
if c := common.LabelKey(k); c != v {
t.Error("expected", v, "got", c, "input", k)
}
}
}
func TestConfigLabelParser(t *testing.T) {
tests := []struct {
name string
json string
label_value string
}{
{
name: "empty",
json: "{}",
label_value: "path/String",
},
{
name: "defined",
json: `{"Labels": {"foo": "bar", "PathString": "moo/Label"}}`,
label_value: "moo/Label",
},
{
name: "undefined",
json: `{"Labels": {"foo": "bar", "NotPathString": "moo/Label"}}`,
label_value: "path/String",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
repo := models.Repository{
DefaultBranch: "master",
}
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
if err != nil || config == nil {
t.Fatal(err)
}
if l := config.Label("path/String"); l != test.label_value {
t.Error("Expecting", test.label_value, "got", l)
}
})
}
}
func TestProjectConfigMatcher(t *testing.T) {
configs := common.AutogitConfigs{
{
@@ -82,15 +21,6 @@ func TestProjectConfigMatcher(t *testing.T) {
Branch: "main",
GitProjectName: "test/prjgit#main",
},
{
Organization: "test",
Branch: "main",
GitProjectName: "test/bar#never_match",
},
{
Organization: "test",
GitProjectName: "test/bar#main",
},
}
tests := []struct {
@@ -120,20 +50,6 @@ func TestProjectConfigMatcher(t *testing.T) {
branch: "main",
config: 1,
},
{
name: "prjgit only match",
org: "test",
repo: "bar",
branch: "main",
config: 3,
},
{
name: "non-default branch match",
org: "test",
repo: "bar",
branch: "something_main",
config: -1,
},
}
for _, test := range tests {
@@ -189,10 +105,6 @@ func TestConfigWorkflowParser(t *testing.T) {
if config.ManualMergeOnly != false {
t.Fatal("This should be false")
}
if config.Label("foobar") != "foobar" {
t.Fatal("undefined label should return default value")
}
})
}
}

View File

@@ -350,10 +350,6 @@ var ExtraGitParams []string
func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string, error) {
cmd := exec.Command("/usr/bin/git", params...)
var identityFile string
if i := os.Getenv("AUTOGITS_IDENTITY_FILE"); len(i) > 0 {
identityFile = " -i " + i
}
cmd.Env = []string{
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
"GIT_CONFIG_GLOBAL=/dev/null",
@@ -362,7 +358,7 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
"EMAIL=not@exist@src.opensuse.org",
"GIT_LFS_SKIP_SMUDGE=1",
"GIT_LFS_SKIP_PUSH=1",
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes" + identityFile,
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
}
if len(ExtraGitParams) > 0 {
cmd.Env = append(cmd.Env, ExtraGitParams...)

View File

@@ -29,7 +29,6 @@ import (
"path"
"path/filepath"
"slices"
"sync"
"time"
transport "github.com/go-openapi/runtime/client"
@@ -67,14 +66,6 @@ const (
ReviewStateUnknown models.ReviewStateType = ""
)
type GiteaLabelGetter interface {
GetLabels(org, repo string, idx int64) ([]*models.Label, error)
}
type GiteaLabelSettter interface {
SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error)
}
type GiteaTimelineFetcher interface {
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
}
@@ -100,10 +91,9 @@ type GiteaPRUpdater interface {
UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error)
}
type GiteaPRTimelineReviewFetcher interface {
type GiteaPRTimelineFetcher interface {
GiteaPRFetcher
GiteaTimelineFetcher
GiteaReviewFetcher
}
type GiteaCommitFetcher interface {
@@ -129,16 +119,10 @@ type GiteaPRChecker interface {
GiteaMaintainershipReader
}
type GiteaReviewFetcherAndRequesterAndUnrequester interface {
type GiteaReviewFetcherAndRequester interface {
GiteaReviewTimelineFetcher
GiteaCommentFetcher
GiteaReviewRequester
GiteaReviewUnrequester
}
type GiteaUnreviewTimelineFetcher interface {
GiteaTimelineFetcher
GiteaReviewUnrequester
}
type GiteaReviewRequester interface {
@@ -198,8 +182,6 @@ type Gitea interface {
GiteaCommitStatusGetter
GiteaCommitStatusSetter
GiteaSetRepoOptions
GiteaLabelGetter
GiteaLabelSettter
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
@@ -207,7 +189,7 @@ type Gitea interface {
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, bool)
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*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)
@@ -484,30 +466,6 @@ func (gitea *GiteaTransport) SetRepoOptions(owner, repo string, manual_merge boo
return ok.Payload, err
}
func (gitea *GiteaTransport) GetLabels(owner, repo string, idx int64) ([]*models.Label, error) {
ret, err := gitea.client.Issue.IssueGetLabels(issue.NewIssueGetLabelsParams().WithOwner(owner).WithRepo(repo).WithIndex(idx), gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, err
}
func (gitea *GiteaTransport) SetLabels(owner, repo string, idx int64, labels []string) ([]*models.Label, error) {
interfaceLabels := make([]interface{}, len(labels))
for i, l := range labels {
interfaceLabels[i] = l
}
ret, err := gitea.client.Issue.IssueAddLabel(issue.NewIssueAddLabelParams().WithOwner(owner).WithRepo(repo).WithIndex(idx).WithBody(&models.IssueLabelsOption{Labels: interfaceLabels}),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, nil
}
const (
GiteaNotificationType_Pull = "Pull"
)
@@ -685,7 +643,7 @@ func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org, repoName s
return repo.Payload, nil
}
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
prOptions := models.CreatePullRequestOption{
Base: targetId,
Head: srcId,
@@ -701,7 +659,7 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
WithHead(srcId),
gitea.transport.DefaultAuthentication,
); err == nil && pr.Payload.State == "open" {
return pr.Payload, nil, false
return pr.Payload, nil
}
pr, err := gitea.client.Repository.RepoCreatePullRequest(
@@ -715,10 +673,10 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
)
if err != nil {
return nil, fmt.Errorf("Cannot create pull request. %w", err), true
return nil, fmt.Errorf("Cannot create pull request. %w", err)
}
return pr.GetPayload(), nil, true
return pr.GetPayload(), nil
}
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) {
@@ -805,79 +763,45 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string)
return nil
}
type TimelineCacheData struct {
data []*models.TimelineComment
lastCheck time.Time
}
var giteaTimelineCache map[string]TimelineCacheData = make(map[string]TimelineCacheData)
var giteaTimelineCacheMutex sync.RWMutex
// returns timeline in reverse chronological create order
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
page := int64(1)
resCount := 1
prID := fmt.Sprintf("%s/%s!%d", org, repo, idx)
giteaTimelineCacheMutex.RLock()
TimelineCache, IsCached := giteaTimelineCache[prID]
var LastCachedTime strfmt.DateTime
if IsCached {
l := len(TimelineCache.data)
if l > 0 {
LastCachedTime = TimelineCache.data[0].Updated
}
// cache data for 5 seconds
if TimelineCache.lastCheck.Add(time.Second*5).Compare(time.Now()) > 0 {
giteaTimelineCacheMutex.RUnlock()
return TimelineCache.data, nil
}
}
giteaTimelineCacheMutex.RUnlock()
giteaTimelineCacheMutex.Lock()
defer giteaTimelineCacheMutex.Unlock()
retData := []*models.TimelineComment{}
for resCount > 0 {
opts := issue.NewIssueGetCommentsAndTimelineParams().WithOwner(org).WithRepo(repo).WithIndex(idx).WithPage(&page)
if !LastCachedTime.IsZero() {
opts = opts.WithSince(&LastCachedTime)
}
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(opts, gitea.transport.DefaultAuthentication)
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(
issue.NewIssueGetCommentsAndTimelineParams().
WithOwner(org).
WithRepo(repo).
WithIndex(idx).
WithPage(&page),
gitea.transport.DefaultAuthentication,
)
if err != nil {
return nil, err
}
if resCount = len(res.Payload); resCount == 0 {
break
}
for _, d := range res.Payload {
if d != nil {
if time.Time(d.Created).Compare(time.Time(LastCachedTime)) > 0 {
// created after last check, so we append here
TimelineCache.data = append(TimelineCache.data, d)
} else {
// we need something updated in the timeline, maybe
}
}
}
if resCount < 10 {
resCount = len(res.Payload)
LogDebug("page:", page, "len:", resCount)
if resCount == 0 {
break
}
page++
for _, d := range res.Payload {
if d != nil {
retData = append(retData, d)
}
}
}
LogDebug("timeline", prID, "# timeline:", len(TimelineCache.data))
slices.SortFunc(TimelineCache.data, func(a, b *models.TimelineComment) int {
LogDebug("total results:", len(retData))
slices.SortFunc(retData, func(a, b *models.TimelineComment) int {
return time.Time(b.Created).Compare(time.Time(a.Created))
})
TimelineCache.lastCheck = time.Now()
giteaTimelineCache[prID] = TimelineCache
return TimelineCache.data, nil
return retData, nil
}
func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, string, error) {

View File

@@ -18,132 +18,6 @@ import (
models "src.opensuse.org/autogits/common/gitea-generated/models"
)
// MockGiteaLabelGetter is a mock of GiteaLabelGetter interface.
type MockGiteaLabelGetter struct {
ctrl *gomock.Controller
recorder *MockGiteaLabelGetterMockRecorder
isgomock struct{}
}
// MockGiteaLabelGetterMockRecorder is the mock recorder for MockGiteaLabelGetter.
type MockGiteaLabelGetterMockRecorder struct {
mock *MockGiteaLabelGetter
}
// NewMockGiteaLabelGetter creates a new mock instance.
func NewMockGiteaLabelGetter(ctrl *gomock.Controller) *MockGiteaLabelGetter {
mock := &MockGiteaLabelGetter{ctrl: ctrl}
mock.recorder = &MockGiteaLabelGetterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaLabelGetter) EXPECT() *MockGiteaLabelGetterMockRecorder {
return m.recorder
}
// GetLabels mocks base method.
func (m *MockGiteaLabelGetter) GetLabels(org, repo string, idx int64) ([]*models.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLabels", org, repo, idx)
ret0, _ := ret[0].([]*models.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetLabels indicates an expected call of GetLabels.
func (mr *MockGiteaLabelGetterMockRecorder) GetLabels(org, repo, idx any) *MockGiteaLabelGetterGetLabelsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGiteaLabelGetter)(nil).GetLabels), org, repo, idx)
return &MockGiteaLabelGetterGetLabelsCall{Call: call}
}
// MockGiteaLabelGetterGetLabelsCall wrap *gomock.Call
type MockGiteaLabelGetterGetLabelsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaLabelGetterGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelGetterGetLabelsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaLabelGetterGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaLabelGetterGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaLabelSettter is a mock of GiteaLabelSettter interface.
type MockGiteaLabelSettter struct {
ctrl *gomock.Controller
recorder *MockGiteaLabelSettterMockRecorder
isgomock struct{}
}
// MockGiteaLabelSettterMockRecorder is the mock recorder for MockGiteaLabelSettter.
type MockGiteaLabelSettterMockRecorder struct {
mock *MockGiteaLabelSettter
}
// NewMockGiteaLabelSettter creates a new mock instance.
func NewMockGiteaLabelSettter(ctrl *gomock.Controller) *MockGiteaLabelSettter {
mock := &MockGiteaLabelSettter{ctrl: ctrl}
mock.recorder = &MockGiteaLabelSettterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaLabelSettter) EXPECT() *MockGiteaLabelSettterMockRecorder {
return m.recorder
}
// SetLabels mocks base method.
func (m *MockGiteaLabelSettter) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels)
ret0, _ := ret[0].([]*models.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SetLabels indicates an expected call of SetLabels.
func (mr *MockGiteaLabelSettterMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaLabelSettterSetLabelsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGiteaLabelSettter)(nil).SetLabels), org, repo, idx, labels)
return &MockGiteaLabelSettterSetLabelsCall{Call: call}
}
// MockGiteaLabelSettterSetLabelsCall wrap *gomock.Call
type MockGiteaLabelSettterSetLabelsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaLabelSettterSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelSettterSetLabelsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaLabelSettterSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaLabelSettterSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface.
type MockGiteaTimelineFetcher struct {
ctrl *gomock.Controller
@@ -562,32 +436,32 @@ func (c *MockGiteaPRUpdaterUpdatePullRequestCall) DoAndReturn(f func(string, str
return c
}
// MockGiteaPRTimelineReviewFetcher is a mock of GiteaPRTimelineReviewFetcher interface.
type MockGiteaPRTimelineReviewFetcher struct {
// MockGiteaPRTimelineFetcher is a mock of GiteaPRTimelineFetcher interface.
type MockGiteaPRTimelineFetcher struct {
ctrl *gomock.Controller
recorder *MockGiteaPRTimelineReviewFetcherMockRecorder
recorder *MockGiteaPRTimelineFetcherMockRecorder
isgomock struct{}
}
// MockGiteaPRTimelineReviewFetcherMockRecorder is the mock recorder for MockGiteaPRTimelineReviewFetcher.
type MockGiteaPRTimelineReviewFetcherMockRecorder struct {
mock *MockGiteaPRTimelineReviewFetcher
// MockGiteaPRTimelineFetcherMockRecorder is the mock recorder for MockGiteaPRTimelineFetcher.
type MockGiteaPRTimelineFetcherMockRecorder struct {
mock *MockGiteaPRTimelineFetcher
}
// NewMockGiteaPRTimelineReviewFetcher creates a new mock instance.
func NewMockGiteaPRTimelineReviewFetcher(ctrl *gomock.Controller) *MockGiteaPRTimelineReviewFetcher {
mock := &MockGiteaPRTimelineReviewFetcher{ctrl: ctrl}
mock.recorder = &MockGiteaPRTimelineReviewFetcherMockRecorder{mock}
// NewMockGiteaPRTimelineFetcher creates a new mock instance.
func NewMockGiteaPRTimelineFetcher(ctrl *gomock.Controller) *MockGiteaPRTimelineFetcher {
mock := &MockGiteaPRTimelineFetcher{ctrl: ctrl}
mock.recorder = &MockGiteaPRTimelineFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaPRTimelineReviewFetcher) EXPECT() *MockGiteaPRTimelineReviewFetcherMockRecorder {
func (m *MockGiteaPRTimelineFetcher) EXPECT() *MockGiteaPRTimelineFetcherMockRecorder {
return m.recorder
}
// GetPullRequest mocks base method.
func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequest(org, project string, num int64) (*models.PullRequest, error) {
func (m *MockGiteaPRTimelineFetcher) GetPullRequest(org, project string, num int64) (*models.PullRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPullRequest", org, project, num)
ret0, _ := ret[0].(*models.PullRequest)
@@ -596,76 +470,37 @@ func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequest(org, project string, n
}
// GetPullRequest indicates an expected call of GetPullRequest.
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetPullRequest(org, project, num any) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
func (mr *MockGiteaPRTimelineFetcherMockRecorder) GetPullRequest(org, project, num any) *MockGiteaPRTimelineFetcherGetPullRequestCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetPullRequest), org, project, num)
return &MockGiteaPRTimelineReviewFetcherGetPullRequestCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockGiteaPRTimelineFetcher)(nil).GetPullRequest), org, project, num)
return &MockGiteaPRTimelineFetcherGetPullRequestCall{Call: call}
}
// MockGiteaPRTimelineReviewFetcherGetPullRequestCall wrap *gomock.Call
type MockGiteaPRTimelineReviewFetcherGetPullRequestCall struct {
// MockGiteaPRTimelineFetcherGetPullRequestCall wrap *gomock.Call
type MockGiteaPRTimelineFetcherGetPullRequestCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaPRTimelineFetcherGetPullRequestCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) Do(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) Do(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineFetcherGetPullRequestCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) DoAndReturn(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetPullRequestReviews mocks base method.
func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPullRequestReviews", org, project, PRnum)
ret0, _ := ret[0].([]*models.PullReview)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPullRequestReviews indicates an expected call of GetPullRequestReviews.
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetPullRequestReviews), org, project, PRnum)
return &MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall{Call: call}
}
// MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall wrap *gomock.Call
type MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) DoAndReturn(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineFetcherGetPullRequestCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetTimeline mocks base method.
func (m *MockGiteaPRTimelineReviewFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
func (m *MockGiteaPRTimelineFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
ret0, _ := ret[0].([]*models.TimelineComment)
@@ -674,31 +509,31 @@ func (m *MockGiteaPRTimelineReviewFetcher) GetTimeline(org, repo string, idx int
}
// GetTimeline indicates an expected call of GetTimeline.
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
func (mr *MockGiteaPRTimelineFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaPRTimelineFetcherGetTimelineCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetTimeline), org, repo, idx)
return &MockGiteaPRTimelineReviewFetcherGetTimelineCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaPRTimelineFetcher)(nil).GetTimeline), org, repo, idx)
return &MockGiteaPRTimelineFetcherGetTimelineCall{Call: call}
}
// MockGiteaPRTimelineReviewFetcherGetTimelineCall wrap *gomock.Call
type MockGiteaPRTimelineReviewFetcherGetTimelineCall struct {
// MockGiteaPRTimelineFetcherGetTimelineCall wrap *gomock.Call
type MockGiteaPRTimelineFetcherGetTimelineCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaPRTimelineFetcherGetTimelineCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineFetcherGetTimelineCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineFetcherGetTimelineCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
@@ -1215,32 +1050,32 @@ func (c *MockGiteaPRCheckerGetTimelineCall) DoAndReturn(f func(string, string, i
return c
}
// MockGiteaReviewFetcherAndRequesterAndUnrequester is a mock of GiteaReviewFetcherAndRequesterAndUnrequester interface.
type MockGiteaReviewFetcherAndRequesterAndUnrequester struct {
// MockGiteaReviewFetcherAndRequester is a mock of GiteaReviewFetcherAndRequester interface.
type MockGiteaReviewFetcherAndRequester struct {
ctrl *gomock.Controller
recorder *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder
recorder *MockGiteaReviewFetcherAndRequesterMockRecorder
isgomock struct{}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder is the mock recorder for MockGiteaReviewFetcherAndRequesterAndUnrequester.
type MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder struct {
mock *MockGiteaReviewFetcherAndRequesterAndUnrequester
// MockGiteaReviewFetcherAndRequesterMockRecorder is the mock recorder for MockGiteaReviewFetcherAndRequester.
type MockGiteaReviewFetcherAndRequesterMockRecorder struct {
mock *MockGiteaReviewFetcherAndRequester
}
// NewMockGiteaReviewFetcherAndRequesterAndUnrequester creates a new mock instance.
func NewMockGiteaReviewFetcherAndRequesterAndUnrequester(ctrl *gomock.Controller) *MockGiteaReviewFetcherAndRequesterAndUnrequester {
mock := &MockGiteaReviewFetcherAndRequesterAndUnrequester{ctrl: ctrl}
mock.recorder = &MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder{mock}
// NewMockGiteaReviewFetcherAndRequester creates a new mock instance.
func NewMockGiteaReviewFetcherAndRequester(ctrl *gomock.Controller) *MockGiteaReviewFetcherAndRequester {
mock := &MockGiteaReviewFetcherAndRequester{ctrl: ctrl}
mock.recorder = &MockGiteaReviewFetcherAndRequesterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) EXPECT() *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder {
func (m *MockGiteaReviewFetcherAndRequester) EXPECT() *MockGiteaReviewFetcherAndRequesterMockRecorder {
return m.recorder
}
// GetIssueComments mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
func (m *MockGiteaReviewFetcherAndRequester) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssueComments", org, project, issueNo)
ret0, _ := ret[0].([]*models.Comment)
@@ -1249,37 +1084,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetIssueComments(org,
}
// GetIssueComments indicates an expected call of GetIssueComments.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetIssueComments(org, project, issueNo any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetIssueComments(org, project, issueNo any) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssueComments", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetIssueComments), org, project, issueNo)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssueComments", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetIssueComments), org, project, issueNo)
return &MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall struct {
// MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) Return(arg0 []*models.Comment, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) Return(arg0 []*models.Comment, arg1 error) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) Do(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) Do(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) DoAndReturn(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) DoAndReturn(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetPullRequestReviews mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
func (m *MockGiteaReviewFetcherAndRequester) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPullRequestReviews", org, project, PRnum)
ret0, _ := ret[0].([]*models.PullReview)
@@ -1288,37 +1123,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetPullRequestReviews
}
// GetPullRequestReviews indicates an expected call of GetPullRequestReviews.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetPullRequestReviews), org, project, PRnum)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetPullRequestReviews), org, project, PRnum)
return &MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall struct {
// MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetTimeline mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
func (m *MockGiteaReviewFetcherAndRequester) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
ret0, _ := ret[0].([]*models.TimelineComment)
@@ -1327,37 +1162,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetTimeline(org, repo
}
// GetTimeline indicates an expected call of GetTimeline.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetTimeline), org, repo, idx)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetTimeline), org, repo, idx)
return &MockGiteaReviewFetcherAndRequesterGetTimelineCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall struct {
// MockGiteaReviewFetcherAndRequesterGetTimelineCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterGetTimelineCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// RequestReviews mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) RequestReviews(pr *models.PullRequest, reviewer ...string) ([]*models.PullReview, error) {
func (m *MockGiteaReviewFetcherAndRequester) RequestReviews(pr *models.PullRequest, reviewer ...string) ([]*models.PullReview, error) {
m.ctrl.T.Helper()
varargs := []any{pr}
for _, a := range reviewer {
@@ -1370,181 +1205,32 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) RequestReviews(pr *mo
}
// RequestReviews indicates an expected call of RequestReviews.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) RequestReviews(pr any, reviewer ...any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) RequestReviews(pr any, reviewer ...any) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{pr}, reviewer...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).RequestReviews), varargs...)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall{Call: call}
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).RequestReviews), varargs...)
return &MockGiteaReviewFetcherAndRequesterRequestReviewsCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall struct {
// MockGiteaReviewFetcherAndRequesterRequestReviewsCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterRequestReviewsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) Do(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) Do(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) DoAndReturn(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// UnrequestReview mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) UnrequestReview(org, repo string, id int64, reviwers ...string) error {
m.ctrl.T.Helper()
varargs := []any{org, repo, id}
for _, a := range reviwers {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UnrequestReview", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UnrequestReview indicates an expected call of UnrequestReview.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) UnrequestReview(org, repo, id any, reviwers ...any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{org, repo, id}, reviwers...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnrequestReview", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).UnrequestReview), varargs...)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Return(arg0 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Do(f func(string, string, int64, ...string) error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) DoAndReturn(f func(string, string, int64, ...string) error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaUnreviewTimelineFetcher is a mock of GiteaUnreviewTimelineFetcher interface.
type MockGiteaUnreviewTimelineFetcher struct {
ctrl *gomock.Controller
recorder *MockGiteaUnreviewTimelineFetcherMockRecorder
isgomock struct{}
}
// MockGiteaUnreviewTimelineFetcherMockRecorder is the mock recorder for MockGiteaUnreviewTimelineFetcher.
type MockGiteaUnreviewTimelineFetcherMockRecorder struct {
mock *MockGiteaUnreviewTimelineFetcher
}
// NewMockGiteaUnreviewTimelineFetcher creates a new mock instance.
func NewMockGiteaUnreviewTimelineFetcher(ctrl *gomock.Controller) *MockGiteaUnreviewTimelineFetcher {
mock := &MockGiteaUnreviewTimelineFetcher{ctrl: ctrl}
mock.recorder = &MockGiteaUnreviewTimelineFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaUnreviewTimelineFetcher) EXPECT() *MockGiteaUnreviewTimelineFetcherMockRecorder {
return m.recorder
}
// GetTimeline mocks base method.
func (m *MockGiteaUnreviewTimelineFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
ret0, _ := ret[0].([]*models.TimelineComment)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTimeline indicates an expected call of GetTimeline.
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).GetTimeline), org, repo, idx)
return &MockGiteaUnreviewTimelineFetcherGetTimelineCall{Call: call}
}
// MockGiteaUnreviewTimelineFetcherGetTimelineCall wrap *gomock.Call
type MockGiteaUnreviewTimelineFetcherGetTimelineCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// UnrequestReview mocks base method.
func (m *MockGiteaUnreviewTimelineFetcher) UnrequestReview(org, repo string, id int64, reviwers ...string) error {
m.ctrl.T.Helper()
varargs := []any{org, repo, id}
for _, a := range reviwers {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UnrequestReview", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UnrequestReview indicates an expected call of UnrequestReview.
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) UnrequestReview(org, repo, id any, reviwers ...any) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
mr.mock.ctrl.T.Helper()
varargs := append([]any{org, repo, id}, reviwers...)
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnrequestReview", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).UnrequestReview), varargs...)
return &MockGiteaUnreviewTimelineFetcherUnrequestReviewCall{Call: call}
}
// MockGiteaUnreviewTimelineFetcherUnrequestReviewCall wrap *gomock.Call
type MockGiteaUnreviewTimelineFetcherUnrequestReviewCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) Return(arg0 error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) Do(f func(string, string, int64, ...string) error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) DoAndReturn(f func(string, string, int64, ...string) error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) DoAndReturn(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
@@ -2164,13 +1850,12 @@ func (c *MockGiteaAddReviewCommentCall) DoAndReturn(f func(*models.PullRequest,
}
// CreatePullRequestIfNotExist mocks base method.
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreatePullRequestIfNotExist", repo, srcId, targetId, title, body)
ret0, _ := ret[0].(*models.PullRequest)
ret1, _ := ret[1].(error)
ret2, _ := ret[2].(bool)
return ret0, ret1, ret2
return ret0, ret1
}
// CreatePullRequestIfNotExist indicates an expected call of CreatePullRequestIfNotExist.
@@ -2186,19 +1871,19 @@ type MockGiteaCreatePullRequestIfNotExistCall struct {
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error, arg2 bool) *MockGiteaCreatePullRequestIfNotExistCall {
c.Call = c.Call.Return(arg0, arg1, arg2)
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaCreatePullRequestIfNotExistCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
@@ -2517,45 +2202,6 @@ func (c *MockGiteaGetIssueCommentsCall) DoAndReturn(f func(string, string, int64
return c
}
// GetLabels mocks base method.
func (m *MockGitea) GetLabels(org, repo string, idx int64) ([]*models.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLabels", org, repo, idx)
ret0, _ := ret[0].([]*models.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetLabels indicates an expected call of GetLabels.
func (mr *MockGiteaMockRecorder) GetLabels(org, repo, idx any) *MockGiteaGetLabelsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGitea)(nil).GetLabels), org, repo, idx)
return &MockGiteaGetLabelsCall{Call: call}
}
// MockGiteaGetLabelsCall wrap *gomock.Call
type MockGiteaGetLabelsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaGetLabelsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetNotifications mocks base method.
func (m *MockGitea) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
m.ctrl.T.Helper()
@@ -3147,45 +2793,6 @@ func (c *MockGiteaSetCommitStatusCall) DoAndReturn(f func(string, string, string
return c
}
// SetLabels mocks base method.
func (m *MockGitea) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels)
ret0, _ := ret[0].([]*models.Label)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SetLabels indicates an expected call of SetLabels.
func (mr *MockGiteaMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaSetLabelsCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGitea)(nil).SetLabels), org, repo, idx, labels)
return &MockGiteaSetLabelsCall{Call: call}
}
// MockGiteaSetLabelsCall wrap *gomock.Call
type MockGiteaSetLabelsCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaSetLabelsCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetNotificationRead mocks base method.
func (m *MockGitea) SetNotificationRead(notificationId int64) error {
m.ctrl.T.Helper()

View File

@@ -23,8 +23,7 @@ type PRSet struct {
PRs []*PRInfo
Config *AutogitConfig
BotUser string
HasAutoStaging bool
BotUser string
}
func (prinfo *PRInfo) PRComponents() (org string, repo string, idx int64) {
@@ -34,41 +33,6 @@ func (prinfo *PRInfo) PRComponents() (org string, repo string, idx int64) {
return
}
func (prinfo *PRInfo) RemoveReviewers(gitea GiteaUnreviewTimelineFetcher, Reviewers []string, BotUser string) {
org, repo, idx := prinfo.PRComponents()
tl, err := gitea.GetTimeline(org, repo, idx)
if err != nil {
LogError("Failed to fetch timeline for", PRtoString(prinfo.PR), err)
}
// find review request for each reviewer
ReviewersToUnrequest := Reviewers
ReviewersAlreadyChecked := []string{}
for _, tlc := range tl {
if tlc.Type == TimelineCommentType_ReviewRequested && tlc.Assignee != nil {
user := tlc.Assignee.UserName
if idx := slices.Index(ReviewersToUnrequest, user); idx >= 0 && !slices.Contains(ReviewersAlreadyChecked, user) {
if tlc.User != nil && tlc.User.UserName == BotUser {
ReviewersAlreadyChecked = append(ReviewersAlreadyChecked, user)
continue
}
ReviewersToUnrequest = slices.Delete(ReviewersToUnrequest, idx, idx+1)
if len(Reviewers) == 0 {
break
}
}
}
}
LogDebug("Unrequesting reviewes for", PRtoString(prinfo.PR), ReviewersToUnrequest)
err = gitea.UnrequestReview(org, repo, idx, ReviewersToUnrequest...)
if err != nil {
LogError("Failed to unrequest reviewers for", PRtoString(prinfo.PR), err)
}
}
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 {
@@ -99,7 +63,7 @@ func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRIn
var Timeline_RefIssueNotFound error = errors.New("RefIssue not found on the timeline")
func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher, org, repo string, num int64, config *AutogitConfig) (*models.PullRequest, error) {
func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineFetcher, org, repo string, num int64, config *AutogitConfig) (*models.PullRequest, error) {
timeline, err := gitea.GetTimeline(org, repo, num)
if err != nil {
LogError("Failed to fetch timeline for", org, repo, "#", num, err)
@@ -124,19 +88,14 @@ func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher,
}
pr, err := gitea.GetPullRequest(prjGitOrg, prjGitRepo, issue.Index)
if err != nil {
switch err.(type) {
case *repository.RepoGetPullRequestNotFound: // deleted?
continue
default:
LogDebug("PrjGit RefIssue fetch error from timeline", issue.Index, err)
continue
}
switch err.(type) {
case *repository.RepoGetPullRequestNotFound: // deleted?
continue
default:
LogDebug("PrjGit RefIssue fetch error from timeline", issue.Index, err)
}
LogDebug("found ref PR on timeline:", PRtoString(pr))
if pr.Base.Name != prjGitBranch {
LogDebug(" -> not matching:", pr.Base.Name, prjGitBranch)
if pr.Base.Ref != prjGitBranch {
continue
}
@@ -156,7 +115,7 @@ func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher,
return nil, Timeline_RefIssueNotFound
}
func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
func FetchPRSet(user string, gitea GiteaPRTimelineFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
var pr *models.PullRequest
var err error
@@ -182,15 +141,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
return nil, err
}
for _, pr := range prs {
org, repo, idx := pr.PRComponents()
reviews, err := FetchGiteaReviews(gitea, org, repo, idx)
if err != nil {
LogError("Error fetching reviews for", PRtoString(pr.PR), ":", err)
}
pr.Reviews = reviews
}
return &PRSet{
PRs: prs,
Config: config,
@@ -198,12 +148,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
}, nil
}
func (prset *PRSet) RemoveReviewers(gitea GiteaUnreviewTimelineFetcher, reviewers []string) {
for _, prinfo := range prset.PRs {
prinfo.RemoveReviewers(gitea, reviewers, prset.BotUser)
}
}
func (rs *PRSet) Find(pr *models.PullRequest) (*PRInfo, bool) {
for _, p := range rs.PRs {
if p.PR.Base.RepoID == pr.Base.RepoID &&
@@ -289,144 +233,67 @@ next_rs:
}
for _, pr := range prjpr_set {
if strings.EqualFold(prinfo.PR.Base.Repo.Owner.UserName, pr.Org) && strings.EqualFold(prinfo.PR.Base.Repo.Name, pr.Repo) && prinfo.PR.Index == pr.Num {
if prinfo.PR.Base.Repo.Owner.UserName == pr.Org && prinfo.PR.Base.Repo.Name == pr.Repo && prinfo.PR.Index == pr.Num {
continue next_rs
}
}
LogDebug(" PR: ", PRtoString(prinfo.PR), "not found in project git PRSet")
return false
}
return true
}
func (rs *PRSet) FindMissingAndExtraReviewers(maintainers MaintainershipData, idx int) (missing, extra []string) {
func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintainers MaintainershipData) error {
configReviewers := ParseReviewers(rs.Config.Reviewers)
// remove reviewers that were already requested and are not stale
prjMaintainers := maintainers.ListProjectMaintainers(nil)
LogDebug("project maintainers:", prjMaintainers)
for _, pr := range rs.PRs {
reviewers := []string{}
pr := rs.PRs[idx]
if rs.IsPrjGitPR(pr.PR) {
missing = slices.Concat(configReviewers.Prj, configReviewers.PrjOptional)
if rs.HasAutoStaging {
missing = append(missing, Bot_BuildReview)
}
LogDebug("PrjGit submitter:", pr.PR.User.UserName)
// only need project maintainer reviews if:
// * not created by a bot and has other PRs, or
// * not created by maintainer
noReviewPRCreators := prjMaintainers
if len(rs.PRs) > 1 {
noReviewPRCreators = append(noReviewPRCreators, rs.BotUser)
}
if slices.Contains(noReviewPRCreators, pr.PR.User.UserName) || pr.Reviews.IsReviewedByOneOf(prjMaintainers...) {
LogDebug("Project already reviewed by a project maintainer, remove rest")
// do not remove reviewers if they are also maintainers
prjMaintainers = slices.DeleteFunc(prjMaintainers, func(m string) bool { return slices.Contains(missing, m) })
extra = slices.Concat(prjMaintainers, []string{rs.BotUser})
if rs.IsPrjGitPR(pr.PR) {
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(nil))
}
} else {
// if bot not created PrjGit or prj maintainer, we need to add project reviewers here
if slices.Contains(noReviewPRCreators, pr.PR.User.UserName) {
LogDebug("No need for project maintainers")
extra = slices.Concat(prjMaintainers, []string{rs.BotUser})
pkg := pr.PR.Base.Repo.Name
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(nil), maintainers.ListPackageMaintainers(pkg, nil), configReviewers.PkgOptional)
}
slices.Sort(reviewers)
reviewers = slices.Compact(reviewers)
// submitters do not need to review their own work
if idx := slices.Index(reviewers, pr.PR.User.UserName); idx != -1 {
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
}
for idx := 0; idx < len(reviewers); {
user := reviewers[idx]
if reviews.HasPendingReviewBy(user) || reviews.IsReviewedBy(user) {
reviewers = slices.Delete(reviewers, idx, idx+1)
LogDebug("removing reviewer:", user)
} else {
LogDebug("Adding prjMaintainers to PrjGit")
missing = append(missing, prjMaintainers...)
idx++
}
}
} else {
pkg := pr.PR.Base.Repo.Name
pkgMaintainers := maintainers.ListPackageMaintainers(pkg, nil)
Maintainers := slices.Concat(prjMaintainers, pkgMaintainers)
noReviewPkgPRCreators := pkgMaintainers
LogDebug("packakge maintainers:", Maintainers)
missing = slices.Concat(configReviewers.Pkg, configReviewers.PkgOptional)
if slices.Contains(noReviewPkgPRCreators, pr.PR.User.UserName) || pr.Reviews.IsReviewedByOneOf(Maintainers...) {
// submitter is maintainer or already reviewed
LogDebug("Package reviewed by maintainer (or subitter is maintainer), remove the rest of them")
// do not remove reviewers if they are also maintainers
Maintainers = slices.DeleteFunc(Maintainers, func(m string) bool { return slices.Contains(missing, m) })
extra = slices.Concat(Maintainers, []string{rs.BotUser})
} else {
// maintainer review is missing
LogDebug("Adding package maintainers to package git")
missing = append(missing, pkgMaintainers...)
}
}
slices.Sort(missing)
missing = slices.Compact(missing)
slices.Sort(extra)
extra = slices.Compact(extra)
// submitters cannot review their own work
if idx := slices.Index(missing, pr.PR.User.UserName); idx != -1 {
missing = slices.Delete(missing, idx, idx+1)
}
LogDebug("PR: ", PRtoString(pr.PR))
LogDebug(" preliminary add reviewers for PR:", missing)
LogDebug(" preliminary rm reviewers for PR:", extra)
// remove missing reviewers that are already done or already pending
for idx := 0; idx < len(missing); {
user := missing[idx]
if pr.Reviews.HasPendingReviewBy(user) || pr.Reviews.IsReviewedBy(user) {
missing = slices.Delete(missing, idx, idx+1)
LogDebug(" removing done/pending reviewer:", user)
} else {
idx++
}
}
// remove extra reviews that are actually only pending, and only pending by us
for idx := 0; idx < len(extra); {
user := extra[idx]
rr := pr.Reviews.FindReviewRequester(user)
if rr != nil && rr.User.UserName == rs.BotUser && pr.Reviews.HasPendingReviewBy(user) {
// good to remove this review
idx++
} else {
// this review should not be considered as extra by us
LogDebug(" - cannot find? to remove", user)
if rr != nil {
LogDebug(" ", rr.User.UserName, "vs.", rs.BotUser, pr.Reviews.HasPendingReviewBy(user))
}
extra = slices.Delete(extra, idx, idx+1)
}
}
LogDebug(" add reviewers for PR:", missing)
LogDebug(" rm reviewers for PR:", extra)
return missing, extra
}
func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequesterAndUnrequester, maintainers MaintainershipData) error {
for idx, pr := range rs.PRs {
missingReviewers, extraReviewers := rs.FindMissingAndExtraReviewers(maintainers, idx)
if len(missingReviewers) > 0 {
LogDebug(" Requesting reviews from:", missingReviewers)
// get maintainers associated with the PR too
if len(reviewers) > 0 {
LogDebug("Requesting reviews from:", reviewers)
if !IsDryRun {
for _, r := range missingReviewers {
for _, r := range reviewers {
if _, err := gitea.RequestReviews(pr.PR, r); err != nil {
LogError("Cannot create reviews on", PRtoString(pr.PR), "for user:", r, err)
}
}
}
}
if len(extraReviewers) > 0 {
LogDebug(" UnRequesting reviews from:", extraReviewers)
if !IsDryRun {
for _, r := range extraReviewers {
org, repo, idx := pr.PRComponents()
if err := gitea.UnrequestReview(org, repo, idx, r); err != nil {
LogError("Cannot unrequest reviews on", PRtoString(pr.PR), "for user:", r, err)
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)
}
}
}
@@ -452,12 +319,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
if err == nil && prjgit != nil {
reviewers := slices.Concat(configReviewers.Prj, maintainers.ListProjectMaintainers(groups))
LogDebug("Fetching reviews for", prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
r, err := FetchGiteaReviews(gitea, 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
}
r.RequestedReviewers = reviewers
prjgit.Reviews = r
if prjgit.Reviews.IsManualMergeOK() {
is_manually_reviewed_ok = true
@@ -473,12 +339,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
pkg := pr.PR.Base.Repo.Name
reviewers := slices.Concat(configReviewers.Pkg, maintainers.ListPackageMaintainers(pkg, groups))
LogDebug("Fetching reviews for", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
r, err := FetchGiteaReviews(gitea, 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
}
r.RequestedReviewers = reviewers
pr.Reviews = r
if !pr.Reviews.IsManualMergeOK() {
LogInfo("Not approved manual merge. PR:", pr.PR.URL)
@@ -500,9 +365,6 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
var pkg string
if rs.IsPrjGitPR(pr.PR) {
reviewers = configReviewers.Prj
if rs.HasAutoStaging {
reviewers = append(reviewers, Bot_BuildReview)
}
pkg = ""
} else {
reviewers = configReviewers.Pkg
@@ -514,12 +376,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
return false
}
r, err := FetchGiteaReviews(gitea, 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 gitea reaviews for PR:", err)
return false
}
r.RequestedReviewers = reviewers
is_manually_reviewed_ok = r.IsApproved()
LogDebug("PR to", pr.PR.Base.Repo.Name, "reviewed?", is_manually_reviewed_ok)
@@ -532,7 +393,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
if need_maintainer_review := !rs.IsPrjGitPR(pr.PR) || pr.PR.User.UserName != rs.BotUser; need_maintainer_review {
// Do not expand groups here, as the group-review-bot will ACK if group has reviewed.
if is_manually_reviewed_ok = maintainers.IsApproved(pkg, r.Reviews, pr.PR.User.UserName, nil); !is_manually_reviewed_ok {
if is_manually_reviewed_ok = maintainers.IsApproved(pkg, r.reviews, pr.PR.User.UserName, nil); !is_manually_reviewed_ok {
LogDebug(" not approved?", pkg)
return false
}

View File

@@ -2,6 +2,7 @@ package common_test
import (
"errors"
"fmt"
"os"
"os/exec"
"path"
@@ -14,23 +15,22 @@ 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 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))
@@ -75,7 +75,7 @@ func TestPR(t *testing.T) {
consistentSet bool
prjGitPRIndex int
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error)
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error)
}{
{
name: "Error fetching PullRequest",
@@ -147,7 +147,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
},
},
@@ -179,7 +179,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
},
},
@@ -207,7 +207,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -241,7 +241,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -275,7 +275,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -311,7 +311,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -346,7 +346,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -388,7 +388,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -430,7 +430,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -473,7 +473,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
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",
@@ -500,7 +500,7 @@ func TestPR(t *testing.T) {
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
config := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
Branch: "branch",
@@ -515,7 +515,7 @@ func TestPR(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaPRChecker(ctl)
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
@@ -619,508 +619,283 @@ func TestPR(t *testing.T) {
}
}
func TestFindMissingAndExtraReviewers(t *testing.T) {
func TestPRAssignReviewers(t *testing.T) {
t.Skip("FAIL: unexpected calls, missing calls")
tests := []struct {
name string
config common.AutogitConfig
reviewers []struct {
org, repo string
num int64
reviewer string
}
prset *common.PRSet
maintainers common.MaintainershipData
pkgReviews []*models.PullReview
pkgTimeline []*models.TimelineComment
prjReviews []*models.PullReview
prjTimeline []*models.TimelineComment
noAutoStaging bool
expected_missing_reviewers [][]string
expected_extra_reviewers [][]string
expectedReviewerCall [2][]string
}{
{
name: "No reviewers",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{},
},
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "One project reviewer only",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1"},
},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
expected_missing_reviewers: [][]string{
[]string{},
[]string{"autogits_obs_staging_bot", "user1"},
},
},
{
name: "One project reviewer only and no auto staging",
noAutoStaging: true,
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1"},
},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
expected_missing_reviewers: [][]string{
nil,
{"user1"},
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "One project reviewer and one pkg reviewer only",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
expected_missing_reviewers: [][]string{
[]string{"user2"},
[]string{"autogits_obs_staging_bot", "user1"},
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"user2", "prjmaintainer", "pkgmaintainer"}},
},
{
name: "No need to get reviews of submitter reviewer",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}},
RequestedReviewers: []string{"m1"},
FullTimeline: []*models.TimelineComment{
{User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested},
},
},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
BotUser: "bot",
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"m1", "submitter"}}},
expected_missing_reviewers: [][]string{
nil,
{"autogits_obs_staging_bot", "user1"},
},
expected_extra_reviewers: [][]string{
{"m1"},
name: "No need to get reviews of submitter",
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "No need to get reviews of submitter maintainer",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "foo"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
name: "Reviews are done",
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
pkgReviews: []*models.PullReview{
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "user2"},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "pkgmaintainer"},
},
{
State: common.ReviewStatePending,
User: &models.User{UserName: "prjmaintainer"},
},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter"}}},
expected_missing_reviewers: [][]string{
[]string{},
[]string{"autogits_obs_staging_bot", "user1"},
},
},
{
name: "Add reviewer if also maintainer where review by maintainer is not needed",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{},
},
prjReviews: []*models.PullReview{
{
State: common.ReviewStateRequestChanges,
User: &models.User{UserName: "user1"},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter", "*reviewer"},
{
State: common.ReviewStateRequestReview,
User: &models.User{UserName: "autogits_obs_staging_bot"},
},
BotUser: "bot",
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}},
expected_missing_reviewers: [][]string{
[]string{"reviewer"},
[]string{"autogits_obs_staging_bot", "reviewer", "user1"},
},
},
{
name: "Dont remove reviewer if also maintainer",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []string{"reviewer"},
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
RequestedReviewers: []string{"reviewer"},
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter", "*reviewer"},
},
BotUser: "bot",
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}},
expected_missing_reviewers: [][]string{
[]string{},
[]string{"autogits_obs_staging_bot", "user1"},
},
},
{
name: "Extra project reviewer on the package",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
},
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user2"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
},
},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
},
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
},
},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
BotUser: "bot",
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer"}, "": {"prjmaintainer"}}},
expected_missing_reviewers: [][]string{},
expected_extra_reviewers: [][]string{{"prjmaintainer"}},
expectedReviewerCall: [2][]string{},
},
{
name: "Extra project reviewers on the package and project",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "pkgm1"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "pkgm2"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "someother"}},
},
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "pkgm2", "someother", "prj1", "prj2"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm2"}},
},
},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}},
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
},
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1", "prj2"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}},
},
},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
BotUser: "bot",
name: "Stale review is not done, re-request it",
config: common.AutogitConfig{
GitProjectName: "org/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}},
expected_missing_reviewers: [][]string{},
expected_extra_reviewers: [][]string{{"pkgm1", "pkgm2", "prj1", "prj2", "prjmaintainer"}, {"prj1", "prj2"}},
pkgReviews: []*models.PullReview{
{
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"}},
},
{
name: "No extra project reviewers on the package and project (all pending)",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "submitter"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgmaintainer"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prjmaintainer"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgm1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "someother"}},
},
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "someother", "prj1"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "someother"}},
},
},
},
{
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prj"}}},
},
Reviews: &common.PRReviews{
Reviews: []*models.PullReview{
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
},
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1"},
FullTimeline: []*models.TimelineComment{
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "user1"}},
},
},
},
},
Config: &common.AutogitConfig{
GitProjectName: "prj/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
BotUser: "bot",
name: "Stale optional review is not done, re-request it",
config: common.AutogitConfig{
GitProjectName: "prg/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2", "~bot"},
},
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}},
expected_missing_reviewers: [][]string{{"pkgm2", "prj2"}},
expected_extra_reviewers: [][]string{{}, {"prj1"}},
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) {
test.prset.HasAutoStaging = !test.noAutoStaging
for idx, pr := range test.prset.PRs {
missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx)
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
// avoid nil dereference below, by adding empty array elements
if idx >= len(test.expected_missing_reviewers) {
test.expected_missing_reviewers = append(test.expected_missing_reviewers, nil)
}
if idx >= len(test.expected_extra_reviewers) {
test.expected_extra_reviewers = append(test.expected_extra_reviewers, nil)
}
if test.pkgTimeline == nil {
test.pkgTimeline = reviewsToTimeline(test.pkgReviews)
}
if test.prjTimeline == nil {
test.prjTimeline = reviewsToTimeline(test.prjReviews)
}
slices.Sort(test.expected_extra_reviewers[idx])
slices.Sort(test.expected_missing_reviewers[idx])
if slices.Compare(missing, test.expected_missing_reviewers[idx]) != 0 {
t.Error("Expected missing reviewers for", common.PRtoString(pr.PR), ":", test.expected_missing_reviewers[idx], "but have:", missing)
}
pr_mock.EXPECT().GetPullRequest("other", "pkgrepo", int64(1)).Return(&models.PullRequest{
Body: "Some description is here",
User: &models.User{UserName: "submitter"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "pkgrepo", Owner: &models.User{UserName: "other"}}},
Head: &models.PRBranchInfo{},
Index: 1,
}, nil)
review_mock.EXPECT().GetPullRequestReviews("other", "pkgrepo", int64(1)).Return(test.pkgReviews, nil)
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"}},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
Head: &models.PRBranchInfo{},
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)
if slices.Compare(extra, test.expected_extra_reviewers[idx]) != 0 {
t.Error("Expected reviewers to remove for", common.PRtoString(pr.PR), ":", test.expected_extra_reviewers[idx], "but have:", extra)
maintainership_mock.EXPECT().ListProjectMaintainers(gomock.Any()).Return([]string{"prjmaintainer"}).AnyTimes()
maintainership_mock.EXPECT().ListPackageMaintainers("pkgrepo", gomock.Any()).Return([]string{"pkgmaintainer"}).AnyTimes()
prs, _ := common.FetchPRSet("test", pr_mock, "other", "pkgrepo", int64(1), &test.config)
if len(prs.PRs) != 2 {
t.Fatal("PRs not fetched")
}
for _, pr := range prs.PRs {
r := test.expectedReviewerCall[0]
if !prs.IsPrjGitPR(pr.PR) {
r = test.expectedReviewerCall[1]
}
slices.Sort(r)
for _, reviewer := range r {
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
}
}
prs.AssignReviewers(review_mock, maintainership_mock)
})
}
prjgit_tests := []struct {
name string
config common.AutogitConfig
reviewers []struct {
org, repo string
num int64
reviewer string
}
prjReviews []*models.PullReview
expectedReviewerCall [2][]string
}{
{
name: "PrjMaintainers in prjgit review when not part of pkg set",
config: common.AutogitConfig{
GitProjectName: "org/repo#main",
Organization: "org",
Branch: "main",
Reviewers: []string{},
},
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot", "prjmaintainer"}},
},
}
for _, test := range prjgit_tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
Body: "Some description is here",
User: &models.User{UserName: "submitter"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
Head: &models.PRBranchInfo{},
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(gomock.Any()).Return([]string{"prjmaintainer"}).AnyTimes()
prs, _ := common.FetchPRSet("test", pr_mock, "org", "repo", int64(1), &test.config)
if len(prs.PRs) != 1 {
t.Fatal("PRs not fetched")
}
for _, pr := range prs.PRs {
r := test.expectedReviewerCall[0]
if !prs.IsPrjGitPR(pr.PR) {
t.Fatal("only prjgit pr here")
}
for _, reviewer := range r {
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
}
}
prs.AssignReviewers(review_mock, maintainership_mock)
})
}
}
@@ -1203,7 +978,7 @@ func TestPRMerge(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
@@ -1262,7 +1037,7 @@ func TestPRChanges(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
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)

View File

@@ -1,5 +1,9 @@
package common
import (
"slices"
)
type Reviewers struct {
Prj []string
Pkg []string
@@ -32,5 +36,10 @@ func ParseReviewers(input []string) *Reviewers {
*pkg = append(*pkg, reviewer)
}
}
if !slices.Contains(r.Prj, Bot_BuildReview) {
r.Prj = append(r.Prj, Bot_BuildReview)
}
return r
}

View File

@@ -21,14 +21,14 @@ func TestReviewers(t *testing.T) {
name: "project and package reviewers",
input: []string{"1", "2", "3", "*5", "+6", "-7"},
prj: []string{"5", "7"},
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"},
prj: []string{"7", common.Bot_BuildReview},
pkg: []string{"2", "3", "6"},
prj_optional: []string{"5"},
pkg_optional: []string{"1", "5"},

View File

@@ -9,14 +9,12 @@ import (
)
type PRReviews struct {
Reviews []*models.PullReview
RequestedReviewers []string
Comments []*models.TimelineComment
FullTimeline []*models.TimelineComment
reviews []*models.PullReview
reviewers []string
comments []*models.TimelineComment
}
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) {
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
@@ -27,14 +25,10 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
return nil, err
}
reviews := make([]*models.PullReview, 0, 10)
needNewReviews := []string{}
reviews := make([]*models.PullReview, 0, len(reviewers))
var comments []*models.TimelineComment
alreadyHaveUserReview := func(user string) bool {
if slices.Contains(needNewReviews, user) {
return true
}
for _, r := range reviews {
if r.User != nil && r.User.UserName == user {
return true
@@ -43,40 +37,32 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
return false
}
LogDebug("FetchingGiteaReviews for", org, repo, no)
LogDebug("Number of reviews:", len(rawReviews))
LogDebug("Number of items in timeline:", len(timeline))
cutOffIdx := len(timeline)
for idx, item := range timeline {
if item.Type == TimelineCommentType_Review || item.Type == TimelineCommentType_ReviewRequested {
if item.Type == TimelineCommentType_Review {
for _, r := range rawReviews {
if r.ID == item.ReviewID {
if !alreadyHaveUserReview(r.User.UserName) {
if item.Type == TimelineCommentType_Review && idx > cutOffIdx {
needNewReviews = append(needNewReviews, r.User.UserName)
} else {
reviews = append(reviews, r)
}
reviews = append(reviews, r)
}
break
}
}
} else if item.Type == TimelineCommentType_Comment && cutOffIdx > idx {
} else if item.Type == TimelineCommentType_Comment {
comments = append(comments, item)
} else if item.Type == TimelineCommentType_PushPull && cutOffIdx == len(timeline) {
LogDebug("cut-off", item.Created, "@", idx)
cutOffIdx = idx
} 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), "timeline:", len(reviews))
LogDebug("num comments:", len(comments), "reviews:", len(reviews), len(timeline))
return &PRReviews{
Reviews: reviews,
Comments: comments,
FullTimeline: timeline,
reviews: reviews,
reviewers: reviewers,
comments: comments,
}, nil
}
@@ -95,27 +81,23 @@ func bodyCommandManualMergeOK(body string) bool {
}
func (r *PRReviews) IsManualMergeOK() bool {
if r == nil {
return false
}
for _, c := range r.Comments {
for _, c := range r.comments {
if c.Updated != c.Created {
continue
}
LogDebug("comment:", c.User.UserName, c.Body)
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
if slices.Contains(r.reviewers, c.User.UserName) {
if bodyCommandManualMergeOK(c.Body) {
return true
}
}
}
for _, c := range r.Reviews {
for _, c := range r.reviews {
if c.Updated != c.Submitted {
continue
}
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
if slices.Contains(r.reviewers, c.User.UserName) {
if bodyCommandManualMergeOK(c.Body) {
return true
}
@@ -126,14 +108,11 @@ func (r *PRReviews) IsManualMergeOK() bool {
}
func (r *PRReviews) IsApproved() bool {
if r == nil {
return false
}
goodReview := true
for _, reviewer := range r.RequestedReviewers {
for _, reviewer := range r.reviewers {
goodReview = false
for _, review := range r.Reviews {
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
@@ -151,11 +130,7 @@ func (r *PRReviews) IsApproved() bool {
func (r *PRReviews) MissingReviews() []string {
missing := []string{}
if r == nil {
return missing
}
for _, reviewer := range r.RequestedReviewers {
for _, reviewer := range r.reviewers {
if !r.IsReviewedBy(reviewer) {
missing = append(missing, reviewer)
}
@@ -163,64 +138,45 @@ func (r *PRReviews) MissingReviews() []string {
return missing
}
func (r *PRReviews) FindReviewRequester(reviewer string) *models.TimelineComment {
if r == nil {
return nil
}
for _, r := range r.FullTimeline {
if r.Type == TimelineCommentType_ReviewRequested && r.Assignee.UserName == reviewer {
return r
}
}
return nil
}
func (r *PRReviews) HasPendingReviewBy(reviewer string) bool {
if r == nil {
if !slices.Contains(r.reviewers, reviewer) {
return false
}
for _, r := range r.Reviews {
if r.User.UserName == reviewer {
isPending := false
for _, r := range r.reviews {
if r.User.UserName == reviewer && !r.Stale {
switch r.State {
case ReviewStateRequestReview, ReviewStatePending:
return true
default:
case ReviewStateApproved:
fallthrough
case ReviewStateRequestChanges:
return false
case ReviewStateRequestReview:
fallthrough
case ReviewStatePending:
isPending = true
}
}
}
return false
return isPending
}
func (r *PRReviews) IsReviewedBy(reviewer string) bool {
if r == nil {
if !slices.Contains(r.reviewers, reviewer) {
return false
}
for _, r := range r.Reviews {
for _, r := range r.reviews {
if r.User.UserName == reviewer && !r.Stale {
switch r.State {
case ReviewStateApproved, ReviewStateRequestChanges:
case ReviewStateApproved:
return true
case ReviewStateRequestChanges:
return true
default:
return false
}
}
}
return false
}
func (r *PRReviews) IsReviewedByOneOf(reviewers ...string) bool {
for _, reviewer := range reviewers {
if r.IsReviewedBy(reviewer) {
return true
}
}
return false
}

View File

@@ -62,23 +62,11 @@ func TestReviews(t *testing.T) {
{
name: "Two reviewer, one stale and pending",
reviews: []*models.PullReview{
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}, Stale: true},
&models.PullReview{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}, Stale: true},
},
reviewers: []string{"user1", "user2"},
isApproved: false,
isPendingByTest1: true,
isReviewedByTest1: false,
},
{
name: "Two reviewer, one stale and pending, other done",
reviews: []*models.PullReview{
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
},
reviewers: []string{"user1", "user2"},
isApproved: false,
isPendingByTest1: true,
isPendingByTest1: false,
isReviewedByTest1: false,
},
{
@@ -151,7 +139,7 @@ func TestReviews(t *testing.T) {
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", "pr", 1)
reviews, err := common.FetchGiteaReviews(rf, test.reviewers, "test", "pr", 1)
if test.fetchErr != nil {
if err != test.fetchErr {
@@ -159,7 +147,6 @@ func TestReviews(t *testing.T) {
}
return
}
reviews.RequestedReviewers = test.reviewers
if r := reviews.IsApproved(); r != test.isApproved {
t.Fatal("Unexpected IsReviewed():", r, "vs. expected", test.isApproved)

View File

@@ -27,87 +27,10 @@ import (
"regexp"
"slices"
"strings"
"unicode"
"src.opensuse.org/autogits/common/gitea-generated/models"
)
type NewRepos struct {
Repos []struct {
Organization, Repository, Branch string
PackageName string
}
IsMaintainer bool
}
const maintainership_line = "MAINTAINER"
var true_lines []string = []string{"1", "TRUE", "YES", "OK", "T"}
func HasSpace(s string) bool {
return strings.IndexFunc(s, unicode.IsSpace) >= 0
}
func FindNewReposInIssueBody(body string) *NewRepos {
Issues := &NewRepos{}
for _, line := range strings.Split(body, "\n") {
line = strings.TrimSpace(line)
if ul := strings.ToUpper(line); strings.HasPrefix(ul, "MAINTAINER") {
value := ""
if idx := strings.IndexRune(ul, ':'); idx > 0 && len(ul) > idx+2 {
value = ul[idx+1:]
} else if idx := strings.IndexRune(ul, ' '); idx > 0 && len(ul) > idx+2 {
value = ul[idx+1:]
}
if slices.Contains(true_lines, strings.TrimSpace(value)) {
Issues.IsMaintainer = true
}
}
// line = strings.TrimSpace(line)
issue := struct{ Organization, Repository, Branch, PackageName string }{}
branch := strings.Split(line, "#")
repo := strings.Split(branch[0], "/")
if len(branch) == 2 {
issue.Branch = strings.TrimSpace(branch[1])
}
if len(repo) == 2 {
issue.Organization = strings.TrimSpace(repo[0])
issue.Repository = strings.TrimSpace(repo[1])
issue.PackageName = issue.Repository
if idx := strings.Index(strings.ToUpper(issue.Branch), " AS "); idx > 0 && len(issue.Branch) > idx+5 {
issue.PackageName = strings.TrimSpace(issue.Branch[idx+3:])
issue.Branch = strings.TrimSpace(issue.Branch[0:idx])
}
if HasSpace(issue.Organization) || HasSpace(issue.Repository) || HasSpace(issue.PackageName) || HasSpace(issue.Branch) {
continue
}
} else {
continue
}
Issues.Repos = append(Issues.Repos, issue)
//PackageNameIdx := strings.Index(strings.ToUpper(line), " AS ")
//words := strings.Split(line)
}
if len(Issues.Repos) == 0 {
return nil
}
return Issues
}
func IssueToString(issue *models.Issue) string {
if issue == nil {
return "(nil)"
}
return fmt.Sprintf("%s/%s#%d", issue.Repository.Owner, issue.Repository.Name, issue.Index)
}
func SplitLines(str string) []string {
return SplitStringNoEmpty(str, "\n")
}

View File

@@ -1,7 +1,6 @@
package common_test
import (
"reflect"
"testing"
"src.opensuse.org/autogits/common"
@@ -221,87 +220,3 @@ func TestRemovedBranchName(t *testing.T) {
})
}
}
func TestNewPackageIssueParsing(t *testing.T) {
tests := []struct {
name string
input string
issues *common.NewRepos
}{
{
name: "Nothing",
},
{
name: "Basic repo",
input: "org/repo#branch",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo"},
},
},
},
{
name: "Default branch and junk lines and approval for maintainership",
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: yes",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org1", Repository: "repo2", Branch: "", PackageName: "repo2"},
},
IsMaintainer: true,
},
},
{
name: "Default branch and junk lines and no maintainership",
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: NEVER",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org1", Repository: "repo2", Branch: "", PackageName: "repo2"},
},
},
},
{
name: "3 repos with comments and maintainership",
input: "\n\nsome comments for org1/repo2 are here and more\n\norg1/repo2#master\n org2/repo3#master\n some/repo3#m\nMaintainer ok",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org1", Repository: "repo2", Branch: "master", PackageName: "repo2"},
{Organization: "org2", Repository: "repo3", Branch: "master", PackageName: "repo3"},
{Organization: "some", Repository: "repo3", Branch: "m", PackageName: "repo3"},
},
IsMaintainer: true,
},
},
{
name: "Invalid repos with spaces",
input: "or g/repo#branch\norg/r epo#branch\norg/repo#br anch\norg/repo#branch As foo ++",
},
{
name: "Valid repos with spaces",
input: " org / repo # branch",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo"},
},
},
},
{
name: "Package name is not repo name",
input: " org / repo # branch as repo++ \nmaintainer true",
issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo++"},
},
IsMaintainer: true,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
issue := common.FindNewReposInIssueBody(test.input)
if !reflect.DeepEqual(test.issues, issue) {
t.Error("Expected", test.issues, "but have", issue)
}
})
}
}

View File

@@ -58,30 +58,6 @@ sub ListPackages {
return @packages;
}
sub FactoryMd5 {
my ($package) = @_;
my $out = "";
if (system("osc ls openSUSE:Factory $package | grep -q build.specials.obscpio") == 0) {
system("mkdir _extract") == 0 || die "_extract exists or can't make it. Aborting.";
chdir("_extract") || die;
system("osc cat openSUSE:Factory $package build.specials.obscpio | cpio -dium 2> /dev/null") == 0 || die;
system("rm .* 2> /dev/null");
open( my $fh, "find -type f -exec /usr/bin/basename {} \\; | xargs md5sum | awk '{print \$1 FS \$2}' | grep -v d41d8cd98f00b204e9800998ecf8427e |") or die;
while ( my $l = <$fh>) {
$out = $out.$l;
}
close($fh);
chdir("..") && system("rm -rf _extract") == 0 || die;
}
open( my $fh, "osc ls -v openSUSE:Factory $package | awk '{print \$1 FS \$7}' | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' |") or die;
while (my $l = <$fh>) {
$out = $out.$l;
}
close($fh);
return $out;
}
# Read project from first argument
sub Usage {
die "Usage: $0 <OBS Project> [org [package]]";
@@ -89,7 +65,6 @@ sub Usage {
my $project = shift or Usage();
my $org = shift;
if (not defined($org)) {
$org = `osc meta prj $project | grep scmsync | sed -e 's,^.*src.opensuse.org/\\(.*\\)/_ObsPrj.*,\\1,'`;
chomp($org);
@@ -164,7 +139,7 @@ if ( scalar @tomove > 0 ) {
system("git -C $pkg push origin factory") == 0 and
system("git obs $super_user api -X PATCH --data '{\"default_branch\": \"factory\"}' /repos/pool/$pkg") == 0
or die "Error in creating a pool repo";
system("for i in \$(git -C $pkg for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin/ | grep -v '\\(^HEAD\$\\|^factory\$\\)'); do git -C $pkg push origin :\$i; done") == 0 or die "failed to cull branches";
system("for i in \$(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin/ | grep -v '\\(^HEAD\$\\|^factory\$\)'); do git -C $pkg push origin :\$i; done") == 0 or die "failed to cull branches";
}
}
@@ -192,7 +167,6 @@ for my $package ( sort(@packages) ) {
or ( push( @tomove, $package ) and die "Can't fetch pool for $package" );
my @commits = FindFactoryCommit($package);
my $Md5Hashes = FactoryMd5($package);
my $c;
my $match = 0;
for my $commit (@commits) {
@@ -205,27 +179,16 @@ for my $package ( sort(@packages) ) {
system("git -C $package lfs fetch pool $commit") == 0
and system("git -C $package checkout -B factory $commit") == 0
and system("git -C $package lfs checkout") == 0
and chdir($package)) {
open(my $fh, "|-", "md5sum -c --quiet") or die $!;
print $fh $Md5Hashes;
close $fh;
if ($? >> 8 != 0) {
chdir("..") || die;
next;
}
open($fh, "|-", "awk '{print \$2}' | sort | bash -c \"diff <(ls -1 | sort) -\"") or die $!;
print $fh $Md5Hashes;
close $fh;
my $ec = $? >> 8;
chdir("..") || die;
if ($ec == 0) {
$c = $commit;
$match = 1;
last;
}
and system(
"cd $package; osc ls -v openSUSE:Factory $package | awk '{print \$1 FS \$7}' | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' | md5sum -c --quiet"
) == 0
and system("bash -c \"diff <(ls -1 $package | sort) <(osc ls openSUSE:Factory $package | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' | sort)\"") == 0
)
{
$c = $commit;
$match = 1;
last;
}
}

View File

@@ -1,23 +1,16 @@
Java:packages
Kernel:firmware
Kernel:kdump
devel:gcc
devel:languages:clojure
devel:languages:erlang
devel:languages:erlang:Factory
devel:languages:hare
devel:languages:javascript
devel:languages:lua
devel:languages:nodejs
devel:languages:perl
devel:languages:python:Factory
devel:languages:python:pytest
devel:openSUSE:Factory
network:chromium
network:dhcp
network:im:whatsapp
network:messaging:xmpp
science:HPC
server:dns
systemsmanagement:cockpit
X11:lxde

View File

@@ -149,7 +149,7 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
}
}()
rx := regexp.MustCompile(`^/?api/v\d+/repos/(?<org>[_\.a-zA-Z0-9-]+)/(?<project>[_\.a-zA-Z0-9-]+)/(?:issues|pulls)/(?<num>[0-9]+)$`)
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 {

View File

@@ -1,10 +1,7 @@
OBS Status Service
==================
Reports build status of OBS service as an easily to produce SVG. Repository
results (build results) are cached for 10 seconds and repository listing
for OBS instance are cached for 5 minutes -- new repositories take up to
5 minutes to be visible.
Reports build status of OBS service as an easily to produce SVG
Requests for individual build results:
@@ -20,31 +17,19 @@ Get requests for / will also return 404 statu normally. If the Backend redis
server is not available, it will return 500
By default, SVG output is generated, suitable for inclusion. But JSON and XML
output is possible by setting `Accept:` request header
| Accept Request Header | Output format
|------------------------|---------------------
| | SVG image
| application/json | JSON data
| application/obs+xml | XML output
Areas of Responsibility
-----------------------
* Fetch and cache internal data from OBS and present it in usable format:
+ Generate SVG output for specific OBS project or package
+ Generate JSON/XML output for automated processing
* Low-overhead
* Monitors RabbitMQ interface for notification of OBS package and project status
* Produces SVG output based on GET request
* Cache results (sqlite) and periodically update results from OBS (in case of messages are missing)
Target Usage
------------
* inside README.md of package git or project git
* README.md of package git or project git
* comment section of a Gitea PR
* automated build result processing
Running
-------
@@ -57,4 +42,3 @@ Default parameters can be changed by env variables
| `OBS_STATUS_SERVICE_LISTEN` | [::1]:8080 | Listening address and port
| `OBS_STATUS_SERVICE_CERT` | /run/obs-status-service.pem | Location of certificate file for service
| `OBS_STATUS_SERVICE_KEY` | /run/obs-status-service.pem | Location of key file for service
| `REDIS` | | OBS's Redis instance URL

Binary file not shown.

View File

@@ -439,3 +439,60 @@ func main() {
log.Fatal(http.ListenAndServeTLS(*listen, *cert, *key, nil))
}
}
func ProjectStatusBarSvg(res []*common.BuildResult) []byte {
// TODO: Implement custom SVG output with just one bar
// [success-other-failures] n / total
if len(res) == 0 {
return nil
}
list := common.BuildResultList{
Result: res,
}
package_names := list.GetPackageList()
maxLen := 0
for _, p := range package_names {
maxLen = max(maxLen, len(p))
}
// width := float32(len(list.Result))*1.5 + float32(maxLen)*0.8
// height := 1.5*float32(maxLen) + 30
ret := NewSvg(SvgType_Project)
status := make([]RepoBuildCounters, len(res))
for i, repo := range res {
status[i].Arch = repo.Arch
status[i].Repository = repo.Repository
status[i].Status = repo.Code
status[i].BuildStatusCounter = make(map[string]int)
for _, pkg := range repo.Status {
status[i].BuildStatusCounter[pkg.Code]++
}
}
slices.SortFunc(status, func(a, b RepoBuildCounters) int {
if r := strings.Compare(a.Repository, b.Repository); r != 0 {
return r
}
return strings.Compare(a.Arch, b.Arch)
})
repoName := ""
ret.ypos = 3.0
for _, repo := range status {
if repo.Repository != repoName {
repoName = repo.Repository
ret.WriteTitle(repoName)
}
ret.WriteSubtitle(repo.Arch)
statuses := slices.Sorted(maps.Keys(repo.BuildStatusCounter))
for _, status := range statuses {
ret.WriteProjectStatus(res[0].Project, repo.Repository, repo.Arch, status, repo.BuildStatusCounter[status])
}
}
return ret.GenerateSvg()
}

View File

@@ -1,11 +1,10 @@
package main
import (
"compress/bzip2"
"encoding/json"
"io"
"os"
"testing"
"encoding/json"
"fmt"
"src.opensuse.org/autogits/common"
)
@@ -86,35 +85,13 @@ func TestStatusSvg(t *testing.T) {
os.WriteFile("testproject.svg", ProjectStatusSummarySvg(data), 0o777)
}
func TestFactoryResults(t *testing.T) {
data, err := os.Open("factory.results.json.bz2")
func TestStatusBarSvg(t *testing.T) {
ObsUrl = &[]string{"http://nothing.is.here"}[0]
content, err := os.ReadFile("test-data/python-pytest.json")
if err != nil {
t.Fatal("Openning factory.results.json.bz2 failed:", err)
}
UncompressedData, err := io.ReadAll(bzip2.NewReader(data))
if err != nil {
t.Fatal("Reading factory.results.json.bz2 failed:", err)
}
var results []*common.BuildResult
if err := json.Unmarshal(UncompressedData, &results); err != nil {
t.Fatal("Failed parsing test data", err)
}
// add tests here
tests := []struct {
name string
}{
// add test data here
{
name: "First test",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// and test code here
})
fmt.Println("error:", err)
}
var data []*common.BuildResult
json.Unmarshal(content, &data)
os.WriteFile("python-pytest.svg", ProjectStatusBarSvg(data), 0o777)
}

View File

@@ -6,7 +6,6 @@ import (
"html"
"net/url"
"slices"
"strings"
)
type SvgWriter struct {
@@ -134,7 +133,7 @@ func (svg *SvgWriter) WritePackageStatus(loglink, arch, status, detail string) {
}
func (svg *SvgWriter) WriteProjectStatus(project, repo, arch, status string, count int) {
u, err := url.Parse(*ObsUrl + "/project/monitor/" + url.PathEscape(project) + "?defaults=0&amp;" + url.QueryEscape(status) + "=1&amp;arch_" + url.QueryEscape(arch) + "=1&amp;repo_" + url.QueryEscape(strings.ReplaceAll(repo, ".", "_")) + "=1")
u, err := url.Parse(*ObsUrl + "/project/monitor/" + url.PathEscape(project) + "?defaults=0&amp;" + url.QueryEscape(status) + "=1&amp;arch_" + url.QueryEscape(arch) + "=1&amp;repo_" + url.QueryEscape(repo) + "=1")
if err != nil {
return
}

View File

@@ -9,10 +9,6 @@ EnvironmentFile=-/etc/default/%i/workflow-direct.env
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
RuntimeDirectory=%i
# SLES 15 doesn't have HOME set for dynamic users, so we improvise
BindReadOnlyPaths=/etc/default/%i/known_hosts:/etc/ssh/ssh_known_hosts /etc/default/%i/config.json:%t/%i/config.json /etc/default/%i/id_ed25519 /etc/default/%i/id_ed25519.pub
WorkingDirectory=%t/%i
[Install]
WantedBy=multi-user.target

View File

@@ -10,6 +10,9 @@ Areas of responsibility
* on repository adds, creates a new submodule (if non empty)
* on repository removal, removes the submodule
NOTE: reverts (push HEAD^) are not supported as they would step-on the
work of the workflow-pr bot. Manual update of the project git is
required in this case.
Configuration
-------------
@@ -23,20 +26,6 @@ Uses `workflow.config` for configuration. Parameters
NOTE: `-rm`, `-removed`, `-deleted` are all removed suffixes used to indicate current branch is a placeholder for previously existing package. These branches will be ignored by the bot, and if default, the package will be removed and will not be added to the project.
Running
-------
* `GITEA_TOKEN` (required)
* `AMQP_USERNAME` (required)
* `AMQP_PASSWORD` (required)
* `AUTOGITS_CONFIG` (required)
* `AUTOGITS_URL` - default: https://src.opensuse.org
* `AUTOGITS_RABBITURL` - default: amqps://rabbit.opensuse.org
* `AUTOGITS_DEBUG` - disabled by default, set to any value to enable
* `AUTOGITS_CHECK_ON_START` - disabled by default, set to any value to enable
* `AUTOGITS_REPO_PATH` - default is temporary directory
* `AUTOGITS_IDENTITY_FILE` - in case where we need explicit identify path for ssh specified
Target Usage
------------

View File

@@ -90,21 +90,6 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
common.LogDebug(" - default branch has deleted suffix. Skipping")
return
}
if len(configBranch) == 0 {
common.LogDebug("Empty default branch in message. Maybe race-condition?")
repo, err := gitea.GetRepository(action.Repository.Owner.Username, action.Repository.Name)
if err != nil {
common.LogError("Failed to fetch repository we have an event for?", action.Repository.Owner.Username, action.Repository.Name)
return
}
if len(repo.DefaultBranch) == 0 {
common.LogError("Default branch is somehow empty. We cannot do anything.")
return
}
configBranch = repo.DefaultBranch
}
}
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
@@ -115,16 +100,15 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
common.PanicOnError(err)
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
switch action.Action {
case "created":
if action.Repository.Object_Format_Name != "sha256" {
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
common.LogError(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name)
return
}
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
if branch != configBranch {
@@ -205,7 +189,6 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
common.PanicOnError(err)
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
headCommitId, err := git.GitRemoteHead(gitPrj, remoteName, gitBranch)
common.PanicOnError(err)
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
@@ -215,24 +198,24 @@ func processConfiguredPushAction(action *common.PushWebhookEvent, config *common
}
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
} else if !stat.IsDir() {
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
return
}
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--force", "--depth", "1", "--checkout", action.Repository.Name)
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name)
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", "origin", branch+":"+branch); err != nil {
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, branch+":"+branch); err != nil {
common.LogError("Error fetching branch:", branch, "Ignoring as non-existent.", err)
return
}
id, err := git.GitBranchHead(filepath.Join(gitPrj, action.Repository.Name), branch)
id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, branch)
common.PanicOnError(err)
if action.Head_Commit.Id == id {
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", fmt.Sprintf("'%s' update via Direct Workflow", action.Repository.Name))
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow")
if !noop {
git.GitExecOrPanic(gitPrj, "push", remoteName)
}
@@ -261,8 +244,7 @@ func verifyProjectState(git common.Git, org string, config *common.AutogitConfig
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
common.PanicOnError(err)
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all")
common.LogDebug(" * Getting submodule list")
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
@@ -297,18 +279,15 @@ next_package:
//}
branch := config.Branch
common.LogDebug(" verifying package:", commitId, "->", filename, "@", branch)
common.LogDebug(" verifying package: %s -> %s(%s)", commitId, filename, branch)
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
common.LogDebug(" repository removed...")
git.GitExecOrPanic(gitPrj, "rm", filename)
isGitUpdated = true
continue
} else if err != nil {
common.LogError("failed fetching repo data", org, filename, err)
continue
} else if len(branch) == 0 {
}
if len(branch) == 0 {
branch = repo.DefaultBranch
common.LogDebug(" -> using default branch", branch)
if common.IsRemovedBranch(branch) {
common.LogDebug(" Default branch for", filename, "is excluded")
git.GitExecOrPanic(gitPrj, "rm", filename)
@@ -420,7 +399,7 @@ next_repo:
}
// add repository to git project
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name))
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
if branch != curBranch {
@@ -434,7 +413,7 @@ next_repo:
}
if isGitUpdated {
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Periodic SYNC in Direct Workflow"))
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow -- SYNC"))
if !noop {
git.GitExecOrPanic(gitPrj, "push", remoteName)
}
@@ -522,7 +501,7 @@ func updateConfiguration(configFilename string, orgs *[]string) {
}
func main() {
configFilename := flag.String("config", "config.json", "List of PrjGit")
configFilename := flag.String("config", "", "List of PrjGit")
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
@@ -541,29 +520,6 @@ func main() {
os.Exit(1)
}
if cf := os.Getenv("AUTOGITS_CONFIG"); len(cf) > 0 {
*configFilename = cf
}
if url := os.Getenv("AUTOGITS_URL"); len(url) > 0 {
*giteaUrl = url
}
if url := os.Getenv("AUTOGITS_RABBITURL"); len(url) > 0 {
*rabbitUrl = url
}
if debug := os.Getenv("AUTOGITS_DEBUG"); len(debug) > 0 {
DebugMode = true
}
if check := os.Getenv("AUTOGITS_CHECK_ON_START"); len(check) > 0 {
checkOnStart = true
}
if p := os.Getenv("AUTOGITS_REPO_PATH"); len(p) > 0 {
*basePath = p
}
if DebugMode {
common.SetLoggingLevel(common.LogLevelDebug)
}
defs := &common.RabbitMQGiteaEventsProcessor{}
var err error

View File

@@ -35,7 +35,6 @@ JSON
* _ReviewRequired_: (true, false) ignores that submitter is a maintainer and require a review from other maintainer IFF available
* _NoProjectGitPR_: (true, false) do not create PrjGit PRs, but still process reviews, etc.
* _Permissions_: permissions and associated accounts/groups. See below.
* _Labels_: (string, string) Labels for PRs. See below.
NOTE: `-rm`, `-removed`, `-deleted` are all removed suffixes used to indicate current branch is a placeholder for previously existing package. These branches will be ignored by the bot, and if default, the package will be removed and will not be added to the project.
example:
@@ -75,18 +74,6 @@ results in
* moo -> package and project reviews, but ignored
Labels
------
The following labels are used, when defined in Repo/Org.
| Label Config Entry | Default label | Description
|--------------------|----------------|----------------------------------------
| StagingAuto | staging/Auto | Assigned to Project Git PRs when first staged
| ReviewPending | review/Pending | Assigned to PR when reviews are still pending
| ReviewDone | review/Done | Assigned to PR when reviews are complete on this particular PR
Maintainership
--------------

View File

@@ -7,7 +7,7 @@ import "src.opensuse.org/autogits/common"
type StateChecker interface {
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
CheckRepos()
CheckRepos() error
ConsistencyCheckProcess() error
}

View File

@@ -170,7 +170,7 @@ func main() {
common.RequestType_PRSync: req,
common.RequestType_PRReviewAccepted: req,
common.RequestType_PRReviewRejected: req,
common.RequestType_PRComment: req,
common.RequestType_IssueComment: req,
},
}
listenDefs.Connection().RabbitURL, _ = url.Parse(*rabbitUrl)

View File

@@ -41,9 +41,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
refs = append(refs, ref)
}
slices.Sort(title_refs)
slices.Sort(refs)
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") + "\n"
@@ -230,18 +227,12 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
}
title, desc := PrjGitDescription(prset)
pr, err, isNew := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
if err != nil {
common.LogError("Error creating PrjGit PR:", err)
return err
}
org := PrjGit.Owner.UserName
repo := PrjGit.Name
idx := pr.Index
if isNew {
Gitea.SetLabels(org, repo, idx, []string{prset.Config.Label(common.Label_StagingAuto)})
}
Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, pr.Index, &models.EditPullRequestOption{
RemoveDeadline: true,
})
@@ -355,20 +346,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
}
// update PR
isPrTitleSame := func(CurrentTitle, NewTitle string) bool {
ctlen := len(CurrentTitle)
for _, suffix := range []string{"...", "…"} {
slen := len(suffix)
if ctlen > 250 && strings.HasSuffix(CurrentTitle, suffix) && len(NewTitle) > ctlen {
NewTitle = NewTitle[0:ctlen-slen] + suffix
if CurrentTitle == NewTitle {
return true
}
}
}
return CurrentTitle == NewTitle
}
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
if PrjGitPR.PR.Body != PrjGitBody || PrjGitPR.PR.Title != PrjGitTitle {
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
RemoveDeadline: true,
Title: PrjGitTitle,
@@ -404,10 +382,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
prjGitPR, err := prset.GetPrjGitPR()
if err == common.PRSet_PrjGitMissing {
if req.State != "open" {
common.LogDebug("This PR is closed and no ProjectGit PR. Ignoring.")
return nil
}
common.LogDebug("Missing PrjGit. Need to create one under branch", prjGitPRbranch)
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
@@ -554,14 +528,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
return err
}
// update prset if we should build it or not
if prjGitPR != nil {
if file, err := git.GitCatFile(common.DefaultGitPrj, prjGitPR.PR.Head.Sha, "staging.config"); err == nil {
prset.HasAutoStaging = (file != nil)
common.LogDebug(" -> automatic staging enabled?:", prset.HasAutoStaging)
}
}
// 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 && prjGitPR != nil && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser {
common.LogDebug(" --> checking if superflous PR")

View File

@@ -103,7 +103,7 @@ func TestOpenPR(t *testing.T) {
}
// 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, true)
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)
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, nil)
gitea.EXPECT().GetPullRequestReviews("test", "testRepo", int64(0)).Return([]*models.PullReview{}, nil)
@@ -153,7 +153,7 @@ func TestOpenPR(t *testing.T) {
}
failedErr := errors.New("Returned error here")
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr, false)
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr)
err := pr.Process(event)
if err != failedErr {
@@ -193,7 +193,7 @@ func TestOpenPR(t *testing.T) {
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)
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil)
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr)
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())

View File

@@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"math/rand"
"path"
@@ -42,15 +43,6 @@ func pullRequestToEventState(state models.StateType) string {
}
func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.AutogitConfig) error {
defer func() {
if r := recover(); r != nil {
common.LogError("panic caught in ProcessPR", common.PRtoString(pr))
if err, ok := r.(error); !ok {
common.LogError(err)
}
common.LogError(string(debug.Stack()))
}
}()
return ProcesPullRequest(pr, common.AutogitConfigs{config})
}
@@ -159,7 +151,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
}
func (s *DefaultStateChecker) CheckRepos() {
func (s *DefaultStateChecker) CheckRepos() error {
defer func() {
if r := recover(); r != nil {
common.LogError("panic caught")
@@ -169,6 +161,7 @@ func (s *DefaultStateChecker) CheckRepos() {
common.LogError(string(debug.Stack()))
}
}()
errorList := make([]error, 0, 10)
for org, configs := range s.processor.configuredRepos {
for _, config := range configs {
@@ -182,12 +175,12 @@ func (s *DefaultStateChecker) CheckRepos() {
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 {
common.LogError("Error fetching pull requests for", fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), err)
break
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))
@@ -200,11 +193,9 @@ func (s *DefaultStateChecker) CheckRepos() {
common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName)
}
if len(configs) == 0 {
common.LogError(" org:", org, "has 0 configs?")
}
}
return errors.Join(errorList...)
}
func (s *DefaultStateChecker) ConsistencyCheckProcess() error {