12 Commits

Author SHA256 Message Date
44bd6c23e5 . 2026-01-22 18:24:23 +01:00
17bfc36801 finish script 2026-01-22 18:21:19 +01:00
73d8c3e97f add parser 2026-01-22 18:12:20 +01:00
64e5b51488 check query prameters 2026-01-22 15:49:17 +01:00
1fe6bd98d4 Merge branch 'gitpkgs' of src.opensuse.org:git-workflow/autogits into gitpkgs 2026-01-22 14:22:14 +01:00
e859ed1b54 proper url parsing 2026-01-22 14:20:14 +01:00
1ad2e1738c more url parsing 2026-01-22 14:11:53 +01:00
a6d5d4c79d add ugly url parsing 2026-01-22 14:11:53 +01:00
f5d6f50b32 wip: git package updates 2026-01-22 14:11:53 +01:00
87633e7508 more url parsing 2026-01-21 13:57:53 +01:00
56ce07514a add ugly url parsing 2026-01-21 12:58:48 +01:00
1d95b4cf0f wip: git package updates 2026-01-21 12:49:19 +01:00
35 changed files with 854 additions and 3119 deletions

View File

@@ -23,6 +23,7 @@ jobs:
- run: git checkout FETCH_HEAD
- run: go generate -C common
- run: go generate -C workflow-pr
- run: go generate -C workflow-pr/interfaces
- run: git add -N .; git diff
- run: |
status=$(git status --short)

View File

@@ -12,6 +12,7 @@ jobs:
- run: git checkout FETCH_HEAD
- run: go generate -C common
- run: go generate -C workflow-pr
- run: go generate -C workflow-pr/interfaces
- run: |
host=${{ gitea.server_url }}
host=${host#https://}

View File

@@ -129,9 +129,6 @@ go build \
go build \
-C utils/hujson \
-buildmode=pie
go build \
-C utils/maintainer-update \
-buildmode=pie
go build \
-C gitea-events-rabbitmq-publisher \
-buildmode=pie
@@ -163,7 +160,6 @@ go test -C group-review -v
go test -C obs-staging-bot -v
go test -C obs-status-service -v
go test -C workflow-direct -v
go test -C utils/maintainer-update
# TODO build fails
#go test -C workflow-pr -v
@@ -183,7 +179,6 @@ install -D -m0755 workflow-direct/workflow-direct
install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
%pre gitea-events-rabbitmq-publisher
%service_add_pre gitea-events-rabbitmq-publisher.service
@@ -290,7 +285,6 @@ install -D -m0755 utils/maintainer-update/maintainer-update
%files utils
%license COPYING
%{_bindir}/hujson
%{_bindir}/maintainer-update
%files workflow-direct
%license COPYING

View File

@@ -54,7 +54,6 @@ type ReviewGroup struct {
type QAConfig struct {
Name string
Origin string
Label string // requires this gitea lable to be set or skipped
BuildDisableRepos []string // which repos to build disable in the new project
}
@@ -93,7 +92,6 @@ type AutogitConfig struct {
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
ReviewRequired bool // always require a maintainer review, even if maintainer submits it. Only ignored if no other package or project reviewers
}
type AutogitConfigs []*AutogitConfig
@@ -295,9 +293,9 @@ func (config *AutogitConfig) GetRemoteBranch() string {
}
func (config *AutogitConfig) Label(label string) string {
if t, found := config.Labels[LabelKey(label)]; found {
return t
}
if t, found := config.Labels[LabelKey(label)]; found {
return t
}
return label
}

View File

@@ -20,13 +20,10 @@ package common
const (
GiteaTokenEnv = "GITEA_TOKEN"
GiteaHostEnv = "GITEA_HOST"
ObsUserEnv = "OBS_USER"
ObsPasswordEnv = "OBS_PASSWORD"
ObsSshkeyEnv = "OBS_SSHKEY"
ObsSshkeyFileEnv = "OBS_SSHKEYFILE"
ObsApiEnv = "OBS_API"
ObsWebEnv = "OBS_WEB"
DefaultGitPrj = "_ObsPrj"
PrjLinksFile = "links.json"

View File

@@ -76,7 +76,6 @@ type GiteaLabelSettter interface {
}
type GiteaTimelineFetcher interface {
ResetTimelineCache(org, repo string, idx int64)
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
}
@@ -814,18 +813,6 @@ type TimelineCacheData struct {
var giteaTimelineCache map[string]TimelineCacheData = make(map[string]TimelineCacheData)
var giteaTimelineCacheMutex sync.RWMutex
func (gitea *GiteaTransport) ResetTimelineCache(org, repo string, idx int64) {
giteaTimelineCacheMutex.Lock()
defer giteaTimelineCacheMutex.Unlock()
prID := fmt.Sprintf("%s/%s!%d", org, repo, idx)
Cache, IsCached := giteaTimelineCache[prID]
if IsCached {
Cache.lastCheck = Cache.lastCheck.Add(-time.Hour)
giteaTimelineCache[prID] = Cache
}
}
// returns timeline in reverse chronological create order
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
page := int64(1)

View File

@@ -1,12 +1,10 @@
package common
import (
"bytes"
"encoding/json"
"fmt"
"io"
"slices"
"strings"
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
"src.opensuse.org/autogits/common/gitea-generated/models"
@@ -27,15 +25,12 @@ const ProjectFileKey = "_project"
type MaintainershipMap struct {
Data map[string][]string
IsDir bool
Config *AutogitConfig
FetchPackage func(string) ([]byte, error)
Raw []byte
}
func ParseMaintainershipData(data []byte) (*MaintainershipMap, error) {
func parseMaintainershipData(data []byte) (*MaintainershipMap, error) {
maintainers := &MaintainershipMap{
Data: make(map[string][]string),
Raw: data,
}
if err := json.Unmarshal(data, &maintainers.Data); err != nil {
return nil, err
@@ -44,9 +39,7 @@ func ParseMaintainershipData(data []byte) (*MaintainershipMap, error) {
return maintainers, nil
}
func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, config *AutogitConfig) (*MaintainershipMap, error) {
org, prjGit, branch := config.GetPrjGit()
func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, org, prjGit, branch string) (*MaintainershipMap, error) {
data, _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, ProjectFileKey)
dir := true
if err != nil || data == nil {
@@ -66,9 +59,8 @@ func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, config *Aut
}
}
m, err := ParseMaintainershipData(data)
m, err := parseMaintainershipData(data)
if m != nil {
m.Config = config
m.IsDir = dir
m.FetchPackage = func(pkg string) ([]byte, error) {
data, _, err := gitea.FetchMaintainershipDirFile(org, prjGit, branch, pkg)
@@ -88,8 +80,6 @@ func (data *MaintainershipMap) ListProjectMaintainers(groups []*ReviewGroup) []s
return nil
}
m = slices.Clone(m)
// expands groups
for _, g := range groups {
m = g.ExpandMaintainers(m)
@@ -126,7 +116,6 @@ func (data *MaintainershipMap) ListPackageMaintainers(pkg string, groups []*Revi
}
}
}
pkgMaintainers = slices.Clone(pkgMaintainers)
prjMaintainers := data.ListProjectMaintainers(nil)
prjMaintainer:
@@ -160,10 +149,7 @@ func (data *MaintainershipMap) IsApproved(pkg string, reviews []*models.PullRevi
}
LogDebug("Looking for review by:", reviewers)
slices.Sort(reviewers)
reviewers = slices.Compact(reviewers)
SubmitterIdxInReviewers := slices.Index(reviewers, submitter)
if SubmitterIdxInReviewers > -1 && (!data.Config.ReviewRequired || len(reviewers) == 1) {
if slices.Contains(reviewers, submitter) {
LogDebug("Submitter is maintainer. Approving.")
return true
}
@@ -178,135 +164,13 @@ func (data *MaintainershipMap) IsApproved(pkg string, reviews []*models.PullRevi
return false
}
func (data *MaintainershipMap) modifyInplace(writer io.StringWriter) error {
var original map[string][]string
if err := json.Unmarshal(data.Raw, &original); err != nil {
return err
}
dec := json.NewDecoder(bytes.NewReader(data.Raw))
_, err := dec.Token()
if err != nil {
return err
}
output := ""
lastPos := 0
modified := false
type entry struct {
key string
valStart int
valEnd int
}
var entries []entry
for dec.More() {
kToken, _ := dec.Token()
key := kToken.(string)
var raw json.RawMessage
dec.Decode(&raw)
valEnd := int(dec.InputOffset())
valStart := valEnd - len(raw)
entries = append(entries, entry{key, valStart, valEnd})
}
changed := make(map[string]bool)
for k, v := range data.Data {
if ov, ok := original[k]; !ok || !slices.Equal(v, ov) {
changed[k] = true
}
}
for k := range original {
if _, ok := data.Data[k]; !ok {
changed[k] = true
}
}
if len(changed) == 0 {
_, err = writer.WriteString(string(data.Raw))
return err
}
for _, e := range entries {
if v, ok := data.Data[e.key]; ok {
prefix := string(data.Raw[lastPos:e.valStart])
if modified && strings.TrimSpace(output) == "{" {
if commaIdx := strings.Index(prefix, ","); commaIdx != -1 {
if quoteIdx := strings.Index(prefix, "\""); quoteIdx == -1 || commaIdx < quoteIdx {
prefix = prefix[:commaIdx] + prefix[commaIdx+1:]
}
}
}
output += prefix
if changed[e.key] {
slices.Sort(v)
newVal, _ := json.Marshal(v)
output += string(newVal)
modified = true
} else {
output += string(data.Raw[e.valStart:e.valEnd])
}
} else {
// Deleted
modified = true
}
lastPos = e.valEnd
}
output += string(data.Raw[lastPos:])
// Handle additions (simplistic: at the end)
for k, v := range data.Data {
if _, ok := original[k]; !ok {
slices.Sort(v)
newVal, _ := json.Marshal(v)
keyStr, _ := json.Marshal(k)
// Insert before closing brace
if idx := strings.LastIndex(output, "}"); idx != -1 {
prefix := output[:idx]
suffix := output[idx:]
trimmedPrefix := strings.TrimRight(prefix, " \n\r\t")
if !strings.HasSuffix(trimmedPrefix, "{") && !strings.HasSuffix(trimmedPrefix, ",") {
// find the actual position of the last non-whitespace character in prefix
lastCharIdx := strings.LastIndexAny(prefix, "]}0123456789\"")
if lastCharIdx != -1 {
prefix = prefix[:lastCharIdx+1] + "," + prefix[lastCharIdx+1:]
}
}
insertion := fmt.Sprintf(" %s: %s", string(keyStr), string(newVal))
if !strings.HasSuffix(prefix, "\n") {
insertion = "\n" + insertion
}
output = prefix + insertion + "\n" + suffix
modified = true
}
}
}
if modified {
_, err := writer.WriteString(output)
return err
}
_, err = writer.WriteString(string(data.Raw))
return err
}
func (data *MaintainershipMap) WriteMaintainershipFile(writer io.StringWriter) error {
if data.IsDir {
return fmt.Errorf("Not implemented")
}
if len(data.Raw) > 0 {
if err := data.modifyInplace(writer); err == nil {
return nil
}
}
// Fallback to full write
writer.WriteString("{\n")
if d, ok := data.Data[""]; ok {
eol := ","
if len(data.Data) == 1 {
@@ -317,12 +181,17 @@ func (data *MaintainershipMap) WriteMaintainershipFile(writer io.StringWriter) e
writer.WriteString(fmt.Sprintf(" \"\": %s%s\n", string(str), eol))
}
keys := make([]string, 0, len(data.Data))
keys := make([]string, len(data.Data))
i := 0
for pkg := range data.Data {
if pkg == "" {
continue
}
keys = append(keys, pkg)
keys[i] = pkg
i++
}
if len(keys) >= i {
keys = slices.Delete(keys, i, len(keys))
}
slices.Sort(keys)
for i, pkg := range keys {

View File

@@ -13,10 +13,10 @@ import (
)
func TestMaintainership(t *testing.T) {
config := &common.AutogitConfig{
config := common.AutogitConfig{
Branch: "bar",
Organization: "foo",
GitProjectName: common.DefaultGitPrj + "#bar",
GitProjectName: common.DefaultGitPrj,
}
packageTests := []struct {
@@ -141,7 +141,7 @@ func TestMaintainership(t *testing.T) {
notFoundError := repository.NewRepoGetContentsNotFound()
for _, test := range packageTests {
runTests := func(t *testing.T, mi common.GiteaMaintainershipReader) {
maintainers, err := common.FetchProjectMaintainershipData(mi, config)
maintainers, err := common.FetchProjectMaintainershipData(mi, config.Organization, config.GitProjectName, config.Branch)
if err != nil && !test.otherError {
if test.maintainersFileErr == nil {
t.Fatal("Unexpected error recieved", err)
@@ -208,7 +208,6 @@ func TestMaintainershipFileWrite(t *testing.T) {
name string
is_dir bool
maintainers map[string][]string
raw []byte
expected_output string
expected_error error
}{
@@ -232,43 +231,6 @@ func TestMaintainershipFileWrite(t *testing.T) {
},
expected_output: "{\n \"\": [\"one\",\"two\"],\n \"foo\": [\"byte\",\"four\"],\n \"pkg1\": []\n}\n",
},
{
name: "surgical modification",
maintainers: map[string][]string{
"": {"one", "two"},
"foo": {"byte", "four", "newone"},
"pkg1": {},
},
raw: []byte("{\n \"\": [\"one\",\"two\"],\n \"foo\": [\"byte\",\"four\"],\n \"pkg1\": []\n}\n"),
expected_output: "{\n \"\": [\"one\",\"two\"],\n \"foo\": [\"byte\",\"four\",\"newone\"],\n \"pkg1\": []\n}\n",
},
{
name: "no change",
maintainers: map[string][]string{
"": {"one", "two"},
"foo": {"byte", "four"},
"pkg1": {},
},
raw: []byte("{\n \"\": [\"one\",\"two\"],\n \"foo\": [\"byte\",\"four\"],\n \"pkg1\": []\n}\n"),
expected_output: "{\n \"\": [\"one\",\"two\"],\n \"foo\": [\"byte\",\"four\"],\n \"pkg1\": []\n}\n",
},
{
name: "surgical addition",
maintainers: map[string][]string{
"": {"one"},
"new": {"user"},
},
raw: []byte("{\n \"\": [ \"one\" ]\n}\n"),
expected_output: "{\n \"\": [ \"one\" ],\n \"new\": [\"user\"]\n}\n",
},
{
name: "surgical deletion",
maintainers: map[string][]string{
"": {"one"},
},
raw: []byte("{\n \"\": [\"one\"],\n \"old\": [\"user\"]\n}\n"),
expected_output: "{\n \"\": [\"one\"]\n}\n",
},
}
for _, test := range tests {
@@ -277,7 +239,6 @@ func TestMaintainershipFileWrite(t *testing.T) {
data := common.MaintainershipMap{
Data: test.maintainers,
IsDir: test.is_dir,
Raw: test.raw,
}
if err := data.WriteMaintainershipFile(&b); err != test.expected_error {
@@ -287,134 +248,8 @@ func TestMaintainershipFileWrite(t *testing.T) {
output := b.String()
if test.expected_output != output {
t.Fatalf("unexpected output:\n%q\nExpecting:\n%q", output, test.expected_output)
t.Fatal("unexpected output:", output, "Expecting:", test.expected_output)
}
})
}
}
func TestReviewRequired(t *testing.T) {
tests := []struct {
name string
maintainers []string
config *common.AutogitConfig
is_approved bool
}{
{
name: "ReviewRequired=false",
maintainers: []string{"maintainer1", "maintainer2"},
config: &common.AutogitConfig{ReviewRequired: false},
is_approved: true,
},
{
name: "ReviewRequired=true",
maintainers: []string{"maintainer1", "maintainer2"},
config: &common.AutogitConfig{ReviewRequired: true},
is_approved: false,
},
{
name: "ReviewRequired=true",
maintainers: []string{"maintainer1"},
config: &common.AutogitConfig{ReviewRequired: true},
is_approved: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
m := &common.MaintainershipMap{
Data: map[string][]string{"": test.maintainers},
}
m.Config = test.config
if approved := m.IsApproved("", nil, "maintainer1", nil); approved != test.is_approved {
t.Error("Expected m.IsApproved()->", test.is_approved, "but didn't get it")
}
})
}
}
func TestMaintainershipDataCorruption_PackageAppend(t *testing.T) {
// Test corruption when append happens (merging project maintainers)
// If backing array has capacity, append writes to it.
// We construct a slice with capacity > len to simulate this common scenario
backingArray := make([]string, 1, 10)
backingArray[0] = "@g1"
initialData := map[string][]string{
"pkg": backingArray, // len 1, cap 10
"": {"prjUser"},
}
m := &common.MaintainershipMap{
Data: initialData,
}
groups := []*common.ReviewGroup{
{
Name: "@g1",
Reviewers: []string{"u1"},
},
}
// ListPackageMaintainers("pkg", groups)
// 1. gets ["@g1"] (cap 10)
// 2. Appends "prjUser" -> ["@g1", "prjUser"] (in backing array)
// 3. Expands "@g1" -> "u1".
// Replace: ["u1", "prjUser"]
// Sort: ["prjUser", "u1"]
//
// The backing array is now ["prjUser", "u1", ...]
// The map entry "pkg" is still len 1.
// So it sees ["prjUser"].
list1 := m.ListPackageMaintainers("pkg", groups)
t.Logf("List1: %v", list1)
// ListPackageMaintainers("pkg", nil)
// Should be ["@g1", "prjUser"] (because prjUser is appended from project maintainers)
// But since backing array is corrupted:
// It sees ["prjUser"] (from map) + appends "prjUser" -> ["prjUser", "prjUser"].
list2 := m.ListPackageMaintainers("pkg", nil)
t.Logf("List2: %v", list2)
if !slices.Contains(list2, "@g1") {
t.Errorf("Corruption: '@g1' is missing from second call. Got %v", list2)
}
}
func TestMaintainershipDataCorruption_ProjectInPlace(t *testing.T) {
// Test corruption in ListProjectMaintainers when replacement fits in place
// e.g. replacing 1 group with 1 user.
initialData := map[string][]string{
"": {"@g1"},
}
m := &common.MaintainershipMap{
Data: initialData,
}
groups := []*common.ReviewGroup{
{
Name: "@g1",
Reviewers: []string{"u1"},
},
}
// First call with expansion
// Replaces "@g1" with "u1". Length stays 1. Modifies backing array in place.
list1 := m.ListProjectMaintainers(groups)
t.Logf("List1: %v", list1)
// Second call without expansion
// Should return ["@g1"]
list2 := m.ListProjectMaintainers(nil)
t.Logf("List2: %v", list2)
if !slices.Contains(list2, "@g1") {
t.Errorf("Corruption: '@g1' is missing from second call (Project). Got %v", list2)
}
}

View File

@@ -207,42 +207,6 @@ func (c *MockGiteaTimelineFetcherGetTimelineCall) DoAndReturn(f func(string, str
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaTimelineFetcher) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaTimelineFetcherMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaTimelineFetcherResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaTimelineFetcher)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaTimelineFetcherResetTimelineCacheCall{Call: call}
}
// MockGiteaTimelineFetcherResetTimelineCacheCall wrap *gomock.Call
type MockGiteaTimelineFetcherResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaTimelineFetcherResetTimelineCacheCall) Return() *MockGiteaTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaTimelineFetcherResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaTimelineFetcherResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaComment is a mock of GiteaComment interface.
type MockGiteaComment struct {
ctrl *gomock.Controller
@@ -739,42 +703,6 @@ func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) DoAndReturn(f func(str
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaPRTimelineReviewFetcher) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall{Call: call}
}
// MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall wrap *gomock.Call
type MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall) Return() *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaPRTimelineReviewFetcherResetTimelineCacheCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaCommitFetcher is a mock of GiteaCommitFetcher interface.
type MockGiteaCommitFetcher struct {
ctrl *gomock.Controller
@@ -1066,42 +994,6 @@ func (c *MockGiteaReviewTimelineFetcherGetTimelineCall) DoAndReturn(f func(strin
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaReviewTimelineFetcher) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaReviewTimelineFetcherMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaReviewTimelineFetcherResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaReviewTimelineFetcher)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaReviewTimelineFetcherResetTimelineCacheCall{Call: call}
}
// MockGiteaReviewTimelineFetcherResetTimelineCacheCall wrap *gomock.Call
type MockGiteaReviewTimelineFetcherResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewTimelineFetcherResetTimelineCacheCall) Return() *MockGiteaReviewTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewTimelineFetcherResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaReviewTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewTimelineFetcherResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaReviewTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaPRChecker is a mock of GiteaPRChecker interface.
type MockGiteaPRChecker struct {
ctrl *gomock.Controller
@@ -1323,42 +1215,6 @@ func (c *MockGiteaPRCheckerGetTimelineCall) DoAndReturn(f func(string, string, i
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaPRChecker) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaPRCheckerMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaPRCheckerResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaPRChecker)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaPRCheckerResetTimelineCacheCall{Call: call}
}
// MockGiteaPRCheckerResetTimelineCacheCall wrap *gomock.Call
type MockGiteaPRCheckerResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaPRCheckerResetTimelineCacheCall) Return() *MockGiteaPRCheckerResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaPRCheckerResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaPRCheckerResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaPRCheckerResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaPRCheckerResetTimelineCacheCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaReviewFetcherAndRequesterAndUnrequester is a mock of GiteaReviewFetcherAndRequesterAndUnrequester interface.
type MockGiteaReviewFetcherAndRequesterAndUnrequester struct {
ctrl *gomock.Controller
@@ -1544,42 +1400,6 @@ func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) DoA
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall) Return() *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterResetTimelineCacheCall {
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()
@@ -1686,42 +1506,6 @@ func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) DoAndReturn(f func(str
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGiteaUnreviewTimelineFetcher) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall{Call: call}
}
// MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall wrap *gomock.Call
type MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall) Return() *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaUnreviewTimelineFetcherResetTimelineCacheCall {
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()
@@ -3324,42 +3108,6 @@ func (c *MockGiteaRequestReviewsCall) DoAndReturn(f func(*models.PullRequest, ..
return c
}
// ResetTimelineCache mocks base method.
func (m *MockGitea) ResetTimelineCache(org, repo string, idx int64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ResetTimelineCache", org, repo, idx)
}
// ResetTimelineCache indicates an expected call of ResetTimelineCache.
func (mr *MockGiteaMockRecorder) ResetTimelineCache(org, repo, idx any) *MockGiteaResetTimelineCacheCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetTimelineCache", reflect.TypeOf((*MockGitea)(nil).ResetTimelineCache), org, repo, idx)
return &MockGiteaResetTimelineCacheCall{Call: call}
}
// MockGiteaResetTimelineCacheCall wrap *gomock.Call
type MockGiteaResetTimelineCacheCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaResetTimelineCacheCall) Return() *MockGiteaResetTimelineCacheCall {
c.Call = c.Call.Return()
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaResetTimelineCacheCall) Do(f func(string, string, int64)) *MockGiteaResetTimelineCacheCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaResetTimelineCacheCall) DoAndReturn(f func(string, string, int64)) *MockGiteaResetTimelineCacheCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// SetCommitStatus mocks base method.
func (m *MockGitea) SetCommitStatus(org, repo, hash string, status *models.CommitStatus) (*models.CommitStatus, error) {
m.ctrl.T.Helper()

View File

@@ -116,18 +116,13 @@ type Flags struct {
Contents string `xml:",innerxml"`
}
type ProjectLinkMeta struct {
Project string `xml:"project,attr"`
}
type ProjectMeta struct {
XMLName xml.Name `xml:"project"`
Name string `xml:"name,attr"`
Title string `xml:"title"`
Description string `xml:"description"`
Url string `xml:"url,omitempty"`
ScmSync string `xml:"scmsync,omitempty"`
Link []ProjectLinkMeta `xml:"link"`
ScmSync string `xml:"scmsync"`
Persons []PersonRepoMeta `xml:"person"`
Groups []GroupRepoMeta `xml:"group"`
Repositories []RepositoryMeta `xml:"repository"`
@@ -143,8 +138,8 @@ type ProjectMeta struct {
type PackageMeta struct {
XMLName xml.Name `xml:"package"`
Name string `xml:"name,attr"`
Project string `xml:"project,attr,omitempty"`
ScmSync string `xml:"scmsync,omitempty"`
Project string `xml:"project,attr"`
ScmSync string `xml:"scmsync"`
Persons []PersonRepoMeta `xml:"person"`
Groups []GroupRepoMeta `xml:"group"`

View File

@@ -160,8 +160,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
var pr *models.PullRequest
var err error
gitea.ResetTimelineCache(org, repo, num)
prjGitOrg, prjGitRepo, _ := config.GetPrjGit()
if prjGitOrg == org && prjGitRepo == repo {
if pr, err = gitea.GetPullRequest(org, repo, num); err != nil {
@@ -186,7 +184,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
for _, pr := range prs {
org, repo, idx := pr.PRComponents()
gitea.ResetTimelineCache(org, repo, idx)
reviews, err := FetchGiteaReviews(gitea, org, repo, idx)
if err != nil {
LogError("Error fetching reviews for", PRtoString(pr.PR), ":", err)
@@ -319,10 +316,7 @@ func (rs *PRSet) FindMissingAndExtraReviewers(maintainers MaintainershipData, id
// only need project maintainer reviews if:
// * not created by a bot and has other PRs, or
// * not created by maintainer
noReviewPRCreators := []string{}
if !rs.Config.ReviewRequired {
noReviewPRCreators = prjMaintainers
}
noReviewPRCreators := prjMaintainers
if len(rs.PRs) > 1 {
noReviewPRCreators = append(noReviewPRCreators, rs.BotUser)
}
@@ -345,10 +339,7 @@ func (rs *PRSet) FindMissingAndExtraReviewers(maintainers MaintainershipData, id
pkg := pr.PR.Base.Repo.Name
pkgMaintainers := maintainers.ListPackageMaintainers(pkg, nil)
Maintainers := slices.Concat(prjMaintainers, pkgMaintainers)
noReviewPkgPRCreators := []string{}
if !rs.Config.ReviewRequired {
noReviewPkgPRCreators = pkgMaintainers
}
noReviewPkgPRCreators := pkgMaintainers
LogDebug("packakge maintainers:", Maintainers)

View File

@@ -833,6 +833,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
[]string{"autogits_obs_staging_bot", "user1"},
},
},
{
name: "Add reviewer if also maintainer where review by maintainer is not needed",
prset: &common.PRSet{
@@ -1094,67 +1095,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
expected_missing_reviewers: [][]string{{"pkgm2", "prj2"}},
expected_extra_reviewers: [][]string{{}, {"prj1"}},
},
{
name: "Package maintainer submitter, AlwaysRequireReview=false",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "pkgmaintainer"},
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{},
ReviewRequired: false,
},
},
maintainers: &common.MaintainershipMap{
Data: map[string][]string{
"pkg": {"pkgmaintainer", "pkgm1"},
},
},
noAutoStaging: true,
expected_missing_reviewers: [][]string{
{},
},
},
{
name: "Package maintainer submitter, AlwaysRequireReview=true",
prset: &common.PRSet{
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
User: &models.User{UserName: "pkgmaintainer"},
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{},
ReviewRequired: true,
},
},
maintainers: &common.MaintainershipMap{
Data: map[string][]string{
"pkg": {"pkgmaintainer", "pkgm1"},
},
},
noAutoStaging: true,
expected_missing_reviewers: [][]string{
{"pkgm1"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.prset.HasAutoStaging = !test.noAutoStaging

View File

@@ -0,0 +1,187 @@
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open2;
use URI;
sub FindFactoryCommit {
my ($package) = @_;
# Execute osc cat and capture output
my $osc_cmd = "osc cat openSUSE:Factory $package $package.changes";
open( my $osc_fh, "$osc_cmd |" ) or die "Failed to run osc: $!";
my $data = do { local $/; <$osc_fh> };
close($osc_fh);
# Calculate size
my $size = length($data);
# Create blob header
my $blob = "blob $size\0$data";
# Open a pipe to openssl to compute the hash
my ( $reader, $writer );
my $pid = open2( $reader, $writer, "openssl sha256" );
# Send blob data
print $writer $blob;
close $writer;
# Read the hash result and extract it
my $hash_line = <$reader>;
waitpid( $pid, 0 );
my ($hash) = $hash_line =~ /([a-fA-F0-9]{64})/;
# Run git search command with the hash
print("looking for hash: $hash\n");
my @hashes;
my $git_cmd =
"git -C $package rev-list --all pool/HEAD | while read commit; do git -C $package ls-tree \"\$commit\" | grep -q '^100644 blob $hash' && echo \"\$commit\"; done";
open( my $git_fh, "$git_cmd |" ) or die "Failed to run git search: $!";
while ( my $commit = <$git_fh> ) {
chomp $commit;
print "Found commit $commit\n";
push( @hashes, $commit );
}
close($git_fh);
return @hashes;
}
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> <package> <repo>";
}
my $project = shift or Usage();
my $pkg = shift;
my $repo = shift;
if (not defined($repo)) {
Usage();
}
my $meta_url = `osc meta pkg $project $pkg | grep scmsync | sed -e 's,\\s*</\\?scmsync>\\s*,,g'`;
chomp($meta_url);
if ($meta_url ne $repo) {
die "meta not equal to repo for $pkg: $meta_url != $repo";
}
my $u = URI->new($meta_url);
die "Only src.opensuse.org is supported" unless $u->scheme =~ /^https?$/ && $u->host eq 'src.opensuse.org';
my (undef, $org, $repo_path) = split('/', $u->path);
my $branch = $u->fragment;
die "Only src.opensuse.org is supported" unless $org;
if ($org eq "pool") {
print "Already a pool package. We are done.\n";
exit(0);
}
my %params = $u->query_form;
delete $params{trackingbranch};
die "Unsupported query parameters: " . join(', ', keys %params) if keys %params;
my @packages = ($pkg) if defined $pkg;
if ( ! -e $org ) {
mkdir($org);
}
chdir($org);
my $super_user = $ENV{SUPER};
if (defined($super_user)) {
$super_user = "-G $super_user";
} else {
$super_user = "";
}
for my $package ( sort(@packages) ) {
print " ----- PROCESSING $package\n";
my $url = "https://src.opensuse.org/$org/$repo_path.git";
my $push_url = "gitea\@src.opensuse.org:pool/$package.git";
if ( not -e $package ) {
print("cloning...\n");
system("git clone --origin pool $url $package") == 0
or die "Can't clone $org/$repo_path";
}
else {
print("adding remote...\n");
system("git -C $package remote rm pool > /dev/null");
system("git -C $package remote add pool $url") == 0
or die "Can't add pool for $package";
}
system("git -C $package remote set-url pool --push $push_url") == 0
or die "Can't add push remote for $package";
print("fetching remote...\n");
system("git -C $package fetch pool") == 0 or die "Can't fetch pool for $package";
my @commits = FindFactoryCommit($package);
my $Md5Hashes = FactoryMd5($package);
my $c;
my $match = 0;
for my $commit (@commits) {
if ( length($commit) != 64 ) {
print("Failed to find factory commit. Aborting.");
exit(1);
}
if (
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;
}
}
}
if ( !$match ) {
die "Match not found. Aborting.";
}
system ("git -C $package push -f pool factory");
print "$package: $c\n";
}

View File

@@ -1,3 +1,4 @@
SystemsManagement
Java:packages
Kernel:firmware
Kernel:kdump
@@ -11,7 +12,6 @@ devel:languages:lua
devel:languages:nodejs
devel:languages:perl
devel:languages:python:Factory
devel:languages:python:mailman
devel:languages:python:pytest
devel:openSUSE:Factory
network:chromium

View File

@@ -0,0 +1,4 @@
#!/usr/bin/bash
osc api '/search/package?match=scmsync' | ../xml_package_parse | ../find_factory_commit_in_gitpkg.pl

View File

@@ -0,0 +1,83 @@
#!/usr/bin/perl
use strict;
use warnings;
use XML::Parser;
my $parser = XML::Parser->new(Handlers => {
Start => \&handle_start,
End => \&handle_end,
Char => \&handle_char,
});
my $current_element = '';
my $current_package_attrs = {};
my $scmsync_content = '';
my %devel_pkgs;
open(my $dfh, "curl -s https://src.opensuse.org/openSUSE/Factory/raw/branch/main/pkgs/_meta/devel_packages |") or die $!;
while(<$dfh>) {
chomp;
$devel_pkgs{$_} = 1;
}
close($dfh);
my $xml_content = do { local $/; <STDIN> };
$parser->parse($xml_content);
sub handle_start {
my ($expat, $element, %attrs) = @_;
$current_element = $element;
if ($element eq 'package') {
$current_package_attrs = \%attrs;
}
if ($element eq 'scmsync') {
$scmsync_content = '';
}
}
sub handle_char {
my ($expat, $string) = @_;
if ($current_element eq 'scmsync') {
$scmsync_content .= $string;
}
}
sub handle_end {
my ($expat, $element) = @_;
if ($element eq 'scmsync') {
my $project = $current_package_attrs->{project};
my $name = $current_package_attrs->{name};
my $scmsync = $scmsync_content;
# Use checks
$project = '' unless defined $project;
$name = '' unless defined $name;
$scmsync = '' unless defined $scmsync;
# Trim
$project =~ s/^\s+|\s+$//g;
$name =~ s/^\s+|\s+$//g;
$scmsync =~ s/^\s+|\s+$//g;
my $has_error = 0;
foreach my $val ($project, $name, $scmsync) {
if ($val =~ /\s/) {
print STDERR "Error: Value '$val' contains whitespace.\n";
$has_error = 1;
}
}
unless ($has_error) {
if ($devel_pkgs{"$name $project"}) {
print "$name $project $scmsync\n";
}
}
}
# Reset current element if we are closing it
if ($current_element eq $element) {
$current_element = '';
}
}

View File

@@ -35,7 +35,6 @@ It's a JSON file with following syntax:
{
"Name": "SLES",
"Origin": "SUSE:SLFO:Products:SLES:16.0",
"Label": "BootstrapRing",
"BuildDisableRepos": ["product"]
}
]
@@ -49,7 +48,6 @@ It's a JSON file with following syntax:
| *QA* | Crucial for generating a product build (such as an ISO or FTP tree) that incorporates the packages. | no | array of objects | | |
| *QA > Name* | Suffix for the QA OBS staging project. The project is named *StagingProject:<PR_Number>:Name*. | no | string | | |
| *QA > Origin* | OBS reference project | no | string | | |
| *QA > Label* | Setup the project only when the given gitea label is set on pull request | no | string | | |
| *QA > BuildDisableRepos* | The names of OBS repositories to build-disable, if any. | no | array of strings | | [] |
@@ -71,10 +69,4 @@ Details
* In this case, the **scmsync** tag is inherited from the `QA > Origin` project.
* It is desirable in some cases to avoid building some specific build service repositories when not needed. In this case, `QA > BuildDisableRepos` can be specified.
These repositories would be disabled in the project meta when generating the QA project.
* QA projects can build on each other. In this case it is important that the order to setup is correct
in the staging.config file.
* Based on Label settings QA projects can get created or removed. The staging bot is also checking that these
projects build successfully.
* It is possible to include the sources from the staging project also in the QA project. Define a template using
a project link pointing to the project defined as "StagingProject". You must *not* use scmsync directly in the
same project then, but you can use it indirectly via a second project link

View File

@@ -19,7 +19,6 @@ package main
*/
import (
"bufio"
"encoding/xml"
"errors"
"flag"
@@ -115,105 +114,161 @@ type DisableFlag struct {
Name string `xml:"repository,attr"`
}
func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
func ProcessBuildStatus(project, refProject *common.BuildResultList) BuildStatusSummary {
if _, finished := refProject.BuildResultSummary(); !finished {
common.LogDebug("refProject not finished building??")
return BuildStatusSummaryUnknown
}
if _, finished := project.BuildResultSummary(); !finished {
common.LogDebug("Still building...")
return BuildStatusSummaryBuilding
}
common.LogDebug("build results", len(project.Result))
// the repositories should be setup equally between the projects. We
// need to verify that packages that are building in `refProject` are not
// failing in the `project`
BuildResultSorter := func(a, b *common.BuildResult) int {
if c := strings.Compare(a.Repository, b.Repository); c != 0 {
return c
}
if c := strings.Compare(a.Arch, b.Arch); c != 0 {
return c
}
panic("Should not happen -- BuiltResultSorter equal repos?")
}
slices.SortFunc(project.Result, BuildResultSorter)
if refProject == nil {
// just return if buid finished and have some successes, since new package
common.LogInfo("New package. Only need some success...")
SomeSuccess := false
for i := 0; i < len(project.Result); i++ {
repoRes := project.Result[i]
repoResStatus, ok := common.ObsRepoStatusDetails[repoRes.Code]
if !ok {
common.LogDebug("cannot find code:", repoRes.Code)
return BuildStatusSummaryUnknown
}
if !repoResStatus.Finished {
return BuildStatusSummaryBuilding
}
for _, pkg := range repoRes.Status {
pkgStatus, ok := common.ObsBuildStatusDetails[pkg.Code]
if !ok {
common.LogInfo("Unknown package build status:", pkg.Code, "for", pkg.Package)
common.LogDebug("Details:", pkg.Details)
}
if pkgStatus.Success {
SomeSuccess = true
}
}
}
if SomeSuccess {
return BuildStatusSummarySuccess
}
return BuildStatusSummaryFailed
}
slices.SortFunc(refProject.Result, BuildResultSorter)
common.LogDebug("comparing results", len(project.Result), "vs. ref", len(refProject.Result))
SomeSuccess := false
for i := 0; i < len(project.Result); i++ {
common.LogDebug("searching for", project.Result[i].Repository, "/", project.Result[i].Arch)
j := 0
found:
for j := 0; j < len(project.Result); j++ {
for ; j < len(refProject.Result); j++ {
if project.Result[i].Repository != refProject.Result[j].Repository ||
project.Result[i].Arch != refProject.Result[j].Arch {
continue
}
common.LogDebug(" found match for @ idx:", j)
res := ProcessRepoBuildStatus(project.Result[i].Status)
res, success := ProcessRepoBuildStatus(project.Result[i].Status, refProject.Result[j].Status)
switch res {
case BuildStatusSummarySuccess:
SomeSuccess = SomeSuccess || success
break found
case BuildStatusSummaryFailed:
return BuildStatusSummaryFailed
default:
return res
}
}
if j >= len(refProject.Result) {
common.LogDebug("Cannot find results...")
common.LogDebug(project.Result[i])
common.LogDebug(refProject.Result)
return BuildStatusSummaryUnknown
}
}
return BuildStatusSummarySuccess
if SomeSuccess {
return BuildStatusSummarySuccess
}
return BuildStatusSummaryFailed
}
func ProcessRepoBuildStatus(results []*common.PackageBuildStatus) (status BuildStatusSummary) {
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
return strings.Compare(a.Package, b.Package)
}
func ProcessRepoBuildStatus(results, ref []*common.PackageBuildStatus) (status BuildStatusSummary, SomeSuccess bool) {
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
return strings.Compare(a.Package, b.Package)
}
common.LogDebug("******** REF: ")
data, _ := xml.MarshalIndent(ref, "", " ")
common.LogDebug(string(data))
common.LogDebug("******* RESULTS: ")
data, _ := xml.MarshalIndent(results, "", " ")
data, _ = xml.MarshalIndent(results, "", " ")
common.LogDebug(string(data))
common.LogDebug("*******")
// compare build result
slices.SortFunc(results, PackageBuildStatusSorter)
slices.SortFunc(ref, PackageBuildStatusSorter)
j := 0
SomeSuccess = false
for i := 0; i < len(results); i++ {
res, ok := common.ObsBuildStatusDetails[results[i].Code]
if !ok {
common.LogInfo("unknown package result code:", results[i].Code, "for package:", results[i].Package)
return BuildStatusSummaryUnknown
return BuildStatusSummaryUnknown, SomeSuccess
}
if !res.Finished {
return BuildStatusSummaryBuilding
return BuildStatusSummaryBuilding, SomeSuccess
}
if !res.Success {
return BuildStatusSummaryFailed
}
}
return BuildStatusSummarySuccess
}
func GetPackageBuildStatus(project *common.BuildResultList, packageName string) (bool, BuildStatusSummary) {
var packageStatuses []*common.PackageBuildStatus
// Collect all statuses for the package
for _, result := range project.Result {
for _, pkgStatus := range result.Status {
if pkgStatus.Package == packageName {
packageStatuses = append(packageStatuses, pkgStatus)
// not failed if reference project also failed for same package here
for ; j < len(results) && strings.Compare(results[i].Package, ref[j].Package) < 0; j++ {
}
if j < len(results) && results[i].Package == ref[j].Package {
refRes, ok := common.ObsBuildStatusDetails[ref[j].Code]
if !ok {
common.LogInfo("unknown ref package result code:", ref[j].Code, "package:", ref[j].Package)
return BuildStatusSummaryUnknown, SomeSuccess
}
if !refRes.Finished {
common.LogDebug("Not finished building in reference project?")
}
if refRes.Success {
return BuildStatusSummaryFailed, SomeSuccess
}
}
} else {
SomeSuccess = true
}
}
if len(packageStatuses) == 0 {
return true, BuildStatusSummaryUnknown // true for 'missing'
}
// Check for any failures
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
}
}
// Check for any unfinished builds
for _, pkgStatus := range packageStatuses {
res, _ := common.ObsBuildStatusDetails[pkgStatus.Code]
// 'ok' is already checked in the loop above
if !res.Finished {
return false, BuildStatusSummaryBuilding
}
}
// If we got here, all are finished and successful
return false, BuildStatusSummarySuccess
return BuildStatusSummarySuccess, SomeSuccess
}
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
@@ -272,9 +327,9 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
return nil, err
}
common.LogInfo("Meta: ", meta)
// generate new project with paths pointinig back to original repos
// disable publishing
meta.Name = stagingPrj
meta.Description = fmt.Sprintf(`Pull request build job PR#%d to branch %s of %s/%s`,
@@ -289,10 +344,7 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
urlPkg := make([]string, 0, len(modifiedOrNew))
for _, pkg := range modifiedOrNew {
// FIXME: skip manifest subdirectories itself
// strip any leading directory name and just hand over last directory as package name
onlybuilds := strings.Split(pkg, "/")
urlPkg = append(urlPkg, "onlybuild="+url.QueryEscape(onlybuilds[len(onlybuilds)-1]))
urlPkg = append(urlPkg, "onlybuild="+url.QueryEscape(pkg))
}
meta.ScmSync = pr.Head.Repo.CloneURL + "?" + strings.Join(urlPkg, "&") + "#" + pr.Head.Sha
if len(meta.ScmSync) >= 65535 {
@@ -332,14 +384,12 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
func CreateQASubProject(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)
if err != nil {
common.LogError("error fetching template project meta for", templateProject, ":", err)
return err
}
// patch baseMeta to become the new project
common.LogDebug("upcoming project name ", stagingProject, ":", subProjectName)
templateMeta.Name = stagingProject + ":" + subProjectName
// freeze tag for now
if len(templateMeta.ScmSync) > 0 {
@@ -377,50 +427,19 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
templateMeta.BuildFlags.Contents += string(output)
}
}
// include sources from submission project when link points to staging project
for idx, l := range templateMeta.Link {
if l.Project == stagingConfig.StagingProject {
templateMeta.Link[idx].Project = stagingProject
}
}
// Cleanup ReleaseTarget and modify affected path entries
for idx, r := range templateMeta.Repositories {
templateMeta.Repositories[idx].ReleaseTargets = nil
for pidx, path := range r.Paths {
// Check for path building against code stream
common.LogDebug(" checking in ", templateMeta.Name)
common.LogDebug(" stagingProject ", stagingProject)
common.LogDebug(" checking for ", templateMeta.Repositories[idx].Paths[pidx].Project)
common.LogDebug(" path.Project ", path.Project)
common.LogDebug(" stagingConfig.ObsProject ", stagingConfig.ObsProject)
common.LogDebug(" stagingConfig.StagingProject ", stagingConfig.StagingProject)
common.LogDebug(" templateProject ", templateProject)
if path.Project == stagingConfig.ObsProject {
templateMeta.Repositories[idx].Paths[pidx].Project = stagingProject
} else
}
// Check for path building against a repo in template project itself
if path.Project == templateProject {
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 + ":") {
newProjectName := stagingProject
// find project name
for _, setup := range stagingConfig.QA {
if setup.Origin == path.Project {
common.LogDebug(" Match:", setup.Origin)
newProjectName = newProjectName + ":" + setup.Name
common.LogDebug(" New:", newProjectName)
break
}
}
templateMeta.Repositories[idx].Paths[pidx].Project = newProjectName
common.LogDebug(" Matched prefix")
}
common.LogDebug(" Path using project ", templateMeta.Repositories[idx].Paths[pidx].Project)
}
}
@@ -428,8 +447,6 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
err = ObsClient.SetProjectMeta(templateMeta)
if err != nil {
common.LogError("cannot create project:", templateMeta.Name, err)
x, _ := xml.MarshalIndent(templateMeta, "", " ")
common.LogError(string(x))
return err
}
} else {
@@ -536,7 +553,7 @@ func FetchOurLatestActionableReview(gitea common.Gitea, org, repo string, id int
}
func ParseNotificationToPR(thread *models.NotificationThread) (org string, repo string, num int64, err error) {
rx := regexp.MustCompile(`^.*/api/v\d+/repos/(?<org>[-_a-zA-Z0-9]+)/(?<project>[-_a-zA-Z0-9]+)/issues/(?<num>[0-9]+)$`)
rx := regexp.MustCompile(`^https://src\.(?:open)?suse\.(?:org|de)/api/v\d+/repos/(?<org>[-_a-zA-Z0-9]+)/(?<project>[-_a-zA-Z0-9]+)/issues/(?<num>[0-9]+)$`)
notification := thread.Subject
match := rx.FindStringSubmatch(notification.URL)
if match == nil {
@@ -685,64 +702,6 @@ 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) {
if IsDryRun {
common.LogInfo("Would comment on package PR %s/%s#%d: %s", org, repo, prNum, msg)
return
}
pr, err := gitea.GetPullRequest(org, repo, prNum)
if err != nil {
common.LogError("Failed to get package PR %s/%s#%d: %v", org, repo, prNum, err)
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)
}
}
// Create and remove QA projects
func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) []string {
usedQAprojects := make([]string, 0)
prLabelNames := make(map[string]int)
for _, label := range pr.Labels {
prLabelNames[label.Name] = 1
}
msg := ""
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)
}
common.LogInfo("QA project ", setup.Name, "has no matching Label")
continue
}
}
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 {
common.LogInfo("Create QA project ", QAproject)
CreateQASubProject(stagingConfig, git, gitea, pr,
stagingProject,
setup.Origin,
setup.Name,
setup.BuildDisableRepos)
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
QAproject + "\n"
}
}
if len(msg) > 1 {
gitea.AddComment(pr, msg)
}
return usedQAprojects
}
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
dir, err := os.MkdirTemp(os.TempDir(), BotName)
common.PanicOnError(err)
@@ -820,12 +779,11 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
if err != nil || meta == nil {
common.LogError("Cannot find reference project meta:", stagingConfig.ObsProject, err)
if !IsDryRun && err == nil {
common.LogError("Reference project is absent:", stagingConfig.ObsProject, err)
if !IsDryRun {
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot fetch reference project meta")
return true, err
}
return true, err
return true, nil
}
if metaUrl, err := url.Parse(meta.ScmSync); err != nil {
@@ -975,49 +933,36 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
msg = "Build is started in " + ObsWebHost + "/project/show/" +
stagingProject + " .\n"
if len(stagingConfig.QA) > 0 {
msg = msg + "\nAdditional QA builds: \n"
}
SetStatus(gitea, org, repo, pr.Head.Sha, status)
for _, setup := range stagingConfig.QA {
CreateQASubProject(stagingConfig, git, gitea, pr,
stagingProject,
setup.Origin,
setup.Name,
setup.BuildDisableRepos)
msg = msg + ObsWebHost + "/project/show/" +
stagingProject + ":" + setup.Name + "\n"
}
}
if change != RequestModificationNoChange && !IsDryRun {
gitea.AddComment(pr, msg)
}
baseResult, err := ObsClient.LastBuildResults(stagingConfig.ObsProject, modifiedPackages...)
if err != nil {
common.LogError("failed fetching ref project status for", stagingConfig.ObsProject, ":", err)
}
stagingResult, err := ObsClient.BuildStatus(stagingProject)
if err != nil {
common.LogError("failed fetching stage project status for", stagingProject, ":", err)
}
_, packagePRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
// always update QA projects because Labels can change
qaProjects := ProcessQaProjects(stagingConfig, git, gitea, pr, stagingProject)
buildStatus := ProcessBuildStatus(stagingResult, baseResult)
done := false
overallBuildStatus := ProcessBuildStatus(stagingResult)
commentSuffix := ""
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
seperator := " in "
for _, qaProject := range qaProjects {
qaResult, err := ObsClient.BuildStatus(qaProject)
if err != nil {
common.LogError("failed fetching stage project status for", qaProject, ":", err)
}
qaBuildStatus := ProcessBuildStatus(qaResult)
if qaBuildStatus != BuildStatusSummarySuccess {
// either still building or in failed state
overallBuildStatus = qaBuildStatus
commentSuffix = commentSuffix + seperator + qaProject
seperator = ", "
}
if qaBuildStatus == BuildStatusSummaryFailed {
// main project was successful, but QA project, adapt the link to QA project
// and change commit state to fail
status.Status = common.CommitStatus_Fail
status.TargetURL = ObsWebHost + "/project/show/" + qaProject
SetStatus(gitea, org, repo, pr.Head.Sha, status)
}
}
}
switch overallBuildStatus {
switch buildStatus {
case BuildStatusSummarySuccess:
status.Status = common.CommitStatus_Success
done = true
@@ -1037,44 +982,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
}
}
}
if overallBuildStatus == BuildStatusSummarySuccess || overallBuildStatus == BuildStatusSummaryFailed {
// avoid commenting while build is in progress
missingPkgs := []string{}
for _, packagePR := range packagePRs {
missing, packageBuildStatus := GetPackageBuildStatus(stagingResult, packagePR.Repo)
if missing {
missingPkgs = append(missingPkgs, packagePR.Repo)
continue
}
var msg string
switch packageBuildStatus {
case BuildStatusSummarySuccess:
msg = fmt.Sprintf("Build successful, for more information go in %s/project/show/%s.\n", ObsWebHost, stagingProject)
case BuildStatusSummaryFailed:
msg = fmt.Sprintf("Build failed, for more information go in %s/project/show/%s.\n", ObsWebHost, stagingProject)
default:
continue
}
commentOnPackagePR(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
}
if len(missingPkgs) > 0 {
overallBuildStatus = BuildStatusSummaryFailed
msg := "The following packages were not found in the staging project:\n"
for _, pkg := range missingPkgs {
msg = msg + " - " + pkg + "\n"
}
common.LogInfo(msg)
err := gitea.AddComment(pr, msg)
if err != nil {
common.LogError(err)
}
}
}
common.LogInfo("Build status:", overallBuildStatus)
common.LogInfo("Build status:", buildStatus)
if !IsDryRun {
if err = SetStatus(gitea, org, repo, pr.Head.Sha, status); err != nil {
return false, err
@@ -1139,7 +1047,6 @@ func PollWorkNotifications(giteaUrl string) {
var ListPullNotificationsOnly bool
var GiteaUrl string
var ObsApiHost string
var ObsWebHost string
var IsDryRun bool
var ProcessPROnly string
@@ -1162,8 +1069,8 @@ func main() {
flag.BoolVar(&ListPullNotificationsOnly, "list-notifications-only", false, "Only lists notifications without acting on them")
ProcessPROnly := flag.String("pr", "", "Process only specific PR and ignore the rest. Use for debugging")
buildRoot := flag.String("build-root", "", "Default build location for staging projects. Default is bot's home project")
flag.StringVar(&GiteaUrl, "gitea-url", "", "Gitea instance")
flag.StringVar(&ObsApiHost, "obs", "", "API for OBS instance")
flag.StringVar(&GiteaUrl, "gitea-url", "https://src.opensuse.org", "Gitea instance")
obsApiHost := flag.String("obs", "https://api.opensuse.org", "API for OBS instance")
flag.StringVar(&ObsWebHost, "obs-web", "", "Web OBS instance, if not derived from the obs config")
flag.BoolVar(&IsDryRun, "dry", false, "Dry-run, don't actually create any build projects or review changes")
debug := flag.Bool("debug", false, "Turns on debug logging")
@@ -1175,34 +1082,19 @@ func main() {
common.SetLoggingLevel(common.LogLevelInfo)
}
if len(GiteaUrl) == 0 {
GiteaUrl = os.Getenv(common.GiteaHostEnv)
}
if len(GiteaUrl) == 0 {
GiteaUrl = "https://src.opensuse.org"
}
if len(ObsApiHost) == 0 {
ObsApiHost = os.Getenv(common.ObsApiEnv)
}
if len(ObsApiHost) == 0 {
ObsApiHost = "https://api.opensuse.org"
}
if len(ObsWebHost) == 0 {
ObsWebHost = os.Getenv(common.ObsWebEnv)
}
if len(ObsWebHost) == 0 {
ObsWebHost = "https://build.opensuse.org"
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
}
common.LogDebug("OBS Gitea Host:", GiteaUrl)
common.LogDebug("OBS Web Host:", ObsWebHost)
common.LogDebug("OBS API Host:", ObsApiHost)
common.LogDebug("OBS API Host:", *obsApiHost)
common.PanicOnErrorWithMsg(common.RequireGiteaSecretToken(), "Cannot find GITEA_TOKEN")
common.PanicOnErrorWithMsg(common.RequireObsSecretToken(), "Cannot find OBS_USER and OBS_PASSWORD")
var err error
if ObsClient, err = common.NewObsClient(ObsApiHost); err != nil {
if ObsClient, err = common.NewObsClient(*obsApiHost); err != nil {
log.Error(err)
return
}

View File

@@ -105,10 +105,10 @@ func ProjectStatusSummarySvg(res []*common.BuildResult) []byte {
func LinkToBuildlog(R *common.BuildResult, S *common.PackageBuildStatus) string {
if R != nil && S != nil {
//switch S.Code {
//case "succeeded", "failed", "building":
switch S.Code {
case "succeeded", "failed", "building":
return "/buildlog/" + url.PathEscape(R.Project) + "/" + url.PathEscape(S.Package) + "/" + url.PathEscape(R.Repository) + "/" + url.PathEscape(R.Arch)
//}
}
}
return ""
}
@@ -170,7 +170,6 @@ func BuildStatusSvg(repo *common.BuildResult, status *common.PackageBuildStatus)
buildStatus, ok := common.ObsBuildStatusDetails[status.Code]
if !ok {
buildStatus = common.ObsBuildStatusDetails["error"]
common.LogError("Cannot find detail for status.Code", status.Code)
}
fillColor := "#480" // orange
textColor := "#888"

View File

@@ -1,98 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"slices"
"src.opensuse.org/autogits/common"
)
func WriteNewMaintainershipFile(m *common.MaintainershipMap, filename string) {
f, err := os.Create(filename + ".new")
common.PanicOnError(err)
common.PanicOnError(m.WriteMaintainershipFile(f))
common.PanicOnError(f.Sync())
common.PanicOnError(f.Close())
common.PanicOnError(os.Rename(filename+".new", filename))
}
func run() error {
pkg := flag.String("package", "", "Package to modify")
rm := flag.Bool("rm", false, "Remove maintainer from package")
add := flag.Bool("add", false, "Add maintainer to package")
lint := flag.Bool("lint-only", false, "Reformat entire _maintainership.json only")
flag.Parse()
if (*add == *rm) && !*lint {
return fmt.Errorf("Need to either add or remove a maintainer, or lint")
}
filename := common.MaintainershipFile
if *lint {
if len(flag.Args()) > 0 {
filename = flag.Arg(0)
}
}
data, err := os.ReadFile(filename)
if os.IsNotExist(err) {
return err
}
if err != nil {
return err
}
m, err := common.ParseMaintainershipData(data)
if err != nil {
return fmt.Errorf("Failed to parse JSON: %w", err)
}
if *lint {
m.Raw = nil // forces a rewrite
} else {
users := flag.Args()
if len(users) > 0 {
maintainers, ok := m.Data[*pkg]
if !ok && !*add {
return fmt.Errorf("No package %s and not adding one.", *pkg)
}
if *add {
for _, u := range users {
if !slices.Contains(maintainers, u) {
maintainers = append(maintainers, u)
}
}
}
if *rm {
newMaintainers := make([]string, 0, len(maintainers))
for _, m := range maintainers {
if !slices.Contains(users, m) {
newMaintainers = append(newMaintainers, m)
}
}
maintainers = newMaintainers
}
if len(maintainers) > 0 {
slices.Sort(maintainers)
m.Data[*pkg] = maintainers
} else {
delete(m.Data, *pkg)
}
}
}
WriteNewMaintainershipFile(m, filename)
return nil
}
func main() {
if err := run(); err != nil {
common.LogError(err)
os.Exit(1)
}
}

View File

@@ -1,242 +0,0 @@
package main
import (
"encoding/json"
"flag"
"os"
"os/exec"
"reflect"
"strings"
"testing"
"src.opensuse.org/autogits/common"
)
func TestMain(m *testing.M) {
if os.Getenv("BE_MAIN") == "1" {
main()
return
}
os.Exit(m.Run())
}
func TestRun(t *testing.T) {
tests := []struct {
name string
inData string
expectedOut string
params []string
expectedError string
isDir bool
}{
{
name: "add user to existing package",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg1", "-add", "user2"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "add user to new package",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg2", "-add", "user2"},
expectedOut: `{"pkg1": ["user1"], "pkg2": ["user2"]}`,
},
{
name: "no-op with no users",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg1", "-add"},
expectedOut: `{"pkg1": ["user1"]}`,
},
{
name: "add existing user",
inData: `{"pkg1": ["user1", "user2"]}`,
params: []string{"-package", "pkg1", "-add", "user2"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "remove user from package",
inData: `{"pkg1": ["user1", "user2"]}`,
params: []string{"-package", "pkg1", "-rm", "user2"},
expectedOut: `{"pkg1": ["user1"]}`,
},
{
name: "remove last user from package",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg1", "-rm", "user1"},
expectedOut: `{}`,
},
{
name: "remove non-existent user",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg1", "-rm", "user2"},
expectedOut: `{"pkg1": ["user1"]}`,
},
{
name: "lint only unsorted",
inData: `{"pkg1": ["user2", "user1"]}`,
params: []string{"-lint-only"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "lint only no changes",
inData: `{"pkg1": ["user1", "user2"]}`,
params: []string{"-lint-only"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "no file",
params: []string{"-add"},
expectedError: "no such file or directory",
},
{
name: "invalid json",
inData: `{"pkg1": ["user1"`,
params: []string{"-add"},
expectedError: "Failed to parse JSON",
},
{
name: "add",
inData: `{"pkg1": ["user1", "user2"]}`,
params: []string{"-package", "pkg1", "-add", "user3"},
expectedOut: `{"pkg1": ["user1", "user2", "user3"]}`,
},
{
name: "lint specific file",
inData: `{"pkg1": ["user2", "user1"]}`,
params: []string{"-lint-only", "other.json"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "add user to package when it was not there before",
inData: `{}`,
params: []string{"-package", "newpkg", "-add", "user1"},
expectedOut: `{"newpkg": ["user1"]}`,
},
{
name: "unreadable file (is a directory)",
isDir: true,
params: []string{"-rm"},
expectedError: "is a directory",
},
{
name: "remove user from non-existent package",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg2", "-rm", "user2"},
expectedError: "No package pkg2 and not adding one.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
oldWd, _ := os.Getwd()
_ = os.Chdir(dir)
defer os.Chdir(oldWd)
targetFile := common.MaintainershipFile
if tt.name == "lint specific file" {
targetFile = "other.json"
}
if tt.isDir {
_ = os.Mkdir(targetFile, 0755)
} else if tt.inData != "" {
_ = os.WriteFile(targetFile, []byte(tt.inData), 0644)
}
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
os.Args = append([]string{"cmd"}, tt.params...)
err := run()
if tt.expectedError != "" {
if err == nil {
t.Fatalf("expected error containing %q, but got none", tt.expectedError)
}
if !strings.Contains(err.Error(), tt.expectedError) {
t.Fatalf("expected error containing %q, got %q", tt.expectedError, err.Error())
}
} else if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tt.expectedOut != "" {
data, _ := os.ReadFile(targetFile)
var got, expected map[string][]string
_ = json.Unmarshal(data, &got)
_ = json.Unmarshal([]byte(tt.expectedOut), &expected)
if len(got) == 0 && len(expected) == 0 {
return
}
if !reflect.DeepEqual(got, expected) {
t.Fatalf("expected %v, got %v", expected, got)
}
}
})
}
}
func TestMainRecursive(t *testing.T) {
tests := []struct {
name string
inData string
expectedOut string
params []string
expectExit bool
}{
{
name: "test main() via recursive call",
inData: `{"pkg1": ["user1"]}`,
params: []string{"-package", "pkg1", "-add", "user2"},
expectedOut: `{"pkg1": ["user1", "user2"]}`,
},
{
name: "test main() failure",
params: []string{"-package", "pkg1"},
expectExit: true,
},
}
exe, _ := os.Executable()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
oldWd, _ := os.Getwd()
_ = os.Chdir(dir)
defer os.Chdir(oldWd)
if tt.inData != "" {
_ = os.WriteFile(common.MaintainershipFile, []byte(tt.inData), 0644)
}
cmd := exec.Command(exe, append([]string{"-test.run=None"}, tt.params...)...)
cmd.Env = append(os.Environ(), "BE_MAIN=1")
out, runErr := cmd.CombinedOutput()
if tt.expectExit {
if runErr == nil {
t.Fatalf("expected exit with error, but it succeeded")
}
return
}
if runErr != nil {
t.Fatalf("unexpected error: %v: %s", runErr, string(out))
}
if tt.expectedOut != "" {
data, _ := os.ReadFile(common.MaintainershipFile)
var got, expected map[string][]string
_ = json.Unmarshal(data, &got)
_ = json.Unmarshal([]byte(tt.expectedOut), &expected)
if !reflect.DeepEqual(got, expected) {
t.Fatalf("expected %v, got %v", expected, got)
}
}
})
}
}

View File

@@ -52,7 +52,7 @@ Config file
| *GitProjectName* | Repository and branch where the ProjectGit lives. | no | string | **Format**: `org/project_repo#branch` | By default assumes `_ObsPrj` with default branch in the *Organization* |
| *ManualMergeOnly* | Merges are permitted only upon receiving a "merge ok" comment from designated maintainers in the PkgGit PR. | no | bool | true, false | false |
| *ManualMergeProject* | Merges are permitted only upon receiving a "merge ok" comment in the ProjectGit PR from project maintainers. | no | bool | true, false | false |
| *ReviewRequired* | If submitter is a maintainer, require review from another maintainer if available. | no | bool | true, false | false |
| *ReviewRequired* | (NOT IMPLEMENTED) If submitter is a maintainer, require review from another maintainer if available. | no | bool | true, false | false |
| *NoProjectGitPR* | Do not create PrjGit PR, but still perform other tasks. | no | bool | true, false | false |
| *Reviewers* | PrjGit reviewers. Additional review requests are triggered for associated PkgGit PRs. PrjGit PR is merged only when all reviews are complete. | no | array of strings | | `[]` |
| *ReviewGroups* | If a group is specified in Reviewers, its members are listed here. | no | array of objects | | `[]` |
@@ -160,4 +160,4 @@ Server configuration
| Field | Type | Notes |
| ----- | ----- | ----- |
| root | Array of string | Format **org/repo\#branch** |
| root | Array of string | Format **org/repo\#branch** |

View File

@@ -0,0 +1,18 @@
package interfaces
import "src.opensuse.org/autogits/common"
//go:generate mockgen -source=state_checker.go -destination=../mock/state_checker.go -typed -package mock_main
type StateChecker interface {
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
CheckRepos()
ConsistencyCheckProcess() error
}
type PRToProcess struct {
Org, Repo, Branch string
}

View File

@@ -2,8 +2,10 @@ package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@@ -20,6 +22,83 @@ func TestProjectBranchName(t *testing.T) {
}
}
const LocalCMD = "---"
func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) {
for _, cmd := range cmds {
if cmd[0] == LocalCMD {
command := exec.Command(cmd[2], cmd[3:]...)
command.Dir = filepath.Join(git.GitPath, cmd[1])
command.Stdin = nil
command.Env = append([]string{"GIT_CONFIG_COUNT=1", "GIT_CONFIG_KEY_1=protocol.file.allow", "GIT_CONFIG_VALUE_1=always"}, common.ExtraGitParams...)
_, err := command.CombinedOutput()
if err != nil {
t.Errorf(" *** error: %v\n", err)
}
} else {
git.GitExecOrPanic(cmd[0], cmd[1:]...)
}
}
}
func commandsForPackages(dir, prefix string, startN, endN int) [][]string {
commands := make([][]string, (endN-startN+2)*6)
if dir == "" {
dir = "."
}
cmdIdx := 0
for idx := startN; idx <= endN; idx++ {
pkgDir := fmt.Sprintf("%s%d", prefix, idx)
commands[cmdIdx+0] = []string{"", "init", "-q", "--object-format", "sha256", "-b", "testing", pkgDir}
commands[cmdIdx+1] = []string{LocalCMD, pkgDir, "/usr/bin/touch", "testFile"}
commands[cmdIdx+2] = []string{pkgDir, "add", "testFile"}
commands[cmdIdx+3] = []string{pkgDir, "commit", "-m", "added testFile"}
commands[cmdIdx+4] = []string{pkgDir, "config", "receive.denyCurrentBranch", "ignore"}
commands[cmdIdx+5] = []string{"prj", "submodule", "add", filepath.Join("..", pkgDir), filepath.Join(dir, pkgDir)}
cmdIdx += 6
}
// add all the submodules to the prj
commands[cmdIdx+0] = []string{"prj", "commit", "-a", "-m", "adding subpackages"}
return commands
}
func setupGitForTests(t *testing.T, git *common.GitHandlerImpl) {
common.ExtraGitParams = []string{
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always",
"GIT_AUTHOR_NAME=testname",
"GIT_AUTHOR_EMAIL=test@suse.com",
"GIT_AUTHOR_DATE='2005-04-07T22:13:13'",
"GIT_COMMITTER_NAME=testname",
"GIT_COMMITTER_EMAIL=test@suse.com",
"GIT_COMMITTER_DATE='2005-04-07T22:13:13'",
}
gitExecs(t, git, [][]string{
{"", "init", "-q", "--object-format", "sha256", "-b", "testing", "prj"},
{"", "init", "-q", "--object-format", "sha256", "-b", "testing", "foo"},
{LocalCMD, "foo", "/usr/bin/touch", "file1"},
{"foo", "add", "file1"},
{"foo", "commit", "-m", "first commit"},
{"prj", "config", "receive.denyCurrentBranch", "ignore"},
{"prj", "submodule", "init"},
{"prj", "submodule", "add", "../foo", "testRepo"},
{"prj", "add", ".gitmodules", "testRepo"},
{"prj", "commit", "-m", "First instance"},
{"prj", "submodule", "deinit", "testRepo"},
{LocalCMD, "foo", "/usr/bin/touch", "file2"},
{"foo", "add", "file2"},
{"foo", "commit", "-m", "added file2"},
})
}
func TestUpdatePrBranch(t *testing.T) {
var buf bytes.Buffer
origLogger := log.Writer()
@@ -46,7 +125,7 @@ func TestUpdatePrBranch(t *testing.T) {
req.Pull_Request.Base.Sha = strings.TrimSpace(revs[1])
req.Pull_Request.Head.Sha = strings.TrimSpace(revs[0])
updateSubmoduleInPR("testRepo", revs[0], git)
updateSubmoduleInPR("mainRepo", revs[0], git)
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", "created commit"))
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", "origin", "+HEAD:+testing"))
git.GitExecOrPanic("prj", "reset", "--hard", "testing")

View File

@@ -0,0 +1,10 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: pr_processor.go
//
// Generated by this command:
//
// mockgen -source=pr_processor.go -destination=mock/pr_processor.go -typed
//
// Package mock_main is a generated GoMock package.
package mock_main

View File

@@ -3,18 +3,18 @@
//
// Generated by this command:
//
// mockgen -source=state_checker.go -destination=mock_state_checker.go -typed -package main
// mockgen -source=state_checker.go -destination=../mock/state_checker.go -typed -package mock_main
//
// Package main is a generated GoMock package.
package main
// Package mock_main is a generated GoMock package.
package mock_main
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
common "src.opensuse.org/autogits/common"
models "src.opensuse.org/autogits/common/gitea-generated/models"
interfaces "src.opensuse.org/autogits/workflow-pr/interfaces"
)
// MockStateChecker is a mock of StateChecker interface.
@@ -42,9 +42,11 @@ func (m *MockStateChecker) EXPECT() *MockStateCheckerMockRecorder {
}
// CheckRepos mocks base method.
func (m *MockStateChecker) CheckRepos() {
func (m *MockStateChecker) CheckRepos() error {
m.ctrl.T.Helper()
m.ctrl.Call(m, "CheckRepos")
ret := m.ctrl.Call(m, "CheckRepos")
ret0, _ := ret[0].(error)
return ret0
}
// CheckRepos indicates an expected call of CheckRepos.
@@ -60,19 +62,19 @@ type MockStateCheckerCheckReposCall struct {
}
// Return rewrite *gomock.Call.Return
func (c *MockStateCheckerCheckReposCall) Return() *MockStateCheckerCheckReposCall {
c.Call = c.Call.Return()
func (c *MockStateCheckerCheckReposCall) Return(arg0 error) *MockStateCheckerCheckReposCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockStateCheckerCheckReposCall) Do(f func()) *MockStateCheckerCheckReposCall {
func (c *MockStateCheckerCheckReposCall) Do(f func() error) *MockStateCheckerCheckReposCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockStateCheckerCheckReposCall) DoAndReturn(f func()) *MockStateCheckerCheckReposCall {
func (c *MockStateCheckerCheckReposCall) DoAndReturn(f func() error) *MockStateCheckerCheckReposCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
@@ -116,10 +118,10 @@ func (c *MockStateCheckerConsistencyCheckProcessCall) DoAndReturn(f func() error
}
// VerifyProjectState mocks base method.
func (m *MockStateChecker) VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error) {
func (m *MockStateChecker) VerifyProjectState(configs *common.AutogitConfig) ([]*interfaces.PRToProcess, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VerifyProjectState", configs)
ret0, _ := ret[0].([]*PRToProcess)
ret0, _ := ret[0].([]*interfaces.PRToProcess)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -137,81 +139,19 @@ type MockStateCheckerVerifyProjectStateCall struct {
}
// Return rewrite *gomock.Call.Return
func (c *MockStateCheckerVerifyProjectStateCall) Return(arg0 []*PRToProcess, arg1 error) *MockStateCheckerVerifyProjectStateCall {
func (c *MockStateCheckerVerifyProjectStateCall) Return(arg0 []*interfaces.PRToProcess, arg1 error) *MockStateCheckerVerifyProjectStateCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockStateCheckerVerifyProjectStateCall) Do(f func(*common.AutogitConfig) ([]*PRToProcess, error)) *MockStateCheckerVerifyProjectStateCall {
func (c *MockStateCheckerVerifyProjectStateCall) Do(f func(*common.AutogitConfig) ([]*interfaces.PRToProcess, error)) *MockStateCheckerVerifyProjectStateCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockStateCheckerVerifyProjectStateCall) DoAndReturn(f func(*common.AutogitConfig) ([]*PRToProcess, error)) *MockStateCheckerVerifyProjectStateCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockPullRequestProcessor is a mock of PullRequestProcessor interface.
type MockPullRequestProcessor struct {
ctrl *gomock.Controller
recorder *MockPullRequestProcessorMockRecorder
isgomock struct{}
}
// MockPullRequestProcessorMockRecorder is the mock recorder for MockPullRequestProcessor.
type MockPullRequestProcessorMockRecorder struct {
mock *MockPullRequestProcessor
}
// NewMockPullRequestProcessor creates a new mock instance.
func NewMockPullRequestProcessor(ctrl *gomock.Controller) *MockPullRequestProcessor {
mock := &MockPullRequestProcessor{ctrl: ctrl}
mock.recorder = &MockPullRequestProcessorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPullRequestProcessor) EXPECT() *MockPullRequestProcessorMockRecorder {
return m.recorder
}
// Process mocks base method.
func (m *MockPullRequestProcessor) Process(req *models.PullRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Process", req)
ret0, _ := ret[0].(error)
return ret0
}
// Process indicates an expected call of Process.
func (mr *MockPullRequestProcessorMockRecorder) Process(req any) *MockPullRequestProcessorProcessCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*MockPullRequestProcessor)(nil).Process), req)
return &MockPullRequestProcessorProcessCall{Call: call}
}
// MockPullRequestProcessorProcessCall wrap *gomock.Call
type MockPullRequestProcessorProcessCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockPullRequestProcessorProcessCall) Return(arg0 error) *MockPullRequestProcessorProcessCall {
c.Call = c.Call.Return(arg0)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockPullRequestProcessorProcessCall) Do(f func(*models.PullRequest) error) *MockPullRequestProcessorProcessCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockPullRequestProcessorProcessCall) DoAndReturn(f func(*models.PullRequest) error) *MockPullRequestProcessorProcessCall {
func (c *MockStateCheckerVerifyProjectStateCall) DoAndReturn(f func(*common.AutogitConfig) ([]*interfaces.PRToProcess, error)) *MockStateCheckerVerifyProjectStateCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View File

@@ -1,5 +1,7 @@
package main
//go:generate mockgen -source=pr_processor.go -destination=mock/pr_processor.go -typed
import (
"encoding/json"
"errors"
@@ -24,7 +26,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
title_refs := make([]string, 0, len(prset.PRs)-1)
refs := make([]string, 0, len(prset.PRs)-1)
prefix := ""
for _, pr := range prset.PRs {
if prset.IsPrjGitPR(pr.PR) {
continue
@@ -33,9 +34,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
// remove PRs that are not open from description
continue
}
if strings.HasPrefix(pr.PR.Title, "WIP:") {
prefix = "WIP: "
}
org, repo, idx := pr.PRComponents()
title_refs = append(title_refs, repo)
@@ -46,7 +44,7 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
slices.Sort(title_refs)
slices.Sort(refs)
title = prefix + "Forwarded PRs: " + strings.Join(title_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"
if prset.Config.ManualMergeOnly {
@@ -291,9 +289,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
git := pr.git
if len(prset.PRs) == 1 {
if len(PrjGitPR.RemoteName) == 0 {
PrjGitPR.RemoteName, _ = git.GitClone(common.DefaultGitPrj, "", PrjGitPR.PR.Base.Repo.SSHURL)
}
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitPR.PR.Head.Sha)
common.LogDebug("Only project git in PR. Nothing to update.")
return nil
@@ -505,7 +500,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
// make sure that prjgit is consistent and only submodules that are to be *updated*
// reset anything that changed that is not part of the prset
// package removals/additions are *not* counted here
org, repo, branch := config.GetPrjGit()
// TODO: this is broken...
if pr, err := prset.GetPrjGitPR(); err == nil && false {
common.LogDebug("Submodule parse begin")
@@ -554,7 +549,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
} else {
common.LogInfo("* No prjgit")
}
maintainers, err := common.FetchProjectMaintainershipData(Gitea, config)
maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch)
if err != nil {
return err
}
@@ -605,7 +600,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
common.LogError("merge error:", err)
}
} else {
err = prset.AssignReviewers(Gitea, maintainers)
prset.AssignReviewers(Gitea, maintainers)
}
return err
}
@@ -631,15 +626,6 @@ func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig)
return PRProcessor.Process(pr)
}
func (w *RequestProcessor) Process(pr *models.PullRequest) error {
configs, ok := w.configuredRepos[pr.Base.Repo.Owner.UserName]
if !ok {
common.LogError("*** Cannot find config for org:", pr.Base.Repo.Owner.UserName)
return fmt.Errorf("*** Cannot find config for org: %s", pr.Base.Repo.Owner.UserName)
}
return ProcesPullRequest(pr, configs)
}
func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
defer func() {
if r := recover(); r != nil {

View File

@@ -6,6 +6,7 @@ import (
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
"src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock"
)
@@ -16,7 +17,7 @@ func TestOpenPR(t *testing.T) {
Reviewers: []string{"reviewer1", "reviewer2"},
Branch: "branch",
Organization: "test",
GitProjectName: "prj#testing",
GitProjectName: "prj",
},
}
@@ -25,7 +26,6 @@ func TestOpenPR(t *testing.T) {
Number: 1,
Pull_Request: &common.PullRequest{
Id: 1,
Number: 1,
Base: common.Head{
Ref: "branch",
Sha: "testing",
@@ -53,56 +53,6 @@ func TestOpenPR(t *testing.T) {
},
}
modelPR := &models.PullRequest{
ID: 1,
Index: 1,
State: "open",
User: &models.User{UserName: "testuser"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{
Ref: "branch",
Sha: "testing",
Repo: &models.Repository{
Name: "testRepo",
Owner: &models.User{
UserName: "test",
},
},
},
Head: &models.PRBranchInfo{
Ref: "branch",
Sha: "testing",
Repo: &models.Repository{
Name: "testRepo",
Owner: &models.User{
UserName: "test",
},
},
},
}
mockCreatePR := &models.PullRequest{
ID: 2,
Index: 2,
Body: "Forwarded PRs: testRepo\n\nPR: test/testRepo!1",
User: &models.User{UserName: "testuser"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{
Name: "testing",
Repo: &models.Repository{
Name: "prjcopy",
Owner: &models.User{UserName: "test"},
},
},
Head: &models.PRBranchInfo{
Sha: "head",
},
}
CurrentUser = &models.User{
UserName: "testuser",
}
git := &common.GitHandlerImpl{
GitCommiter: "tester",
GitEmail: "test@suse.com",
@@ -110,47 +60,14 @@ func TestOpenPR(t *testing.T) {
t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
Gitea = mock_common.NewMockGitea(ctl)
git.GitPath = t.TempDir()
pr.config.GitProjectName = "testRepo#testing"
pr.config.GitProjectName = "testRepo"
event.Repository.Name = "testRepo"
mockGit := mock_common.NewMockGit(ctl)
pr.git = mockGit
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"testRepo": "testing"}, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitCatFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Label{}, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{
Owner: &models.User{UserName: "test"},
Name: "prjcopy",
SSHURL: "git@src.opensuse.org:test/prj.git",
}, nil).AnyTimes()
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCreatePR, nil, true).AnyTimes()
gitea.EXPECT().RequestReviews(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
if err := pr.Process(modelPR); err != nil {
if err := pr.Process(event); err != nil {
t.Error("Error PrjGit opened request. Should be no error.", err)
}
})
@@ -158,52 +75,43 @@ func TestOpenPR(t *testing.T) {
t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
event.Repository.Name = "testRepo"
pr.config.GitProjectName = "prjcopy#testing"
pr.config.GitProjectName = "prjcopy"
git.GitPath = t.TempDir()
setupGitForTests(t, git)
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCreatePR, nil, true).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().RequestReviews(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
prjgit := &models.Repository{
SSHURL: "./prj",
DefaultBranch: "testing",
}
giteaPR := &models.PullRequest{
Base: &models.PRBranchInfo{
Repo: &models.Repository{
Owner: &models.User{
UserName: "test",
},
Name: "testRepo",
},
},
User: &models.User{
UserName: "test",
},
}
// 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().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)
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
gitea.EXPECT().FetchMaintainershipFile("test", "prjcopy", "branch").Return(nil, "", repository.NewRepoGetRawFileNotFound())
mockGit := mock_common.NewMockGit(ctl)
pr.git = mockGit
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"testRepo": "testing"}, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitCatFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Label{}, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{
Owner: &models.User{UserName: "test"},
Name: "prjcopy",
SSHURL: "git@src.opensuse.org:test/prj.git",
}, nil).AnyTimes()
err := pr.Process(modelPR)
err := pr.Process(event)
if err != nil {
t.Error("error:", err)
}
@@ -212,61 +120,30 @@ func TestOpenPR(t *testing.T) {
t.Run("Cannot create prjgit repository", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
event.Repository.Name = "testRepo"
pr.config.GitProjectName = "prjcopy#testing"
pr.config.GitProjectName = "prjcopy"
git.GitPath = t.TempDir()
setupGitForTests(t, git)
failedErr := errors.New("Returned error here")
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr).AnyTimes()
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCreatePR, nil, true).AnyTimes()
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(nil, failedErr)
mockGit := mock_common.NewMockGit(ctl)
pr.git = mockGit
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"testRepo": "testing"}, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitCatFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().RequestReviews(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Label{}, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{
Owner: &models.User{UserName: "test"},
Name: "prjcopy",
SSHURL: "git@src.opensuse.org:test/prj.git",
}, nil).AnyTimes()
err := pr.Process(modelPR)
if err != nil {
err := pr.Process(event)
if err != failedErr {
t.Error("error:", err)
}
})
t.Run("Cannot create PR", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
event.Repository.Name = "testRepo"
pr.config.GitProjectName = "prjcopy#testing"
pr.config.GitProjectName = "prjcopy"
git.GitPath = t.TempDir()
setupGitForTests(t, git)
@@ -275,37 +152,10 @@ func TestOpenPR(t *testing.T) {
DefaultBranch: "testing",
}
failedErr := errors.New("Returned error here")
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjgit, nil).AnyTimes()
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr, false)
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)
mockGit := mock_common.NewMockGit(ctl)
pr.git = mockGit
gitea.EXPECT().RequestReviews(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"testRepo": "testing"}, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitCatFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Label{}, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{
Owner: &models.User{UserName: "test"},
Name: "prjcopy",
SSHURL: "git@src.opensuse.org:test/prj.git",
}, nil).AnyTimes()
err := pr.Process(modelPR)
err := pr.Process(event)
if err != failedErr {
t.Error("error:", err)
}
@@ -313,54 +163,44 @@ func TestOpenPR(t *testing.T) {
t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
event.Repository.Name = "testRepo"
pr.config.GitProjectName = "prjcopy#testing"
pr.config.GitProjectName = "prjcopy"
git.GitPath = t.TempDir()
setupGitForTests(t, git)
prjgit := &models.Repository{
Name: "SomeRepo",
Owner: &models.User{
UserName: "org",
},
SSHURL: "./prj",
DefaultBranch: "testing",
}
giteaPR := &models.PullRequest{
Base: &models.PRBranchInfo{
Repo: prjgit,
},
Index: 13,
User: &models.User{
UserName: "test",
},
}
failedErr := errors.New("Returned error here")
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
gitea.EXPECT().GetPullRequestReviews("org", "SomeRepo", int64(13)).Return([]*models.PullReview{}, nil)
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr)
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(mockCreatePR, nil, true).AnyTimes()
gitea.EXPECT().RequestReviews(gomock.Any(), gomock.Any()).Return(nil, failedErr).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
gitea.EXPECT().FetchMaintainershipFile("test", "prjcopy", "branch").Return(nil, "", repository.NewRepoGetRawFileNotFound())
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
mockGit := mock_common.NewMockGit(ctl)
pr.git = mockGit
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"testRepo": "testing"}, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitCatFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(modelPR, nil).AnyTimes()
gitea.EXPECT().SetLabels(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Label{}, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "git@src.opensuse.org:test/prj.git"}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{
Owner: &models.User{UserName: "test"},
Name: "prjcopy",
SSHURL: "git@src.opensuse.org:test/prj.git",
}, nil).AnyTimes()
err := pr.Process(modelPR)
if err != nil {
err := pr.Process(event)
if errors.Unwrap(err) != failedErr {
t.Error("error:", err)
}
})

View File

@@ -1,7 +1,12 @@
package main
/*
import (
"bytes"
"errors"
"log"
"os"
"path"
"strings"
"testing"
"go.uber.org/mock/gomock"
@@ -11,147 +16,217 @@ import (
)
func TestSyncPR(t *testing.T) {
config := &common.AutogitConfig{
Reviewers: []string{"reviewer1", "reviewer2"},
Branch: "testing",
Organization: "test-org",
GitProjectName: "test-prj#testing",
pr := PRProcessor{
config: &common.AutogitConfig{
Reviewers: []string{"reviewer1", "reviewer2"},
Branch: "testing",
Organization: "test",
GitProjectName: "prj",
},
}
git := &common.GitHandlerImpl{
GitCommiter: "tester",
GitEmail: "test@suse.com",
GitPath: t.TempDir(),
}
processor := &PRProcessor{
config: config,
git: git,
event := &common.PullRequestWebhookEvent{
Action: "syncronized",
Number: 42,
Pull_Request: &common.PullRequest{
Number: 42,
Base: common.Head{
Ref: "branch",
Sha: "8a6a69a4232cabda04a4d9563030aa888ff5482f75aa4c6519da32a951a072e2",
Repo: &common.Repository{
Name: "testRepo",
Owner: &common.Organization{
Username: pr.config.Organization,
},
Default_Branch: "main1",
},
},
Head: common.Head{
Ref: "branch",
Sha: "11eb36d5a58d7bb376cac59ac729a1986c6a7bfc63e7818e14382f545ccda985",
Repo: &common.Repository{
Name: "testRepo",
Default_Branch: "main1",
},
},
},
Repository: &common.Repository{
Owner: &common.Organization{
Username: pr.config.Organization,
},
},
}
modelPR := &models.PullRequest{
Index: 42,
Body: "PR: test-org/test-prj#24",
Body: "PR: test/prj#24",
Base: &models.PRBranchInfo{
Ref: "main",
Ref: "branch",
Sha: "8a6a69a4232cabda04a4d9563030aa888ff5482f75aa4c6519da32a951a072e2",
Repo: &models.Repository{
Name: "test-repo",
Owner: &models.User{UserName: "test-org"},
DefaultBranch: "main",
Name: "testRepo",
Owner: &models.User{
UserName: "test",
},
DefaultBranch: "main1",
},
},
Head: &models.PRBranchInfo{
Ref: "branch",
Sha: "11eb36d5a58d7bb376cac59ac729a1986c6a7bfc63e7818e14382f545ccda985",
Repo: &models.Repository{
Name: "test-repo",
Owner: &models.User{UserName: "test-org"},
DefaultBranch: "main",
Name: "testRepo",
Owner: &models.User{
UserName: "test",
},
DefaultBranch: "main1",
},
},
}
PrjGitPR := &models.PullRequest{
Title: "some pull request",
Body: "PR: test-org/test-repo#42",
Body: "PR: test/testRepo#42",
Index: 24,
Base: &models.PRBranchInfo{
Ref: "testing",
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
SSHURL: "url",
},
},
Head: &models.PRBranchInfo{
Name: "PR_test-repo#42",
Name: "testing",
Sha: "db8adab91edb476b9762097d10c6379aa71efd6b60933a1c0e355ddacf419a95",
Repo: &models.Repository{
SSHURL: "url",
SSHURL: "./prj",
},
},
}
t.Run("PR_sync_request_against_PrjGit_==_no_action", func(t *testing.T) {
git := &common.GitHandlerImpl{
GitCommiter: "tester",
GitEmail: "test@suse.com",
}
t.Run("PR sync request against PrjGit == no action", func(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
Gitea = mock_common.NewMockGitea(ctl)
// Common expectations for FetchPRSet and downstream checks
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
git.GitPath = t.TempDir()
prjGitRepoPR := &models.PullRequest{
Index: 100,
Base: &models.PRBranchInfo{
Ref: "testing",
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Ref: "branch",
},
pr.config.GitProjectName = "testRepo"
event.Repository.Name = "testRepo"
if err := pr.Process(event); err != nil {
t.Error("Error PrjGit sync request. Should be no error.", err)
}
})
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjGitRepoPR, nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
t.Run("Missing submodule in prjgit", func(t *testing.T) {
ctl := gomock.NewController(t)
mock := mock_common.NewMockGitea(ctl)
if err := processor.Process(prjGitRepoPR); err != nil {
t.Errorf("Expected nil error for PrjGit sync request, got %v", err)
pr.gitea = mock
git.GitPath = t.TempDir()
pr.config.GitProjectName = "prjGit"
event.Repository.Name = "testRepo"
setupGitForTests(t, git)
oldSha := PrjGitPR.Head.Sha
defer func() { PrjGitPR.Head.Sha = oldSha }()
PrjGitPR.Head.Sha = "ab8adab91edb476b9762097d10c6379aa71efd6b60933a1c0e355ddacf419a95"
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
err := pr.Process(event)
if err == nil || err.Error() != "Cannot fetch submodule commit id in prjgit for 'testRepo'" {
t.Error("Invalid error received.", err)
}
})
t.Run("Missing PrjGit PR for the sync", func(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mock := mock_common.NewMockGitea(ctl)
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
pr.gitea = mock
git.GitPath = t.TempDir()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("not found")).AnyTimes()
pr.config.GitProjectName = "prjGit"
event.Repository.Name = "tester"
err := processor.Process(modelPR)
// It should fail because it can't find the project PR linked in body
if err == nil {
t.Errorf("Expected error for missing project PR, got nil")
setupGitForTests(t, git)
expectedErr := errors.New("Missing PR should throw error")
mock.EXPECT().GetPullRequest(config.Organization, "tester", event.Pull_Request.Number).Return(modelPR, expectedErr)
err := pr.Process(event, git, config)
if err == nil || errors.Unwrap(err) != expectedErr {
t.Error("Invalid error received.", err)
}
})
t.Run("PR sync", func(t *testing.T) {
var b bytes.Buffer
w := log.Writer()
log.SetOutput(&b)
defer log.SetOutput(w)
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mock := mock_common.NewMockGitea(ctl)
Gitea = mock
git.GitPath = t.TempDir()
pr.config.GitProjectName = "prjGit"
event.Repository.Name = "testRepo"
setupGitForTests(t, git)
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(PrjGitPR, nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
// mock.EXPECT().GetAssociatedPrjGitPR(event).Return(PrjGitPR, nil)
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
// For UpdatePrjGitPR
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := pr.Process(event)
err := processor.Process(modelPR)
if err != nil {
t.Errorf("Unexpected error: %v", err)
t.Error("Invalid error received.", err)
t.Error(b.String())
}
// check that we actually created the branch in the prjgit
id, ok := git.GitSubmoduleCommitId("prj", "testRepo", "c097b9d1d69892d0ef2afa66d4e8abf0a1612c6f95d271a6e15d6aff1ad2854c")
if id != "11eb36d5a58d7bb376cac59ac729a1986c6a7bfc63e7818e14382f545ccda985" || !ok {
t.Error("Failed creating PR")
t.Error(b.String())
}
// does nothing on next sync of already synced data -- PR is updated
os.RemoveAll(path.Join(git.GitPath, common.DefaultGitPrj))
mock.EXPECT().GetPullRequest(pr.config.Organization, "testRepo", event.Pull_Request.Number).Return(modelPR, nil)
mock.EXPECT().GetPullRequest(pr.config.Organization, "prj", int64(24)).Return(PrjGitPR, nil)
err = pr.Process(event)
if err != nil {
t.Error("Invalid error received.", err)
t.Error(b.String())
}
// check that we actually created the branch in the prjgit
id, ok = git.GitSubmoduleCommitId("prj", "testRepo", "c097b9d1d69892d0ef2afa66d4e8abf0a1612c6f95d271a6e15d6aff1ad2854c")
if id != "11eb36d5a58d7bb376cac59ac729a1986c6a7bfc63e7818e14382f545ccda985" || !ok {
t.Error("Failed creating PR")
t.Error(b.String())
}
if id, err := git.GitBranchHead("prj", "PR_testRepo#42"); id != "c097b9d1d69892d0ef2afa66d4e8abf0a1612c6f95d271a6e15d6aff1ad2854c" || err != nil {
t.Error("no branch?", err)
t.Error(b.String())
}
if !strings.Contains(b.String(), "commitID already match - nothing to do") {
// os.CopyFS("/tmp/test", os.DirFS(git.GitPath))
t.Log(b.String())
}
})
}
*/

View File

@@ -1,945 +0,0 @@
package main
import (
"errors"
"fmt"
"strings"
"testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock"
)
func TestPrjGitDescription(t *testing.T) {
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
State: "open",
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "pkg-a",
Owner: &models.User{UserName: "test-org"},
},
},
},
},
{
PR: &models.PullRequest{
State: "open",
Index: 2,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "pkg-b",
Owner: &models.User{UserName: "test-org"},
},
},
},
},
},
}
GitAuthor = "Bot"
title, desc := PrjGitDescription(prset)
expectedTitle := "Forwarded PRs: pkg-a, pkg-b"
if title != expectedTitle {
t.Errorf("Expected title %q, got %q", expectedTitle, title)
}
if !strings.Contains(desc, "PR: test-org/pkg-a!1") || !strings.Contains(desc, "PR: test-org/pkg-b!2") {
t.Errorf("Description missing PR references: %s", desc)
}
}
func TestAllocatePRProcessor(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
configs := common.AutogitConfigs{
{
Organization: "test-org",
Branch: "main",
GitProjectName: "test-prj#main",
},
}
req := &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "test-repo",
Owner: &models.User{UserName: "test-org"},
},
},
}
mockGitGen.EXPECT().CreateGitHandler("test-org").Return(mockGit, nil)
mockGit.EXPECT().GetPath().Return("/tmp/test")
processor, err := AllocatePRProcessor(req, configs)
if err != nil {
t.Fatalf("AllocatePRProcessor failed: %v", err)
}
if processor.config.Organization != "test-org" {
t.Errorf("Expected organization test-org, got %s", processor.config.Organization)
}
}
func TestAllocatePRProcessor_Failures(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
configs := common.AutogitConfigs{} // Empty configs
req := &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "test-repo",
Owner: &models.User{UserName: "test-org"},
},
},
}
t.Run("Missing config", func(t *testing.T) {
processor, err := AllocatePRProcessor(req, configs)
if err == nil || err.Error() != "Cannot find config for PR" {
t.Errorf("Expected 'Cannot find config for PR' error, got %v", err)
}
if processor != nil {
t.Error("Expected nil processor")
}
})
t.Run("GitHandler failure", func(t *testing.T) {
validConfigs := common.AutogitConfigs{
{
Organization: "test-org",
Branch: "main",
GitProjectName: "test-prj#main",
},
}
mockGitGen.EXPECT().CreateGitHandler("test-org").Return(nil, errors.New("git error"))
processor, err := AllocatePRProcessor(req, validConfigs)
if err == nil || !strings.Contains(err.Error(), "Error allocating GitHandler") {
t.Errorf("Expected GitHandler error, got %v", err)
}
if processor != nil {
t.Error("Expected nil processor")
}
})
}
func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
t.Run("GitSubmoduleList failure", func(t *testing.T) {
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(nil, errors.New("list error"))
err := processor.SetSubmodulesToMatchPRSet(&common.PRSet{})
if err == nil || err.Error() != "list error" {
t.Errorf("Expected 'list error', got %v", err)
}
})
}
func TestSetSubmodulesToMatchPRSet(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
State: "open",
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "pkg-a",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Sha: "new-sha",
},
},
},
},
}
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "old-sha"}, nil)
// Expect submodule update and commit
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitStatus(gomock.Any()).Return([]common.GitStatusData{}, nil).AnyTimes()
err := processor.SetSubmodulesToMatchPRSet(prset)
if err != nil {
t.Errorf("SetSubmodulesToMatchPRSet failed: %v", err)
}
}
func TestRebaseAndSkipSubmoduleCommits(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
RemoteName: "origin",
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "main",
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
},
},
},
},
},
}
t.Run("Clean rebase", func(t *testing.T) {
mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(nil)
err := processor.RebaseAndSkipSubmoduleCommits(prset, "main")
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
t.Run("Rebase with submodule conflict - skip", func(t *testing.T) {
// First rebase fails
mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(errors.New("conflict"))
// Status shows submodule change
mockGit.EXPECT().GitStatus(common.DefaultGitPrj).Return([]common.GitStatusData{
{SubmoduleChanges: "S..."},
}, nil)
// Skip called
mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "--skip").Return(nil)
err := processor.RebaseAndSkipSubmoduleCommits(prset, "main")
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
t.Run("Rebase with real conflict - abort", func(t *testing.T) {
mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(errors.New("conflict"))
// Status shows real change
mockGit.EXPECT().GitStatus(common.DefaultGitPrj).Return([]common.GitStatusData{
{SubmoduleChanges: "N..."},
}, nil)
// Abort called
mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort").Return()
err := processor.RebaseAndSkipSubmoduleCommits(prset, "main")
if err == nil || !strings.Contains(err.Error(), "Unexpected conflict in rebase") {
t.Errorf("Expected 'Unexpected conflict' error, got %v", err)
}
})
}
func TestUpdatePrjGitPR(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
CurrentUser = &models.User{UserName: "bot"}
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
t.Run("Only project git in PR", func(t *testing.T) {
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
RemoteName: "origin",
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "main",
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Sha: "sha1",
},
},
},
},
}
mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "fetch", "origin", "sha1")
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Only project git in PR - needs clone", func(t *testing.T) {
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
RemoteName: "", // Triggers GitClone
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "main",
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
SSHURL: "ssh://git@example.com/test-prj.git",
},
},
Head: &models.PRBranchInfo{
Sha: "sha1",
},
},
},
},
}
mockGit.EXPECT().GitClone(common.DefaultGitPrj, "", "ssh://git@example.com/test-prj.git").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "fetch", "origin", "sha1")
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("PR on another remote", func(t *testing.T) {
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
RemoteName: "origin",
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "main",
RepoID: 1,
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
SSHURL: "url",
},
},
Head: &models.PRBranchInfo{
Name: "feature",
RepoID: 2, // Different RepoID
Sha: "sha1",
},
},
},
{
PR: &models.PullRequest{
Base: &models.PRBranchInfo{
Name: "other",
Repo: &models.Repository{
Name: "other-pkg",
Owner: &models.User{UserName: "test-org"},
},
},
},
},
},
}
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("remote2", nil)
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", "remote2", "sha1")
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "checkout", "sha1")
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Standard update with rebase and force push", func(t *testing.T) {
prset := &common.PRSet{
Config: config,
BotUser: "bot",
PRs: []*common.PRInfo{
{
RemoteName: "origin",
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Mergeable: false, // Triggers rebase
Base: &models.PRBranchInfo{
Name: "main",
RepoID: 1,
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
SSHURL: "url",
},
},
Head: &models.PRBranchInfo{
Name: "PR_branch",
RepoID: 1,
Sha: "old-head",
},
},
},
{
PR: &models.PullRequest{
State: "open",
Base: &models.PRBranchInfo{
Repo: &models.Repository{
Name: "pkg-a",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
},
},
},
}
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any())
// Rebase expectations
mockGit.EXPECT().GitExec(gomock.Any(), "rebase", gomock.Any()).Return(nil)
// First call returns old-head, second returns new-head to trigger push
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("old-head", nil).Times(1)
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("new-head", nil).Times(1)
// SetSubmodulesToMatchPRSet expectations
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "old-pkg-sha"}, nil)
// Catch all GitExec calls with any number of arguments up to 5
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes()
// UpdatePullRequest expectation
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("isPrTitleSame logic", func(t *testing.T) {
longTitle := strings.Repeat("a", 251) + "..."
prset := &common.PRSet{
Config: config,
BotUser: "bot",
PRs: []*common.PRInfo{
{
RemoteName: "origin",
PR: &models.PullRequest{
User: &models.User{UserName: "bot"},
Title: longTitle,
Base: &models.PRBranchInfo{
Name: "main",
RepoID: 1,
Repo: &models.Repository{
Name: "test-prj",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{
Name: "PR_branch",
RepoID: 1,
Sha: "head",
},
},
},
{
PR: &models.PullRequest{
State: "open",
Base: &models.PRBranchInfo{
Repo: &models.Repository{
Name: "pkg-a",
Owner: &models.User{UserName: "test-org"},
},
},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
},
},
},
}
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any())
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "pkg-sha"}, nil)
// mockGit.EXPECT().GitExec(...) not called because no push (headCommit == newHeadCommit)
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
err := processor.UpdatePrjGitPR(prset)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
}
func TestCreatePRjGitPR_Integration(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{
PR: &models.PullRequest{
State: "open",
Base: &models.PRBranchInfo{
Repo: &models.Repository{Name: "pkg-a", Owner: &models.User{UserName: "test-org"}},
},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
},
},
},
}
t.Run("Create new project PR with label", func(t *testing.T) {
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head-sha", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes()
prjPR := &models.PullRequest{
Index: 10,
Base: &models.PRBranchInfo{
Name: "main",
RepoID: 1,
Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}},
},
Head: &models.PRBranchInfo{
Sha: "prj-head-sha",
},
}
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{Owner: &models.User{UserName: "test-org"}}, nil).AnyTimes()
// CreatePullRequestIfNotExist returns isNew=true
gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil, true).AnyTimes()
// Expect SetLabels to be called for new PR
gitea.EXPECT().SetLabels("test-org", gomock.Any(), int64(10), gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any()).Return().AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes()
err := processor.CreatePRjGitPR("PR_branch", prset)
if err != nil {
t.Errorf("CreatePRjGitPR failed: %v", err)
}
})
}
func TestMultiPackagePRSet(t *testing.T) {
GitAuthor = "Bot" // Ensure non-empty author
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
prset := &common.PRSet{
Config: config,
}
for i := 1; i <= 5; i++ {
name := fmt.Sprintf("pkg-%d", i)
prset.PRs = append(prset.PRs, &common.PRInfo{
PR: &models.PullRequest{
Index: int64(i),
State: "open",
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{Name: name, Owner: &models.User{UserName: "test-org"}},
},
},
})
}
GitAuthor = "Bot"
title, desc := PrjGitDescription(prset)
// PrjGitDescription generates title like "Forwarded PRs: pkg-1, pkg-2, pkg-3, pkg-4, pkg-5"
for i := 1; i <= 5; i++ {
name := fmt.Sprintf("pkg-%d", i)
if !strings.Contains(title, name) {
t.Errorf("Title missing package %s: %s", name, title)
}
}
for i := 1; i <= 5; i++ {
ref := fmt.Sprintf("PR: test-org/pkg-%d!%d", i, i)
if !strings.Contains(desc, ref) {
t.Errorf("Description missing reference %s", ref)
}
}
}
func TestPRProcessor_Process_EdgeCases(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
CurrentUser = &models.User{UserName: "bot"}
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
processor := &PRProcessor{
config: config,
git: mockGit,
}
t.Run("Merged project PR - update downstream", func(t *testing.T) {
prjPR := &models.PullRequest{
State: "closed",
HasMerged: true,
Index: 100,
Base: &models.PRBranchInfo{
Name: "main",
Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}, SSHURL: "url"},
},
Head: &models.PRBranchInfo{Name: "PR_branch"},
}
pkgPR := &models.PullRequest{
State: "open",
Index: 1,
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg-a", Owner: &models.User{UserName: "test-org"}}},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
}
prset := &common.PRSet{
BotUser: "bot",
Config: config,
PRs: []*common.PRInfo{
{PR: prjPR},
{PR: pkgPR},
},
}
_ = prset // Suppress unused for now if it's really unused, but it's likely used by common.FetchPRSet internally if we weren't mocking everything
// Mock expectations for Process setup
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
// Mock maintainership file calls
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
// Mock expectations for the merged path
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil)
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"pkg-a": "old-sha"}, nil).AnyTimes()
gitea.EXPECT().GetRecentCommits(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Commit{{SHA: "pkg-sha"}}, nil).AnyTimes()
// Downstream update expectations
gitea.EXPECT().AddComment(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
gitea.EXPECT().ManualMergePR(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
err := processor.Process(pkgPR)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Superfluous PR - close it", func(t *testing.T) {
prjPR := &models.PullRequest{
State: "open",
Index: 100,
User: &models.User{UserName: "bot"},
Body: "Forwarded PRs: \n", // No PRs linked
Base: &models.PRBranchInfo{
Name: "main",
Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}},
},
Head: &models.PRBranchInfo{Name: "PR_branch", Sha: "head-sha"},
MergeBase: "base-sha",
}
prset := &common.PRSet{
BotUser: "bot",
Config: config,
PRs: []*common.PRInfo{
{PR: prjPR},
},
}
_ = prset
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{Owner: &models.User{UserName: "test-org"}}, nil).AnyTimes()
// Mock maintainership file calls
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
// Standard update calls within Process
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any()).AnyTimes()
mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head-sha", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes()
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
// Diff check for superfluous
mockGit.EXPECT().GitDiff(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes()
// Expectations for closing
gitea.EXPECT().AddComment(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := processor.Process(prjPR)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
}
func TestVerifyRepositoryConfiguration(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
repo := &models.Repository{
Name: "test-repo",
Owner: &models.User{
UserName: "test-user",
},
AutodetectManualMerge: true,
AllowManualMerge: true,
}
t.Run("Config already correct", func(t *testing.T) {
err := verifyRepositoryConfiguration(repo)
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
t.Run("Config incorrect - trigger update", func(t *testing.T) {
repo.AllowManualMerge = false
gitea.EXPECT().SetRepoOptions("test-user", "test-repo", true).Return(&models.Repository{}, nil)
err := verifyRepositoryConfiguration(repo)
if err != nil {
t.Errorf("Expected nil error, got %v", err)
}
})
t.Run("Update failure", func(t *testing.T) {
repo.AllowManualMerge = false
expectedErr := errors.New("update failed")
gitea.EXPECT().SetRepoOptions("test-user", "test-repo", true).Return(nil, expectedErr)
err := verifyRepositoryConfiguration(repo)
if err != expectedErr {
t.Errorf("Expected %v, got %v", expectedErr, err)
}
})
}
func TestProcessFunc(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mockGit := mock_common.NewMockGit(ctl)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
reqProc := &RequestProcessor{
configuredRepos: map[string][]*common.AutogitConfig{
"test-org": {config},
},
}
modelPR := &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "test-repo",
DefaultBranch: "main",
Owner: &models.User{UserName: "test-org"},
},
},
}
t.Run("PullRequestWebhookEvent", func(t *testing.T) {
event := &common.PullRequestWebhookEvent{
Pull_Request: &common.PullRequest{
Number: 1,
Base: common.Head{
Ref: "main",
Repo: &common.Repository{
Name: "test-repo",
Owner: &common.Organization{
Username: "test-org",
},
},
},
},
}
gitea.EXPECT().GetPullRequest("test-org", "test-repo", int64(1)).Return(modelPR, nil).AnyTimes()
// AllocatePRProcessor and ProcesPullRequest calls inside
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil)
mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes()
mockGit.EXPECT().Close().Return(nil)
// Expect Process calls (mocked via mockGit mostly)
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := reqProc.ProcessFunc(&common.Request{Data: event})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("IssueCommentWebhookEvent", func(t *testing.T) {
event := &common.IssueCommentWebhookEvent{
Issue: &common.IssueDetail{Number: 1},
Repository: &common.Repository{
Name: "test-repo",
Owner: &common.Organization{Username: "test-org"},
},
}
gitea.EXPECT().GetPullRequest("test-org", "test-repo", int64(1)).Return(modelPR, nil).AnyTimes()
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
err := reqProc.ProcessFunc(&common.Request{Data: event})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Recursion limit", func(t *testing.T) {
reqProc.recursive = 3
err := reqProc.ProcessFunc(&common.Request{})
if err != nil {
t.Errorf("Expected nil error on recursion limit, got %v", err)
}
if reqProc.recursive != 3 {
t.Errorf("Expected recursive to be 3, got %d", reqProc.recursive)
}
reqProc.recursive = 0 // Reset
})
t.Run("Invalid data format", func(t *testing.T) {
err := reqProc.ProcessFunc(&common.Request{Data: nil})
if err == nil || !strings.Contains(err.Error(), "Invalid data format") {
t.Errorf("Expected 'Invalid data format' error, got %v", err)
}
})
}

View File

@@ -10,6 +10,7 @@ import (
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
"src.opensuse.org/autogits/workflow-pr/interfaces"
)
type DefaultStateChecker struct {
@@ -17,11 +18,11 @@ type DefaultStateChecker struct {
checkOnStart bool
checkInterval time.Duration
processor PullRequestProcessor
i StateChecker
processor *RequestProcessor
i interfaces.StateChecker
}
func CreateDefaultStateChecker(checkOnStart bool, processor PullRequestProcessor, gitea common.Gitea, interval time.Duration) *DefaultStateChecker {
func CreateDefaultStateChecker(checkOnStart bool, processor *RequestProcessor, gitea common.Gitea, interval time.Duration) *DefaultStateChecker {
var s = &DefaultStateChecker{
checkInterval: interval,
checkOnStart: checkOnStart,
@@ -53,7 +54,7 @@ func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.A
return ProcesPullRequest(pr, common.AutogitConfigs{config})
}
func PrjGitSubmoduleCheck(config *common.AutogitConfig, git common.Git, repo string, submodules map[string]string) (prsToProcess []*PRToProcess, err error) {
func PrjGitSubmoduleCheck(config *common.AutogitConfig, git common.Git, repo string, submodules map[string]string) (prsToProcess []*interfaces.PRToProcess, err error) {
nextSubmodule:
for sub, commitID := range submodules {
common.LogDebug(" + checking", sub, commitID)
@@ -73,7 +74,7 @@ nextSubmodule:
branch = repo.DefaultBranch
}
prsToProcess = append(prsToProcess, &PRToProcess{
prsToProcess = append(prsToProcess, &interfaces.PRToProcess{
Org: config.Organization,
Repo: submoduleName,
Branch: branch,
@@ -116,7 +117,7 @@ nextSubmodule:
return prsToProcess, nil
}
func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) ([]*PRToProcess, error) {
func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) ([]*interfaces.PRToProcess, error) {
defer func() {
if r := recover(); r != nil {
common.LogError("panic caught")
@@ -127,7 +128,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
}
}()
prsToProcess := []*PRToProcess{}
prsToProcess := []*interfaces.PRToProcess{}
prjGitOrg, prjGitRepo, prjGitBranch := config.GetPrjGit()
common.LogInfo(" checking", prjGitOrg+"/"+prjGitRepo+"#"+prjGitBranch)
@@ -147,7 +148,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
_, err = git.GitClone(prjGitRepo, prjGitBranch, repo.SSHURL)
common.PanicOnError(err)
prsToProcess = append(prsToProcess, &PRToProcess{
prsToProcess = append(prsToProcess, &interfaces.PRToProcess{
Org: prjGitOrg,
Repo: prjGitRepo,
Branch: prjGitBranch,
@@ -155,8 +156,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
submodules, err := git.GitSubmoduleList(prjGitRepo, "HEAD")
// forward any package-gits referred by the project git, but don't go back
subPrs, err := PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
return append(prsToProcess, subPrs...), err
return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
}
func (s *DefaultStateChecker) CheckRepos() {
@@ -170,8 +170,7 @@ func (s *DefaultStateChecker) CheckRepos() {
}
}()
processor := s.processor.(*RequestProcessor)
for org, configs := range processor.configuredRepos {
for org, configs := range s.processor.configuredRepos {
for _, config := range configs {
if s.checkInterval > 0 {
sleepInterval := (s.checkInterval - s.checkInterval/2) + time.Duration(rand.Int63n(int64(s.checkInterval)))

View File

@@ -1,338 +0,0 @@
package main
import (
"errors"
"strings"
"testing"
"time"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock"
)
func TestPrjGitSubmoduleCheck(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit := mock_common.NewMockGit(ctl)
Gitea = gitea
config := &common.AutogitConfig{
Organization: "test-org",
Branch: "main",
}
t.Run("Submodule up to date", func(t *testing.T) {
submodules := map[string]string{
"pkg-a": "sha-1",
}
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return([]*models.Commit{
{SHA: "sha-1"},
}, nil)
prs, err := PrjGitSubmoduleCheck(config, mockGit, "prj-repo", submodules)
if err != nil {
t.Fatalf("PrjGitSubmoduleCheck failed: %v", err)
}
if len(prs) != 1 || prs[0].Repo != "pkg-a" {
t.Errorf("Expected 1 PR to process for pkg-a, got %v", prs)
}
})
t.Run("Submodule behind branch", func(t *testing.T) {
submodules := map[string]string{
"pkg-a": "sha-old",
}
// sha-old is the second commit, meaning it's behind the head (sha-new)
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return([]*models.Commit{
{SHA: "sha-new"},
{SHA: "sha-old"},
}, nil)
prs, err := PrjGitSubmoduleCheck(config, mockGit, "prj-repo", submodules)
if err != nil {
t.Fatalf("PrjGitSubmoduleCheck failed: %v", err)
}
if len(prs) != 1 || prs[0].Repo != "pkg-a" {
t.Errorf("Expected 1 PR to process for pkg-a, got %v", prs)
}
})
t.Run("Submodule with new commits - advance branch", func(t *testing.T) {
submodules := map[string]string{
"pkg-a": "sha-very-new",
}
// sha-very-new is NOT in recent commits
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return([]*models.Commit{
{SHA: "sha-new"},
{SHA: "sha-old"},
}, nil)
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExecWithOutputOrPanic(gomock.Any(), "rev-list", gomock.Any(), gomock.Any()).Return("commit-1\n").AnyTimes()
mockGit.EXPECT().GitExecWithOutputOrPanic(gomock.Any(), "remote", gomock.Any(), gomock.Any(), gomock.Any()).Return("https://src.opensuse.org/test-org/pkg-a.git\n").AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any()).Return().AnyTimes()
prs, err := PrjGitSubmoduleCheck(config, mockGit, "prj-repo", submodules)
if err != nil {
t.Fatalf("PrjGitSubmoduleCheck failed: %v", err)
}
if len(prs) != 1 || prs[0].Repo != "pkg-a" {
t.Errorf("Expected 1 PR to process for pkg-a, got %v", prs)
}
})
}
func TestPrjGitSubmoduleCheck_Failures(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mockGit := mock_common.NewMockGit(ctl)
Gitea = gitea
config := &common.AutogitConfig{
Organization: "test-org",
Branch: "main",
}
t.Run("GetRecentCommits failure", func(t *testing.T) {
submodules := map[string]string{"pkg-a": "sha-1"}
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return(nil, errors.New("gitea error"))
_, err := PrjGitSubmoduleCheck(config, mockGit, "prj-repo", submodules)
if err == nil || !strings.Contains(err.Error(), "Error fetching recent commits") {
t.Errorf("Expected gitea error, got %v", err)
}
})
t.Run("SSH translation failure", func(t *testing.T) {
submodules := map[string]string{"pkg-a": "sha-new"}
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return([]*models.Commit{{SHA: "sha-old"}}, nil)
mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any()).Return().AnyTimes()
mockGit.EXPECT().GitExecWithOutputOrPanic(gomock.Any(), "rev-list", gomock.Any(), gomock.Any()).Return("commit-1\n").AnyTimes()
// Return invalid URL that cannot be translated to SSH
mockGit.EXPECT().GitExecWithOutputOrPanic(gomock.Any(), "remote", gomock.Any(), gomock.Any(), gomock.Any()).Return("not-a-url").AnyTimes()
_, err := PrjGitSubmoduleCheck(config, mockGit, "prj-repo", submodules)
if err == nil || !strings.Contains(err.Error(), "Cannot traslate HTTPS git URL to SSH_URL") {
t.Errorf("Expected SSH translation error, got %v", err)
}
})
}
func TestPullRequestToEventState(t *testing.T) {
tests := []struct {
state models.StateType
expected string
}{
{"open", "opened"},
{"closed", "closed"},
{"merged", "merged"},
}
for _, tt := range tests {
if got := pullRequestToEventState(tt.state); got != tt.expected {
t.Errorf("pullRequestToEventState(%v) = %v; want %v", tt.state, got, tt.expected)
}
}
}
func TestDefaultStateChecker_ProcessPR(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mockGit := mock_common.NewMockGit(ctl)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
checker := CreateDefaultStateChecker(false, nil, gitea, time.Duration(0))
pr := &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "test-repo",
DefaultBranch: "main",
Owner: &models.User{UserName: "test-org"},
},
},
}
mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil)
mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes()
mockGit.EXPECT().Close().Return(nil)
// Expectations for ProcesPullRequest
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(pr, nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
err := checker.ProcessPR(pr, config)
if err != nil {
t.Errorf("ProcessPR failed: %v", err)
}
}
func TestDefaultStateChecker_VerifyProjectState(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mockGit := mock_common.NewMockGit(ctl)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
checker := CreateDefaultStateChecker(false, nil, gitea, 0)
t.Run("VerifyProjectState success", func(t *testing.T) {
mockGitGen.EXPECT().CreateGitHandler("test-org").Return(mockGit, nil)
mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes()
mockGit.EXPECT().Close().Return(nil)
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), "test-org", "test-prj").Return(&models.Repository{SSHURL: "url"}, nil)
mockGit.EXPECT().GitClone("test-prj", "main", "url").Return("origin", nil)
mockGit.EXPECT().GitSubmoduleList("test-prj", "HEAD").Return(map[string]string{"pkg-a": "sha-1"}, nil)
// PrjGitSubmoduleCheck call inside
gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{DefaultBranch: "main"}, nil).AnyTimes()
// Return commits where sha-1 is NOT present
gitea.EXPECT().GetRecentCommits("test-org", "pkg-a", "main", int64(10)).Return([]*models.Commit{
{SHA: "sha-new"},
}, nil).AnyTimes()
// rev-list returns empty string, so no new commits on branch relative to submodule commitID
mockGit.EXPECT().GitExecWithOutputOrPanic(gomock.Any(), "rev-list", gomock.Any(), "sha-1").Return("").AnyTimes()
// And ensure submodule update is called
mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "submodule", "update", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes()
prs, err := checker.VerifyProjectState(config)
if err != nil {
t.Errorf("VerifyProjectState failed: %v", err)
}
// Expect project git + pkg-a
if len(prs) != 2 {
t.Errorf("Expected 2 PRs to process, got %d", len(prs))
}
})
t.Run("VerifyProjectState failure - CreateRepository", func(t *testing.T) {
mockGitGen.EXPECT().CreateGitHandler("test-org").Return(mockGit, nil)
mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes()
mockGit.EXPECT().Close().Return(nil)
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), "test-org", "test-prj").Return(nil, errors.New("gitea error"))
_, err := checker.VerifyProjectState(config)
if err == nil || !strings.Contains(err.Error(), "Error fetching or creating") {
t.Errorf("Expected gitea error, got %v", err)
}
})
}
func TestDefaultStateChecker_CheckRepos(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
mockGit := mock_common.NewMockGit(ctl)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
config := &common.AutogitConfig{
Organization: "test-org",
GitProjectName: "test-prj#main",
}
reqProc := &RequestProcessor{
configuredRepos: map[string][]*common.AutogitConfig{
"test-org": {config},
},
}
checker := CreateDefaultStateChecker(false, nil, gitea, 0)
checker.processor = reqProc
t.Run("CheckRepos success with PRs", func(t *testing.T) {
// Mock VerifyProjectState results
// TODO: fix below
// Since we can't easily mock the internal call s.i.VerifyProjectState because s.i is the checker itself
// and VerifyProjectState is not a separate interface method in repo_check.go (it is, but used internally).
// Actually DefaultStateChecker implements i (StateChecker interface).
mockGitGen.EXPECT().CreateGitHandler("test-org").Return(mockGit, nil).AnyTimes()
mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes()
mockGit.EXPECT().Close().Return(nil).AnyTimes()
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "url"}, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes()
// GetRecentPullRequests for the project git
gitea.EXPECT().GetRecentPullRequests("test-org", "test-prj", "main").Return([]*models.PullRequest{
{Index: 1, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}}}},
}, nil).AnyTimes()
// ProcessPR calls for the found PR
gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}},
},
}, nil).AnyTimes()
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes()
gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
checker.CheckRepos()
})
t.Run("CheckRepos failure - GetRecentPullRequests", func(t *testing.T) {
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{SSHURL: "url"}, nil).AnyTimes()
mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes()
mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes()
gitea.EXPECT().GetRecentPullRequests("test-org", "test-prj", "main").Return(nil, errors.New("gitea error")).AnyTimes()
checker.CheckRepos()
// Should log error and continue (no panic)
})
}

View File

@@ -10,6 +10,7 @@ import (
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock"
mock_main "src.opensuse.org/autogits/workflow-pr/mock"
)
func TestRepoCheck(t *testing.T) {
@@ -21,15 +22,16 @@ func TestRepoCheck(t *testing.T) {
t.Run("Consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl)
state := mock_main.NewMockStateChecker(ctl)
c.i = state
state.EXPECT().CheckRepos().Do(func() {
state.EXPECT().CheckRepos().Do(func() error {
// only checkOnStart has checkInterval = 0
if c.checkInterval != 0 {
t.Fail()
}
c.exitCheckLoop = true
return nil
})
c.ConsistencyCheckProcess()
@@ -41,11 +43,11 @@ func TestRepoCheck(t *testing.T) {
t.Run("No consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl)
state := mock_main.NewMockStateChecker(ctl)
c.i = state
nCalls := 10
state.EXPECT().CheckRepos().Do(func() {
state.EXPECT().CheckRepos().Do(func() error {
// only checkOnStart has checkInterval = 0
if c.checkInterval != 100 {
t.Fail()
@@ -55,6 +57,7 @@ func TestRepoCheck(t *testing.T) {
if nCalls == 0 {
c.exitCheckLoop = true
}
return nil
}).Times(nCalls)
c.checkOnStart = false
@@ -63,9 +66,8 @@ func TestRepoCheck(t *testing.T) {
t.Run("CheckRepos() calls CheckProjectState() for each project", func(t *testing.T) {
ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl)
state := mock_main.NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
config1 := &common.AutogitConfig{
GitProjectName: "git_repo1",
@@ -95,14 +97,15 @@ func TestRepoCheck(t *testing.T) {
state.EXPECT().VerifyProjectState(configs.configuredRepos["repo2_org"][0])
state.EXPECT().VerifyProjectState(configs.configuredRepos["repo3_org"][0])
c.CheckRepos()
if err := c.CheckRepos(); err != nil {
t.Error(err)
}
})
t.Run("CheckRepos errors", func(t *testing.T) {
ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl)
state := mock_main.NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
config1 := &common.AutogitConfig{
GitProjectName: "git_repo1",
@@ -122,7 +125,11 @@ func TestRepoCheck(t *testing.T) {
err := errors.New("test error")
state.EXPECT().VerifyProjectState(configs.configuredRepos["repo1_org"][0]).Return(nil, err)
c.CheckRepos()
r := c.CheckRepos()
if !errors.Is(r, err) {
t.Error(err)
}
})
}
@@ -147,7 +154,6 @@ func TestVerifyProjectState(t *testing.T) {
t.Run("Project state with no PRs", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
git := &common.GitHandlerImpl{
GitCommiter: "TestCommiter",
@@ -171,11 +177,11 @@ func TestVerifyProjectState(t *testing.T) {
},
}
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), config1.GitProjectName).Return(&models.Repository{
SSHURL: "./prj",
}, nil).AnyTimes()
gitea.EXPECT().GetRecentPullRequests(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullRequest{}, nil).AnyTimes()
gitea.EXPECT().GetRecentCommits(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Commit{}, nil).AnyTimes()
}, nil)
gitea.EXPECT().GetRecentPullRequests(org, "testRepo", "testing")
gitea.EXPECT().GetRecentCommits(org, "testRepo", "testing", gomock.Any())
c := CreateDefaultStateChecker(false, configs, gitea, 0)
/*
@@ -193,7 +199,7 @@ func TestVerifyProjectState(t *testing.T) {
t.Run("Project state with 1 PRs that doesn't trigger updates", func(t *testing.T) {
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
process := mock_main.NewMockPullRequestProcessor(ctl)
git := &common.GitHandlerImpl{
GitCommiter: "TestCommiter",
@@ -217,11 +223,11 @@ func TestVerifyProjectState(t *testing.T) {
},
}
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), gomock.Any()).Return(&models.Repository{
gitea.EXPECT().CreateRepositoryIfNotExist(gomock.Any(), gomock.Any(), config1.GitProjectName).Return(&models.Repository{
SSHURL: "./prj",
}, nil).AnyTimes()
}, nil)
gitea.EXPECT().GetRecentPullRequests(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullRequest{
gitea.EXPECT().GetRecentPullRequests(org, "testRepo", "testing").Return([]*models.PullRequest{
&models.PullRequest{
ID: 1234,
URL: "url here",
@@ -253,16 +259,16 @@ func TestVerifyProjectState(t *testing.T) {
},
},
},
}, nil).AnyTimes()
}, nil)
gitea.EXPECT().GetRecentCommits(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Commit{}, nil).AnyTimes()
gitea.EXPECT().GetRecentCommits(org, "testRepo", "testing", gomock.Any())
c := CreateDefaultStateChecker(false, configs, gitea, 0)
/*
c.git = &testGit{
git: git,
}*/
// process.EXPECT().Process(gomock.Any())
process.EXPECT().Process(gomock.Any(), gomock.Any(), gomock.Any())
// c.processor.Opened = process
_, err := c.VerifyProjectState(configs.configuredRepos[org][0])

View File

@@ -1,23 +0,0 @@
package main
import (
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
)
//go:generate mockgen -source=state_checker.go -destination=mock_state_checker.go -typed -package main
type StateChecker interface {
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
CheckRepos()
ConsistencyCheckProcess() error
}
type PullRequestProcessor interface {
Process(req *models.PullRequest) error
}
type PRToProcess struct {
Org, Repo, Branch string
}

View File

@@ -1,87 +0,0 @@
package main
import (
"fmt"
"os/exec"
"path/filepath"
"testing"
"src.opensuse.org/autogits/common"
)
const LocalCMD = "---"
func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) {
for _, cmd := range cmds {
if cmd[0] == LocalCMD {
command := exec.Command(cmd[2], cmd[3:]...)
command.Dir = filepath.Join(git.GitPath, cmd[1])
command.Stdin = nil
command.Env = append([]string{"GIT_CONFIG_COUNT=1", "GIT_CONFIG_KEY_1=protocol.file.allow", "GIT_CONFIG_VALUE_1=always"}, common.ExtraGitParams...)
_, err := command.CombinedOutput()
if err != nil {
t.Errorf(" *** error: %v\n", err)
}
} else {
git.GitExecOrPanic(cmd[0], cmd[1:]...)
}
}
}
func commandsForPackages(dir, prefix string, startN, endN int) [][]string {
commands := make([][]string, (endN-startN+2)*6)
if dir == "" {
dir = "."
}
cmdIdx := 0
for idx := startN; idx <= endN; idx++ {
pkgDir := fmt.Sprintf("%s%d", prefix, idx)
commands[cmdIdx+0] = []string{"", "init", "-q", "--object-format", "sha256", "-b", "testing", pkgDir}
commands[cmdIdx+1] = []string{LocalCMD, pkgDir, "/usr/bin/touch", "testFile"}
commands[cmdIdx+2] = []string{pkgDir, "add", "testFile"}
commands[cmdIdx+3] = []string{pkgDir, "commit", "-m", "added testFile"}
commands[cmdIdx+4] = []string{pkgDir, "config", "receive.denyCurrentBranch", "ignore"}
commands[cmdIdx+5] = []string{"prj", "submodule", "add", filepath.Join("..", pkgDir), filepath.Join(dir, pkgDir)}
cmdIdx += 6
}
// add all the submodules to the prj
commands[cmdIdx+0] = []string{"prj", "commit", "-a", "-m", "adding subpackages"}
return commands
}
func setupGitForTests(t *testing.T, git *common.GitHandlerImpl) {
common.ExtraGitParams = []string{
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always",
"GIT_AUTHOR_NAME=testname",
"GIT_AUTHOR_EMAIL=test@suse.com",
"GIT_AUTHOR_DATE='2005-04-07T22:13:13'",
"GIT_COMMITTER_NAME=testname",
"GIT_COMMITTER_EMAIL=test@suse.com",
"GIT_COMMITTER_DATE='2005-04-07T22:13:13'",
}
gitExecs(t, git, [][]string{
{"", "init", "-q", "--object-format", "sha256", "-b", "testing", "prj"},
{"", "init", "-q", "--object-format", "sha256", "-b", "testing", "foo"},
{LocalCMD, "foo", "/usr/bin/touch", "file1"},
{"foo", "add", "file1"},
{"foo", "commit", "-m", "first commit"},
{"prj", "config", "receive.denyCurrentBranch", "ignore"},
{"prj", "submodule", "init"},
{"prj", "submodule", "add", "../foo", "testRepo"},
{"prj", "add", ".gitmodules", "testRepo"},
{"prj", "commit", "-m", "First instance"},
{"prj", "submodule", "deinit", "testRepo"},
{LocalCMD, "foo", "/usr/bin/touch", "file2"},
{"foo", "add", "file2"},
{"foo", "commit", "-m", "added file2"},
})
}