25 Commits

Author SHA256 Message Date
9b3d6e9a66 pr: Always allow maintainer edits in new packages
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 25s
2026-01-31 10:38:03 +01:00
f47da62481 common: replace legacy logger with standard impl
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 27s
2026-01-31 10:18:14 +01:00
79e5345ac6 test: refactor loggin in unit tests
Use test logger when running under a test.
2026-01-31 10:04:08 +01:00
4790ee3096 pr: small refactor
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 10s
2026-01-30 16:44:11 +01:00
7d85c014d6 pr: test coverage 2026-01-30 16:44:11 +01:00
05aaf56cb7 pr: test coverage 2026-01-30 16:44:11 +01:00
74491cb9cf pr: small refactor 2026-01-30 16:44:11 +01:00
9775320411 pr: close associated PR if issue closed. 2026-01-30 16:44:11 +01:00
790540d6c5 common: test fixes after rebase 2026-01-30 16:44:11 +01:00
990f71677b pr: no need to create PR if already exists. 2026-01-30 16:44:11 +01:00
514725fb0b pr: Only update issues if they are actually open 2026-01-30 16:44:11 +01:00
ea69dcd19f pr: fix tests 2026-01-30 16:44:11 +01:00
055de8c9eb pr: use top 20 commits as base, if available 2026-01-30 16:44:11 +01:00
a13f57e464 common: add test for cloning hashes 2026-01-30 16:44:11 +01:00
b147fc7622 common: add test for directory listing 2026-01-30 16:44:11 +01:00
fc7fb4d49f pr: test updated issue ref
If issue is a SHA ref, and then it's updated, we need to make
sure that the branch is also updated.
2026-01-30 16:44:11 +01:00
40eeb25336 pr: move functions around 2026-01-30 16:44:11 +01:00
68a142046c pr: fix tests 2026-01-30 16:44:11 +01:00
240d79a4d6 pr: new package handling 2026-01-30 16:44:11 +01:00
21a8670674 pr: make sure new repos have fork/parent relationship
If new target repo is "reparented", it will have correct relationship
here. Otherwise PR creation will fail
2026-01-30 16:44:11 +01:00
d2e9abde6e common: more unit tests fixes
TZ needs to be defined, otherwise it was assumed to be local which
then resulted in unpredictable commit hashes. We define it to UTC
for unit tests

PR have state "open" not "opened"
2026-01-30 16:44:11 +01:00
a4f677690a pr: fix more unit tests 2026-01-30 16:44:10 +01:00
70460813f5 pr: merge new package 2026-01-30 16:44:10 +01:00
0651fe01d4 pr: implement first part of issue processing 2026-01-30 16:44:10 +01:00
e03fd0bdcf wip: process issues 2026-01-30 16:44:10 +01:00
46 changed files with 1986 additions and 703 deletions

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

@@ -14,6 +14,7 @@ func newStringScanner(s string) *bufio.Scanner {
}
func TestAssociatedPRScanner(t *testing.T) {
common.SetTestLogger(t)
testTable := []struct {
name string
input string
@@ -95,6 +96,7 @@ func TestAssociatedPRScanner(t *testing.T) {
}
func TestAppendingPRsToDescription(t *testing.T) {
common.SetTestLogger(t)
testTable := []struct {
name string
desc string

View File

@@ -66,6 +66,7 @@ const (
Label_StagingAuto = "staging/Auto"
Label_ReviewPending = "review/Pending"
Label_ReviewDone = "review/Done"
Label_NewRepository = "new/New Repository"
)
func LabelKey(tag_value string) string {

View File

@@ -4,7 +4,6 @@ import (
"slices"
"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"
@@ -54,7 +53,7 @@ func TestConfigLabelParser(t *testing.T) {
DefaultBranch: "master",
}
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
@@ -176,7 +175,7 @@ func TestConfigWorkflowParser(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.config_json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&test.repo, nil)

View File

@@ -42,6 +42,7 @@ type GitSubmoduleLister interface {
type GitDirectoryLister interface {
GitDirectoryList(gitPath, commitId string) (dirlist map[string]string, err error)
GitDirectoryContentList(gitPath, commitId string) (dirlist map[string]string, err error)
}
type GitStatusLister interface {
@@ -272,7 +273,11 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
LogDebug("branch", branch)
}
*/
args := []string{"fetch", "--prune", remoteName, branch}
args := []string{"fetch", "--prune", remoteName}
if len(branch) > 0 {
args = append(args, branch)
}
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
args = slices.Insert(args, 1, "--unshallow")
}
@@ -787,7 +792,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
return
}
// return (filename) -> (hash) map for all submodules
// return (directory) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryList map[string]string, err error) {
var done sync.Mutex
directoryList = make(map[string]string)
@@ -861,6 +866,82 @@ func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryLi
return directoryList, err
}
// return (directory) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (directoryList map[string]string, err error) {
var done sync.Mutex
directoryList = make(map[string]string)
done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
LogDebug("Getting directory content for:", commitId)
go func() {
defer done.Unlock()
defer close(data_out.ch)
data_out.Write([]byte(commitId))
data_out.ch <- '\x00'
var c GitCommit
c, err = parseGitCommit(data_in.ch)
if err != nil {
err = fmt.Errorf("Error parsing git commit. Err: %w", err)
return
}
trees := make(map[string]string)
trees[""] = c.Tree
for len(trees) > 0 {
for p, tree := range trees {
delete(trees, p)
data_out.Write([]byte(tree))
data_out.ch <- '\x00'
var tree GitTree
tree, err = parseGitTree(data_in.ch)
if err != nil {
err = fmt.Errorf("Error parsing git tree: %w", err)
return
}
for _, te := range tree.items {
if te.isBlob() || te.isSubmodule() {
directoryList[p+te.name] = te.hash
} else if te.isTree() {
trees[p+te.name] = te.hash
}
}
}
}
}()
cmd := exec.Command("/usr/bin/git", "cat-file", "--batch", "-Z")
cmd.Env = []string{
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
"GIT_LFS_SKIP_SMUDGE=1",
"GIT_CONFIG_GLOBAL=/dev/null",
}
cmd.Dir = filepath.Join(e.GitPath, gitPath)
cmd.Stdout = &data_in
cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data))
return len(data), nil
})
LogDebug("command run:", cmd.Args)
if e := cmd.Run(); e != nil {
LogError(e)
close(data_in.ch)
close(data_out.ch)
return directoryList, e
}
done.Lock()
return directoryList, err
}
// return (filename) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleList map[string]string, err error) {
var done sync.Mutex

View File

@@ -24,12 +24,14 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"strings"
"testing"
)
func TestGitClone(t *testing.T) {
SetTestLogger(t)
tests := []struct {
name string
@@ -93,7 +95,59 @@ func TestGitClone(t *testing.T) {
}
}
func TestGitCloneCommitID(t *testing.T) {
SetTestLogger(t)
execPath, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
d := t.TempDir()
if err := os.Chdir(d); err != nil {
t.Fatal(err)
}
defer os.Chdir(execPath)
cmd := exec.Command(path.Join(execPath, "test_repo_setup.sh"))
if out, err := cmd.CombinedOutput(); err != nil {
t.Log(string(out))
t.Fatal(err)
}
gh, err := AllocateGitWorkTree(d, "Test", "test@example.com")
if err != nil {
t.Fatal(err)
}
g, err := gh.CreateGitHandler("org")
if err != nil {
t.Fatal(err)
}
// Get a commit ID from pkgA
remoteUrl := "file://" + d + "/pkgA"
out, err := exec.Command("git", "-C", path.Join(d, "pkgA"), "rev-parse", "main").Output()
if err != nil {
t.Fatal(err)
}
commitID := strings.TrimSpace(string(out))
repo := "pkgAcloneCommitID"
if _, err := g.GitClone(repo, commitID, remoteUrl); err != nil {
t.Skip("TODO: Add GitClone CommitID support")
t.Fatalf("GitClone failed with commit ID: %v", err)
}
// Verify we are at the right commit
head, err := g.GitBranchHead(repo, commitID)
if err != nil {
t.Fatalf("GitBranchHead failed: %v", err)
}
if head != commitID {
t.Errorf("Expected head %s, got %s", commitID, head)
}
}
func TestGitMsgParsing(t *testing.T) {
SetTestLogger(t)
t.Run("tree message with size 56", func(t *testing.T) {
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
@@ -172,6 +226,7 @@ func TestGitMsgParsing(t *testing.T) {
}
func TestGitCommitParsing(t *testing.T) {
SetTestLogger(t)
t.Run("parse valid commit message", func(t *testing.T) {
const commitData = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f99 commit 253\000" +
`tree e20033df9f18780756ba4a96dbc7eb1a626253961039cb674156f266ba7a4e53
@@ -382,6 +437,7 @@ dummy change, don't merge
}
func TestCommitTreeParsing(t *testing.T) {
SetTestLogger(t)
gitDir := t.TempDir()
testDir, _ := os.Getwd()
var commitId string
@@ -490,6 +546,7 @@ func TestCommitTreeParsing(t *testing.T) {
}
func TestGitStatusParse(t *testing.T) {
SetTestLogger(t)
testData := []struct {
name string
data []byte
@@ -596,3 +653,67 @@ func TestGitStatusParse(t *testing.T) {
})
}
}
func TestGitDirectoryListRepro(t *testing.T) {
SetTestLogger(t)
d := t.TempDir()
// Setup a mock environment for GitHandlerImpl
gh, err := AllocateGitWorkTree(d, "Test", "test@example.com")
if err != nil {
t.Fatal(err)
}
org := "repo-org"
repoName := "test-repo"
repoPath := filepath.Join(d, org, repoName)
err = os.MkdirAll(repoPath, 0755)
if err != nil {
t.Fatal(err)
}
runGit := func(args ...string) {
cmd := exec.Command("git", args...)
cmd.Dir = repoPath
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v failed: %v\n%s", args, err, out)
}
}
runGit("init", "-b", "main", "--object-format=sha256")
runGit("config", "user.email", "test@example.com")
runGit("config", "user.name", "test")
// Create a directory and a file
err = os.Mkdir(filepath.Join(repoPath, "subdir"), 0755)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(repoPath, "subdir", "file.txt"), []byte("hello"), 0644)
if err != nil {
t.Fatal(err)
}
runGit("add", "subdir/file.txt")
runGit("commit", "-m", "add subdir")
// Now create the handler
g, err := gh.CreateGitHandler(org)
if err != nil {
t.Fatal(err)
}
// Call GitDirectoryList
dirs, err := g.GitDirectoryList(repoName, "HEAD")
if err != nil {
t.Fatal(err)
}
t.Logf("Directories found: %v", dirs)
if len(dirs) == 0 {
t.Error("No directories found, but 'subdir' should be there")
}
if _, ok := dirs["subdir"]; !ok {
t.Errorf("Expected 'subdir' in directory list, got %v", dirs)
}
}

View File

@@ -75,6 +75,10 @@ type GiteaLabelSettter interface {
SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error)
}
type GiteaIssueFetcher interface {
GetIssue(org, repo string, idx int64) (*models.Issue, error)
}
type GiteaTimelineFetcher interface {
ResetTimelineCache(org, repo string, idx int64)
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
@@ -148,6 +152,7 @@ type GiteaReviewRequester interface {
type GiteaReviewUnrequester interface {
UnrequestReview(org, repo string, id int64, reviwers ...string) error
UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error)
}
type GiteaReviewer interface {
@@ -201,6 +206,7 @@ type Gitea interface {
GiteaSetRepoOptions
GiteaLabelGetter
GiteaLabelSettter
GiteaIssueFetcher
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
@@ -509,6 +515,26 @@ func (gitea *GiteaTransport) SetLabels(owner, repo string, idx int64, labels []s
return ret.Payload, nil
}
func (gitea *GiteaTransport) GetIssue(owner, repo string, idx int64) (*models.Issue, error) {
ret, err := gitea.client.Issue.IssueGetIssue(
issue.NewIssueGetIssueParams().WithOwner(owner).WithRepo(repo).WithIndex(idx),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, nil
}
func (gitea *GiteaTransport) UpdateIssue(owner, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
ret, err := gitea.client.Issue.IssueEditIssue(
issue.NewIssueEditIssueParams().WithOwner(owner).WithRepo(repo).WithIndex(idx).WithBody(options),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, nil
}
const (
GiteaNotificationType_Pull = "Pull"
)
@@ -716,6 +742,7 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
)
if err != nil {
LogError("owner:", repo.Owner.UserName, " repo:", repo.Name, " body:", prOptions)
return nil, fmt.Errorf("Cannot create pull request. %w", err), true
}
@@ -822,7 +849,6 @@ func (gitea *GiteaTransport) ResetTimelineCache(org, repo string, idx int64) {
Cache, IsCached := giteaTimelineCache[prID]
if IsCached {
Cache.lastCheck = Cache.lastCheck.Add(-time.Hour)
giteaTimelineCache[prID] = Cache
}
}

View File

@@ -67,6 +67,16 @@ func GetLoggingLevel() LogLevel {
return logLevel
}
type Logger interface {
Log(args ...any)
}
var testLogger Logger
func SetTestLogger(l Logger) {
testLogger = l
}
func SetLoggingLevelFromString(ll string) error {
switch ll {
case "info":
@@ -86,18 +96,26 @@ func SetLoggingLevelFromString(ll string) error {
func LogError(params ...any) {
if logLevel >= LogLevelError {
log.Println(append([]any{"[E]"}, params...)...)
logit("[E]", params...)
}
}
func LogDebug(params ...any) {
if logLevel >= LogLevelDebug {
log.Println(append([]any{"[D]"}, params...)...)
logit("[D]", params...)
}
}
func LogInfo(params ...any) {
if logLevel >= LogLevelInfo {
log.Println(append([]any{"[I]"}, params...)...)
logit("[I]", params...)
}
}
func logit(prefix string, params ...any) {
if testLogger != nil {
testLogger.Log(append([]any{prefix}, params...)...)
} else {
log.Println(append([]any{prefix}, params...)...)
}
}

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"
@@ -29,13 +27,11 @@ type MaintainershipMap struct {
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
@@ -66,7 +62,7 @@ func FetchProjectMaintainershipData(gitea GiteaMaintainershipReader, config *Aut
}
}
m, err := ParseMaintainershipData(data)
m, err := parseMaintainershipData(data)
if m != nil {
m.Config = config
m.IsDir = dir
@@ -175,135 +171,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 {
@@ -314,12 +188,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

@@ -172,7 +172,7 @@ func TestMaintainership(t *testing.T) {
}
t.Run(test.name+"_File", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
mi := mock_common.NewMockGiteaMaintainershipReader(ctl)
// tests with maintainership file
@@ -185,7 +185,7 @@ func TestMaintainership(t *testing.T) {
})
t.Run(test.name+"_Dir", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
mi := mock_common.NewMockGiteaMaintainershipReader(ctl)
// run same tests with directory maintainership data
@@ -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,7 +248,7 @@ 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)
}
})
}

View File

@@ -7,6 +7,7 @@ import (
)
func TestManifestSubdirAssignments(t *testing.T) {
common.SetTestLogger(t)
tests := []struct {
Name string
ManifestContent string

View File

@@ -142,6 +142,45 @@ func (m *MockGitDirectoryLister) EXPECT() *MockGitDirectoryListerMockRecorder {
return m.recorder
}
// GitDirectoryContentList mocks base method.
func (m *MockGitDirectoryLister) GitDirectoryContentList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GitDirectoryContentList", gitPath, commitId)
ret0, _ := ret[0].(map[string]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GitDirectoryContentList indicates an expected call of GitDirectoryContentList.
func (mr *MockGitDirectoryListerMockRecorder) GitDirectoryContentList(gitPath, commitId any) *MockGitDirectoryListerGitDirectoryContentListCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitDirectoryContentList", reflect.TypeOf((*MockGitDirectoryLister)(nil).GitDirectoryContentList), gitPath, commitId)
return &MockGitDirectoryListerGitDirectoryContentListCall{Call: call}
}
// MockGitDirectoryListerGitDirectoryContentListCall wrap *gomock.Call
type MockGitDirectoryListerGitDirectoryContentListCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGitDirectoryListerGitDirectoryContentListCall) Return(dirlist map[string]string, err error) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.Return(dirlist, err)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGitDirectoryListerGitDirectoryContentListCall) Do(f func(string, string) (map[string]string, error)) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGitDirectoryListerGitDirectoryContentListCall) DoAndReturn(f func(string, string) (map[string]string, error)) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GitDirectoryList mocks base method.
func (m *MockGitDirectoryLister) GitDirectoryList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()
@@ -563,6 +602,45 @@ func (c *MockGitGitDiffCall) DoAndReturn(f func(string, string, string) (string,
return c
}
// GitDirectoryContentList mocks base method.
func (m *MockGit) GitDirectoryContentList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GitDirectoryContentList", gitPath, commitId)
ret0, _ := ret[0].(map[string]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GitDirectoryContentList indicates an expected call of GitDirectoryContentList.
func (mr *MockGitMockRecorder) GitDirectoryContentList(gitPath, commitId any) *MockGitGitDirectoryContentListCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitDirectoryContentList", reflect.TypeOf((*MockGit)(nil).GitDirectoryContentList), gitPath, commitId)
return &MockGitGitDirectoryContentListCall{Call: call}
}
// MockGitGitDirectoryContentListCall wrap *gomock.Call
type MockGitGitDirectoryContentListCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGitGitDirectoryContentListCall) Return(dirlist map[string]string, err error) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.Return(dirlist, err)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGitGitDirectoryContentListCall) Do(f func(string, string) (map[string]string, error)) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGitGitDirectoryContentListCall) DoAndReturn(f func(string, string) (map[string]string, error)) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GitDirectoryList mocks base method.
func (m *MockGit) GitDirectoryList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()

View File

@@ -144,6 +144,69 @@ func (c *MockGiteaLabelSettterSetLabelsCall) DoAndReturn(f func(string, string,
return c
}
// MockGiteaIssueFetcher is a mock of GiteaIssueFetcher interface.
type MockGiteaIssueFetcher struct {
ctrl *gomock.Controller
recorder *MockGiteaIssueFetcherMockRecorder
isgomock struct{}
}
// MockGiteaIssueFetcherMockRecorder is the mock recorder for MockGiteaIssueFetcher.
type MockGiteaIssueFetcherMockRecorder struct {
mock *MockGiteaIssueFetcher
}
// NewMockGiteaIssueFetcher creates a new mock instance.
func NewMockGiteaIssueFetcher(ctrl *gomock.Controller) *MockGiteaIssueFetcher {
mock := &MockGiteaIssueFetcher{ctrl: ctrl}
mock.recorder = &MockGiteaIssueFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaIssueFetcher) EXPECT() *MockGiteaIssueFetcherMockRecorder {
return m.recorder
}
// GetIssue mocks base method.
func (m *MockGiteaIssueFetcher) GetIssue(org, repo string, idx int64) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssue", org, repo, idx)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssue indicates an expected call of GetIssue.
func (mr *MockGiteaIssueFetcherMockRecorder) GetIssue(org, repo, idx any) *MockGiteaIssueFetcherGetIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssue", reflect.TypeOf((*MockGiteaIssueFetcher)(nil).GetIssue), org, repo, idx)
return &MockGiteaIssueFetcherGetIssueCall{Call: call}
}
// MockGiteaIssueFetcherGetIssueCall wrap *gomock.Call
type MockGiteaIssueFetcherGetIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaIssueFetcherGetIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaIssueFetcherGetIssueCall) Do(f func(string, string, int64) (*models.Issue, error)) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaIssueFetcherGetIssueCall) DoAndReturn(f func(string, string, int64) (*models.Issue, error)) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface.
type MockGiteaTimelineFetcher struct {
ctrl *gomock.Controller
@@ -1623,6 +1686,45 @@ func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Do
return c
}
// UpdateIssue mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaUnreviewTimelineFetcher is a mock of GiteaUnreviewTimelineFetcher interface.
type MockGiteaUnreviewTimelineFetcher struct {
ctrl *gomock.Controller
@@ -1765,6 +1867,45 @@ func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) DoAndReturn(f func
return c
}
// UpdateIssue mocks base method.
func (m *MockGiteaUnreviewTimelineFetcher) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaUnreviewTimelineFetcherUpdateIssueCall{Call: call}
}
// MockGiteaUnreviewTimelineFetcherUpdateIssueCall wrap *gomock.Call
type MockGiteaUnreviewTimelineFetcherUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaReviewRequester is a mock of GiteaReviewRequester interface.
type MockGiteaReviewRequester struct {
ctrl *gomock.Controller
@@ -1900,6 +2041,45 @@ func (c *MockGiteaReviewUnrequesterUnrequestReviewCall) DoAndReturn(f func(strin
return c
}
// UpdateIssue mocks base method.
func (m *MockGiteaReviewUnrequester) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaReviewUnrequesterMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaReviewUnrequesterUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaReviewUnrequester)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaReviewUnrequesterUpdateIssueCall{Call: call}
}
// MockGiteaReviewUnrequesterUpdateIssueCall wrap *gomock.Call
type MockGiteaReviewUnrequesterUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaReviewer is a mock of GiteaReviewer interface.
type MockGiteaReviewer struct {
ctrl *gomock.Controller
@@ -2694,6 +2874,45 @@ func (c *MockGiteaGetDoneNotificationsCall) DoAndReturn(f func(string, int64) ([
return c
}
// GetIssue mocks base method.
func (m *MockGitea) GetIssue(org, repo string, idx int64) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssue", org, repo, idx)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssue indicates an expected call of GetIssue.
func (mr *MockGiteaMockRecorder) GetIssue(org, repo, idx any) *MockGiteaGetIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssue", reflect.TypeOf((*MockGitea)(nil).GetIssue), org, repo, idx)
return &MockGiteaGetIssueCall{Call: call}
}
// MockGiteaGetIssueCall wrap *gomock.Call
type MockGiteaGetIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaGetIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaGetIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaGetIssueCall) Do(f func(string, string, int64) (*models.Issue, error)) *MockGiteaGetIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaGetIssueCall) DoAndReturn(f func(string, string, int64) (*models.Issue, error)) *MockGiteaGetIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetIssueComments mocks base method.
func (m *MockGitea) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
m.ctrl.T.Helper()
@@ -3558,6 +3777,45 @@ func (c *MockGiteaUnrequestReviewCall) DoAndReturn(f func(string, string, int64,
return c
}
// UpdateIssue mocks base method.
func (m *MockGitea) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGitea)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaUpdateIssueCall{Call: call}
}
// MockGiteaUpdateIssueCall wrap *gomock.Call
type MockGiteaUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// UpdatePullRequest mocks base method.
func (m *MockGitea) UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error) {
m.ctrl.T.Helper()

View File

@@ -6,7 +6,9 @@ import (
"fmt"
"os"
"path"
"regexp"
"slices"
"strconv"
"strings"
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
@@ -659,6 +661,8 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
}
// FF all non-prj git and unrequest reviews.
newRepoIssues := make(map[int64]string) // issue index -> org/repo
for _, prinfo := range rs.PRs {
// remove pending review requests
repo := prinfo.PR.Base.Repo
@@ -680,6 +684,15 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
if rs.IsPrjGitPR(prinfo.PR) {
continue
}
isNewRepo := false
for _, l := range prinfo.PR.Labels {
if l.Name == Label_NewRepository {
isNewRepo = true
break
}
}
br := rs.Config.Branch
if len(br) == 0 {
// if branch is unspecified, take it from the PR as it
@@ -688,11 +701,30 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
} else if br != prinfo.PR.Base.Name {
panic(prinfo.PR.Base.Name + " is expected to match " + br)
}
if isNewRepo {
// Extract issue reference from body: "See issue #XYZ"
rx := regexp.MustCompile(`See issue #(\d+)`)
if matches := rx.FindStringSubmatch(prinfo.PR.Body); len(matches) > 1 {
if issueIdx, err := strconv.ParseInt(matches[1], 10, 64); err == nil {
// We need to know which project git this issue belongs to.
// Since the PR set is linked to a ProjectGit, we can use its org/repo.
prjGitOrg, prjGitRepo, _ := rs.Config.GetPrjGit()
newRepoIssues[issueIdx] = prjGitOrg + "/" + prjGitRepo
}
}
}
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
PanicOnError(err)
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
if isNewRepo {
LogInfo("Force-pushing new repository branch", br, "to", head.Sha)
// we don't merge, we just set the branch to this commit
} else {
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
}
}
// push changes
@@ -707,12 +739,37 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
}
repo := prinfo.PR.Base.Repo
isNewRepo := false
for _, l := range prinfo.PR.Labels {
if l.Name == Label_NewRepository {
isNewRepo = true
break
}
}
if !IsDryRun {
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
if isNewRepo {
git.GitExecOrPanic(repo.Name, "push", "-f", prinfo.RemoteName, prinfo.PR.Head.Sha+":"+prinfo.PR.Base.Name)
} else {
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
}
} else {
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
}
}
// Close referencing issues
if !IsDryRun {
for issueIdx, prjPath := range newRepoIssues {
parts := strings.Split(prjPath, "/")
if len(parts) == 2 {
LogInfo("Closing issue", prjPath+"#"+strconv.FormatInt(issueIdx, 10))
gitea.UpdateIssue(parts[0], parts[1], issueIdx, &models.EditIssueOption{
State: "closed",
})
}
}
}
return nil
}

123
common/pr_linkage_test.go Normal file
View File

@@ -0,0 +1,123 @@
package common_test
import (
"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 TestFetchPRSet_Linkage(t *testing.T) {
config := &common.AutogitConfig{
Organization: "target-org",
GitProjectName: "test-org/prjgit#main",
}
// 1. Mock a package PR
pkgPR := &models.PullRequest{
Index: 101,
State: "open",
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
}
// 2. Mock a ProjectGit PR that references the package PR
prjGitPR := &models.PullRequest{
Index: 500,
State: "open",
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{
Name: "prjgit",
Owner: &models.User{UserName: "test-org"},
},
},
Body: "Forwarded PRs: pkg1\n\nPR: target-org/pkg1!101",
}
t.Run("Fetch from ProjectGit PR", func(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mockGitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
// Expect fetch of prjGitPR
mockGitea.EXPECT().GetPullRequest("test-org", "prjgit", int64(500)).Return(prjGitPR, nil)
// Expect fetch of pkgPR because it's linked in body
mockGitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(101)).Return(pkgPR, nil)
// Expect review fetching (part of FetchPRSet)
mockGitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
prset, err := common.FetchPRSet("bot", mockGitea, "test-org", "prjgit", 500, config)
if err != nil {
t.Fatalf("FetchPRSet failed: %v", err)
}
if len(prset.PRs) != 2 {
t.Errorf("Expected 2 PRs in set, got %d", len(prset.PRs))
}
if !prset.IsConsistent() {
t.Error("PR set should be consistent")
}
})
t.Run("Fetch from Package PR via Timeline", func(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mockGitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
// 1. FetchPRSet for pkgPR will call LastPrjGitRefOnTimeline
mockGitea.EXPECT().GetTimeline("target-org", "pkg1", int64(101)).Return([]*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 500,
Body: "PR: target-org/pkg1!101",
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "prjgit",
},
User: &models.User{UserName: "bot"},
},
},
}, nil)
// 2. It will then fetch the prjGitPR found in timeline (twice in LastPrjGitRefOnTimeline)
mockGitea.EXPECT().GetPullRequest("test-org", "prjgit", int64(500)).Return(prjGitPR, nil).Times(2)
// 3. Then it will recursively fetch linked PRs from prjGitPR body in readPRData
mockGitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(101)).Return(pkgPR, nil)
// Review fetching for all PRs in the set
mockGitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline("test-org", "prjgit", int64(500)).Return([]*models.TimelineComment{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline("target-org", "pkg1", int64(101)).Return([]*models.TimelineComment{}, nil).AnyTimes()
prset, err := common.FetchPRSet("bot", mockGitea, "target-org", "pkg1", 101, config)
if err != nil {
t.Fatalf("FetchPRSet failed: %v", err)
}
if len(prset.PRs) != 2 {
t.Errorf("Expected 2 PRs in set, got %d", len(prset.PRs))
}
prjPRInfo, err := prset.GetPrjGitPR()
if err != nil || prjPRInfo.PR.Index != 500 {
t.Errorf("Expected ProjectGit PR 500 to be found, got %v", prjPRInfo)
}
})
}

View File

@@ -0,0 +1,94 @@
package common_test
import (
"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 TestPRSet_Merge_Special(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaReviewUnrequester(ctl)
mockGit := mock_common.NewMockGit(ctl)
config := &common.AutogitConfig{
Organization: "target-org",
GitProjectName: "test-org/prjgit#main",
Branch: "main",
}
// 1. Regular ProjectGit PR
prjGitPR := &models.PullRequest{
Index: 500,
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "test-org"}, SSHURL: "prj-ssh-url"},
Sha: "base-sha",
},
Head: &models.PRBranchInfo{Sha: "prj-head-sha"},
}
// 2. "new/New Repository" Package PR
newPkgPR := &models.PullRequest{
Index: 101,
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{Name: "new-pkg", Owner: &models.User{UserName: "target-org"}, SSHURL: "pkg-ssh-url"},
},
Head: &models.PRBranchInfo{Sha: "pkg-head-sha"},
Labels: []*models.Label{
{Name: "new/New Repository"},
},
Body: "See issue #123",
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{PR: prjGitPR},
{PR: newPkgPR},
},
}
common.IsDryRun = false
// Mock expectations for Merge
// Clone and fetch for PrjGit
mockGit.EXPECT().GitClone("_ObsPrj", "main", "prj-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", "prj-head-sha")
// mockGit.EXPECT().GitExecWithOutputOrPanic("_ObsPrj", "merge-base", "HEAD", "base-sha", "prj-head-sha").Return("base-sha")
mockGit.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), "prj-head-sha").Return(nil)
// Unrequest reviews
mockGitea.EXPECT().UnrequestReview("test-org", "prjgit", int64(500), gomock.Any()).Return(nil)
mockGitea.EXPECT().UnrequestReview("target-org", "new-pkg", int64(101), gomock.Any()).Return(nil)
// Clone and fetch for new-pkg
mockGit.EXPECT().GitClone("new-pkg", "main", "pkg-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("new-pkg", "fetch", "origin", "pkg-head-sha")
// Pushing changes
mockGit.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
// Special push for new repo: git push -f origin pkg-head-sha:main
mockGit.EXPECT().GitExecOrPanic("new-pkg", "push", "-f", "origin", "pkg-head-sha:main")
// Closing issue
mockGitea.EXPECT().UpdateIssue("test-org", "prjgit", int64(123), gomock.Any()).DoAndReturn(func(org, repo string, idx int64, opt *models.EditIssueOption) (*models.Issue, error) {
if opt.State != "closed" {
t.Errorf("Expected issue state to be closed, got %s", opt.State)
}
return nil, nil
})
err := prset.Merge(mockGitea, mockGit)
if err != nil {
t.Fatalf("Merge failed: %v", err)
}
}

View File

@@ -48,8 +48,6 @@ func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
}
func TestPR(t *testing.T) {
return
baseConfig := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
@@ -80,21 +78,21 @@ func TestPR(t *testing.T) {
{
name: "Error fetching PullRequest",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("Missing PR")},
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, pr_err: errors.New("Missing PR")},
},
prjGitPRIndex: -1,
},
{
name: "Error fetching PullRequest in PrjGit",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("missing PR")},
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, pr_err: errors.New("missing PR")},
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
},
},
{
name: "Error fetching prjgit",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}},
},
resLen: 1,
prjGitPRIndex: -1,
@@ -102,8 +100,20 @@ func TestPR(t *testing.T) {
{
name: "Review set is consistent",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42",
},
},
}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
},
resLen: 2,
prjGitPRIndex: 1,
@@ -113,8 +123,21 @@ func TestPR(t *testing.T) {
{
name: "Review set is consistent: 1pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42",
},
},
},
},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
},
resLen: 2,
prjGitPRIndex: 1,
@@ -123,9 +146,22 @@ func TestPR(t *testing.T) {
{
name: "Review set is consistent: 2pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "some desc", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/fer#33", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{
pr: &models.PullRequest{Body: "some desc\nPR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42\nPR: test/repo2#41",
},
},
},
},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/barPrj#22", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "open"}},
},
resLen: 3,
prjGitPRIndex: 1,
@@ -135,7 +171,7 @@ func TestPR(t *testing.T) {
name: "Review set of prjgit PR is consistent",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -154,10 +190,23 @@ func TestPR(t *testing.T) {
{
name: "Review set is consistent: 2pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42\nPR: test/repo2#41",
},
},
},
},
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "open"}},
},
resLen: 3,
prjGitPRIndex: 2,
@@ -167,7 +216,7 @@ func TestPR(t *testing.T) {
name: "WIP PR is not approved",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Title: "WIP: some title", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "", Title: "WIP: some title", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -184,10 +233,9 @@ func TestPR(t *testing.T) {
},
},
{
name: "Manual review is missing",
data: []prdata{
name: "Manual review is missing", data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -195,7 +243,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -212,16 +260,15 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
})
},
},
}},
{
name: "Manual review is done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -229,7 +276,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -246,7 +293,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
})
},
@@ -255,7 +302,7 @@ func TestPR(t *testing.T) {
name: "Manual review is done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -263,7 +310,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -280,7 +327,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
ManualMergeProject: true,
})
@@ -290,7 +337,7 @@ func TestPR(t *testing.T) {
name: "Manual review is not done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved},
{Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
@@ -299,7 +346,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -316,7 +363,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
ManualMergeProject: true,
})
@@ -326,7 +373,7 @@ func TestPR(t *testing.T) {
name: "Manual review is done via PackageGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -334,7 +381,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -351,7 +398,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
})
},
@@ -360,7 +407,7 @@ func TestPR(t *testing.T) {
name: "Manual review done via PkgGits",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -368,7 +415,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -376,7 +423,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -393,7 +440,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
})
},
@@ -402,7 +449,7 @@ func TestPR(t *testing.T) {
name: "Manual review done via PkgGits not allowed",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -410,7 +457,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -418,7 +465,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -435,7 +482,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
ManualMergeProject: true,
})
@@ -445,7 +492,7 @@ func TestPR(t *testing.T) {
name: "Manual review is is missing on one PR",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -453,7 +500,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -461,7 +508,7 @@ func TestPR(t *testing.T) {
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -478,7 +525,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
ManualMergeOnly: true,
})
},
@@ -487,7 +534,7 @@ func TestPR(t *testing.T) {
name: "PR is approved with negative optional review",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
@@ -505,7 +552,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
GitProjectName: "barPrj#master",
}
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config)
},
@@ -514,32 +561,37 @@ func TestPR(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
pr_mock.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
review_mock := mock_common.NewMockGiteaPRChecker(ctl)
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
prjGitOrg, prjGitRepo, _ := baseConfig.GetPrjGit()
if test.reviewSetFetcher == nil { // if we are fetching the prjgit directly, the these mocks are not called
if test.prjGitPRIndex >= 0 {
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, test.prjGitPRIndex).
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err)
pr_mock.EXPECT().GetPullRequest(prjGitOrg, prjGitRepo, int64(test.data[test.prjGitPRIndex].pr.Index)).
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err).AnyTimes()
} else if test.prjGitPRIndex < 0 {
// no prjgit PR
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, gomock.Any()).
Return(nil, nil)
pr_mock.EXPECT().GetPullRequest(prjGitOrg, prjGitRepo, gomock.Any()).
Return(nil, nil).AnyTimes()
}
}
var test_err error
for _, data := range test.data {
pr_mock.EXPECT().GetPullRequest(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.pr, data.pr_err).AnyTimes()
if data.pr_err != nil {
test_err = data.pr_err
// test_err is not used and was causing a build error.
// data.pr_err is directly used in the previous EXPECT call.
}
review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
if data.timeline == nil {
data.timeline = reviewsToTimeline(data.reviews)
}
pr_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
pr_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
review_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
}
@@ -552,27 +604,12 @@ func TestPR(t *testing.T) {
res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig)
}
if err == nil {
if test_err != nil {
t.Fatal("Expected", test_err, "but got", err)
}
} else {
if res != nil {
t.Fatal("error but got ReviewSet?")
}
if test.api_error != "" {
if err.Error() != test.api_error {
t.Fatal("expected", test.api_error, "but got", err)
}
} else if test_err != err {
t.Fatal("expected", test_err, "but got", err)
}
if res == nil {
return
}
if test.resLen != len(res.PRs) {
t.Error("expected result len", test.resLen, "but got", len(res.PRs))
if len(res.PRs) != test.resLen {
t.Errorf("Test Case '%s': expected result len %d but got %d", test.name, test.resLen, len(res.PRs))
}
PrjGitPR, err := res.GetPrjGitPR()
@@ -583,6 +620,9 @@ func TestPR(t *testing.T) {
}
pr_found := false
if test.prjGitPRIndex >= 0 {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := range test.data {
if PrjGitPR.PR == test.data[i].pr && i == test.prjGitPRIndex {
t.Log("found at index", i)
@@ -1184,7 +1224,6 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
}
func TestPRMerge(t *testing.T) {
t.Skip("FAIL: No PrjGit PR found, missing calls")
repoDir := t.TempDir()
cwd, _ := os.Getwd()
@@ -1194,7 +1233,11 @@ func TestPRMerge(t *testing.T) {
t.Fatal(string(out))
}
t.Skip("No tests of PRMerge yet")
return
common.ExtraGitParams = []string{
"TZ=UTC",
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always",
@@ -1209,7 +1252,7 @@ func TestPRMerge(t *testing.T) {
config := &common.AutogitConfig{
Organization: "org",
GitProjectName: "org/prj#master",
GitProjectName: "org/prj#main",
}
tests := []struct {
@@ -1221,8 +1264,10 @@ func TestPRMerge(t *testing.T) {
name: "Merge base not merged in main",
pr: &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
Name: "main",
Sha: "96515c092626c716a4613ba4f68a8d1cc4894317658342c450e656390f524ec3", // "base_add_b1"
Repo: &models.Repository{
Name: "prj",
Owner: &models.User{
@@ -1232,7 +1277,7 @@ func TestPRMerge(t *testing.T) {
},
},
Head: &models.PRBranchInfo{
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2"
},
},
mergeError: "Aborting merge",
@@ -1241,8 +1286,10 @@ func TestPRMerge(t *testing.T) {
name: "Merge conflict in modules, auto-resolved",
pr: &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
Name: "main",
Sha: "85f59f7aa732b742e58b48356cd46cb1ab1b5c4349eb5c0eda324e2dbea8f9e7",
Repo: &models.Repository{
Name: "prj",
Owner: &models.User{
@@ -1252,7 +1299,7 @@ func TestPRMerge(t *testing.T) {
},
},
Head: &models.PRBranchInfo{
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2"
},
},
},
@@ -1260,15 +1307,18 @@ func TestPRMerge(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mock.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
testDir := t.TempDir()
t.Log("dir:", testDir)
mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)
mock.EXPECT().GetTimeline("org", "prj", int64(1)).Return(nil, nil).AnyTimes()
mock.EXPECT().GetPullRequestReviews("org", "prj", int64(1)).Return(nil, nil).AnyTimes()
set, err := common.FetchPRSet("test", mock, "org", "prj", 1, config)
if err != nil {
@@ -1289,11 +1339,11 @@ func TestPRMerge(t *testing.T) {
}
func TestPRChanges(t *testing.T) {
t.Skip("FAIL: unexpected calls, missing calls")
tests := []struct {
name string
PRs []*models.PullRequest
PrjPRs *models.PullRequest
name string
PRs []*models.PullRequest
PrjPRs *models.PullRequest
Timeline []*models.TimelineComment
}{
{
name: "Pkg PR is closed",
@@ -1305,10 +1355,22 @@ func TestPRChanges(t *testing.T) {
},
},
PrjPRs: &models.PullRequest{
Index: 42,
Title: "some PR",
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
Base: &models.PRBranchInfo{Name: "branch", Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
Body: "PR: org/repo#42",
State: "opened",
State: "open",
},
Timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 42,
Repository: &models.RepositoryMeta{Name: "prjgit", Owner: "org"},
User: &models.User{UserName: "user"},
Body: "PR: org/repo#42",
},
},
},
},
}
@@ -1319,11 +1381,16 @@ func TestPRChanges(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
mock_fetcher.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil).AnyTimes()
mock_fetcher.EXPECT().GetTimeline("org", "prjgit", int64(42)).Return(nil, nil).AnyTimes()
mock_fetcher.EXPECT().GetPullRequestReviews("org", "prjgit", int64(42)).Return(nil, nil).AnyTimes()
for _, pr := range test.PRs {
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
mock_fetcher.EXPECT().GetTimeline(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(test.Timeline, nil).AnyTimes()
mock_fetcher.EXPECT().GetPullRequestReviews(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(nil, nil).AnyTimes()
}
PRs, err := common.FetchPRSet("user", mock_fetcher, "org", "repo", 42, &config)

View File

@@ -21,8 +21,6 @@ package common
import (
"encoding/json"
"fmt"
"log"
"os"
)
type RequestType interface {
@@ -87,24 +85,14 @@ func ParseRequestJSON(reqType string, data []byte) (req *Request, err error) {
}
type RequestHandler struct {
StdLogger, ErrLogger *log.Logger
Request *Request
}
func (r *RequestHandler) WriteError() {
r.ErrLogger.Println("internal error sent")
LogError("internal error sent")
}
func CreateRequestHandler() (*RequestHandler, error) {
var h *RequestHandler = new(RequestHandler)
h.StdLogger, h.ErrLogger = CreateStdoutLogger(os.Stdout, os.Stderr)
/* var err error
h.Git, err = CreateGitHandler(git_author, name)
if err != nil {
return nil, err
}
*/
return h, nil
}

View File

@@ -19,7 +19,6 @@ package common
*/
import (
"os"
"strings"
"testing"
)
@@ -27,8 +26,6 @@ import (
func TestPrParsing(t *testing.T) {
t.Run("Test parsing", func(t *testing.T) {
var h RequestHandler
h.StdLogger, h.ErrLogger = CreateStdoutLogger(os.Stdout, os.Stdout)
pr, err := h.parsePullRequest(strings.NewReader(samplePR_JSON))
if err != nil {
t.Fatalf("error parsing PR: %v\n", err)

View File

@@ -62,7 +62,7 @@ func (h *RequestHandler) ParsePushRequest(data io.Reader) (*PushWebhookEvent, er
return nil, fmt.Errorf("Unexpected URL for SSH repository: '%s'", action.Repository.Name)
}
h.StdLogger.Printf("Request push for repo: %s\n", action.Repository.Full_Name)
LogInfo("Request push for repo:", action.Repository.Full_Name)
h.Request = &Request{
Type: RequestType_Push,
Data: action,

View File

@@ -19,7 +19,6 @@ package common_test
*/
import (
"os"
"strings"
"testing"
@@ -27,10 +26,10 @@ import (
)
func TestPushRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stderr)
json, err := h.ParsePushRequest(strings.NewReader(examplePushJSON))
if err != nil {
t.Fatalf("failed to parser push request: %v", err)

View File

@@ -117,7 +117,7 @@ func (h *RequestHandler) ParseRepositoryRequest(dataReader io.Reader) (data *Rep
data.PrjGit = data.Repository.Ssh_Url[:repoIdx+1] + DefaultGitPrj + ".git"
h.StdLogger.Printf("Request '%s' for repo: %s\n", data.Action, data.Repository.Full_Name)
LogInfo(data.Action, "request for repo:", data.Repository.Full_Name)
if len(data.Action) < 1 {
return nil, fmt.Errorf("Request has no data.... skipping")
}

View File

@@ -19,7 +19,6 @@ package common_test
*/
import (
"os"
"strings"
"testing"
@@ -36,10 +35,10 @@ func (s *testLogger) WriteString(str2 string) (int, error) {
}
func TestRepositoryRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stdout)
json, err := h.ParseRepositoryRequest(strings.NewReader(repoCreateJSON))
if err != nil {
t.Fatalf("Can't parse struct: %s", err)

View File

@@ -52,7 +52,7 @@ func (h *RequestHandler) ParseStatusRequest(data io.Reader) (*StatusWebhookEvent
return nil, fmt.Errorf("Got error while parsing: %w", err)
}
h.StdLogger.Printf("Request status for repo: %s#%s\n", action.Repository.Full_Name, action.Sha)
LogInfo("Request status for repo:", action.Repository.Full_Name+"#"+action.Sha)
h.Request = &Request{
Type: RequestType_Status,
Data: action,

View File

@@ -1,7 +1,6 @@
package common_test
import (
"os"
"strings"
"testing"
@@ -9,10 +8,10 @@ import (
)
func TestStatusRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stdout)
json, err := h.ParseStatusRequest(strings.NewReader(requestStatusJSON))
if err != nil {
t.Fatalf("Can't parse struct: %s", err)

View File

@@ -8,6 +8,7 @@ import (
)
func TestMaintainerGroupReplacer(t *testing.T) {
common.SetTestLogger(t)
GroupName := "my_group"
tests := []struct {

View File

@@ -8,6 +8,7 @@ import (
)
func TestReviewers(t *testing.T) {
common.SetTestLogger(t)
tests := []struct {
name string
input []string

View File

@@ -4,7 +4,6 @@ import (
"errors"
"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"
@@ -142,7 +141,7 @@ func TestReviews(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
rf := mock_common.NewMockGiteaReviewTimelineFetcher(ctl)
if test.timeline == nil {

View File

@@ -10,6 +10,7 @@ import (
)
func TestSubmodulesParsing(t *testing.T) {
common.SetTestLogger(t)
tests := []struct {
name string
file string
@@ -123,6 +124,7 @@ func TestSubmodulesParsing(t *testing.T) {
}
func TestSubmodulesWriting(t *testing.T) {
common.SetTestLogger(t)
tests := []struct {
name string
subs []common.Submodule

View File

@@ -2,6 +2,7 @@
set -x
export TZ=UTC
export GIT_CONFIG_COUNT=2
export GIT_CONFIG_KEY_0=protocol.file.allow

View File

@@ -286,3 +286,4 @@ func TrimRemovedBranchSuffix(branchName string) string {
return branchName
}

View File

@@ -3,6 +3,7 @@ package common_test
import (
"reflect"
"testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common"
)
@@ -305,3 +306,8 @@ func TestNewPackageIssueParsing(t *testing.T) {
})
}
}
func NewController(t *testing.T) *gomock.Controller {
common.SetTestLogger(t)
return gomock.NewController(t)
}

View File

@@ -11,7 +11,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

@@ -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

@@ -0,0 +1,314 @@
package main
import (
"fmt"
"slices"
"strings"
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
)
func FindSourceRepository(org, repo string) (*models.Repository, error) {
srcRepo, err := Gitea.GetRepository(org, repo)
if err != nil {
return nil, err
}
if srcRepo == nil {
return nil, fmt.Errorf("Source repository not found: %s/%s", org, repo)
}
if srcRepo.Parent == nil {
return nil, fmt.Errorf("Source has no parents: %s/%s", org, repo)
}
return srcRepo, nil
}
func createEmptyBranch(git common.Git, PackageName, Branch string) {
git.GitExecOrPanic(PackageName, "checkout", "--detach")
git.GitExec(PackageName, "branch", "-D", Branch)
git.GitExecOrPanic(PackageName, "checkout", "-f", "--orphan", Branch)
git.GitExecOrPanic(PackageName, "rm", "-rf", ".")
git.GitExecOrPanic(PackageName, "commit", "--allow-empty", "-m", "Initial empty branch")
}
type TimelineInterface interface {
FindPullRequestReferences(org, repo string, idx int64, creator []string) []*models.TimelineComment
}
type Timeline []*models.TimelineComment
func (timeline *Timeline) FindIssuePullRequestRererences(org, repo string, idx int64, creator []string) []*models.TimelineComment {
ret := make([]*models.TimelineComment, 0, 1)
for _, t := range *timeline {
if t.Type == common.TimelineCommentType_PullRequestRef &&
t.RefIssue != nil &&
t.RefIssue.Repository.Owner == org &&
t.RefIssue.Repository.Name == repo &&
(idx == 0 || t.RefIssue.Index == idx) &&
(len(creator) == 0 || slices.Contains(creator, t.User.UserName)) {
ret = append(ret, t)
}
}
return ret
}
type IssueProcessorInterface interface {
IsAddIssue() bool
IsRmIssue() bool
GetTargetBranch() string
}
type IssueProcessor struct {
issue *models.Issue
IssueTimeline Timeline
TargetBranch string
}
func (i *IssueProcessor) GetTargetBranch() string {
const BranchPrefix = "refs/heads/"
branch := i.issue.Ref
if branch, found := strings.CutPrefix(branch, BranchPrefix); found {
return branch
} else {
common.LogDebug("Invalid branch specified:", branch, ". Using default.")
branch = ""
}
return branch
}
func ProcessIssue(issue *models.Issue, configs common.AutogitConfigs) error {
i := &IssueProcessor{issue: issue}
return i.ProcessIssue(configs)
}
func (i *IssueProcessor) IsAddIssue() bool {
if i == nil || i.issue == nil {
return false
}
title := i.issue.Title
return len(title) > 5 && strings.EqualFold(title[0:5], "[ADD]")
}
func (i *IssueProcessor) IsRmIssue() bool {
if i == nil || i.issue == nil {
return false
}
title := i.issue.Title
return len(title) > 4 && strings.EqualFold(title[0:4], "[RM]")
}
func (i *IssueProcessor) ProcessAddIssue(config *common.AutogitConfig) error {
issue := i.issue
org := issue.Repository.Owner
repo := issue.Repository.Name
// idx := issue.Index
// we need "New Package" label and "Approval Required" label, unless already approved
// either via Label "Approved" or via review comment.
NewIssues := common.FindNewReposInIssueBody(issue.Body)
if NewIssues == nil {
common.LogDebug("No new repos found in issue body")
return nil
}
git, err := GitHandler.CreateGitHandler(config.Organization)
if err != nil {
return err
}
defer git.Close()
for _, nr := range NewIssues.Repos {
common.LogDebug(" - Processing new repository src:", nr.Organization+"/"+nr.PackageName+"#"+nr.Branch)
targetRepo, err := Gitea.GetRepository(config.Organization, nr.PackageName)
if err != nil {
return err
}
if targetRepo == nil {
common.LogInfo(" - Repository", config.Organization+"/"+nr.PackageName, "does not exist. Labeling issue.")
if !common.IsDryRun && issue.State == "open" {
Gitea.SetLabels(org, repo, issue.Index, []string{config.Label(common.Label_NewRepository)})
}
common.LogDebug(" # Done for now with this repo")
continue
}
// check if we already have created a PR here
// TODO, we need to filter by project config permissions of target project, not just assume bot here.
users := []string{CurrentUser.UserName}
prs := i.IssueTimeline.FindIssuePullRequestRererences(config.Organization, nr.PackageName, 0, users)
for _, t := range prs {
pr, err := Gitea.GetPullRequest(config.Organization, nr.PackageName, t.RefIssue.Index)
if err != nil {
common.LogError("Failed to fetch PR", common.PRtoString(pr), ":", err)
}
if issue.State == "open" {
// PR already created, we just need to update it now
common.LogInfo("Update PR ", common.PRtoString(pr), "only... Nothing to do now")
return nil
}
// so, issue is closed .... close associated package PR
_, err = Gitea.UpdateIssue(config.Organization, nr.PackageName, t.RefIssue.Index, &models.EditIssueOption{State: "closed"})
if err != nil {
common.LogError("Failed to close associated PR", common.PRtoString(pr), ":", err)
}
// remove branch if it's a new repository.
return err
}
srcRepo, err := FindSourceRepository(nr.Organization, nr.Repository)
if err != nil {
continue
}
if len(nr.Branch) == 0 {
nr.Branch = srcRepo.DefaultBranch
}
srcRemoteName, err := git.GitClone(nr.PackageName, nr.Branch, srcRepo.SSHURL)
if err != nil {
return err
}
remoteName, err := git.GitClone(nr.PackageName, nr.Branch, targetRepo.SSHURL)
if err != nil {
return err
}
// Check that fork/parent repository relationship exists
if srcRepo.Parent.Name != targetRepo.Name || srcRepo.Parent.Owner.UserName != targetRepo.Owner.UserName {
common.LogError("Source repository is not fork of the Target repository. Fork of:", srcRepo.Parent.Owner.UserName+"/"+srcRepo.Parent.Name)
continue
}
srcBranch := nr.Branch
if srcBranch == "" {
srcBranch = srcRepo.DefaultBranch
}
// We are ready to setup a pending PR.
// 1. empty target branch with empty commit, this will be discarded no merge
// 2. create PR from source to target
// a) if source is not branch, create a source branch in target repo that contains the relevant commit
SourceCommitList := common.SplitLines(git.GitExecWithOutputOrPanic(nr.PackageName, "rev-list", "--first-parent", srcRemoteName+"/"+nr.Branch))
CommitLength := len(SourceCommitList)
SourceCommitId := SourceCommitList[CommitLength-1]
if CommitLength > 20 {
SourceCommitId = SourceCommitList[20]
}
if CommitLength < 2 {
// only 1 commit, then we need empty branch on target
if dl, err := git.GitDirectoryContentList(nr.PackageName, nr.Branch); err == nil && len(dl) > 0 {
createEmptyBranch(git, nr.PackageName, nr.Branch)
}
} else {
git.GitExecOrPanic(nr.PackageName, "checkout", "-B", nr.Branch, SourceCommitId)
}
if !common.IsDryRun {
git.GitExecOrPanic(nr.PackageName, "push", "-f", remoteName, nr.Branch)
}
head := nr.Organization + ":" + srcBranch
isBranch := false
// Hash can be branch name! Check if it's a branch or tag on the remote
out, err := git.GitExecWithOutput(nr.PackageName, "ls-remote", "--heads", srcRepo.SSHURL, srcBranch)
if err == nil && strings.Contains(out, "refs/heads/"+srcBranch) {
isBranch = true
}
if !isBranch {
tempBranch := fmt.Sprintf("new_package_%d_%s", issue.Index, nr.PackageName)
// Re-clone or use existing if branch check was done above
remoteName, err := git.GitClone(nr.PackageName, srcBranch, targetRepo.SSHURL)
if err != nil {
return err
}
git.GitExecOrPanic(nr.PackageName, "remote", "add", "source", srcRepo.SSHURL)
git.GitExecOrPanic(nr.PackageName, "fetch", "source", srcBranch)
git.GitExecOrPanic(nr.PackageName, "checkout", "-B", tempBranch, "FETCH_HEAD")
if !common.IsDryRun {
git.GitExecOrPanic(nr.PackageName, "push", "-f", remoteName, tempBranch)
}
head = tempBranch
}
title := fmt.Sprintf("Add package %s", nr.PackageName)
prjGitOrg, prjGitRepo, _ := config.GetPrjGit()
body := fmt.Sprintf("See issue %s/%s#%d", prjGitOrg, prjGitRepo, issue.Index)
br := i.TargetBranch
if len(br) == 0 {
br = targetRepo.DefaultBranch
}
pr, err, isNew := Gitea.CreatePullRequestIfNotExist(targetRepo, head, br, title, body)
if err != nil {
common.LogError(targetRepo.Name, head, i.TargetBranch, title, body)
return err
}
if !isNew && (pr.Body != body || !pr.AllowMaintainerEdit) {
Gitea.UpdatePullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index, &models.EditPullRequestOption{
AllowMaintainerEdit: true,
Body: body,
})
}
if isNew {
if _, err := Gitea.SetLabels(config.Organization, nr.PackageName, pr.Index, []string{config.Label(common.Label_NewRepository)}); err != nil {
common.LogError("Failed to set label:", common.Label_NewRepository, err)
}
}
}
return nil
}
func (i *IssueProcessor) ProcessIssue(configs common.AutogitConfigs) error {
issue := i.issue
org := issue.Repository.Owner
repo := issue.Repository.Name
idx := issue.Index
// out, _ := json.MarshalIndent(issue, "", " ")
// common.LogDebug(string(out))
var err error
i.IssueTimeline, err = Gitea.GetTimeline(org, repo, idx)
if err != nil {
common.LogError(" timeline fetch failed:", err)
return err
}
i.TargetBranch = i.GetTargetBranch()
config := configs.GetPrjGitConfig(org, repo, i.TargetBranch)
if config == nil {
return fmt.Errorf("Cannot find config for %s/%s#%s", org, repo, i.TargetBranch)
}
common.LogDebug("issue processing:", common.IssueToString(issue), "@", i.TargetBranch)
if i.IsAddIssue() {
i.ProcessAddIssue(config)
} else if i.IsRmIssue() {
// to remove a package, no approval is required. This should happen via
// project git PR reviews
} else {
common.LogError("Non-standard issue created. Ignoring", common.IssueToString(issue))
return nil
}
return nil
}

View File

@@ -0,0 +1,523 @@
package main
import (
"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"
"testing"
)
func TestProcessIssue_Add(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
Gitea = gitea
common.IsDryRun = false
gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Not(int64(999))).Return([]*models.TimelineComment{}, nil).AnyTimes()
CurrentUser = &models.User{UserName: "bot-user"}
config := &common.AutogitConfig{
Organization: "target-org",
GitProjectName: "test-org/test-prj#main",
}
configs := []*common.AutogitConfig{config}
issue := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#master",
Index: 123,
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "test-prj",
},
Ref: "refs/heads/main",
State: "open",
}
expectedBody := "See issue test-org/test-prj#123"
t.Run("Repository does not exist - labels issue", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(nil, nil)
gitea.EXPECT().SetLabels("test-org", "test-prj", int64(123), []string{"new/New Repository"}).Return(nil, nil)
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Source is SHA - creates temp branch in target", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
SSHURL: "target-ssh-url",
Owner: &models.User{UserName: "target-org"},
}
srcRepo := &models.Repository{
Name: "pkg1",
SSHURL: "src-ssh-url",
DefaultBranch: "master",
Owner: &models.User{UserName: "src-org"},
Parent: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
}
sha := "abcdef0123456789abcdef0123456789abcdef01"
issueSHA := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#" + sha,
Index: 123,
Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"},
Ref: "refs/heads/main",
State: "open",
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", sha, "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", sha, "target-ssh-url").Return("origin", nil)
// Source commit list and reset logic
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha).Return(sha + "\n" + "parent-sha")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha, "parent-sha")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha)
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha).Return("", nil)
// SHA source logic (creates temp branch)
tempBranch := "new_package_123_pkg1"
mockGit.EXPECT().GitClone("pkg1", sha, "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url")
mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha)
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch)
// PR creation using temp branch
pr := &models.PullRequest{
Index: 456,
Body: expectedBody,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, true)
gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil)
err := ProcessIssue(issueSHA, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Repository exists - continue processing and create PR", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
SSHURL: "target-ssh-url",
Owner: &models.User{UserName: "target-org"},
}
srcRepo := &models.Repository{
Name: "pkg1",
SSHURL: "src-ssh-url",
DefaultBranch: "master",
Owner: &models.User{UserName: "src-org"},
Parent: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil)
// Commit list logic
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1\nsha2")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", "master", "sha2")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master")
// Check if source is a branch via ls-remote
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil)
// PR creation
pr := &models.PullRequest{
Index: 456,
Body: expectedBody,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, true)
gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil)
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Source repository is not fork of target repository - aborts", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
SSHURL: "target-ssh-url",
Owner: &models.User{UserName: "target-org"},
}
srcRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "src-org"},
SSHURL: "src-ssh-url",
Parent: &models.Repository{
Name: "other-repo",
Owner: &models.User{UserName: "other-org"},
},
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil)
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Source repository is fork of target repository - proceeds", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
SSHURL: "target-ssh-url",
}
srcRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "src-org"},
SSHURL: "src-ssh-url",
Parent: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
DefaultBranch: "master",
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1\nsha2")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", "master", "sha2")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master")
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil)
pr := &models.PullRequest{
Index: 456,
Body: expectedBody,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, false)
gitea.EXPECT().UpdatePullRequest("target-org", "pkg1", int64(456), gomock.Any())
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Source repository has no parent (not a fork) - aborts", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
SSHURL: "target-ssh-url",
}
srcRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "src-org"},
SSHURL: "src-ssh-url",
Parent: nil,
DefaultBranch: "master",
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Target branch missing - creates orphan branch", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
SSHURL: "target-ssh-url",
Owner: &models.User{UserName: "target-org"},
}
srcRepo := &models.Repository{
Name: "pkg1",
SSHURL: "src-ssh-url",
DefaultBranch: "master",
Owner: &models.User{UserName: "src-org"},
Parent: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil)
// Branch check - rev-list works but says only 1 commit
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1")
// Orphan branch creation via createEmptyBranch
mockGit.EXPECT().GitDirectoryContentList("pkg1", "master").Return(map[string]string{"file": "sha"}, nil)
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "--detach")
mockGit.EXPECT().GitExec("pkg1", "branch", "-D", "master")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-f", "--orphan", "master")
mockGit.EXPECT().GitExecOrPanic("pkg1", "rm", "-rf", ".")
mockGit.EXPECT().GitExecOrPanic("pkg1", "commit", "--allow-empty", "-m", "Initial empty branch")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master")
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil)
// PR creation
pr := &models.PullRequest{
Index: 456,
Body: expectedBody,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, true)
gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil)
err := ProcessIssue(issue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Config not found", func(t *testing.T) {
issueNoConfig := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#master",
Index: 123,
Repository: &models.RepositoryMeta{
Owner: "other-org",
Name: "other-prj",
},
Ref: "refs/heads/main",
State: "open",
}
err := ProcessIssue(issueNoConfig, configs)
if err == nil || err.Error() != "Cannot find config for other-org/other-prj#main" {
t.Errorf("Expected config not found error, got %v", err)
}
})
t.Run("No repos in body", func(t *testing.T) {
err := ProcessIssue(&models.Issue{
Title: "[ADD] pkg1",
Body: "nothing here",
Ref: "refs/heads/main",
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "test-prj",
},
State: "open",
}, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("Source SHA update - updates existing temp branch", func(t *testing.T) {
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil).Times(2)
mockGit.EXPECT().Close().Return(nil).Times(2)
targetRepo := &models.Repository{
Name: "pkg1",
SSHURL: "target-ssh-url",
Owner: &models.User{UserName: "target-org"},
}
srcRepo := &models.Repository{
Name: "pkg1",
SSHURL: "src-ssh-url",
DefaultBranch: "master",
Owner: &models.User{UserName: "src-org"},
Parent: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
}
sha1 := "abcdef0123456789abcdef0123456789abcdef01"
issue1 := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#" + sha1,
Index: 123,
Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"},
Ref: "refs/heads/main",
State: "open",
}
// First call expectations
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", sha1, "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", sha1, "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha1).Return(sha1 + "\n" + "parent")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha1, "parent")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha1)
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha1).Return("", nil)
tempBranch := "new_package_123_pkg1"
mockGit.EXPECT().GitClone("pkg1", sha1, "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url")
mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha1)
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch)
pr := &models.PullRequest{
Index: 456,
Body: expectedBody,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, true)
gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil)
err := ProcessIssue(issue1, configs)
if err != nil {
t.Errorf("First call failed: %v", err)
}
// Second call with different SHA
sha2 := "0123456789abcdef0123456789abcdef01234567"
issue2 := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#" + sha2,
Index: 123,
Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"},
Ref: "refs/heads/main",
State: "open",
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil)
mockGit.EXPECT().GitClone("pkg1", sha2, "src-ssh-url").Return("src-remote", nil)
mockGit.EXPECT().GitClone("pkg1", sha2, "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha2).Return(sha2 + "\n" + "parent")
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha2, "parent")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha2)
mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha2).Return("", nil)
mockGit.EXPECT().GitClone("pkg1", sha2, "target-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url")
mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha2)
mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD")
mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch)
// CreatePullRequestIfNotExist should be called with same tempBranch, return existing PR
gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, false)
gitea.EXPECT().UpdatePullRequest("target-org", "pkg1", int64(456), gomock.Any())
err = ProcessIssue(issue2, configs)
if err != nil {
t.Errorf("Second call failed: %v", err)
}
})
t.Run("PR already exists and issue is open - does nothing", func(t *testing.T) {
issue999 := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#master",
Index: 999,
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "test-prj",
},
Ref: "refs/heads/main",
State: "open",
}
timeline := []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 456,
Repository: &models.RepositoryMeta{
Owner: "target-org",
Name: "pkg1",
},
},
User: &models.User{UserName: "bot-user"},
},
}
// We need to override the default GetTimeline mock
gitea.EXPECT().GetTimeline("test-org", "test-prj", int64(999)).Return(timeline, nil)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
pr := &models.PullRequest{
Index: 456,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(456)).Return(pr, nil)
err := ProcessIssue(issue999, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
t.Run("PR already exists and issue is closed - closes PR", func(t *testing.T) {
closedIssue := &models.Issue{
Title: "[ADD] pkg1",
Body: "src-org/pkg1#master",
Index: 999,
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "test-prj",
},
Ref: "refs/heads/main",
State: "closed",
}
timeline := []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 456,
Repository: &models.RepositoryMeta{
Owner: "target-org",
Name: "pkg1",
},
},
User: &models.User{UserName: "bot-user"},
},
}
gitea.EXPECT().GetTimeline("test-org", "test-prj", int64(999)).Return(timeline, nil)
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
GitHandler = mockGitGen
mockGit := mock_common.NewMockGit(ctl)
mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil)
mockGit.EXPECT().Close().Return(nil)
targetRepo := &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
}
gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil)
pr := &models.PullRequest{
Index: 456,
Base: &models.PRBranchInfo{
Repo: targetRepo,
},
}
gitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(456)).Return(pr, nil)
gitea.EXPECT().UpdateIssue("target-org", "pkg1", int64(456), gomock.Any()).Return(nil, nil)
err := ProcessIssue(closedIssue, configs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
}

View File

@@ -146,15 +146,22 @@ func main() {
num, err := strconv.ParseInt(data[3], 10, 64)
common.LogInfo("Processing:", org, "/", repo, "#", num)
common.PanicOnError(err)
pr, err := Gitea.GetPullRequest(org, repo, num)
if err != nil {
common.LogError("Cannot fetch PR", err)
if pr, err := Gitea.GetPullRequest(org, repo, num); err == nil && pr != nil {
if err = ProcesPullRequest(pr, configs); err != nil {
common.LogError("PR processor returned error", err)
}
} else if issue, err := Gitea.GetIssue(org, repo, num); err == nil && issue != nil {
processor := &IssueProcessor{
issue: issue,
}
if err = processor.ProcessIssue(configs); err != nil {
common.LogError("issue processor returned error:", err)
}
} else {
common.LogError("Cannot fetch PR or Issue", err)
return
}
if err = ProcesPullRequest(pr, configs); err != nil {
common.LogError("processor returned error", err)
}
}
return

View File

@@ -14,6 +14,7 @@ import (
)
func TestProjectBranchName(t *testing.T) {
common.SetTestLogger(t)
branchName := prGitBranchNameForPR("testingRepo", 10)
if branchName != "PR_testingRepo#10" {
t.Error("Unexpected branch name:", branchName)
@@ -21,6 +22,7 @@ func TestProjectBranchName(t *testing.T) {
}
func TestUpdatePrBranch(t *testing.T) {
common.SetTestLogger(t)
var buf bytes.Buffer
origLogger := log.Writer()
log.SetOutput(&buf)
@@ -58,6 +60,7 @@ func TestUpdatePrBranch(t *testing.T) {
}
func TestCreatePrBranch(t *testing.T) {
common.SetTestLogger(t)
var buf bytes.Buffer
origLogger := log.Writer()
log.SetOutput(&buf)

View File

@@ -24,7 +24,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 +32,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 +42,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 {
@@ -194,7 +190,14 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
}
if !submodule_found {
common.LogError("Failed to find expected repo:", repo)
common.LogInfo("Adding new submodule", repo, "to PrjGit")
ref := fmt.Sprintf(common.PrPattern, org, repo, idx)
commitMsg := fmt.Sprintln("Add package", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nreferencing PRs:\n", ref)
git.GitExecOrPanic(common.DefaultGitPrj, "submodule", "add", "-b", pr.PR.Base.Name, pr.PR.Base.Repo.SSHURL, repo)
updateSubmoduleInPR(repo, prHead, git)
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
}
}
return nil
@@ -615,6 +618,14 @@ type RequestProcessor struct {
recursive int
}
func (w *RequestProcessor) Process(pr *models.PullRequest) error {
configs, ok := w.configuredRepos[pr.Base.Repo.Owner.UserName]
if !ok {
return fmt.Errorf("no config found for org %s", pr.Base.Repo.Owner.UserName)
}
return ProcesPullRequest(pr, configs)
}
func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error {
if len(configs) < 1 {
// ignoring pull request against unconfigured project (could be just regular sources?)
@@ -631,15 +642,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 {
@@ -668,6 +670,21 @@ func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
return err
}
} else if req, ok := request.Data.(*common.IssueWebhookEvent); ok {
issue, err := Gitea.GetIssue(req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
if err != nil {
common.LogError("Cannot find issue for issue event:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
return err
}
configs, ok := w.configuredRepos[req.Repository.Owner.Username]
if !ok {
common.LogError("*** Cannot find config for org:", req.Repository.Owner.Username)
return nil
}
processor := &IssueProcessor{
issue: issue,
}
return processor.ProcessIssue(configs)
} else {
common.LogError("*** Invalid data format for PR processing.")
return fmt.Errorf("*** Invalid data format for PR processing.")

View File

@@ -109,7 +109,7 @@ func TestOpenPR(t *testing.T) {
}
t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea
@@ -156,7 +156,7 @@ func TestOpenPR(t *testing.T) {
})
t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -210,7 +210,7 @@ func TestOpenPR(t *testing.T) {
})
t.Run("Cannot create prjgit repository", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -259,7 +259,7 @@ func TestOpenPR(t *testing.T) {
}
})
t.Run("Cannot create PR", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -311,7 +311,7 @@ func TestOpenPR(t *testing.T) {
}
})
t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

View File

@@ -11,6 +11,7 @@ import (
)
func TestSyncPR(t *testing.T) {
CurrentUser = &models.User{UserName: "testuser"}
config := &common.AutogitConfig{
Reviewers: []string{"reviewer1", "reviewer2"},
Branch: "testing",
@@ -73,7 +74,7 @@ func TestSyncPR(t *testing.T) {
}
t.Run("PR_sync_request_against_PrjGit_==_no_action", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -108,7 +109,7 @@ func TestSyncPR(t *testing.T) {
})
t.Run("Missing PrjGit PR for the sync", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -130,7 +131,7 @@ func TestSyncPR(t *testing.T) {
})
t.Run("PR sync", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -154,4 +155,3 @@ func TestSyncPR(t *testing.T) {
}
})
}

View File

@@ -64,7 +64,7 @@ func TestPrjGitDescription(t *testing.T) {
}
func TestAllocatePRProcessor(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
@@ -104,7 +104,7 @@ func TestAllocatePRProcessor(t *testing.T) {
}
func TestAllocatePRProcessor_Failures(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
@@ -154,7 +154,7 @@ func TestAllocatePRProcessor_Failures(t *testing.T) {
}
func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -178,7 +178,7 @@ func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) {
}
func TestSetSubmodulesToMatchPRSet(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -226,7 +226,7 @@ func TestSetSubmodulesToMatchPRSet(t *testing.T) {
}
func TestRebaseAndSkipSubmoduleCommits(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -299,7 +299,7 @@ func TestRebaseAndSkipSubmoduleCommits(t *testing.T) {
}
func TestUpdatePrjGitPR(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -547,7 +547,7 @@ func TestUpdatePrjGitPR(t *testing.T) {
}
func TestCreatePRjGitPR_Integration(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -667,7 +667,7 @@ func TestMultiPackagePRSet(t *testing.T) {
}
func TestPRProcessor_Process_EdgeCases(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl)
@@ -791,7 +791,7 @@ func TestPRProcessor_Process_EdgeCases(t *testing.T) {
}
func TestVerifyRepositoryConfiguration(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
@@ -837,7 +837,7 @@ func TestVerifyRepositoryConfiguration(t *testing.T) {
}
func TestProcessFunc(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)

View File

@@ -13,7 +13,7 @@ import (
)
func TestPrjGitSubmoduleCheck(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
@@ -94,7 +94,7 @@ func TestPrjGitSubmoduleCheck(t *testing.T) {
}
func TestPrjGitSubmoduleCheck_Failures(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
@@ -152,7 +152,7 @@ func TestPullRequestToEventState(t *testing.T) {
}
func TestDefaultStateChecker_ProcessPR(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
@@ -200,7 +200,7 @@ func TestDefaultStateChecker_ProcessPR(t *testing.T) {
}
func TestDefaultStateChecker_VerifyProjectState(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)
@@ -264,7 +264,7 @@ func TestDefaultStateChecker_VerifyProjectState(t *testing.T) {
}
func TestDefaultStateChecker_CheckRepos(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl)

View File

@@ -20,7 +20,7 @@ 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)
ctl := NewController(t)
state := NewMockStateChecker(ctl)
c.i = state
state.EXPECT().CheckRepos().Do(func() {
@@ -40,7 +40,7 @@ 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)
ctl := NewController(t)
state := NewMockStateChecker(ctl)
c.i = state
@@ -62,7 +62,7 @@ func TestRepoCheck(t *testing.T) {
})
t.Run("CheckRepos() calls CheckProjectState() for each project", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
state := NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -99,7 +99,7 @@ func TestRepoCheck(t *testing.T) {
})
t.Run("CheckRepos errors", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
state := NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -145,7 +145,7 @@ func TestVerifyProjectState(t *testing.T) {
defer log.SetOutput(oldOut)
t.Run("Project state with no PRs", func(t *testing.T) {
ctl := gomock.NewController(t)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -191,7 +191,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)
ctl := NewController(t)
gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

View File

@@ -7,8 +7,14 @@ import (
"testing"
"src.opensuse.org/autogits/common"
"go.uber.org/mock/gomock"
)
func NewController(t *testing.T) *gomock.Controller {
common.SetTestLogger(t)
return gomock.NewController(t)
}
const LocalCMD = "---"
func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) {
@@ -56,6 +62,7 @@ func commandsForPackages(dir, prefix string, startN, endN int) [][]string {
func setupGitForTests(t *testing.T, git *common.GitHandlerImpl) {
common.ExtraGitParams = []string{
"TZ=UTC",
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always",