forked from git-workflow/autogits
Compare commits
28 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| f997c51393 | |||
| 6eb9e1749e | |||
| ae4dffc4b1 | |||
| 2415feacc1 | |||
| 7457fef780 | |||
| 7af2068801 | |||
|
|
b4b3bf584e | ||
| 65307cfb5e | |||
| 5669083388 | |||
|
|
cb9131a5dd | ||
| 582df2555b | |||
| 91d22f7eea | |||
| 913b8c8a4b | |||
| e1825dc658 | |||
| 59965e7b5c | |||
| 24a4a592a7 | |||
| d3d9d66797 | |||
| 7a2f7a6ee7 | |||
| 34a3a4795b | |||
| bb5daebdfa | |||
| 70bba5e239 | |||
| 5793391586 | |||
| d923db3f87 | |||
| fc4547f9a9 | |||
| 6fa57fc4d4 | |||
| 82d4e2ed5d | |||
| 8920644792 | |||
| 06772ca662 |
@@ -22,8 +22,6 @@ Release: 0
|
||||
Summary: GitWorkflow utilities
|
||||
License: GPL-2.0-or-later
|
||||
URL: https://src.opensuse.org/adamm/autogits
|
||||
#!RemoteAsset: git+https://src.suse.de/adrianSuSE/autogits#ibs_state
|
||||
Source0: %name-%version.tar.xz
|
||||
BuildRequires: git
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: go
|
||||
|
||||
@@ -288,7 +288,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
|
||||
func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) {
|
||||
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--heads", "--hash", branchName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Can't find default branch: %s in %s", branchName, gitDir)
|
||||
return "", fmt.Errorf("Can't find default branch: %s", branchName)
|
||||
}
|
||||
|
||||
id = strings.TrimSpace(SplitLines(id)[0])
|
||||
@@ -302,7 +302,7 @@ func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error
|
||||
func (e *GitHandlerImpl) GitRemoteHead(gitDir, remote, branchName string) (string, error) {
|
||||
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--hash", "--verify", "refs/remotes/"+remote+"/"+branchName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Can't find default branch: %s in %s", branchName, gitDir)
|
||||
return "", fmt.Errorf("Can't find default branch: %s", branchName)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(id), nil
|
||||
|
||||
@@ -83,3 +83,260 @@ func (c *MockObsStatusFetcherWithStateBuildStatusWithStateCall) DoAndReturn(f fu
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockObsClientInterface is a mock of ObsClientInterface interface.
|
||||
type MockObsClientInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockObsClientInterfaceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceMockRecorder is the mock recorder for MockObsClientInterface.
|
||||
type MockObsClientInterfaceMockRecorder struct {
|
||||
mock *MockObsClientInterface
|
||||
}
|
||||
|
||||
// NewMockObsClientInterface creates a new mock instance.
|
||||
func NewMockObsClientInterface(ctrl *gomock.Controller) *MockObsClientInterface {
|
||||
mock := &MockObsClientInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockObsClientInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockObsClientInterface) EXPECT() *MockObsClientInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildStatus mocks base method.
|
||||
func (m *MockObsClientInterface) BuildStatus(project string, packages ...string) (*common.BuildResultList, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{project}
|
||||
for _, a := range packages {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "BuildStatus", varargs...)
|
||||
ret0, _ := ret[0].(*common.BuildResultList)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildStatus indicates an expected call of BuildStatus.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) BuildStatus(project any, packages ...any) *MockObsClientInterfaceBuildStatusCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{project}, packages...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildStatus", reflect.TypeOf((*MockObsClientInterface)(nil).BuildStatus), varargs...)
|
||||
return &MockObsClientInterfaceBuildStatusCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceBuildStatusCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceBuildStatusCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Return(arg0 *common.BuildResultList, arg1 error) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Do(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) DoAndReturn(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DeleteProject mocks base method.
|
||||
func (m *MockObsClientInterface) DeleteProject(project string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteProject", project)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteProject indicates an expected call of DeleteProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) DeleteProject(project any) *MockObsClientInterfaceDeleteProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProject", reflect.TypeOf((*MockObsClientInterface)(nil).DeleteProject), project)
|
||||
return &MockObsClientInterfaceDeleteProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceDeleteProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceDeleteProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Return(arg0 error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Do(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) DoAndReturn(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) GetHomeProject() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetHomeProject")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetHomeProject indicates an expected call of GetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetHomeProject() *MockObsClientInterfaceGetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).GetHomeProject))
|
||||
return &MockObsClientInterfaceGetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Return(arg0 string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Do(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) DoAndReturn(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) GetProjectMeta(project string) (*common.ProjectMeta, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetProjectMeta", project)
|
||||
ret0, _ := ret[0].(*common.ProjectMeta)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetProjectMeta indicates an expected call of GetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetProjectMeta(project any) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).GetProjectMeta), project)
|
||||
return &MockObsClientInterfaceGetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Return(arg0 *common.ProjectMeta, arg1 error) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Do(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) DoAndReturn(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) SetHomeProject(project string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetHomeProject", project)
|
||||
}
|
||||
|
||||
// SetHomeProject indicates an expected call of SetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetHomeProject(project any) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).SetHomeProject), project)
|
||||
return &MockObsClientInterfaceSetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Return() *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Do(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) DoAndReturn(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) SetProjectMeta(meta *common.ProjectMeta) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetProjectMeta", meta)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetProjectMeta indicates an expected call of SetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetProjectMeta(meta any) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).SetProjectMeta), meta)
|
||||
return &MockObsClientInterfaceSetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Return(arg0 error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Do(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) DoAndReturn(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -46,6 +46,15 @@ type ObsStatusFetcherWithState interface {
|
||||
BuildStatusWithState(project string, opts *BuildResultOptions, packages ...string) (*BuildResultList, error)
|
||||
}
|
||||
|
||||
type ObsClientInterface interface {
|
||||
GetProjectMeta(project string) (*ProjectMeta, error)
|
||||
SetProjectMeta(meta *ProjectMeta) error
|
||||
DeleteProject(project string) error
|
||||
BuildStatus(project string, packages ...string) (*BuildResultList, error)
|
||||
GetHomeProject() string
|
||||
SetHomeProject(project string)
|
||||
}
|
||||
|
||||
type ObsClient struct {
|
||||
baseUrl *url.URL
|
||||
client *http.Client
|
||||
@@ -57,6 +66,14 @@ type ObsClient struct {
|
||||
HomeProject string
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetHomeProject() string {
|
||||
return c.HomeProject
|
||||
}
|
||||
|
||||
func (c *ObsClient) SetHomeProject(project string) {
|
||||
c.HomeProject = project
|
||||
}
|
||||
|
||||
func NewObsClient(host string) (*ObsClient, error) {
|
||||
baseUrl, err := url.Parse(host)
|
||||
if err != nil {
|
||||
@@ -694,15 +711,13 @@ func (r *BuildResultList) BuildResultSummary() (success, finished bool) {
|
||||
if !ok {
|
||||
panic("Unknown result code: " + result.Code)
|
||||
}
|
||||
if r.isLastBuild {
|
||||
// we are always finished, since it is the last result
|
||||
// also when there is "unknown" state, it just means it
|
||||
// it was never done
|
||||
finished = true
|
||||
} else {
|
||||
finished = finished && detail.Finished
|
||||
if r.isLastBuild && result.Code == "unknown" {
|
||||
// it means the package has never build yet,
|
||||
// but we don't know the reason
|
||||
detail.Finished = true
|
||||
}
|
||||
|
||||
finished = finished && detail.Finished
|
||||
success = success && detail.Success
|
||||
|
||||
if !finished {
|
||||
|
||||
@@ -468,7 +468,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
r.SetRequiredReviewers(reviewers)
|
||||
prjgit.Reviews = r
|
||||
if prjgit.Reviews.IsManualMergeOK() {
|
||||
is_manually_reviewed_ok = true
|
||||
@@ -489,7 +489,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
r.SetRequiredReviewers(reviewers)
|
||||
pr.Reviews = r
|
||||
if !pr.Reviews.IsManualMergeOK() {
|
||||
LogInfo("Not approved manual merge. PR:", pr.PR.URL)
|
||||
@@ -530,7 +530,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
LogError("Cannot fetch gitea reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
r.SetRequiredReviewers(reviewers)
|
||||
|
||||
is_manually_reviewed_ok = r.IsApproved()
|
||||
LogDebug("PR to", pr.PR.Base.Repo.Name, "reviewed?", is_manually_reviewed_ok)
|
||||
|
||||
@@ -807,9 +807,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
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{
|
||||
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}},
|
||||
RequestedReviewers: []*models.TimelineComment{
|
||||
{User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested},
|
||||
},
|
||||
},
|
||||
@@ -919,8 +918,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
},
|
||||
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"}}},
|
||||
RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -930,8 +928,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
},
|
||||
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"}}},
|
||||
RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -966,8 +963,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
RequestedReviewers: []*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"}},
|
||||
@@ -985,8 +981,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{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{
|
||||
RequestedReviewers: []*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"}},
|
||||
},
|
||||
@@ -1026,8 +1021,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{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{
|
||||
RequestedReviewers: []*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"}},
|
||||
@@ -1050,8 +1044,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{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{
|
||||
RequestedReviewers: []*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"}},
|
||||
@@ -1090,8 +1083,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{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{
|
||||
RequestedReviewers: []*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"}},
|
||||
@@ -1112,8 +1104,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
{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{
|
||||
RequestedReviewers: []*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"}},
|
||||
@@ -1199,6 +1190,9 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.prset.HasAutoStaging = !test.noAutoStaging
|
||||
for idx, pr := range test.prset.PRs {
|
||||
if pr.Reviews != nil {
|
||||
pr.Reviews.SetRequiredReviewers(test.prset.Config.Reviewers)
|
||||
}
|
||||
missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx)
|
||||
|
||||
// avoid nil dereference below, by adding empty array elements
|
||||
|
||||
@@ -8,12 +8,28 @@ import (
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
type ReviewInterface interface {
|
||||
IsManualMergeOK() bool
|
||||
IsApproved() bool
|
||||
MisingReviews() []string
|
||||
FindReviewRequester(reviewer string) *models.TimelineComment
|
||||
HasPendingReviewBy(reviewer string) bool
|
||||
IsReviewedBy(reviewer string) bool
|
||||
IsReviewedByOneOf(reviewers ...string) bool
|
||||
|
||||
SetRequiredReviewers(reviewers []string)
|
||||
}
|
||||
|
||||
type PRReviews struct {
|
||||
Reviews []*models.PullReview
|
||||
RequestedReviewers []string
|
||||
RequestedReviewers []*models.TimelineComment
|
||||
Comments []*models.TimelineComment
|
||||
|
||||
FullTimeline []*models.TimelineComment
|
||||
RequiredReviewers []string
|
||||
}
|
||||
|
||||
func (r *PRReviews) SetRequiredReviewers(reviewers []string) {
|
||||
r.RequiredReviewers = reviewers
|
||||
}
|
||||
|
||||
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) {
|
||||
@@ -28,11 +44,11 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
|
||||
}
|
||||
|
||||
reviews := make([]*models.PullReview, 0, 10)
|
||||
needNewReviews := []string{}
|
||||
var comments []*models.TimelineComment
|
||||
|
||||
var foundUsers []string
|
||||
alreadyHaveUserReview := func(user string) bool {
|
||||
if slices.Contains(needNewReviews, user) {
|
||||
if slices.Contains(foundUsers, user) {
|
||||
return true
|
||||
}
|
||||
for _, r := range reviews {
|
||||
@@ -48,20 +64,24 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
|
||||
LogDebug("Number of items in timeline:", len(timeline))
|
||||
|
||||
cutOffIdx := len(timeline)
|
||||
var PendingRequestedReviews []*models.TimelineComment
|
||||
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 r.ID == item.ReviewID && r.User != nil {
|
||||
if !alreadyHaveUserReview(r.User.UserName) {
|
||||
if item.Type == TimelineCommentType_Review && idx > cutOffIdx {
|
||||
needNewReviews = append(needNewReviews, r.User.UserName)
|
||||
} else {
|
||||
if idx < cutOffIdx {
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
foundUsers = append(foundUsers, r.User.UserName)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if item.Type == TimelineCommentType_ReviewRequested && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) {
|
||||
PendingRequestedReviews = append(PendingRequestedReviews, item)
|
||||
} else if item.Type == TimelineCommentType_DismissReview && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) {
|
||||
foundUsers = append(foundUsers, item.Assignee.UserName)
|
||||
} else if item.Type == TimelineCommentType_Comment && cutOffIdx > idx {
|
||||
comments = append(comments, item)
|
||||
} else if item.Type == TimelineCommentType_PushPull && cutOffIdx == len(timeline) {
|
||||
@@ -74,9 +94,9 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
|
||||
LogDebug("num comments:", len(comments), "timeline:", len(reviews))
|
||||
|
||||
return &PRReviews{
|
||||
Reviews: reviews,
|
||||
Comments: comments,
|
||||
FullTimeline: timeline,
|
||||
Reviews: reviews,
|
||||
Comments: comments,
|
||||
RequestedReviewers: PendingRequestedReviews,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -104,7 +124,7 @@ func (r *PRReviews) IsManualMergeOK() bool {
|
||||
continue
|
||||
}
|
||||
LogDebug("comment:", c.User.UserName, c.Body)
|
||||
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
|
||||
if slices.Contains(r.RequiredReviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
@@ -115,7 +135,7 @@ func (r *PRReviews) IsManualMergeOK() bool {
|
||||
if c.Updated != c.Submitted {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
|
||||
if slices.Contains(r.RequiredReviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
@@ -131,7 +151,7 @@ func (r *PRReviews) IsApproved() bool {
|
||||
}
|
||||
goodReview := true
|
||||
|
||||
for _, reviewer := range r.RequestedReviewers {
|
||||
for _, reviewer := range r.RequiredReviewers {
|
||||
goodReview = false
|
||||
for _, review := range r.Reviews {
|
||||
if review.User.UserName == reviewer && review.State == ReviewStateApproved && !review.Stale && !review.Dismissed {
|
||||
@@ -155,7 +175,7 @@ func (r *PRReviews) MissingReviews() []string {
|
||||
return missing
|
||||
}
|
||||
|
||||
for _, reviewer := range r.RequestedReviewers {
|
||||
for _, reviewer := range r.RequiredReviewers {
|
||||
if !r.IsReviewedBy(reviewer) {
|
||||
missing = append(missing, reviewer)
|
||||
}
|
||||
@@ -168,12 +188,11 @@ func (r *PRReviews) FindReviewRequester(reviewer string) *models.TimelineComment
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range r.FullTimeline {
|
||||
if r.Type == TimelineCommentType_ReviewRequested && r.Assignee.UserName == reviewer {
|
||||
return r
|
||||
for _, t := range r.RequestedReviewers {
|
||||
if t.Assignee.UserName == reviewer {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -193,6 +212,13 @@ func (r *PRReviews) HasPendingReviewBy(reviewer string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// at this point, we do not have actual review by user. Check if we have a pending review
|
||||
for _, t := range r.RequestedReviewers {
|
||||
if t.Assignee != nil && t.Assignee.UserName == reviewer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,61 @@ func TestReviews(t *testing.T) {
|
||||
isApproved: false,
|
||||
isReviewedByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "Ghost user review",
|
||||
reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateApproved, User: nil},
|
||||
},
|
||||
reviewers: []string{"user1"},
|
||||
isApproved: false,
|
||||
},
|
||||
{
|
||||
name: "ReviewRequested predates PushPull should be seen as pending",
|
||||
reviews: []*models.PullReview{},
|
||||
timeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_PushPull},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
|
||||
},
|
||||
reviewers: []string{"user1"},
|
||||
isPendingByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "ReviewRequested postdates PushPull but blocked by older dismiss",
|
||||
reviews: []*models.PullReview{},
|
||||
timeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
|
||||
{Type: common.TimelineCommentType_PushPull},
|
||||
{Type: common.TimelineCommentType_ReviewDismissed, Assignee: &models.User{UserName: "user1"}},
|
||||
},
|
||||
reviewers: []string{"user1"},
|
||||
isPendingByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "ReviewRequested predates PushPull should be seen as pending",
|
||||
reviews: []*models.PullReview{
|
||||
{ID: 101, State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}},
|
||||
},
|
||||
timeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_PushPull},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
|
||||
},
|
||||
reviewers: []string{"user1"},
|
||||
isPendingByTest1: true,
|
||||
},
|
||||
{
|
||||
name: "Review requested, review, then push needs re-requesting",
|
||||
reviews: []*models.PullReview{
|
||||
{ID: 100, State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
|
||||
},
|
||||
timeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_PushPull},
|
||||
{Type: common.TimelineCommentType_Review, ReviewID: 100},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}},
|
||||
},
|
||||
reviewers: []string{"user1"},
|
||||
isReviewedByTest1: false, // Should be stale
|
||||
isPendingByTest1: false, // Should be stale
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -158,7 +213,7 @@ func TestReviews(t *testing.T) {
|
||||
}
|
||||
return
|
||||
}
|
||||
reviews.RequestedReviewers = test.reviewers
|
||||
reviews.SetRequiredReviewers(test.reviewers)
|
||||
|
||||
if r := reviews.IsApproved(); r != test.isApproved {
|
||||
t.Fatal("Unexpected IsReviewed():", r, "vs. expected", test.isApproved)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TimelineCommentType_ReviewDismissed = "dismiss_review"
|
||||
TimelineCommentType_ReviewRequested = "review_request"
|
||||
TimelineCommentType_Review = "review"
|
||||
TimelineCommentType_PushPull = "pull_push"
|
||||
|
||||
@@ -8,6 +8,7 @@ services:
|
||||
gitea:
|
||||
build: ./gitea
|
||||
container_name: gitea-test
|
||||
init: true
|
||||
environment:
|
||||
- GITEA_WORK_DIR=/var/lib/gitea
|
||||
networks:
|
||||
@@ -27,6 +28,7 @@ services:
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.13.7-management
|
||||
container_name: rabbitmq-test
|
||||
init: true
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "check_running", "-q"]
|
||||
interval: 30s
|
||||
@@ -55,6 +57,7 @@ services:
|
||||
context: ..
|
||||
dockerfile: integration/gitea-events-rabbitmq-publisher/Dockerfile${GIWTF_IMAGE_SUFFIX}
|
||||
container_name: gitea-publisher
|
||||
init: true
|
||||
networks:
|
||||
- gitea-network
|
||||
depends_on:
|
||||
@@ -75,6 +78,7 @@ services:
|
||||
context: ..
|
||||
dockerfile: integration/workflow-pr/Dockerfile${GIWTF_IMAGE_SUFFIX}
|
||||
container_name: workflow-pr
|
||||
init: true
|
||||
networks:
|
||||
- gitea-network
|
||||
depends_on:
|
||||
@@ -103,6 +107,7 @@ services:
|
||||
mock-obs:
|
||||
build: ./mock-obs
|
||||
container_name: mock-obs
|
||||
init: true
|
||||
networks:
|
||||
- gitea-network
|
||||
ports:
|
||||
@@ -116,6 +121,7 @@ services:
|
||||
context: ..
|
||||
dockerfile: integration/obs-staging-bot/Dockerfile${GIWTF_IMAGE_SUFFIX}
|
||||
container_name: obs-staging-bot
|
||||
init: true
|
||||
networks:
|
||||
- gitea-network
|
||||
depends_on:
|
||||
|
||||
@@ -65,11 +65,11 @@ The testing will be conducted in a dedicated test environment that mimics the pr
|
||||
| **TC-REVIEW-001** | P | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
|
||||
| **TC-REVIEW-002** | - | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
|
||||
| **TC-REVIEW-003** | - | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
|
||||
| **TC-REVIEW-004** | x | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
|
||||
| **TC-REVIEW-004** | P | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
|
||||
| **TC-REVIEW-005** | P | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-006** | P | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-007** | P | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
|
||||
| **TC-MERGE-001** | x | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-001** | P | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-002** | - | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-003** | - | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
|
||||
| **TC-MERGE-004** | - | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
|
||||
|
||||
@@ -5,7 +5,6 @@ from pathlib import Path
|
||||
from tests.lib.common_test_utils import GiteaAPIClient
|
||||
|
||||
@pytest.mark.t001
|
||||
@pytest.mark.xfail(reason="The bot sometimes re-request reviews despite having all the approvals")
|
||||
def test_001_automerge(automerge_env, test_user_client):
|
||||
"""
|
||||
Test scenario:
|
||||
|
||||
@@ -43,7 +43,6 @@ index 0000000..e69de29
|
||||
|
||||
|
||||
@pytest.mark.t004
|
||||
@pytest.mark.xfail(reason="the bot sometimes re-requests review from autogits_obs_staging_bot despite having the approval")
|
||||
def test_004_maintainer(maintainer_env, ownerA_client):
|
||||
"""
|
||||
Test scenario:
|
||||
@@ -152,7 +151,6 @@ index 0000000..e69de29
|
||||
|
||||
|
||||
@pytest.mark.t005
|
||||
# @pytest.mark.xfail(reason="TBD troubleshoot")
|
||||
def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
@@ -51,6 +50,10 @@ const (
|
||||
|
||||
var runId uint
|
||||
|
||||
var GitWorkTreeAllocate func(string, string, string) (common.GitHandlerGenerator, error) = func(basePath, gitAuthor, email string) (common.GitHandlerGenerator, error) {
|
||||
return common.AllocateGitWorkTree(basePath, gitAuthor, email)
|
||||
}
|
||||
|
||||
func FetchPrGit(git common.Git, pr *models.PullRequest) error {
|
||||
// clone PR head via base (target) repo
|
||||
cloneURL := pr.Base.Repo.CloneURL
|
||||
@@ -128,10 +131,6 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
|
||||
found:
|
||||
for j := 0; j < len(project.Result); j++ {
|
||||
common.LogDebug(" found match for @ idx:", j)
|
||||
if project.Result[i].Dirty {
|
||||
// ignore possible temporary failures and wait for settling
|
||||
return BuildStatusSummaryBuilding
|
||||
}
|
||||
res := ProcessRepoBuildStatus(project.Result[i].Status)
|
||||
switch res {
|
||||
case BuildStatusSummarySuccess:
|
||||
@@ -149,9 +148,9 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
|
||||
|
||||
func ProcessRepoBuildStatus(results []*common.PackageBuildStatus) (status BuildStatusSummary) {
|
||||
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
|
||||
common.LogDebug("******* RESULTS: ")
|
||||
data, _ := xml.MarshalIndent(results, "", " ")
|
||||
@@ -196,24 +195,23 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return true, BuildStatusSummaryUnknown // true for 'missing'
|
||||
}
|
||||
|
||||
// Check for any failures
|
||||
// Check for any unfinished builds
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, ok := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
if !ok {
|
||||
common.LogInfo("unknown package result code:", pkgStatus.Code, "for package:", pkgStatus.Package)
|
||||
return false, BuildStatusSummaryUnknown
|
||||
}
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any unfinished builds
|
||||
// Check for any failures
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, _ := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
// 'ok' is already checked in the loop above
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +219,7 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return false, BuildStatusSummarySuccess
|
||||
}
|
||||
|
||||
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
common.LogDebug("repo content fetching ...")
|
||||
err := FetchPrGit(git, pr)
|
||||
if err != nil {
|
||||
@@ -265,13 +263,13 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
|
||||
}
|
||||
|
||||
common.LogDebug("Trying first staging master project: ", stagingMasterPrj)
|
||||
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
|
||||
meta, err := obs.GetProjectMeta(stagingMasterPrj)
|
||||
if err == nil {
|
||||
// success, so we use that staging master project as our build project
|
||||
buildPrj = stagingMasterPrj
|
||||
} else {
|
||||
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
|
||||
meta, err = ObsClient.GetProjectMeta(buildPrj)
|
||||
meta, err = obs.GetProjectMeta(buildPrj)
|
||||
}
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
|
||||
@@ -335,10 +333,10 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
|
||||
// stagingProject:$buildProject
|
||||
// ^- stagingProject:$buildProject:$subProjectName (based on templateProject)
|
||||
|
||||
func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
common.LogDebug("Setup QA sub projects")
|
||||
common.LogDebug("reading templateProject ", templateProject)
|
||||
templateMeta, err := ObsClient.GetProjectMeta(templateProject)
|
||||
templateMeta, err := obs.GetProjectMeta(templateProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching template project meta for", templateProject, ":", err)
|
||||
return err
|
||||
@@ -348,10 +346,10 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
||||
// freeze tag for now
|
||||
if len(templateMeta.ScmSync) > 0 {
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
common.LogDebug("getting data for ", repository.EscapedPath())
|
||||
split := strings.Split(repository.EscapedPath(), "/")
|
||||
@@ -359,12 +357,12 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
|
||||
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
|
||||
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// set expanded commit url
|
||||
repository.Fragment = branch.SHA
|
||||
repository.Fragment = branch.SHA
|
||||
templateMeta.ScmSync = repository.String()
|
||||
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
|
||||
}
|
||||
@@ -411,11 +409,11 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = templateMeta.Name
|
||||
} else
|
||||
// Check for path prefixes against a template project inside of template project area
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject + ":") {
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject+":") {
|
||||
newProjectName := stagingProject
|
||||
// find project name
|
||||
for _, setup := range stagingConfig.QA {
|
||||
if setup.Origin == path.Project {
|
||||
if setup.Origin == path.Project {
|
||||
common.LogDebug(" Match:", setup.Origin)
|
||||
newProjectName = newProjectName + ":" + setup.Name
|
||||
common.LogDebug(" New:", newProjectName)
|
||||
@@ -423,14 +421,14 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
}
|
||||
}
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = newProjectName
|
||||
common.LogDebug(" Matched prefix")
|
||||
common.LogDebug(" Matched prefix")
|
||||
}
|
||||
common.LogDebug(" Path using project ", templateMeta.Repositories[idx].Paths[pidx].Project)
|
||||
}
|
||||
}
|
||||
|
||||
if !IsDryRun {
|
||||
err = ObsClient.SetProjectMeta(templateMeta)
|
||||
err = obs.SetProjectMeta(templateMeta)
|
||||
if err != nil {
|
||||
common.LogError("cannot create project:", templateMeta.Name, err)
|
||||
x, _ := xml.MarshalIndent(templateMeta, "", " ")
|
||||
@@ -444,10 +442,10 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
common.LogDebug("fetching OBS project Meta")
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
meta, err := ObsClient.GetProjectMeta(obsPrProject)
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
meta, err := obs.GetProjectMeta(obsPrProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", obsPrProject, ":", err)
|
||||
return RequestModificationNoChange, err
|
||||
@@ -472,7 +470,7 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
|
||||
if meta == nil {
|
||||
// new build
|
||||
common.LogDebug(" Staging master:", config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(obs, git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
if err != nil {
|
||||
return RequestModificationNoChange, err
|
||||
}
|
||||
@@ -484,7 +482,7 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
|
||||
common.LogDebug("Creating build project:")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
} else {
|
||||
err = ObsClient.SetProjectMeta(meta)
|
||||
err = obs.SetProjectMeta(meta)
|
||||
if err != nil {
|
||||
x, _ := xml.MarshalIndent(meta, "", " ")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
@@ -555,7 +553,7 @@ func ParseNotificationToPR(thread *models.NotificationThread) (org string, repo
|
||||
return
|
||||
}
|
||||
|
||||
func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThread) {
|
||||
func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -571,7 +569,7 @@ func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThre
|
||||
}
|
||||
common.LogInfo("processing PR:", org, "/", repo, "#", num)
|
||||
|
||||
done, err := ProcessPullRequest(gitea, org, repo, num)
|
||||
done, err := ProcessPullRequest(obs, gitea, org, repo, num)
|
||||
if !IsDryRun && err == nil && done {
|
||||
gitea.SetNotificationRead(thread.ID)
|
||||
} else if err != nil {
|
||||
@@ -581,7 +579,7 @@ func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThre
|
||||
|
||||
var CleanedUpIssues []int64 = []int64{}
|
||||
|
||||
func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -648,8 +646,8 @@ func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThre
|
||||
return false
|
||||
}
|
||||
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
if prj, err := ObsClient.GetProjectMeta(stagingProject); err != nil {
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
if prj, err := obs.GetProjectMeta(stagingProject); err != nil {
|
||||
common.LogError("Failed fetching meta for project:", stagingProject, ". Not cleaning up")
|
||||
return false
|
||||
} else if prj == nil && err == nil {
|
||||
@@ -663,13 +661,13 @@ func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThre
|
||||
project := stagingProject + ":" + qa.Name
|
||||
common.LogDebug("Cleaning up QA staging", project)
|
||||
if !IsDryRun {
|
||||
if err := ObsClient.DeleteProject(project); err != nil {
|
||||
if err := obs.DeleteProject(project); err != nil {
|
||||
common.LogError("Failed to cleanup QA staging", project, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !IsDryRun {
|
||||
if err := ObsClient.DeleteProject(stagingProject); err != nil {
|
||||
if err := obs.DeleteProject(stagingProject); err != nil {
|
||||
common.LogError("Failed to cleanup staging", stagingProject, err)
|
||||
}
|
||||
}
|
||||
@@ -690,7 +688,7 @@ func SetStatus(gitea common.Gitea, org, repo, hash string, status *models.Commit
|
||||
return err
|
||||
}
|
||||
|
||||
func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
if IsDryRun {
|
||||
common.LogInfo("Would comment on package PR %s/%s#%d: %s", org, repo, prNum, msg)
|
||||
return
|
||||
@@ -702,6 +700,18 @@ func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64
|
||||
return
|
||||
}
|
||||
|
||||
timeline, err := gitea.GetTimeline(org, repo, prNum)
|
||||
if err != nil {
|
||||
common.LogError("Failed to get timeline for PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range timeline {
|
||||
if t.User != nil && t.User.UserName == BotUser && t.Type == common.TimelineCommentType_Comment && t.Body == msg {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError("Failed to comment on package PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
@@ -709,20 +719,21 @@ func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64
|
||||
}
|
||||
|
||||
// Create and remove QA projects
|
||||
func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) []string {
|
||||
func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) ([]string, string) {
|
||||
usedQAprojects := make([]string, 0)
|
||||
prLabelNames := make(map[string]int)
|
||||
for _, label := range pr.Labels {
|
||||
prLabelNames[label.Name] = 1
|
||||
}
|
||||
msg := ""
|
||||
var qa_projects []string
|
||||
for _, setup := range stagingConfig.QA {
|
||||
QAproject := stagingProject + ":" + setup.Name
|
||||
if len(setup.Label) > 0 {
|
||||
if _, ok := prLabelNames[setup.Label]; !ok {
|
||||
if !IsDryRun {
|
||||
// blindly remove, will fail when not existing
|
||||
ObsClient.DeleteProject(QAproject)
|
||||
obs.DeleteProject(QAproject)
|
||||
}
|
||||
common.LogInfo("QA project ", setup.Name, "has no matching Label")
|
||||
continue
|
||||
@@ -731,24 +742,25 @@ func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gite
|
||||
|
||||
usedQAprojects = append(usedQAprojects, QAproject)
|
||||
// check for existens first, no error, but no meta is a 404
|
||||
if meta, err := ObsClient.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
if meta, err := obs.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
common.LogInfo("Create QA project ", QAproject)
|
||||
CreateQASubProject(stagingConfig, git, gitea, pr,
|
||||
CreateQASubProject(obs, stagingConfig, git, gitea, pr,
|
||||
stagingProject,
|
||||
setup.Origin,
|
||||
setup.Name,
|
||||
setup.BuildDisableRepos)
|
||||
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
|
||||
QAproject + "\n"
|
||||
qa_projects = append(qa_projects, ObsWebHost+"/project/show/"+QAproject)
|
||||
}
|
||||
}
|
||||
if len(msg) > 1 {
|
||||
gitea.AddComment(pr, msg)
|
||||
|
||||
if len(qa_projects) > 0 {
|
||||
msg = "Additional QA builds:\n" + strings.Join(qa_projects, "\n")
|
||||
}
|
||||
return usedQAprojects
|
||||
|
||||
return usedQAprojects, msg
|
||||
}
|
||||
|
||||
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
dir, err := os.MkdirTemp(os.TempDir(), BotName)
|
||||
common.PanicOnError(err)
|
||||
if IsDryRun {
|
||||
@@ -757,7 +769,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
defer os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
gh, err := common.AllocateGitWorkTree(dir, GitAuthor, "noaddress@suse.de")
|
||||
gh, err := GitWorkTreeAllocate(dir, GitAuthor, "noaddress@suse.de")
|
||||
common.PanicOnError(err)
|
||||
|
||||
git, err := gh.CreateGitHandler(org)
|
||||
@@ -802,7 +814,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
if err != nil {
|
||||
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
|
||||
if !IsDryRun {
|
||||
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
_, _ = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
@@ -822,7 +834,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
return true, nil
|
||||
}
|
||||
|
||||
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
|
||||
meta, err := obs.GetProjectMeta(stagingConfig.ObsProject)
|
||||
if err != nil || meta == nil {
|
||||
common.LogError("Cannot find reference project meta:", stagingConfig.ObsProject, err)
|
||||
if !IsDryRun && err == nil {
|
||||
@@ -891,13 +903,10 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
if !stagingConfig.RebuildAll {
|
||||
for pkg, headOid := range headSubmodules {
|
||||
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
|
||||
if pkg != "rpms" && pkg != "dependencies" {
|
||||
_, spkg := filepath.Split(pkg)
|
||||
if exists {
|
||||
modifiedPackages = append(modifiedPackages, spkg)
|
||||
modifiedPackages = append(modifiedPackages, pkg)
|
||||
} else {
|
||||
newPackages = append(newPackages, spkg)
|
||||
}
|
||||
newPackages = append(newPackages, pkg)
|
||||
}
|
||||
common.LogDebug(pkg, ":", baseOid, "->", headOid)
|
||||
}
|
||||
@@ -954,8 +963,8 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
}
|
||||
|
||||
common.LogDebug("ObsProject:", stagingConfig.ObsProject)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, ObsClient.HomeProject, pr)
|
||||
change, err := StartOrUpdateBuild(stagingConfig, git, gitea, pr)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, obs.GetHomeProject(), pr)
|
||||
change, err := StartOrUpdateBuild(obs, stagingConfig, git, gitea, pr)
|
||||
status := &models.CommitStatus{
|
||||
Context: BotName,
|
||||
Description: "OBS Staging build",
|
||||
@@ -986,11 +995,8 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
|
||||
SetStatus(gitea, org, repo, pr.Head.Sha, status)
|
||||
}
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
gitea.AddComment(pr, msg)
|
||||
}
|
||||
|
||||
stagingResult, err := ObsClient.BuildStatus(stagingProject)
|
||||
stagingResult, err := obs.BuildStatus(stagingProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", stagingProject, ":", err)
|
||||
}
|
||||
@@ -998,7 +1004,14 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
_, packagePRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
|
||||
|
||||
// always update QA projects because Labels can change
|
||||
qaProjects := ProcessQaProjects(stagingConfig, git, gitea, pr, stagingProject)
|
||||
qaProjects, qaProjectMsg := ProcessQaProjects(obs, stagingConfig, git, gitea, pr, stagingProject)
|
||||
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
if len(qaProjectMsg) > 0 {
|
||||
msg += "\n" + qaProjectMsg
|
||||
}
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
}
|
||||
|
||||
done := false
|
||||
overallBuildStatus := ProcessBuildStatus(stagingResult)
|
||||
@@ -1006,7 +1019,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
|
||||
seperator := " in "
|
||||
for _, qaProject := range qaProjects {
|
||||
qaResult, err := ObsClient.BuildStatus(qaProject)
|
||||
qaResult, err := obs.BuildStatus(qaProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", qaProject, ":", err)
|
||||
}
|
||||
@@ -1026,7 +1039,6 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch overallBuildStatus {
|
||||
case BuildStatusSummarySuccess:
|
||||
status.Status = common.CommitStatus_Success
|
||||
@@ -1067,7 +1079,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
default:
|
||||
continue
|
||||
}
|
||||
commentOnPackagePR(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
CommentPROnce(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
}
|
||||
|
||||
if len(missingPkgs) > 0 {
|
||||
@@ -1077,10 +1089,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
msg = msg + " - " + pkg + "\n"
|
||||
}
|
||||
common.LogInfo(msg)
|
||||
err := gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1099,8 +1108,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func PollWorkNotifications(giteaUrl string) {
|
||||
gitea := common.AllocateGiteaTransport(giteaUrl)
|
||||
func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
data, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1116,7 +1124,7 @@ func PollWorkNotifications(giteaUrl string) {
|
||||
if !ListPullNotificationsOnly {
|
||||
switch notification.Subject.Type {
|
||||
case "Pull":
|
||||
ProcessPullNotification(gitea, notification)
|
||||
ProcessPullNotification(obs, gitea, notification)
|
||||
default:
|
||||
if !IsDryRun {
|
||||
gitea.SetNotificationRead(notification.ID)
|
||||
@@ -1139,7 +1147,7 @@ func PollWorkNotifications(giteaUrl string) {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanupFinished = CleanupPullNotification(gitea, n) && cleanupFinished
|
||||
cleanupFinished = CleanupPullNotification(obs, gitea, n) && cleanupFinished
|
||||
}
|
||||
} else if err != nil {
|
||||
common.LogError(err)
|
||||
@@ -1153,7 +1161,8 @@ var ObsApiHost string
|
||||
var ObsWebHost string
|
||||
var IsDryRun bool
|
||||
var ProcessPROnly string
|
||||
var ObsClient *common.ObsClient
|
||||
var ObsClient common.ObsClientInterface
|
||||
var BotUser string
|
||||
|
||||
func ObsWebHostFromApiHost(apihost string) string {
|
||||
u, err := url.Parse(apihost)
|
||||
@@ -1218,9 +1227,18 @@ func main() {
|
||||
}
|
||||
|
||||
if len(*buildRoot) > 0 {
|
||||
ObsClient.HomeProject = *buildRoot
|
||||
ObsClient.SetHomeProject(*buildRoot)
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
|
||||
user, err := gitea.GetCurrentUser()
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch current user:", err)
|
||||
return
|
||||
}
|
||||
BotUser = user.UserName
|
||||
|
||||
if len(*ProcessPROnly) > 0 {
|
||||
rx := regexp.MustCompile("^([^/#]+)/([^/#]+)#([0-9]+)$")
|
||||
m := rx.FindStringSubmatch(*ProcessPROnly)
|
||||
@@ -1229,15 +1247,14 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
id, _ := strconv.ParseInt(m[3], 10, 64)
|
||||
|
||||
ProcessPullRequest(gitea, m[1], m[2], id)
|
||||
ProcessPullRequest(ObsClient, gitea, m[1], m[2], id)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
PollWorkNotifications(GiteaUrl)
|
||||
PollWorkNotifications(ObsClient, gitea)
|
||||
common.LogInfo("Poll cycle finished")
|
||||
time.Sleep(5 * time.Minute)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,9 @@ After=network-online.target
|
||||
Type=exec
|
||||
ExecStart=/usr/bin/obs-staging-bot
|
||||
EnvironmentFile=-/etc/default/obs-staging-bot.env
|
||||
User=autogits_obs_staging_bot
|
||||
Group=users
|
||||
|
||||
# This may work when not using ssh api connections:
|
||||
#DynamicUser=yes
|
||||
#NoNewPrivileges=yes
|
||||
#ProtectSystem=strict
|
||||
DynamicUser=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -123,7 +123,7 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
||||
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
|
||||
return
|
||||
}
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + action.Repository.Name, action.Repository.Name))
|
||||
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")
|
||||
|
||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
||||
@@ -215,7 +215,7 @@ 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.Name, action.Repository.Name)
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--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.")
|
||||
@@ -420,7 +420,7 @@ next_repo:
|
||||
}
|
||||
|
||||
// add repository to git project
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", "../" + r.Name, r.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
|
||||
|
||||
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||
if branch != curBranch {
|
||||
|
||||
1
workflow-pr/dummy
Normal file
1
workflow-pr/dummy
Normal file
@@ -0,0 +1 @@
|
||||
5
|
||||
@@ -180,33 +180,6 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
updateSubmoduleInPR(submodulePath, prHead, git)
|
||||
err := git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fetch")
|
||||
common.LogError("lfs fetch err: ", err)
|
||||
if err = git.GitExec(path.Join(common.DefaultGitPrj, submodulePath), "lfs", "fsck"); err != nil {
|
||||
|
||||
found_comment := false
|
||||
timeline, terr := common.FetchTimelineSinceLastPush(Gitea, prHead, org, repo, idx)
|
||||
if terr != nil {
|
||||
common.LogError("lfs fsck error, but timeline fetch failed")
|
||||
break
|
||||
}
|
||||
msgPrefix := "The LFS objects are broken!"
|
||||
for _, t := range timeline {
|
||||
if t.Type == common.TimelineCommentType_Comment && strings.HasPrefix(t.Body, msgPrefix) {
|
||||
found_comment = true
|
||||
common.LogError("lfs fsck Comment already found")
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found_comment && !common.IsDryRun {
|
||||
Gitea.AddComment(pr.PR, msgPrefix + " Please verify with 'git lfs fsck'")
|
||||
}
|
||||
common.LogError("lfs fsck failed with: ", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := git.GitStatus(common.DefaultGitPrj)
|
||||
common.LogDebug("status:", status)
|
||||
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
|
||||
@@ -336,16 +309,14 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
PrjGit := PrjGitPR.PR.Base.Repo
|
||||
prjGitPRbranch := PrjGitPR.PR.Head.Name
|
||||
if PrjGitPR.PR.Base.RepoID != PrjGitPR.PR.Head.RepoID {
|
||||
// permission check, if submission comes from foreign repo
|
||||
if !PrjGitPR.PR.AllowMaintainerEdit {
|
||||
common.LogError("Warning: source and target branch are in different repositories. We may not have the right permissions...")
|
||||
// Gitea.AddComment(PrjGitPR.PR, "This PR does not allow maintainer changes, but referenced package branch has changed!")
|
||||
return nil
|
||||
}
|
||||
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, "", PrjGit.SSHURL)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitPR.PR.Head.Sha)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "checkout", PrjGitPR.PR.Head.Sha)
|
||||
common.LogInfo("Cannot update this PR as it's on another remote, not branch:", prjGitPRbranch, "Assuming this is by-design. (eg. project git PR only)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
|
||||
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, PrjGitPR.PR.Head.Ref, PrjGitPR.PR.Head.Repo.SSHURL)
|
||||
PrjGitPR.RemoteName, err = git.GitClone(common.DefaultGitPrj, prjGitPRbranch, PrjGit.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitBranch)
|
||||
|
||||
@@ -393,7 +364,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, params...))
|
||||
PrjGitPR.PR.Head.Sha = newHeadCommit
|
||||
Gitea.SetLabels(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, []string{prset.Config.Label("PR/updated")})
|
||||
}
|
||||
|
||||
// update PR
|
||||
@@ -410,7 +380,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
return CurrentTitle == NewTitle
|
||||
}
|
||||
if !pr.config.NoProjectGitPR && PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
|
||||
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
Title: PrjGitTitle,
|
||||
|
||||
Reference in New Issue
Block a user