Compare commits
43 Commits
no-package
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
| 7a0f651eaf | |||
| 2e47104b17 | |||
| 76bfa612c5 | |||
| 71aa0813ad | |||
| cc675c1b24 | |||
| 44e4941120 | |||
| 86acfa6871 | |||
| 7f09b2d2d3 | |||
| f3a37f1158 | |||
| 9d6db86318 | |||
| e11993c81f | |||
| 4bd259a2a0 | |||
| 162ae11cdd | |||
| 8431b47322 | |||
| 3ed5ecc3f0 | |||
| d08ab3efd6 | |||
| a4f6628e52 | |||
| 25073dd619 | |||
| 4293181b4e | |||
| 551a4ef577 | |||
| 6afb18fc58 | |||
| f310220261 | |||
| ef7c0c1cea | |||
| 27230fa03b | |||
| c52d40b760 | |||
| d3ba579a8b | |||
| 9ef8209622 | |||
| ba66dd868e | |||
| 17755fa2b5 | |||
| f94d3a8942 | |||
| 20e1109602 | |||
| c25d3be44e | |||
| 8db558891a | |||
| 0e06ba5993 | |||
| 736769d630 | |||
| 93c970d0dd | |||
| 5544a65947 | |||
| 918723d57b | |||
|
55846562c1
|
|||
|
95c7770cad
|
|||
|
1b900e3202
|
|||
|
d083acfd1c
|
|||
|
244160e20e
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
|
|
||||||
Name: autogits
|
Name: autogits
|
||||||
Version: 0
|
Version: 1
|
||||||
Release: 0
|
Release: 0
|
||||||
Summary: GitWorkflow utilities
|
Summary: GitWorkflow utilities
|
||||||
License: GPL-2.0-or-later
|
License: GPL-2.0-or-later
|
||||||
@@ -41,6 +41,7 @@ Command-line tool to import devel projects from obs to git
|
|||||||
|
|
||||||
%package doc
|
%package doc
|
||||||
Summary: Common documentation files
|
Summary: Common documentation files
|
||||||
|
BuildArch: noarch
|
||||||
|
|
||||||
%description -n autogits-doc
|
%description -n autogits-doc
|
||||||
Common documentation files
|
Common documentation files
|
||||||
@@ -56,10 +57,11 @@ with a topic
|
|||||||
|
|
||||||
|
|
||||||
%package gitea-status-proxy
|
%package gitea-status-proxy
|
||||||
Summary: gitea-status-proxy
|
Summary: Proxy for setting commit status in Gitea
|
||||||
|
|
||||||
%description 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
|
%package group-review
|
||||||
Summary: Reviews of groups defined in ProjectGit
|
Summary: Reviews of groups defined in ProjectGit
|
||||||
@@ -173,6 +175,7 @@ install -D -m0644 systemd/obs-staging-bot.service
|
|||||||
install -D -m0755 obs-status-service/obs-status-service %{buildroot}%{_bindir}/obs-status-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 -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 -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 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
|
||||||
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
|
||||||
|
|
||||||
@@ -212,6 +215,18 @@ install -D -m0755 utils/hujson/hujson
|
|||||||
%postun obs-status-service
|
%postun obs-status-service
|
||||||
%service_del_postun obs-status-service.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
|
%files devel-importer
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc devel-importer/README.md
|
%doc devel-importer/README.md
|
||||||
@@ -261,6 +276,7 @@ install -D -m0755 utils/hujson/hujson
|
|||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-direct/README.md
|
%doc workflow-direct/README.md
|
||||||
%{_bindir}/workflow-direct
|
%{_bindir}/workflow-direct
|
||||||
|
%{_unitdir}/workflow-direct@.service
|
||||||
|
|
||||||
%files workflow-pr
|
%files workflow-pr
|
||||||
%license COPYING
|
%license COPYING
|
||||||
|
|||||||
@@ -61,6 +61,20 @@ type Permissions struct {
|
|||||||
Members []string
|
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 {
|
type AutogitConfig struct {
|
||||||
Workflows []string // [pr, direct, test]
|
Workflows []string // [pr, direct, test]
|
||||||
Organization string
|
Organization string
|
||||||
@@ -72,6 +86,8 @@ type AutogitConfig struct {
|
|||||||
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
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
|
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
|
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
|
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
|
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
|
||||||
@@ -188,6 +204,8 @@ func (configs AutogitConfigs) GetPrjGitConfig(org, repo, branch string) *Autogit
|
|||||||
if c.GitProjectName == prjgit {
|
if c.GitProjectName == prjgit {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for _, c := range configs {
|
||||||
if c.Organization == org && c.Branch == branch {
|
if c.Organization == org && c.Branch == branch {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@@ -273,6 +291,14 @@ func (config *AutogitConfig) GetRemoteBranch() string {
|
|||||||
return "origin_" + config.Branch
|
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 {
|
type StagingConfig struct {
|
||||||
ObsProject string
|
ObsProject string
|
||||||
RebuildAll bool
|
RebuildAll bool
|
||||||
|
|||||||
@@ -10,6 +10,67 @@ import (
|
|||||||
mock_common "src.opensuse.org/autogits/common/mock"
|
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) {
|
func TestProjectConfigMatcher(t *testing.T) {
|
||||||
configs := common.AutogitConfigs{
|
configs := common.AutogitConfigs{
|
||||||
{
|
{
|
||||||
@@ -21,6 +82,15 @@ func TestProjectConfigMatcher(t *testing.T) {
|
|||||||
Branch: "main",
|
Branch: "main",
|
||||||
GitProjectName: "test/prjgit#main",
|
GitProjectName: "test/prjgit#main",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Organization: "test",
|
||||||
|
Branch: "main",
|
||||||
|
GitProjectName: "test/bar#never_match",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Organization: "test",
|
||||||
|
GitProjectName: "test/bar#main",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -50,6 +120,20 @@ func TestProjectConfigMatcher(t *testing.T) {
|
|||||||
branch: "main",
|
branch: "main",
|
||||||
config: 1,
|
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 {
|
for _, test := range tests {
|
||||||
@@ -105,6 +189,10 @@ func TestConfigWorkflowParser(t *testing.T) {
|
|||||||
if config.ManualMergeOnly != false {
|
if config.ManualMergeOnly != false {
|
||||||
t.Fatal("This should be false")
|
t.Fatal("This should be false")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Label("foobar") != "foobar" {
|
||||||
|
t.Fatal("undefined label should return default value")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,10 @@ var ExtraGitParams []string
|
|||||||
|
|
||||||
func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string, error) {
|
func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string, error) {
|
||||||
cmd := exec.Command("/usr/bin/git", params...)
|
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{
|
cmd.Env = []string{
|
||||||
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
|
||||||
"GIT_CONFIG_GLOBAL=/dev/null",
|
"GIT_CONFIG_GLOBAL=/dev/null",
|
||||||
@@ -358,7 +362,7 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
|
|||||||
"EMAIL=not@exist@src.opensuse.org",
|
"EMAIL=not@exist@src.opensuse.org",
|
||||||
"GIT_LFS_SKIP_SMUDGE=1",
|
"GIT_LFS_SKIP_SMUDGE=1",
|
||||||
"GIT_LFS_SKIP_PUSH=1",
|
"GIT_LFS_SKIP_PUSH=1",
|
||||||
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes",
|
"GIT_SSH_COMMAND=/usr/bin/ssh -o StrictHostKeyChecking=yes" + identityFile,
|
||||||
}
|
}
|
||||||
if len(ExtraGitParams) > 0 {
|
if len(ExtraGitParams) > 0 {
|
||||||
cmd.Env = append(cmd.Env, ExtraGitParams...)
|
cmd.Env = append(cmd.Env, ExtraGitParams...)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
transport "github.com/go-openapi/runtime/client"
|
transport "github.com/go-openapi/runtime/client"
|
||||||
@@ -66,6 +67,14 @@ const (
|
|||||||
ReviewStateUnknown models.ReviewStateType = ""
|
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 {
|
type GiteaTimelineFetcher interface {
|
||||||
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
|
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
|
||||||
}
|
}
|
||||||
@@ -182,6 +191,8 @@ type Gitea interface {
|
|||||||
GiteaCommitStatusGetter
|
GiteaCommitStatusGetter
|
||||||
GiteaCommitStatusSetter
|
GiteaCommitStatusSetter
|
||||||
GiteaSetRepoOptions
|
GiteaSetRepoOptions
|
||||||
|
GiteaLabelGetter
|
||||||
|
GiteaLabelSettter
|
||||||
|
|
||||||
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
||||||
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
||||||
@@ -189,7 +200,7 @@ type Gitea interface {
|
|||||||
GetOrganization(orgName string) (*models.Organization, error)
|
GetOrganization(orgName string) (*models.Organization, error)
|
||||||
GetOrganizationRepositories(orgName string) ([]*models.Repository, error)
|
GetOrganizationRepositories(orgName string) ([]*models.Repository, error)
|
||||||
CreateRepositoryIfNotExist(git Git, org, repoName 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)
|
CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool)
|
||||||
GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, string, error)
|
GetPullRequestFileContent(pr *models.PullRequest, path string) ([]byte, string, error)
|
||||||
GetRecentPullRequests(org, repo, branch string) ([]*models.PullRequest, error)
|
GetRecentPullRequests(org, repo, branch string) ([]*models.PullRequest, error)
|
||||||
GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error)
|
GetRecentCommits(org, repo, branch string, commitNo int64) ([]*models.Commit, error)
|
||||||
@@ -466,6 +477,30 @@ func (gitea *GiteaTransport) SetRepoOptions(owner, repo string, manual_merge boo
|
|||||||
return ok.Payload, err
|
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 (
|
const (
|
||||||
GiteaNotificationType_Pull = "Pull"
|
GiteaNotificationType_Pull = "Pull"
|
||||||
)
|
)
|
||||||
@@ -643,7 +678,7 @@ func (gitea *GiteaTransport) CreateRepositoryIfNotExist(git Git, org, repoName s
|
|||||||
return repo.Payload, nil
|
return repo.Payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
|
||||||
prOptions := models.CreatePullRequestOption{
|
prOptions := models.CreatePullRequestOption{
|
||||||
Base: targetId,
|
Base: targetId,
|
||||||
Head: srcId,
|
Head: srcId,
|
||||||
@@ -659,7 +694,7 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
|
|||||||
WithHead(srcId),
|
WithHead(srcId),
|
||||||
gitea.transport.DefaultAuthentication,
|
gitea.transport.DefaultAuthentication,
|
||||||
); err == nil && pr.Payload.State == "open" {
|
); err == nil && pr.Payload.State == "open" {
|
||||||
return pr.Payload, nil
|
return pr.Payload, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, err := gitea.client.Repository.RepoCreatePullRequest(
|
pr, err := gitea.client.Repository.RepoCreatePullRequest(
|
||||||
@@ -673,10 +708,10 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Cannot create pull request. %w", err)
|
return nil, fmt.Errorf("Cannot create pull request. %w", err), true
|
||||||
}
|
}
|
||||||
|
|
||||||
return pr.GetPayload(), nil
|
return pr.GetPayload(), nil, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) {
|
func (gitea *GiteaTransport) RequestReviews(pr *models.PullRequest, reviewers ...string) ([]*models.PullReview, error) {
|
||||||
@@ -763,45 +798,78 @@ func (gitea *GiteaTransport) AddComment(pr *models.PullRequest, comment string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimelineCacheData struct {
|
||||||
|
data []*models.TimelineComment
|
||||||
|
lastCheck time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var giteaTimelineCache map[string]TimelineCacheData = make(map[string]TimelineCacheData)
|
||||||
|
var giteaTimelineCacheMutex sync.RWMutex
|
||||||
|
|
||||||
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) {
|
||||||
page := int64(1)
|
page := int64(1)
|
||||||
resCount := 1
|
resCount := 1
|
||||||
|
|
||||||
retData := []*models.TimelineComment{}
|
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()
|
||||||
|
|
||||||
for resCount > 0 {
|
for resCount > 0 {
|
||||||
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(
|
opts := issue.NewIssueGetCommentsAndTimelineParams().WithOwner(org).WithRepo(repo).WithIndex(idx).WithPage(&page)
|
||||||
issue.NewIssueGetCommentsAndTimelineParams().
|
if !LastCachedTime.IsZero() {
|
||||||
WithOwner(org).
|
opts = opts.WithSince(&LastCachedTime)
|
||||||
WithRepo(repo).
|
}
|
||||||
WithIndex(idx).
|
res, err := gitea.client.Issue.IssueGetCommentsAndTimeline(opts, gitea.transport.DefaultAuthentication)
|
||||||
WithPage(&page),
|
|
||||||
gitea.transport.DefaultAuthentication,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resCount = len(res.Payload)
|
if resCount = len(res.Payload); resCount == 0 {
|
||||||
LogDebug("page:", page, "len:", resCount)
|
|
||||||
if resCount == 0 {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
page++
|
|
||||||
|
|
||||||
for _, d := range res.Payload {
|
for _, d := range res.Payload {
|
||||||
if d != nil {
|
if d != nil {
|
||||||
retData = append(retData, d)
|
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 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
page++
|
||||||
}
|
}
|
||||||
LogDebug("total results:", len(retData))
|
LogDebug("timeline", prID, "# timeline:", len(TimelineCache.data))
|
||||||
slices.SortFunc(retData, func(a, b *models.TimelineComment) int {
|
slices.SortFunc(TimelineCache.data, func(a, b *models.TimelineComment) int {
|
||||||
return time.Time(b.Created).Compare(time.Time(a.Created))
|
return time.Time(b.Created).Compare(time.Time(a.Created))
|
||||||
})
|
})
|
||||||
|
|
||||||
return retData, nil
|
TimelineCache.lastCheck = time.Now()
|
||||||
|
giteaTimelineCache[prID] = TimelineCache
|
||||||
|
|
||||||
|
return TimelineCache.data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, string, error) {
|
func (gitea *GiteaTransport) GetRepositoryFileContent(org, repo, hash, path string) ([]byte, string, error) {
|
||||||
|
|||||||
@@ -18,6 +18,132 @@ import (
|
|||||||
models "src.opensuse.org/autogits/common/gitea-generated/models"
|
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.
|
// MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface.
|
||||||
type MockGiteaTimelineFetcher struct {
|
type MockGiteaTimelineFetcher struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
@@ -1850,12 +1976,13 @@ func (c *MockGiteaAddReviewCommentCall) DoAndReturn(f func(*models.PullRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullRequestIfNotExist mocks base method.
|
// CreatePullRequestIfNotExist mocks base method.
|
||||||
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error) {
|
func (m *MockGitea) CreatePullRequestIfNotExist(repo *models.Repository, srcId, targetId, title, body string) (*models.PullRequest, error, bool) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreatePullRequestIfNotExist", repo, srcId, targetId, title, body)
|
ret := m.ctrl.Call(m, "CreatePullRequestIfNotExist", repo, srcId, targetId, title, body)
|
||||||
ret0, _ := ret[0].(*models.PullRequest)
|
ret0, _ := ret[0].(*models.PullRequest)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
ret2, _ := ret[2].(bool)
|
||||||
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullRequestIfNotExist indicates an expected call of CreatePullRequestIfNotExist.
|
// CreatePullRequestIfNotExist indicates an expected call of CreatePullRequestIfNotExist.
|
||||||
@@ -1871,19 +1998,19 @@ type MockGiteaCreatePullRequestIfNotExistCall struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return rewrite *gomock.Call.Return
|
// Return rewrite *gomock.Call.Return
|
||||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error) *MockGiteaCreatePullRequestIfNotExistCall {
|
func (c *MockGiteaCreatePullRequestIfNotExistCall) Return(arg0 *models.PullRequest, arg1 error, arg2 bool) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||||
c.Call = c.Call.Return(arg0, arg1)
|
c.Call = c.Call.Return(arg0, arg1, arg2)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do rewrite *gomock.Call.Do
|
// Do rewrite *gomock.Call.Do
|
||||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
|
func (c *MockGiteaCreatePullRequestIfNotExistCall) Do(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||||
c.Call = c.Call.Do(f)
|
c.Call = c.Call.Do(f)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||||
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error)) *MockGiteaCreatePullRequestIfNotExistCall {
|
func (c *MockGiteaCreatePullRequestIfNotExistCall) DoAndReturn(f func(*models.Repository, string, string, string, string) (*models.PullRequest, error, bool)) *MockGiteaCreatePullRequestIfNotExistCall {
|
||||||
c.Call = c.Call.DoAndReturn(f)
|
c.Call = c.Call.DoAndReturn(f)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@@ -2202,6 +2329,45 @@ func (c *MockGiteaGetIssueCommentsCall) DoAndReturn(f func(string, string, int64
|
|||||||
return c
|
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.
|
// GetNotifications mocks base method.
|
||||||
func (m *MockGitea) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
|
func (m *MockGitea) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -2793,6 +2959,45 @@ func (c *MockGiteaSetCommitStatusCall) DoAndReturn(f func(string, string, string
|
|||||||
return c
|
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.
|
// SetNotificationRead mocks base method.
|
||||||
func (m *MockGitea) SetNotificationRead(notificationId int64) error {
|
func (m *MockGitea) SetNotificationRead(notificationId int64) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|||||||
13
common/pr.go
13
common/pr.go
@@ -233,10 +233,11 @@ next_rs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pr := range prjpr_set {
|
for _, pr := range prjpr_set {
|
||||||
if prinfo.PR.Base.Repo.Owner.UserName == pr.Org && prinfo.PR.Base.Repo.Name == pr.Repo && prinfo.PR.Index == pr.Num {
|
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 {
|
||||||
continue next_rs
|
continue next_rs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LogDebug(" PR: ", PRtoString(prinfo.PR), "not found in project git PRSet")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -256,17 +257,13 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pkg := pr.PR.Base.Repo.Name
|
pkg := pr.PR.Base.Repo.Name
|
||||||
pkg_maintainers := maintainers.ListPackageMaintainers(pkg, nil)
|
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(nil), maintainers.ListPackageMaintainers(pkg, nil), configReviewers.PkgOptional)
|
||||||
if slices.Contains(pkg_maintainers, pr.PR.User.UserName) {
|
|
||||||
pkg_maintainers = nil
|
|
||||||
}
|
|
||||||
reviewers = slices.Concat(configReviewers.Pkg, maintainers.ListProjectMaintainers(nil), pkg_maintainers, configReviewers.PkgOptional)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.Sort(reviewers)
|
slices.Sort(reviewers)
|
||||||
reviewers = slices.Compact(reviewers)
|
reviewers = slices.Compact(reviewers)
|
||||||
|
|
||||||
// submitters cannot review their own work
|
// submitters do not need to review their own work
|
||||||
if idx := slices.Index(reviewers, pr.PR.User.UserName); idx != -1 {
|
if idx := slices.Index(reviewers, pr.PR.User.UserName); idx != -1 {
|
||||||
reviewers = slices.Delete(reviewers, idx, idx+1)
|
reviewers = slices.Delete(reviewers, idx, idx+1)
|
||||||
}
|
}
|
||||||
@@ -297,7 +294,7 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
|||||||
if !IsDryRun {
|
if !IsDryRun {
|
||||||
for _, r := range reviewers {
|
for _, r := range reviewers {
|
||||||
if _, err := gitea.RequestReviews(pr.PR, r); err != nil {
|
if _, err := gitea.RequestReviews(pr.PR, r); err != nil {
|
||||||
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)
|
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, r), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,9 +168,10 @@ func FetchDevelProjects() (DevelProjects, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var DevelProjectNotFound = errors.New("Devel project not found")
|
var DevelProjectNotFound = errors.New("Devel project not found")
|
||||||
|
|
||||||
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
||||||
for _, item := range d {
|
for _, item := range d {
|
||||||
if item.Package == pkg {
|
if item.Package == pkg {
|
||||||
return item.Project, nil
|
return item.Project, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,3 +179,33 @@ func (d DevelProjects) GetDevelProject(pkg string) (string, error) {
|
|||||||
return "", DevelProjectNotFound
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -165,3 +165,58 @@ 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,30 @@ sub ListPackages {
|
|||||||
return @packages;
|
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
|
# Read project from first argument
|
||||||
sub Usage {
|
sub Usage {
|
||||||
die "Usage: $0 <OBS Project> [org [package]]";
|
die "Usage: $0 <OBS Project> [org [package]]";
|
||||||
@@ -65,6 +89,7 @@ sub Usage {
|
|||||||
|
|
||||||
my $project = shift or Usage();
|
my $project = shift or Usage();
|
||||||
my $org = shift;
|
my $org = shift;
|
||||||
|
|
||||||
if (not defined($org)) {
|
if (not defined($org)) {
|
||||||
$org = `osc meta prj $project | grep scmsync | sed -e 's,^.*src.opensuse.org/\\(.*\\)/_ObsPrj.*,\\1,'`;
|
$org = `osc meta prj $project | grep scmsync | sed -e 's,^.*src.opensuse.org/\\(.*\\)/_ObsPrj.*,\\1,'`;
|
||||||
chomp($org);
|
chomp($org);
|
||||||
@@ -139,7 +164,7 @@ if ( scalar @tomove > 0 ) {
|
|||||||
system("git -C $pkg push origin factory") == 0 and
|
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
|
system("git obs $super_user api -X PATCH --data '{\"default_branch\": \"factory\"}' /repos/pool/$pkg") == 0
|
||||||
or die "Error in creating a pool repo";
|
or die "Error in creating a pool repo";
|
||||||
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";
|
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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +192,7 @@ for my $package ( sort(@packages) ) {
|
|||||||
or ( push( @tomove, $package ) and die "Can't fetch pool for $package" );
|
or ( push( @tomove, $package ) and die "Can't fetch pool for $package" );
|
||||||
|
|
||||||
my @commits = FindFactoryCommit($package);
|
my @commits = FindFactoryCommit($package);
|
||||||
|
my $Md5Hashes = FactoryMd5($package);
|
||||||
my $c;
|
my $c;
|
||||||
my $match = 0;
|
my $match = 0;
|
||||||
for my $commit (@commits) {
|
for my $commit (@commits) {
|
||||||
@@ -179,16 +205,27 @@ for my $package ( sort(@packages) ) {
|
|||||||
system("git -C $package lfs fetch pool $commit") == 0
|
system("git -C $package lfs fetch pool $commit") == 0
|
||||||
and system("git -C $package checkout -B factory $commit") == 0
|
and system("git -C $package checkout -B factory $commit") == 0
|
||||||
and system("git -C $package lfs checkout") == 0
|
and system("git -C $package lfs checkout") == 0
|
||||||
and system(
|
and chdir($package)) {
|
||||||
"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
|
open(my $fh, "|-", "md5sum -c --quiet") or die $!;
|
||||||
and system("bash -c \"diff <(ls -1 $package | sort) <(osc ls openSUSE:Factory $package | grep -v -F '_scmsync.obsinfo\nbuild.specials.obscpio' | sort)\"") == 0
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
$c = $commit;
|
|
||||||
$match = 1;
|
|
||||||
last;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
|
Java:packages
|
||||||
Kernel:firmware
|
Kernel:firmware
|
||||||
Kernel:kdump
|
Kernel:kdump
|
||||||
|
devel:gcc
|
||||||
devel:languages:clojure
|
devel:languages:clojure
|
||||||
devel:languages:erlang
|
devel:languages:erlang
|
||||||
devel:languages:erlang:Factory
|
devel:languages:erlang:Factory
|
||||||
devel:languages:hare
|
devel:languages:hare
|
||||||
devel:languages:javascript
|
devel:languages:javascript
|
||||||
devel:languages:lua
|
devel:languages:lua
|
||||||
|
devel:languages:nodejs
|
||||||
devel:languages:perl
|
devel:languages:perl
|
||||||
|
devel:languages:python:Factory
|
||||||
|
devel:languages:python:pytest
|
||||||
devel:openSUSE:Factory
|
devel:openSUSE:Factory
|
||||||
|
network:chromium
|
||||||
network:dhcp
|
network:dhcp
|
||||||
network:im:whatsapp
|
network:im:whatsapp
|
||||||
network:messaging:xmpp
|
network:messaging:xmpp
|
||||||
|
science:HPC
|
||||||
server:dns
|
server:dns
|
||||||
systemsmanagement:cockpit
|
systemsmanagement:cockpit
|
||||||
X11:lxde
|
X11:lxde
|
||||||
|
|||||||
@@ -14,15 +14,11 @@ import (
|
|||||||
"src.opensuse.org/autogits/common"
|
"src.opensuse.org/autogits/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status struct {
|
|
||||||
Context string `json:"context"`
|
|
||||||
State string `json:"state"`
|
|
||||||
TargetUrl string `json:"target_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusInput struct {
|
type StatusInput struct {
|
||||||
State string `json:"state"`
|
Description string `json:"description"`
|
||||||
TargetUrl string `json:"target_url"`
|
Context string `json:"context"`
|
||||||
|
State string `json:"state"`
|
||||||
|
TargetUrl string `json:"target_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -59,23 +55,26 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
config, ok := r.Context().Value(configKey).(*Config)
|
config, ok := r.Context().Value(configKey).(*Config)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
common.LogError("Config missing from context")
|
common.LogDebug("Config missing from context")
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
header := r.Header.Get("Authorization")
|
header := r.Header.Get("Authorization")
|
||||||
if header == "" {
|
if header == "" {
|
||||||
|
common.LogDebug("Authorization header not found")
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token_arr := strings.Split(header, " ")
|
token_arr := strings.Split(header, " ")
|
||||||
if len(token_arr) != 2 {
|
if len(token_arr) != 2 {
|
||||||
|
common.LogDebug("Authorization header malformed")
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.EqualFold(token_arr[0], "Bearer") {
|
if !strings.EqualFold(token_arr[0], "token") {
|
||||||
|
common.LogDebug("Token not found in Authorization header")
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -83,6 +82,7 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
token := token_arr[1]
|
token := token_arr[1]
|
||||||
|
|
||||||
if !slices.Contains(config.Keys, token) {
|
if !slices.Contains(config.Keys, token) {
|
||||||
|
common.LogDebug("Provided token is not known")
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -104,13 +104,8 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status := Status{
|
|
||||||
Context: "Build in obs",
|
|
||||||
State: statusinput.State,
|
|
||||||
TargetUrl: statusinput.TargetUrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
status_payload, err := json.Marshal(status)
|
status_payload, err := json.Marshal(statusinput)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
@@ -131,8 +126,8 @@ func StatusProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "Content-Type")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ForgeToken))
|
req.Header.Add("Authorization", fmt.Sprintf("token %s", ForgeToken))
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
|||||||
48
gitea_status_proxy/readme.md
Normal file
48
gitea_status_proxy/readme.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 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
|
subject := notification.Subject
|
||||||
u, err := url.Parse(notification.Subject.URL)
|
u, err := url.Parse(notification.Subject.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -386,6 +386,28 @@ func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, git
|
|||||||
}
|
}
|
||||||
// patch baseMeta to become the new project
|
// patch baseMeta to become the new project
|
||||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
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
|
// Cleanup ReleaseTarget and modify affected path entries
|
||||||
for idx, r := range templateMeta.Repositories {
|
for idx, r := range templateMeta.Repositories {
|
||||||
templateMeta.Repositories[idx].ReleaseTargets = nil
|
templateMeta.Repositories[idx].ReleaseTargets = nil
|
||||||
@@ -1044,6 +1066,7 @@ func main() {
|
|||||||
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
|
ObsWebHost = ObsWebHostFromApiHost(*obsApiHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
common.LogDebug("OBS Gitea Host:", GiteaUrl)
|
||||||
common.LogDebug("OBS Web Host:", ObsWebHost)
|
common.LogDebug("OBS Web Host:", ObsWebHost)
|
||||||
common.LogDebug("OBS API Host:", *obsApiHost)
|
common.LogDebug("OBS API Host:", *obsApiHost)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
OBS Status Service
|
OBS Status Service
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Reports build status of OBS service as an easily to produce SVG
|
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.
|
||||||
|
|
||||||
Requests for individual build results:
|
Requests for individual build results:
|
||||||
|
|
||||||
@@ -17,19 +20,32 @@ Get requests for / will also return 404 statu normally. If the Backend redis
|
|||||||
server is not available, it will return 500
|
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
|
Areas of Responsibility
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
* Monitors RabbitMQ interface for notification of OBS package and project status
|
* Fetch and cache internal data from OBS and present it in usable format:
|
||||||
* Produces SVG output based on GET request
|
+ Generate SVG output for specific OBS project or package
|
||||||
* Cache results (sqlite) and periodically update results from OBS (in case of messages are missing)
|
+ Generate JSON/XML output for automated processing
|
||||||
|
* Low-overhead
|
||||||
|
|
||||||
|
|
||||||
Target Usage
|
Target Usage
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* README.md of package git or project git
|
* inside README.md of package git or project git
|
||||||
* comment section of a Gitea PR
|
* comment section of a Gitea PR
|
||||||
|
* automated build result processing
|
||||||
|
|
||||||
Running
|
Running
|
||||||
-------
|
-------
|
||||||
@@ -42,3 +58,4 @@ Default parameters can be changed by env variables
|
|||||||
| `OBS_STATUS_SERVICE_LISTEN` | [::1]:8080 | Listening address and port
|
| `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_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
|
| `OBS_STATUS_SERVICE_KEY` | /run/obs-status-service.pem | Location of key file for service
|
||||||
|
| `REDIS` | | OBS's Redis instance URL
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"html"
|
"html"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SvgWriter struct {
|
type SvgWriter struct {
|
||||||
@@ -133,7 +134,7 @@ func (svg *SvgWriter) WritePackageStatus(loglink, arch, status, detail string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (svg *SvgWriter) WriteProjectStatus(project, repo, arch, status string, count int) {
|
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(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(strings.ReplaceAll(repo, ".", "_")) + "=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
19
systemd/workflow-direct@.service
Normal file
19
systemd/workflow-direct@.service
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[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
|
||||||
|
|
||||||
@@ -10,9 +10,6 @@ Areas of responsibility
|
|||||||
* on repository adds, creates a new submodule (if non empty)
|
* on repository adds, creates a new submodule (if non empty)
|
||||||
* on repository removal, removes the submodule
|
* 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
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
@@ -26,6 +23,20 @@ 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.
|
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
|
Target Usage
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -40,7 +39,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
AppName = "direct_workflow"
|
AppName = "direct_workflow"
|
||||||
GitAuthor = "AutoGits prjgit-updater"
|
GitAuthor = "AutoGits prjgit-updater"
|
||||||
GitEmail = "adam+autogits-direct@zombino.com"
|
GitEmail = "autogits-direct@noreply@src.opensuse.org"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configuredRepos map[string][]*common.AutogitConfig
|
var configuredRepos map[string][]*common.AutogitConfig
|
||||||
@@ -53,18 +52,6 @@ func isConfiguredOrg(org *common.Organization) bool {
|
|||||||
return found
|
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{}
|
type RepositoryActionProcessor struct{}
|
||||||
|
|
||||||
func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
||||||
@@ -72,69 +59,90 @@ func (*RepositoryActionProcessor) ProcessFunc(request *common.Request) error {
|
|||||||
configs, configFound := configuredRepos[action.Organization.Username]
|
configs, configFound := configuredRepos[action.Organization.Username]
|
||||||
|
|
||||||
if !configFound {
|
if !configFound {
|
||||||
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Organization.Username)
|
common.LogInfo("Repository event for", action.Organization.Username, ". Not configured. Ignoring.", action.Organization.Username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name {
|
if org, repo, _ := config.GetPrjGit(); org == action.Repository.Owner.Username && repo == action.Repository.Name {
|
||||||
log.Println("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
common.LogError("+ ignoring repo event for PrjGit repository", config.GitProjectName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
err = concatenateErrors(err, processConfiguredRepositoryAction(action, config))
|
processConfiguredRepositoryAction(action, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) error {
|
func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, config *common.AutogitConfig) {
|
||||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||||
git, err := gh.CreateGitHandler(config.Organization)
|
git, err := gh.CreateGitHandler(config.Organization)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
if len(config.Branch) == 0 {
|
configBranch := config.Branch
|
||||||
config.Branch = action.Repository.Default_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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s#%s err: %w", gitOrg, gitPrj, gitBranch, err)
|
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, gitBranch, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
|
|
||||||
switch action.Action {
|
switch action.Action {
|
||||||
case "created":
|
case "created":
|
||||||
if action.Repository.Object_Format_Name != "sha256" {
|
if action.Repository.Object_Format_Name != "sha256" {
|
||||||
return fmt.Errorf(" - '%s' repo is not sha256. Ignoring.", action.Repository.Name)
|
common.LogError(" - ", action.Repository.Name, "repo is not sha256. Ignoring.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name))
|
||||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
|
|
||||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, action.Repository.Name), "branch", "--show-current"))
|
||||||
if branch != config.Branch {
|
if branch != configBranch {
|
||||||
if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", config.Branch+":"+config.Branch); err != nil {
|
if err := git.GitExec(path.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "origin", configBranch+":"+configBranch); 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.LogError("error fetching branch", configBranch, ". ignoring as non-existent.", err) // no branch? so ignore repo here
|
||||||
|
return
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", config.Branch))
|
common.PanicOnError(git.GitExec(path.Join(gitPrj, action.Repository.Name), "checkout", configBranch))
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package inclusion via Direct Workflow"))
|
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Auto-inclusion "+action.Repository.Name))
|
||||||
if !noop {
|
if !noop {
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "push"))
|
common.PanicOnError(git.GitExec(gitPrj, "push"))
|
||||||
}
|
}
|
||||||
|
|
||||||
case "deleted":
|
case "deleted":
|
||||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
||||||
if DebugMode {
|
common.LogDebug("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
||||||
log.Println("delete event for", action.Repository.Name, "-- not in project. Ignoring")
|
return
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "rm", action.Repository.Name))
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow"))
|
common.PanicOnError(git.GitExec(gitPrj, "commit", "-m", "Automatic package removal via Direct Workflow"))
|
||||||
@@ -143,10 +151,9 @@ func processConfiguredRepositoryAction(action *common.RepositoryWebhookEvent, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s: %s", "Unknown action type", action.Action)
|
common.LogError("Unknown action type:", action.Action)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PushActionProcessor struct{}
|
type PushActionProcessor struct{}
|
||||||
@@ -156,77 +163,83 @@ func (*PushActionProcessor) ProcessFunc(request *common.Request) error {
|
|||||||
configs, configFound := configuredRepos[action.Repository.Owner.Username]
|
configs, configFound := configuredRepos[action.Repository.Owner.Username]
|
||||||
|
|
||||||
if !configFound {
|
if !configFound {
|
||||||
log.Printf("Repository event for %s. Not configured. Ignoring.\n", action.Repository.Owner.Username)
|
common.LogDebug("Repository event for", action.Repository.Owner.Username, ". Not configured. Ignoring.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name {
|
if gitOrg, gitPrj, _ := config.GetPrjGit(); gitOrg == action.Repository.Owner.Username && gitPrj == action.Repository.Name {
|
||||||
log.Println("+ ignoring push to PrjGit repository", config.GitProjectName)
|
common.LogInfo("+ ignoring push to PrjGit repository", config.GitProjectName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
err = concatenateErrors(err, processConfiguredPushAction(action, config))
|
processConfiguredPushAction(action, config)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) error {
|
func processConfiguredPushAction(action *common.PushWebhookEvent, config *common.AutogitConfig) {
|
||||||
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
gitOrg, gitPrj, gitBranch := config.GetPrjGit()
|
||||||
git, err := gh.CreateGitHandler(config.Organization)
|
git, err := gh.CreateGitHandler(config.Organization)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
log.Printf("push to: %s/%s for %s/%s#%s", action.Repository.Owner.Username, action.Repository.Name, gitOrg, gitPrj, gitBranch)
|
common.LogDebug("push to:", action.Repository.Owner.Username, action.Repository.Name, "for:", gitOrg, gitPrj, gitBranch)
|
||||||
if len(config.Branch) == 0 {
|
branch := config.Branch
|
||||||
config.Branch = action.Repository.Default_Branch
|
if len(branch) == 0 {
|
||||||
log.Println(" + default branch", action.Repository.Default_Branch)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
prjGitRepo, err := gitea.CreateRepositoryIfNotExist(git, gitOrg, gitPrj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error accessing/creating prjgit: %s/%s err: %w", gitOrg, gitPrj, err)
|
common.LogError("Error accessing/creating prjgit:", gitOrg, gitPrj, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, prjGitRepo.SSHURL)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
headCommitId, err := git.GitRemoteHead(gitPrj, remoteName, gitBranch)
|
headCommitId, err := git.GitRemoteHead(gitPrj, remoteName, gitBranch)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
|
commit, ok := git.GitSubmoduleCommitId(gitPrj, action.Repository.Name, headCommitId)
|
||||||
for ok && action.Head_Commit.Id == commit {
|
for ok && action.Head_Commit.Id == commit {
|
||||||
log.Println(" -- nothing to do, commit already in ProjectGit")
|
common.LogDebug(" -- nothing to do, commit already in ProjectGit")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil || !stat.IsDir() {
|
if stat, err := os.Stat(filepath.Join(git.GetPath(), gitPrj, action.Repository.Name)); err != nil {
|
||||||
if DebugMode {
|
git.GitExecOrPanic(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", action.Repository.Clone_Url, action.Repository.Name)
|
||||||
log.Println("Pushed to package that is not part of the project. Ignoring:", err)
|
common.LogDebug("Pushed to package that is not part of the project. Re-adding...", err)
|
||||||
}
|
} else if !stat.IsDir() {
|
||||||
return nil
|
common.LogError("Pushed to a package that is not a submodule but exists in the project. Ignoring.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", action.Repository.Name)
|
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--force", "--depth", "1", "--checkout", action.Repository.Name)
|
||||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
|
|
||||||
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", remoteName, config.Branch+":"+config.Branch); err != nil {
|
if err := git.GitExec(filepath.Join(gitPrj, action.Repository.Name), "fetch", "--depth", "1", "--force", "origin", branch+":"+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.LogError("Error fetching branch:", branch, "Ignoring as non-existent.", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
id, err := git.GitRemoteHead(filepath.Join(gitPrj, action.Repository.Name), remoteName, config.Branch)
|
id, err := git.GitBranchHead(filepath.Join(gitPrj, action.Repository.Name), branch)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
if action.Head_Commit.Id == id {
|
if action.Head_Commit.Id == id {
|
||||||
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
|
git.GitExecOrPanic(filepath.Join(gitPrj, action.Repository.Name), "checkout", id)
|
||||||
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow")
|
git.GitExecOrPanic(gitPrj, "commit", "-a", "-m", fmt.Sprintf("'%s' update via Direct Workflow", action.Repository.Name))
|
||||||
if !noop {
|
if !noop {
|
||||||
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("push of refs not on the configured branch", config.Branch, ". ignoring.")
|
common.LogDebug("push of refs not on the configured branch", branch, ". ignoring.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) {
|
func verifyProjectState(git common.Git, org string, config *common.AutogitConfig, configs []*common.AutogitConfig) (err error) {
|
||||||
@@ -248,51 +261,65 @@ func verifyProjectState(git common.Git, org string, config *common.AutogitConfig
|
|||||||
|
|
||||||
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
|
remoteName, err := git.GitClone(gitPrj, gitBranch, repo.SSHURL)
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
defer git.GitExecOrPanic(gitPrj, "submodule", "deinit", "--all")
|
git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
|
defer git.GitExecQuietOrPanic(gitPrj, "submodule", "deinit", "--all", "-f")
|
||||||
|
|
||||||
log.Println(" * Getting submodule list")
|
common.LogDebug(" * Getting submodule list")
|
||||||
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
|
sub, err := git.GitSubmoduleList(gitPrj, "HEAD")
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
|
|
||||||
log.Println(" * Getting package links")
|
common.LogDebug(" * Getting package links")
|
||||||
var pkgLinks []*PackageRebaseLink
|
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 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 {
|
if data, err := os.ReadFile(path.Join(git.GetPath(), gitPrj, common.PrjLinksFile)); err == nil {
|
||||||
pkgLinks, err = parseProjectLinks(data)
|
pkgLinks, err = parseProjectLinks(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Cannot parse project links file:", err.Error())
|
common.LogError("Cannot parse project links file:", err.Error())
|
||||||
pkgLinks = nil
|
pkgLinks = nil
|
||||||
} else {
|
} else {
|
||||||
ResolveLinks(org, pkgLinks, gitea)
|
ResolveLinks(org, pkgLinks, gitea)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Println(" - No package links defined")
|
common.LogInfo(" - No package links defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check existing submodule that they are updated */
|
/* Check existing submodule that they are updated */
|
||||||
|
|
||||||
isGitUpdated := false
|
isGitUpdated := false
|
||||||
next_package:
|
next_package:
|
||||||
for filename, commitId := range sub {
|
for filename, commitId := range sub {
|
||||||
// ignore project gits
|
// ignore project gits
|
||||||
//for _, c := range configs {
|
//for _, c := range configs {
|
||||||
if gitPrj == filename {
|
if gitPrj == filename {
|
||||||
log.Println(" prjgit as package? ignoring project git:", filename)
|
common.LogDebug(" prjgit as package? ignoring project git:", filename)
|
||||||
continue next_package
|
continue next_package
|
||||||
}
|
}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
log.Printf(" verifying package: %s -> %s(%s)", commitId, filename, config.Branch)
|
branch := config.Branch
|
||||||
commits, err := gitea.GetRecentCommits(org, filename, config.Branch, 10)
|
common.LogDebug(" verifying package:", commitId, "->", filename, "@", branch)
|
||||||
if len(commits) == 0 {
|
if repo, err := gitea.GetRepository(org, filename); repo == nil && err == nil {
|
||||||
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")
|
||||||
git.GitExecOrPanic(gitPrj, "rm", filename)
|
git.GitExecOrPanic(gitPrj, "rm", filename)
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commits, err := gitea.GetRecentCommits(org, filename, branch, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
common.LogDebug(" -> failed to fetch recent commits for package:", filename, " Err:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +336,7 @@ next_package:
|
|||||||
if l.Pkg == filename {
|
if l.Pkg == filename {
|
||||||
link = l
|
link = l
|
||||||
|
|
||||||
log.Println(" -> linked package")
|
common.LogDebug(" -> linked package")
|
||||||
// so, we need to rebase here. Can't really optimize, so clone entire package tree and remote
|
// so, we need to rebase here. Can't really optimize, so clone entire package tree and remote
|
||||||
pkgPath := path.Join(gitPrj, filename)
|
pkgPath := path.Join(gitPrj, filename)
|
||||||
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename)
|
git.GitExecOrPanic(gitPrj, "submodule", "update", "--init", "--checkout", filename)
|
||||||
@@ -323,7 +350,7 @@ next_package:
|
|||||||
nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n"))
|
nCommits := len(common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(pkgPath, "rev-list", "^NOW", "HEAD"), "\n"))
|
||||||
if nCommits > 0 {
|
if nCommits > 0 {
|
||||||
if !noop {
|
if !noop {
|
||||||
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+config.Branch)
|
git.GitExecOrPanic(pkgPath, "push", "-f", "origin", "HEAD:"+branch)
|
||||||
}
|
}
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
}
|
}
|
||||||
@@ -340,42 +367,27 @@ next_package:
|
|||||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "update", "--init", "--depth", "1", "--checkout", filename))
|
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), "fetch", "--depth", "1", "origin", commits[0].SHA))
|
||||||
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA))
|
common.PanicOnError(git.GitExec(filepath.Join(gitPrj, filename), "checkout", commits[0].SHA))
|
||||||
log.Println(" -> updated to", commits[0].SHA)
|
common.LogDebug(" -> updated to", commits[0].SHA)
|
||||||
isGitUpdated = true
|
isGitUpdated = true
|
||||||
} else {
|
} else {
|
||||||
// probably need `merge-base` or `rev-list` here instead, or the project updated already
|
// probably need `merge-base` or `rev-list` here instead, or the project updated already
|
||||||
log.Println(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
common.LogInfo(" *** Cannot find SHA of last matching update for package:", filename, " Ignoring")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all missing repositories, and add them
|
// find all missing repositories, and add them
|
||||||
if DebugMode {
|
common.LogDebug("checking for missing repositories...")
|
||||||
log.Println("checking for missing repositories...")
|
|
||||||
}
|
|
||||||
repos, err := gitea.GetOrganizationRepositories(org)
|
repos, err := gitea.GetOrganizationRepositories(org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if DebugMode {
|
common.LogDebug(" nRepos:", len(repos))
|
||||||
log.Println(" nRepos:", len(repos))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check repositories in org to make sure they are included in project git */
|
/* Check repositories in org to make sure they are included in project git */
|
||||||
next_repo:
|
next_repo:
|
||||||
for _, r := range repos {
|
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 {
|
// for _, c := range configs {
|
||||||
if gitPrj == r.Name {
|
if gitPrj == r.Name {
|
||||||
// ignore project gits
|
// ignore project gits
|
||||||
@@ -390,43 +402,45 @@ next_repo:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if DebugMode {
|
common.LogDebug(" -- checking repository:", r.Name)
|
||||||
log.Println(" -- checking repository:", r.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := gitea.GetRecentCommits(org, r.Name, config.Branch, 1); err != nil {
|
branch := config.Branch
|
||||||
|
if len(branch) == 0 {
|
||||||
|
branch = r.DefaultBranch
|
||||||
|
if common.IsRemovedBranch(branch) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if commits, err := gitea.GetRecentCommits(org, r.Name, branch, 1); err != nil || len(commits) == 0 {
|
||||||
// assumption that package does not exist, so not part of project
|
// assumption that package does not exist, so not part of project
|
||||||
// https://github.com/go-gitea/gitea/issues/31976
|
// https://github.com/go-gitea/gitea/issues/31976
|
||||||
|
|
||||||
|
// or, we do not have commits here
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// add repository to git project
|
// add repository to git project
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--depth", "1", r.CloneURL, r.Name))
|
common.PanicOnError(git.GitExec(gitPrj, "submodule", "--quiet", "add", "--force", "--depth", "1", r.CloneURL, r.Name))
|
||||||
|
|
||||||
if len(config.Branch) > 0 {
|
curBranch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
||||||
branch := strings.TrimSpace(git.GitExecWithOutputOrPanic(path.Join(gitPrj, r.Name), "branch", "--show-current"))
|
if branch != curBranch {
|
||||||
if branch != config.Branch {
|
if err := git.GitExec(path.Join(gitPrj, r.Name), "fetch", "--depth", "1", "origin", branch+":"+branch); err != nil {
|
||||||
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.", branch, repo.Owner.UserName, r.Name)
|
||||||
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
|
isGitUpdated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGitUpdated {
|
if isGitUpdated {
|
||||||
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Automatic update via push via Direct Workflow -- SYNC"))
|
common.PanicOnError(git.GitExec(gitPrj, "commit", "-a", "-m", "Periodic SYNC in Direct Workflow"))
|
||||||
if !noop {
|
if !noop {
|
||||||
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
git.GitExecOrPanic(gitPrj, "push", remoteName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if DebugMode {
|
common.LogInfo("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
||||||
log.Println("Verification finished for ", org, ", prjgit:", config.GitProjectName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,17 +451,17 @@ var checkInterval time.Duration
|
|||||||
func checkOrg(org string, configs []*common.AutogitConfig) {
|
func checkOrg(org string, configs []*common.AutogitConfig) {
|
||||||
git, err := gh.CreateGitHandler(org)
|
git, err := gh.CreateGitHandler(org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Faield to allocate GitHandler:", err)
|
common.LogError("Failed to allocate GitHandler:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer git.Close()
|
defer git.Close()
|
||||||
|
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName)
|
||||||
if err := verifyProjectState(git, org, config, configs); err != nil {
|
if err := verifyProjectState(git, org, config, configs); err != nil {
|
||||||
log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err)
|
common.LogError(" *** verification failed, org:", org, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName)
|
common.LogError(" ++ verification complete, org:", org, config.GitProjectName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +470,7 @@ func checkRepos() {
|
|||||||
for org, configs := range configuredRepos {
|
for org, configs := range configuredRepos {
|
||||||
if checkInterval > 0 {
|
if checkInterval > 0 {
|
||||||
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval)))
|
sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval)))
|
||||||
log.Println(" - sleep interval", sleepInterval, "until next check")
|
common.LogInfo(" - sleep interval", sleepInterval, "until next check")
|
||||||
time.Sleep(sleepInterval)
|
time.Sleep(sleepInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,9 +482,9 @@ func consistencyCheckProcess() {
|
|||||||
if checkOnStart {
|
if checkOnStart {
|
||||||
savedCheckInterval := checkInterval
|
savedCheckInterval := checkInterval
|
||||||
checkInterval = 0
|
checkInterval = 0
|
||||||
log.Println("== Startup consistency check begin...")
|
common.LogInfo("== Startup consistency check begin...")
|
||||||
checkRepos()
|
checkRepos()
|
||||||
log.Println("== Startup consistency check done...")
|
common.LogInfo("== Startup consistency check done...")
|
||||||
checkInterval = savedCheckInterval
|
checkInterval = savedCheckInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +499,8 @@ var gh common.GitHandlerGenerator
|
|||||||
func updateConfiguration(configFilename string, orgs *[]string) {
|
func updateConfiguration(configFilename string, orgs *[]string) {
|
||||||
configFile, err := common.ReadConfigFile(configFilename)
|
configFile, err := common.ReadConfigFile(configFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
common.LogError(err)
|
||||||
|
os.Exit(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
configs, _ := common.ResolveWorkflowConfigs(gitea, configFile)
|
configs, _ := common.ResolveWorkflowConfigs(gitea, configFile)
|
||||||
@@ -493,9 +508,7 @@ func updateConfiguration(configFilename string, orgs *[]string) {
|
|||||||
*orgs = make([]string, 0, 1)
|
*orgs = make([]string, 0, 1)
|
||||||
for _, c := range configs {
|
for _, c := range configs {
|
||||||
if slices.Contains(c.Workflows, "direct") {
|
if slices.Contains(c.Workflows, "direct") {
|
||||||
if DebugMode {
|
common.LogDebug(" + adding org:", c.Organization, ", branch:", c.Branch, ", prjgit:", c.GitProjectName)
|
||||||
log.Printf(" + adding org: '%s', branch: '%s', prjgit: '%s'\n", c.Organization, c.Branch, c.GitProjectName)
|
|
||||||
}
|
|
||||||
configs := configuredRepos[c.Organization]
|
configs := configuredRepos[c.Organization]
|
||||||
if configs == nil {
|
if configs == nil {
|
||||||
configs = make([]*common.AutogitConfig, 0, 1)
|
configs = make([]*common.AutogitConfig, 0, 1)
|
||||||
@@ -509,7 +522,7 @@ func updateConfiguration(configFilename string, orgs *[]string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configFilename := flag.String("config", "", "List of PrjGit")
|
configFilename := flag.String("config", "config.json", "List of PrjGit")
|
||||||
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
|
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
|
||||||
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
|
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
|
||||||
flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
|
flag.BoolVar(&DebugMode, "debug", false, "Extra debugging information")
|
||||||
@@ -520,10 +533,35 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := common.RequireGiteaSecretToken(); err != nil {
|
if err := common.RequireGiteaSecretToken(); err != nil {
|
||||||
log.Fatal(err)
|
common.LogError(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if err := common.RequireRabbitSecrets(); err != nil {
|
if err := common.RequireRabbitSecrets(); err != nil {
|
||||||
log.Fatal(err)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
defs := &common.RabbitMQGiteaEventsProcessor{}
|
defs := &common.RabbitMQGiteaEventsProcessor{}
|
||||||
@@ -532,12 +570,14 @@ func main() {
|
|||||||
if len(*basePath) == 0 {
|
if len(*basePath) == 0 {
|
||||||
*basePath, err = os.MkdirTemp(os.TempDir(), AppName)
|
*basePath, err = os.MkdirTemp(os.TempDir(), AppName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
common.LogError(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail)
|
gh, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
common.LogError(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle reconfiguration
|
// handle reconfiguration
|
||||||
@@ -552,10 +592,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sig != syscall.SIGHUP {
|
if sig != syscall.SIGHUP {
|
||||||
log.Println("Unexpected signal received:", sig)
|
common.LogError("Unexpected signal received:", sig)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Println("*** Reconfiguring ***")
|
common.LogError("*** Reconfiguring ***")
|
||||||
updateConfiguration(*configFilename, &defs.Orgs)
|
updateConfiguration(*configFilename, &defs.Orgs)
|
||||||
defs.Connection().UpdateTopics(defs)
|
defs.Connection().UpdateTopics(defs)
|
||||||
}
|
}
|
||||||
@@ -567,23 +607,25 @@ func main() {
|
|||||||
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
||||||
CurrentUser, err := gitea.GetCurrentUser()
|
CurrentUser, err := gitea.GetCurrentUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Cannot fetch current user:", err)
|
common.LogError("Cannot fetch current user:", err)
|
||||||
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
log.Println("Current User:", CurrentUser.UserName)
|
common.LogInfo("Current User:", CurrentUser.UserName)
|
||||||
|
|
||||||
updateConfiguration(*configFilename, &defs.Orgs)
|
updateConfiguration(*configFilename, &defs.Orgs)
|
||||||
|
|
||||||
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
defs.Connection().RabbitURL, err = url.Parse(*rabbitUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("cannot parse server URL. Err: %#v\n", err)
|
common.LogError("cannot parse server URL. Err:", err)
|
||||||
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
go consistencyCheckProcess()
|
go consistencyCheckProcess()
|
||||||
log.Println("defs:", *defs)
|
common.LogInfo("defs:", *defs)
|
||||||
|
|
||||||
defs.Handlers = make(map[string]common.RequestProcessor)
|
defs.Handlers = make(map[string]common.RequestProcessor)
|
||||||
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
defs.Handlers[common.RequestType_Push] = &PushActionProcessor{}
|
||||||
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
defs.Handlers[common.RequestType_Repository] = &RepositoryActionProcessor{}
|
||||||
|
|
||||||
log.Fatal(common.ProcessRabbitMQEvents(defs))
|
common.LogError(common.ProcessRabbitMQEvents(defs))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ JSON
|
|||||||
* _ReviewRequired_: (true, false) ignores that submitter is a maintainer and require a review from other maintainer IFF available
|
* _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.
|
* _NoProjectGitPR_: (true, false) do not create PrjGit PRs, but still process reviews, etc.
|
||||||
* _Permissions_: permissions and associated accounts/groups. See below.
|
* _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.
|
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:
|
example:
|
||||||
@@ -74,6 +75,18 @@ results in
|
|||||||
* moo -> package and project reviews, but ignored
|
* 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
|
Maintainership
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import "src.opensuse.org/autogits/common"
|
|||||||
|
|
||||||
type StateChecker interface {
|
type StateChecker interface {
|
||||||
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
|
VerifyProjectState(configs *common.AutogitConfig) ([]*PRToProcess, error)
|
||||||
CheckRepos() error
|
CheckRepos()
|
||||||
ConsistencyCheckProcess() error
|
ConsistencyCheckProcess() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,12 +227,18 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
|
|||||||
}
|
}
|
||||||
|
|
||||||
title, desc := PrjGitDescription(prset)
|
title, desc := PrjGitDescription(prset)
|
||||||
pr, err := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
|
pr, err, isNew := Gitea.CreatePullRequestIfNotExist(PrjGit, prjGitPRbranch, PrjGitBranch, title, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError("Error creating PrjGit PR:", err)
|
common.LogError("Error creating PrjGit PR:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, pr.Index, &models.EditPullRequestOption{
|
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{
|
||||||
RemoveDeadline: true,
|
RemoveDeadline: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -382,6 +388,10 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||||
prjGitPR, err := prset.GetPrjGitPR()
|
prjGitPR, err := prset.GetPrjGitPR()
|
||||||
if err == common.PRSet_PrjGitMissing {
|
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)
|
common.LogDebug("Missing PrjGit. Need to create one under branch", prjGitPRbranch)
|
||||||
|
|
||||||
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func TestOpenPR(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
// gitea.EXPECT().GetAssociatedPrjGitPR("test", "prjcopy", "test", "testRepo", int64(1)).Return(nil, nil)
|
||||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, 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)
|
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
|
||||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||||
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, nil)
|
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, nil)
|
||||||
gitea.EXPECT().GetPullRequestReviews("test", "testRepo", int64(0)).Return([]*models.PullReview{}, 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")
|
failedErr := errors.New("Returned error here")
|
||||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||||
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr)
|
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, failedErr, false)
|
||||||
|
|
||||||
err := pr.Process(event)
|
err := pr.Process(event)
|
||||||
if err != failedErr {
|
if err != failedErr {
|
||||||
@@ -193,7 +193,7 @@ func TestOpenPR(t *testing.T) {
|
|||||||
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
gitea.EXPECT().CreateRepositoryIfNotExist(git, "test", "prjcopy").Return(prjgit, nil)
|
||||||
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
gitea.EXPECT().GetPullRequest("test", "testRepo", int64(1)).Return(giteaPR, nil)
|
||||||
gitea.EXPECT().GetPullRequestReviews("org", "SomeRepo", int64(13)).Return([]*models.PullReview{}, 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)
|
gitea.EXPECT().CreatePullRequestIfNotExist(prjgit, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(giteaPR, nil, true)
|
||||||
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr)
|
gitea.EXPECT().RequestReviews(giteaPR, "reviewer1", "reviewer2").Return(nil, failedErr)
|
||||||
|
|
||||||
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
gitea.EXPECT().FetchMaintainershipDirFile("test", "prjcopy", "branch", "_project").Return(nil, "", repository.NewRepoGetRawFileNotFound())
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"path"
|
"path"
|
||||||
@@ -43,6 +42,15 @@ func pullRequestToEventState(state models.StateType) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.AutogitConfig) error {
|
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})
|
return ProcesPullRequest(pr, common.AutogitConfigs{config})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +159,7 @@ func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) (
|
|||||||
return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
|
return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultStateChecker) CheckRepos() error {
|
func (s *DefaultStateChecker) CheckRepos() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
common.LogError("panic caught")
|
common.LogError("panic caught")
|
||||||
@@ -161,7 +169,6 @@ func (s *DefaultStateChecker) CheckRepos() error {
|
|||||||
common.LogError(string(debug.Stack()))
|
common.LogError(string(debug.Stack()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
errorList := make([]error, 0, 10)
|
|
||||||
|
|
||||||
for org, configs := range s.processor.configuredRepos {
|
for org, configs := range s.processor.configuredRepos {
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
@@ -175,12 +182,12 @@ func (s *DefaultStateChecker) CheckRepos() error {
|
|||||||
prs, err := s.i.VerifyProjectState(config)
|
prs, err := s.i.VerifyProjectState(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError(" *** verification failed, org:", org, err)
|
common.LogError(" *** verification failed, org:", org, err)
|
||||||
errorList = append(errorList, err)
|
|
||||||
}
|
}
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
prs, err := Gitea.GetRecentPullRequests(pr.Org, pr.Repo, pr.Branch)
|
prs, err := Gitea.GetRecentPullRequests(pr.Org, pr.Repo, pr.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error fetching pull requests for %s/%s#%s. Err: %w", pr.Org, pr.Repo, pr.Branch, err)
|
common.LogError("Error fetching pull requests for", fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), err)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if len(prs) > 0 {
|
if len(prs) > 0 {
|
||||||
common.LogDebug(fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), " - # of PRs to check:", len(prs))
|
common.LogDebug(fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), " - # of PRs to check:", len(prs))
|
||||||
@@ -193,9 +200,11 @@ func (s *DefaultStateChecker) CheckRepos() error {
|
|||||||
|
|
||||||
common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName)
|
common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Join(errorList...)
|
if len(configs) == 0 {
|
||||||
|
common.LogError(" org:", org, "has 0 configs?")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultStateChecker) ConsistencyCheckProcess() error {
|
func (s *DefaultStateChecker) ConsistencyCheckProcess() error {
|
||||||
|
|||||||
Reference in New Issue
Block a user