forked from git-workflow/autogits
Compare commits
3 Commits
main
...
submodulem
| Author | SHA256 | Date | |
|---|---|---|---|
| 66a2c0565e | |||
| d5d6910906 | |||
| 444959540a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
*.osc
|
||||
*.conf
|
||||
utils/gitmodules-automerge/gitmodules-automerge
|
||||
utils/hujson/hujson
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
|
||||
Name: autogits
|
||||
Version: 1
|
||||
Version: 0
|
||||
Release: 0
|
||||
Summary: GitWorkflow utilities
|
||||
License: GPL-2.0-or-later
|
||||
@@ -41,7 +41,6 @@ Command-line tool to import devel projects from obs to git
|
||||
|
||||
%package doc
|
||||
Summary: Common documentation files
|
||||
BuildArch: noarch
|
||||
|
||||
%description -n autogits-doc
|
||||
Common documentation files
|
||||
@@ -57,11 +56,10 @@ with a topic
|
||||
|
||||
|
||||
%package gitea-status-proxy
|
||||
Summary: Proxy for setting commit status in Gitea
|
||||
Summary: gitea-status-proxy
|
||||
|
||||
%description gitea-status-proxy
|
||||
Setting commit status requires code write access token. This proxy
|
||||
is middleware that delegates status setting without access to other APIs
|
||||
|
||||
|
||||
%package group-review
|
||||
Summary: Reviews of groups defined in ProjectGit
|
||||
@@ -98,6 +96,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
|
||||
@@ -129,6 +128,9 @@ go build \
|
||||
go build \
|
||||
-C utils/hujson \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C utils/gitmodules-automerge \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C gitea-events-rabbitmq-publisher \
|
||||
-buildmode=pie
|
||||
@@ -175,9 +177,9 @@ install -D -m0644 systemd/obs-staging-bot.service
|
||||
install -D -m0755 obs-status-service/obs-status-service %{buildroot}%{_bindir}/obs-status-service
|
||||
install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service
|
||||
install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
|
||||
install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
|
||||
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||
install -D -m0755 utils/gitmodules-automerge/gitmodules-automerge %{buildroot}%{_bindir}/gitmodules-automerge
|
||||
|
||||
%pre gitea-events-rabbitmq-publisher
|
||||
%service_add_pre gitea-events-rabbitmq-publisher.service
|
||||
@@ -215,18 +217,6 @@ install -D -m0755 utils/hujson/hujson
|
||||
%postun obs-status-service
|
||||
%service_del_postun obs-status-service.service
|
||||
|
||||
%pre workflow-pr
|
||||
%service_add_pre workflow-direct@.service
|
||||
|
||||
%post workflow-pr
|
||||
%service_add_post workflow-direct@.service
|
||||
|
||||
%preun workflow-pr
|
||||
%service_del_preun workflow-direct@.service
|
||||
|
||||
%postun workflow-pr
|
||||
%service_del_postun workflow-direct@.service
|
||||
|
||||
%files devel-importer
|
||||
%license COPYING
|
||||
%doc devel-importer/README.md
|
||||
@@ -271,12 +261,12 @@ install -D -m0755 utils/hujson/hujson
|
||||
%files utils
|
||||
%license COPYING
|
||||
%{_bindir}/hujson
|
||||
%{_bindir}/gitmodules-automerge
|
||||
|
||||
%files workflow-direct
|
||||
%license COPYING
|
||||
%doc workflow-direct/README.md
|
||||
%{_bindir}/workflow-direct
|
||||
%{_unitdir}/workflow-direct@.service
|
||||
|
||||
%files workflow-pr
|
||||
%license COPYING
|
||||
|
||||
@@ -61,20 +61,6 @@ type Permissions struct {
|
||||
Members []string
|
||||
}
|
||||
|
||||
const (
|
||||
Label_StagingAuto = "staging/Auto"
|
||||
Label_ReviewPending = "review/Pending"
|
||||
Label_ReviewDone = "review/Done"
|
||||
)
|
||||
|
||||
func LabelKey(tag_value string) string {
|
||||
// capitalize first letter and remove /
|
||||
if len(tag_value) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(tag_value[0:1]) + strings.ReplaceAll(tag_value[1:], "/", "")
|
||||
}
|
||||
|
||||
type AutogitConfig struct {
|
||||
Workflows []string // [pr, direct, test]
|
||||
Organization string
|
||||
@@ -86,8 +72,6 @@ type AutogitConfig struct {
|
||||
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
||||
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
||||
|
||||
Labels map[string]string // list of tags, if not default, to apply
|
||||
|
||||
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
|
||||
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
||||
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
|
||||
@@ -204,8 +188,6 @@ func (configs AutogitConfigs) GetPrjGitConfig(org, repo, branch string) *Autogit
|
||||
if c.GitProjectName == prjgit {
|
||||
return c
|
||||
}
|
||||
}
|
||||
for _, c := range configs {
|
||||
if c.Organization == org && c.Branch == branch {
|
||||
return c
|
||||
}
|
||||
@@ -291,14 +273,6 @@ func (config *AutogitConfig) GetRemoteBranch() string {
|
||||
return "origin_" + config.Branch
|
||||
}
|
||||
|
||||
func (config *AutogitConfig) Label(label string) string {
|
||||
if t, found := config.Labels[LabelKey(label)]; found {
|
||||
return t
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
type StagingConfig struct {
|
||||
ObsProject string
|
||||
RebuildAll bool
|
||||
|
||||
@@ -10,67 +10,6 @@ import (
|
||||
mock_common "src.opensuse.org/autogits/common/mock"
|
||||
)
|
||||
|
||||
func TestLabelKey(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"": "",
|
||||
"foo": "Foo",
|
||||
"foo/bar": "Foobar",
|
||||
"foo/Bar": "FooBar",
|
||||
}
|
||||
|
||||
for k, v := range tests {
|
||||
if c := common.LabelKey(k); c != v {
|
||||
t.Error("expected", v, "got", c, "input", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigLabelParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
label_value string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
json: "{}",
|
||||
label_value: "path/String",
|
||||
},
|
||||
{
|
||||
name: "defined",
|
||||
json: `{"Labels": {"foo": "bar", "PathString": "moo/Label"}}`,
|
||||
label_value: "moo/Label",
|
||||
},
|
||||
{
|
||||
name: "undefined",
|
||||
json: `{"Labels": {"foo": "bar", "NotPathString": "moo/Label"}}`,
|
||||
label_value: "path/String",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
repo := models.Repository{
|
||||
DefaultBranch: "master",
|
||||
}
|
||||
|
||||
ctl := gomock.NewController(t)
|
||||
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
|
||||
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
|
||||
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
|
||||
|
||||
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
|
||||
if err != nil || config == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if l := config.Label("path/String"); l != test.label_value {
|
||||
t.Error("Expecting", test.label_value, "got", l)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectConfigMatcher(t *testing.T) {
|
||||
configs := common.AutogitConfigs{
|
||||
{
|
||||
@@ -82,15 +21,6 @@ func TestProjectConfigMatcher(t *testing.T) {
|
||||
Branch: "main",
|
||||
GitProjectName: "test/prjgit#main",
|
||||
},
|
||||
{
|
||||
Organization: "test",
|
||||
Branch: "main",
|
||||
GitProjectName: "test/bar#never_match",
|
||||
},
|
||||
{
|
||||
Organization: "test",
|
||||
GitProjectName: "test/bar#main",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -120,20 +50,6 @@ func TestProjectConfigMatcher(t *testing.T) {
|
||||
branch: "main",
|
||||
config: 1,
|
||||
},
|
||||
{
|
||||
name: "prjgit only match",
|
||||
org: "test",
|
||||
repo: "bar",
|
||||
branch: "main",
|
||||
config: 3,
|
||||
},
|
||||
{
|
||||
name: "non-default branch match",
|
||||
org: "test",
|
||||
repo: "bar",
|
||||
branch: "something_main",
|
||||
config: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -189,10 +105,6 @@ func TestConfigWorkflowParser(t *testing.T) {
|
||||
if config.ManualMergeOnly != false {
|
||||
t.Fatal("This should be false")
|
||||
}
|
||||
|
||||
if config.Label("foobar") != "foobar" {
|
||||
t.Fatal("undefined label should return default value")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -44,6 +42,11 @@ type GitDirectoryLister interface {
|
||||
GitDirectoryList(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 {
|
||||
GitStatus(cwd string) ([]GitStatusData, error)
|
||||
}
|
||||
@@ -75,6 +78,7 @@ type Git interface {
|
||||
GitExecQuietOrPanic(cwd string, params ...string)
|
||||
|
||||
GitDiffLister
|
||||
GitSubmoduleFileConflictResolver
|
||||
}
|
||||
|
||||
type GitHandlerImpl struct {
|
||||
@@ -350,20 +354,23 @@ var ExtraGitParams []string
|
||||
|
||||
func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string, error) {
|
||||
cmd := exec.Command("/usr/bin/git", params...)
|
||||
var identityFile string
|
||||
if i := os.Getenv("AUTOGITS_IDENTITY_FILE"); len(i) > 0 {
|
||||
identityFile = " -i " + i
|
||||
}
|
||||
cmd.Env = []string{
|
||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||
"GIT_CONFIG_GLOBAL=/dev/null",
|
||||
"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,
|
||||
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
|
||||
}
|
||||
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...)
|
||||
}
|
||||
@@ -1006,193 +1013,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",
|
||||
@@ -1209,7 +1033,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) {
|
||||
@@ -1234,3 +1063,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,6 +24,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -93,6 +94,145 @@ func TestGitClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := exec.Command(cwd + "/test_repo_setup.sh")
|
||||
cmd.Dir = d
|
||||
_, err = cmd.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gh, err := AllocateGitWorkTree(d, "test", "foo@example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
success := true
|
||||
noErrorOrFail := func(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatal(string(debug.Stack()), err)
|
||||
}
|
||||
}
|
||||
for _, test := range tests {
|
||||
success = t.Run(test.name, func(t *testing.T) {
|
||||
git, err := gh.ReadExistingPath("prjgit")
|
||||
defer git.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if success {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitMsgParsing(t *testing.T) {
|
||||
t.Run("tree message with size 56", func(t *testing.T) {
|
||||
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
|
||||
@@ -584,12 +724,15 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
transport "github.com/go-openapi/runtime/client"
|
||||
@@ -67,14 +66,6 @@ const (
|
||||
ReviewStateUnknown models.ReviewStateType = ""
|
||||
)
|
||||
|
||||
type GiteaLabelGetter interface {
|
||||
GetLabels(org, repo string, idx int64) ([]*models.Label, error)
|
||||
}
|
||||
|
||||
type GiteaLabelSettter interface {
|
||||
SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error)
|
||||
}
|
||||
|
||||
type GiteaTimelineFetcher interface {
|
||||
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
|
||||
}
|
||||
@@ -100,10 +91,9 @@ type GiteaPRUpdater interface {
|
||||
UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error)
|
||||
}
|
||||
|
||||
type GiteaPRTimelineReviewFetcher interface {
|
||||
type GiteaPRTimelineFetcher interface {
|
||||
GiteaPRFetcher
|
||||
GiteaTimelineFetcher
|
||||
GiteaReviewFetcher
|
||||
}
|
||||
|
||||
type GiteaCommitFetcher interface {
|
||||
@@ -129,16 +119,10 @@ type GiteaPRChecker interface {
|
||||
GiteaMaintainershipReader
|
||||
}
|
||||
|
||||
type GiteaReviewFetcherAndRequesterAndUnrequester interface {
|
||||
type GiteaReviewFetcherAndRequester interface {
|
||||
GiteaReviewTimelineFetcher
|
||||
GiteaCommentFetcher
|
||||
GiteaReviewRequester
|
||||
GiteaReviewUnrequester
|
||||
}
|
||||
|
||||
type GiteaUnreviewTimelineFetcher interface {
|
||||
GiteaTimelineFetcher
|
||||
GiteaReviewUnrequester
|
||||
}
|
||||
|
||||
type GiteaReviewRequester interface {
|
||||
@@ -198,8 +182,6 @@ type Gitea interface {
|
||||
GiteaCommitStatusGetter
|
||||
GiteaCommitStatusSetter
|
||||
GiteaSetRepoOptions
|
||||
GiteaLabelGetter
|
||||
GiteaLabelSettter
|
||||
|
||||
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
||||
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
||||
@@ -207,7 +189,7 @@ type Gitea interface {
|
||||
GetOrganization(orgName string) (*models.Organization, error)
|
||||
GetOrganizationRepositories(orgName string) ([]*models.Repository, error)
|
||||
CreateRepositoryIfNotExist(git Git, org, repoName string) (*models.Repository, error)
|
||||
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool)
|
||||
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error)
|
||||
GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, string, error)
|
||||
GetRecentPullRequests(org, repo, branch string) ([]*models.PullRequest, error)
|
||||
GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error)
|
||||
@@ -484,30 +466,6 @@ func (gitea *GiteaTransport) SetRepoOptions(owner, repo string, manual_merge boo
|
||||
return ok.Payload, err
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetLabels(owner, repo string, idx int64) ([]*models.Label, error) {
|
||||
ret, err := gitea.client.Issue.IssueGetLabels(issue.NewIssueGetLabelsParams().WithOwner(owner).WithRepo(repo).WithIndex(idx), gitea.transport.DefaultAuthentication)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret.Payload, err
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) SetLabels(owner, repo string, idx int64, labels []string) ([]*models.Label, error) {
|
||||
interfaceLabels := make([]interface{}, len(labels))
|
||||
for i, l := range labels {
|
||||
interfaceLabels[i] = l
|
||||
}
|
||||
|
||||
ret, err := gitea.client.Issue.IssueAddLabel(issue.NewIssueAddLabelParams().WithOwner(owner).WithRepo(repo).WithIndex(idx).WithBody(&models.IssueLabelsOption{Labels: interfaceLabels}),
|
||||
gitea.transport.DefaultAuthentication)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret.Payload, nil
|
||||
}
|
||||
|
||||
const (
|
||||
GiteaNotificationType_Pull = "Pull"
|
||||
)
|
||||
@@ -685,7 +643,7 @@ func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org, repoName s
|
||||
return repo.Payload, nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
|
||||
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
||||
prOptions := models.CreatePullRequestOption{
|
||||
Base: targetId,
|
||||
Head: srcId,
|
||||
@@ -701,7 +659,7 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
|
||||
WithHead(srcId),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
); err == nil && pr.Payload.State == "open" {
|
||||
return pr.Payload, nil, false
|
||||
return pr.Payload, nil
|
||||
}
|
||||
|
||||
pr, err := gitea.client.Repository.RepoCreatePullRequest(
|
||||
@@ -715,10 +673,10 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot create pull request. %w", err), true
|
||||
return nil, fmt.Errorf("Cannot create pull request. %w", err)
|
||||
}
|
||||
|
||||
return pr.GetPayload(), nil, true
|
||||
return pr.GetPayload(), nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) {
|
||||
@@ -805,79 +763,45 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string)
|
||||
return nil
|
||||
}
|
||||
|
||||
type TimelineCacheData struct {
|
||||
data []*models.TimelineComment
|
||||
lastCheck time.Time
|
||||
}
|
||||
|
||||
var giteaTimelineCache map[string]TimelineCacheData = make(map[string]TimelineCacheData)
|
||||
var giteaTimelineCacheMutex sync.RWMutex
|
||||
|
||||
// returns timeline in reverse chronological create order
|
||||
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
page := int64(1)
|
||||
resCount := 1
|
||||
|
||||
prID := fmt.Sprintf("%s/%s!%d", org, repo, idx)
|
||||
giteaTimelineCacheMutex.RLock()
|
||||
TimelineCache, IsCached := giteaTimelineCache[prID]
|
||||
var LastCachedTime strfmt.DateTime
|
||||
if IsCached {
|
||||
l := len(TimelineCache.data)
|
||||
if l > 0 {
|
||||
LastCachedTime = TimelineCache.data[0].Updated
|
||||
}
|
||||
|
||||
// cache data for 5 seconds
|
||||
if TimelineCache.lastCheck.Add(time.Second*5).Compare(time.Now()) > 0 {
|
||||
giteaTimelineCacheMutex.RUnlock()
|
||||
return TimelineCache.data, nil
|
||||
}
|
||||
}
|
||||
giteaTimelineCacheMutex.RUnlock()
|
||||
|
||||
giteaTimelineCacheMutex.Lock()
|
||||
defer giteaTimelineCacheMutex.Unlock()
|
||||
retData := []*models.TimelineComment{}
|
||||
|
||||
for resCount > 0 {
|
||||
opts := issue.NewIssueGetCommentsAndTimelineParams().WithOwner(org).WithRepo(repo).WithIndex(idx).WithPage(&page)
|
||||
if !LastCachedTime.IsZero() {
|
||||
opts = opts.WithSince(&LastCachedTime)
|
||||
}
|
||||
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(opts, gitea.transport.DefaultAuthentication)
|
||||
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(
|
||||
issue.NewIssueGetCommentsAndTimelineParams().
|
||||
WithOwner(org).
|
||||
WithRepo(repo).
|
||||
WithIndex(idx).
|
||||
WithPage(&page),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resCount = len(res.Payload); resCount == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, d := range res.Payload {
|
||||
if d != nil {
|
||||
if time.Time(d.Created).Compare(time.Time(LastCachedTime)) > 0 {
|
||||
// created after last check, so we append here
|
||||
TimelineCache.data = append(TimelineCache.data, d)
|
||||
} else {
|
||||
// we need something updated in the timeline, maybe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resCount < 10 {
|
||||
resCount = len(res.Payload)
|
||||
LogDebug("page:", page, "len:", resCount)
|
||||
if resCount == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
|
||||
for _, d := range res.Payload {
|
||||
if d != nil {
|
||||
retData = append(retData, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
LogDebug("timeline", prID, "# timeline:", len(TimelineCache.data))
|
||||
slices.SortFunc(TimelineCache.data, func(a, b *models.TimelineComment) int {
|
||||
LogDebug("total results:", len(retData))
|
||||
slices.SortFunc(retData, func(a, b *models.TimelineComment) int {
|
||||
return time.Time(b.Created).Compare(time.Time(a.Created))
|
||||
})
|
||||
|
||||
TimelineCache.lastCheck = time.Now()
|
||||
giteaTimelineCache[prID] = TimelineCache
|
||||
|
||||
return TimelineCache.data, nil
|
||||
return retData, nil
|
||||
}
|
||||
|
||||
func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, string, error) {
|
||||
|
||||
@@ -18,132 +18,6 @@ import (
|
||||
models "src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
// MockGiteaLabelGetter is a mock of GiteaLabelGetter interface.
|
||||
type MockGiteaLabelGetter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGiteaLabelGetterMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGiteaLabelGetterMockRecorder is the mock recorder for MockGiteaLabelGetter.
|
||||
type MockGiteaLabelGetterMockRecorder struct {
|
||||
mock *MockGiteaLabelGetter
|
||||
}
|
||||
|
||||
// NewMockGiteaLabelGetter creates a new mock instance.
|
||||
func NewMockGiteaLabelGetter(ctrl *gomock.Controller) *MockGiteaLabelGetter {
|
||||
mock := &MockGiteaLabelGetter{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaLabelGetterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGiteaLabelGetter) EXPECT() *MockGiteaLabelGetterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetLabels mocks base method.
|
||||
func (m *MockGiteaLabelGetter) GetLabels(org, repo string, idx int64) ([]*models.Label, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLabels", org, repo, idx)
|
||||
ret0, _ := ret[0].([]*models.Label)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetLabels indicates an expected call of GetLabels.
|
||||
func (mr *MockGiteaLabelGetterMockRecorder) GetLabels(org, repo, idx any) *MockGiteaLabelGetterGetLabelsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGiteaLabelGetter)(nil).GetLabels), org, repo, idx)
|
||||
return &MockGiteaLabelGetterGetLabelsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaLabelGetterGetLabelsCall wrap *gomock.Call
|
||||
type MockGiteaLabelGetterGetLabelsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaLabelGetterGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelGetterGetLabelsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaLabelGetterGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaLabelGetterGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaLabelGetterGetLabelsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockGiteaLabelSettter is a mock of GiteaLabelSettter interface.
|
||||
type MockGiteaLabelSettter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGiteaLabelSettterMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGiteaLabelSettterMockRecorder is the mock recorder for MockGiteaLabelSettter.
|
||||
type MockGiteaLabelSettterMockRecorder struct {
|
||||
mock *MockGiteaLabelSettter
|
||||
}
|
||||
|
||||
// NewMockGiteaLabelSettter creates a new mock instance.
|
||||
func NewMockGiteaLabelSettter(ctrl *gomock.Controller) *MockGiteaLabelSettter {
|
||||
mock := &MockGiteaLabelSettter{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaLabelSettterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGiteaLabelSettter) EXPECT() *MockGiteaLabelSettterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// SetLabels mocks base method.
|
||||
func (m *MockGiteaLabelSettter) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels)
|
||||
ret0, _ := ret[0].([]*models.Label)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SetLabels indicates an expected call of SetLabels.
|
||||
func (mr *MockGiteaLabelSettterMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaLabelSettterSetLabelsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGiteaLabelSettter)(nil).SetLabels), org, repo, idx, labels)
|
||||
return &MockGiteaLabelSettterSetLabelsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaLabelSettterSetLabelsCall wrap *gomock.Call
|
||||
type MockGiteaLabelSettterSetLabelsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaLabelSettterSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaLabelSettterSetLabelsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaLabelSettterSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaLabelSettterSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaLabelSettterSetLabelsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface.
|
||||
type MockGiteaTimelineFetcher struct {
|
||||
ctrl *gomock.Controller
|
||||
@@ -562,32 +436,32 @@ func (c *MockGiteaPRUpdaterUpdatePullRequestCall) DoAndReturn(f func(string, str
|
||||
return c
|
||||
}
|
||||
|
||||
// MockGiteaPRTimelineReviewFetcher is a mock of GiteaPRTimelineReviewFetcher interface.
|
||||
type MockGiteaPRTimelineReviewFetcher struct {
|
||||
// MockGiteaPRTimelineFetcher is a mock of GiteaPRTimelineFetcher interface.
|
||||
type MockGiteaPRTimelineFetcher struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGiteaPRTimelineReviewFetcherMockRecorder
|
||||
recorder *MockGiteaPRTimelineFetcherMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGiteaPRTimelineReviewFetcherMockRecorder is the mock recorder for MockGiteaPRTimelineReviewFetcher.
|
||||
type MockGiteaPRTimelineReviewFetcherMockRecorder struct {
|
||||
mock *MockGiteaPRTimelineReviewFetcher
|
||||
// MockGiteaPRTimelineFetcherMockRecorder is the mock recorder for MockGiteaPRTimelineFetcher.
|
||||
type MockGiteaPRTimelineFetcherMockRecorder struct {
|
||||
mock *MockGiteaPRTimelineFetcher
|
||||
}
|
||||
|
||||
// NewMockGiteaPRTimelineReviewFetcher creates a new mock instance.
|
||||
func NewMockGiteaPRTimelineReviewFetcher(ctrl *gomock.Controller) *MockGiteaPRTimelineReviewFetcher {
|
||||
mock := &MockGiteaPRTimelineReviewFetcher{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaPRTimelineReviewFetcherMockRecorder{mock}
|
||||
// NewMockGiteaPRTimelineFetcher creates a new mock instance.
|
||||
func NewMockGiteaPRTimelineFetcher(ctrl *gomock.Controller) *MockGiteaPRTimelineFetcher {
|
||||
mock := &MockGiteaPRTimelineFetcher{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaPRTimelineFetcherMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGiteaPRTimelineReviewFetcher) EXPECT() *MockGiteaPRTimelineReviewFetcherMockRecorder {
|
||||
func (m *MockGiteaPRTimelineFetcher) EXPECT() *MockGiteaPRTimelineFetcherMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetPullRequest mocks base method.
|
||||
func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequest(org, project string, num int64) (*models.PullRequest, error) {
|
||||
func (m *MockGiteaPRTimelineFetcher) GetPullRequest(org, project string, num int64) (*models.PullRequest, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPullRequest", org, project, num)
|
||||
ret0, _ := ret[0].(*models.PullRequest)
|
||||
@@ -596,76 +470,37 @@ func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequest(org, project string, n
|
||||
}
|
||||
|
||||
// GetPullRequest indicates an expected call of GetPullRequest.
|
||||
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetPullRequest(org, project, num any) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
|
||||
func (mr *MockGiteaPRTimelineFetcherMockRecorder) GetPullRequest(org, project, num any) *MockGiteaPRTimelineFetcherGetPullRequestCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetPullRequest), org, project, num)
|
||||
return &MockGiteaPRTimelineReviewFetcherGetPullRequestCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockGiteaPRTimelineFetcher)(nil).GetPullRequest), org, project, num)
|
||||
return &MockGiteaPRTimelineFetcherGetPullRequestCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaPRTimelineReviewFetcherGetPullRequestCall wrap *gomock.Call
|
||||
type MockGiteaPRTimelineReviewFetcherGetPullRequestCall struct {
|
||||
// MockGiteaPRTimelineFetcherGetPullRequestCall wrap *gomock.Call
|
||||
type MockGiteaPRTimelineFetcherGetPullRequestCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaPRTimelineFetcherGetPullRequestCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) Do(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) Do(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineFetcherGetPullRequestCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestCall) DoAndReturn(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetPullRequestReviews mocks base method.
|
||||
func (m *MockGiteaPRTimelineReviewFetcher) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPullRequestReviews", org, project, PRnum)
|
||||
ret0, _ := ret[0].([]*models.PullReview)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPullRequestReviews indicates an expected call of GetPullRequestReviews.
|
||||
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetPullRequestReviews), org, project, PRnum)
|
||||
return &MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall wrap *gomock.Call
|
||||
type MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaPRTimelineReviewFetcherGetPullRequestReviewsCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetPullRequestCall) DoAndReturn(f func(string, string, int64) (*models.PullRequest, error)) *MockGiteaPRTimelineFetcherGetPullRequestCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetTimeline mocks base method.
|
||||
func (m *MockGiteaPRTimelineReviewFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
func (m *MockGiteaPRTimelineFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
|
||||
ret0, _ := ret[0].([]*models.TimelineComment)
|
||||
@@ -674,31 +509,31 @@ func (m *MockGiteaPRTimelineReviewFetcher) GetTimeline(org, repo string, idx int
|
||||
}
|
||||
|
||||
// GetTimeline indicates an expected call of GetTimeline.
|
||||
func (mr *MockGiteaPRTimelineReviewFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
|
||||
func (mr *MockGiteaPRTimelineFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaPRTimelineFetcherGetTimelineCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaPRTimelineReviewFetcher)(nil).GetTimeline), org, repo, idx)
|
||||
return &MockGiteaPRTimelineReviewFetcherGetTimelineCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaPRTimelineFetcher)(nil).GetTimeline), org, repo, idx)
|
||||
return &MockGiteaPRTimelineFetcherGetTimelineCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaPRTimelineReviewFetcherGetTimelineCall wrap *gomock.Call
|
||||
type MockGiteaPRTimelineReviewFetcherGetTimelineCall struct {
|
||||
// MockGiteaPRTimelineFetcherGetTimelineCall wrap *gomock.Call
|
||||
type MockGiteaPRTimelineFetcherGetTimelineCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaPRTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaPRTimelineReviewFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineReviewFetcherGetTimelineCall {
|
||||
func (c *MockGiteaPRTimelineFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaPRTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -1215,32 +1050,32 @@ func (c *MockGiteaPRCheckerGetTimelineCall) DoAndReturn(f func(string, string, i
|
||||
return c
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequester is a mock of GiteaReviewFetcherAndRequesterAndUnrequester interface.
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequester struct {
|
||||
// MockGiteaReviewFetcherAndRequester is a mock of GiteaReviewFetcherAndRequester interface.
|
||||
type MockGiteaReviewFetcherAndRequester struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder
|
||||
recorder *MockGiteaReviewFetcherAndRequesterMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder is the mock recorder for MockGiteaReviewFetcherAndRequesterAndUnrequester.
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder struct {
|
||||
mock *MockGiteaReviewFetcherAndRequesterAndUnrequester
|
||||
// MockGiteaReviewFetcherAndRequesterMockRecorder is the mock recorder for MockGiteaReviewFetcherAndRequester.
|
||||
type MockGiteaReviewFetcherAndRequesterMockRecorder struct {
|
||||
mock *MockGiteaReviewFetcherAndRequester
|
||||
}
|
||||
|
||||
// NewMockGiteaReviewFetcherAndRequesterAndUnrequester creates a new mock instance.
|
||||
func NewMockGiteaReviewFetcherAndRequesterAndUnrequester(ctrl *gomock.Controller) *MockGiteaReviewFetcherAndRequesterAndUnrequester {
|
||||
mock := &MockGiteaReviewFetcherAndRequesterAndUnrequester{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder{mock}
|
||||
// NewMockGiteaReviewFetcherAndRequester creates a new mock instance.
|
||||
func NewMockGiteaReviewFetcherAndRequester(ctrl *gomock.Controller) *MockGiteaReviewFetcherAndRequester {
|
||||
mock := &MockGiteaReviewFetcherAndRequester{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaReviewFetcherAndRequesterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) EXPECT() *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder {
|
||||
func (m *MockGiteaReviewFetcherAndRequester) EXPECT() *MockGiteaReviewFetcherAndRequesterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetIssueComments mocks base method.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
|
||||
func (m *MockGiteaReviewFetcherAndRequester) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIssueComments", org, project, issueNo)
|
||||
ret0, _ := ret[0].([]*models.Comment)
|
||||
@@ -1249,37 +1084,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetIssueComments(org,
|
||||
}
|
||||
|
||||
// GetIssueComments indicates an expected call of GetIssueComments.
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetIssueComments(org, project, issueNo any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetIssueComments(org, project, issueNo any) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssueComments", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetIssueComments), org, project, issueNo)
|
||||
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssueComments", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetIssueComments), org, project, issueNo)
|
||||
return &MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall struct {
|
||||
// MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) Return(arg0 []*models.Comment, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) Return(arg0 []*models.Comment, arg1 error) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) Do(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) Do(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall) DoAndReturn(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetIssueCommentsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall) DoAndReturn(f func(string, string, int64) ([]*models.Comment, error)) *MockGiteaReviewFetcherAndRequesterGetIssueCommentsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetPullRequestReviews mocks base method.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
|
||||
func (m *MockGiteaReviewFetcherAndRequester) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPullRequestReviews", org, project, PRnum)
|
||||
ret0, _ := ret[0].([]*models.PullReview)
|
||||
@@ -1288,37 +1123,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetPullRequestReviews
|
||||
}
|
||||
|
||||
// GetPullRequestReviews indicates an expected call of GetPullRequestReviews.
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetPullRequestReviews(org, project, PRnum any) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetPullRequestReviews), org, project, PRnum)
|
||||
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetPullRequestReviews), org, project, PRnum)
|
||||
return &MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall struct {
|
||||
// MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) Do(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetPullRequestReviewsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall) DoAndReturn(f func(string, string, int64) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterGetPullRequestReviewsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetTimeline mocks base method.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
func (m *MockGiteaReviewFetcherAndRequester) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
|
||||
ret0, _ := ret[0].([]*models.TimelineComment)
|
||||
@@ -1327,37 +1162,37 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) GetTimeline(org, repo
|
||||
}
|
||||
|
||||
// GetTimeline indicates an expected call of GetTimeline.
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).GetTimeline), org, repo, idx)
|
||||
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).GetTimeline), org, repo, idx)
|
||||
return &MockGiteaReviewFetcherAndRequesterGetTimelineCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall struct {
|
||||
// MockGiteaReviewFetcherAndRequesterGetTimelineCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterGetTimelineCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterGetTimelineCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaReviewFetcherAndRequesterGetTimelineCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// RequestReviews mocks base method.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) RequestReviews(pr *models.PullRequest, reviewer ...string) ([]*models.PullReview, error) {
|
||||
func (m *MockGiteaReviewFetcherAndRequester) RequestReviews(pr *models.PullRequest, reviewer ...string) ([]*models.PullReview, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{pr}
|
||||
for _, a := range reviewer {
|
||||
@@ -1370,181 +1205,32 @@ func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) RequestReviews(pr *mo
|
||||
}
|
||||
|
||||
// RequestReviews indicates an expected call of RequestReviews.
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) RequestReviews(pr any, reviewer ...any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterMockRecorder) RequestReviews(pr any, reviewer ...any) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{pr}, reviewer...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).RequestReviews), varargs...)
|
||||
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall{Call: call}
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestReviews", reflect.TypeOf((*MockGiteaReviewFetcherAndRequester)(nil).RequestReviews), varargs...)
|
||||
return &MockGiteaReviewFetcherAndRequesterRequestReviewsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall struct {
|
||||
// MockGiteaReviewFetcherAndRequesterRequestReviewsCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterRequestReviewsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) Return(arg0 []*models.PullReview, arg1 error) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) Do(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) Do(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall) DoAndReturn(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterRequestReviewsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// UnrequestReview mocks base method.
|
||||
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) UnrequestReview(org, repo string, id int64, reviwers ...string) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{org, repo, id}
|
||||
for _, a := range reviwers {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UnrequestReview", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnrequestReview indicates an expected call of UnrequestReview.
|
||||
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) UnrequestReview(org, repo, id any, reviwers ...any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{org, repo, id}, reviwers...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnrequestReview", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).UnrequestReview), varargs...)
|
||||
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall wrap *gomock.Call
|
||||
type MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Return(arg0 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Do(f func(string, string, int64, ...string) error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) DoAndReturn(f func(string, string, int64, ...string) error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockGiteaUnreviewTimelineFetcher is a mock of GiteaUnreviewTimelineFetcher interface.
|
||||
type MockGiteaUnreviewTimelineFetcher struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGiteaUnreviewTimelineFetcherMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockGiteaUnreviewTimelineFetcherMockRecorder is the mock recorder for MockGiteaUnreviewTimelineFetcher.
|
||||
type MockGiteaUnreviewTimelineFetcherMockRecorder struct {
|
||||
mock *MockGiteaUnreviewTimelineFetcher
|
||||
}
|
||||
|
||||
// NewMockGiteaUnreviewTimelineFetcher creates a new mock instance.
|
||||
func NewMockGiteaUnreviewTimelineFetcher(ctrl *gomock.Controller) *MockGiteaUnreviewTimelineFetcher {
|
||||
mock := &MockGiteaUnreviewTimelineFetcher{ctrl: ctrl}
|
||||
mock.recorder = &MockGiteaUnreviewTimelineFetcherMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockGiteaUnreviewTimelineFetcher) EXPECT() *MockGiteaUnreviewTimelineFetcherMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetTimeline mocks base method.
|
||||
func (m *MockGiteaUnreviewTimelineFetcher) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetTimeline", org, repo, idx)
|
||||
ret0, _ := ret[0].([]*models.TimelineComment)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetTimeline indicates an expected call of GetTimeline.
|
||||
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) GetTimeline(org, repo, idx any) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTimeline", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).GetTimeline), org, repo, idx)
|
||||
return &MockGiteaUnreviewTimelineFetcherGetTimelineCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaUnreviewTimelineFetcherGetTimelineCall wrap *gomock.Call
|
||||
type MockGiteaUnreviewTimelineFetcherGetTimelineCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) Return(arg0 []*models.TimelineComment, arg1 error) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) Do(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaUnreviewTimelineFetcherGetTimelineCall) DoAndReturn(f func(string, string, int64) ([]*models.TimelineComment, error)) *MockGiteaUnreviewTimelineFetcherGetTimelineCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// UnrequestReview mocks base method.
|
||||
func (m *MockGiteaUnreviewTimelineFetcher) UnrequestReview(org, repo string, id int64, reviwers ...string) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{org, repo, id}
|
||||
for _, a := range reviwers {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UnrequestReview", varargs...)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnrequestReview indicates an expected call of UnrequestReview.
|
||||
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) UnrequestReview(org, repo, id any, reviwers ...any) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{org, repo, id}, reviwers...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnrequestReview", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).UnrequestReview), varargs...)
|
||||
return &MockGiteaUnreviewTimelineFetcherUnrequestReviewCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaUnreviewTimelineFetcherUnrequestReviewCall wrap *gomock.Call
|
||||
type MockGiteaUnreviewTimelineFetcherUnrequestReviewCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) Return(arg0 error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) Do(f func(string, string, int64, ...string) error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) DoAndReturn(f func(string, string, int64, ...string) error) *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall {
|
||||
func (c *MockGiteaReviewFetcherAndRequesterRequestReviewsCall) DoAndReturn(f func(*models.PullRequest, ...string) ([]*models.PullReview, error)) *MockGiteaReviewFetcherAndRequesterRequestReviewsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -2164,13 +1850,12 @@ func (c *MockGiteaAddReviewCommentCall) DoAndReturn(f func(*models.PullRequest,
|
||||
}
|
||||
|
||||
// CreatePullRequestIfNotExist mocks base method.
|
||||
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
|
||||
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreatePullRequestIfNotExist", repo, srcId, targetId, title, body)
|
||||
ret0, _ := ret[0].(*models.PullRequest)
|
||||
ret1, _ := ret[1].(error)
|
||||
ret2, _ := ret[2].(bool)
|
||||
return ret0, ret1, ret2
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreatePullRequestIfNotExist indicates an expected call of CreatePullRequestIfNotExist.
|
||||
@@ -2186,19 +1871,19 @@ type MockGiteaCreatePullRequestIfNotExistCall struct {
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error, arg2 bool) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
c.Call = c.Call.Return(arg0, arg1, arg2)
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -2517,45 +2202,6 @@ func (c *MockGiteaGetIssueCommentsCall) DoAndReturn(f func(string, string, int64
|
||||
return c
|
||||
}
|
||||
|
||||
// GetLabels mocks base method.
|
||||
func (m *MockGitea) GetLabels(org, repo string, idx int64) ([]*models.Label, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLabels", org, repo, idx)
|
||||
ret0, _ := ret[0].([]*models.Label)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetLabels indicates an expected call of GetLabels.
|
||||
func (mr *MockGiteaMockRecorder) GetLabels(org, repo, idx any) *MockGiteaGetLabelsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLabels", reflect.TypeOf((*MockGitea)(nil).GetLabels), org, repo, idx)
|
||||
return &MockGiteaGetLabelsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaGetLabelsCall wrap *gomock.Call
|
||||
type MockGiteaGetLabelsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaGetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaGetLabelsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaGetLabelsCall) Do(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaGetLabelsCall) DoAndReturn(f func(string, string, int64) ([]*models.Label, error)) *MockGiteaGetLabelsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetNotifications mocks base method.
|
||||
func (m *MockGitea) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3147,45 +2793,6 @@ func (c *MockGiteaSetCommitStatusCall) DoAndReturn(f func(string, string, string
|
||||
return c
|
||||
}
|
||||
|
||||
// SetLabels mocks base method.
|
||||
func (m *MockGitea) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetLabels", org, repo, idx, labels)
|
||||
ret0, _ := ret[0].([]*models.Label)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SetLabels indicates an expected call of SetLabels.
|
||||
func (mr *MockGiteaMockRecorder) SetLabels(org, repo, idx, labels any) *MockGiteaSetLabelsCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLabels", reflect.TypeOf((*MockGitea)(nil).SetLabels), org, repo, idx, labels)
|
||||
return &MockGiteaSetLabelsCall{Call: call}
|
||||
}
|
||||
|
||||
// MockGiteaSetLabelsCall wrap *gomock.Call
|
||||
type MockGiteaSetLabelsCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockGiteaSetLabelsCall) Return(arg0 []*models.Label, arg1 error) *MockGiteaSetLabelsCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockGiteaSetLabelsCall) Do(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockGiteaSetLabelsCall) DoAndReturn(f func(string, string, int64, []string) ([]*models.Label, error)) *MockGiteaSetLabelsCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetNotificationRead mocks base method.
|
||||
func (m *MockGitea) SetNotificationRead(notificationId int64) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
329
common/pr.go
329
common/pr.go
@@ -4,8 +4,6 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -23,8 +21,7 @@ type PRSet struct {
|
||||
PRs []*PRInfo
|
||||
Config *AutogitConfig
|
||||
|
||||
BotUser string
|
||||
HasAutoStaging bool
|
||||
BotUser string
|
||||
}
|
||||
|
||||
func (prinfo *PRInfo) PRComponents() (org string, repo string, idx int64) {
|
||||
@@ -34,41 +31,6 @@ func (prinfo *PRInfo) PRComponents() (org string, repo string, idx int64) {
|
||||
return
|
||||
}
|
||||
|
||||
func (prinfo *PRInfo) RemoveReviewers(gitea GiteaUnreviewTimelineFetcher, Reviewers []string, BotUser string) {
|
||||
org, repo, idx := prinfo.PRComponents()
|
||||
tl, err := gitea.GetTimeline(org, repo, idx)
|
||||
if err != nil {
|
||||
LogError("Failed to fetch timeline for", PRtoString(prinfo.PR), err)
|
||||
}
|
||||
|
||||
// find review request for each reviewer
|
||||
ReviewersToUnrequest := Reviewers
|
||||
ReviewersAlreadyChecked := []string{}
|
||||
|
||||
for _, tlc := range tl {
|
||||
if tlc.Type == TimelineCommentType_ReviewRequested && tlc.Assignee != nil {
|
||||
user := tlc.Assignee.UserName
|
||||
|
||||
if idx := slices.Index(ReviewersToUnrequest, user); idx >= 0 && !slices.Contains(ReviewersAlreadyChecked, user) {
|
||||
if tlc.User != nil && tlc.User.UserName == BotUser {
|
||||
ReviewersAlreadyChecked = append(ReviewersAlreadyChecked, user)
|
||||
continue
|
||||
}
|
||||
ReviewersToUnrequest = slices.Delete(ReviewersToUnrequest, idx, idx+1)
|
||||
if len(Reviewers) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug("Unrequesting reviewes for", PRtoString(prinfo.PR), ReviewersToUnrequest)
|
||||
err = gitea.UnrequestReview(org, repo, idx, ReviewersToUnrequest...)
|
||||
if err != nil {
|
||||
LogError("Failed to unrequest reviewers for", PRtoString(prinfo.PR), err)
|
||||
}
|
||||
}
|
||||
|
||||
func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRInfo, config *AutogitConfig) ([]*PRInfo, error) {
|
||||
for _, p := range currentSet {
|
||||
if pr.Index == p.PR.Index && pr.Base.Repo.Name == p.PR.Base.Repo.Name && pr.Base.Repo.Owner.UserName == p.PR.Base.Repo.Owner.UserName {
|
||||
@@ -99,7 +61,7 @@ func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRIn
|
||||
|
||||
var Timeline_RefIssueNotFound error = errors.New("RefIssue not found on the timeline")
|
||||
|
||||
func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher, org, repo string, num int64, config *AutogitConfig) (*models.PullRequest, error) {
|
||||
func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineFetcher, org, repo string, num int64, config *AutogitConfig) (*models.PullRequest, error) {
|
||||
timeline, err := gitea.GetTimeline(org, repo, num)
|
||||
if err != nil {
|
||||
LogError("Failed to fetch timeline for", org, repo, "#", num, err)
|
||||
@@ -124,19 +86,14 @@ func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher,
|
||||
}
|
||||
|
||||
pr, err := gitea.GetPullRequest(prjGitOrg, prjGitRepo, issue.Index)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *repository.RepoGetPullRequestNotFound: // deleted?
|
||||
continue
|
||||
default:
|
||||
LogDebug("PrjGit RefIssue fetch error from timeline", issue.Index, err)
|
||||
continue
|
||||
}
|
||||
switch err.(type) {
|
||||
case *repository.RepoGetPullRequestNotFound: // deleted?
|
||||
continue
|
||||
default:
|
||||
LogDebug("PrjGit RefIssue fetch error from timeline", issue.Index, err)
|
||||
}
|
||||
|
||||
LogDebug("found ref PR on timeline:", PRtoString(pr))
|
||||
if pr.Base.Name != prjGitBranch {
|
||||
LogDebug(" -> not matching:", pr.Base.Name, prjGitBranch)
|
||||
if pr.Base.Ref != prjGitBranch {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -156,7 +113,7 @@ func LastPrjGitRefOnTimeline(botUser string, gitea GiteaPRTimelineReviewFetcher,
|
||||
return nil, Timeline_RefIssueNotFound
|
||||
}
|
||||
|
||||
func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
|
||||
func FetchPRSet(user string, gitea GiteaPRTimelineFetcher, org, repo string, num int64, config *AutogitConfig) (*PRSet, error) {
|
||||
var pr *models.PullRequest
|
||||
var err error
|
||||
|
||||
@@ -182,15 +139,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pr := range prs {
|
||||
org, repo, idx := pr.PRComponents()
|
||||
reviews, err := FetchGiteaReviews(gitea, org, repo, idx)
|
||||
if err != nil {
|
||||
LogError("Error fetching reviews for", PRtoString(pr.PR), ":", err)
|
||||
}
|
||||
pr.Reviews = reviews
|
||||
}
|
||||
|
||||
return &PRSet{
|
||||
PRs: prs,
|
||||
Config: config,
|
||||
@@ -198,12 +146,6 @@ func FetchPRSet(user string, gitea GiteaPRTimelineReviewFetcher, org, repo strin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (prset *PRSet) RemoveReviewers(gitea GiteaUnreviewTimelineFetcher, reviewers []string) {
|
||||
for _, prinfo := range prset.PRs {
|
||||
prinfo.RemoveReviewers(gitea, reviewers, prset.BotUser)
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *PRSet) Find(pr *models.PullRequest) (*PRInfo, bool) {
|
||||
for _, p := range rs.PRs {
|
||||
if p.PR.Base.RepoID == pr.Base.RepoID &&
|
||||
@@ -289,144 +231,67 @@ next_rs:
|
||||
}
|
||||
|
||||
for _, pr := range prjpr_set {
|
||||
if strings.EqualFold(prinfo.PR.Base.Repo.Owner.UserName, pr.Org) && strings.EqualFold(prinfo.PR.Base.Repo.Name, pr.Repo) && prinfo.PR.Index == pr.Num {
|
||||
if prinfo.PR.Base.Repo.Owner.UserName == pr.Org && prinfo.PR.Base.Repo.Name == pr.Repo && prinfo.PR.Index == pr.Num {
|
||||
continue next_rs
|
||||
}
|
||||
}
|
||||
LogDebug(" PR: ", PRtoString(prinfo.PR), "not found in project git PRSet")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rs *PRSet) FindMissingAndExtraReviewers(maintainers MaintainershipData, idx int) (missing, extra []string) {
|
||||
func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintainers MaintainershipData) error {
|
||||
configReviewers := ParseReviewers(rs.Config.Reviewers)
|
||||
|
||||
// remove reviewers that were already requested and are not stale
|
||||
prjMaintainers := maintainers.ListProjectMaintainers(nil)
|
||||
LogDebug("project maintainers:", prjMaintainers)
|
||||
for _, pr := range rs.PRs {
|
||||
reviewers := []string{}
|
||||
|
||||
pr := rs.PRs[idx]
|
||||
if rs.IsPrjGitPR(pr.PR) {
|
||||
missing = slices.Concat(configReviewers.Prj, configReviewers.PrjOptional)
|
||||
if rs.HasAutoStaging {
|
||||
missing = append(missing, Bot_BuildReview)
|
||||
}
|
||||
LogDebug("PrjGit submitter:", pr.PR.User.UserName)
|
||||
// only need project maintainer reviews if:
|
||||
// * not created by a bot and has other PRs, or
|
||||
// * not created by maintainer
|
||||
noReviewPRCreators := prjMaintainers
|
||||
if len(rs.PRs) > 1 {
|
||||
noReviewPRCreators = append(noReviewPRCreators, rs.BotUser)
|
||||
}
|
||||
if slices.Contains(noReviewPRCreators, pr.PR.User.UserName) || pr.Reviews.IsReviewedByOneOf(prjMaintainers...) {
|
||||
LogDebug("Project already reviewed by a project maintainer, remove rest")
|
||||
// do not remove reviewers if they are also maintainers
|
||||
prjMaintainers = slices.DeleteFunc(prjMaintainers, func(m string) bool { return slices.Contains(missing, m) })
|
||||
extra = slices.Concat(prjMaintainers, []string{rs.BotUser})
|
||||
if rs.IsPrjGitPR(pr.PR) {
|
||||
reviewers = slices.Concat(configReviewers.Prj, configReviewers.PrjOptional)
|
||||
LogDebug("PrjGit submitter:", pr.PR.User.UserName)
|
||||
if len(rs.PRs) == 1 {
|
||||
reviewers = slices.Concat(reviewers, maintainers.ListProjectMaintainers(nil))
|
||||
}
|
||||
} else {
|
||||
// if bot not created PrjGit or prj maintainer, we need to add project reviewers here
|
||||
if slices.Contains(noReviewPRCreators, pr.PR.User.UserName) {
|
||||
LogDebug("No need for project maintainers")
|
||||
extra = slices.Concat(prjMaintainers, []string{rs.BotUser})
|
||||
pkg := pr.PR.Base.Repo.Name
|
||||
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(nil), maintainers.ListPackageMaintainers(pkg, nil), configReviewers.PkgOptional)
|
||||
}
|
||||
|
||||
slices.Sort(reviewers)
|
||||
reviewers = slices.Compact(reviewers)
|
||||
|
||||
// submitters do not need to review their own work
|
||||
if idx := slices.Index(reviewers, pr.PR.User.UserName); idx != -1 {
|
||||
reviewers = slices.Delete(reviewers, idx, idx+1)
|
||||
}
|
||||
|
||||
LogDebug("PR: ", pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
LogDebug("reviewers for PR:", reviewers)
|
||||
|
||||
// remove reviewers that were already requested and are not stale
|
||||
reviews, err := FetchGiteaReviews(gitea, reviewers, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Error fetching reviews:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for idx := 0; idx < len(reviewers); {
|
||||
user := reviewers[idx]
|
||||
if reviews.HasPendingReviewBy(user) || reviews.IsReviewedBy(user) {
|
||||
reviewers = slices.Delete(reviewers, idx, idx+1)
|
||||
LogDebug("removing reviewer:", user)
|
||||
} else {
|
||||
LogDebug("Adding prjMaintainers to PrjGit")
|
||||
missing = append(missing, prjMaintainers...)
|
||||
idx++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pkg := pr.PR.Base.Repo.Name
|
||||
pkgMaintainers := maintainers.ListPackageMaintainers(pkg, nil)
|
||||
Maintainers := slices.Concat(prjMaintainers, pkgMaintainers)
|
||||
noReviewPkgPRCreators := pkgMaintainers
|
||||
|
||||
LogDebug("packakge maintainers:", Maintainers)
|
||||
|
||||
missing = slices.Concat(configReviewers.Pkg, configReviewers.PkgOptional)
|
||||
if slices.Contains(noReviewPkgPRCreators, pr.PR.User.UserName) || pr.Reviews.IsReviewedByOneOf(Maintainers...) {
|
||||
// submitter is maintainer or already reviewed
|
||||
LogDebug("Package reviewed by maintainer (or subitter is maintainer), remove the rest of them")
|
||||
// do not remove reviewers if they are also maintainers
|
||||
Maintainers = slices.DeleteFunc(Maintainers, func(m string) bool { return slices.Contains(missing, m) })
|
||||
extra = slices.Concat(Maintainers, []string{rs.BotUser})
|
||||
} else {
|
||||
// maintainer review is missing
|
||||
LogDebug("Adding package maintainers to package git")
|
||||
missing = append(missing, pkgMaintainers...)
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(missing)
|
||||
missing = slices.Compact(missing)
|
||||
|
||||
slices.Sort(extra)
|
||||
extra = slices.Compact(extra)
|
||||
|
||||
// submitters cannot review their own work
|
||||
if idx := slices.Index(missing, pr.PR.User.UserName); idx != -1 {
|
||||
missing = slices.Delete(missing, idx, idx+1)
|
||||
}
|
||||
|
||||
LogDebug("PR: ", PRtoString(pr.PR))
|
||||
LogDebug(" preliminary add reviewers for PR:", missing)
|
||||
LogDebug(" preliminary rm reviewers for PR:", extra)
|
||||
|
||||
// remove missing reviewers that are already done or already pending
|
||||
for idx := 0; idx < len(missing); {
|
||||
user := missing[idx]
|
||||
if pr.Reviews.HasPendingReviewBy(user) || pr.Reviews.IsReviewedBy(user) {
|
||||
missing = slices.Delete(missing, idx, idx+1)
|
||||
LogDebug(" removing done/pending reviewer:", user)
|
||||
} else {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
// remove extra reviews that are actually only pending, and only pending by us
|
||||
for idx := 0; idx < len(extra); {
|
||||
user := extra[idx]
|
||||
rr := pr.Reviews.FindReviewRequester(user)
|
||||
if rr != nil && rr.User.UserName == rs.BotUser && pr.Reviews.HasPendingReviewBy(user) {
|
||||
// good to remove this review
|
||||
idx++
|
||||
} else {
|
||||
// this review should not be considered as extra by us
|
||||
LogDebug(" - cannot find? to remove", user)
|
||||
if rr != nil {
|
||||
LogDebug(" ", rr.User.UserName, "vs.", rs.BotUser, pr.Reviews.HasPendingReviewBy(user))
|
||||
}
|
||||
extra = slices.Delete(extra, idx, idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug(" add reviewers for PR:", missing)
|
||||
LogDebug(" rm reviewers for PR:", extra)
|
||||
|
||||
return missing, extra
|
||||
}
|
||||
|
||||
func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequesterAndUnrequester, maintainers MaintainershipData) error {
|
||||
for idx, pr := range rs.PRs {
|
||||
missingReviewers, extraReviewers := rs.FindMissingAndExtraReviewers(maintainers, idx)
|
||||
|
||||
if len(missingReviewers) > 0 {
|
||||
LogDebug(" Requesting reviews from:", missingReviewers)
|
||||
// get maintainers associated with the PR too
|
||||
if len(reviewers) > 0 {
|
||||
LogDebug("Requesting reviews from:", reviewers)
|
||||
if !IsDryRun {
|
||||
for _, r := range missingReviewers {
|
||||
for _, r := range reviewers {
|
||||
if _, err := gitea.RequestReviews(pr.PR, r); err != nil {
|
||||
LogError("Cannot create reviews on", PRtoString(pr.PR), "for user:", r, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(extraReviewers) > 0 {
|
||||
LogDebug(" UnRequesting reviews from:", extraReviewers)
|
||||
if !IsDryRun {
|
||||
for _, r := range extraReviewers {
|
||||
org, repo, idx := pr.PRComponents()
|
||||
if err := gitea.UnrequestReview(org, repo, idx, r); err != nil {
|
||||
LogError("Cannot unrequest reviews on", PRtoString(pr.PR), "for user:", r, err)
|
||||
LogError("Cannot create reviews on", fmt.Sprintf("%s/%s!%d for [%s]", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index, strings.Join(reviewers, ", ")), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,12 +317,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
if err == nil && prjgit != nil {
|
||||
reviewers := slices.Concat(configReviewers.Prj, maintainers.ListProjectMaintainers(groups))
|
||||
LogDebug("Fetching reviews for", prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, reviewers, prjgit.PR.Base.Repo.Owner.UserName, prjgit.PR.Base.Repo.Name, prjgit.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
prjgit.Reviews = r
|
||||
if prjgit.Reviews.IsManualMergeOK() {
|
||||
is_manually_reviewed_ok = true
|
||||
@@ -473,12 +337,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
pkg := pr.PR.Base.Repo.Name
|
||||
reviewers := slices.Concat(configReviewers.Pkg, maintainers.ListPackageMaintainers(pkg, groups))
|
||||
LogDebug("Fetching reviews for", pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, reviewers, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Cannot fetch gita reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
pr.Reviews = r
|
||||
if !pr.Reviews.IsManualMergeOK() {
|
||||
LogInfo("Not approved manual merge. PR:", pr.PR.URL)
|
||||
@@ -500,9 +363,6 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
var pkg string
|
||||
if rs.IsPrjGitPR(pr.PR) {
|
||||
reviewers = configReviewers.Prj
|
||||
if rs.HasAutoStaging {
|
||||
reviewers = append(reviewers, Bot_BuildReview)
|
||||
}
|
||||
pkg = ""
|
||||
} else {
|
||||
reviewers = configReviewers.Pkg
|
||||
@@ -514,12 +374,11 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
return false
|
||||
}
|
||||
|
||||
r, err := FetchGiteaReviews(gitea, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
r, err := FetchGiteaReviews(gitea, reviewers, pr.PR.Base.Repo.Owner.UserName, pr.PR.Base.Repo.Name, pr.PR.Index)
|
||||
if err != nil {
|
||||
LogError("Cannot fetch gitea reaviews for PR:", err)
|
||||
return false
|
||||
}
|
||||
r.RequestedReviewers = reviewers
|
||||
|
||||
is_manually_reviewed_ok = r.IsApproved()
|
||||
LogDebug("PR to", pr.PR.Base.Repo.Name, "reviewed?", is_manually_reviewed_ok)
|
||||
@@ -532,7 +391,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
|
||||
|
||||
if need_maintainer_review := !rs.IsPrjGitPR(pr.PR) || pr.PR.User.UserName != rs.BotUser; need_maintainer_review {
|
||||
// Do not expand groups here, as the group-review-bot will ACK if group has reviewed.
|
||||
if is_manually_reviewed_ok = maintainers.IsApproved(pkg, r.Reviews, pr.PR.User.UserName, nil); !is_manually_reviewed_ok {
|
||||
if is_manually_reviewed_ok = maintainers.IsApproved(pkg, r.reviews, pr.PR.User.UserName, nil); !is_manually_reviewed_ok {
|
||||
LogDebug(" not approved?", pkg)
|
||||
return false
|
||||
}
|
||||
@@ -572,80 +431,8 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package common_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -75,7 +76,7 @@ func TestPR(t *testing.T) {
|
||||
consistentSet bool
|
||||
prjGitPRIndex int
|
||||
|
||||
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error)
|
||||
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error)
|
||||
}{
|
||||
{
|
||||
name: "Error fetching PullRequest",
|
||||
@@ -147,7 +148,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
|
||||
},
|
||||
},
|
||||
@@ -179,7 +180,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
|
||||
},
|
||||
},
|
||||
@@ -207,7 +208,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -241,7 +242,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -275,7 +276,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -311,7 +312,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -346,7 +347,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -388,7 +389,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -430,7 +431,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -473,7 +474,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: false,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
@@ -500,7 +501,7 @@ func TestPR(t *testing.T) {
|
||||
prjGitPRIndex: 0,
|
||||
consistentSet: true,
|
||||
reviewed: true,
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineReviewFetcher) (*common.PRSet, error) {
|
||||
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
|
||||
config := common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
|
||||
Branch: "branch",
|
||||
@@ -515,7 +516,7 @@ func TestPR(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaPRChecker(ctl)
|
||||
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
|
||||
|
||||
@@ -619,508 +620,283 @@ func TestPR(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMissingAndExtraReviewers(t *testing.T) {
|
||||
func TestPRAssignReviewers(t *testing.T) {
|
||||
t.Skip("FAIL: unexpected calls, missing calls")
|
||||
tests := []struct {
|
||||
name string
|
||||
config common.AutogitConfig
|
||||
reviewers []struct {
|
||||
org, repo string
|
||||
num int64
|
||||
reviewer string
|
||||
}
|
||||
|
||||
prset *common.PRSet
|
||||
maintainers common.MaintainershipData
|
||||
pkgReviews []*models.PullReview
|
||||
pkgTimeline []*models.TimelineComment
|
||||
prjReviews []*models.PullReview
|
||||
prjTimeline []*models.TimelineComment
|
||||
|
||||
noAutoStaging bool
|
||||
|
||||
expected_missing_reviewers [][]string
|
||||
expected_extra_reviewers [][]string
|
||||
expectedReviewerCall [2][]string
|
||||
}{
|
||||
{
|
||||
name: "No reviewers",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{},
|
||||
},
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
|
||||
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "One project reviewer only",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1"},
|
||||
},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
[]string{},
|
||||
[]string{"autogits_obs_staging_bot", "user1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "One project reviewer only and no auto staging",
|
||||
noAutoStaging: true,
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1"},
|
||||
},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
nil,
|
||||
{"user1"},
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1"},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "One project reviewer and one pkg reviewer only",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
[]string{"user2"},
|
||||
[]string{"autogits_obs_staging_bot", "user1"},
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"user2", "prjmaintainer", "pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "No need to get reviews of submitter reviewer",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}},
|
||||
RequestedReviewers: []string{"m1"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"m1", "submitter"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
nil,
|
||||
{"autogits_obs_staging_bot", "user1"},
|
||||
},
|
||||
expected_extra_reviewers: [][]string{
|
||||
{"m1"},
|
||||
name: "No need to get reviews of submitter",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "No need to get reviews of submitter maintainer",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "foo"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
name: "Reviews are done",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
},
|
||||
pkgReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "pkgmaintainer"},
|
||||
},
|
||||
{
|
||||
State: common.ReviewStatePending,
|
||||
User: &models.User{UserName: "prjmaintainer"},
|
||||
},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
[]string{},
|
||||
[]string{"autogits_obs_staging_bot", "user1"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Add reviewer if also maintainer where review by maintainer is not needed",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "bot"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{},
|
||||
},
|
||||
prjReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateRequestChanges,
|
||||
User: &models.User{UserName: "user1"},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter", "*reviewer"},
|
||||
{
|
||||
State: common.ReviewStateRequestReview,
|
||||
User: &models.User{UserName: "autogits_obs_staging_bot"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
[]string{"reviewer"},
|
||||
[]string{"autogits_obs_staging_bot", "reviewer", "user1"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Dont remove reviewer if also maintainer",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
|
||||
RequestedReviewers: []string{"reviewer"},
|
||||
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "bot"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}},
|
||||
RequestedReviewers: []string{"reviewer"},
|
||||
FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter", "*reviewer"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{
|
||||
[]string{},
|
||||
[]string{"autogits_obs_staging_bot", "user1"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Extra project reviewer on the package",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user2"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "bot"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer"}, "": {"prjmaintainer"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{},
|
||||
expected_extra_reviewers: [][]string{{"prjmaintainer"}},
|
||||
expectedReviewerCall: [2][]string{},
|
||||
},
|
||||
{
|
||||
name: "Extra project reviewers on the package and project",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "pkgm1"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "pkgm2"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "someother"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "pkgm2", "someother", "prj1", "prj2"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "bot"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}},
|
||||
{State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1", "prj2"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
name: "Stale review is not done, re-request it",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "org/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{},
|
||||
expected_extra_reviewers: [][]string{{"pkgm1", "pkgm2", "prj1", "prj2", "prjmaintainer"}, {"prj1", "prj2"}},
|
||||
pkgReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
},
|
||||
{
|
||||
State: common.ReviewStatePending,
|
||||
User: &models.User{UserName: "prjmaintainer"},
|
||||
},
|
||||
},
|
||||
prjReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateRequestChanges,
|
||||
User: &models.User{UserName: "user1"},
|
||||
Stale: true,
|
||||
},
|
||||
{
|
||||
State: common.ReviewStateRequestReview,
|
||||
Stale: true,
|
||||
User: &models.User{UserName: "autogits_obs_staging_bot"},
|
||||
},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer"}},
|
||||
},
|
||||
{
|
||||
name: "No extra project reviewers on the package and project (all pending)",
|
||||
prset: &common.PRSet{
|
||||
PRs: []*common.PRInfo{
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "submitter"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgmaintainer"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prjmaintainer"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgm1"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "someother"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "someother", "prj1"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "someother"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
PR: &models.PullRequest{
|
||||
User: &models.User{UserName: "bot"},
|
||||
Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prj"}}},
|
||||
},
|
||||
Reviews: &common.PRReviews{
|
||||
Reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}},
|
||||
},
|
||||
RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1"},
|
||||
FullTimeline: []*models.TimelineComment{
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}},
|
||||
{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "user1"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &common.AutogitConfig{
|
||||
GitProjectName: "prj/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
},
|
||||
BotUser: "bot",
|
||||
name: "Stale optional review is not done, re-request it",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2", "~bot"},
|
||||
},
|
||||
maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}},
|
||||
|
||||
expected_missing_reviewers: [][]string{{"pkgm2", "prj2"}},
|
||||
expected_extra_reviewers: [][]string{{}, {"prj1"}},
|
||||
pkgReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "bot"},
|
||||
Stale: true,
|
||||
},
|
||||
{
|
||||
State: common.ReviewStateApproved,
|
||||
User: &models.User{UserName: "user2"},
|
||||
},
|
||||
{
|
||||
State: common.ReviewStatePending,
|
||||
User: &models.User{UserName: "prjmaintainer"},
|
||||
},
|
||||
},
|
||||
prjReviews: []*models.PullReview{
|
||||
{
|
||||
State: common.ReviewStateRequestChanges,
|
||||
User: &models.User{UserName: "user1"},
|
||||
Stale: true,
|
||||
},
|
||||
{
|
||||
State: common.ReviewStateRequestReview,
|
||||
Stale: true,
|
||||
User: &models.User{UserName: "autogits_obs_staging_bot"},
|
||||
},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer", "bot"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.prset.HasAutoStaging = !test.noAutoStaging
|
||||
for idx, pr := range test.prset.PRs {
|
||||
missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx)
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
|
||||
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
|
||||
|
||||
// avoid nil dereference below, by adding empty array elements
|
||||
if idx >= len(test.expected_missing_reviewers) {
|
||||
test.expected_missing_reviewers = append(test.expected_missing_reviewers, nil)
|
||||
}
|
||||
if idx >= len(test.expected_extra_reviewers) {
|
||||
test.expected_extra_reviewers = append(test.expected_extra_reviewers, nil)
|
||||
}
|
||||
if test.pkgTimeline == nil {
|
||||
test.pkgTimeline = reviewsToTimeline(test.pkgReviews)
|
||||
}
|
||||
if test.prjTimeline == nil {
|
||||
test.prjTimeline = reviewsToTimeline(test.prjReviews)
|
||||
}
|
||||
|
||||
slices.Sort(test.expected_extra_reviewers[idx])
|
||||
slices.Sort(test.expected_missing_reviewers[idx])
|
||||
if slices.Compare(missing, test.expected_missing_reviewers[idx]) != 0 {
|
||||
t.Error("Expected missing reviewers for", common.PRtoString(pr.PR), ":", test.expected_missing_reviewers[idx], "but have:", missing)
|
||||
}
|
||||
pr_mock.EXPECT().GetPullRequest("other", "pkgrepo", int64(1)).Return(&models.PullRequest{
|
||||
Body: "Some description is here",
|
||||
User: &models.User{UserName: "submitter"},
|
||||
RequestedReviewers: []*models.User{},
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "pkgrepo", Owner: &models.User{UserName: "other"}}},
|
||||
Head: &models.PRBranchInfo{},
|
||||
Index: 1,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("other", "pkgrepo", int64(1)).Return(test.pkgReviews, nil)
|
||||
review_mock.EXPECT().GetTimeline("other", "pkgrepo", int64(1)).Return(test.pkgTimeline, nil)
|
||||
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
|
||||
Body: fmt.Sprintf(common.PrPattern, "other", "pkgrepo", 1),
|
||||
User: &models.User{UserName: "bot1"},
|
||||
RequestedReviewers: []*models.User{{UserName: "main_reviewer"}},
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
|
||||
Head: &models.PRBranchInfo{},
|
||||
Index: 42,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(42)).Return(test.prjReviews, nil)
|
||||
review_mock.EXPECT().GetTimeline("org", "repo", int64(42)).Return(test.prjTimeline, nil)
|
||||
|
||||
if slices.Compare(extra, test.expected_extra_reviewers[idx]) != 0 {
|
||||
t.Error("Expected reviewers to remove for", common.PRtoString(pr.PR), ":", test.expected_extra_reviewers[idx], "but have:", extra)
|
||||
maintainership_mock.EXPECT().ListProjectMaintainers(gomock.Any()).Return([]string{"prjmaintainer"}).AnyTimes()
|
||||
maintainership_mock.EXPECT().ListPackageMaintainers("pkgrepo", gomock.Any()).Return([]string{"pkgmaintainer"}).AnyTimes()
|
||||
|
||||
prs, _ := common.FetchPRSet("test", pr_mock, "other", "pkgrepo", int64(1), &test.config)
|
||||
if len(prs.PRs) != 2 {
|
||||
t.Fatal("PRs not fetched")
|
||||
}
|
||||
for _, pr := range prs.PRs {
|
||||
r := test.expectedReviewerCall[0]
|
||||
if !prs.IsPrjGitPR(pr.PR) {
|
||||
r = test.expectedReviewerCall[1]
|
||||
}
|
||||
slices.Sort(r)
|
||||
for _, reviewer := range r {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
|
||||
}
|
||||
}
|
||||
prs.AssignReviewers(review_mock, maintainership_mock)
|
||||
})
|
||||
}
|
||||
|
||||
prjgit_tests := []struct {
|
||||
name string
|
||||
config common.AutogitConfig
|
||||
reviewers []struct {
|
||||
org, repo string
|
||||
num int64
|
||||
reviewer string
|
||||
}
|
||||
|
||||
prjReviews []*models.PullReview
|
||||
|
||||
expectedReviewerCall [2][]string
|
||||
}{
|
||||
{
|
||||
name: "PrjMaintainers in prjgit review when not part of pkg set",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "org/repo#main",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{},
|
||||
},
|
||||
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot", "prjmaintainer"}},
|
||||
},
|
||||
}
|
||||
for _, test := range prjgit_tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
|
||||
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
|
||||
|
||||
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
|
||||
Body: "Some description is here",
|
||||
User: &models.User{UserName: "submitter"},
|
||||
RequestedReviewers: []*models.User{},
|
||||
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
|
||||
Head: &models.PRBranchInfo{},
|
||||
Index: 1,
|
||||
}, nil)
|
||||
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(1)).Return(test.prjReviews, nil)
|
||||
review_mock.EXPECT().GetTimeline("org", "repo", int64(1)).Return(nil, nil)
|
||||
|
||||
maintainership_mock.EXPECT().ListProjectMaintainers(gomock.Any()).Return([]string{"prjmaintainer"}).AnyTimes()
|
||||
|
||||
prs, _ := common.FetchPRSet("test", pr_mock, "org", "repo", int64(1), &test.config)
|
||||
if len(prs.PRs) != 1 {
|
||||
t.Fatal("PRs not fetched")
|
||||
}
|
||||
for _, pr := range prs.PRs {
|
||||
r := test.expectedReviewerCall[0]
|
||||
if !prs.IsPrjGitPR(pr.PR) {
|
||||
t.Fatal("only prjgit pr here")
|
||||
}
|
||||
for _, reviewer := range r {
|
||||
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
|
||||
}
|
||||
}
|
||||
prs.AssignReviewers(review_mock, maintainership_mock)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1165,6 +941,7 @@ func TestPRMerge(t *testing.T) {
|
||||
pr: &models.PullRequest{
|
||||
Base: &models.PRBranchInfo{
|
||||
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
|
||||
Name: "master",
|
||||
Repo: &models.Repository{
|
||||
Name: "prj",
|
||||
Owner: &models.User{
|
||||
@@ -1185,6 +962,7 @@ func TestPRMerge(t *testing.T) {
|
||||
pr: &models.PullRequest{
|
||||
Base: &models.PRBranchInfo{
|
||||
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
|
||||
Name: "master",
|
||||
Repo: &models.Repository{
|
||||
Name: "prj",
|
||||
Owner: &models.User{
|
||||
@@ -1203,7 +981,8 @@ func TestPRMerge(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
|
||||
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
|
||||
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
|
||||
|
||||
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
@@ -1262,7 +1041,7 @@ func TestPRChanges(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctl := gomock.NewController(t)
|
||||
mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
|
||||
mock_fetcher := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
|
||||
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
|
||||
for _, pr := range test.PRs {
|
||||
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Reviewers struct {
|
||||
Prj []string
|
||||
Pkg []string
|
||||
@@ -32,5 +36,10 @@ func ParseReviewers(input []string) *Reviewers {
|
||||
*pkg = append(*pkg, reviewer)
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Contains(r.Prj, Bot_BuildReview) {
|
||||
r.Prj = append(r.Prj, Bot_BuildReview)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ func TestReviewers(t *testing.T) {
|
||||
name: "project and package reviewers",
|
||||
input: []string{"1", "2", "3", "*5", "+6", "-7"},
|
||||
|
||||
prj: []string{"5", "7"},
|
||||
prj: []string{"5", "7", common.Bot_BuildReview},
|
||||
pkg: []string{"1", "2", "3", "5", "6"},
|
||||
},
|
||||
{
|
||||
name: "optional project and package reviewers",
|
||||
input: []string{"~1", "2", "3", "~*5", "+6", "-7"},
|
||||
|
||||
prj: []string{"7"},
|
||||
prj: []string{"7", common.Bot_BuildReview},
|
||||
pkg: []string{"2", "3", "6"},
|
||||
prj_optional: []string{"5"},
|
||||
pkg_optional: []string{"1", "5"},
|
||||
|
||||
@@ -9,14 +9,12 @@ import (
|
||||
)
|
||||
|
||||
type PRReviews struct {
|
||||
Reviews []*models.PullReview
|
||||
RequestedReviewers []string
|
||||
Comments []*models.TimelineComment
|
||||
|
||||
FullTimeline []*models.TimelineComment
|
||||
reviews []*models.PullReview
|
||||
reviewers []string
|
||||
comments []*models.TimelineComment
|
||||
}
|
||||
|
||||
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) {
|
||||
func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, reviewers []string, org, repo string, no int64) (*PRReviews, error) {
|
||||
timeline, err := rf.GetTimeline(org, repo, no)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -27,14 +25,10 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reviews := make([]*models.PullReview, 0, 10)
|
||||
needNewReviews := []string{}
|
||||
reviews := make([]*models.PullReview, 0, len(reviewers))
|
||||
var comments []*models.TimelineComment
|
||||
|
||||
alreadyHaveUserReview := func(user string) bool {
|
||||
if slices.Contains(needNewReviews, user) {
|
||||
return true
|
||||
}
|
||||
for _, r := range reviews {
|
||||
if r.User != nil && r.User.UserName == user {
|
||||
return true
|
||||
@@ -43,40 +37,32 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64
|
||||
return false
|
||||
}
|
||||
|
||||
LogDebug("FetchingGiteaReviews for", org, repo, no)
|
||||
LogDebug("Number of reviews:", len(rawReviews))
|
||||
LogDebug("Number of items in timeline:", len(timeline))
|
||||
|
||||
cutOffIdx := len(timeline)
|
||||
for idx, item := range timeline {
|
||||
if item.Type == TimelineCommentType_Review || item.Type == TimelineCommentType_ReviewRequested {
|
||||
if item.Type == TimelineCommentType_Review {
|
||||
for _, r := range rawReviews {
|
||||
if r.ID == item.ReviewID {
|
||||
if !alreadyHaveUserReview(r.User.UserName) {
|
||||
if item.Type == TimelineCommentType_Review && idx > cutOffIdx {
|
||||
needNewReviews = append(needNewReviews, r.User.UserName)
|
||||
} else {
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
reviews = append(reviews, r)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if item.Type == TimelineCommentType_Comment && cutOffIdx > idx {
|
||||
} else if item.Type == TimelineCommentType_Comment {
|
||||
comments = append(comments, item)
|
||||
} else if item.Type == TimelineCommentType_PushPull && cutOffIdx == len(timeline) {
|
||||
LogDebug("cut-off", item.Created, "@", idx)
|
||||
cutOffIdx = idx
|
||||
} else if item.Type == TimelineCommentType_PushPull {
|
||||
LogDebug("cut-off", item.Created)
|
||||
timeline = timeline[0:idx]
|
||||
break
|
||||
} else {
|
||||
LogDebug("Unhandled timeline type:", item.Type)
|
||||
}
|
||||
}
|
||||
LogDebug("num comments:", len(comments), "timeline:", len(reviews))
|
||||
LogDebug("num comments:", len(comments), "reviews:", len(reviews), len(timeline))
|
||||
|
||||
return &PRReviews{
|
||||
Reviews: reviews,
|
||||
Comments: comments,
|
||||
FullTimeline: timeline,
|
||||
reviews: reviews,
|
||||
reviewers: reviewers,
|
||||
comments: comments,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -95,27 +81,23 @@ func bodyCommandManualMergeOK(body string) bool {
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsManualMergeOK() bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range r.Comments {
|
||||
for _, c := range r.comments {
|
||||
if c.Updated != c.Created {
|
||||
continue
|
||||
}
|
||||
LogDebug("comment:", c.User.UserName, c.Body)
|
||||
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
|
||||
if slices.Contains(r.reviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range r.Reviews {
|
||||
for _, c := range r.reviews {
|
||||
if c.Updated != c.Submitted {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(r.RequestedReviewers, c.User.UserName) {
|
||||
if slices.Contains(r.reviewers, c.User.UserName) {
|
||||
if bodyCommandManualMergeOK(c.Body) {
|
||||
return true
|
||||
}
|
||||
@@ -126,14 +108,11 @@ func (r *PRReviews) IsManualMergeOK() bool {
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsApproved() bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
goodReview := true
|
||||
|
||||
for _, reviewer := range r.RequestedReviewers {
|
||||
for _, reviewer := range r.reviewers {
|
||||
goodReview = false
|
||||
for _, review := range r.Reviews {
|
||||
for _, review := range r.reviews {
|
||||
if review.User.UserName == reviewer && review.State == ReviewStateApproved && !review.Stale && !review.Dismissed {
|
||||
LogDebug(" -- found review: ", review.User.UserName)
|
||||
goodReview = true
|
||||
@@ -151,11 +130,7 @@ func (r *PRReviews) IsApproved() bool {
|
||||
|
||||
func (r *PRReviews) MissingReviews() []string {
|
||||
missing := []string{}
|
||||
if r == nil {
|
||||
return missing
|
||||
}
|
||||
|
||||
for _, reviewer := range r.RequestedReviewers {
|
||||
for _, reviewer := range r.reviewers {
|
||||
if !r.IsReviewedBy(reviewer) {
|
||||
missing = append(missing, reviewer)
|
||||
}
|
||||
@@ -163,64 +138,45 @@ func (r *PRReviews) MissingReviews() []string {
|
||||
return missing
|
||||
}
|
||||
|
||||
func (r *PRReviews) FindReviewRequester(reviewer string) *models.TimelineComment {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, r := range r.FullTimeline {
|
||||
if r.Type == TimelineCommentType_ReviewRequested && r.Assignee.UserName == reviewer {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PRReviews) HasPendingReviewBy(reviewer string) bool {
|
||||
if r == nil {
|
||||
if !slices.Contains(r.reviewers, reviewer) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range r.Reviews {
|
||||
if r.User.UserName == reviewer {
|
||||
isPending := false
|
||||
for _, r := range r.reviews {
|
||||
if r.User.UserName == reviewer && !r.Stale {
|
||||
switch r.State {
|
||||
case ReviewStateRequestReview, ReviewStatePending:
|
||||
return true
|
||||
default:
|
||||
case ReviewStateApproved:
|
||||
fallthrough
|
||||
case ReviewStateRequestChanges:
|
||||
return false
|
||||
case ReviewStateRequestReview:
|
||||
fallthrough
|
||||
case ReviewStatePending:
|
||||
isPending = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return isPending
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsReviewedBy(reviewer string) bool {
|
||||
if r == nil {
|
||||
if !slices.Contains(r.reviewers, reviewer) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range r.Reviews {
|
||||
for _, r := range r.reviews {
|
||||
if r.User.UserName == reviewer && !r.Stale {
|
||||
switch r.State {
|
||||
case ReviewStateApproved, ReviewStateRequestChanges:
|
||||
case ReviewStateApproved:
|
||||
return true
|
||||
case ReviewStateRequestChanges:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PRReviews) IsReviewedByOneOf(reviewers ...string) bool {
|
||||
for _, reviewer := range reviewers {
|
||||
if r.IsReviewedBy(reviewer) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -62,23 +62,11 @@ func TestReviews(t *testing.T) {
|
||||
{
|
||||
name: "Two reviewer, one stale and pending",
|
||||
reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}, Stale: true},
|
||||
&models.PullReview{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}, Stale: true},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isPendingByTest1: true,
|
||||
isReviewedByTest1: false,
|
||||
},
|
||||
{
|
||||
name: "Two reviewer, one stale and pending, other done",
|
||||
reviews: []*models.PullReview{
|
||||
{State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}},
|
||||
{State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}},
|
||||
{State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}},
|
||||
},
|
||||
reviewers: []string{"user1", "user2"},
|
||||
isApproved: false,
|
||||
isPendingByTest1: true,
|
||||
isPendingByTest1: false,
|
||||
isReviewedByTest1: false,
|
||||
},
|
||||
{
|
||||
@@ -151,7 +139,7 @@ func TestReviews(t *testing.T) {
|
||||
rf.EXPECT().GetTimeline("test", "pr", int64(1)).Return(test.timeline, nil)
|
||||
rf.EXPECT().GetPullRequestReviews("test", "pr", int64(1)).Return(test.reviews, test.fetchErr)
|
||||
|
||||
reviews, err := common.FetchGiteaReviews(rf, "test", "pr", 1)
|
||||
reviews, err := common.FetchGiteaReviews(rf, test.reviewers, "test", "pr", 1)
|
||||
|
||||
if test.fetchErr != nil {
|
||||
if err != test.fetchErr {
|
||||
@@ -159,7 +147,6 @@ func TestReviews(t *testing.T) {
|
||||
}
|
||||
return
|
||||
}
|
||||
reviews.RequestedReviewers = test.reviewers
|
||||
|
||||
if r := reviews.IsApproved(); r != test.isApproved {
|
||||
t.Fatal("Unexpected IsReviewed():", r, "vs. expected", test.isApproved)
|
||||
|
||||
@@ -40,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
|
||||
}
|
||||
|
||||
110
common/utils.go
110
common/utils.go
@@ -27,87 +27,10 @@ import (
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"src.opensuse.org/autogits/common/gitea-generated/models"
|
||||
)
|
||||
|
||||
type NewRepos struct {
|
||||
Repos []struct {
|
||||
Organization, Repository, Branch string
|
||||
PackageName string
|
||||
}
|
||||
IsMaintainer bool
|
||||
}
|
||||
|
||||
const maintainership_line = "MAINTAINER"
|
||||
|
||||
var true_lines []string = []string{"1", "TRUE", "YES", "OK", "T"}
|
||||
|
||||
func HasSpace(s string) bool {
|
||||
return strings.IndexFunc(s, unicode.IsSpace) >= 0
|
||||
}
|
||||
|
||||
func FindNewReposInIssueBody(body string) *NewRepos {
|
||||
Issues := &NewRepos{}
|
||||
for _, line := range strings.Split(body, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if ul := strings.ToUpper(line); strings.HasPrefix(ul, "MAINTAINER") {
|
||||
value := ""
|
||||
if idx := strings.IndexRune(ul, ':'); idx > 0 && len(ul) > idx+2 {
|
||||
value = ul[idx+1:]
|
||||
} else if idx := strings.IndexRune(ul, ' '); idx > 0 && len(ul) > idx+2 {
|
||||
value = ul[idx+1:]
|
||||
}
|
||||
|
||||
if slices.Contains(true_lines, strings.TrimSpace(value)) {
|
||||
Issues.IsMaintainer = true
|
||||
}
|
||||
}
|
||||
// line = strings.TrimSpace(line)
|
||||
issue := struct{ Organization, Repository, Branch, PackageName string }{}
|
||||
|
||||
branch := strings.Split(line, "#")
|
||||
repo := strings.Split(branch[0], "/")
|
||||
|
||||
if len(branch) == 2 {
|
||||
issue.Branch = strings.TrimSpace(branch[1])
|
||||
}
|
||||
if len(repo) == 2 {
|
||||
issue.Organization = strings.TrimSpace(repo[0])
|
||||
issue.Repository = strings.TrimSpace(repo[1])
|
||||
issue.PackageName = issue.Repository
|
||||
|
||||
if idx := strings.Index(strings.ToUpper(issue.Branch), " AS "); idx > 0 && len(issue.Branch) > idx+5 {
|
||||
issue.PackageName = strings.TrimSpace(issue.Branch[idx+3:])
|
||||
issue.Branch = strings.TrimSpace(issue.Branch[0:idx])
|
||||
}
|
||||
|
||||
if HasSpace(issue.Organization) || HasSpace(issue.Repository) || HasSpace(issue.PackageName) || HasSpace(issue.Branch) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
Issues.Repos = append(Issues.Repos, issue)
|
||||
//PackageNameIdx := strings.Index(strings.ToUpper(line), " AS ")
|
||||
//words := strings.Split(line)
|
||||
}
|
||||
|
||||
if len(Issues.Repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
return Issues
|
||||
}
|
||||
|
||||
func IssueToString(issue *models.Issue) string {
|
||||
if issue == nil {
|
||||
return "(nil)"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s#%d", issue.Repository.Owner, issue.Repository.Name, issue.Index)
|
||||
}
|
||||
|
||||
func SplitLines(str string) []string {
|
||||
return SplitStringNoEmpty(str, "\n")
|
||||
}
|
||||
@@ -245,10 +168,9 @@ func FetchDevelProjects() (DevelProjects, error) {
|
||||
}
|
||||
|
||||
var DevelProjectNotFound = errors.New("Devel project not found")
|
||||
|
||||
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
||||
for _, item := range d {
|
||||
if item.Package == pkg {
|
||||
if item.Package == pkg {
|
||||
return item.Project, nil
|
||||
}
|
||||
}
|
||||
@@ -256,33 +178,3 @@ func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
||||
return "", DevelProjectNotFound
|
||||
}
|
||||
|
||||
var removedBranchNameSuffixes []string = []string{
|
||||
"-rm",
|
||||
"-removed",
|
||||
"-deleted",
|
||||
}
|
||||
|
||||
func findRemovedBranchSuffix(branchName string) string {
|
||||
branchName = strings.ToLower(branchName)
|
||||
|
||||
for _, suffix := range removedBranchNameSuffixes {
|
||||
if len(suffix) < len(branchName) && strings.HasSuffix(branchName, suffix) {
|
||||
return suffix
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func IsRemovedBranch(branchName string) bool {
|
||||
return len(findRemovedBranchSuffix(branchName)) > 0
|
||||
}
|
||||
|
||||
func TrimRemovedBranchSuffix(branchName string) string {
|
||||
suffix := findRemovedBranchSuffix(branchName)
|
||||
if len(suffix) > 0 {
|
||||
return branchName[0 : len(branchName)-len(suffix)]
|
||||
}
|
||||
|
||||
return branchName
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"src.opensuse.org/autogits/common"
|
||||
@@ -166,142 +165,3 @@ func TestRemoteName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemovedBranchName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
branchName string
|
||||
isRemoved bool
|
||||
regularName string
|
||||
}{
|
||||
{
|
||||
name: "Empty branch",
|
||||
},
|
||||
{
|
||||
name: "Removed suffix only",
|
||||
branchName: "-rm",
|
||||
isRemoved: false,
|
||||
regularName: "-rm",
|
||||
},
|
||||
{
|
||||
name: "Capital suffix",
|
||||
branchName: "Foo-Rm",
|
||||
isRemoved: true,
|
||||
regularName: "Foo",
|
||||
},
|
||||
{
|
||||
name: "Other suffixes",
|
||||
isRemoved: true,
|
||||
branchName: "Goo-Rm-DeleteD",
|
||||
regularName: "Goo-Rm",
|
||||
},
|
||||
{
|
||||
name: "Other suffixes",
|
||||
isRemoved: true,
|
||||
branchName: "main-REMOVED",
|
||||
regularName: "main",
|
||||
},
|
||||
{
|
||||
name: "Not removed separator",
|
||||
isRemoved: false,
|
||||
branchName: "main;REMOVED",
|
||||
regularName: "main;REMOVED",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if r := common.IsRemovedBranch(test.branchName); r != test.isRemoved {
|
||||
t.Error("Expecting isRemoved:", test.isRemoved, "but received", r)
|
||||
}
|
||||
|
||||
if tn := common.TrimRemovedBranchSuffix(test.branchName); tn != test.regularName {
|
||||
t.Error("Expected stripped branch name to be:", test.regularName, "but have:", tn)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPackageIssueParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
issues *common.NewRepos
|
||||
}{
|
||||
{
|
||||
name: "Nothing",
|
||||
},
|
||||
{
|
||||
name: "Basic repo",
|
||||
input: "org/repo#branch",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Default branch and junk lines and approval for maintainership",
|
||||
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: yes",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org1", Repository: "repo2", Branch: "", PackageName: "repo2"},
|
||||
},
|
||||
IsMaintainer: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Default branch and junk lines and no maintainership",
|
||||
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: NEVER",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org1", Repository: "repo2", Branch: "", PackageName: "repo2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "3 repos with comments and maintainership",
|
||||
input: "\n\nsome comments for org1/repo2 are here and more\n\norg1/repo2#master\n org2/repo3#master\n some/repo3#m\nMaintainer ok",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org1", Repository: "repo2", Branch: "master", PackageName: "repo2"},
|
||||
{Organization: "org2", Repository: "repo3", Branch: "master", PackageName: "repo3"},
|
||||
{Organization: "some", Repository: "repo3", Branch: "m", PackageName: "repo3"},
|
||||
},
|
||||
IsMaintainer: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid repos with spaces",
|
||||
input: "or g/repo#branch\norg/r epo#branch\norg/repo#br anch\norg/repo#branch As foo ++",
|
||||
},
|
||||
{
|
||||
name: "Valid repos with spaces",
|
||||
input: " org / repo # branch",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Package name is not repo name",
|
||||
input: " org / repo # branch as repo++ \nmaintainer true",
|
||||
issues: &common.NewRepos{
|
||||
Repos: []struct{ Organization, Repository, Branch, PackageName string }{
|
||||
{Organization: "org", Repository: "repo", Branch: "branch", PackageName: "repo++"},
|
||||
},
|
||||
IsMaintainer: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
issue := common.FindNewReposInIssueBody(test.input)
|
||||
if !reflect.DeepEqual(test.issues, issue) {
|
||||
t.Error("Expected", test.issues, "but have", issue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,30 +58,6 @@ sub ListPackages {
|
||||
return @packages;
|
||||
}
|
||||
|
||||
sub FactoryMd5 {
|
||||
my ($package) = @_;
|
||||
my $out = "";
|
||||
|
||||
if (system("osc ls openSUSE:Factory $package | grep -q build.specials.obscpio") == 0) {
|
||||
system("mkdir _extract") == 0 || die "_extract exists or can't make it. Aborting.";
|
||||
chdir("_extract") || die;
|
||||
system("osc cat openSUSE:Factory $package build.specials.obscpio | cpio -dium 2> /dev/null") == 0 || die;
|
||||
system("rm .* 2> /dev/null");
|
||||
open( my $fh, "find -type f -exec /usr/bin/basename {} \\; | xargs md5sum | awk '{print \$1 FS \$2}' | grep -v d41d8cd98f00b204e9800998ecf8427e |") or die;
|
||||
while ( my $l = <$fh>) {
|
||||
$out = $out.$l;
|
||||
}
|
||||
close($fh);
|
||||
chdir("..") && system("rm -rf _extract") == 0 || die;
|
||||
}
|
||||
open( my $fh, "osc ls -v openSUSE:Factory $package | awk '{print \$1 FS \$7}' | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' |") or die;
|
||||
while (my $l = <$fh>) {
|
||||
$out = $out.$l;
|
||||
}
|
||||
close($fh);
|
||||
return $out;
|
||||
}
|
||||
|
||||
# Read project from first argument
|
||||
sub Usage {
|
||||
die "Usage: $0 <OBS Project> [org [package]]";
|
||||
@@ -89,7 +65,6 @@ sub Usage {
|
||||
|
||||
my $project = shift or Usage();
|
||||
my $org = shift;
|
||||
|
||||
if (not defined($org)) {
|
||||
$org = `osc meta prj $project | grep scmsync | sed -e 's,^.*src.opensuse.org/\\(.*\\)/_ObsPrj.*,\\1,'`;
|
||||
chomp($org);
|
||||
@@ -164,7 +139,7 @@ if ( scalar @tomove > 0 ) {
|
||||
system("git -C $pkg push origin factory") == 0 and
|
||||
system("git obs $super_user api -X PATCH --data '{\"default_branch\": \"factory\"}' /repos/pool/$pkg") == 0
|
||||
or die "Error in creating a pool repo";
|
||||
system("for i in \$(git -C $pkg for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin/ | grep -v '\\(^HEAD\$\\|^factory\$\\)'); do git -C $pkg push origin :\$i; done") == 0 or die "failed to cull branches";
|
||||
system("for i in \$(git for-each-ref --format='%(refname:lstrip=3)' refs/remotes/origin/ | grep -v '\\(^HEAD\$\\|^factory\$\)'); do git -C $pkg push origin :\$i; done") == 0 or die "failed to cull branches";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +167,6 @@ for my $package ( sort(@packages) ) {
|
||||
or ( push( @tomove, $package ) and die "Can't fetch pool for $package" );
|
||||
|
||||
my @commits = FindFactoryCommit($package);
|
||||
my $Md5Hashes = FactoryMd5($package);
|
||||
my $c;
|
||||
my $match = 0;
|
||||
for my $commit (@commits) {
|
||||
@@ -205,27 +179,16 @@ for my $package ( sort(@packages) ) {
|
||||
system("git -C $package lfs fetch pool $commit") == 0
|
||||
and system("git -C $package checkout -B factory $commit") == 0
|
||||
and system("git -C $package lfs checkout") == 0
|
||||
and chdir($package)) {
|
||||
|
||||
open(my $fh, "|-", "md5sum -c --quiet") or die $!;
|
||||
print $fh $Md5Hashes;
|
||||
close $fh;
|
||||
if ($? >> 8 != 0) {
|
||||
chdir("..") || die;
|
||||
next;
|
||||
}
|
||||
open($fh, "|-", "awk '{print \$2}' | sort | bash -c \"diff <(ls -1 | sort) -\"") or die $!;
|
||||
print $fh $Md5Hashes;
|
||||
close $fh;
|
||||
my $ec = $? >> 8;
|
||||
chdir("..") || die;
|
||||
|
||||
if ($ec == 0) {
|
||||
$c = $commit;
|
||||
$match = 1;
|
||||
last;
|
||||
}
|
||||
and system(
|
||||
"cd $package; osc ls -v openSUSE:Factory $package | awk '{print \$1 FS \$7}' | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' | md5sum -c --quiet"
|
||||
) == 0
|
||||
and system("bash -c \"diff <(ls -1 $package | sort) <(osc ls openSUSE:Factory $package | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' | sort)\"") == 0
|
||||
)
|
||||
{
|
||||
|
||||
$c = $commit;
|
||||
$match = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
Java:packages
|
||||
Kernel:firmware
|
||||
Kernel:kdump
|
||||
devel:gcc
|
||||
devel:languages:clojure
|
||||
devel:languages:erlang
|
||||
devel:languages:erlang:Factory
|
||||
devel:languages:hare
|
||||
devel:languages:javascript
|
||||
devel:languages:lua
|
||||
devel:languages:nodejs
|
||||
devel:languages:perl
|
||||
devel:languages:python:Factory
|
||||
devel:languages:python:pytest
|
||||
devel:openSUSE:Factory
|
||||
network:chromium
|
||||
network:dhcp
|
||||
network:im:whatsapp
|
||||
network:messaging:xmpp
|
||||
science:HPC
|
||||
server:dns
|
||||
systemsmanagement:cockpit
|
||||
X11:lxde
|
||||
|
||||
@@ -14,11 +14,15 @@ import (
|
||||
"src.opensuse.org/autogits/common"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Context string `json:"context"`
|
||||
State string `json:"state"`
|
||||
TargetUrl string `json:"target_url"`
|
||||
}
|
||||
|
||||
type StatusInput struct {
|
||||
Description string `json:"description"`
|
||||
Context string `json:"context"`
|
||||
State string `json:"state"`
|
||||
TargetUrl string `json:"target_url"`
|
||||
State string `json:"state"`
|
||||
TargetUrl string `json:"target_url"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -55,26 +59,23 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
||||
config, ok := r.Context().Value(configKey).(*Config)
|
||||
|
||||
if !ok {
|
||||
common.LogDebug("Config missing from context")
|
||||
common.LogError("Config missing from context")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
header := r.Header.Get("Authorization")
|
||||
if header == "" {
|
||||
common.LogDebug("Authorization header not found")
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
token_arr := strings.Split(header, " ")
|
||||
if len(token_arr) != 2 {
|
||||
common.LogDebug("Authorization header malformed")
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(token_arr[0], "token") {
|
||||
common.LogDebug("Token not found in Authorization header")
|
||||
if !strings.EqualFold(token_arr[0], "Bearer") {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -82,7 +83,6 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
||||
token := token_arr[1]
|
||||
|
||||
if !slices.Contains(config.Keys, token) {
|
||||
common.LogDebug("Provided token is not known")
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -104,8 +104,13 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status := Status{
|
||||
Context: "Build in obs",
|
||||
State: statusinput.State,
|
||||
TargetUrl: statusinput.TargetUrl,
|
||||
}
|
||||
|
||||
status_payload, err := json.Marshal(statusinput)
|
||||
status_payload, err := json.Marshal(status)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
@@ -126,8 +131,8 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", ForgeToken))
|
||||
req.Header.Add("Content-Type", "Content-Type")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ForgeToken))
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# gitea_status_proxy
|
||||
|
||||
Allows bots without code owner permission to set Gitea's commit status
|
||||
|
||||
## Basic usage
|
||||
|
||||
To beging, you need the json config and a Gitea token with permissions to the repository you want to write to.
|
||||
|
||||
Keys should be randomly generated, i.e by using openssl: `openssl rand -base64 48`
|
||||
|
||||
Generate a json config file, with the key generated from running the command above, save as example.json:
|
||||
|
||||
```
|
||||
{
|
||||
"forge_url": "https://src.opensuse.org/api/v1",
|
||||
"keys": ["$YOUR_TOKEN_GOES_HERE"]
|
||||
}
|
||||
```
|
||||
|
||||
### start the proxy:
|
||||
|
||||
```
|
||||
GITEA_TOKEN=YOURTOKEN ./gitea_status_proxy -config example.json
|
||||
2025/10/30 12:53:18 [I] server up and listening on :3000
|
||||
```
|
||||
|
||||
Now the proxy should be able to accept requests under: `localhost:3000/repos/{owner}/{repo}/statuses/{sha}`, the token to be used when authenticating to the proxy must be in the `keys` list of the configuration json file (example.json above)
|
||||
|
||||
### example:
|
||||
|
||||
On a separate terminal, you can use curl to post a status to the proxy, if the GITEA_TOKEN has permissions on the target
|
||||
repository, it will result in a new status being set for the given commit
|
||||
|
||||
```
|
||||
curl -X 'POST' \
|
||||
'localhost:3000/repos/szarate/test-actions-gitea/statuses/cd5847c92fb65a628bdd6015f96ee7e569e1ad6e4fc487acc149b52e788262f9' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Authorization: token $YOUR_TOKEN_GOES_HERE' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"context": "Proxy test",
|
||||
"description": "Status posted from the proxy",
|
||||
"state": "success",
|
||||
"target_url": "https://src.opensuse.org"
|
||||
}'
|
||||
```
|
||||
|
||||
After this you should be able to the results in the pull request, e.g from above: https://src.opensuse.org/szarate/test-actions-gitea/pulls/1
|
||||
@@ -149,7 +149,7 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
}
|
||||
}()
|
||||
|
||||
rx := regexp.MustCompile(`^/?api/v\d+/repos/(?<org>[_\.a-zA-Z0-9-]+)/(?<project>[_\.a-zA-Z0-9-]+)/(?:issues|pulls)/(?<num>[0-9]+)$`)
|
||||
rx := regexp.MustCompile(`^/?api/v\d+/repos/(?<org>[_a-zA-Z0-9-]+)/(?<project>[_a-zA-Z0-9-]+)/(?:issues|pulls)/(?<num>[0-9]+)$`)
|
||||
subject := notification.Subject
|
||||
u, err := url.Parse(notification.Subject.URL)
|
||||
if err != nil {
|
||||
|
||||
@@ -386,28 +386,6 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
||||
}
|
||||
// patch baseMeta to become the new project
|
||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
||||
// freeze tag for now
|
||||
if len(templateMeta.ScmSync) > 0 {
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
common.LogDebug("getting data for ", repository.EscapedPath())
|
||||
split := strings.Split(repository.EscapedPath(), "/")
|
||||
org, repo := split[1], split[2]
|
||||
|
||||
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
|
||||
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// set expanded commit url
|
||||
repository.Fragment = branch.SHA
|
||||
templateMeta.ScmSync = repository.String()
|
||||
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
|
||||
}
|
||||
// Cleanup ReleaseTarget and modify affected path entries
|
||||
for idx, r := range templateMeta.Repositories {
|
||||
templateMeta.Repositories[idx].ReleaseTargets = nil
|
||||
@@ -1066,7 +1044,6 @@ func main() {
|
||||
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
|
||||
}
|
||||
|
||||
common.LogDebug("OBS Gitea Host:", GiteaUrl)
|
||||
common.LogDebug("OBS Web Host:", ObsWebHost)
|
||||
common.LogDebug("OBS API Host:", *obsApiHost)
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
OBS Status Service
|
||||
==================
|
||||
|
||||
Reports build status of OBS service as an easily to produce SVG. Repository
|
||||
results (build results) are cached for 10 seconds and repository listing
|
||||
for OBS instance are cached for 5 minutes -- new repositories take up to
|
||||
5 minutes to be visible.
|
||||
Reports build status of OBS service as an easily to produce SVG
|
||||
|
||||
Requests for individual build results:
|
||||
|
||||
@@ -20,31 +17,19 @@ Get requests for / will also return 404 statu normally. If the Backend redis
|
||||
server is not available, it will return 500
|
||||
|
||||
|
||||
By default, SVG output is generated, suitable for inclusion. But JSON and XML
|
||||
output is possible by setting `Accept:` request header
|
||||
|
||||
| Accept Request Header | Output format
|
||||
|------------------------|---------------------
|
||||
| | SVG image
|
||||
| application/json | JSON data
|
||||
| application/obs+xml | XML output
|
||||
|
||||
|
||||
Areas of Responsibility
|
||||
-----------------------
|
||||
|
||||
* Fetch and cache internal data from OBS and present it in usable format:
|
||||
+ Generate SVG output for specific OBS project or package
|
||||
+ Generate JSON/XML output for automated processing
|
||||
* Low-overhead
|
||||
* Monitors RabbitMQ interface for notification of OBS package and project status
|
||||
* Produces SVG output based on GET request
|
||||
* Cache results (sqlite) and periodically update results from OBS (in case of messages are missing)
|
||||
|
||||
|
||||
Target Usage
|
||||
------------
|
||||
|
||||
* inside README.md of package git or project git
|
||||
* README.md of package git or project git
|
||||
* comment section of a Gitea PR
|
||||
* automated build result processing
|
||||
|
||||
Running
|
||||
-------
|
||||
@@ -57,4 +42,3 @@ Default parameters can be changed by env variables
|
||||
| `OBS_STATUS_SERVICE_LISTEN` | [::1]:8080 | Listening address and port
|
||||
| `OBS_STATUS_SERVICE_CERT` | /run/obs-status-service.pem | Location of certificate file for service
|
||||
| `OBS_STATUS_SERVICE_KEY` | /run/obs-status-service.pem | Location of key file for service
|
||||
| `REDIS` | | OBS's Redis instance URL
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -85,36 +82,3 @@ func TestStatusSvg(t *testing.T) {
|
||||
os.WriteFile("testpackage.svg", PackageStatusSummarySvg("pkg2", data), 0o777)
|
||||
os.WriteFile("testproject.svg", ProjectStatusSummarySvg(data), 0o777)
|
||||
}
|
||||
|
||||
func TestFactoryResults(t *testing.T) {
|
||||
data, err := os.Open("factory.results.json.bz2")
|
||||
if err != nil {
|
||||
t.Fatal("Openning factory.results.json.bz2 failed:", err)
|
||||
}
|
||||
UncompressedData, err := io.ReadAll(bzip2.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatal("Reading factory.results.json.bz2 failed:", err)
|
||||
}
|
||||
|
||||
var results []*common.BuildResult
|
||||
if err := json.Unmarshal(UncompressedData, &results); err != nil {
|
||||
t.Fatal("Failed parsing test data", err)
|
||||
}
|
||||
|
||||
// add tests here
|
||||
tests := []struct {
|
||||
name string
|
||||
}{
|
||||
// add test data here
|
||||
{
|
||||
name: "First test",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// and test code here
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"html"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SvgWriter struct {
|
||||
@@ -134,7 +133,7 @@ func (svg *SvgWriter) WritePackageStatus(loglink, arch, status, detail string) {
|
||||
}
|
||||
|
||||
func (svg *SvgWriter) WriteProjectStatus(project, repo, arch, status string, count int) {
|
||||
u, err := url.Parse(*ObsUrl + "/project/monitor/" + url.PathEscape(project) + "?defaults=0&" + url.QueryEscape(status) + "=1&arch_" + url.QueryEscape(arch) + "=1&repo_" + url.QueryEscape(strings.ReplaceAll(repo, ".", "_")) + "=1")
|
||||
u, err := url.Parse(*ObsUrl + "/project/monitor/" + url.PathEscape(project) + "?defaults=0&" + url.QueryEscape(status) + "=1&arch_" + url.QueryEscape(arch) + "=1&repo_" + url.QueryEscape(repo) + "=1")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[Unit]
|
||||
Description=WorkflowDirect git bot for %i
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
ExecStart=/usr/bin/workflow-direct
|
||||
EnvironmentFile=-/etc/default/%i/workflow-direct.env
|
||||
DynamicUser=yes
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
RuntimeDirectory=%i
|
||||
# SLES 15 doesn't have HOME set for dynamic users, so we improvise
|
||||
BindReadOnlyPaths=/etc/default/%i/known_hosts:/etc/ssh/ssh_known_hosts /etc/default/%i/config.json:%t/%i/config.json /etc/default/%i/id_ed25519 /etc/default/%i/id_ed25519.pub
|
||||
WorkingDirectory=%t/%i
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ Areas of responsibility
|
||||
* on repository adds, creates a new submodule (if non empty)
|
||||
* on repository removal, removes the submodule
|
||||
|
||||
NOTE: reverts (push HEAD^) are not supported as they would step-on the
|
||||
work of the workflow-pr bot. Manual update of the project git is
|
||||
required in this case.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
@@ -23,20 +26,6 @@ Uses `workflow.config` for configuration. Parameters
|
||||
|
||||
NOTE: `-rm`, `-removed`, `-deleted` are all removed suffixes used to indicate current branch is a placeholder for previously existing package. These branches will be ignored by the bot, and if default, the package will be removed and will not be added to the project.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
* `GITEA_TOKEN` (required)
|
||||
* `AMQP_USERNAME` (required)
|
||||
* `AMQP_PASSWORD` (required)
|
||||
* `AUTOGITS_CONFIG` (required)
|
||||
* `AUTOGITS_URL` - default: https://src.opensuse.org
|
||||
* `AUTOGITS_RABBITURL` - default: amqps://rabbit.opensuse.org
|
||||
* `AUTOGITS_DEBUG` - disabled by default, set to any value to enable
|
||||
* `AUTOGITS_CHECK_ON_START` - disabled by default, set to any value to enable
|
||||
* `AUTOGITS_REPO_PATH` - default is temporary directory
|
||||
* `AUTOGITS_IDENTITY_FILE` - in case where we need explicit identify path for ssh specified
|
||||
|
||||
Target Usage
|
||||
------------
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -39,7 +40,7 @@ import (
|
||||
const (
|
||||
AppName = "direct_workflow"
|
||||
GitAuthor = "AutoGits prjgit-updater"
|
||||
GitEmail = "autogits-direct@noreply@src.opensuse.org"
|
||||
GitEmail = "adam+autogits-direct@zombino.com"
|
||||
)
|
||||
|
||||
var configuredRepos map[string][]*common.AutogitConfig
|
||||
@@ -52,6 +53,18 @@ func isConfiguredOrg(org *common.Organization) bool {
|
||||
return found
|
||||
}
|
||||
|
||||
func concatenateErrors(err1, err2 error) error {
|
||||
if err1 == nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
if err2 == nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w\n%w", err1, err2)
|
||||
}
|
||||
|
||||
type RepositoryActionProcessor struct{}
|
||||
|
||||
func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
||||
@@ -59,90 +72,69 @@ func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
||||
configs, configFound := configuredRepos[action.Organization.Username]
|
||||
|
||||
if !configFound {
|
||||
common.LogInfo("Repository event for", action.Organization.Username, ". Not configured. Ignoring.", action.Organization.Username)
|
||||
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Organization.Username)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name {
|
||||
common.LogError("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
||||
log.Println("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, config := range configs {
|
||||
processConfiguredRepositoryAction(action, config)
|
||||
err = concatenateErrors(err, processConfiguredRepositoryAction(action, config))
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) {
|
||||
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) error {
|
||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||
git, err := gh.CreateGitHandler(config.Organization)
|
||||
common.PanicOnError(err)
|
||||
defer git.Close()
|
||||
|
||||
configBranch := config.Branch
|
||||
if len(configBranch) == 0 {
|
||||
configBranch = action.Repository.Default_Branch
|
||||
if common.IsRemovedBranch(configBranch) {
|
||||
common.LogDebug(" - default branch has deleted suffix. Skipping")
|
||||
return
|
||||
}
|
||||
|
||||
if len(configBranch) == 0 {
|
||||
common.LogDebug("Empty default branch in message. Maybe race-condition?")
|
||||
repo, err := gitea.GetRepository(action.Repository.Owner.Username, action.Repository.Name)
|
||||
if err != nil {
|
||||
common.LogError("Failed to fetch repository we have an event for?", action.Repository.Owner.Username, action.Repository.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if len(repo.DefaultBranch) == 0 {
|
||||
common.LogError("Default branch is somehow empty. We cannot do anything.")
|
||||
return
|
||||
}
|
||||
configBranch = repo.DefaultBranch
|
||||
}
|
||||
if len(config.Branch) == 0 {
|
||||
config.Branch = action.Repository.Default_Branch
|
||||
}
|
||||
|
||||
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||
if err != nil {
|
||||
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, gitBranch, err)
|
||||
return
|
||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%s err: %w", gitOrg, gitPrj, gitBranch, err)
|
||||
}
|
||||
|
||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
|
||||
switch action.Action {
|
||||
case "created":
|
||||
if action.Repository.Object_Format_Name != "sha256" {
|
||||
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
|
||||
return
|
||||
return fmt.Errorf(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name)
|
||||
}
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||
|
||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
||||
if branch != configBranch {
|
||||
if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", configBranch+":"+configBranch); err != nil {
|
||||
common.LogError("error fetching branch", configBranch, ". ignoring as non-existent.", err) // no branch? so ignore repo here
|
||||
return
|
||||
if branch != config.Branch {
|
||||
if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil {
|
||||
return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here
|
||||
}
|
||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", configBranch))
|
||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", config.Branch))
|
||||
}
|
||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Auto-inclusion "+action.Repository.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package inclusion via Direct Workflow"))
|
||||
if !noop {
|
||||
common.PanicOnError(git.GitExec(gitPrj, "push"))
|
||||
}
|
||||
|
||||
case "deleted":
|
||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
||||
common.LogDebug("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
||||
return
|
||||
if DebugMode {
|
||||
log.Println("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow"))
|
||||
@@ -151,9 +143,10 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
||||
}
|
||||
|
||||
default:
|
||||
common.LogError("Unknown action type:", action.Action)
|
||||
return
|
||||
return fmt.Errorf("%s: %s", "Unknown action type", action.Action)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushActionProcessor struct{}
|
||||
@@ -163,83 +156,77 @@ func (*PushActionProcessor) ProcessFunc(request *common.Request) error {
|
||||
configs, configFound := configuredRepos[action.Repository.Owner.Username]
|
||||
|
||||
if !configFound {
|
||||
common.LogDebug("Repository event for", action.Repository.Owner.Username, ". Not configured. Ignoring.")
|
||||
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Repository.Owner.Username)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name {
|
||||
common.LogInfo("+ ignoring push to PrjGit repository", config.GitProjectName)
|
||||
log.Println("+ ignoring push to PrjGit repository", config.GitProjectName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, config := range configs {
|
||||
processConfiguredPushAction(action, config)
|
||||
err = concatenateErrors(err, processConfiguredPushAction(action, config))
|
||||
}
|
||||
return nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) {
|
||||
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) error {
|
||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||
git, err := gh.CreateGitHandler(config.Organization)
|
||||
common.PanicOnError(err)
|
||||
defer git.Close()
|
||||
|
||||
common.LogDebug("push to:", action.Repository.Owner.Username, action.Repository.Name, "for:", gitOrg, gitPrj, gitBranch)
|
||||
branch := config.Branch
|
||||
if len(branch) == 0 {
|
||||
if common.IsRemovedBranch(branch) {
|
||||
common.LogDebug(" + default branch has removed suffix:", branch, "Skipping.")
|
||||
return
|
||||
}
|
||||
branch = action.Repository.Default_Branch
|
||||
common.LogDebug(" + using default branch", branch)
|
||||
log.Printf("push to: %s/%s for %s/%s#%s", action.Repository.Owner.Username, action.Repository.Name, gitOrg, gitPrj, gitBranch)
|
||||
if len(config.Branch) == 0 {
|
||||
config.Branch = action.Repository.Default_Branch
|
||||
log.Println(" + default branch", action.Repository.Default_Branch)
|
||||
}
|
||||
|
||||
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||
if err != nil {
|
||||
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, err)
|
||||
return
|
||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s err: %w", gitOrg, gitPrj, err)
|
||||
}
|
||||
|
||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
headCommitId, err := git.GitRemoteHead(gitPrj, remoteName, gitBranch)
|
||||
common.PanicOnError(err)
|
||||
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
|
||||
for ok && action.Head_Commit.Id == commit {
|
||||
common.LogDebug(" -- nothing to do, commit already in ProjectGit")
|
||||
return
|
||||
log.Println(" -- nothing to do, commit already in ProjectGit")
|
||||
return nil
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
|
||||
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
|
||||
} else if !stat.IsDir() {
|
||||
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
|
||||
return
|
||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
||||
if DebugMode {
|
||||
log.Println("Pushed to package that is not part of the project. Ignoring:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--force", "--depth", "1", "--checkout", action.Repository.Name)
|
||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name)
|
||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||
|
||||
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", "origin", branch+":"+branch); err != nil {
|
||||
common.LogError("Error fetching branch:", branch, "Ignoring as non-existent.", err)
|
||||
return
|
||||
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, config.Branch+":"+config.Branch); err != nil {
|
||||
return fmt.Errorf("error fetching branch %s. ignoring as non-existent. err: %w", config.Branch, err) // no branch? so ignore repo here
|
||||
}
|
||||
id, err := git.GitBranchHead(filepath.Join(gitPrj, action.Repository.Name), branch)
|
||||
id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, config.Branch)
|
||||
common.PanicOnError(err)
|
||||
if action.Head_Commit.Id == id {
|
||||
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
|
||||
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", fmt.Sprintf("'%s' update via Direct Workflow", action.Repository.Name))
|
||||
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow")
|
||||
if !noop {
|
||||
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
common.LogDebug("push of refs not on the configured branch", branch, ". ignoring.")
|
||||
log.Println("push of refs not on the configured branch", config.Branch, ". ignoring.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) {
|
||||
@@ -261,65 +248,51 @@ func verifyProjectState(git common.Git, org string, config *common.AutogitConfig
|
||||
|
||||
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
|
||||
common.PanicOnError(err)
|
||||
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
||||
|
||||
common.LogDebug(" * Getting submodule list")
|
||||
log.Println(" * Getting submodule list")
|
||||
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
|
||||
common.PanicOnError(err)
|
||||
|
||||
common.LogDebug(" * Getting package links")
|
||||
log.Println(" * Getting package links")
|
||||
var pkgLinks []*PackageRebaseLink
|
||||
if f, err := fs.Stat(os.DirFS(path.Join(git.GetPath(), gitPrj)), common.PrjLinksFile); err == nil && (f.Mode()&fs.ModeType == 0) && f.Size() < 1000000 {
|
||||
if data, err := os.ReadFile(path.Join(git.GetPath(), gitPrj, common.PrjLinksFile)); err == nil {
|
||||
pkgLinks, err = parseProjectLinks(data)
|
||||
if err != nil {
|
||||
common.LogError("Cannot parse project links file:", err.Error())
|
||||
log.Println("Cannot parse project links file:", err.Error())
|
||||
pkgLinks = nil
|
||||
} else {
|
||||
ResolveLinks(org, pkgLinks, gitea)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
common.LogInfo(" - No package links defined")
|
||||
log.Println(" - No package links defined")
|
||||
}
|
||||
|
||||
/* Check existing submodule that they are updated */
|
||||
|
||||
isGitUpdated := false
|
||||
next_package:
|
||||
for filename, commitId := range sub {
|
||||
// ignore project gits
|
||||
//for _, c := range configs {
|
||||
if gitPrj == filename {
|
||||
common.LogDebug(" prjgit as package? ignoring project git:", filename)
|
||||
log.Println(" prjgit as package? ignoring project git:", filename)
|
||||
continue next_package
|
||||
}
|
||||
//}
|
||||
|
||||
branch := config.Branch
|
||||
common.LogDebug(" verifying package:", commitId, "->", filename, "@", branch)
|
||||
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
|
||||
common.LogDebug(" repository removed...")
|
||||
git.GitExecOrPanic(gitPrj, "rm", filename)
|
||||
isGitUpdated = true
|
||||
continue
|
||||
} else if err != nil {
|
||||
common.LogError("failed fetching repo data", org, filename, err)
|
||||
continue
|
||||
} else if len(branch) == 0 {
|
||||
branch = repo.DefaultBranch
|
||||
common.LogDebug(" -> using default branch", branch)
|
||||
if common.IsRemovedBranch(branch) {
|
||||
common.LogDebug(" Default branch for", filename, "is excluded")
|
||||
log.Printf(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch)
|
||||
commits, err := gitea.GetRecentCommits(org, filename, config.Branch, 10)
|
||||
if len(commits) == 0 {
|
||||
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
|
||||
git.GitExecOrPanic(gitPrj, "rm", filename)
|
||||
isGitUpdated = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
commits, err := gitea.GetRecentCommits(org, filename, branch, 10)
|
||||
if err != nil {
|
||||
common.LogDebug(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
||||
log.Println(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -336,7 +309,7 @@ next_package:
|
||||
if l.Pkg == filename {
|
||||
link = l
|
||||
|
||||
common.LogDebug(" -> linked package")
|
||||
log.Println(" -> linked package")
|
||||
// so, we need to rebase here. Can't really optimize, so clone entire package tree and remote
|
||||
pkgPath := path.Join(gitPrj, filename)
|
||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename)
|
||||
@@ -350,7 +323,7 @@ next_package:
|
||||
nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n"))
|
||||
if nCommits > 0 {
|
||||
if !noop {
|
||||
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+branch)
|
||||
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+config.Branch)
|
||||
}
|
||||
isGitUpdated = true
|
||||
}
|
||||
@@ -367,27 +340,42 @@ next_package:
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", filename))
|
||||
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "fetch", "--depth", "1", "origin", commits[0].SHA))
|
||||
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA))
|
||||
common.LogDebug(" -> updated to", commits[0].SHA)
|
||||
log.Println(" -> updated to", commits[0].SHA)
|
||||
isGitUpdated = true
|
||||
} else {
|
||||
// probably need `merge-base` or `rev-list` here instead, or the project updated already
|
||||
common.LogInfo(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
||||
log.Println(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find all missing repositories, and add them
|
||||
common.LogDebug("checking for missing repositories...")
|
||||
if DebugMode {
|
||||
log.Println("checking for missing repositories...")
|
||||
}
|
||||
repos, err := gitea.GetOrganizationRepositories(org)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
common.LogDebug(" nRepos:", len(repos))
|
||||
if DebugMode {
|
||||
log.Println(" nRepos:", len(repos))
|
||||
}
|
||||
|
||||
/* Check repositories in org to make sure they are included in project git */
|
||||
next_repo:
|
||||
for _, r := range repos {
|
||||
if DebugMode {
|
||||
log.Println(" -- checking", r.Name)
|
||||
}
|
||||
|
||||
if r.ObjectFormatName != "sha256" {
|
||||
if DebugMode {
|
||||
log.Println(" + ", r.ObjectFormatName, ". Needs to be sha256. Ignoring")
|
||||
}
|
||||
continue next_repo
|
||||
}
|
||||
|
||||
// for _, c := range configs {
|
||||
if gitPrj == r.Name {
|
||||
// ignore project gits
|
||||
@@ -402,45 +390,43 @@ next_repo:
|
||||
}
|
||||
}
|
||||
|
||||
common.LogDebug(" -- checking repository:", r.Name)
|
||||
|
||||
branch := config.Branch
|
||||
if len(branch) == 0 {
|
||||
branch = r.DefaultBranch
|
||||
if common.IsRemovedBranch(branch) {
|
||||
continue
|
||||
}
|
||||
if DebugMode {
|
||||
log.Println(" -- checking repository:", r.Name)
|
||||
}
|
||||
if commits, err := gitea.GetRecentCommits(org, r.Name, branch, 1); err != nil || len(commits) == 0 {
|
||||
|
||||
if _, err := gitea.GetRecentCommits(org, r.Name, config.Branch, 1); err != nil {
|
||||
// assumption that package does not exist, so not part of project
|
||||
// https://github.com/go-gitea/gitea/issues/31976
|
||||
|
||||
// or, we do not have commits here
|
||||
continue
|
||||
}
|
||||
|
||||
// add repository to git project
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name))
|
||||
|
||||
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||
if branch != curBranch {
|
||||
if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", branch+":"+branch); err != nil {
|
||||
return fmt.Errorf("Fetching branch %s for %s/%s failed. Ignoring.", branch, repo.Owner.UserName, r.Name)
|
||||
if len(config.Branch) > 0 {
|
||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||
if branch != config.Branch {
|
||||
if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil {
|
||||
return fmt.Errorf("Fetching branch %s for %s/%s failed. Ignoring.", config.Branch, repo.Owner.UserName, r.Name)
|
||||
}
|
||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, r.Name), "checkout", config.Branch))
|
||||
}
|
||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, r.Name), "checkout", branch))
|
||||
}
|
||||
|
||||
isGitUpdated = true
|
||||
}
|
||||
|
||||
if isGitUpdated {
|
||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Periodic SYNC in Direct Workflow"))
|
||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow -- SYNC"))
|
||||
if !noop {
|
||||
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
||||
if DebugMode {
|
||||
log.Println("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -451,17 +437,17 @@ var checkInterval time.Duration
|
||||
func checkOrg(org string, configs []*common.AutogitConfig) {
|
||||
git, err := gh.CreateGitHandler(org)
|
||||
if err != nil {
|
||||
common.LogError("Failed to allocate GitHandler:", err)
|
||||
log.Println("Faield to allocate GitHandler:", err)
|
||||
return
|
||||
}
|
||||
defer git.Close()
|
||||
|
||||
for _, config := range configs {
|
||||
common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName)
|
||||
log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
||||
if err := verifyProjectState(git, org, config, configs); err != nil {
|
||||
common.LogError(" *** verification failed, org:", org, err)
|
||||
log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err)
|
||||
} else {
|
||||
common.LogError(" ++ verification complete, org:", org, config.GitProjectName)
|
||||
log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,7 +456,7 @@ func checkRepos() {
|
||||
for org, configs := range configuredRepos {
|
||||
if checkInterval > 0 {
|
||||
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval)))
|
||||
common.LogInfo(" - sleep interval", sleepInterval, "until next check")
|
||||
log.Println(" - sleep interval", sleepInterval, "until next check")
|
||||
time.Sleep(sleepInterval)
|
||||
}
|
||||
|
||||
@@ -482,9 +468,9 @@ func consistencyCheckProcess() {
|
||||
if checkOnStart {
|
||||
savedCheckInterval := checkInterval
|
||||
checkInterval = 0
|
||||
common.LogInfo("== Startup consistency check begin...")
|
||||
log.Println("== Startup consistency check begin...")
|
||||
checkRepos()
|
||||
common.LogInfo("== Startup consistency check done...")
|
||||
log.Println("== Startup consistency check done...")
|
||||
checkInterval = savedCheckInterval
|
||||
}
|
||||
|
||||
@@ -499,8 +485,7 @@ var gh common.GitHandlerGenerator
|
||||
func updateConfiguration(configFilename string, orgs *[]string) {
|
||||
configFile, err := common.ReadConfigFile(configFilename)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
os.Exit(4)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
configs, _ := common.ResolveWorkflowConfigs(gitea, configFile)
|
||||
@@ -508,7 +493,9 @@ func updateConfiguration(configFilename string, orgs *[]string) {
|
||||
*orgs = make([]string, 0, 1)
|
||||
for _, c := range configs {
|
||||
if slices.Contains(c.Workflows, "direct") {
|
||||
common.LogDebug(" + adding org:", c.Organization, ", branch:", c.Branch, ", prjgit:", c.GitProjectName)
|
||||
if DebugMode {
|
||||
log.Printf(" + adding org: '%s', branch: '%s', prjgit: '%s'\n", c.Organization, c.Branch, c.GitProjectName)
|
||||
}
|
||||
configs := configuredRepos[c.Organization]
|
||||
if configs == nil {
|
||||
configs = make([]*common.AutogitConfig, 0, 1)
|
||||
@@ -522,7 +509,7 @@ func updateConfiguration(configFilename string, orgs *[]string) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
configFilename := flag.String("config", "config.json", "List of PrjGit")
|
||||
configFilename := flag.String("config", "", "List of PrjGit")
|
||||
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
|
||||
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
|
||||
flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
|
||||
@@ -533,35 +520,10 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
if err := common.RequireGiteaSecretToken(); err != nil {
|
||||
common.LogError(err)
|
||||
os.Exit(1)
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := common.RequireRabbitSecrets(); err != nil {
|
||||
common.LogError(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if cf := os.Getenv("AUTOGITS_CONFIG"); len(cf) > 0 {
|
||||
*configFilename = cf
|
||||
}
|
||||
if url := os.Getenv("AUTOGITS_URL"); len(url) > 0 {
|
||||
*giteaUrl = url
|
||||
}
|
||||
if url := os.Getenv("AUTOGITS_RABBITURL"); len(url) > 0 {
|
||||
*rabbitUrl = url
|
||||
}
|
||||
if debug := os.Getenv("AUTOGITS_DEBUG"); len(debug) > 0 {
|
||||
DebugMode = true
|
||||
}
|
||||
if check := os.Getenv("AUTOGITS_CHECK_ON_START"); len(check) > 0 {
|
||||
checkOnStart = true
|
||||
}
|
||||
if p := os.Getenv("AUTOGITS_REPO_PATH"); len(p) > 0 {
|
||||
*basePath = p
|
||||
}
|
||||
|
||||
if DebugMode {
|
||||
common.SetLoggingLevel(common.LogLevelDebug)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defs := &common.RabbitMQGiteaEventsProcessor{}
|
||||
@@ -570,14 +532,12 @@ func main() {
|
||||
if len(*basePath) == 0 {
|
||||
*basePath, err = os.MkdirTemp(os.TempDir(), AppName)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
os.Exit(1)
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
os.Exit(1)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// handle reconfiguration
|
||||
@@ -592,10 +552,10 @@ func main() {
|
||||
}
|
||||
|
||||
if sig != syscall.SIGHUP {
|
||||
common.LogError("Unexpected signal received:", sig)
|
||||
log.Println("Unexpected signal received:", sig)
|
||||
continue
|
||||
}
|
||||
common.LogError("*** Reconfiguring ***")
|
||||
log.Println("*** Reconfiguring ***")
|
||||
updateConfiguration(*configFilename, &defs.Orgs)
|
||||
defs.Connection().UpdateTopics(defs)
|
||||
}
|
||||
@@ -607,25 +567,23 @@ func main() {
|
||||
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
||||
CurrentUser, err := gitea.GetCurrentUser()
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch current user:", err)
|
||||
os.Exit(2)
|
||||
log.Fatalln("Cannot fetch current user:", err)
|
||||
}
|
||||
common.LogInfo("Current User:", CurrentUser.UserName)
|
||||
log.Println("Current User:", CurrentUser.UserName)
|
||||
|
||||
updateConfiguration(*configFilename, &defs.Orgs)
|
||||
|
||||
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
||||
if err != nil {
|
||||
common.LogError("cannot parse server URL. Err:", err)
|
||||
os.Exit(3)
|
||||
log.Panicf("cannot parse server URL. Err: %#v\n", err)
|
||||
}
|
||||
|
||||
go consistencyCheckProcess()
|
||||
common.LogInfo("defs:", *defs)
|
||||
log.Println("defs:", *defs)
|
||||
|
||||
defs.Handlers = make(map[string]common.RequestProcessor)
|
||||
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
||||
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
||||
|
||||
common.LogError(common.ProcessRabbitMQEvents(defs))
|
||||
log.Fatal(common.ProcessRabbitMQEvents(defs))
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ JSON
|
||||
* _ReviewRequired_: (true, false) ignores that submitter is a maintainer and require a review from other maintainer IFF available
|
||||
* _NoProjectGitPR_: (true, false) do not create PrjGit PRs, but still process reviews, etc.
|
||||
* _Permissions_: permissions and associated accounts/groups. See below.
|
||||
* _Labels_: (string, string) Labels for PRs. See below.
|
||||
|
||||
NOTE: `-rm`, `-removed`, `-deleted` are all removed suffixes used to indicate current branch is a placeholder for previously existing package. These branches will be ignored by the bot, and if default, the package will be removed and will not be added to the project.
|
||||
example:
|
||||
@@ -75,18 +74,6 @@ results in
|
||||
* moo -> package and project reviews, but ignored
|
||||
|
||||
|
||||
Labels
|
||||
------
|
||||
|
||||
The following labels are used, when defined in Repo/Org.
|
||||
|
||||
| Label Config Entry | Default label | Description
|
||||
|--------------------|----------------|----------------------------------------
|
||||
| StagingAuto | staging/Auto | Assigned to Project Git PRs when first staged
|
||||
| ReviewPending | review/Pending | Assigned to PR when reviews are still pending
|
||||
| ReviewDone | review/Done | Assigned to PR when reviews are complete on this particular PR
|
||||
|
||||
|
||||
Maintainership
|
||||
--------------
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import "src.opensuse.org/autogits/common"
|
||||
|
||||
type StateChecker interface {
|
||||
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
|
||||
CheckRepos()
|
||||
CheckRepos() error
|
||||
ConsistencyCheckProcess() error
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ func main() {
|
||||
common.RequestType_PRSync: req,
|
||||
common.RequestType_PRReviewAccepted: req,
|
||||
common.RequestType_PRReviewRejected: req,
|
||||
common.RequestType_PRComment: req,
|
||||
common.RequestType_IssueComment: req,
|
||||
},
|
||||
}
|
||||
listenDefs.Connection().RabbitURL, _ = url.Parse(*rabbitUrl)
|
||||
|
||||
@@ -41,9 +41,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
|
||||
slices.Sort(title_refs)
|
||||
slices.Sort(refs)
|
||||
|
||||
title = "Forwarded PRs: " + strings.Join(title_refs, ", ")
|
||||
desc = fmt.Sprintf("This is a forwarded pull request by %s\nreferencing the following pull request(s):\n\n", GitAuthor) + strings.Join(refs, "\n") + "\n"
|
||||
|
||||
@@ -230,18 +227,12 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
|
||||
}
|
||||
|
||||
title, desc := PrjGitDescription(prset)
|
||||
pr, err, isNew := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
|
||||
pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
|
||||
if err != nil {
|
||||
common.LogError("Error creating PrjGit PR:", err)
|
||||
return err
|
||||
}
|
||||
org := PrjGit.Owner.UserName
|
||||
repo := PrjGit.Name
|
||||
idx := pr.Index
|
||||
if isNew {
|
||||
Gitea.SetLabels(org, repo, idx, []string{prset.Config.Label(common.Label_StagingAuto)})
|
||||
}
|
||||
Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, pr.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
})
|
||||
|
||||
@@ -278,7 +269,6 @@ func (pr *PRProcessor) RebaseAndSkipSubmoduleCommits(prset *common.PRSet, branch
|
||||
}
|
||||
|
||||
var updatePrjGitError_requeue error = errors.New("Commits do not match. Requeing after 5 seconds.")
|
||||
|
||||
func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
_, _, PrjGitBranch := prset.Config.GetPrjGit()
|
||||
PrjGitPR, err := prset.GetPrjGitPR()
|
||||
@@ -355,20 +345,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
// update PR
|
||||
isPrTitleSame := func(CurrentTitle, NewTitle string) bool {
|
||||
ctlen := len(CurrentTitle)
|
||||
for _, suffix := range []string{"...", "…"} {
|
||||
slen := len(suffix)
|
||||
if ctlen > 250 && strings.HasSuffix(CurrentTitle, suffix) && len(NewTitle) > ctlen {
|
||||
NewTitle = NewTitle[0:ctlen-slen] + suffix
|
||||
if CurrentTitle == NewTitle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return CurrentTitle == NewTitle
|
||||
}
|
||||
if PrjGitPR.PR.User.UserName == CurrentUser.UserName && (PrjGitPR.PR.Body != PrjGitBody || !isPrTitleSame(PrjGitPR.PR.Title, PrjGitTitle)) {
|
||||
if PrjGitPR.PR.Body != PrjGitBody || PrjGitPR.PR.Title != PrjGitTitle {
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
Title: PrjGitTitle,
|
||||
@@ -404,10 +381,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||
prjGitPR, err := prset.GetPrjGitPR()
|
||||
if err == common.PRSet_PrjGitMissing {
|
||||
if req.State != "open" {
|
||||
common.LogDebug("This PR is closed and no ProjectGit PR. Ignoring.")
|
||||
return nil
|
||||
}
|
||||
common.LogDebug("Missing PrjGit. Need to create one under branch", prjGitPRbranch)
|
||||
|
||||
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
||||
@@ -554,14 +527,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// update prset if we should build it or not
|
||||
if prjGitPR != nil {
|
||||
if file, err := git.GitCatFile(common.DefaultGitPrj, prjGitPR.PR.Head.Sha, "staging.config"); err == nil {
|
||||
prset.HasAutoStaging = (file != nil)
|
||||
common.LogDebug(" -> automatic staging enabled?:", prset.HasAutoStaging)
|
||||
}
|
||||
}
|
||||
|
||||
// handle case where PrjGit PR is only one left and there are no changes, then we can just close the PR
|
||||
if len(prset.PRs) == 1 && prjGitPR != nil && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser {
|
||||
common.LogDebug(" --> checking if superflous PR")
|
||||
@@ -607,7 +572,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
|
||||
type RequestProcessor struct {
|
||||
configuredRepos map[string][]*common.AutogitConfig
|
||||
recursive int
|
||||
recursive int
|
||||
}
|
||||
|
||||
func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error {
|
||||
@@ -648,7 +613,7 @@ func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
|
||||
common.LogError("Cannot find PR for issue:", req.Pull_Request.Base.Repo.Owner.Username, req.Pull_Request.Base.Repo.Name, req.Pull_Request.Number)
|
||||
return err
|
||||
}
|
||||
} else if req, ok := request.Data.(*common.IssueCommentWebhookEvent); ok {
|
||||
} else if req, ok := request.Data.(*common.IssueWebhookEvent); ok {
|
||||
pr, err = Gitea.GetPullRequest(req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
||||
if err != nil {
|
||||
common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestOpenPR(t *testing.T) {
|
||||
}
|
||||
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil)
|
||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, nil)
|
||||
gitea.EXPECT().GetPullRequestReviews("test", "testRepo", int64(0)).Return([]*models.PullReview{}, nil)
|
||||
@@ -153,7 +153,7 @@ func TestOpenPR(t *testing.T) {
|
||||
}
|
||||
failedErr := errors.New("Returned error here")
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr, false)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr)
|
||||
|
||||
err := pr.Process(event)
|
||||
if err != failedErr {
|
||||
@@ -193,7 +193,7 @@ func TestOpenPR(t *testing.T) {
|
||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||
gitea.EXPECT().GetPullRequestReviews("org", "SomeRepo", int64(13)).Return([]*models.PullReview{}, nil)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
|
||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil)
|
||||
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr)
|
||||
|
||||
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path"
|
||||
@@ -42,15 +43,6 @@ func pullRequestToEventState(state models.StateType) string {
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.AutogitConfig) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.LogError("panic caught in ProcessPR", common.PRtoString(pr))
|
||||
if err, ok := r.(error); !ok {
|
||||
common.LogError(err)
|
||||
}
|
||||
common.LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
return ProcesPullRequest(pr, common.AutogitConfigs{config})
|
||||
}
|
||||
|
||||
@@ -159,7 +151,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
|
||||
return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) CheckRepos() {
|
||||
func (s *DefaultStateChecker) CheckRepos() error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.LogError("panic caught")
|
||||
@@ -169,6 +161,7 @@ func (s *DefaultStateChecker) CheckRepos() {
|
||||
common.LogError(string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
errorList := make([]error, 0, 10)
|
||||
|
||||
for org, configs := range s.processor.configuredRepos {
|
||||
for _, config := range configs {
|
||||
@@ -182,12 +175,12 @@ func (s *DefaultStateChecker) CheckRepos() {
|
||||
prs, err := s.i.VerifyProjectState(config)
|
||||
if err != nil {
|
||||
common.LogError(" *** verification failed, org:", org, err)
|
||||
errorList = append(errorList, err)
|
||||
}
|
||||
for _, pr := range prs {
|
||||
prs, err := Gitea.GetRecentPullRequests(pr.Org, pr.Repo, pr.Branch)
|
||||
if err != nil {
|
||||
common.LogError("Error fetching pull requests for", fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), err)
|
||||
break
|
||||
return fmt.Errorf("Error fetching pull requests for %s/%s#%s. Err: %w", pr.Org, pr.Repo, pr.Branch, err)
|
||||
}
|
||||
if len(prs) > 0 {
|
||||
common.LogDebug(fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), " - # of PRs to check:", len(prs))
|
||||
@@ -200,11 +193,9 @@ func (s *DefaultStateChecker) CheckRepos() {
|
||||
|
||||
common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName)
|
||||
}
|
||||
|
||||
if len(configs) == 0 {
|
||||
common.LogError(" org:", org, "has 0 configs?")
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errorList...)
|
||||
}
|
||||
|
||||
func (s *DefaultStateChecker) ConsistencyCheckProcess() error {
|
||||
|
||||
Reference in New Issue
Block a user