Compare commits
2 Commits
fix-multip
...
submodulem
| Author | SHA256 | Date | |
|---|---|---|---|
| 03868953fd | |||
| 914149df7d |
@@ -1,52 +0,0 @@
|
||||
name: Integration tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HOME: /var/lib/gitea-runner
|
||||
REPO_URL: http://src.opensuse.org//git-workflow/autogits.git
|
||||
|
||||
jobs:
|
||||
t:
|
||||
runs-on: linux-x86_64
|
||||
steps:
|
||||
- name: whoami
|
||||
run: whoami
|
||||
- name: pwd
|
||||
run: pwd
|
||||
- name: vars
|
||||
run: |
|
||||
set | grep GITEA_
|
||||
- name: Clone
|
||||
run: |
|
||||
git clone -q ${{ env.REPO_URL }}
|
||||
- name: Checkout
|
||||
run: |
|
||||
echo ${{ gitea.ref }}
|
||||
git fetch origin ${{ gitea.ref }}
|
||||
git checkout FETCH_HEAD
|
||||
working-directory: ./autogits
|
||||
- name: Prepare binaries
|
||||
run: make build
|
||||
working-directory: ./autogits
|
||||
- name: Prepare images
|
||||
run: make build
|
||||
working-directory: ./autogits/integration
|
||||
- name: Make sure the pod is down
|
||||
run: make down
|
||||
working-directory: ./autogits/integration
|
||||
- name: Start images
|
||||
run: make up
|
||||
working-directory: ./autogits/integration
|
||||
- name: Run tests
|
||||
run: py.test-3.11 -v tests
|
||||
working-directory: ./autogits/integration
|
||||
- name: Make sure the pod is down
|
||||
if: always()
|
||||
run: make down
|
||||
working-directory: ./autogits/integration
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
*.osc
|
||||
*.conf
|
||||
!/integration/**/*.conf
|
||||
/integration/gitea-data
|
||||
/integration/gitea-logs
|
||||
/integration/rabbitmq-data
|
||||
/integration/workflow-pr-repos
|
||||
__pycache__/
|
||||
utils/gitmodules-automerge/gitmodules-automerge
|
||||
utils/hujson/hujson
|
||||
|
||||
@@ -98,6 +98,7 @@ Provides: /usr/bin/hujson
|
||||
|
||||
%description utils
|
||||
HuJSON to JSON parser, using stdin -> stdout pipe
|
||||
gitmodules-automerge fixes conflicts in .gitmodules conflicts during merge
|
||||
|
||||
|
||||
%package workflow-direct
|
||||
@@ -132,6 +133,9 @@ go build \
|
||||
go build \
|
||||
-C utils/maintainer-update \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C utils/gitmodules-automerge \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C gitea-events-rabbitmq-publisher \
|
||||
-buildmode=pie
|
||||
@@ -187,6 +191,7 @@ install -D -m0644 systemd/workflow-pr@.service
|
||||
install -D -m0644 systemd/workflow-pr.target %{buildroot}%{_unitdir}/workflow-pr.target
|
||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
|
||||
install -D -m0755 utils/gitmodules-automerge/gitmodules-automerge %{buildroot}%{_bindir}/gitmodules-automerge
|
||||
|
||||
%pre gitea-events-rabbitmq-publisher
|
||||
%service_add_pre gitea-events-rabbitmq-publisher.service
|
||||
@@ -306,6 +311,7 @@ install -D -m0755 utils/maintainer-update/maintainer-update
|
||||
%license COPYING
|
||||
%{_bindir}/hujson
|
||||
%{_bindir}/maintainer-update
|
||||
%{_bindir}/gitmodules-automerge
|
||||
|
||||
%files workflow-direct
|
||||
%license COPYING
|
||||
|
||||
@@ -14,7 +14,6 @@ func newStringScanner(s string) *bufio.Scanner {
|
||||
}
|
||||
|
||||
func TestAssociatedPRScanner(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
testTable := []struct {
|
||||
name string
|
||||
input string
|
||||
@@ -96,7 +95,6 @@ func TestAssociatedPRScanner(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppendingPRsToDescription(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
testTable := []struct {
|
||||
name string
|
||||
desc string
|
||||
|
||||
@@ -67,7 +67,6 @@ const (
|
||||
Label_StagingAuto = "staging/Auto"
|
||||
Label_ReviewPending = "review/Pending"
|
||||
Label_ReviewDone = "review/Done"
|
||||
Label_NewRepository = "new/New Repository"
|
||||
)
|
||||
|
||||
func LabelKey(tag_value string) string {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
@@ -53,7 +54,7 @@ func TestConfigLabelParser(t *testing.T) {
|
||||
DefaultBranch: "master",
|
||||
}
|
||||
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
|
||||
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
|
||||
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
|
||||
@@ -175,7 +176,7 @@ func TestConfigWorkflowParser(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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)
|
||||
|
||||
296
common/git_parser.go
Normal file
296
common/git_parser.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
GitStatus_Untracked = 0
|
||||
GitStatus_Modified = 1
|
||||
GitStatus_Ignored = 2
|
||||
GitStatus_Unmerged = 3 // States[0..3] -- Stage1, Stage2, Stage3 of merge objects
|
||||
GitStatus_Renamed = 4 // orig name in States[0]
|
||||
)
|
||||
|
||||
type GitStatusData struct {
|
||||
Path string
|
||||
Status int
|
||||
States [3]string
|
||||
|
||||
/*
|
||||
<sub> A 4 character field describing the submodule state.
|
||||
"N..." when the entry is not a submodule.
|
||||
"S<c><m><u>" when the entry is a submodule.
|
||||
<c> is "C" if the commit changed; otherwise ".".
|
||||
<m> is "M" if it has tracked changes; otherwise ".".
|
||||
<u> is "U" if there are untracked changes; otherwise ".".
|
||||
*/
|
||||
SubmoduleChanges string
|
||||
}
|
||||
|
||||
func parseGit_HexString(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 32)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch {
|
||||
case c == 0 || c == ' ':
|
||||
return string(str), nil
|
||||
case c >= 'a' && c <= 'f':
|
||||
case c >= 'A' && c <= 'F':
|
||||
case c >= '0' && c <= '9':
|
||||
default:
|
||||
return "", errors.New("Invalid character in hex string:" + string(c))
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
func parseGit_String(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
||||
}
|
||||
if c == 0 || c == ' ' {
|
||||
return string(str), nil
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
|
||||
func parseGit_StringWithSpace(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
||||
}
|
||||
if c == 0 {
|
||||
return string(str), nil
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
|
||||
func skipGitStatusEntry(data io.ByteReader, skipSpaceLen int) error {
|
||||
for skipSpaceLen > 0 {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == ' ' {
|
||||
skipSpaceLen--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
ret := GitStatusData{}
|
||||
statusType, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch statusType {
|
||||
case '1':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 8); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Modified
|
||||
ret.Path, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '2':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 9); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Renamed
|
||||
ret.Path, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.States[0], err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '?':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Untracked
|
||||
ret.Path, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '!':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Ignored
|
||||
ret.Path, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 'u':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.SubmoduleChanges, err = parseGit_String(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = skipGitStatusEntry(data, 4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ret.States[0], err = parseGit_HexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.States[1], err = parseGit_HexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.States[2], err = parseGit_HexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Unmerged
|
||||
ret.Path, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("Invalid status type" + string(statusType))
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func parseGitStatusData(data io.ByteReader) (Data, error) {
|
||||
ret := make([]GitStatusData, 0, 10)
|
||||
for {
|
||||
data, err := parseSingleStatusEntry(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
ret = append(ret, *data)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type Data interface{}
|
||||
|
||||
type CommitStatus int
|
||||
|
||||
const (
|
||||
Add CommitStatus = iota
|
||||
Rm
|
||||
Copy
|
||||
Modify
|
||||
Rename
|
||||
TypeChange
|
||||
Unmerged
|
||||
Unknown
|
||||
)
|
||||
|
||||
type GitDiffRawData struct {
|
||||
SrcMode, DstMode string
|
||||
SrcCommit, DstCommit string
|
||||
Status CommitStatus
|
||||
Src, Dst string
|
||||
}
|
||||
|
||||
func parseGit_DiffIndexStatus(data io.ByteReader, d *GitDiffRawData) error {
|
||||
b, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch b {
|
||||
case 'A':
|
||||
d.Status = Add
|
||||
case 'C':
|
||||
d.Status = Copy
|
||||
case 'D':
|
||||
d.Status = Rm
|
||||
case 'M':
|
||||
d.Status = Modify
|
||||
case 'R':
|
||||
d.Status = Rename
|
||||
case 'T':
|
||||
d.Status = TypeChange
|
||||
case 'U':
|
||||
d.Status = Unmerged
|
||||
case 'X':
|
||||
return fmt.Errorf("Unexpected unknown change type. This is a git bug")
|
||||
}
|
||||
_, err = parseGit_StringWithSpace(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSingleGitDiffIndexRawData(data io.ByteReader) (*GitDiffRawData, error) {
|
||||
var ret GitDiffRawData
|
||||
|
||||
b, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b != ':' {
|
||||
return nil, fmt.Errorf("Expected ':' but got '%s'", string(b))
|
||||
}
|
||||
|
||||
if ret.SrcMode, err = parseGit_String(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.DstMode, err = parseGit_String(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Src, err = parseGit_String(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Dst, err = parseGit_String(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = parseGit_DiffIndexStatus(data, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Dst = ret.Src
|
||||
switch ret.Status {
|
||||
case Copy, Rename:
|
||||
if ret.Src, err = parseGit_StringWithSpace(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func parseGitDiffIndexRawData(data io.ByteReader) (Data, error) {
|
||||
ret := make([]GitDiffRawData, 0, 10)
|
||||
for {
|
||||
data, err := parseSingleGitDiffIndexRawData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
ret = append(ret, *data)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@@ -19,9 +19,7 @@ package common
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -42,7 +40,11 @@ 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 GitSubmoduleFileConflictResolver interface {
|
||||
GitResolveConflicts(cwd, MergeBase, Head, MergeHead string) error
|
||||
GitResolveSubmoduleFileConflict(s GitStatusData, cwd, mergeBase, head, mergeHead string) error
|
||||
}
|
||||
|
||||
type GitStatusLister interface {
|
||||
@@ -76,6 +78,7 @@ type Git interface {
|
||||
GitExecQuietOrPanic(cwd string, params ...string)
|
||||
|
||||
GitDiffLister
|
||||
GitSubmoduleFileConflictResolver
|
||||
}
|
||||
|
||||
type GitHandlerImpl struct {
|
||||
@@ -273,11 +276,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
|
||||
LogDebug("branch", branch)
|
||||
}
|
||||
*/
|
||||
args := []string{"fetch", "--prune", remoteName}
|
||||
if len(branch) > 0 {
|
||||
args = append(args, branch)
|
||||
}
|
||||
|
||||
args := []string{"fetch", "--prune", remoteName, branch}
|
||||
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
|
||||
args = slices.Insert(args, 1, "--unshallow")
|
||||
}
|
||||
@@ -362,13 +361,20 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
|
||||
cmd.Env = []string{
|
||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||
"GIT_CONFIG_GLOBAL=/dev/null",
|
||||
"GIT_AUTHOR_NAME=" + e.GitCommiter,
|
||||
"GIT_COMMITTER_NAME=" + e.GitCommiter,
|
||||
"EMAIL=not@exist@src.opensuse.org",
|
||||
"GIT_LFS_SKIP_SMUDGE=1",
|
||||
"GIT_LFS_SKIP_PUSH=1",
|
||||
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes" + identityFile,
|
||||
}
|
||||
if len(e.GitEmail) > 0 {
|
||||
cmd.Env = append(cmd.Env, "EMAIL="+e.GitEmail)
|
||||
}
|
||||
if len(e.GitCommiter) > 0 {
|
||||
cmd.Env = append(cmd.Env,
|
||||
"GIT_AUTHOR_NAME="+e.GitCommiter,
|
||||
"GIT_COMMITTER_NAME="+e.GitCommiter,
|
||||
)
|
||||
}
|
||||
|
||||
if len(ExtraGitParams) > 0 {
|
||||
cmd.Env = append(cmd.Env, ExtraGitParams...)
|
||||
}
|
||||
@@ -792,7 +798,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
|
||||
return
|
||||
}
|
||||
|
||||
// return (directory) -> (hash) map for all submodules
|
||||
// return (filename) -> (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)
|
||||
@@ -866,82 +872,6 @@ 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
|
||||
@@ -1087,193 +1017,10 @@ func (e *GitHandlerImpl) GitSubmoduleCommitId(cwd, packageName, commitId string)
|
||||
return subCommitId, len(subCommitId) > 0
|
||||
}
|
||||
|
||||
const (
|
||||
GitStatus_Untracked = 0
|
||||
GitStatus_Modified = 1
|
||||
GitStatus_Ignored = 2
|
||||
GitStatus_Unmerged = 3 // States[0..3] -- Stage1, Stage2, Stage3 of merge objects
|
||||
GitStatus_Renamed = 4 // orig name in States[0]
|
||||
)
|
||||
|
||||
type GitStatusData struct {
|
||||
Path string
|
||||
Status int
|
||||
States [3]string
|
||||
|
||||
/*
|
||||
<sub> A 4 character field describing the submodule state.
|
||||
"N..." when the entry is not a submodule.
|
||||
"S<c><m><u>" when the entry is a submodule.
|
||||
<c> is "C" if the commit changed; otherwise ".".
|
||||
<m> is "M" if it has tracked changes; otherwise ".".
|
||||
<u> is "U" if there are untracked changes; otherwise ".".
|
||||
*/
|
||||
SubmoduleChanges string
|
||||
}
|
||||
|
||||
func parseGitStatusHexString(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 32)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch {
|
||||
case c == 0 || c == ' ':
|
||||
return string(str), nil
|
||||
case c >= 'a' && c <= 'f':
|
||||
case c >= 'A' && c <= 'F':
|
||||
case c >= '0' && c <= '9':
|
||||
default:
|
||||
return "", errors.New("Invalid character in hex string:" + string(c))
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
func parseGitStatusString(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
||||
}
|
||||
if c == 0 || c == ' ' {
|
||||
return string(str), nil
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
|
||||
func parseGitStatusStringWithSpace(data io.ByteReader) (string, error) {
|
||||
str := make([]byte, 0, 100)
|
||||
for {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return "", errors.New("Unexpected EOF. Expected NUL string term")
|
||||
}
|
||||
if c == 0 {
|
||||
return string(str), nil
|
||||
}
|
||||
str = append(str, c)
|
||||
}
|
||||
}
|
||||
|
||||
func skipGitStatusEntry(data io.ByteReader, skipSpaceLen int) error {
|
||||
for skipSpaceLen > 0 {
|
||||
c, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == ' ' {
|
||||
skipSpaceLen--
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSingleStatusEntry(data io.ByteReader) (*GitStatusData, error) {
|
||||
ret := GitStatusData{}
|
||||
statusType, err := data.ReadByte()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch statusType {
|
||||
case '1':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 8); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Modified
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '2':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 9); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Renamed
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.States[0], err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '?':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Untracked
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case '!':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Ignored
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 'u':
|
||||
var err error
|
||||
if err = skipGitStatusEntry(data, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.SubmoduleChanges, err = parseGitStatusString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = skipGitStatusEntry(data, 4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ret.States[0], err = parseGitStatusHexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.States[1], err = parseGitStatusHexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.States[2], err = parseGitStatusHexString(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Status = GitStatus_Unmerged
|
||||
ret.Path, err = parseGitStatusStringWithSpace(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("Invalid status type" + string(statusType))
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func parseGitStatusData(data io.ByteReader) ([]GitStatusData, error) {
|
||||
ret := make([]GitStatusData, 0, 10)
|
||||
for {
|
||||
data, err := parseSingleStatusEntry(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if data == nil {
|
||||
break
|
||||
}
|
||||
|
||||
ret = append(ret, *data)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) {
|
||||
LogDebug("getting git-status()")
|
||||
|
||||
cmd := exec.Command("/usr/bin/git", "status", "--porcelain=2", "-z")
|
||||
func (e *GitHandlerImpl) GitExecWithDataParse(cwd string, dataprocessor func(io.ByteReader) (Data, error), gitcmd string, args ...string) (Data, error) {
|
||||
LogDebug("getting", gitcmd)
|
||||
args = append([]string{gitcmd}, args...)
|
||||
cmd := exec.Command("/usr/bin/git", args...)
|
||||
cmd.Env = []string{
|
||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||
"GIT_LFS_SKIP_SMUDGE=1",
|
||||
@@ -1290,7 +1037,12 @@ func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error)
|
||||
LogError("Error running command", cmd.Args, err)
|
||||
}
|
||||
|
||||
return parseGitStatusData(bufio.NewReader(bytes.NewReader(out)))
|
||||
return dataprocessor(bytes.NewReader(out))
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitStatus(cwd string) (ret []GitStatusData, err error) {
|
||||
data, err := e.GitExecWithDataParse(cwd, parseGitStatusData, "status", "--porcelain=2", "-z")
|
||||
return data.([]GitStatusData), err
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
||||
@@ -1315,3 +1067,122 @@ func (e *GitHandlerImpl) GitDiff(cwd, base, head string) (string, error) {
|
||||
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitDiffIndex(cwd, commit string) ([]GitDiffRawData, error) {
|
||||
data, err := e.GitExecWithDataParse("diff-index", parseGitDiffIndexRawData, cwd, "diff-index", "-z", "--raw", "--full-index", "--submodule=short", "HEAD")
|
||||
return data.([]GitDiffRawData), err
|
||||
}
|
||||
|
||||
func (git *GitHandlerImpl) GitResolveConflicts(cwd, mergeBase, head, mergeHead string) error {
|
||||
status, err := git.GitStatus(cwd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Status failed: %w", err)
|
||||
}
|
||||
|
||||
// we can only resolve conflicts with .gitmodules
|
||||
for _, s := range status {
|
||||
if s.Status == GitStatus_Unmerged && s.Path == ".gitmodules" {
|
||||
if err := git.GitResolveSubmoduleFileConflict(s, cwd, mergeBase, head, mergeHead); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if s.Status == GitStatus_Unmerged {
|
||||
return fmt.Errorf("Cannot automatically resolve conflict: %s", s.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return git.GitExec(cwd, "-c", "core.editor=true", "merge", "--continue")
|
||||
}
|
||||
|
||||
func (git *GitHandlerImpl) GitResolveSubmoduleFileConflict(s GitStatusData, cwd, mergeBase, head, mergeHead string) error {
|
||||
submodules1, err := git.GitSubmoduleList(cwd, mergeBase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||
}
|
||||
/*
|
||||
submodules2, err := git.GitSubmoduleList(cwd, head)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||
}
|
||||
*/
|
||||
submodules3, err := git.GitSubmoduleList(cwd, mergeHead)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||
}
|
||||
|
||||
// find modified submodules in the mergeHead
|
||||
modifiedSubmodules := make([]string, 0, 10)
|
||||
removedSubmodules := make([]string, 0, 10)
|
||||
addedSubmodules := make([]string, 0, 10)
|
||||
|
||||
for submodulePath, oldHash := range submodules1 {
|
||||
if newHash, found := submodules3[submodulePath]; found && newHash != oldHash {
|
||||
modifiedSubmodules = append(modifiedSubmodules, submodulePath)
|
||||
} else if !found {
|
||||
removedSubmodules = append(removedSubmodules, submodulePath)
|
||||
}
|
||||
}
|
||||
for submodulePath, _ := range submodules3 {
|
||||
if _, found := submodules1[submodulePath]; !found {
|
||||
addedSubmodules = append(addedSubmodules, submodulePath)
|
||||
}
|
||||
}
|
||||
|
||||
// We need to adjust the `submodules` list by the pending changes to the index
|
||||
s1, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
s2, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
s3, err := git.GitExecWithOutput(cwd, "cat-file", "blob", s.States[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
|
||||
_, err = ParseSubmodulesFile(strings.NewReader(s1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
subs2, err := ParseSubmodulesFile(strings.NewReader(s2))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
subs3, err := ParseSubmodulesFile(strings.NewReader(s3))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
|
||||
overrideSet := make([]Submodule, 0, len(addedSubmodules)+len(modifiedSubmodules))
|
||||
for i := range subs3 {
|
||||
if slices.Contains(addedSubmodules, subs3[i].Path) || slices.Contains(modifiedSubmodules, subs3[i].Path) {
|
||||
overrideSet = append(overrideSet, subs3[i])
|
||||
}
|
||||
}
|
||||
// merge from subs1 (merge-base), subs2 (changes in base since merge-base HEAD), subs3 (merge_source MERGE_HEAD)
|
||||
// this will update submodules
|
||||
SubmoduleCompare := func(a, b Submodule) int { return strings.Compare(a.Path, b.Path) }
|
||||
CompactCompare := func(a, b Submodule) bool { return a.Path == b.Path }
|
||||
|
||||
// remove submodules that are removed in the PR
|
||||
subs2 = slices.DeleteFunc(subs2, func(a Submodule) bool { return slices.Contains(removedSubmodules, a.Path) })
|
||||
|
||||
mergedSubs := slices.Concat(overrideSet, subs2)
|
||||
slices.SortStableFunc(mergedSubs, SubmoduleCompare)
|
||||
filteredSubs := slices.CompactFunc(mergedSubs, CompactCompare)
|
||||
|
||||
out, err := os.Create(path.Join(git.GetPath(), cwd, ".gitmodules"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't open .gitmodules for writing: %w", err)
|
||||
}
|
||||
if err = WriteSubmodules(filteredSubs, out); err != nil {
|
||||
return fmt.Errorf("Can't write .gitmodules: %w", err)
|
||||
}
|
||||
if out.Close(); err != nil {
|
||||
return fmt.Errorf("Can't close .gitmodules: %w", err)
|
||||
}
|
||||
|
||||
git.GitExecOrPanic(cwd, "add", ".gitmodules")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,14 +24,13 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitClone(t *testing.T) {
|
||||
SetTestLogger(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -95,59 +94,146 @@ func TestGitClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitCloneCommitID(t *testing.T) {
|
||||
SetTestLogger(t)
|
||||
execPath, err := os.Getwd()
|
||||
func TestSubmoduleConflictResolution(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
checkout, merge string
|
||||
result string
|
||||
merge_fail bool
|
||||
}{
|
||||
{
|
||||
name: "adding two submodules",
|
||||
checkout: "base_add_b1",
|
||||
merge: "base_add_b2",
|
||||
result: `[submodule "pkgA"]
|
||||
path = pkgA
|
||||
url = ../pkgA
|
||||
[submodule "pkgB"]
|
||||
path = pkgB
|
||||
url = ../pkgB
|
||||
[submodule "pkgB1"]
|
||||
path = pkgB1
|
||||
url = ../pkgB1
|
||||
[submodule "pkgB2"]
|
||||
path = pkgB2
|
||||
url = ../pkgB2
|
||||
[submodule "pkgC"]
|
||||
path = pkgC
|
||||
url = ../pkgC
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "remove one module and add another",
|
||||
checkout: "base_rm_c",
|
||||
merge: "base_add_b2",
|
||||
result: `[submodule "pkgA"]
|
||||
path = pkgA
|
||||
url = ../pkgA
|
||||
[submodule "pkgB"]
|
||||
path = pkgB
|
||||
url = ../pkgB
|
||||
[submodule "pkgB2"]
|
||||
path = pkgB2
|
||||
url = ../pkgB2
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "add one and remove another",
|
||||
checkout: "base_add_b2",
|
||||
merge: "base_rm_c",
|
||||
result: `[submodule "pkgA"]
|
||||
path = pkgA
|
||||
url = ../pkgA
|
||||
[submodule "pkgB"]
|
||||
path = pkgB
|
||||
url = ../pkgB
|
||||
[submodule "pkgB2"]
|
||||
path = pkgB2
|
||||
url = ../pkgB2
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "rm modified submodule",
|
||||
checkout: "base_modify_c",
|
||||
merge: "base_rm_c",
|
||||
merge_fail: true,
|
||||
},
|
||||
{
|
||||
name: "modified removed submodule",
|
||||
checkout: "base_rm_c",
|
||||
merge: "base_modify_c",
|
||||
merge_fail: true,
|
||||
},
|
||||
}
|
||||
|
||||
d, err := os.MkdirTemp(os.TempDir(), "submoduletests")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d := t.TempDir()
|
||||
if err := os.Chdir(d); err != nil {
|
||||
cwd, err := os.Getwd()
|
||||
if 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")
|
||||
cmd := exec.Command(cwd + "/test_repo_setup.sh")
|
||||
cmd.Dir = d
|
||||
_, err = cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
g, err := gh.CreateGitHandler("org")
|
||||
gh, err := AllocateGitWorkTree(d, "test", "foo@example.com")
|
||||
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)
|
||||
success := true
|
||||
noErrorOrFail := func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatal(string(debug.Stack()), err)
|
||||
}
|
||||
}
|
||||
commitID := strings.TrimSpace(string(out))
|
||||
for _, test := range tests {
|
||||
success = t.Run(test.name, func(t *testing.T) {
|
||||
git, err := gh.ReadExistingPath("prjgit")
|
||||
defer git.Close()
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
noErrorOrFail(t, git.GitExec("", "reset", "--hard"))
|
||||
noErrorOrFail(t, git.GitExec("", "checkout", "-B", "test", test.checkout))
|
||||
// noErrorOrFail(t, git.GitExec("", "merge", test.checkout))
|
||||
err = git.GitExec("", "merge", test.merge)
|
||||
if err == nil {
|
||||
t.Fatal("expected a conflict")
|
||||
}
|
||||
err = git.GitResolveConflicts("", "main", test.checkout, test.merge)
|
||||
if err != nil {
|
||||
if test.merge_fail {
|
||||
return // success
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.merge_fail {
|
||||
t.Fatal("Expected fail but succeeded?")
|
||||
}
|
||||
data, err := os.ReadFile(git.GetPath() + "/.gitmodules")
|
||||
if err != nil {
|
||||
t.Fatal("Cannot read .gitmodules.", err)
|
||||
}
|
||||
|
||||
if string(data) != test.result {
|
||||
t.Error("Expected", len(test.result), test.result, "but have", len(data), string(data))
|
||||
}
|
||||
}) && success
|
||||
}
|
||||
|
||||
// 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)
|
||||
if success {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitMsgParsing(t *testing.T) {
|
||||
SetTestLogger(t)
|
||||
t.Run("tree message with size 56", func(t *testing.T) {
|
||||
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
|
||||
|
||||
@@ -226,7 +312,6 @@ 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
|
||||
@@ -437,7 +522,6 @@ dummy change, don't merge
|
||||
}
|
||||
|
||||
func TestCommitTreeParsing(t *testing.T) {
|
||||
SetTestLogger(t)
|
||||
gitDir := t.TempDir()
|
||||
testDir, _ := os.Getwd()
|
||||
var commitId string
|
||||
@@ -546,7 +630,6 @@ func TestCommitTreeParsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGitStatusParse(t *testing.T) {
|
||||
SetTestLogger(t)
|
||||
testData := []struct {
|
||||
name string
|
||||
data []byte
|
||||
@@ -641,79 +724,18 @@ func TestGitStatusParse(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(r) != len(test.res) {
|
||||
t.Fatal("len(r):", len(r), "is not expected", len(test.res))
|
||||
|
||||
res := r.([]GitStatusData)
|
||||
|
||||
if len(res) != len(test.res) {
|
||||
t.Fatal("len(r):", len(res), "is not expected", len(test.res))
|
||||
}
|
||||
|
||||
for _, expected := range test.res {
|
||||
if !slices.Contains(r, expected) {
|
||||
if !slices.Contains(res, expected) {
|
||||
t.Fatal("result", r, "doesn't contains expected", expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +75,6 @@ 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)
|
||||
@@ -152,7 +148,6 @@ 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 {
|
||||
@@ -206,7 +201,6 @@ type Gitea interface {
|
||||
GiteaSetRepoOptions
|
||||
GiteaLabelGetter
|
||||
GiteaLabelSettter
|
||||
GiteaIssueFetcher
|
||||
|
||||
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
||||
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
||||
@@ -515,26 +509,6 @@ 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"
|
||||
)
|
||||
@@ -742,7 +716,6 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -67,16 +67,6 @@ 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":
|
||||
@@ -96,26 +86,18 @@ func SetLoggingLevelFromString(ll string) error {
|
||||
|
||||
func LogError(params ...any) {
|
||||
if logLevel >= LogLevelError {
|
||||
logit("[E]", params...)
|
||||
log.Println(append([]any{"[E]"}, params...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func LogDebug(params ...any) {
|
||||
if logLevel >= LogLevelDebug {
|
||||
logit("[D]", params...)
|
||||
log.Println(append([]any{"[D]"}, params...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func LogInfo(params ...any) {
|
||||
if logLevel >= LogLevelInfo {
|
||||
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...)...)
|
||||
log.Println(append([]any{"[I]"}, params...)...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func TestMaintainership(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(test.name+"_File", func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
mi := mock_common.NewMockGiteaMaintainershipReader(ctl)
|
||||
|
||||
// run same tests with directory maintainership data
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestManifestSubdirAssignments(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
tests := []struct {
|
||||
Name string
|
||||
ManifestContent string
|
||||
|
||||
@@ -142,45 +142,6 @@ 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()
|
||||
@@ -602,45 +563,6 @@ 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()
|
||||
|
||||
@@ -144,69 +144,6 @@ 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
|
||||
@@ -1686,45 +1623,6 @@ 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
|
||||
@@ -1867,45 +1765,6 @@ 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
|
||||
@@ -2041,45 +1900,6 @@ 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
|
||||
@@ -2874,45 +2694,6 @@ 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()
|
||||
@@ -3777,45 +3558,6 @@ 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()
|
||||
|
||||
@@ -83,260 +83,3 @@ func (c *MockObsStatusFetcherWithStateBuildStatusWithStateCall) DoAndReturn(f fu
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockObsClientInterface is a mock of ObsClientInterface interface.
|
||||
type MockObsClientInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockObsClientInterfaceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceMockRecorder is the mock recorder for MockObsClientInterface.
|
||||
type MockObsClientInterfaceMockRecorder struct {
|
||||
mock *MockObsClientInterface
|
||||
}
|
||||
|
||||
// NewMockObsClientInterface creates a new mock instance.
|
||||
func NewMockObsClientInterface(ctrl *gomock.Controller) *MockObsClientInterface {
|
||||
mock := &MockObsClientInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockObsClientInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockObsClientInterface) EXPECT() *MockObsClientInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildStatus mocks base method.
|
||||
func (m *MockObsClientInterface) BuildStatus(project string, packages ...string) (*common.BuildResultList, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{project}
|
||||
for _, a := range packages {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "BuildStatus", varargs...)
|
||||
ret0, _ := ret[0].(*common.BuildResultList)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildStatus indicates an expected call of BuildStatus.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) BuildStatus(project any, packages ...any) *MockObsClientInterfaceBuildStatusCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{project}, packages...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildStatus", reflect.TypeOf((*MockObsClientInterface)(nil).BuildStatus), varargs...)
|
||||
return &MockObsClientInterfaceBuildStatusCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceBuildStatusCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceBuildStatusCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Return(arg0 *common.BuildResultList, arg1 error) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Do(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) DoAndReturn(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DeleteProject mocks base method.
|
||||
func (m *MockObsClientInterface) DeleteProject(project string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteProject", project)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteProject indicates an expected call of DeleteProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) DeleteProject(project any) *MockObsClientInterfaceDeleteProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProject", reflect.TypeOf((*MockObsClientInterface)(nil).DeleteProject), project)
|
||||
return &MockObsClientInterfaceDeleteProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceDeleteProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceDeleteProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Return(arg0 error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Do(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) DoAndReturn(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) GetHomeProject() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetHomeProject")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetHomeProject indicates an expected call of GetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetHomeProject() *MockObsClientInterfaceGetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).GetHomeProject))
|
||||
return &MockObsClientInterfaceGetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Return(arg0 string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Do(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) DoAndReturn(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) GetProjectMeta(project string) (*common.ProjectMeta, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetProjectMeta", project)
|
||||
ret0, _ := ret[0].(*common.ProjectMeta)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetProjectMeta indicates an expected call of GetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetProjectMeta(project any) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).GetProjectMeta), project)
|
||||
return &MockObsClientInterfaceGetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Return(arg0 *common.ProjectMeta, arg1 error) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Do(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) DoAndReturn(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) SetHomeProject(project string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetHomeProject", project)
|
||||
}
|
||||
|
||||
// SetHomeProject indicates an expected call of SetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetHomeProject(project any) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).SetHomeProject), project)
|
||||
return &MockObsClientInterfaceSetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Return() *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Do(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) DoAndReturn(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) SetProjectMeta(meta *common.ProjectMeta) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetProjectMeta", meta)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetProjectMeta indicates an expected call of SetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetProjectMeta(meta any) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).SetProjectMeta), meta)
|
||||
return &MockObsClientInterfaceSetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Return(arg0 error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Do(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) DoAndReturn(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -46,15 +46,6 @@ type ObsStatusFetcherWithState interface {
|
||||
BuildStatusWithState(project string, opts *BuildResultOptions, packages ...string) (*BuildResultList, error)
|
||||
}
|
||||
|
||||
type ObsClientInterface interface {
|
||||
GetProjectMeta(project string) (*ProjectMeta, error)
|
||||
SetProjectMeta(meta *ProjectMeta) error
|
||||
DeleteProject(project string) error
|
||||
BuildStatus(project string, packages ...string) (*BuildResultList, error)
|
||||
GetHomeProject() string
|
||||
SetHomeProject(project string)
|
||||
}
|
||||
|
||||
type ObsClient struct {
|
||||
baseUrl *url.URL
|
||||
client *http.Client
|
||||
@@ -66,14 +57,6 @@ type ObsClient struct {
|
||||
HomeProject string
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetHomeProject() string {
|
||||
return c.HomeProject
|
||||
}
|
||||
|
||||
func (c *ObsClient) SetHomeProject(project string) {
|
||||
c.HomeProject = project
|
||||
}
|
||||
|
||||
func NewObsClient(host string) (*ObsClient, error) {
|
||||
baseUrl, err := url.Parse(host)
|
||||
if err != nil {
|
||||
|
||||
139
common/pr.go
139
common/pr.go
@@ -4,11 +4,7 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"src.opensuse.org/autogits/common/gitea-generated/client/repository"
|
||||
@@ -583,86 +579,12 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||
|
||||
err = git.GitExec(DefaultGitPrj, "merge", "--no-ff", "-m", msg, prjgit.Head.Sha)
|
||||
if err != nil {
|
||||
status, statusErr := git.GitStatus(DefaultGitPrj)
|
||||
if statusErr != nil {
|
||||
return fmt.Errorf("Failed to merge: %w . Status also failed: %w", err, statusErr)
|
||||
}
|
||||
|
||||
// we can only resolve conflicts with .gitmodules
|
||||
for _, s := range status {
|
||||
if s.Status == GitStatus_Unmerged {
|
||||
panic("Can't handle conflicts yet")
|
||||
if s.Path != ".gitmodules" {
|
||||
return err
|
||||
}
|
||||
|
||||
submodules, err := git.GitSubmoduleList(DefaultGitPrj, "MERGE_HEAD")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch submodules during merge resolution: %w", err)
|
||||
}
|
||||
s1, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
s2, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
s3, err := git.GitExecWithOutput(DefaultGitPrj, "cat-file", "blob", s.States[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed fetching data during .gitmodules merge resoulution: %w", err)
|
||||
}
|
||||
|
||||
subs1, err := ParseSubmodulesFile(strings.NewReader(s1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
subs2, err := ParseSubmodulesFile(strings.NewReader(s2))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
subs3, err := ParseSubmodulesFile(strings.NewReader(s3))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed parsing submodule file [%s] in merge: %w", s.States[0], err)
|
||||
}
|
||||
|
||||
// merge from subs3 (target), subs1 (orig), subs2 (2-nd base that is missing from target base)
|
||||
// this will update submodules
|
||||
mergedSubs := slices.Concat(subs1, subs2, subs3)
|
||||
|
||||
var filteredSubs []Submodule = make([]Submodule, 0, max(len(subs1), len(subs2), len(subs3)))
|
||||
nextSub:
|
||||
for subName := range submodules {
|
||||
|
||||
for i := range mergedSubs {
|
||||
if path.Base(mergedSubs[i].Path) == subName {
|
||||
filteredSubs = append(filteredSubs, mergedSubs[i])
|
||||
continue nextSub
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Cannot find submodule for path: %s", subName)
|
||||
}
|
||||
|
||||
out, err := os.Create(path.Join(git.GetPath(), DefaultGitPrj, ".gitmodules"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't open .gitmodules for writing: %w", err)
|
||||
}
|
||||
if err = WriteSubmodules(filteredSubs, out); err != nil {
|
||||
return fmt.Errorf("Can't write .gitmodules: %w", err)
|
||||
}
|
||||
if out.Close(); err != nil {
|
||||
return fmt.Errorf("Can't close .gitmodules: %w", err)
|
||||
}
|
||||
|
||||
git.GitExecOrPanic(DefaultGitPrj, "add", ".gitmodules")
|
||||
git.GitExecOrPanic(DefaultGitPrj, "-c", "core.editor=true", "merge", "--continue")
|
||||
}
|
||||
if resolveError := git.GitResolveConflicts(DefaultGitPrj, prjgit.MergeBase, prjgit.Base.Sha, prjgit.Head.Sha); resolveError != nil {
|
||||
return fmt.Errorf("Merge failed. (%w): %w", err, resolveError)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -684,15 +606,6 @@ 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
|
||||
@@ -701,30 +614,11 @@ 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
|
||||
@@ -739,37 +633,12 @@ 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 {
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,8 @@ func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
|
||||
}
|
||||
|
||||
func TestPR(t *testing.T) {
|
||||
return
|
||||
|
||||
baseConfig := common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -78,21 +80,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: "open"}, pr_err: errors.New("Missing PR")},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("Missing PR")},
|
||||
},
|
||||
prjGitPRIndex: -1,
|
||||
},
|
||||
{
|
||||
name: "Error fetching PullRequest in PrjGit",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, 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"}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("missing PR")},
|
||||
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error fetching prjgit",
|
||||
data: []prdata{
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 1,
|
||||
prjGitPRIndex: -1,
|
||||
@@ -100,20 +102,8 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent",
|
||||
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: "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"}},
|
||||
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -123,21 +113,8 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent: 1pkg",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, 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"}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 2,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -146,22 +123,9 @@ func TestPR(t *testing.T) {
|
||||
{
|
||||
name: "Review set is consistent: 2pkg",
|
||||
data: []prdata{
|
||||
{
|
||||
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"}},
|
||||
{pr: &models.PullRequest{Body: "some desc", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/fer#33", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 1,
|
||||
@@ -171,7 +135,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -190,23 +154,10 @@ 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: "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"}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
|
||||
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
|
||||
},
|
||||
resLen: 3,
|
||||
prjGitPRIndex: 2,
|
||||
@@ -216,7 +167,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "", Title: "WIP: some title", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -233,9 +184,10 @@ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -243,7 +195,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -260,15 +212,16 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Manual review is done, via PrjGit",
|
||||
data: []prdata{
|
||||
{
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -276,7 +229,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -293,7 +246,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
@@ -302,7 +255,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -310,7 +263,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -327,7 +280,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
@@ -337,7 +290,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
@@ -346,7 +299,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -363,7 +316,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
@@ -373,7 +326,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -381,7 +334,7 @@ func TestPR(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -398,7 +351,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
@@ -407,7 +360,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -415,7 +368,7 @@ func TestPR(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -423,7 +376,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -440,7 +393,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
@@ -449,7 +402,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -457,7 +410,7 @@ func TestPR(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
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"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -465,7 +418,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -482,7 +435,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
ManualMergeProject: true,
|
||||
})
|
||||
@@ -492,7 +445,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -500,7 +453,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -508,7 +461,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: "open"},
|
||||
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
|
||||
@@ -525,7 +478,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
ManualMergeOnly: true,
|
||||
})
|
||||
},
|
||||
@@ -534,7 +487,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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"},
|
||||
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
|
||||
reviews: []*models.PullReview{
|
||||
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
|
||||
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
|
||||
@@ -552,7 +505,7 @@ func TestPR(t *testing.T) {
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
}
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config)
|
||||
},
|
||||
@@ -561,37 +514,32 @@ func TestPR(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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(prjGitOrg, prjGitRepo, int64(test.data[test.prjGitPRIndex].pr.Index)).
|
||||
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err).AnyTimes()
|
||||
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, test.prjGitPRIndex).
|
||||
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err)
|
||||
} else if test.prjGitPRIndex < 0 {
|
||||
// no prjgit PR
|
||||
pr_mock.EXPECT().GetPullRequest(prjGitOrg, prjGitRepo, gomock.Any()).
|
||||
Return(nil, nil).AnyTimes()
|
||||
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, gomock.Any()).
|
||||
Return(nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
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 is not used and was causing a build error.
|
||||
// data.pr_err is directly used in the previous EXPECT call.
|
||||
test_err = data.pr_err
|
||||
}
|
||||
review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
|
||||
if data.timeline == nil {
|
||||
data.timeline = reviewsToTimeline(data.reviews)
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -604,12 +552,27 @@ func TestPR(t *testing.T) {
|
||||
res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig)
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
if test.resLen != len(res.PRs) {
|
||||
t.Error("expected result len", test.resLen, "but got", len(res.PRs))
|
||||
}
|
||||
|
||||
PrjGitPR, err := res.GetPrjGitPR()
|
||||
@@ -620,9 +583,6 @@ 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)
|
||||
@@ -1224,6 +1184,7 @@ 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()
|
||||
@@ -1233,11 +1194,7 @@ 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",
|
||||
@@ -1252,7 +1209,7 @@ func TestPRMerge(t *testing.T) {
|
||||
|
||||
config := &common.AutogitConfig{
|
||||
Organization: "org",
|
||||
GitProjectName: "org/prj#main",
|
||||
GitProjectName: "org/prj#master",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -1264,10 +1221,9 @@ func TestPRMerge(t *testing.T) {
|
||||
name: "Merge base not merged in main",
|
||||
|
||||
pr: &models.PullRequest{
|
||||
Index: 1,
|
||||
Base: &models.PRBranchInfo{
|
||||
Name: "main",
|
||||
Sha: "96515c092626c716a4613ba4f68a8d1cc4894317658342c450e656390f524ec3", // "base_add_b1"
|
||||
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
|
||||
Name: "master",
|
||||
Repo: &models.Repository{
|
||||
Name: "prj",
|
||||
Owner: &models.User{
|
||||
@@ -1277,7 +1233,7 @@ func TestPRMerge(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Head: &models.PRBranchInfo{
|
||||
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2"
|
||||
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
|
||||
},
|
||||
},
|
||||
mergeError: "Aborting merge",
|
||||
@@ -1286,10 +1242,9 @@ func TestPRMerge(t *testing.T) {
|
||||
name: "Merge conflict in modules, auto-resolved",
|
||||
|
||||
pr: &models.PullRequest{
|
||||
Index: 1,
|
||||
Base: &models.PRBranchInfo{
|
||||
Name: "main",
|
||||
Sha: "85f59f7aa732b742e58b48356cd46cb1ab1b5c4349eb5c0eda324e2dbea8f9e7",
|
||||
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
|
||||
Name: "master",
|
||||
Repo: &models.Repository{
|
||||
Name: "prj",
|
||||
Owner: &models.User{
|
||||
@@ -1299,7 +1254,7 @@ func TestPRMerge(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Head: &models.PRBranchInfo{
|
||||
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2"
|
||||
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1307,18 +1262,15 @@ func TestPRMerge(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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).AnyTimes()
|
||||
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
||||
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
|
||||
testDir := t.TempDir()
|
||||
t.Log("dir:", testDir)
|
||||
mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)
|
||||
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 {
|
||||
@@ -1339,11 +1291,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
|
||||
Timeline []*models.TimelineComment
|
||||
name string
|
||||
PRs []*models.PullRequest
|
||||
PrjPRs *models.PullRequest
|
||||
}{
|
||||
{
|
||||
name: "Pkg PR is closed",
|
||||
@@ -1355,22 +1307,10 @@ func TestPRChanges(t *testing.T) {
|
||||
},
|
||||
},
|
||||
PrjPRs: &models.PullRequest{
|
||||
Index: 42,
|
||||
Title: "some PR",
|
||||
Base: &models.PRBranchInfo{Name: "branch", Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
|
||||
Body: "PR: org/repo#42",
|
||||
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",
|
||||
},
|
||||
},
|
||||
State: "opened",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1381,16 +1321,11 @@ func TestPRChanges(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
|
||||
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()
|
||||
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
|
||||
for _, pr := range test.PRs {
|
||||
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
|
||||
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)
|
||||
|
||||
@@ -21,6 +21,8 @@ package common
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type RequestType interface {
|
||||
@@ -85,14 +87,24 @@ func ParseRequestJSON(reqType string, data []byte) (req *Request, err error) {
|
||||
}
|
||||
|
||||
type RequestHandler struct {
|
||||
StdLogger, ErrLogger *log.Logger
|
||||
Request *Request
|
||||
}
|
||||
|
||||
func (r *RequestHandler) WriteError() {
|
||||
LogError("internal error sent")
|
||||
r.ErrLogger.Println("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
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package common
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -26,6 +27,8 @@ 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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
LogInfo("Request push for repo:", action.Repository.Full_Name)
|
||||
h.StdLogger.Printf("Request push for repo: %s\n", action.Repository.Full_Name)
|
||||
h.Request = &Request{
|
||||
Type: RequestType_Push,
|
||||
Data: action,
|
||||
|
||||
@@ -19,6 +19,7 @@ package common_test
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -26,10 +27,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)
|
||||
|
||||
@@ -117,7 +117,7 @@ func (h *RequestHandler) ParseRepositoryRequest(dataReader io.Reader) (data *Rep
|
||||
|
||||
data.PrjGit = data.Repository.Ssh_Url[:repoIdx+1] + DefaultGitPrj + ".git"
|
||||
|
||||
LogInfo(data.Action, "request for repo:", data.Repository.Full_Name)
|
||||
h.StdLogger.Printf("Request '%s' for repo: %s\n", data.Action, data.Repository.Full_Name)
|
||||
if len(data.Action) < 1 {
|
||||
return nil, fmt.Errorf("Request has no data.... skipping")
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package common_test
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -35,10 +36,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)
|
||||
|
||||
@@ -52,7 +52,7 @@ func (h *RequestHandler) ParseStatusRequest(data io.Reader) (*StatusWebhookEvent
|
||||
return nil, fmt.Errorf("Got error while parsing: %w", err)
|
||||
}
|
||||
|
||||
LogInfo("Request status for repo:", action.Repository.Full_Name+"#"+action.Sha)
|
||||
h.StdLogger.Printf("Request status for repo: %s#%s\n", action.Repository.Full_Name, action.Sha)
|
||||
h.Request = &Request{
|
||||
Type: RequestType_Status,
|
||||
Data: action,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -8,10 +9,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)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMaintainerGroupReplacer(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
GroupName := "my_group"
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestReviewers(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
|
||||
@@ -17,16 +17,15 @@ type PRReviews struct {
|
||||
}
|
||||
|
||||
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) {
|
||||
rawReviews, err := rf.GetPullRequestReviews(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeline, err := rf.GetTimeline(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawReviews, err := rf.GetPullRequestReviews(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]*models.PullReview, 0, 10)
|
||||
needNewReviews := []string{}
|
||||
@@ -188,6 +187,8 @@ func (r *PRReviews) HasPendingReviewBy(reviewer string) bool {
|
||||
switch r.State {
|
||||
case ReviewStateRequestReview, ReviewStatePending:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,20 +201,18 @@ func (r *PRReviews) IsReviewedBy(reviewer string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
res := false
|
||||
|
||||
for _, i := range r.Reviews {
|
||||
if i.User.UserName == reviewer && !i.Stale {
|
||||
switch i.State {
|
||||
for _, r := range r.Reviews {
|
||||
if r.User.UserName == reviewer && !r.Stale {
|
||||
switch r.State {
|
||||
case ReviewStateApproved, ReviewStateRequestChanges:
|
||||
res = true
|
||||
case ReviewStateRequestReview, ReviewStatePending:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsReviewedByOneOf(reviewers ...string) bool {
|
||||
|
||||
@@ -4,6 +4,7 @@ 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"
|
||||
@@ -141,7 +142,7 @@ func TestReviews(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
rf := mock_common.NewMockGiteaReviewTimelineFetcher(ctl)
|
||||
|
||||
if test.timeline == nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
)
|
||||
|
||||
func TestSubmodulesParsing(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
file string
|
||||
@@ -124,7 +123,6 @@ func TestSubmodulesParsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubmodulesWriting(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
subs []common.Submodule
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
set -x
|
||||
|
||||
export TZ=UTC
|
||||
export GIT_CONFIG_COUNT=2
|
||||
|
||||
export GIT_CONFIG_KEY_0=protocol.file.allow
|
||||
@@ -41,10 +40,24 @@ create_prjgit_sample() {
|
||||
git submodule -q add ../pkgB2 pkgB2
|
||||
git commit -q -m "pkgB2 added"
|
||||
|
||||
git checkout main
|
||||
git checkout -b base_rm_c main
|
||||
git clean -ffxd
|
||||
git submodule -q add -f ../pkgB1 pkgB1
|
||||
git commit -q -m "main adding pkgB1"
|
||||
git rm pkgC
|
||||
git commit -q -m 'pkgC removed'
|
||||
|
||||
git checkout -b base_modify_c main
|
||||
git submodule update --init pkgC
|
||||
pushd pkgC
|
||||
echo "mofieid" >> README.md
|
||||
git commit -q -m "modified" README.md
|
||||
popd
|
||||
git commit pkgC -m "modifiedC"
|
||||
git submodule deinit -f pkgC
|
||||
|
||||
# git checkout main
|
||||
# git clean -ffxd
|
||||
# git submodule -q add -f ../pkgB1 pkgB1
|
||||
# git commit -q -m "main adding pkgB1"
|
||||
|
||||
popd
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -287,30 +286,3 @@ func TrimRemovedBranchSuffix(branchName string) string {
|
||||
|
||||
return branchName
|
||||
}
|
||||
|
||||
func GetEnvOverrideString(envValue, def string) string {
|
||||
if len(envValue) != 0 {
|
||||
return envValue
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func GetEnvOverrideBool(envValue string, def bool) bool {
|
||||
if len(envValue) == 0 {
|
||||
return def
|
||||
}
|
||||
|
||||
if value, err := strconv.Atoi(envValue); err == nil {
|
||||
if value > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
envValue = strings.TrimSpace(strings.ToLower(envValue))
|
||||
switch envValue {
|
||||
case "t", "true", "yes", "y", "on":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package common_test
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
)
|
||||
@@ -223,60 +222,6 @@ func TestRemovedBranchName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitStringNoEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
sep string
|
||||
expected []string
|
||||
}{
|
||||
{"Empty string", "", ",", []string{}},
|
||||
{"Only separators", ",,,", ",", []string{}},
|
||||
{"Spaces and separators", " , , ", ",", []string{}},
|
||||
{"Normal split", "a,b,c", ",", []string{"a", "b", "c"}},
|
||||
{"Leading/trailing spaces", " a , b ", ",", []string{"a", "b"}},
|
||||
{"Multiple separators", "a,,b", ",", []string{"a", "b"}},
|
||||
{"Newlines", "line1\n\nline2", "\n", []string{"line1", "line2"}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := common.SplitStringNoEmpty(test.input, test.sep)
|
||||
if !reflect.DeepEqual(res, test.expected) {
|
||||
t.Errorf("SplitStringNoEmpty(%q, %q) = %v; want %v", test.input, test.sep, res, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateHttpsToSshUrl(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
err bool
|
||||
}{
|
||||
{"Opensuse HTTPS", "https://src.opensuse.org/org/repo", "ssh://gitea@src.opensuse.org/org/repo", false},
|
||||
{"Suse HTTPS", "https://src.suse.de/org/repo", "ssh://gitea@src.suse.de/org/repo", false},
|
||||
{"Already SSH", "ssh://gitea@src.opensuse.org/org/repo", "ssh://gitea@src.opensuse.org/org/repo", false},
|
||||
{"Native SSH", "gitea@src.opensuse.org:org/repo", "gitea@src.opensuse.org:org/repo", false},
|
||||
{"Unknown URL", "https://github.com/org/repo", "", true},
|
||||
{"Empty URL", "", "", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := common.TranslateHttpsToSshUrl(test.input)
|
||||
if (err != nil) != test.err {
|
||||
t.Errorf("TranslateHttpsToSshUrl(%q) error = %v; want error %v", test.input, err, test.err)
|
||||
}
|
||||
if res != test.expected {
|
||||
t.Errorf("TranslateHttpsToSshUrl(%q) = %q; want %q", test.input, res, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPackageIssueParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -296,7 +241,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Default branch and junk lines and approval for maintainership",
|
||||
name: "Default branch and junk lines and approval for maintainership",
|
||||
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: yes",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
@@ -306,7 +251,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Default branch and junk lines and no maintainership",
|
||||
name: "Default branch and junk lines and no maintainership",
|
||||
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: NEVER",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
@@ -315,7 +260,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "3 repos with comments and maintainership",
|
||||
name: "3 repos with comments and maintainership",
|
||||
input: "\n\nsome comments for org1/repo2 are here and more\n\norg1/repo2#master\n org2/repo3#master\n some/repo3#m\nMaintainer ok",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
@@ -327,11 +272,11 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid repos with spaces",
|
||||
name: "Invalid repos with spaces",
|
||||
input: "or g/repo#branch\norg/r epo#branch\norg/repo#br anch\norg/repo#branch As foo ++",
|
||||
},
|
||||
{
|
||||
name: "Valid repos with spaces",
|
||||
name: "Valid repos with spaces",
|
||||
input: " org / repo # branch",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
@@ -340,7 +285,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Package name is not repo name",
|
||||
name: "Package name is not repo name",
|
||||
input: " org / repo # branch as repo++ \nmaintainer true",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
@@ -360,63 +305,3 @@ func TestNewPackageIssueParsing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnvOverride(t *testing.T) {
|
||||
t.Run("GetEnvOverrideString", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
envValue string
|
||||
def string
|
||||
expected string
|
||||
}{
|
||||
{"", "default", "default"},
|
||||
{"override", "default", "override"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if res := common.GetEnvOverrideString(test.envValue, test.def); res != test.expected {
|
||||
t.Errorf("GetEnvOverrideString(%q, %q) = %q; want %q", test.envValue, test.def, res, test.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetEnvOverrideBool", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
def bool
|
||||
expected bool
|
||||
}{
|
||||
{"Empty env value, default false", "", false, false},
|
||||
{"Empty env value, default true", "", true, true},
|
||||
{"Env '1', default false", "1", false, true},
|
||||
{"Env '2', default false", "2", false, true},
|
||||
{"Env '0', default false", "0", false, false},
|
||||
{"Env 'invalid', default true", "abc", true, false},
|
||||
{"Env 'true', default false", "true", false, true},
|
||||
{"Env 'YES', default false", "YES", false, true},
|
||||
{"Env '0', default true", "0", true, false},
|
||||
{"Env 'false', default true", "false", true, false},
|
||||
{"Env 'FALSE', default true", "FALSE", true, false},
|
||||
{"Env ' true ', default false", " true ", false, true},
|
||||
{"Env 'no', default true", "no", true, false},
|
||||
{"Env 'NO', default true", "NO", true, false},
|
||||
{"Env 'off', default true", "off", true, false},
|
||||
{"Env 'on', default false", "on", false, true},
|
||||
{"Env 'invalid', default false", "tbc", false, false},
|
||||
{"Env 'garbage', default false", "!@#$", false, false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if res := common.GetEnvOverrideBool(test.envValue, test.def); res != test.expected {
|
||||
t.Errorf("GetEnvOverrideBool(%q, %v) = %v; want %v", test.envValue, test.def, res, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func NewController(t *testing.T) *gomock.Controller {
|
||||
common.SetTestLogger(t)
|
||||
return gomock.NewController(t)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
# 3. 'pytest -v tests/*' - run tests
|
||||
# 4. 'make down' - once the containers are not needed
|
||||
# B2: (make sure the go binaries in the parent folder are built)
|
||||
# 1. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`)
|
||||
# 2. 'make up' - spawns podman-compose
|
||||
# 3. 'pytest -v tests/*' - run tests
|
||||
# 4. 'make down' - once the containers are not needed
|
||||
# 4. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`)
|
||||
# 5. 'make up' - spawns podman-compose
|
||||
# 6. 'pytest -v tests/*' - run tests
|
||||
# 7. 'make down' - once the containers are not needed
|
||||
|
||||
|
||||
AUTO_DETECT_MODE := $(shell if test -e ../workflow-pr/workflow-pr; then echo .local; else echo .package; fi)
|
||||
@@ -44,7 +44,7 @@ build_container:
|
||||
|
||||
# Run tests in topology 1
|
||||
test_container:
|
||||
podman run --rm --privileged -t -e GIWTF_IMAGE_SUFFIX=$(GIWTF_IMAGE_SUFFIX) autogits_integration /usr/bin/bash -c "make build && make up && sleep 25 && pytest -v tests/*"
|
||||
podman run --rm --privileged -t --network integration_gitea-network -e GIWTF_IMAGE_SUFFIX=$(GIWTF_IMAGE_SUFFIX) autogits_integration /usr/bin/bash -c "make build && make up && sleep 25 && pytest -v tests/*"
|
||||
|
||||
|
||||
build_local: AUTO_DETECT_MODE=.local
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
| Makefile Targets |
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
| |
|
||||
| [Default Test Workflow] |
|
||||
| test (Auto-detects mode: .local or .package) |
|
||||
| └─> build_container |
|
||||
| └─> test_container |
|
||||
| |
|
||||
| [Specific Test Workflows - Topology 1: Privileged Container] |
|
||||
| test_package (Mode A1: Bots from official packages) |
|
||||
| └─> build_container |
|
||||
| └─> test_container |
|
||||
| |
|
||||
| test_local (Mode B1: Bots from local binaries) |
|
||||
| └─> build_container |
|
||||
| └─> test_container |
|
||||
| |
|
||||
| build_container |
|
||||
| - Action: Builds the `autogits_integration` privileged container image. |
|
||||
| - Purpose: Prepares an environment for running tests within a single container. |
|
||||
| |
|
||||
| test_container |
|
||||
| - Action: Runs `autogits_integration` container, executes `make build`, `make up`, and |
|
||||
| `pytest -v tests/*` inside it. |
|
||||
| - Purpose: Executes the full test suite in Topology 1 (privileged container). |
|
||||
| |
|
||||
| [Build & Orchestration Workflows - Topology 2: podman-compose] |
|
||||
| |
|
||||
| build_package (Mode A: Builds service images from official packages) |
|
||||
| └─> build |
|
||||
| |
|
||||
| build_local (Mode B: Builds service images from local binaries) |
|
||||
| └─> build |
|
||||
| |
|
||||
| build |
|
||||
| - Action: Pulls `rabbitmq` image and iterates through `podman-compose.yml` services |
|
||||
| to build each one. |
|
||||
| - Purpose: Prepares all necessary service images for Topology 2 deployment. |
|
||||
| |
|
||||
| up |
|
||||
| - Action: Starts all services defined in `podman-compose.yml` in detached mode. |
|
||||
| - Purpose: Deploys the application topology (containers) for testing or development. |
|
||||
| |
|
||||
| down |
|
||||
| - Action: Stops and removes all services started by `up`. |
|
||||
| - Purpose: Cleans up the deployed application topology. |
|
||||
| |
|
||||
| up-bots-package (Mode A: Spawns Topology 2 with official package bots) |
|
||||
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.package`. |
|
||||
| - Purpose: Specifically brings up the environment using official package bots. |
|
||||
| |
|
||||
| up-bots-local (Mode B: Spawns Topology 2 with local binaries) |
|
||||
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.local`. |
|
||||
| - Purpose: Specifically brings up the environment using local binaries. |
|
||||
| |
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
@@ -10,7 +10,4 @@ echo "!!!!!!!!!!!!!!!! using binary $exe; installed package: $package"
|
||||
which strings > /dev/null 2>&1 && strings "$exe" | grep -A 2 vcs.revision= | head -4 || :
|
||||
echo "RABBITMQ_HOST: $RABBITMQ_HOST"
|
||||
|
||||
echo "sleep 12 sec to let rabbitmq set up, because the bot currently retries only once"
|
||||
sleep 12
|
||||
|
||||
exec $exe "$@"
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN zypper -n install \
|
||||
openssh \
|
||||
jq \
|
||||
devel_Factory_git-workflow:gitea \
|
||||
&& rm -rf /var/cache/zypp/* || ( tail -n 1000 /var/log/zypper.log ; exit 1 )
|
||||
&& rm -rf /var/cache/zypp/*
|
||||
|
||||
# Copy the minimal set of required files from the local 'container-files' directory
|
||||
COPY container-files/ /
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
| Podman-Compose Services Diagram |
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
| |
|
||||
| [Network] |
|
||||
| gitea-network (Bridge network for inter-service communication) |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: gitea] |
|
||||
| Description: Self-hosted Git service, central hub for repositories and code management. |
|
||||
| Container Name: gitea-test |
|
||||
| Image: Built from ./gitea Dockerfile |
|
||||
| Ports: 3000 (HTTP), 3022 (SSH) |
|
||||
| Volumes: ./gitea-data (for persistent data), ./gitea-logs (for logs) |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: rabbitmq] |
|
||||
| Description: Message broker for asynchronous communication between services. |
|
||||
| Container Name: rabbitmq-test |
|
||||
| Image: rabbitmq:3.13.7-management |
|
||||
| Ports: 5671 (AMQP), 15672 (Management UI) |
|
||||
| Volumes: ./rabbitmq-data (for persistent data), ./rabbitmq-config/certs (TLS certs), |
|
||||
| ./rabbitmq-config/rabbitmq.conf (config), ./rabbitmq-config/definitions.json (exchanges)|
|
||||
| Healthcheck: Ensures RabbitMQ is running and healthy. |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: gitea-publisher] |
|
||||
| Description: Publishes events from Gitea to the RabbitMQ message queue. |
|
||||
| Container Name: gitea-publisher |
|
||||
| Image: Built from ../gitea-events-rabbitmq-publisher/Dockerfile (local/package) |
|
||||
| Dependencies: gitea (started), rabbitmq (healthy) |
|
||||
| Environment: RABBITMQ_HOST, RABBITMQ_USERNAME, RABBITMQ_PASSWORD, SSL_CERT_FILE |
|
||||
| Command: Listens for Gitea events, publishes to 'suse' topic, debug enabled. |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: workflow-pr] |
|
||||
| Description: Manages pull request workflows, likely consuming events from RabbitMQ and |
|
||||
| interacting with Gitea. |
|
||||
| Container Name: workflow-pr |
|
||||
| Image: Built from ../workflow-pr/Dockerfile (local/package) |
|
||||
| Dependencies: gitea (started), rabbitmq (healthy) |
|
||||
| Environment: AMQP_USERNAME, AMQP_PASSWORD, SSL_CERT_FILE |
|
||||
| Volumes: ./gitea-data (read-only), ./workflow-pr/workflow-pr.json (config), |
|
||||
| ./workflow-pr-repos (for repositories) |
|
||||
| Command: Configures Gitea/RabbitMQ URLs, enables debug, manages repositories. |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: mock-obs] |
|
||||
| Description: A mock (simulated) service for the Open Build Service (OBS) for testing. |
|
||||
| Container Name: mock-obs |
|
||||
| Image: Built from ./mock-obs Dockerfile |
|
||||
| Ports: 8080 |
|
||||
| Volumes: ./mock-obs/responses (for mock API responses) |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
|-------------------------------------------------------------------------------------------------|
|
||||
| |
|
||||
| [Service: obs-staging-bot] |
|
||||
| Description: A bot that interacts with Gitea and the mock OBS, likely for staging processes. |
|
||||
| Container Name: obs-staging-bot |
|
||||
| Image: Built from ../obs-staging-bot/Dockerfile (local/package) |
|
||||
| Dependencies: gitea (started), mock-obs (started) |
|
||||
| Environment: OBS_USER, OBS_PASSWORD |
|
||||
| Volumes: ./gitea-data (read-only) |
|
||||
| Command: Configures Gitea/OBS URLs, enables debug. |
|
||||
| Network: gitea-network |
|
||||
| |
|
||||
+-------------------------------------------------------------------------------------------------+
|
||||
@@ -1,10 +0,0 @@
|
||||
[pytest]
|
||||
markers =
|
||||
t001: Test case 001
|
||||
t002: Test case 002
|
||||
t003: Test case 003
|
||||
t004: Test case 004
|
||||
t005: Test case 005
|
||||
t006: Test case 006
|
||||
t007: Test case 007
|
||||
dependency: pytest-dependency marker
|
||||
@@ -1,7 +0,0 @@
|
||||
listeners.ssl.default = 5671
|
||||
|
||||
ssl_options.certfile = /etc/rabbitmq/certs/cert.pem
|
||||
ssl_options.keyfile = /etc/rabbitmq/certs/key.pem
|
||||
ssl_options.verify = verify_none
|
||||
ssl_options.fail_if_no_peer_cert = false
|
||||
management.load_definitions = /etc/rabbitmq/definitions.json
|
||||
@@ -53,37 +53,31 @@ The testing will be conducted in a dedicated test environment that mimics the pr
|
||||
|
||||
## 5. Test Cases
|
||||
|
||||
| Test Case ID | Status | Description | Steps to Reproduce | Expected Results | Priority |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **TC-SYNC-001** | P | **Create ProjectGit PR from PackageGit PR** | 1. Create a new PR in a PackageGit repository. | 1. A new PR is created in the corresponding ProjectGit repository with the title "Forwarded PRs: <package_name>".<br>2. The ProjectGit PR description contains a link to the PackageGit PR (e.g., `PR: org/package_repo!pr_number`).<br>3. The package submodule in the ProjectGit PR points to the PackageGit PR's commit. | High |
|
||||
| **TC-SYNC-002** | P | **Update ProjectGit PR from PackageGit PR** | 1. Push a new commit to an existing PackageGit PR. | 1. The corresponding ProjectGit PR's head branch is updated with the new commit. | High |
|
||||
| **TC-SYNC-003** | P | **WIP Flag Synchronization** | 1. Mark a PackageGit PR as "Work In Progress".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium |
|
||||
| **TC-SYNC-004** | - | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium |
|
||||
| **TC-SYNC-005** | x | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
|
||||
| **TC-SYNC-006** | x | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
|
||||
| **TC-COMMENT-001** | - | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High |
|
||||
| **TC-REVIEW-001** | P | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
|
||||
| **TC-REVIEW-002** | - | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
|
||||
| **TC-REVIEW-003** | - | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
|
||||
| **TC-REVIEW-004** | P | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
|
||||
| **TC-REVIEW-005** | P | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-006** | P | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-007** | P | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
|
||||
| **TC-MERGE-001** | P | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-002** | - | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-003** | - | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
|
||||
| **TC-MERGE-004** | - | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
|
||||
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-006** | - | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-007** | - | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High |
|
||||
| **TC-CONFIG-001** | - | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High |
|
||||
| **TC-LABEL-001** | P | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High |
|
||||
| **TC-LABEL-002** | x | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium |
|
||||
| **TC-LABEL-003** | - | **Apply `review/Done` label** | 1. Ensure all mandatory reviews for a PR are completed. | 1. The `review/Done` label is applied to the ProjectGit PR when all mandatory reviews are completed. | Medium |
|
||||
| Test Case ID | Description | Steps to Reproduce | Expected Results | Priority |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **TC-SYNC-001** | **Create ProjectGit PR from PackageGit PR** | 1. Create a new PR in a PackageGit repository. | 1. A new PR is created in the corresponding ProjectGit repository with the title "Forwarded PRs: <package_name>".<br>2. The ProjectGit PR description contains a link to the PackageGit PR (e.g., `PR: org/package_repo!pr_number`).<br>3. The package submodule in the ProjectGit PR points to the PackageGit PR's commit. | High |
|
||||
| **TC-SYNC-002** | **Update ProjectGit PR from PackageGit PR** | 1. Push a new commit to an existing PackageGit PR. | 1. The corresponding ProjectGit PR's head branch is updated with the new commit. | High |
|
||||
| **TC-SYNC-003** | **WIP Flag Synchronization** | 1. Mark a PackageGit PR as "Work In Progress".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium |
|
||||
| **TC-SYNC-004** | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium |
|
||||
| **TC-SYNC-005** | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
|
||||
| **TC-SYNC-006** | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
|
||||
| **TC-COMMENT-001** | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High |
|
||||
| **TC-REVIEW-001** | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
|
||||
| **TC-REVIEW-002** | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
|
||||
| **TC-REVIEW-003** | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
|
||||
| **TC-REVIEW-004** | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
|
||||
| **TC-REVIEW-005** | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-006** | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-007** | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
|
||||
| **TC-MERGE-001** | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-002** | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-003** | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
|
||||
| **TC-MERGE-004** | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
|
||||
| **TC-MERGE-005** | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-006** | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-007** | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High |
|
||||
| **TC-CONFIG-001** | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High |
|
||||
| **TC-LABEL-001** | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High |
|
||||
| **TC-LABEL-002** | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium |
|
||||
| **TC-LABEL-003** | **Apply `review/Done` label** | 1. Ensure all mandatory reviews for a PR are completed. | 1. The `review/Done` label is applied to the ProjectGit PR when all mandatory reviews are completed. | Medium |
|
||||
|
||||
|
||||
#### Legend:
|
||||
* P = implemented and passing;
|
||||
* x = likely implemented, but investigation is needed;
|
||||
* X = implemented and likely to pass, but someteimes may fail, but troubleshooting is needed;
|
||||
* - = test is not implemented
|
||||
|
||||
@@ -6,163 +6,58 @@ import pytest
|
||||
import requests
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
|
||||
# Assuming GiteaAPIClient is in tests/lib/common_test_utils.py
|
||||
from tests.lib.common_test_utils import GiteaAPIClient
|
||||
|
||||
BRANCH_CONFIG_COMMON = {
|
||||
"workflow.config": {
|
||||
"Workflows": ["pr"],
|
||||
"Organization": "pool",
|
||||
"Reviewers": ["-autogits_obs_staging_bot"],
|
||||
"GitProjectName": "products/SLFO#{branch}"
|
||||
},
|
||||
"_maintainership.json": {
|
||||
"": ["ownerX", "ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB", "ownerBB"]
|
||||
}
|
||||
}
|
||||
|
||||
BRANCH_CONFIG_CUSTOM = {
|
||||
"main": {
|
||||
"workflow.config": {
|
||||
"ManualMergeProject": True
|
||||
},
|
||||
"staging.config": {
|
||||
"ObsProject": "openSUSE:Leap:16.0",
|
||||
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||
}
|
||||
},
|
||||
"merge": {
|
||||
"workflow.config": {
|
||||
"Reviewers": ["+usera", "+userb", "-autogits_obs_staging_bot"]
|
||||
}
|
||||
},
|
||||
"maintainer-merge": {
|
||||
"workflow.config": {
|
||||
}
|
||||
},
|
||||
"review-required": {
|
||||
"workflow.config": {
|
||||
"ReviewRequired": True
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
"workflow.config": {
|
||||
"ManualMergeProject": True,
|
||||
"NoProjectGitPR": True
|
||||
}
|
||||
},
|
||||
"label-test": {
|
||||
"workflow.config": {
|
||||
"ManualMergeProject": True,
|
||||
"Reviewers": ["*usera"],
|
||||
"ReviewRequired": True,
|
||||
"Labels": {
|
||||
"StagingAuto": "staging/Backlog",
|
||||
"ReviewPending": "review/Pending"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict):
|
||||
"""
|
||||
Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators.
|
||||
"""
|
||||
all_users = set()
|
||||
|
||||
# Extract from workflow.config Reviewers
|
||||
reviewers = wf.get("Reviewers", [])
|
||||
for r in reviewers:
|
||||
username = r.lstrip("+-*")
|
||||
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
|
||||
all_users.add(username)
|
||||
|
||||
# Extract from maintainership
|
||||
for pkg, users in mt.items():
|
||||
for username in users:
|
||||
all_users.add(username)
|
||||
|
||||
# Create all users
|
||||
for username in all_users:
|
||||
client.create_user(username, "password123", f"{username}@example.com")
|
||||
client.add_collaborator("products", "SLFO", username, "write")
|
||||
|
||||
# Set specific repository permissions based on maintainership
|
||||
for pkg, users in mt.items():
|
||||
repo_name = pkg if pkg else None
|
||||
for username in users:
|
||||
if not repo_name:
|
||||
client.add_collaborator("pool", "pkgA", username, "write")
|
||||
client.add_collaborator("pool", "pkgB", username, "write")
|
||||
else:
|
||||
client.add_collaborator("pool", repo_name, username, "write")
|
||||
|
||||
def ensure_config_file(client: GiteaAPIClient, owner: str, repo: str, branch: str, file_name: str, expected_content_dict: dict):
|
||||
"""
|
||||
Checks if a config file exists and has the correct content.
|
||||
Returns True if a change was made, False otherwise.
|
||||
"""
|
||||
file_info = client.get_file_info(owner, repo, file_name, branch=branch)
|
||||
expected_content = json.dumps(expected_content_dict, indent=4)
|
||||
|
||||
if file_info:
|
||||
current_content_raw = base64.b64decode(file_info["content"]).decode("utf-8")
|
||||
try:
|
||||
current_content_dict = json.loads(current_content_raw)
|
||||
if current_content_dict == expected_content_dict:
|
||||
return False
|
||||
except json.JSONDecodeError:
|
||||
pass # Overwrite invalid JSON
|
||||
|
||||
client.create_file(owner, repo, file_name, expected_content, branch=branch)
|
||||
return True
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def gitea_env():
|
||||
"""
|
||||
Global fixture to set up the Gitea environment for all tests.
|
||||
Sets up the Gitea environment with dummy data and provides a GiteaAPIClient instance.
|
||||
"""
|
||||
gitea_url = "http://127.0.0.1:3000"
|
||||
admin_token_path = "./gitea-data/admin.token"
|
||||
|
||||
# Read admin token
|
||||
admin_token_path = "./gitea-data/admin.token" # Corrected path
|
||||
admin_token = None
|
||||
try:
|
||||
with open(admin_token_path, "r") as f:
|
||||
admin_token = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
raise Exception(f"Admin token file not found at {admin_token_path}.")
|
||||
raise Exception(f"Admin token file not found at {admin_token_path}. Ensure it's generated and accessible.")
|
||||
|
||||
# Headers for authenticated requests
|
||||
auth_headers = {"Authorization": f"token {admin_token}", "Content-Type": "application/json"}
|
||||
|
||||
# Wait for Gitea to be available
|
||||
print(f"Waiting for Gitea at {gitea_url}...")
|
||||
max_retries = 30
|
||||
for i in range(max_retries):
|
||||
try:
|
||||
# Check a specific API endpoint that indicates readiness
|
||||
response = requests.get(f"{gitea_url}/api/v1/version", headers=auth_headers, timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("Gitea API is available.")
|
||||
break
|
||||
except requests.exceptions.ConnectionError:
|
||||
pass
|
||||
print(f"Gitea not ready ({response.status_code if 'response' in locals() else 'ConnectionError'}), retrying in 5 seconds... ({i+1}/{max_retries})")
|
||||
time.sleep(5)
|
||||
else:
|
||||
raise Exception("Gitea did not become available within the expected time.")
|
||||
|
||||
client = GiteaAPIClient(base_url=gitea_url, token=admin_token)
|
||||
|
||||
# Wait for Gitea
|
||||
for i in range(10):
|
||||
try:
|
||||
if client._request("GET", "version").status_code == 200:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise Exception("Gitea not available.")
|
||||
|
||||
print("--- Starting Gitea Global Setup ---")
|
||||
# Setup dummy data
|
||||
print("--- Starting Gitea Dummy Data Setup from Pytest Fixture ---")
|
||||
client.create_org("products")
|
||||
client.create_org("pool")
|
||||
|
||||
client.create_repo("products", "SLFO")
|
||||
client.create_repo("pool", "pkgA")
|
||||
client.create_repo("pool", "pkgB")
|
||||
client.update_repo_settings("products", "SLFO")
|
||||
client.update_repo_settings("pool", "pkgA")
|
||||
client.update_repo_settings("pool", "pkgB")
|
||||
|
||||
# Create labels
|
||||
client.create_label("products", "SLFO", "staging/Backlog", color="#0000ff")
|
||||
client.create_label("products", "SLFO", "review/Pending", color="#ffff00")
|
||||
|
||||
# Submodules in SLFO
|
||||
|
||||
# The add_submodules method also creates workflow.config and staging.config
|
||||
client.add_submodules("products", "SLFO")
|
||||
|
||||
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
||||
@@ -170,92 +65,14 @@ def gitea_env():
|
||||
client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
|
||||
client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
|
||||
|
||||
restart_needed = False
|
||||
|
||||
# Setup all branches and configs
|
||||
for branch_name, custom_configs in BRANCH_CONFIG_CUSTOM.items():
|
||||
# Ensure branch exists in all 3 repos
|
||||
for owner, repo in [("products", "SLFO"), ("pool", "pkgA"), ("pool", "pkgB")]:
|
||||
if branch_name != "main":
|
||||
try:
|
||||
main_sha = client._request("GET", f"repos/{owner}/{repo}/branches/main").json()["commit"]["id"]
|
||||
client.create_branch(owner, repo, branch_name, main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Merge configs
|
||||
merged_configs = {}
|
||||
for file_name, common_content in BRANCH_CONFIG_COMMON.items():
|
||||
merged_configs[file_name] = common_content.copy()
|
||||
# Dynamically format values containing {branch}
|
||||
if file_name == "workflow.config":
|
||||
if "GitProjectName" in merged_configs[file_name]:
|
||||
merged_configs[file_name]["GitProjectName"] = merged_configs[file_name]["GitProjectName"].format(branch=branch_name)
|
||||
# Inject branch name dynamically
|
||||
merged_configs[file_name]["Branch"] = branch_name
|
||||
|
||||
for file_name, custom_content in custom_configs.items():
|
||||
if file_name in merged_configs:
|
||||
merged_configs[file_name].update(custom_content)
|
||||
else:
|
||||
merged_configs[file_name] = custom_content
|
||||
client.update_repo_settings("products", "SLFO")
|
||||
client.update_repo_settings("pool", "pkgA")
|
||||
client.update_repo_settings("pool", "pkgB")
|
||||
print("--- Gitea Dummy Data Setup Complete ---")
|
||||
time.sleep(5) # Add a small delay for Gitea to fully process changes
|
||||
|
||||
# Ensure config files in products/SLFO
|
||||
for file_name, content_dict in merged_configs.items():
|
||||
if ensure_config_file(client, "products", "SLFO", branch_name, file_name, content_dict):
|
||||
restart_needed = True
|
||||
|
||||
# Setup users (using configs from this branch)
|
||||
setup_users_from_config(client, merged_configs.get("workflow.config", {}), merged_configs.get("_maintainership.json", {}))
|
||||
|
||||
if restart_needed:
|
||||
client.restart_service("workflow-pr")
|
||||
time.sleep(2) # Give it time to pick up changes
|
||||
|
||||
print("--- Gitea Global Setup Complete ---")
|
||||
yield client
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def automerge_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "merge"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def maintainer_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "maintainer-merge"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def review_required_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "review-required"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def no_project_git_pr_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "dev"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def label_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "label-test"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerA_client(gitea_env):
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerA")
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerB_client(gitea_env):
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerB")
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerBB_client(gitea_env):
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerBB")
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def staging_bot_client(gitea_env):
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="autogits_obs_staging_bot")
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_user_client(gitea_env):
|
||||
username = f"test-user-{int(time.time())}"
|
||||
gitea_env.create_user(username, "password123", f"{username}@example.com")
|
||||
gitea_env.add_collaborator("pool", "pkgA", username, "write")
|
||||
gitea_env.add_collaborator("products", "SLFO", username, "write")
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo=username)
|
||||
# Teardown (optional, depending on test strategy)
|
||||
# For now, we'll leave resources for inspection. If a clean slate is needed for each test,
|
||||
# this fixture's scope would be 'function' and teardown logic would be added here.
|
||||
|
||||
@@ -6,7 +6,6 @@ import json
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
TEST_DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
BUILD_RESULT_TEMPLATE = TEST_DATA_DIR / "build_result.xml.template"
|
||||
@@ -44,11 +43,9 @@ def mock_build_result():
|
||||
|
||||
|
||||
class GiteaAPIClient:
|
||||
def __init__(self, base_url, token, sudo=None):
|
||||
def __init__(self, base_url, token):
|
||||
self.base_url = base_url
|
||||
self.headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
||||
if sudo:
|
||||
self.headers["Sudo"] = sudo
|
||||
|
||||
def _request(self, method, path, **kwargs):
|
||||
url = f"{self.base_url}/api/v1/{path}"
|
||||
@@ -61,48 +58,6 @@ class GiteaAPIClient:
|
||||
raise
|
||||
return response
|
||||
|
||||
def get_file_info(self, owner: str, repo: str, file_path: str, branch: str = "main"):
|
||||
url = f"repos/{owner}/{repo}/contents/{file_path}"
|
||||
if branch and branch != "main":
|
||||
url += f"?ref={branch}"
|
||||
try:
|
||||
response = self._request("GET", url)
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
return None
|
||||
raise
|
||||
|
||||
def create_user(self, username, password, email):
|
||||
print(f"--- Creating user: {username} ---")
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"email": email,
|
||||
"must_change_password": False,
|
||||
"send_notify": False
|
||||
}
|
||||
try:
|
||||
self._request("POST", "admin/users", json=data)
|
||||
print(f"User '{username}' created.")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 422: # Already exists
|
||||
print(f"User '{username}' already exists. Updating password...")
|
||||
# Update password to be sure it matches our expectation
|
||||
self._request("PATCH", f"admin/users/{username}", json={"password": password, "login_name": username})
|
||||
else:
|
||||
raise
|
||||
|
||||
def get_user_token(self, username, password, token_name="test-token"):
|
||||
print(f"--- Getting token for user: {username} ---")
|
||||
url = f"{self.base_url}/api/v1/users/{username}/tokens"
|
||||
|
||||
# Create new token using Basic Auth
|
||||
response = requests.post(url, auth=(username, password), json={"name": token_name})
|
||||
if response.status_code == 201:
|
||||
return response.json()["sha1"]
|
||||
response.raise_for_status()
|
||||
|
||||
def create_org(self, org_name):
|
||||
print(f"--- Checking organization: {org_name} ---")
|
||||
try:
|
||||
@@ -116,18 +71,6 @@ class GiteaAPIClient:
|
||||
print(f"Organization '{org_name}' created.")
|
||||
else:
|
||||
raise
|
||||
print(f"--- Checking organization: {org_name} ---")
|
||||
try:
|
||||
self._request("GET", f"orgs/{org_name}")
|
||||
print(f"Organization '{org_name}' already exists.")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print(f"Creating organization '{org_name}'...")
|
||||
data = {"username": org_name, "full_name": org_name}
|
||||
self._request("POST", "orgs", json=data)
|
||||
print(f"Organization '{org_name}' created.")
|
||||
else:
|
||||
raise
|
||||
|
||||
def create_repo(self, org_name, repo_name):
|
||||
print(f"--- Checking repository: {org_name}/{repo_name} ---")
|
||||
@@ -148,7 +91,7 @@ class GiteaAPIClient:
|
||||
}
|
||||
self._request("POST", f"orgs/{org_name}/repos", json=data)
|
||||
print(f"Repository '{org_name}/{repo_name}' created with a README.")
|
||||
time.sleep(0.1) # Added delay to allow Git operations to become available
|
||||
time.sleep(1) # Added delay to allow Git operations to become available
|
||||
else:
|
||||
raise
|
||||
|
||||
@@ -204,8 +147,30 @@ index 0000000..{pkg_b_sha}
|
||||
+++ b/pkgB
|
||||
@@ -0,0 +1 @@
|
||||
+Subproject commit {pkg_b_sha}
|
||||
diff --git a/workflow.config b/workflow.config
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/workflow.config
|
||||
@@ -0,0 +7 @@
|
||||
+{{
|
||||
+ "Workflows": ["pr"],
|
||||
+ "GitProjectName": "products/SLFO#main",
|
||||
+ "Organization": "pool",
|
||||
+ "Branch": "main",
|
||||
+ "ManualMergeProject": true,
|
||||
+ "Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||
+}}
|
||||
diff --git a/staging.config b/staging.config
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/staging.config
|
||||
@@ -0,0 +3 @@
|
||||
+{{
|
||||
+ "ObsProject": "openSUSE:Leap:16.0",
|
||||
+ "StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||
+}}
|
||||
"""
|
||||
message = "Add pkgA and pkgB as submodules"
|
||||
message = "Add pkgA and pkgB as submodules and config files"
|
||||
data = {
|
||||
"branch": "main",
|
||||
"content": diff_content,
|
||||
@@ -226,161 +191,57 @@ index 0000000..{pkg_b_sha}
|
||||
self._request("PATCH", f"repos/{org_name}/{repo_name}", json=repo_data)
|
||||
print(f"Repository settings for '{org_name}/{repo_name}' updated.")
|
||||
|
||||
def create_label(self, owner: str, repo: str, name: str, color: str = "#abcdef"):
|
||||
print(f"--- Creating label '{name}' in {owner}/{repo} ---")
|
||||
url = f"repos/{owner}/{repo}/labels"
|
||||
data = {
|
||||
"name": name,
|
||||
"color": color
|
||||
}
|
||||
try:
|
||||
self._request("POST", url, json=data)
|
||||
print(f"Label '{name}' created.")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 422: # Already exists
|
||||
print(f"Label '{name}' already exists.")
|
||||
else:
|
||||
raise
|
||||
|
||||
def create_file(self, owner: str, repo: str, file_path: str, content: str, branch: str = "main", message: str = "Add file"):
|
||||
file_info = self.get_file_info(owner, repo, file_path, branch=branch)
|
||||
|
||||
data = {
|
||||
"content": base64.b64encode(content.encode('utf-8')).decode('ascii'),
|
||||
"branch": branch,
|
||||
"message": message
|
||||
}
|
||||
|
||||
if file_info:
|
||||
print(f"--- Updating file {file_path} in {owner}/{repo} ---")
|
||||
# Re-fetch file_info to get the latest SHA right before update
|
||||
latest_file_info = self.get_file_info(owner, repo, file_path, branch=branch)
|
||||
if not latest_file_info:
|
||||
raise Exception(f"File {file_path} disappeared during update attempt.")
|
||||
data["sha"] = latest_file_info["sha"]
|
||||
data["message"] = f"Update {file_path}"
|
||||
method = "PUT"
|
||||
else:
|
||||
print(f"--- Creating file {file_path} in {owner}/{repo} ---")
|
||||
method = "POST"
|
||||
|
||||
url = f"repos/{owner}/{repo}/contents/{file_path}"
|
||||
self._request(method, url, json=data)
|
||||
print(f"File {file_path} {'updated' if file_info else 'created'} in {owner}/{repo}.")
|
||||
|
||||
def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str, use_fork: bool, base_branch: str = "main", body: str = ""):
|
||||
def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str):
|
||||
owner, repo = repo_full_name.split("/")
|
||||
|
||||
head_owner, head_repo = owner, repo
|
||||
|
||||
if use_fork:
|
||||
sudo_user = self.headers.get("Sudo")
|
||||
head_owner = sudo_user
|
||||
head_repo = repo
|
||||
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
|
||||
|
||||
print(f"--- Forking {repo_full_name} ---")
|
||||
try:
|
||||
self._request("POST", f"repos/{owner}/{repo}/forks", json={})
|
||||
print(f"--- Forked to {head_owner}/{head_repo} ---")
|
||||
time.sleep(0.5) # Give more time for fork to be ready
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 409: # Already forked
|
||||
print(f"--- Already forked to {head_owner}/{head_repo} ---")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Create a unique branch in the FORK
|
||||
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
||||
print(f"--- Creating branch {new_branch_name} in {head_owner}/{head_repo} from {base_branch} ({base_commit_sha}) ---")
|
||||
self._request("POST", f"repos/{head_owner}/{head_repo}/branches", json={
|
||||
"new_branch_name": new_branch_name,
|
||||
"old_ref": base_commit_sha
|
||||
})
|
||||
else:
|
||||
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
|
||||
# Get the latest commit SHA of the base branch from the ORIGINAL repo
|
||||
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
||||
|
||||
# Try to create the branch in the ORIGINAL repo
|
||||
print(f"--- Creating branch {new_branch_name} in {repo_full_name} ---")
|
||||
self._request("POST", f"repos/{owner}/{repo}/branches", json={
|
||||
"new_branch_name": new_branch_name,
|
||||
"old_ref": base_commit_sha
|
||||
})
|
||||
url = f"repos/{owner}/{repo}/pulls"
|
||||
base_branch = "main"
|
||||
|
||||
# Apply the diff using diffpatch in the branch (wherever it is)
|
||||
print(f"--- Applying diff to {head_owner}/{head_repo} branch {new_branch_name} ---")
|
||||
self._request("POST", f"repos/{head_owner}/{head_repo}/diffpatch", json={
|
||||
"branch": new_branch_name,
|
||||
"content": diff_content,
|
||||
"message": title
|
||||
# Create a new branch for the PR
|
||||
new_branch_name = f"pr-branch-{int(time.time())}"
|
||||
|
||||
# Get the latest commit SHA of the base branch
|
||||
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
|
||||
|
||||
# Create the new branch
|
||||
self._request("POST", f"repos/{owner}/{repo}/branches", json={
|
||||
"new_branch_name": new_branch_name,
|
||||
"old_ref": base_commit_sha # Use the commit SHA directly
|
||||
})
|
||||
|
||||
# Now create the PR in the ORIGINAL repo
|
||||
# Create a new file or modify an existing one in the new branch
|
||||
file_path = f"test-file-{int(time.time())}.txt"
|
||||
file_content = "This is a test file for the PR."
|
||||
self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
|
||||
"content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
|
||||
"message": "Add test file",
|
||||
"branch": new_branch_name
|
||||
})
|
||||
|
||||
# Now create the PR
|
||||
data = {
|
||||
"head": f"{head_owner}:{new_branch_name}" if head_owner != owner else new_branch_name,
|
||||
"head": new_branch_name, # Use the newly created branch as head
|
||||
"base": base_branch,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"allow_maintainer_edit": True
|
||||
"body": "Test Pull Request"
|
||||
}
|
||||
print(f"--- Creating PR in {repo_full_name} from {data['head']} ---")
|
||||
response = self._request("POST", f"repos/{owner}/{repo}/pulls", json=data)
|
||||
response = self._request("POST", url, json=data)
|
||||
return response.json()
|
||||
|
||||
def create_branch(self, owner: str, repo: str, new_branch_name: str, old_ref: str):
|
||||
print(f"--- Checking branch '{new_branch_name}' in {owner}/{repo} ---")
|
||||
try:
|
||||
self._request("GET", f"repos/{owner}/{repo}/branches/{new_branch_name}")
|
||||
print(f"Branch '{new_branch_name}' already exists.")
|
||||
return
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code != 404:
|
||||
raise # Re-raise other HTTP errors
|
||||
|
||||
print(f"--- Creating branch '{new_branch_name}' in {owner}/{repo} from {old_ref} ---")
|
||||
url = f"repos/{owner}/{repo}/branches"
|
||||
data = {
|
||||
"new_branch_name": new_branch_name,
|
||||
"old_ref": old_ref
|
||||
}
|
||||
self._request("POST", url, json=data)
|
||||
print(f"Branch '{new_branch_name}' created in {owner}/{repo}.")
|
||||
|
||||
def ensure_branch_exists(self, owner: str, repo: str, branch: str = "main", timeout: int = 10):
|
||||
print(f"--- Ensuring branch '{branch}' exists in {owner}/{repo} ---")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
self._request("GET", f"repos/{owner}/{repo}/branches/{branch}")
|
||||
print(f"Branch '{branch}' confirmed in {owner}/{repo}.")
|
||||
return
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print(f"Branch '{branch}' not found yet in {owner}/{repo}. Retrying...")
|
||||
time.sleep(1)
|
||||
continue
|
||||
raise
|
||||
raise Exception(f"Timeout waiting for branch {branch} in {owner}/{repo}")
|
||||
|
||||
|
||||
|
||||
def modify_gitea_pr(self, repo_full_name: str, pr_number: int, diff_content: str, message: str):
|
||||
owner, repo = repo_full_name.split("/")
|
||||
|
||||
# Get PR details to find the head branch AND head repo
|
||||
# Get PR details to find the head branch
|
||||
pr_details = self._request("GET", f"repos/{owner}/{repo}/pulls/{pr_number}").json()
|
||||
head_branch = pr_details["head"]["ref"]
|
||||
head_repo_owner = pr_details["head"]["repo"]["owner"]["login"]
|
||||
head_repo_name = pr_details["head"]["repo"]["name"]
|
||||
|
||||
# Apply the diff using diffpatch
|
||||
print(f"--- Modifying PR #{pr_number} in {head_repo_owner}/{head_repo_name} branch {head_branch} ---")
|
||||
self._request("POST", f"repos/{head_repo_owner}/{head_repo_name}/diffpatch", json={
|
||||
"branch": head_branch,
|
||||
"content": diff_content,
|
||||
"message": message
|
||||
file_path = f"modified-file-{int(time.time())}.txt"
|
||||
file_content = "This is a modified test file for the PR."
|
||||
|
||||
self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
|
||||
"content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
|
||||
"message": message,
|
||||
"branch": head_branch
|
||||
})
|
||||
|
||||
def update_gitea_pr_properties(self, repo_full_name: str, pr_number: int, **kwargs):
|
||||
@@ -400,12 +261,12 @@ index 0000000..{pkg_b_sha}
|
||||
timeline_events = response.json()
|
||||
if timeline_events: # Check if timeline_events list is not empty
|
||||
return timeline_events
|
||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 1 seconds...")
|
||||
time.sleep(1)
|
||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 1 seconds...")
|
||||
time.sleep(1)
|
||||
print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
else:
|
||||
raise # Re-raise other HTTP errors
|
||||
raise Exception(f"Failed to retrieve timeline for PR {pr_number} after multiple retries.")
|
||||
@@ -422,12 +283,12 @@ index 0000000..{pkg_b_sha}
|
||||
print(f"Attempt {i+1}: Comments for PR {pr_number} received: {comments}") # Added debug print
|
||||
if comments: # Check if comments list is not empty
|
||||
return comments
|
||||
print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 1 seconds...")
|
||||
time.sleep(1)
|
||||
print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 1 seconds...")
|
||||
time.sleep(1)
|
||||
print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
else:
|
||||
raise # Re-raise other HTTP errors
|
||||
raise Exception(f"Failed to retrieve comments for PR {pr_number} after multiple retries.")
|
||||
@@ -438,87 +299,3 @@ index 0000000..{pkg_b_sha}
|
||||
response = self._request("GET", url)
|
||||
return response.json()
|
||||
|
||||
def create_review(self, repo_full_name: str, pr_number: int, event: str = "APPROVED", body: str = "LGTM"):
|
||||
owner, repo = repo_full_name.split("/")
|
||||
|
||||
# Check if this user already has an APPROVED review to avoid 422
|
||||
current_user = self.headers.get("Sudo") or "admin" # simplified
|
||||
existing_reviews = self.list_reviews(repo_full_name, pr_number)
|
||||
for r in existing_reviews:
|
||||
if r["user"]["login"] == current_user and r["state"] == "APPROVED" and event == "APPROVED":
|
||||
print(f"User {current_user} already has an APPROVED review for {repo_full_name} PR #{pr_number}")
|
||||
return r
|
||||
|
||||
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
|
||||
data = {
|
||||
"event": event,
|
||||
"body": body
|
||||
}
|
||||
print(f"--- Creating and submitting review ({event}) for {repo_full_name} PR #{pr_number} as {current_user} ---")
|
||||
try:
|
||||
response = self._request("POST", url, json=data)
|
||||
review = response.json()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# If it fails with 422, it might be because a review is already pending or something else
|
||||
print(f"Failed to create review: {e.response.text}")
|
||||
# Try to find a pending review to submit
|
||||
existing_reviews = self.list_reviews(repo_full_name, pr_number)
|
||||
pending_review = next((r for r in existing_reviews if r["user"]["login"] == current_user and r["state"] == "PENDING"), None)
|
||||
if pending_review:
|
||||
review = pending_review
|
||||
else:
|
||||
raise
|
||||
|
||||
# If the state is PENDING, we submit it.
|
||||
if review.get("state") == "PENDING":
|
||||
review_id = review["id"]
|
||||
submit_url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}"
|
||||
submit_data = {
|
||||
"event": event,
|
||||
"body": body
|
||||
}
|
||||
try:
|
||||
self._request("POST", submit_url, json=submit_data)
|
||||
print(f"--- Review {review_id} submitted ---")
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if "already" in e.response.text.lower() or "stay pending" in e.response.text.lower():
|
||||
print(f"Review {review_id} could not be submitted further: {e.response.text}")
|
||||
else:
|
||||
raise
|
||||
|
||||
return review
|
||||
|
||||
def list_reviews(self, repo_full_name: str, pr_number: int):
|
||||
owner, repo = repo_full_name.split("/")
|
||||
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
|
||||
response = self._request("GET", url)
|
||||
return response.json()
|
||||
|
||||
def approve_requested_reviews(self, repo_full_name: str, pr_number: int):
|
||||
print(f"--- Checking for REQUEST_REVIEW state in {repo_full_name} PR #{pr_number} ---")
|
||||
reviews = self.list_reviews(repo_full_name, pr_number)
|
||||
|
||||
requested_reviews = [r for r in reviews if r["state"] == "REQUEST_REVIEW"]
|
||||
if not requested_reviews:
|
||||
print(f"No reviews in REQUEST_REVIEW state found for {repo_full_name} PR #{pr_number}")
|
||||
return
|
||||
|
||||
admin_token = self.headers["Authorization"].split(" ")[1]
|
||||
for r in requested_reviews:
|
||||
reviewer_username = r["user"]["login"]
|
||||
print(f"Reacting on REQUEST_REVIEW for user {reviewer_username} by approving...")
|
||||
|
||||
reviewer_client = GiteaAPIClient(base_url=self.base_url, token=admin_token, sudo=reviewer_username)
|
||||
time.sleep(1) # give a chance to avoid possible concurrency issues with reviews request/approval
|
||||
reviewer_client.create_review(repo_full_name, pr_number, event="APPROVED", body="Approving requested review")
|
||||
|
||||
def restart_service(self, service_name: str):
|
||||
print(f"--- Restarting service: {service_name} ---")
|
||||
try:
|
||||
# Assumes podman-compose.yml is in the parent directory of tests/lib
|
||||
subprocess.run(["podman-compose", "restart", service_name], check=True, cwd=os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)))
|
||||
print(f"Service {service_name} restarted successfully.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error restarting service {service_name}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from tests.lib.common_test_utils import (
|
||||
def test_pr_workflow_succeeded(gitea_env, mock_build_result):
|
||||
"""End-to-end test for a successful PR workflow."""
|
||||
diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False)
|
||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed")
|
||||
initial_pr_number = pr["number"]
|
||||
|
||||
compose_dir = Path(__file__).parent.parent
|
||||
@@ -87,7 +87,7 @@ def test_pr_workflow_succeeded(gitea_env, mock_build_result):
|
||||
def test_pr_workflow_failed(gitea_env, mock_build_result):
|
||||
"""End-to-end test for a failed PR workflow."""
|
||||
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False)
|
||||
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail")
|
||||
initial_pr_number = pr["number"]
|
||||
|
||||
compose_dir = Path(__file__).parent.parent
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import pytest
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from tests.lib.common_test_utils import (
|
||||
GiteaAPIClient,
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# TEST CASES
|
||||
# =============================================================================
|
||||
|
||||
@pytest.mark.t001
|
||||
@pytest.mark.xfail(reason="review pending label is not applied")
|
||||
def test_001_project_pr_labels(label_env, staging_bot_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. Setup custom workflow.config with Labels: { "StagingAuto": "staging/Backlog", "ReviewPending": "review/Pending" }.
|
||||
2. Create a package PR in 'label-test' branch.
|
||||
3. Make sure the workflow-pr service created related project PR in 'label-test' branch.
|
||||
4. Wait for the project PR to have the label "staging/Backlog".
|
||||
5. Post approval from autogits_obs_staging_bot.
|
||||
6. Check that the project PR gets the label "review/Pending".
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = label_env
|
||||
|
||||
# 1. Create a package PR
|
||||
diff = """diff --git a/label_test_fixture.txt b/label_test_fixture.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} ---")
|
||||
package_pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test Labels Fixture", False, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgA#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = None
|
||||
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||
for _ in range(40):
|
||||
time.sleep(1)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_number = int(match.group(1))
|
||||
break
|
||||
if project_pr_number:
|
||||
break
|
||||
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||
|
||||
# 3. Wait for the project PR to have the label "staging/Backlog"
|
||||
print(f"Checking for 'staging/Backlog' label on project PR products/SLFO#{project_pr_number}...")
|
||||
|
||||
backlog_label_found = False
|
||||
expected_backlog_label = "staging/Backlog"
|
||||
|
||||
for _ in range(20):
|
||||
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||
labels = project_pr_details.get("labels", [])
|
||||
label_names = [l["name"] for l in labels]
|
||||
if expected_backlog_label in label_names:
|
||||
backlog_label_found = True
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
assert backlog_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_backlog_label}'."
|
||||
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_backlog_label}'.")
|
||||
|
||||
# 4. Post approval from autogits_obs_staging_bot
|
||||
print(f"--- Posting approval from autogits_obs_staging_bot on project PR products/SLFO#{project_pr_number} ---")
|
||||
staging_bot_client.create_review("products/SLFO", project_pr_number, event="APPROVED", body="Staging OK")
|
||||
|
||||
# 5. Check that the project PR has the label "review/Pending"
|
||||
print(f"Checking for 'review/Pending' label on project PR products/SLFO#{project_pr_number}...")
|
||||
|
||||
pending_label_found = False
|
||||
expected_pending_label = "review/Pending"
|
||||
|
||||
for _ in range(20):
|
||||
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||
labels = project_pr_details.get("labels", [])
|
||||
label_names = [l["name"] for l in labels]
|
||||
print(f"Current labels: {label_names}")
|
||||
if expected_pending_label in label_names:
|
||||
pending_label_found = True
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
assert pending_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_pending_label}'."
|
||||
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_pending_label}'.")
|
||||
@@ -1,81 +0,0 @@
|
||||
import pytest
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from tests.lib.common_test_utils import GiteaAPIClient
|
||||
|
||||
@pytest.mark.t001
|
||||
def test_001_automerge(automerge_env, test_user_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. Setup custom workflow.config with mandatory reviewers (+usera, +userb).
|
||||
2. Create a package PR in 'merge' branch.
|
||||
3. Make sure the workflow-pr service created related project PR in 'merge' branch.
|
||||
4. React on 'requested' reviews by approving them.
|
||||
5. Make sure both PRs are merged automatically by the workflow-pr service.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, merge_branch_name = automerge_env
|
||||
|
||||
# 1. Create a package PR
|
||||
diff = """diff --git a/merge_test_fixture.txt b/merge_test_fixture.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---")
|
||||
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgA#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = None
|
||||
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||
for _ in range(40):
|
||||
time.sleep(1)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_number = int(match.group(1))
|
||||
break
|
||||
if project_pr_number:
|
||||
break
|
||||
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||
|
||||
# 4. Make sure both PRs are merged automatically by the workflow-pr service
|
||||
print("Polling for PR merge status and reacting on REQUEST_REVIEW...")
|
||||
package_merged = False
|
||||
project_merged = False
|
||||
|
||||
for i in range(15): # Poll for up to 15 seconds
|
||||
# Package PR
|
||||
if not package_merged:
|
||||
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||
if pkg_details.get("merged"):
|
||||
package_merged = True
|
||||
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
|
||||
else:
|
||||
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
|
||||
|
||||
# Project PR
|
||||
if not project_merged:
|
||||
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||
if prj_details.get("merged"):
|
||||
project_merged = True
|
||||
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||
else:
|
||||
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||
|
||||
if package_merged and project_merged:
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
|
||||
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||
print("Both PRs merged successfully.")
|
||||
@@ -1,382 +0,0 @@
|
||||
import pytest
|
||||
import re
|
||||
import time
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from tests.lib.common_test_utils import GiteaAPIClient
|
||||
|
||||
@pytest.mark.t001
|
||||
def test_001_review_requests_matching_config(automerge_env, ownerA_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
|
||||
2. Check that review request comes to ownerB and ownerBB (package maintainers)
|
||||
AND usera and userb (from workflow.config).
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = automerge_env
|
||||
|
||||
# 1. Create a package PR for pool/pkgB as ownerA
|
||||
diff = """diff --git a/pkgB_test_001.txt b/pkgB_test_001.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Requests Config", True, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||
|
||||
# 2. Check that review requests came to ownerB, ownerBB, usera, and userb
|
||||
print("Checking for review requests from maintainers and workflow.config...")
|
||||
reviewers_requested = set()
|
||||
expected_reviewers = {"ownerB", "ownerBB", "usera", "userb"}
|
||||
for _ in range(30):
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
if expected_reviewers.issubset(reviewers_requested):
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
for reviewer in expected_reviewers:
|
||||
assert reviewer in reviewers_requested, f"{reviewer} was not requested for review. Requested: {reviewers_requested}"
|
||||
|
||||
print(f"Confirmed: {expected_reviewers} were requested for review.")
|
||||
|
||||
|
||||
@pytest.mark.t004
|
||||
def test_004_maintainer(maintainer_env, ownerA_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. workflow.config will not have users with '+' sign.
|
||||
2. The package PR is opened by the package maintainer (ownerA for pkgA).
|
||||
3. Do not submit any review approval.
|
||||
4. Check that both PRs are automatically merged anyway.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||
|
||||
# 0. Smoke test ownerA_client
|
||||
print(f"--- Smoke testing ownerA_client ---")
|
||||
ownerA_client._request("GET", "users/admin")
|
||||
print(f"ownerA_client smoke test passed")
|
||||
|
||||
# 0.1 Verify all users from config exist
|
||||
print("--- Verifying all users from config exist ---")
|
||||
import json
|
||||
wf_file = gitea_env.get_file_info("products", "SLFO", "workflow.config", branch=branch_name)
|
||||
wf = json.loads(base64.b64decode(wf_file["content"]).decode("utf-8"))
|
||||
mt_file = gitea_env.get_file_info("products", "SLFO", "_maintainership.json", branch=branch_name)
|
||||
mt = json.loads(base64.b64decode(mt_file["content"]).decode("utf-8"))
|
||||
|
||||
expected_users = set()
|
||||
for r in wf.get("Reviewers", []):
|
||||
username = r.lstrip("+-")
|
||||
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
|
||||
expected_users.add(username)
|
||||
for pkg_users in mt.values():
|
||||
for username in pkg_users:
|
||||
expected_users.add(username)
|
||||
|
||||
for username in expected_users:
|
||||
gitea_env._request("GET", f"users/{username}")
|
||||
print(f"Verified user exists: {username}")
|
||||
|
||||
# 1. Create a package PR as ownerA
|
||||
diff = """diff --git a/maintainer_test_fixture.txt b/maintainer_test_fixture.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} as ownerA ---")
|
||||
package_pr = ownerA_client.create_gitea_pr("pool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgA#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = None
|
||||
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
|
||||
for _ in range(40):
|
||||
time.sleep(1)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_number = int(match.group(1))
|
||||
break
|
||||
if project_pr_number:
|
||||
break
|
||||
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||
|
||||
# 3. Make sure both PRs are merged automatically WITHOUT manual approvals
|
||||
print("Polling for PR merge status (only bot approval allowed)...")
|
||||
package_merged = False
|
||||
project_merged = False
|
||||
|
||||
for i in range(15): # Poll for up to 15 seconds
|
||||
# Package PR
|
||||
if not package_merged:
|
||||
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||
if pkg_details.get("merged"):
|
||||
package_merged = True
|
||||
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
|
||||
else:
|
||||
# Approve ONLY bot if requested
|
||||
reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number)
|
||||
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
|
||||
|
||||
# Project PR
|
||||
if not project_merged:
|
||||
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||
if prj_details.get("merged"):
|
||||
project_merged = True
|
||||
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||
else:
|
||||
# Approve ONLY bot if requested
|
||||
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
|
||||
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||
|
||||
if package_merged and project_merged:
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
|
||||
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||
print("Both PRs merged successfully by maintainer rule.")
|
||||
|
||||
|
||||
@pytest.mark.t005
|
||||
def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
|
||||
2. Check that review request comes to both ownerB and ownerBB.
|
||||
3. ownerB doesn't leave review.
|
||||
4. check that review from ownerBB was enough to get both PRs merged.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||
|
||||
# 1. Create a package PR for pool/pkgB as ownerA
|
||||
diff = """diff --git a/pkgB_test_fixture.txt b/pkgB_test_fixture.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = None
|
||||
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||
for _ in range(40):
|
||||
time.sleep(1)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_number = int(match.group(1))
|
||||
break
|
||||
if project_pr_number:
|
||||
break
|
||||
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||
|
||||
# 3. Check that review requests came to ownerB and ownerBB
|
||||
print("Checking for review requests from ownerB and ownerBB...")
|
||||
reviewers_requested = set()
|
||||
for _ in range(20):
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
assert "ownerB" in reviewers_requested, f"ownerB was not requested for review. Requested: {reviewers_requested}"
|
||||
assert "ownerBB" in reviewers_requested, f"ownerBB was not requested for review. Requested: {reviewers_requested}"
|
||||
print(f"Confirmed: ownerB and ownerBB were requested for review.")
|
||||
|
||||
# 4. ownerBB leaves review, ownerB does not.
|
||||
print("ownerBB approving the PR...")
|
||||
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
|
||||
|
||||
# 5. Check that both PRs are merged automatically
|
||||
print("Polling for PR merge status (only bot approval allowed for project PR)...")
|
||||
package_merged = False
|
||||
project_merged = False
|
||||
|
||||
for i in range(15): # Poll for up to 15 seconds
|
||||
# Package PR
|
||||
if not package_merged:
|
||||
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||
if pkg_details.get("merged"):
|
||||
package_merged = True
|
||||
print(f"Package PR pool/pkgB#{package_pr_number} merged.")
|
||||
|
||||
# Project PR
|
||||
if not project_merged:
|
||||
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
|
||||
if prj_details.get("merged"):
|
||||
project_merged = True
|
||||
print(f"Project PR products/SLFO#{project_pr_number} merged.")
|
||||
else:
|
||||
# Approve ONLY bot if requested
|
||||
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
|
||||
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
|
||||
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
|
||||
|
||||
if package_merged and project_merged:
|
||||
break
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
assert package_merged, f"Package PR pool/pkgB#{package_pr_number} was not merged automatically."
|
||||
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
|
||||
print("Both PRs merged successfully with only one maintainer approval.")
|
||||
|
||||
|
||||
@pytest.mark.t006
|
||||
@pytest.mark.xfail(reason="tbd flacky in ci")
|
||||
def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
|
||||
2. Check that review request comes to both ownerB and ownerBB.
|
||||
3. ownerBB rejects the PR (REQUEST_CHANGES).
|
||||
4. Check that review request for ownerB is removed.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = maintainer_env
|
||||
|
||||
# 1. Create a package PR for pool/pkgB as ownerA
|
||||
diff = """diff --git a/pkgB_rejection_test.txt b/pkgB_rejection_test.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||
|
||||
# 2. Check that review requests came to ownerB and ownerBB
|
||||
print("Checking for review requests from ownerB and ownerBB...")
|
||||
for _ in range(20):
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
|
||||
|
||||
# 3. ownerBB rejects the PR
|
||||
print("ownerBB rejecting the PR...")
|
||||
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB")
|
||||
|
||||
# 4. Check that review request for ownerB is removed
|
||||
print("Checking if ownerB's review request is removed...")
|
||||
for _ in range(20):
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
if "ownerB" not in reviewers_requested:
|
||||
print("Confirmed: ownerB's review request was removed.")
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
pytest.fail("ownerB's review request was not removed after ownerBB rejection.")
|
||||
|
||||
|
||||
@pytest.mark.t007
|
||||
@pytest.mark.xfail(reason="TBD troubleshoot")
|
||||
def test_007_review_required_needs_all_approvals(review_required_env, ownerA_client, ownerBB_client):
|
||||
"""
|
||||
Test scenario:
|
||||
1. it uses new fixture with "ReviewRequired = true" in the workflow.config.
|
||||
2. Package PR for pkgB opened by ownerA.
|
||||
3. Check review request comes to both ownerB and ownerBB.
|
||||
4. ownerBB approves.
|
||||
5. make sure that review is not merged automatically and the request for ownerB is not removed.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, branch_name = review_required_env
|
||||
|
||||
# 0. Smoke test ownerA_client
|
||||
print(f"--- Smoke testing ownerA_client ---")
|
||||
ownerA_client._request("GET", "users/admin")
|
||||
print(f"ownerA_client smoke test passed")
|
||||
|
||||
# 1. Create a package PR for pool/pkgB as ownerA
|
||||
diff = """diff --git a/pkgB_review_required_test.txt b/pkgB_review_required_test.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
"""
|
||||
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
|
||||
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Required", True, base_branch=branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created package PR pool/pkgB#{package_pr_number}")
|
||||
|
||||
# 2. Make sure the workflow-pr service created related project PR
|
||||
project_pr_number = None
|
||||
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
|
||||
for _ in range(40):
|
||||
time.sleep(1)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_number = int(match.group(1))
|
||||
break
|
||||
if project_pr_number:
|
||||
break
|
||||
|
||||
assert project_pr_number is not None, "Workflow bot did not create a project PR."
|
||||
print(f"Found project PR: products/SLFO#{project_pr_number}")
|
||||
|
||||
# 3. Check that review requests came to ownerB and ownerBB
|
||||
print("Checking for review requests from ownerB and ownerBB...")
|
||||
for _ in range(20):
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
|
||||
|
||||
# 4. ownerBB leaves review, ownerB does not.
|
||||
print("ownerBB approving the PR...")
|
||||
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
|
||||
|
||||
# 5. Check that the PR is NOT merged automatically and ownerB request remains
|
||||
print("Waiting to ensure PR is NOT merged and ownerB request remains...")
|
||||
for i in range(10):
|
||||
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
review_states = [(r["user"]["login"], r["state"]) for r in reviews]
|
||||
print(f"Attempt {i+1}: Merged={pkg_details.get('merged')}, Reviews={review_states}")
|
||||
time.sleep(2)
|
||||
|
||||
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
|
||||
assert not pkg_details.get("merged"), "Package PR was merged automatically but it should NOT have been (ReviewRequired=true)."
|
||||
|
||||
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
|
||||
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
|
||||
assert "ownerB" in reviewers_requested, f"ownerB's review request was removed, but it should have remained. All reviews: {[(r['user']['login'], r['state']) for r in reviews]}"
|
||||
|
||||
print("Confirmed: PR not merged and ownerB review request remains as expected.")
|
||||
@@ -18,12 +18,11 @@ pytest.initial_pr_number = None
|
||||
pytest.forwarded_pr_number = None
|
||||
|
||||
|
||||
@pytest.mark.t001
|
||||
@pytest.mark.dependency()
|
||||
def test_001_project_pr(gitea_env):
|
||||
"""Forwarded PR correct title"""
|
||||
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
|
||||
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR", False)
|
||||
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR")
|
||||
pytest.initial_pr_number = pytest.pr["number"]
|
||||
time.sleep(5) # Give Gitea some time to process the PR and make the timeline available
|
||||
|
||||
@@ -57,7 +56,6 @@ def test_001_project_pr(gitea_env):
|
||||
), "Forwarded PR correct title"
|
||||
|
||||
|
||||
@pytest.mark.t002
|
||||
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
||||
def test_002_updated_project_pr(gitea_env):
|
||||
"""Forwarded PR head is updated"""
|
||||
@@ -78,7 +76,6 @@ def test_002_updated_project_pr(gitea_env):
|
||||
assert sha_changed, "Forwarded PR has sha updated"
|
||||
|
||||
|
||||
@pytest.mark.t003
|
||||
@pytest.mark.dependency(depends=["test_001_project_pr"])
|
||||
def test_003_wip(gitea_env):
|
||||
"""WIP flag set for PR"""
|
||||
@@ -118,209 +115,3 @@ def test_003_wip(gitea_env):
|
||||
wip_flag_removed = True
|
||||
break
|
||||
assert wip_flag_removed, "WIP flag was not removed from the forwarded PR."
|
||||
|
||||
|
||||
@pytest.mark.t005
|
||||
@pytest.mark.xfail(reason="works only in ibs_state branch?")
|
||||
@pytest.mark.dependency()
|
||||
def test_005_NoProjectGitPR_edits_disabled(no_project_git_pr_env, test_user_client):
|
||||
"""
|
||||
Reworked test: Sets workflow.config with NoProjectGitPR: true and creates a Package PR.
|
||||
Verifies that no Project PR is created, then manually creates one and checks for bot warning.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
|
||||
|
||||
# 1. Create a Package PR (without "Allow edits from maintainers" enabled)
|
||||
initial_diff = """diff --git a/first_file.txt b/first_file.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
--- /dev/null
|
||||
+++ b/first_file.txt
|
||||
@@ -0,0 +1 @@
|
||||
+Initial content
|
||||
"""
|
||||
package_pr = test_user_client.create_gitea_pr("pool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
print(f"Created Package PR #{package_pr_number}")
|
||||
|
||||
# 2. Verify that the workflow-pr bot did not create a Project PR
|
||||
project_pr_created = False
|
||||
for i in range(10): # Poll for some time
|
||||
time.sleep(2)
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_created = True
|
||||
break
|
||||
if project_pr_created:
|
||||
break
|
||||
|
||||
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
|
||||
print("Verification complete: No Project PR was created by the bot.")
|
||||
|
||||
# 3. Manually create the Project PR
|
||||
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
|
||||
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
||||
|
||||
project_pr_title = "Forwarded PRs: pkgA (Manual)"
|
||||
project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
|
||||
project_pr_diff = f"""diff --git a/pkgA b/pkgA
|
||||
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
|
||||
--- a/pkgA
|
||||
+++ b/pkgA
|
||||
@@ -1 +1 @@
|
||||
-Subproject commit {pkgA_main_sha}
|
||||
+Subproject commit {pkgA_pr_head_sha}
|
||||
"""
|
||||
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
|
||||
manual_project_pr_number = manual_project_pr["number"]
|
||||
|
||||
# Verify and set allow_maintainer_edit to False
|
||||
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=False)
|
||||
|
||||
# Verify that allow_maintainer_edit is now disabled
|
||||
updated_pr = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||
assert updated_pr.get("allow_maintainer_edit") is False, "Expected allow_maintainer_edit to be False after update"
|
||||
|
||||
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
|
||||
|
||||
# 4. Trigger an update on the Package PR to prompt the bot to react to the manual Project PR
|
||||
new_diff_content = """diff --git a/trigger_bot.txt b/trigger_bot.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
--- /dev/null
|
||||
+++ b/trigger_bot.txt
|
||||
@@ -0,0 +1 @@
|
||||
+Trigger content
|
||||
"""
|
||||
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Trigger bot update")
|
||||
|
||||
# 5. Verify that the bot adds a warning comment because it cannot update the manual PR (edits disabled)
|
||||
warning_found = False
|
||||
print(f"Polling Package PR #{package_pr_number} for warning comment...")
|
||||
for _ in range(20):
|
||||
time.sleep(3)
|
||||
comments = gitea_env.get_comments("pool/pkgA", package_pr_number)
|
||||
for comment in comments:
|
||||
# According to test-plan.md, the warning explains that it cannot update the PR.
|
||||
if "cannot update" in comment.get("body", "").lower():
|
||||
warning_found = True
|
||||
print(f"Warning comment found: {comment.get('body')}")
|
||||
break
|
||||
if warning_found:
|
||||
break
|
||||
|
||||
# assert warning_found, "Bot did not post the expected warning comment on the Package PR."
|
||||
# print("Verification complete: Bot posted a warning comment as expected.")
|
||||
|
||||
|
||||
@pytest.mark.t006
|
||||
@pytest.mark.xfail(reason="works only in ibs_state branch?")
|
||||
@pytest.mark.dependency()
|
||||
def test_006_NoProjectGitPR_edits_enabled(no_project_git_pr_env, test_user_client):
|
||||
"""
|
||||
Verify that no project PR is created when "NoProjectGitPR" is true
|
||||
and "Allow edits from maintainers" is enabled, using a dev branch.
|
||||
"""
|
||||
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
|
||||
|
||||
# 2. Create a Package PR with "Allow edits from maintainers" enabled
|
||||
diff = """diff --git a/new_feature.txt b/new_feature.txt
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
--- /dev/null
|
||||
+++ b/new_feature.txt
|
||||
@@ -0,0 +1 @@
|
||||
+New feature content
|
||||
"""
|
||||
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name)
|
||||
package_pr_number = package_pr["number"]
|
||||
|
||||
# Enable "Allow edits from maintainers"
|
||||
test_user_client.update_gitea_pr_properties("pool/pkgA", package_pr_number, allow_maintainer_edit=True)
|
||||
print(f"Created Package PR #{package_pr_number} and enabled 'Allow edits from maintainers'.")
|
||||
|
||||
# Get SHAs needed for the manual Project PR diff
|
||||
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
|
||||
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
|
||||
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
|
||||
|
||||
# 3. Assert that the workflow-pr bot did not create a Project PR in the products/SLFO repository
|
||||
project_pr_created = False
|
||||
for i in range(20): # Poll for a reasonable time
|
||||
time.sleep(2) # Wait a bit longer to be sure
|
||||
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
|
||||
for event in timeline_events:
|
||||
if event.get("type") == "pull_ref":
|
||||
if not (ref_issue := event.get("ref_issue")):
|
||||
continue
|
||||
url_to_check = ref_issue.get("html_url", "")
|
||||
# Regex now searches for products/SLFO/pulls/(\d+)
|
||||
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
|
||||
if match:
|
||||
project_pr_created = True
|
||||
break
|
||||
if project_pr_created:
|
||||
break
|
||||
|
||||
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
|
||||
print("Verification complete: No Project PR was created in products/SLFO as expected.")
|
||||
|
||||
# 1. Create that Project PR from the test code.
|
||||
project_pr_title = "Forwarded PRs: pkgA"
|
||||
project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
|
||||
project_pr_diff = f"""diff --git a/pkgA b/pkgA
|
||||
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
|
||||
--- a/pkgA
|
||||
+++ b/pkgA
|
||||
@@ -1 +1 @@
|
||||
-Subproject commit {pkgA_main_sha}
|
||||
+Subproject commit {pkgA_pr_head_sha}
|
||||
"""
|
||||
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
|
||||
manual_project_pr_number = manual_project_pr["number"]
|
||||
# Explicitly ensure allow_maintainer_edit is True (it should be by default now, but just in case)
|
||||
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=True)
|
||||
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
|
||||
time.sleep(5) # Give the bot time to potentially react or for the PR to settle
|
||||
|
||||
# Get initial SHA of the manually created Project PR
|
||||
initial_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||
initial_head_sha = initial_project_pr_details["head"]["sha"]
|
||||
print(f"Manually created Project PR initial head SHA: {initial_head_sha}")
|
||||
|
||||
# 2. Add new commit to the package PR.
|
||||
new_diff_content = """diff --git a/another_file.txt b/another_file.txt
|
||||
new file mode 100644
|
||||
index 0000000..f587a12
|
||||
--- /dev/null
|
||||
+++ b/another_file.txt
|
||||
@@ -0,0 +1 @@
|
||||
+Another file content
|
||||
"""
|
||||
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR")
|
||||
print(f"Added new commit to Package PR #{package_pr_number}.")
|
||||
time.sleep(5) # Give the bot time to react
|
||||
|
||||
# 3. Make sure the project PR is properly updated by the bot
|
||||
project_pr_updated = False
|
||||
print(f"Polling manually created Project PR #{manual_project_pr_number} for update...")
|
||||
for _ in range(20): # Poll for a reasonable time
|
||||
time.sleep(2) # Wait a bit longer to be sure
|
||||
current_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
|
||||
current_head_sha = current_project_pr_details["head"]["sha"]
|
||||
if current_head_sha != initial_head_sha:
|
||||
project_pr_updated = True
|
||||
print(f"Manually created Project PR updated. New head SHA: {current_head_sha}")
|
||||
break
|
||||
|
||||
assert project_pr_updated, "Manually created Project PR was not updated by the bot."
|
||||
print("Verification complete: Manually created Project PR was updated by the bot as expected.")
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/git
|
||||
RUN update-ca-certificates
|
||||
|
||||
# Install git and ssh
|
||||
RUN zypper -n in git-core openssh-clients binutils git-lfs || (tail -n 1000 /var/log/zypper.log; exit 1)
|
||||
RUN zypper -n in git-core openssh-clients binutils
|
||||
|
||||
# Copy the pre-built binary into the container
|
||||
COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
FROM registry.suse.com/bci/bci-base:15.7
|
||||
|
||||
# Add the custom CA to the trust store
|
||||
COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt
|
||||
COPY rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt
|
||||
RUN update-ca-certificates
|
||||
|
||||
RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-workflow/15.7/devel:Factory:git-workflow.repo
|
||||
RUN zypper --gpg-auto-import-keys ref
|
||||
|
||||
# Install git and ssh
|
||||
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils git-lfs || ( tail -n 1000 /var/log/zypper.log; exit 1 )
|
||||
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils
|
||||
|
||||
COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +4755 /usr/local/bin/entrypoint.sh
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
[
|
||||
"products/SLFO#main",
|
||||
"products/SLFO#dev",
|
||||
"products/SLFO#merge",
|
||||
"products/SLFO#maintainer-merge",
|
||||
"products/SLFO#review-required",
|
||||
"products/SLFO#label-test"
|
||||
"products/SLFO#main"
|
||||
]
|
||||
|
||||
@@ -50,10 +50,6 @@ const (
|
||||
|
||||
var runId uint
|
||||
|
||||
var GitWorkTreeAllocate func(string, string, string) (common.GitHandlerGenerator, error) = func(basePath, gitAuthor, email string) (common.GitHandlerGenerator, error) {
|
||||
return common.AllocateGitWorkTree(basePath, gitAuthor, email)
|
||||
}
|
||||
|
||||
func FetchPrGit(git common.Git, pr *models.PullRequest) error {
|
||||
// clone PR head via base (target) repo
|
||||
cloneURL := pr.Base.Repo.CloneURL
|
||||
@@ -148,9 +144,9 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
|
||||
|
||||
func ProcessRepoBuildStatus(results []*common.PackageBuildStatus) (status BuildStatusSummary) {
|
||||
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
|
||||
common.LogDebug("******* RESULTS: ")
|
||||
data, _ := xml.MarshalIndent(results, "", " ")
|
||||
@@ -195,23 +191,24 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return true, BuildStatusSummaryUnknown // true for 'missing'
|
||||
}
|
||||
|
||||
// Check for any unfinished builds
|
||||
// Check for any failures
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, ok := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
if !ok {
|
||||
common.LogInfo("unknown package result code:", pkgStatus.Code, "for package:", pkgStatus.Package)
|
||||
return false, BuildStatusSummaryUnknown
|
||||
}
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any failures
|
||||
// Check for any unfinished builds
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, _ := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
// 'ok' is already checked in the loop above
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +216,7 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return false, BuildStatusSummarySuccess
|
||||
}
|
||||
|
||||
func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
common.LogDebug("repo content fetching ...")
|
||||
err := FetchPrGit(git, pr)
|
||||
if err != nil {
|
||||
@@ -263,13 +260,13 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
|
||||
}
|
||||
|
||||
common.LogDebug("Trying first staging master project: ", stagingMasterPrj)
|
||||
meta, err := obs.GetProjectMeta(stagingMasterPrj)
|
||||
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
|
||||
if err == nil {
|
||||
// success, so we use that staging master project as our build project
|
||||
buildPrj = stagingMasterPrj
|
||||
} else {
|
||||
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
|
||||
meta, err = obs.GetProjectMeta(buildPrj)
|
||||
meta, err = ObsClient.GetProjectMeta(buildPrj)
|
||||
}
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
|
||||
@@ -333,10 +330,10 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
|
||||
// stagingProject:$buildProject
|
||||
// ^- stagingProject:$buildProject:$subProjectName (based on templateProject)
|
||||
|
||||
func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
common.LogDebug("Setup QA sub projects")
|
||||
common.LogDebug("reading templateProject ", templateProject)
|
||||
templateMeta, err := obs.GetProjectMeta(templateProject)
|
||||
templateMeta, err := ObsClient.GetProjectMeta(templateProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching template project meta for", templateProject, ":", err)
|
||||
return err
|
||||
@@ -346,10 +343,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
||||
// freeze tag for now
|
||||
if len(templateMeta.ScmSync) > 0 {
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
common.LogDebug("getting data for ", repository.EscapedPath())
|
||||
split := strings.Split(repository.EscapedPath(), "/")
|
||||
@@ -357,12 +354,12 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
|
||||
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
|
||||
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// set expanded commit url
|
||||
repository.Fragment = branch.SHA
|
||||
repository.Fragment = branch.SHA
|
||||
templateMeta.ScmSync = repository.String()
|
||||
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
|
||||
}
|
||||
@@ -409,11 +406,11 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = templateMeta.Name
|
||||
} else
|
||||
// Check for path prefixes against a template project inside of template project area
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject+":") {
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject + ":") {
|
||||
newProjectName := stagingProject
|
||||
// find project name
|
||||
for _, setup := range stagingConfig.QA {
|
||||
if setup.Origin == path.Project {
|
||||
if setup.Origin == path.Project {
|
||||
common.LogDebug(" Match:", setup.Origin)
|
||||
newProjectName = newProjectName + ":" + setup.Name
|
||||
common.LogDebug(" New:", newProjectName)
|
||||
@@ -421,14 +418,14 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
}
|
||||
}
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = newProjectName
|
||||
common.LogDebug(" Matched prefix")
|
||||
common.LogDebug(" Matched prefix")
|
||||
}
|
||||
common.LogDebug(" Path using project ", templateMeta.Repositories[idx].Paths[pidx].Project)
|
||||
}
|
||||
}
|
||||
|
||||
if !IsDryRun {
|
||||
err = obs.SetProjectMeta(templateMeta)
|
||||
err = ObsClient.SetProjectMeta(templateMeta)
|
||||
if err != nil {
|
||||
common.LogError("cannot create project:", templateMeta.Name, err)
|
||||
x, _ := xml.MarshalIndent(templateMeta, "", " ")
|
||||
@@ -442,10 +439,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
common.LogDebug("fetching OBS project Meta")
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
meta, err := obs.GetProjectMeta(obsPrProject)
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
meta, err := ObsClient.GetProjectMeta(obsPrProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", obsPrProject, ":", err)
|
||||
return RequestModificationNoChange, err
|
||||
@@ -470,7 +467,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
|
||||
if meta == nil {
|
||||
// new build
|
||||
common.LogDebug(" Staging master:", config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(obs, git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
if err != nil {
|
||||
return RequestModificationNoChange, err
|
||||
}
|
||||
@@ -482,7 +479,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
|
||||
common.LogDebug("Creating build project:")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
} else {
|
||||
err = obs.SetProjectMeta(meta)
|
||||
err = ObsClient.SetProjectMeta(meta)
|
||||
if err != nil {
|
||||
x, _ := xml.MarshalIndent(meta, "", " ")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
@@ -553,7 +550,7 @@ func ParseNotificationToPR(thread *models.NotificationThread) (org string, repo
|
||||
return
|
||||
}
|
||||
|
||||
func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) {
|
||||
func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThread) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -569,7 +566,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
}
|
||||
common.LogInfo("processing PR:", org, "/", repo, "#", num)
|
||||
|
||||
done, err := ProcessPullRequest(obs, gitea, org, repo, num)
|
||||
done, err := ProcessPullRequest(gitea, org, repo, num)
|
||||
if !IsDryRun && err == nil && done {
|
||||
gitea.SetNotificationRead(thread.ID)
|
||||
} else if err != nil {
|
||||
@@ -579,7 +576,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
|
||||
var CleanedUpIssues []int64 = []int64{}
|
||||
|
||||
func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -646,8 +643,8 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
return false
|
||||
}
|
||||
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
if prj, err := obs.GetProjectMeta(stagingProject); err != nil {
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
if prj, err := ObsClient.GetProjectMeta(stagingProject); err != nil {
|
||||
common.LogError("Failed fetching meta for project:", stagingProject, ". Not cleaning up")
|
||||
return false
|
||||
} else if prj == nil && err == nil {
|
||||
@@ -661,13 +658,13 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
project := stagingProject + ":" + qa.Name
|
||||
common.LogDebug("Cleaning up QA staging", project)
|
||||
if !IsDryRun {
|
||||
if err := obs.DeleteProject(project); err != nil {
|
||||
if err := ObsClient.DeleteProject(project); err != nil {
|
||||
common.LogError("Failed to cleanup QA staging", project, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !IsDryRun {
|
||||
if err := obs.DeleteProject(stagingProject); err != nil {
|
||||
if err := ObsClient.DeleteProject(stagingProject); err != nil {
|
||||
common.LogError("Failed to cleanup staging", stagingProject, err)
|
||||
}
|
||||
}
|
||||
@@ -688,7 +685,7 @@ func SetStatus(gitea common.Gitea, org, repo, hash string, status *models.Commit
|
||||
return err
|
||||
}
|
||||
|
||||
func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
if IsDryRun {
|
||||
common.LogInfo("Would comment on package PR %s/%s#%d: %s", org, repo, prNum, msg)
|
||||
return
|
||||
@@ -700,18 +697,6 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
|
||||
return
|
||||
}
|
||||
|
||||
timeline, err := gitea.GetTimeline(org, repo, prNum)
|
||||
if err != nil {
|
||||
common.LogError("Failed to get timeline for PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range timeline {
|
||||
if t.User != nil && t.User.UserName == BotUser && t.Type == common.TimelineCommentType_Comment && t.Body == msg {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError("Failed to comment on package PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
@@ -719,21 +704,20 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
|
||||
}
|
||||
|
||||
// Create and remove QA projects
|
||||
func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) ([]string, string) {
|
||||
func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) []string {
|
||||
usedQAprojects := make([]string, 0)
|
||||
prLabelNames := make(map[string]int)
|
||||
for _, label := range pr.Labels {
|
||||
prLabelNames[label.Name] = 1
|
||||
}
|
||||
msg := ""
|
||||
var qa_projects []string
|
||||
for _, setup := range stagingConfig.QA {
|
||||
QAproject := stagingProject + ":" + setup.Name
|
||||
if len(setup.Label) > 0 {
|
||||
if _, ok := prLabelNames[setup.Label]; !ok {
|
||||
if !IsDryRun {
|
||||
// blindly remove, will fail when not existing
|
||||
obs.DeleteProject(QAproject)
|
||||
ObsClient.DeleteProject(QAproject)
|
||||
}
|
||||
common.LogInfo("QA project ", setup.Name, "has no matching Label")
|
||||
continue
|
||||
@@ -742,25 +726,24 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
|
||||
|
||||
usedQAprojects = append(usedQAprojects, QAproject)
|
||||
// check for existens first, no error, but no meta is a 404
|
||||
if meta, err := obs.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
if meta, err := ObsClient.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
common.LogInfo("Create QA project ", QAproject)
|
||||
CreateQASubProject(obs, stagingConfig, git, gitea, pr,
|
||||
CreateQASubProject(stagingConfig, git, gitea, pr,
|
||||
stagingProject,
|
||||
setup.Origin,
|
||||
setup.Name,
|
||||
setup.BuildDisableRepos)
|
||||
qa_projects = append(qa_projects, ObsWebHost+"/project/show/"+QAproject)
|
||||
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
|
||||
QAproject + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if len(qa_projects) > 0 {
|
||||
msg = "Additional QA builds:\n" + strings.Join(qa_projects, "\n")
|
||||
if len(msg) > 1 {
|
||||
gitea.AddComment(pr, msg)
|
||||
}
|
||||
|
||||
return usedQAprojects, msg
|
||||
return usedQAprojects
|
||||
}
|
||||
|
||||
func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
dir, err := os.MkdirTemp(os.TempDir(), BotName)
|
||||
common.PanicOnError(err)
|
||||
if IsDryRun {
|
||||
@@ -769,7 +752,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
defer os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
gh, err := GitWorkTreeAllocate(dir, GitAuthor, "noaddress@suse.de")
|
||||
gh, err := common.AllocateGitWorkTree(dir, GitAuthor, "noaddress@suse.de")
|
||||
common.PanicOnError(err)
|
||||
|
||||
git, err := gh.CreateGitHandler(org)
|
||||
@@ -814,7 +797,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
if err != nil {
|
||||
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
|
||||
if !IsDryRun {
|
||||
_, _ = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
@@ -834,7 +817,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
meta, err := obs.GetProjectMeta(stagingConfig.ObsProject)
|
||||
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
|
||||
if err != nil || meta == nil {
|
||||
common.LogError("Cannot find reference project meta:", stagingConfig.ObsProject, err)
|
||||
if !IsDryRun && err == nil {
|
||||
@@ -963,8 +946,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
}
|
||||
|
||||
common.LogDebug("ObsProject:", stagingConfig.ObsProject)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, obs.GetHomeProject(), pr)
|
||||
change, err := StartOrUpdateBuild(obs, stagingConfig, git, gitea, pr)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, ObsClient.HomeProject, pr)
|
||||
change, err := StartOrUpdateBuild(stagingConfig, git, gitea, pr)
|
||||
status := &models.CommitStatus{
|
||||
Context: BotName,
|
||||
Description: "OBS Staging build",
|
||||
@@ -995,8 +978,11 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
|
||||
SetStatus(gitea, org, repo, pr.Head.Sha, status)
|
||||
}
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
gitea.AddComment(pr, msg)
|
||||
}
|
||||
|
||||
stagingResult, err := obs.BuildStatus(stagingProject)
|
||||
stagingResult, err := ObsClient.BuildStatus(stagingProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", stagingProject, ":", err)
|
||||
}
|
||||
@@ -1004,14 +990,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
_, packagePRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
|
||||
|
||||
// always update QA projects because Labels can change
|
||||
qaProjects, qaProjectMsg := ProcessQaProjects(obs, stagingConfig, git, gitea, pr, stagingProject)
|
||||
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
if len(qaProjectMsg) > 0 {
|
||||
msg += "\n" + qaProjectMsg
|
||||
}
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
}
|
||||
qaProjects := ProcessQaProjects(stagingConfig, git, gitea, pr, stagingProject)
|
||||
|
||||
done := false
|
||||
overallBuildStatus := ProcessBuildStatus(stagingResult)
|
||||
@@ -1019,7 +998,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
|
||||
seperator := " in "
|
||||
for _, qaProject := range qaProjects {
|
||||
qaResult, err := obs.BuildStatus(qaProject)
|
||||
qaResult, err := ObsClient.BuildStatus(qaProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", qaProject, ":", err)
|
||||
}
|
||||
@@ -1079,7 +1058,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
default:
|
||||
continue
|
||||
}
|
||||
CommentPROnce(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
commentOnPackagePR(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
}
|
||||
|
||||
if len(missingPkgs) > 0 {
|
||||
@@ -1089,7 +1068,10 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
msg = msg + " - " + pkg + "\n"
|
||||
}
|
||||
common.LogInfo(msg)
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
err := gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,7 +1090,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
func PollWorkNotifications(giteaUrl string) {
|
||||
gitea := common.AllocateGiteaTransport(giteaUrl)
|
||||
data, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1124,7 +1107,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
if !ListPullNotificationsOnly {
|
||||
switch notification.Subject.Type {
|
||||
case "Pull":
|
||||
ProcessPullNotification(obs, gitea, notification)
|
||||
ProcessPullNotification(gitea, notification)
|
||||
default:
|
||||
if !IsDryRun {
|
||||
gitea.SetNotificationRead(notification.ID)
|
||||
@@ -1147,7 +1130,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanupFinished = CleanupPullNotification(obs, gitea, n) && cleanupFinished
|
||||
cleanupFinished = CleanupPullNotification(gitea, n) && cleanupFinished
|
||||
}
|
||||
} else if err != nil {
|
||||
common.LogError(err)
|
||||
@@ -1161,8 +1144,7 @@ var ObsApiHost string
|
||||
var ObsWebHost string
|
||||
var IsDryRun bool
|
||||
var ProcessPROnly string
|
||||
var ObsClient common.ObsClientInterface
|
||||
var BotUser string
|
||||
var ObsClient *common.ObsClient
|
||||
|
||||
func ObsWebHostFromApiHost(apihost string) string {
|
||||
u, err := url.Parse(apihost)
|
||||
@@ -1227,18 +1209,9 @@ func main() {
|
||||
}
|
||||
|
||||
if len(*buildRoot) > 0 {
|
||||
ObsClient.SetHomeProject(*buildRoot)
|
||||
ObsClient.HomeProject = *buildRoot
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
|
||||
user, err := gitea.GetCurrentUser()
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch current user:", err)
|
||||
return
|
||||
}
|
||||
BotUser = user.UserName
|
||||
|
||||
if len(*ProcessPROnly) > 0 {
|
||||
rx := regexp.MustCompile("^([^/#]+)/([^/#]+)#([0-9]+)$")
|
||||
m := rx.FindStringSubmatch(*ProcessPROnly)
|
||||
@@ -1247,14 +1220,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
id, _ := strconv.ParseInt(m[3], 10, 64)
|
||||
|
||||
ProcessPullRequest(ObsClient, gitea, m[1], m[2], id)
|
||||
ProcessPullRequest(gitea, m[1], m[2], id)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
PollWorkNotifications(ObsClient, gitea)
|
||||
PollWorkNotifications(GiteaUrl)
|
||||
common.LogInfo("Poll cycle finished")
|
||||
time.Sleep(5 * time.Minute)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ After=network-online.target
|
||||
[Service]
|
||||
Type=exec
|
||||
ExecStart=/usr/bin/workflow-direct
|
||||
EnvironmentFile=/etc/default/%i/workflow-direct.env
|
||||
EnvironmentFile=-/etc/default/%i/workflow-direct.env
|
||||
#DynamicUser=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
|
||||
@@ -5,7 +5,7 @@ After=network-online.target
|
||||
[Service]
|
||||
Type=exec
|
||||
ExecStart=/usr/bin/workflow-pr
|
||||
EnvironmentFile=/etc/default/%i/workflow-pr.env
|
||||
EnvironmentFile=-/etc/default/%i/workflow-pr.env
|
||||
#DynamicUser=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
|
||||
9
utils/gitmodules-automerge/README.md
Normal file
9
utils/gitmodules-automerge/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Purpose
|
||||
-------
|
||||
|
||||
Automatically resolve git configlicts in .gitmodules if there's a conflict
|
||||
due to a merge.
|
||||
|
||||
It uses HEAD and MERGE_HEAD to calculate merge base and pass it to the
|
||||
conflict resolution
|
||||
|
||||
42
utils/gitmodules-automerge/main.go
Normal file
42
utils/gitmodules-automerge/main.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
gh, err := common.AllocateGitWorkTree(cwd, "", "")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
git, err := gh.ReadExistingPath("")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
MergeBase := strings.TrimSpace(git.GitExecWithOutputOrPanic("", "merge-base", "HEAD", "MERGE_HEAD"))
|
||||
status, err := git.GitStatus("")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range status {
|
||||
if s.Path == ".gitmodules" && s.Status == common.GitStatus_Unmerged {
|
||||
if err := git.GitResolveSubmoduleFileConflict(s, "", MergeBase, "HEAD", "MERGE_HEAD"); err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,8 @@ Main Tasks
|
||||
| ManualMergeOnly | true | Both PackageGit PR and ProjectGit PR are merged upon an allowed package maintainer or project maintainer commenting “merge ok” in the PackageGit PR. |
|
||||
| ManualMergeOnly and ManualMergeProject | false | Both ProjectGit and PackageGit PRs are merged as soon as all reviews are completed in both PrjGit and PkgGit PRs. |
|
||||
|
||||
Project specific config file
|
||||
----------------------------
|
||||
|
||||
This is the ProjectGit config file. For runtime config file, see bottom.
|
||||
Config file
|
||||
-----------
|
||||
|
||||
* Filename: `workflow.config`
|
||||
* Location: ProjectGit
|
||||
@@ -158,44 +156,8 @@ NOTE: Project Maintainers have these permissions automatically.
|
||||
Server configuration
|
||||
--------------------------
|
||||
|
||||
The configuration file is a JSON file that consists of a list of project git locations
|
||||
that are then consulted for their `workflow.config` config files.
|
||||
|
||||
```
|
||||
[]ProjectGit = {
|
||||
"org" | "org/repo" | "org/repo#branch"
|
||||
}
|
||||
|
||||
default repo = _ObsPrj
|
||||
default branch = as specified in Gitea
|
||||
```
|
||||
|
||||
For example,
|
||||
|
||||
```
|
||||
[ "org", "openSUSE/Leap", "openSUSE/Leap#16.0" ]
|
||||
```
|
||||
|
||||
Are all valid entries. These are then resolved to,
|
||||
|
||||
* For `org`, it's assumed that default repository of `_ObsPrj` in `org` organization and using Gitea's default branch
|
||||
* For `openSUSE/Leap`, the repository "Leap" using Gitea's default branch in `openSUSE` organization.
|
||||
* For `openSUSE/Leap#16.0`, the repository "Leap" with branch "16.0" in `openSUSE` organization.
|
||||
|
||||
For each of these project gits, `workflow.config` is read.
|
||||
|
||||
|
||||
**Runtime Options**
|
||||
|
||||
| Option | Default | Environmental Default | Notes |
|
||||
|---------------|----------------------------|-----------------------|------------------------------------|
|
||||
| git-author | AutoGits PR Review Bot | AUTOGITS_GIT_AUTHOR | Name of author for bot created commits |
|
||||
| git-email | noone@suse.de | AUTOGITS_GIT_EMAIL | Email for the bot created commits |
|
||||
| config | | AUTOGITS_CONFIG | Path to above config file |
|
||||
| gitea-url | https://src.opensuse.org | AUTOGITS_GITEA_URL | Gitea's URL instance |
|
||||
| rabbit-url | amqps://rabbit.opensuse.org| AUTOGITS_RABBIT_URL | RabbitMQ's URL instance |
|
||||
| debug | false | AUTOGITS_DEBUG | Extra logging |
|
||||
| check-on-start| false | AUTOGITS_CHECK_ON_START| Whether to check all projects for consistency on start. Can take a while |
|
||||
| check-interval| 5 | | Consistency check interval |
|
||||
| repo-path | Uses temp directory | AUTOGITS_REPO_PATH | Path where to store repositories. |
|
||||
**Configuration file:**
|
||||
|
||||
| Field | Type | Notes |
|
||||
| ----- | ----- | ----- |
|
||||
| root | Array of string | Format **org/repo\#branch** |
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,523 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -44,30 +44,39 @@ var CurrentUser *models.User
|
||||
var GitHandler common.GitHandlerGenerator
|
||||
var Gitea common.Gitea
|
||||
|
||||
func getEnvOverrideString(env, def string) string {
|
||||
if envValue := os.Getenv(env); len(envValue) != 0 {
|
||||
return envValue
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func getEnvOverrideBool(env string, def bool) bool {
|
||||
if envValue := os.Getenv(env); len(envValue) != 0 {
|
||||
if value, err := strconv.Atoi(envValue); err == nil && value > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&GitAuthor, "git-author", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GIT_AUTHOR"), "AutoGits PR Review Bot"), "Git commit author")
|
||||
flag.StringVar(&GitEmail, "git-email", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GIT_EMAIL"), "noone@suse.de"), "Git commit email")
|
||||
flag.StringVar(&GitAuthor, "git-author", "AutoGits PR Review Bot", "Git commit author")
|
||||
flag.StringVar(&GitEmail, "git-email", "amajer+devel-git@suse.de", "Git commit email")
|
||||
|
||||
workflowConfig := flag.String("config", common.GetEnvOverrideString(os.Getenv("AUTOGITS_CONFIG"), ""), "Repository and workflow definition file")
|
||||
giteaUrl := flag.String("gitea-url", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GITEA_URL"), "https://src.opensuse.org"), "Gitea instance")
|
||||
|
||||
legacyRabbitUrl := flag.String("url", "", "Legacy. Use rabbit-url") /* TO BE REMOVED */
|
||||
rabbitUrl := flag.String("rabbit-url", common.GetEnvOverrideString(os.Getenv("AUTOGITS_RABBIT_URL"), "amqps://rabbit.opensuse.org"), "URL for RabbitMQ instance")
|
||||
|
||||
debugMode := flag.Bool("debug", common.GetEnvOverrideBool(os.Getenv("AUTOGITS_DEBUG"), false), "Extra debugging information")
|
||||
checkOnStart := flag.Bool("check-on-start", common.GetEnvOverrideBool(os.Getenv("AUTOGITS_CHECK_ON_START"), false), "Check all repositories for consistency on start, without delays")
|
||||
workflowConfig := flag.String("config", getEnvOverrideString("AUTOGITS_CONFIG", ""), "Repository and workflow definition file")
|
||||
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
|
||||
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
|
||||
debugMode := flag.Bool("debug", getEnvOverrideBool("AUTOGITS_DEBUG", false), "Extra debugging information")
|
||||
checkOnStart := flag.Bool("check-on-start", getEnvOverrideBool("AUTOGITS_CHECK_ON_START", false), "Check all repositories for consistency on start, without delays")
|
||||
checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours")
|
||||
flag.BoolVar(&ListPROnly, "list-prs-only", false, "Only lists PRs without acting on them")
|
||||
flag.Int64Var(&PRID, "id", -1, "Process only the specific ID and ignore the rest. Use for debugging")
|
||||
basePath := flag.String("repo-path", common.GetEnvOverrideString(os.Getenv("AUTOGITS_REPO_PATH"), ""), "Repository path. Default is temporary directory")
|
||||
basePath := flag.String("repo-path", getEnvOverrideString("AUTOGITS_REPO_PATH", ""), "Repository path. Default is temporary directory")
|
||||
pr := flag.String("only-pr", "", "Only specific PR to process. For debugging")
|
||||
flag.BoolVar(&common.IsDryRun, "dry", false, "Dry mode. Do not push changes to remote repo.")
|
||||
flag.Parse()
|
||||
|
||||
if len(*legacyRabbitUrl) > 0 {
|
||||
*rabbitUrl = *legacyRabbitUrl
|
||||
}
|
||||
|
||||
common.SetLoggingLevel(common.LogLevelInfo)
|
||||
if *debugMode {
|
||||
common.SetLoggingLevel(common.LogLevelDebug)
|
||||
@@ -153,22 +162,15 @@ func main() {
|
||||
num, err := strconv.ParseInt(data[3], 10, 64)
|
||||
common.LogInfo("Processing:", org, "/", repo, "#", num)
|
||||
common.PanicOnError(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)
|
||||
pr, err := Gitea.GetPullRequest(org, repo, num)
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch PR", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = ProcesPullRequest(pr, configs); err != nil {
|
||||
common.LogError("processor returned error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
func TestProjectBranchName(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
branchName := prGitBranchNameForPR("testingRepo", 10)
|
||||
if branchName != "PR_testingRepo#10" {
|
||||
t.Error("Unexpected branch name:", branchName)
|
||||
@@ -22,7 +21,6 @@ func TestProjectBranchName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdatePrBranch(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
var buf bytes.Buffer
|
||||
origLogger := log.Writer()
|
||||
log.SetOutput(&buf)
|
||||
@@ -60,7 +58,6 @@ func TestUpdatePrBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreatePrBranch(t *testing.T) {
|
||||
common.SetTestLogger(t)
|
||||
var buf bytes.Buffer
|
||||
origLogger := log.Writer()
|
||||
log.SetOutput(&buf)
|
||||
|
||||
@@ -194,14 +194,7 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
if !submodule_found {
|
||||
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))
|
||||
common.LogError("Failed to find expected repo:", repo)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -471,11 +464,11 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
|
||||
common.PanicOnError(err)
|
||||
}
|
||||
// } else {
|
||||
// Gitea.AddComment(pr.PR, "Closing here because the associated Project PR has been closed.")
|
||||
// Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
|
||||
// State: "closed",
|
||||
// })
|
||||
} else {
|
||||
Gitea.AddComment(pr.PR, "Closing here because the associated Project PR has been closed.")
|
||||
Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
|
||||
State: "closed",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,14 +615,6 @@ 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?)
|
||||
@@ -646,6 +631,15 @@ 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 {
|
||||
@@ -674,21 +668,6 @@ 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.")
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestOpenPR(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
)
|
||||
|
||||
func TestSyncPR(t *testing.T) {
|
||||
CurrentUser = &models.User{UserName: "testuser"}
|
||||
config := &common.AutogitConfig{
|
||||
Reviewers: []string{"reviewer1", "reviewer2"},
|
||||
Branch: "testing",
|
||||
@@ -74,7 +73,7 @@ func TestSyncPR(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("PR_sync_request_against_PrjGit_==_no_action", func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
@@ -109,7 +108,7 @@ func TestSyncPR(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Missing PrjGit PR for the sync", func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
@@ -131,7 +130,7 @@ func TestSyncPR(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("PR sync", func(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
@@ -155,3 +154,4 @@ func TestSyncPR(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestPrjGitDescription(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllocatePRProcessor(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPrjGitSubmoduleCheck(t *testing.T) {
|
||||
ctl := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
defer ctl.Finish()
|
||||
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
|
||||
@@ -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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.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 := NewController(t)
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGitea(ctl)
|
||||
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
|
||||
|
||||
|
||||
@@ -7,14 +7,8 @@ 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) {
|
||||
@@ -62,7 +56,6 @@ 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",
|
||||
|
||||
Reference in New Issue
Block a user