2 Commits

Author SHA256 Message Date
Jan Zerebecki
ff217cef01 Add go generate result 2025-08-26 16:15:17 +02:00
Gitea Actions
61c59ed3e1 CI run result of: go mod vendor 2025-08-26 16:15:14 +02:00
31 changed files with 186 additions and 705 deletions

View File

@@ -1,30 +0,0 @@
name: go-generate-check
on:
push:
branches: ['main']
pull_request:
paths:
- '**.go'
- '**.mod'
- '**.sum'
workflow_dispatch:
jobs:
go-generate-check:
name: go-generate-check
container:
image: registry.opensuse.org/home/jzerebecki/branches/devel/factory/git-workflow/containers/opensuse/bci/golang-extended:latest
steps:
- run: git clone --no-checkout --depth 1 ${{ gitea.server_url }}/${{ gitea.repository }} .
- run: git fetch origin ${{ gitea.ref }}
- run: git checkout FETCH_HEAD
- run: go generate -C common
- run: go generate -C workflow-pr
- run: go generate -C workflow-pr/interfaces
- run: git add -N .; git diff
- run: |
status=$(git status --short)
if [[ -n "$status" ]]; then
echo -e "$status"
echo "Please commit the differences from running: go generate"
false
fi

View File

@@ -1,25 +0,0 @@
name: go-generate-push
on:
workflow_dispatch:
jobs:
go-generate-push:
name: go-generate-push
container:
image: registry.opensuse.org/home/jzerebecki/branches/devel/factory/git-workflow/containers/opensuse/bci/golang-extended:latest
steps:
- run: git clone --no-checkout --depth 1 ${{ gitea.server_url }}/${{ gitea.repository }} .
- run: git fetch origin ${{ gitea.ref }}
- run: git checkout FETCH_HEAD
- run: go generate -C common
- run: go generate -C workflow-pr
- run: go generate -C workflow-pr/interfaces
- run: |
host=${{ gitea.server_url }}
host=${host#https://}
echo $host
git remote set-url origin "https://x-access-token:${{ secrets.GITEA_TOKEN }}@$host/${{ gitea.repository }}"
git config user.name "Gitea Actions"
git config user.email "gitea_noreply@opensuse.org"
- run: 'git status --short; git status --porcelain=2|grep --quiet -v . || ( git add .;git commit -m "CI run result of: go generate"; git push origin HEAD:${{ gitea.ref }} )'
- run: git log -p FETCH_HEAD...HEAD
- run: git log --numstat FETCH_HEAD...HEAD

View File

@@ -1,30 +0,0 @@
name: go-vendor-check
on:
push:
branches: ['main']
pull_request:
paths:
- '**.mod'
- '**.sum'
workflow_dispatch:
jobs:
go-generate-check:
name: go-vendor-check
container:
image: registry.opensuse.org/home/jzerebecki/branches/devel/factory/git-workflow/containers/opensuse/bci/golang-extended:latest
steps:
- run: git clone --no-checkout --depth 1 ${{ gitea.server_url }}/${{ gitea.repository }} .
- run: git fetch origin ${{ gitea.ref }}
- run: git checkout FETCH_HEAD
- run: go mod download
- run: go mod vendor
- run: go mod verify
- run: git add -N .; git diff
- run: go mod tidy -diff || true
- run: |
status=$(git status --short)
if [[ -n "$status" ]]; then
echo -e "$status"
echo "Please commit the differences from running: go generate"
false
fi

View File

@@ -1,26 +0,0 @@
name: go-generate-push
on:
workflow_dispatch:
jobs:
go-generate-push:
name: go-generate-push
container:
image: registry.opensuse.org/home/jzerebecki/branches/devel/factory/git-workflow/containers/opensuse/bci/golang-extended:latest
steps:
- run: git clone --no-checkout --depth 1 ${{ gitea.server_url }}/${{ gitea.repository }} .
- run: git fetch origin ${{ gitea.ref }}
- run: git checkout FETCH_HEAD
- run: go mod download
- run: go mod vendor
- run: go mod verify
- run: |
host=${{ gitea.server_url }}
host=${host#https://}
echo $host
git remote set-url origin "https://x-access-token:${{ secrets.GITEA_TOKEN }}@$host/${{ gitea.repository }}"
git config user.name "Gitea Actions"
git config user.email "gitea_noreply@opensuse.org"
- run: 'git status --short; git status --porcelain=2|grep --quiet -v . || ( git add .;git commit -m "CI run result of: go mod vendor"; git push origin HEAD:${{ gitea.ref }} )'
- run: go mod tidy -diff || true
- run: git log -p FETCH_HEAD...HEAD
- run: git log --numstat FETCH_HEAD...HEAD

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
node_modules
*.obscpio
autogits-tmp.tar.zst
*.osc *.osc
*.conf *.conf

15
_service Normal file
View File

@@ -0,0 +1,15 @@
<services>
<!-- workaround, go_modules needs a tar and obs_scm doesn't take file://. -->
<service name="roast" mode="manual">
<param name="target">.</param>
<param name="reproducible">true</param>
<param name="outfile">autogits-tmp.tar.zst</param>
<param name="exclude">autogits-tmp.tar.zst</param>
</service>
<service name="go_modules" mode="manual">
<param name="basename">./</param>
<param name="compression">zst</param>
<param name="vendorname">vendor</param>
</service>
</services>

View File

@@ -22,7 +22,6 @@ Release: 0
Summary: GitWorkflow utilities Summary: GitWorkflow utilities
License: GPL-2.0-or-later License: GPL-2.0-or-later
URL: https://src.opensuse.org/adamm/autogits URL: https://src.opensuse.org/adamm/autogits
BuildRequires: git
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
BuildRequires: go BuildRequires: go
%{?systemd_ordering} %{?systemd_ordering}
@@ -32,85 +31,55 @@ Git Workflow tooling and utilities enabling automated handing of OBS projects
as git repositories as git repositories
%package -n autogits-devel-importer %package -n gitea-events-rabbitmq-publisher
Summary: Imports devel projects from obs to git
%description -n autogits-devel-importer
Command-line tool to import devel projects from obs to git
%package -n autogits-doc
Summary: Common documentation files
%description -n autogits-doc
Common documentation files
%package -n autogits-hujson
Summary: HuJSON to JSON parser
%description -n autogits-hujson
HuJSON to JSON parser, using stdin -> stdout pipe
%package -n autogits-gitea-events-rabbitmq-publisher
Summary: Publishes Gitea webhook data via RabbitMQ Summary: Publishes Gitea webhook data via RabbitMQ
%description -n autogits-gitea-events-rabbitmq-publisher %description -n gitea-events-rabbitmq-publisher
Listens on an HTTP socket and publishes Gitea events on a RabbitMQ instance Listens on an HTTP socket and publishes Gitea events on a RabbitMQ instance
with a topic with a topic
<scope>.src.$organization.$webhook_type.[$webhook_action_type] <scope>.src.$organization.$webhook_type.[$webhook_action_type]
%package -n autogits-gitea-status-proxy %package -n doc
Summary: gitea-status-proxy Summary: Common documentation files
%description -n autogits-gitea-status-proxy %description -n doc
Common documentation files
%package -n autogits-group-review %package -n group-review
Summary: Reviews of groups defined in ProjectGit Summary: Reviews of groups defined in ProjectGit
%description -n autogits-group-review %description -n group-review
Is used to handle reviews associated with groups defined in the Is used to handle reviews associated with groups defined in the
ProjectGit. ProjectGit.
%package -n autogits-obs-forward-bot %package -n obs-staging-bot
Summary: obs-forward-bot
%description -n autogits-obs-forward-bot
%package -n autogits-obs-staging-bot
Summary: Build a PR against a ProjectGit, if review is requested Summary: Build a PR against a ProjectGit, if review is requested
%description -n autogits-obs-staging-bot %description -n obs-staging-bot
Build a PR against a ProjectGit, if review is requested. Build a PR against a ProjectGit, if review is requested.
%package -n autogits-obs-status-service %package -n obs-status-service
Summary: Reports build status of OBS service as an easily to produce SVG Summary: Reports build status of OBS service as an easily to produce SVG
%description -n autogits-obs-status-service %description -n 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
%package -n autogits-workflow-direct %package -n workflow-direct
Summary: Keep ProjectGit in sync for a devel project Summary: Keep ProjectGit in sync for a devel project
Requires: openssh-clients
Requires: git-core
%description -n autogits-workflow-direct %description -n workflow-direct
Keep ProjectGit in sync with packages in the organization of a devel project Keep ProjectGit in sync with packages in the organization of a devel project
%package -n autogits-workflow-pr %package -n workflow-pr
Summary: Keeps ProjectGit PR in-sync with a PackageGit PR Summary: Keeps ProjectGit PR in-sync with a PackageGit PR
Requires: openssh-clients
Requires: git-core
%description -n autogits-workflow-pr %description -n workflow-pr
Keeps ProjectGit PR in-sync with a PackageGit PR Keeps ProjectGit PR in-sync with a PackageGit PR
@@ -119,24 +88,12 @@ Keeps ProjectGit PR in-sync with a PackageGit PR
cp -r /home/abuild/rpmbuild/SOURCES/* ./ cp -r /home/abuild/rpmbuild/SOURCES/* ./
%build %build
go build \
-C devel-importer \
-buildmode=pie
go build \
-C hujson \
-buildmode=pie
go build \ go build \
-C gitea-events-rabbitmq-publisher \ -C gitea-events-rabbitmq-publisher \
-buildmode=pie -buildmode=pie
go build \
-C gitea_status_proxy \
-buildmode=pie
go build \ go build \
-C group-review \ -C group-review \
-buildmode=pie -buildmode=pie
go build \
-C obs-forward-bot \
-buildmode=pie
go build \ go build \
-C obs-staging-bot \ -C obs-staging-bot \
-buildmode=pie -buildmode=pie
@@ -150,104 +107,59 @@ go build \
-C workflow-pr \ -C workflow-pr \
-buildmode=pie -buildmode=pie
%check
# TODO currently needs the source git history, maybe rewrite to create a git repo in the test?
#go test -C common
go test -C group-review
go test -C obs-staging-bot
go test -C obs-status-service
go test -C workflow-direct
# TODO build fails
#go test -C workflow-pr
%install %install
install -D -m0755 devel-importer/devel-importer %{buildroot}%{_bindir}/devel-importer
install -D -m0755 gitea-events-rabbitmq-publisher/gitea-events-rabbitmq-publisher %{buildroot}%{_bindir}/gitea-events-rabbitmq-publisher install -D -m0755 gitea-events-rabbitmq-publisher/gitea-events-rabbitmq-publisher %{buildroot}%{_bindir}/gitea-events-rabbitmq-publisher
install -D -m0644 systemd/gitea-events-rabbitmq-publisher.service %{buildroot}%{_unitdir}/gitea-events-rabbitmq-publisher.service install -D -m0644 systemd/gitea-events-rabbitmq-publisher.service %{buildroot}%{_unitdir}/gitea-events-rabbitmq-publisher.service
install -D -m0755 gitea_status_proxy/gitea_status_proxy %{buildroot}%{_bindir}/gitea_status_proxy
install -D -m0755 group-review/group-review %{buildroot}%{_bindir}/group-review install -D -m0755 group-review/group-review %{buildroot}%{_bindir}/group-review
install -D -m0755 obs-forward-bot/obs-forward-bot %{buildroot}%{_bindir}/obs-forward-bot
install -D -m0755 obs-staging-bot/obs-staging-bot %{buildroot}%{_bindir}/obs-staging-bot install -D -m0755 obs-staging-bot/obs-staging-bot %{buildroot}%{_bindir}/obs-staging-bot
install -D -m0644 systemd/obs-staging-bot.service %{buildroot}%{_unitdir}/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 -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
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 hujson/hujson %{buildroot}%{_bindir}/hujson
%pre -n autogits-gitea-events-rabbitmq-publisher %pre -n gitea-events-rabbitmq-publisher
%service_add_pre gitea-events-rabbitmq-publisher.service %service_add_pre gitea-events-rabbitmq-publisher.service
%post -n autogits-gitea-events-rabbitmq-publisher %post -n gitea-events-rabbitmq-publisher
%service_add_post gitea-events-rabbitmq-publisher.service %service_add_post gitea-events-rabbitmq-publisher.service
%preun -n autogits-gitea-events-rabbitmq-publisher %preun -n gitea-events-rabbitmq-publisher
%service_del_preun gitea-events-rabbitmq-publisher.service %service_del_preun gitea-events-rabbitmq-publisher.service
%postun -n autogits-gitea-events-rabbitmq-publisher %postun -n gitea-events-rabbitmq-publisher
%service_del_postun gitea-events-rabbitmq-publisher.service %service_del_postun gitea-events-rabbitmq-publisher.service
%pre -n autogits-obs-staging-bot %files -n gitea-events-rabbitmq-publisher
%service_add_pre obs-staging-bot.service
%post -n autogits-obs-staging-bot
%service_add_post obs-staging-bot.service
%preun -n autogits-obs-staging-bot
%service_del_preun obs-staging-bot.service
%postun -n autogits-obs-staging-bot
%service_del_postun obs-staging-bot.service
%files -n autogits-devel-importer
%license COPYING
%doc devel-importer/README.md
%{_bindir}/devel-importer
%files -n autogits-hujson
%license COPYING
%{_bindir}/hujson
%files -n autogits-doc
%license COPYING
%doc doc/README.md
%doc doc/workflows.md
%files -n autogits-gitea-events-rabbitmq-publisher
%license COPYING %license COPYING
%doc gitea-events-rabbitmq-publisher/README.md %doc gitea-events-rabbitmq-publisher/README.md
%{_bindir}/gitea-events-rabbitmq-publisher %{_bindir}/gitea-events-rabbitmq-publisher
%{_unitdir}/gitea-events-rabbitmq-publisher.service %{_unitdir}/gitea-events-rabbitmq-publisher.service
%files -n autogits-gitea-status-proxy %files -n doc
%license COPYING %license COPYING
%{_bindir}/gitea_status_proxy %doc doc/README.md
%doc doc/workflows.md
%files -n autogits-group-review %files -n group-review
%license COPYING %license COPYING
%doc group-review/README.md %doc group-review/README.md
%{_bindir}/group-review %{_bindir}/group-review
%files -n autogits-obs-forward-bot %files -n obs-staging-bot
%license COPYING
%{_bindir}/obs-forward-bot
%files -n autogits-obs-staging-bot
%license COPYING %license COPYING
%doc obs-staging-bot/README.md %doc obs-staging-bot/README.md
%{_bindir}/obs-staging-bot %{_bindir}/obs-staging-bot
%{_unitdir}/obs-staging-bot.service
%files -n autogits-obs-status-service %files -n obs-status-service
%license COPYING %license COPYING
%doc obs-status-service/README.md %doc obs-status-service/README.md
%{_bindir}/obs-status-service %{_bindir}/obs-status-service
%files -n autogits-workflow-direct %files -n workflow-direct
%license COPYING %license COPYING
%doc workflow-direct/README.md %doc workflow-direct/README.md
%{_bindir}/workflow-direct %{_bindir}/workflow-direct
%files -n autogits-workflow-pr %files -n workflow-pr
%license COPYING %license COPYING
%doc workflow-pr/README.md %doc workflow-pr/README.md
%{_bindir}/workflow-pr %{_bindir}/workflow-pr

View File

@@ -39,7 +39,7 @@ func parsePrLine(line string) (BasicPR, error) {
repo := strings.SplitN(org[1], "!", 2) repo := strings.SplitN(org[1], "!", 2)
ret.Repo = repo[0] ret.Repo = repo[0]
if len(repo) != 2 { if len(repo) != 2 {
repo = strings.SplitN(org[1], "#", 2) repo := strings.SplitN(org[1], "#", 2)
ret.Repo = repo[0] ret.Repo = repo[0]
} }
if len(repo) != 2 { if len(repo) != 2 {

View File

@@ -34,7 +34,7 @@ func TestAssociatedPRScanner(t *testing.T) {
}, },
{ {
"Multiple PRs", "Multiple PRs",
"Some header of the issue\n\nFollowed by some description\nPR: test/foo#4\n\nPR: test/goo!5\n", "Some header of the issue\n\nFollowed by some description\nPR: test/foo#4\n\nPR: test/goo#5\n",
[]common.BasicPR{ []common.BasicPR{
{Org: "test", Repo: "foo", Num: 4}, {Org: "test", Repo: "foo", Num: 4},
{Org: "test", Repo: "goo", Num: 5}, {Org: "test", Repo: "goo", Num: 5},
@@ -107,7 +107,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
[]common.BasicPR{ []common.BasicPR{
{Org: "a", Repo: "b", Num: 100}, {Org: "a", Repo: "b", Num: 100},
}, },
"something\n\nPR: a/b!100", "something\n\nPR: a/b#100",
}, },
{ {
"Append multiple PR to end of description", "Append multiple PR to end of description",
@@ -119,7 +119,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
{Org: "b", Repo: "b", Num: 100}, {Org: "b", Repo: "b", Num: 100},
{Org: "c", Repo: "b", Num: 100}, {Org: "c", Repo: "b", Num: 100},
}, },
"something\n\nPR: a1/b!100\nPR: a1/c!100\nPR: a1/c!101\nPR: b/b!100\nPR: c/b!100", "something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
}, },
{ {
"Append multiple sorted PR to end of description and remove dups", "Append multiple sorted PR to end of description and remove dups",
@@ -133,7 +133,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
{Org: "a1", Repo: "c", Num: 101}, {Org: "a1", Repo: "c", Num: 101},
{Org: "a1", Repo: "b", Num: 100}, {Org: "a1", Repo: "b", Num: 100},
}, },
"something\n\nPR: a1/b!100\nPR: a1/c!100\nPR: a1/c!101\nPR: b/b!100\nPR: c/b!100", "something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
}, },
} }

View File

@@ -62,7 +62,6 @@ type AutogitConfig struct {
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers 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
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
} }

View File

@@ -109,7 +109,6 @@ func TestConfigWorkflowParser(t *testing.T) {
} }
} }
// FIXME: should test ReadWorkflowConfig as it will always set prjgit completely
func TestProjectGitParser(t *testing.T) { func TestProjectGitParser(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@@ -120,21 +119,20 @@ func TestProjectGitParser(t *testing.T) {
}{ }{
{ {
name: "repo only", name: "repo only",
prjgit: "repo.git#master", prjgit: "repo.git",
org: "org", org: "org",
branch: "br", branch: "br",
res: [3]string{"org", "repo.git", "master"}, res: [3]string{"org", "repo.git", "master"},
}, },
{ {
name: "default", name: "default",
org: "org", org: "org",
prjgit: "org/_ObsPrj#master", res: [3]string{"org", common.DefaultGitPrj, "master"},
res: [3]string{"org", common.DefaultGitPrj, "master"},
}, },
{ {
name: "repo with branch", name: "repo with branch",
org: "org2", org: "org2",
prjgit: "org2/repo.git#somebranch", prjgit: "repo.git#somebranch",
res: [3]string{"org2", "repo.git", "somebranch"}, res: [3]string{"org2", "repo.git", "somebranch"},
}, },
{ {
@@ -151,25 +149,25 @@ func TestProjectGitParser(t *testing.T) {
{ {
name: "repo org and empty branch", name: "repo org and empty branch",
org: "org3", org: "org3",
prjgit: "oorg/foo.bar#master", prjgit: "oorg/foo.bar#",
res: [3]string{"oorg", "foo.bar", "master"}, res: [3]string{"oorg", "foo.bar", "master"},
}, },
{ {
name: "only branch defined", name: "only branch defined",
org: "org3", org: "org3",
prjgit: "org3/_ObsPrj#mybranch", prjgit: "#mybranch",
res: [3]string{"org3", "_ObsPrj", "mybranch"}, res: [3]string{"org3", "_ObsPrj", "mybranch"},
}, },
{ {
name: "only org and branch defined", name: "only org and branch defined",
org: "org3", org: "org3",
prjgit: "org1/_ObsPrj#mybranch", prjgit: "org1/#mybranch",
res: [3]string{"org1", "_ObsPrj", "mybranch"}, res: [3]string{"org1", "_ObsPrj", "mybranch"},
}, },
{ {
name: "empty org and repo", name: "empty org and repo",
org: "org3", org: "org3",
prjgit: "org3/repo#master", prjgit: "/repo#",
res: [3]string{"org3", "repo", "master"}, res: [3]string{"org3", "repo", "master"},
}, },
} }

View File

@@ -67,7 +67,6 @@ type Git interface {
GitExecOrPanic(cwd string, params ...string) GitExecOrPanic(cwd string, params ...string)
GitExec(cwd string, params ...string) error GitExec(cwd string, params ...string) error
GitExecWithOutput(cwd string, params ...string) (string, error) GitExecWithOutput(cwd string, params ...string) (string, error)
GitExecQuietOrPanic(cwd string, params ...string)
GitDiffLister GitDiffLister
} }
@@ -77,8 +76,7 @@ type GitHandlerImpl struct {
GitCommiter string GitCommiter string
GitEmail string GitEmail string
lock *sync.Mutex lock *sync.Mutex
quiet bool
} }
func (s *GitHandlerImpl) GetPath() string { func (s *GitHandlerImpl) GetPath() string {
@@ -213,7 +211,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
return "", fmt.Errorf("Cannot parse remote URL: %w", err) return "", fmt.Errorf("Cannot parse remote URL: %w", err)
} }
remoteBranch := "HEAD" remoteBranch := "HEAD"
if len(branch) == 0 && remoteUrlComp != nil && remoteUrlComp.Commit != "HEAD" { if len(branch) == 0 && remoteUrlComp != nil {
branch = remoteUrlComp.Commit branch = remoteUrlComp.Commit
remoteBranch = branch remoteBranch = branch
} else if len(branch) > 0 { } else if len(branch) > 0 {
@@ -242,12 +240,12 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
// check if we have submodule to deinit // check if we have submodule to deinit
if list, _ := e.GitSubmoduleList(repo, "HEAD"); len(list) > 0 { if list, _ := e.GitSubmoduleList(repo, "HEAD"); len(list) > 0 {
e.GitExecQuietOrPanic(repo, "submodule", "deinit", "--all", "--force") e.GitExecOrPanic(repo, "submodule", "deinit", "--all", "--force")
} }
e.GitExecOrPanic(repo, "fetch", "--prune", remoteName, remoteBranch) e.GitExecOrPanic(repo, "fetch", "--prune", remoteName, remoteBranch)
} }
/*
refsBytes, err := os.ReadFile(path.Join(e.GitPath, repo, ".git/refs/remotes", remoteName, "HEAD")) refsBytes, err := os.ReadFile(path.Join(e.GitPath, repo, ".git/refs/remotes", remoteName, "HEAD"))
if err != nil { if err != nil {
LogError("Cannot read HEAD of remote", remoteName) LogError("Cannot read HEAD of remote", remoteName)
@@ -266,7 +264,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
LogDebug("remoteRef", remoteRef) LogDebug("remoteRef", remoteRef)
LogDebug("branch", branch) LogDebug("branch", branch)
} }
*/
args := []string{"fetch", "--prune", remoteName, branch} args := []string{"fetch", "--prune", remoteName, branch}
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" { if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
args = slices.Insert(args, 1, "--unshallow") args = slices.Insert(args, 1, "--unshallow")
@@ -276,7 +274,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
} }
func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) { func (e *GitHandlerImpl) GitBranchHead(gitDir, branchName string) (string, error) {
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--heads", "--hash", branchName) id, err := e.GitExecWithOutput(gitDir, "show-ref", "--branches", "--hash", branchName)
if err != nil { if err != nil {
return "", fmt.Errorf("Can't find default branch: %s", branchName) return "", fmt.Errorf("Can't find default branch: %s", branchName)
} }
@@ -363,9 +361,7 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
LogDebug("git execute @", cwd, ":", cmd.Args) LogDebug("git execute @", cwd, ":", cmd.Args)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if !e.quiet { LogDebug(string(out))
LogDebug(string(out))
}
if err != nil { if err != nil {
LogError("git", cmd.Args, " error:", err) LogError("git", cmd.Args, " error:", err)
return "", fmt.Errorf("error executing: git %#v \n%s\n err: %w", cmd.Args, out, err) return "", fmt.Errorf("error executing: git %#v \n%s\n err: %w", cmd.Args, out, err)
@@ -374,13 +370,6 @@ func (e *GitHandlerImpl) GitExecWithOutput(cwd string, params ...string) (string
return string(out), nil return string(out), nil
} }
func (e *GitHandlerImpl) GitExecQuietOrPanic(cwd string, params ...string) {
e.quiet = true
e.GitExecOrPanic(cwd, params...)
e.quiet = false
return
}
type ChanIO struct { type ChanIO struct {
ch chan byte ch chan byte
} }

View File

@@ -24,13 +24,11 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"slices" "slices"
"strconv"
"time" "time"
transport "github.com/go-openapi/runtime/client" transport "github.com/go-openapi/runtime/client"
@@ -184,6 +182,7 @@ type Gitea interface {
GiteaCommitStatusGetter GiteaCommitStatusGetter
GiteaCommitStatusSetter GiteaCommitStatusSetter
GiteaSetRepoOptions GiteaSetRepoOptions
GiteaTimelineFetcher
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)
@@ -200,32 +199,7 @@ type Gitea interface {
GetCurrentUser() (*models.User, error) GetCurrentUser() (*models.User, error)
} }
type GiteaHeaderInterceptor struct {
Length int
http.RoundTripper
}
func (i *GiteaHeaderInterceptor) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := i.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
count_header := resp.Header["X-Total-Count"]
if len(count_header) == 1 {
i.Length, err = strconv.Atoi(resp.Header["X-Total-Count"][0])
if err != nil {
LogError("Converting X-Total-Count response header error", err)
i.Length = -1
return nil, err
}
} else {
i.Length = -1
}
return resp, nil
}
type GiteaTransport struct { type GiteaTransport struct {
headers *GiteaHeaderInterceptor
transport *transport.Runtime transport *transport.Runtime
client *apiclient.GiteaAPI client *apiclient.GiteaAPI
} }
@@ -238,9 +212,7 @@ func AllocateGiteaTransport(giteaUrl string) Gitea {
log.Panicln("Failed to parse gitea url:", err) log.Panicln("Failed to parse gitea url:", err)
} }
r.headers = &GiteaHeaderInterceptor{RoundTripper: http.DefaultTransport}
r.transport = transport.New(url.Host, apiclient.DefaultBasePath, [](string){url.Scheme}) r.transport = transport.New(url.Host, apiclient.DefaultBasePath, [](string){url.Scheme})
r.transport.Transport = r.headers
r.transport.DefaultAuthentication = transport.BearerToken(giteaToken) r.transport.DefaultAuthentication = transport.BearerToken(giteaToken)
r.client = apiclient.New(r.transport, nil) r.client = apiclient.New(r.transport, nil)
@@ -315,9 +287,10 @@ func (gitea *GiteaTransport) ManualMergePR(org, repo string, num int64, commitid
} }
func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRequest, error) { func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRequest, error) {
var page int64 var page, limit int64
prs := make([]*models.PullRequest, 0) prs := make([]*models.PullRequest, 0)
limit = 20
state := "open" state := "open"
for { for {
@@ -329,18 +302,16 @@ func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRe
WithOwner(org). WithOwner(org).
WithRepo(repo). WithRepo(repo).
WithState(&state). WithState(&state).
WithPage(&page), WithPage(&page).
WithLimit(&limit),
gitea.transport.DefaultAuthentication) gitea.transport.DefaultAuthentication)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", org, repo, err) return nil, fmt.Errorf("cannot fetch PR list for %s / %s : %w", org, repo, err)
} }
if len(req.Payload) == 0 {
break
}
prs = slices.Concat(prs, req.Payload) prs = slices.Concat(prs, req.Payload)
if len(prs) >= gitea.headers.Length { if len(req.Payload) < int(limit) {
break break
} }
} }
@@ -349,23 +320,21 @@ func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRe
} }
func (gitea *GiteaTransport) GetCommitStatus(org, repo, hash string) ([]*models.CommitStatus, error) { func (gitea *GiteaTransport) GetCommitStatus(org, repo, hash string) ([]*models.CommitStatus, error) {
var page int64 page := int64(1)
limit := int64(10)
var res []*models.CommitStatus var res []*models.CommitStatus
for { for {
page++
r, err := gitea.client.Repository.RepoListStatuses( r, err := gitea.client.Repository.RepoListStatuses(
repository.NewRepoListStatusesParams().WithDefaults().WithOwner(org).WithRepo(repo).WithSha(hash).WithPage(&page), repository.NewRepoListStatusesParams().WithDefaults().WithOwner(org).WithRepo(repo).WithSha(hash).WithPage(&page).WithLimit(&limit),
gitea.transport.DefaultAuthentication) gitea.transport.DefaultAuthentication)
if err != nil { if err != nil {
return res, err return res, err
} }
if len(r.Payload) == 0 {
break
}
res = append(res, r.Payload...) res = append(res, r.Payload...)
if len(res) >= gitea.headers.Length { if len(r.Payload) < int(limit) {
break break
} }
} }
@@ -408,18 +377,19 @@ func (gitea *GiteaTransport) GetRepository(org, pkg string) (*models.Repository,
} }
func (gitea *GiteaTransport) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) { func (gitea *GiteaTransport) GetPullRequestReviews(org, project string, PRnum int64) ([]*models.PullReview, error) {
limit := int64(20)
var page int64 var page int64
var allReviews []*models.PullReview var allReviews []*models.PullReview
for { for {
page++
reviews, err := gitea.client.Repository.RepoListPullReviews( reviews, err := gitea.client.Repository.RepoListPullReviews(
repository.NewRepoListPullReviewsParams(). repository.NewRepoListPullReviewsParams().
WithDefaults(). WithDefaults().
WithOwner(org). WithOwner(org).
WithRepo(project). WithRepo(project).
WithIndex(PRnum). WithIndex(PRnum).
WithPage(&page), WithPage(&page).
WithLimit(&limit),
gitea.transport.DefaultAuthentication, gitea.transport.DefaultAuthentication,
) )
@@ -427,13 +397,11 @@ func (gitea *GiteaTransport) GetPullRequestReviews(org, project string, PRnum in
return nil, err return nil, err
} }
if len(reviews.Payload) == 0 {
break
}
allReviews = slices.Concat(allReviews, reviews.Payload) allReviews = slices.Concat(allReviews, reviews.Payload)
if len(allReviews) >= gitea.headers.Length { if len(reviews.Payload) < int(limit) {
break break
} }
page++
} }
return allReviews, nil return allReviews, nil
@@ -501,6 +469,7 @@ const (
) )
func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) { func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
bigLimit := int64(20)
ret := make([]*models.NotificationThread, 0, 100) ret := make([]*models.NotificationThread, 0, 100)
for page := int64(1); ; page++ { for page := int64(1); ; page++ {
@@ -508,6 +477,7 @@ func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]
WithDefaults(). WithDefaults().
WithSubjectType([]string{Type}). WithSubjectType([]string{Type}).
WithStatusTypes([]string{"unread"}). WithStatusTypes([]string{"unread"}).
WithLimit(&bigLimit).
WithPage(&page) WithPage(&page)
if since != nil { if since != nil {
@@ -520,11 +490,8 @@ func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]
return nil, err return nil, err
} }
if len(list.Payload) == 0 {
break
}
ret = slices.Concat(ret, list.Payload) ret = slices.Concat(ret, list.Payload)
if len(ret) >= gitea.headers.Length { if len(list.Payload) < int(bigLimit) {
break break
} }
} }
@@ -533,6 +500,7 @@ func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]
} }
func (gitea *GiteaTransport) GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error) { func (gitea *GiteaTransport) GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error) {
limit := int64(20)
t := true t := true
if page <= 0 { if page <= 0 {
@@ -543,6 +511,7 @@ func (gitea *GiteaTransport) GetDoneNotifications(Type string, page int64) ([]*m
WithAll(&t). WithAll(&t).
WithSubjectType([]string{Type}). WithSubjectType([]string{Type}).
WithStatusTypes([]string{"read"}). WithStatusTypes([]string{"read"}).
WithLimit(&limit).
WithPage(&page), WithPage(&page),
gitea.transport.DefaultAuthentication) gitea.transport.DefaultAuthentication)
if err != nil { if err != nil {
@@ -595,12 +564,9 @@ func (gitea *GiteaTransport) GetOrganizationRepositories(orgName string) ([]*mod
if len(ret.Payload) == 0 { if len(ret.Payload) == 0 {
break break
} }
repos = append(repos, ret.Payload...) repos = append(repos, ret.Payload...)
page++ page++
if len(repos) >= gitea.headers.Length {
break
}
} }
return repos, nil return repos, nil
@@ -814,18 +780,15 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
resCount = len(res.Payload) resCount = len(res.Payload)
LogDebug("page:", page, "len:", resCount) LogDebug("page:", page, "len:", resCount)
if resCount == 0 {
break
}
page++ page++
retData = append(retData, res.Payload...) for _, d := range res.Payload {
if len(retData) >= gitea.headers.Length { if d != nil {
break retData = append(retData, d)
}
} }
} }
LogDebug("total results:", len(retData)) LogDebug("total results:", len(retData))
retData = slices.DeleteFunc(retData, func(a *models.TimelineComment) bool { return a == nil })
slices.SortFunc(retData, func(a, b *models.TimelineComment) int { slices.SortFunc(retData, 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))
}) })

View File

@@ -63,10 +63,6 @@ func SetLoggingLevel(ll LogLevel) {
logLevel = ll logLevel = ll
} }
func GetLoggingLevel() LogLevel {
return logLevel
}
func SetLoggingLevelFromString(ll string) error { func SetLoggingLevelFromString(ll string) error {
switch ll { switch ll {
case "info": case "info":

View File

@@ -63,6 +63,7 @@ func readPRData(gitea GiteaPRFetcher, pr *models.PullRequest, currentSet []*PRIn
var Timeline_RefIssueNotFound error = errors.New("RefIssue not found on the timeline") var Timeline_RefIssueNotFound error = errors.New("RefIssue not found on the timeline")
func LastPrjGitRefOnTimeline(gitea GiteaPRTimelineFetcher, org, repo string, num int64, prjGitOrg, prjGitRepo string) (*models.PullRequest, error) { func LastPrjGitRefOnTimeline(gitea GiteaPRTimelineFetcher, org, repo string, num int64, prjGitOrg, prjGitRepo string) (*models.PullRequest, error) {
prRefLine := fmt.Sprintf(PrPattern, org, repo, num)
timeline, err := gitea.GetTimeline(org, repo, num) timeline, err := gitea.GetTimeline(org, repo, num)
if err != nil { if err != nil {
LogError("Failed to fetch timeline for", org, repo, "#", num, err) LogError("Failed to fetch timeline for", org, repo, "#", num, err)
@@ -78,9 +79,9 @@ func LastPrjGitRefOnTimeline(gitea GiteaPRTimelineFetcher, org, repo string, num
issue.Repository.Owner == prjGitOrg && issue.Repository.Owner == prjGitOrg &&
issue.Repository.Name == prjGitRepo { issue.Repository.Name == prjGitRepo {
_, prs := ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(item.RefIssue.Body))) lines := SplitLines(item.RefIssue.Body)
for _, pr := range prs { for _, line := range lines {
if pr.Org == org && pr.Repo == repo && pr.Num == num { if strings.TrimSpace(line) == prRefLine {
LogDebug("Found PrjGit PR in Timeline:", issue.Index) LogDebug("Found PrjGit PR in Timeline:", issue.Index)
// found prjgit PR in timeline. Return it // found prjgit PR in timeline. Return it
@@ -281,12 +282,6 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
return nil return nil
} }
func (rs *PRSet) RemoveClosedPRs() {
rs.PRs = slices.DeleteFunc(rs.PRs, func(pr *PRInfo) bool {
return pr.PR.State != "open"
})
}
func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData) bool { func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData) bool {
configReviewers := ParseReviewers(rs.Config.Reviewers) configReviewers := ParseReviewers(rs.Config.Reviewers)
@@ -385,8 +380,7 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
} }
prjgit := prjgit_info.PR prjgit := prjgit_info.PR
_, _, prjgitBranch := rs.Config.GetPrjGit() remote, err := git.GitClone(DefaultGitPrj, rs.Config.Branch, prjgit.Base.Repo.SSHURL)
remote, err := git.GitClone(DefaultGitPrj, prjgitBranch, prjgit.Base.Repo.SSHURL)
PanicOnError(err) PanicOnError(err)
git.GitExecOrPanic(DefaultGitPrj, "fetch", remote, prjgit.Head.Sha) git.GitExecOrPanic(DefaultGitPrj, "fetch", remote, prjgit.Head.Sha)
@@ -415,7 +409,6 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
// we can only resolve conflicts with .gitmodules // we can only resolve conflicts with .gitmodules
for _, s := range status { for _, s := range status {
if s.Status == GitStatus_Unmerged { if s.Status == GitStatus_Unmerged {
panic("Can't handle conflicts yet")
if s.Path != ".gitmodules" { if s.Path != ".gitmodules" {
return err return err
} }
@@ -506,15 +499,7 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
if rs.IsPrjGitPR(prinfo.PR) { if rs.IsPrjGitPR(prinfo.PR) {
continue continue
} }
br := rs.Config.Branch prinfo.RemoteName, err = git.GitClone(repo.Name, rs.Config.Branch, repo.SSHURL)
if len(br) == 0 {
// if branch is unspecified, take it from the PR as it
// matches default branch already
br = prinfo.PR.Base.Name
} else if br != prinfo.PR.Base.Name {
panic(prinfo.PR.Base.Name + " is expected to match " + br)
}
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
PanicOnError(err) PanicOnError(err)
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha) git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha) git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)

View File

@@ -48,13 +48,11 @@ func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
} }
func TestPR(t *testing.T) { func TestPR(t *testing.T) {
return
baseConfig := common.AutogitConfig{ baseConfig := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "foo/barPrj#master", GitProjectName: "barPrj",
} }
type prdata struct { type prdata struct {
@@ -640,7 +638,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "No reviewers", name: "No reviewers",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{}, Reviewers: []string{},
@@ -650,7 +648,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "One project reviewer only", name: "One project reviewer only",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1"}, Reviewers: []string{"-user1"},
@@ -660,7 +658,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "One project reviewer and one pkg reviewer only", name: "One project reviewer and one pkg reviewer only",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1", "user2"}, Reviewers: []string{"-user1", "user2"},
@@ -670,7 +668,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "No need to get reviews of submitter", name: "No need to get reviews of submitter",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1", "submitter"}, Reviewers: []string{"-user1", "submitter"},
@@ -680,7 +678,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "Reviews are done", name: "Reviews are done",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1", "user2"}, Reviewers: []string{"-user1", "user2"},
@@ -714,7 +712,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "Stale review is not done, re-request it", name: "Stale review is not done, re-request it",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "org/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1", "user2"}, Reviewers: []string{"-user1", "user2"},
@@ -746,7 +744,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "Stale optional review is not done, re-request it", name: "Stale optional review is not done, re-request it",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "prg/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{"-user1", "user2", "~bot"}, Reviewers: []string{"-user1", "user2", "~bot"},
@@ -854,7 +852,7 @@ func TestPRAssignReviewers(t *testing.T) {
{ {
name: "PrjMaintainers in prjgit review when not part of pkg set", name: "PrjMaintainers in prjgit review when not part of pkg set",
config: common.AutogitConfig{ config: common.AutogitConfig{
GitProjectName: "org/repo#main", GitProjectName: "repo",
Organization: "org", Organization: "org",
Branch: "main", Branch: "main",
Reviewers: []string{}, Reviewers: []string{},
@@ -925,7 +923,7 @@ func TestPRMerge(t *testing.T) {
config := &common.AutogitConfig{ config := &common.AutogitConfig{
Organization: "org", Organization: "org",
GitProjectName: "org/prj#master", GitProjectName: "prj",
} }
tests := []struct { tests := []struct {

View File

@@ -164,7 +164,7 @@ func (l *RabbitConnection) ConnectAndProcessRabbitMQ(ch chan<- RabbitMessage) {
for { for {
err := l.ProcessRabbitMQ(ch) err := l.ProcessRabbitMQ(ch)
if err != nil { if err != nil {
LogError("Error in RabbitMQ connection:", err) LogError("Error in RabbitMQ connection. %#v", err)
LogInfo("Reconnecting in 2 seconds...") LogInfo("Reconnecting in 2 seconds...")
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }

View File

@@ -99,12 +99,12 @@ func (gitea *RabbitMQGiteaEventsProcessor) ProcessRabbitMessage(msg RabbitMessag
req, err := ParseRequestJSON(reqType, msg.Body) req, err := ParseRequestJSON(reqType, msg.Body)
if err != nil { if err != nil {
LogError("Error parsing request JSON:", err) LogError("Error parsing request JSON:", err)
return nil
} else { } else {
LogDebug("processing req", req.Type) LogDebug("processing req", req.Type)
// h.Request = req // h.Request = req
ProcessEvent(handler, req) ProcessEvent(handler, req)
} }
return nil
} }
} }

4
go.mod
View File

@@ -10,10 +10,8 @@ require (
github.com/go-openapi/validate v0.24.0 github.com/go-openapi/validate v0.24.0
github.com/opentracing/opentracing-go v1.2.0 github.com/opentracing/opentracing-go v1.2.0
github.com/rabbitmq/amqp091-go v1.10.0 github.com/rabbitmq/amqp091-go v1.10.0
github.com/redis/go-redis/v9 v9.11.0
github.com/tailscale/hujson v0.0.0-20250226034555-ec1d1c113d33 github.com/tailscale/hujson v0.0.0-20250226034555-ec1d1c113d33
go.uber.org/mock v0.5.0 go.uber.org/mock v0.5.0
gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
@@ -32,9 +30,11 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/redis/go-redis/v9 v9.11.0 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

4
go.sum
View File

@@ -1,9 +1,5 @@
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -125,16 +125,6 @@ func FindAcceptableReviewInTimeline(user string, timeline []*models.TimelineComm
return nil return nil
} }
func FindOurLastReviewInTimeline(timeline []*models.TimelineComment) *models.TimelineComment {
for _, t := range timeline {
if t.Type == common.TimelineCommentType_Review && t.User.UserName == groupName && t.Created == t.Updated {
return t
}
}
return nil
}
func UnrequestReviews(gitea common.Gitea, org, repo string, id int64, users []string) { func UnrequestReviews(gitea common.Gitea, org, repo string, id int64, users []string) {
if err := gitea.UnrequestReview(org, repo, id, users...); err != nil { if err := gitea.UnrequestReview(org, repo, id, users...); err != nil {
common.LogError("Can't remove reviewrs after a review:", err) common.LogError("Can't remove reviewrs after a review:", err)
@@ -174,22 +164,6 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
return return
} }
if err := ProcessPR(pr); err == nil && !common.IsDryRun {
if err := gitea.SetNotificationRead(notification.ID); err != nil {
common.LogDebug(" Cannot set notification as read", err)
}
} else if err != nil && err != ReviewNotFinished {
common.LogError(err)
}
}
var ReviewNotFinished = fmt.Errorf("Review is not finished")
func ProcessPR(pr *models.PullRequest) error {
org := pr.Base.Repo.Owner.UserName
repo := pr.Base.Repo.Name
id := pr.Index
found := false found := false
for _, reviewer := range pr.RequestedReviewers { for _, reviewer := range pr.RequestedReviewers {
if reviewer != nil && reviewer.UserName == groupName { if reviewer != nil && reviewer.UserName == groupName {
@@ -199,32 +173,42 @@ func ProcessPR(pr *models.PullRequest) error {
} }
if !found { if !found {
common.LogInfo(" review is not requested for", groupName) common.LogInfo(" review is not requested for", groupName)
return nil if !common.IsDryRun {
gitea.SetNotificationRead(notification.ID)
}
return
} }
config := configs.GetPrjGitConfig(org, repo, pr.Base.Name) config := configs.GetPrjGitConfig(org, repo, pr.Base.Name)
if config == nil { if config == nil {
return fmt.Errorf("Cannot find config for: %s", pr.URL) common.LogError("Cannot find config for:", fmt.Sprintf("%s/%s!%s", org, repo, pr.Base.Name))
return
} }
if pr.State == "closed" { if pr.State == "closed" {
// dismiss the review // dismiss the review
common.LogInfo(" -- closed request, so nothing to review") common.LogInfo(" -- closed request, so nothing to review")
return nil if !common.IsDryRun {
gitea.SetNotificationRead(notification.ID)
}
return
} }
reviews, err := gitea.GetPullRequestReviews(org, repo, id) reviews, err := gitea.GetPullRequestReviews(org, repo, id)
if err != nil { if err != nil {
return fmt.Errorf("Failed to fetch reviews for: %v: %w", pr.URL, err) common.LogInfo(" ** No reviews associated with request:", subject.URL, "Error:", err)
return
} }
timeline, err := common.FetchTimelineSinceReviewRequestOrPush(gitea, groupName, pr.Head.Sha, org, repo, id) timeline, err := common.FetchTimelineSinceReviewRequestOrPush(gitea, groupName, pr.Head.Sha, org, repo, id)
if err != nil { if err != nil {
return fmt.Errorf("Failed to fetch timeline to review. %w", err) common.LogError(err)
return
} }
groupConfig, err := config.GetReviewGroup(groupName) groupConfig, err := config.GetReviewGroup(groupName)
if err != nil { if err != nil {
return fmt.Errorf("Failed to fetch review group. %w", err) common.LogError(err)
return
} }
// submitter cannot be reviewer // submitter cannot be reviewer
@@ -236,31 +220,27 @@ func ProcessPR(pr *models.PullRequest) error {
if review := FindAcceptableReviewInTimeline(reviewer, timeline, reviews); review != nil { if review := FindAcceptableReviewInTimeline(reviewer, timeline, reviews); review != nil {
if ReviewAccepted(review.Body) { if ReviewAccepted(review.Body) {
if !common.IsDryRun { if !common.IsDryRun {
text := reviewer + " approved a review on behalf of " + groupName gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+reviewer)
if review := FindOurLastReviewInTimeline(timeline); review == nil || review.Body != text { UnrequestReviews(gitea, org, repo, id, requestReviewers)
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, text) if !common.IsDryRun {
if err != nil { if err := gitea.SetNotificationRead(notification.ID); err != nil {
common.LogError(" -> failed to write approval comment", err) common.LogDebug(" Cannot set notification as read", err)
} }
UnrequestReviews(gitea, org, repo, id, requestReviewers)
} }
} }
common.LogInfo(" -> approved by", reviewer) common.LogInfo(" -> approved by", reviewer)
common.LogInfo(" review at", review.Created) common.LogInfo(" review at", review.Created)
return nil return
} else if ReviewRejected(review.Body) { } else if ReviewRejected(review.Body) {
if !common.IsDryRun { if !common.IsDryRun {
text := reviewer + " requested changes on behalf of " + groupName + ". See " + review.HTMLURL gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer)
if review := FindOurLastReviewInTimeline(timeline); review == nil || review.Body != text { UnrequestReviews(gitea, org, repo, id, requestReviewers)
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer) if err := gitea.SetNotificationRead(notification.ID); err != nil {
if err != nil { common.LogDebug(" Cannot set notification as read", err)
common.LogError(" -> failed to write rejecting comment", err)
}
UnrequestReviews(gitea, org, repo, id, requestReviewers)
} }
} }
common.LogInfo(" -> declined by", reviewer) common.LogInfo(" -> declined by", reviewer)
return nil return
} }
} }
} }
@@ -290,22 +270,15 @@ func ProcessPR(pr *models.PullRequest) error {
} }
if !found_help_comment && !common.IsDryRun { if !found_help_comment && !common.IsDryRun {
helpComment := fmt.Sprintln("Review by", groupName, "represents a group of reviewers:", strings.Join(requestReviewers, ", "), ".\n\n"+ helpComment := fmt.Sprintln("Review by", groupName, "represents a group of reviewers:", strings.Join(requestReviewers, ", "), ". To review as part of this group, create a comment with contents @"+groupName+": LGTM on a separate line to accept a review. To request changes, write @"+groupName+": followed by reason for rejection. Do not use reviews to review as a group. Editing a comment invalidates that comment.")
"Do **not** use standard review interface to review on behalf of the group.\n"+
"To accept the review on behalf of the group, create the following comment: `@"+groupName+": approve`.\n"+
"To request changes on behalf of the group, create the following comment: `@"+groupName+": decline` followed with lines justifying the decision.\n"+
"Future edits of the comments are ignored, a new comment is required to change the review state.")
if slices.Contains(groupConfig.Reviewers, pr.User.UserName) { if slices.Contains(groupConfig.Reviewers, pr.User.UserName) {
helpComment = helpComment + "\n\n" + helpComment = helpComment + "\n\n" + fmt.Sprintln("Submitter is member of this review group, hence they are excluded from being one of the reviewers here")
"Submitter is member of this review group, hence they are excluded from being one of the reviewers here"
} }
gitea.AddComment(pr, helpComment) gitea.AddComment(pr, helpComment)
} }
return ReviewNotFinished
} }
func PeriodReviewCheck() { func PeriodReviewCheck(gitea common.Gitea) {
notifications, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil) notifications, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
if err != nil { if err != nil {
common.LogError(" Error fetching unread notifications: %w", err) common.LogError(" Error fetching unread notifications: %w", err)
@@ -314,15 +287,14 @@ func PeriodReviewCheck() {
for _, notification := range notifications { for _, notification := range notifications {
ProcessNotifications(notification, gitea) ProcessNotifications(notification, gitea)
} }
} }
var gitea common.Gitea
func main() { func main() {
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance used for reviews") giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance used for reviews")
rabbitMqHost := flag.String("rabbit-url", "amqps://rabbit.opensuse.org", "RabbitMQ instance where Gitea webhook notifications are sent") rabbitMqHost := flag.String("rabbit-url", "amqps://rabbit.opensuse.org", "RabbitMQ instance where Gitea webhook notifications are sent")
interval := flag.Int64("interval", 10, "Notification polling interval in minutes (min 1 min)") interval := flag.Int64("interval", 5, "Notification polling interval in minutes (min 1 min)")
configFile := flag.String("config", "", "PrjGit listing config file") configFile := flag.String("config", "", "PrjGit listing config file")
logging := flag.String("logging", "info", "Logging level: [none, error, info, debug]") logging := flag.String("logging", "info", "Logging level: [none, error, info, debug]")
flag.BoolVar(&common.IsDryRun, "dry", false, "Dry run, no effect. For debugging") flag.BoolVar(&common.IsDryRun, "dry", false, "Dry run, no effect. For debugging")
@@ -359,7 +331,7 @@ func main() {
return return
} }
gitea = common.AllocateGiteaTransport(*giteaUrl) gitea := common.AllocateGiteaTransport(*giteaUrl)
configs, err = common.ResolveWorkflowConfigs(gitea, configData) configs, err = common.ResolveWorkflowConfigs(gitea, configData)
if err != nil { if err != nil {
common.LogError("Cannot parse workflow configs:", err) common.LogError("Cannot parse workflow configs:", err)
@@ -403,13 +375,10 @@ func main() {
config_modified: make(chan *common.AutogitConfig), config_modified: make(chan *common.AutogitConfig),
} }
process_issue_pr := IssueCommentProcessor{}
configUpdates := &common.RabbitMQGiteaEventsProcessor{ configUpdates := &common.RabbitMQGiteaEventsProcessor{
Orgs: []string{}, Orgs: []string{},
Handlers: map[string]common.RequestProcessor{ Handlers: map[string]common.RequestProcessor{
common.RequestType_Push: &config_update, common.RequestType_Push: &config_update,
common.RequestType_IssueComment: &process_issue_pr,
}, },
} }
configUpdates.Connection().RabbitURL = u configUpdates.Connection().RabbitURL = u
@@ -446,7 +415,7 @@ func main() {
} }
} }
PeriodReviewCheck() PeriodReviewCheck(gitea)
time.Sleep(time.Duration(*interval * int64(time.Minute))) time.Sleep(time.Duration(*interval * int64(time.Minute)))
} }
} }

View File

@@ -7,25 +7,6 @@ import (
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
) )
type IssueCommentProcessor struct{}
func (s *IssueCommentProcessor) ProcessFunc(req *common.Request) error {
if req.Type != common.RequestType_IssueComment {
return fmt.Errorf("Unhandled, ignored request type: %s", req.Type)
}
data := req.Data.(*common.IssueCommentWebhookEvent)
org := data.Repository.Owner.Username
repo := data.Repository.Name
index := int64(data.Issue.Number)
pr, err := gitea.GetPullRequest(org, repo, index)
if err != nil {
return fmt.Errorf("Failed to fetch PullRequest from event: %s/%s!%d Error: %w", org, repo, index, err)
}
return ProcessPR(pr)
}
type ConfigUpdatePush struct { type ConfigUpdatePush struct {
config_modified chan *common.AutogitConfig config_modified chan *common.AutogitConfig
} }

View File

@@ -1,27 +0,0 @@
package main
import (
"io"
"log"
"os"
"github.com/tailscale/hujson"
)
func main() {
data, err := io.ReadAll(os.Stdin)
if err != nil {
log.Println(err)
os.Exit(1)
}
json, err := hujson.Standardize(data)
if err != nil {
log.Println(err)
os.Exit(2)
}
_, err = os.Stdout.Write(json)
if err != nil {
log.Print(err)
os.Exit(3)
}
}

View File

@@ -263,7 +263,7 @@ func ProcessRepoBuildStatus(results, ref []*common.PackageBuildStatus) (status B
return BuildStatusSummarySuccess, SomeSuccess return BuildStatusSummarySuccess, SomeSuccess
} }
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) { func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string) (*common.ProjectMeta, error) {
common.LogDebug("repo content fetching ...") common.LogDebug("repo content fetching ...")
err := FetchPrGit(git, pr) err := FetchPrGit(git, pr)
if err != nil { if err != nil {
@@ -289,15 +289,7 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
} }
} }
common.LogDebug("Trying first staging master project: ", stagingMasterPrj) meta, err := ObsClient.GetProjectMeta(buildPrj)
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
if err == nil {
// success, so we use that staging master project as our build project
buildPrj = stagingMasterPrj
} else {
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
meta, err = ObsClient.GetProjectMeta(buildPrj)
}
if err != nil { if err != nil {
common.LogError("error fetching project meta for", buildPrj, ". Err:", err) common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
return nil, err return nil, err
@@ -322,13 +314,10 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
urlPkg = append(urlPkg, "onlybuild="+url.QueryEscape(pkg)) urlPkg = append(urlPkg, "onlybuild="+url.QueryEscape(pkg))
} }
meta.ScmSync = pr.Head.Repo.CloneURL + "?" + strings.Join(urlPkg, "&") + "#" + pr.Head.Sha meta.ScmSync = pr.Head.Repo.CloneURL + "?" + strings.Join(urlPkg, "&") + "#" + pr.Head.Sha
if len(meta.ScmSync) >= 65535 {
return nil, errors.New("Reached max amount of package changes per request")
}
meta.Title = fmt.Sprintf("PR#%d to %s", pr.Index, pr.Base.Name) meta.Title = fmt.Sprintf("PR#%d to %s", pr.Index, pr.Base.Name)
// QE wants it published ... also we should not hardcode it here, since // QE wants it published ... also we should not hardcode it here, since
// it is configurable via the :PullRequest project // it is configurable via the :PullRequest project
// meta.PublicFlags = common.Flags{Contents: "<disable/>"} // meta.PublicFlags = common.Flags{Contents: "<disable/>"}
meta.Groups = nil meta.Groups = nil
meta.Persons = nil meta.Persons = nil
@@ -425,8 +414,7 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
var state RequestModification = RequestModificationSourceChanged var state RequestModification = RequestModificationSourceChanged
if meta == nil { if meta == nil {
// new build // new build
common.LogDebug(" Staging master:", config.StagingProject) meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject)
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
if err != nil { if err != nil {
return RequestModificationNoChange, err return RequestModificationNoChange, err
} }
@@ -440,8 +428,6 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
} else { } else {
err = ObsClient.SetProjectMeta(meta) err = ObsClient.SetProjectMeta(meta)
if err != nil { if err != nil {
x, _ := xml.MarshalIndent(meta, "", " ")
common.LogDebug(" meta:", string(x))
common.LogError("cannot create meta project:", err) common.LogError("cannot create meta project:", err)
return RequestModificationNoChange, err return RequestModificationNoChange, err
} }
@@ -598,7 +584,7 @@ func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThre
} }
if !pr.HasMerged && time.Since(time.Time(pr.Closed)) < time.Duration(config.CleanupDelay)*time.Hour { if !pr.HasMerged && time.Since(time.Time(pr.Closed)) < time.Duration(config.CleanupDelay)*time.Hour {
common.LogInfo("Cooldown period for cleanup of", thread.Subject.HTMLURL) common.LogInfo("Cooldown period for cleanup of", thread.URL)
return false return false
} }
@@ -636,14 +622,6 @@ func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThre
return false // cleaned up now, but the cleanup was not aleady done return false // cleaned up now, but the cleanup was not aleady done
} }
func SetStatus(gitea common.Gitea, org, repo, hash string, status *models.CommitStatus) error {
_, err := gitea.SetCommitStatus(org, repo, hash, status)
if err != nil {
common.LogError(err)
}
return err
}
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) { func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
dir, err := os.MkdirTemp(os.TempDir(), BotName) dir, err := os.MkdirTemp(os.TempDir(), BotName)
common.PanicOnError(err) common.PanicOnError(err)
@@ -827,12 +805,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
if !rebuild_all { if !rebuild_all {
common.LogInfo("No package changes detected. Ignoring") common.LogInfo("No package changes detected. Ignoring")
if !IsDryRun { if !IsDryRun {
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, "No package changes, not rebuilding project by default, accepting change") _, err = gitea.AddReviewComment(pr, common.ReviewStateComment, "No package changes. Not rebuilding project by default")
if err != nil {
common.LogError(err)
} else {
return true, nil
}
} }
return true, err return true, err
} }
@@ -848,22 +821,6 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
TargetURL: ObsWebHost + "/project/show/" + stagingProject, TargetURL: ObsWebHost + "/project/show/" + stagingProject,
} }
if err != nil {
msg := "Unable to setup stage project " + stagingConfig.ObsProject
status.Status = common.CommitStatus_Fail
common.LogError(msg)
if !IsDryRun {
SetStatus(gitea, org, repo, pr.Head.Sha, status)
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, msg)
if err != nil {
common.LogError(err)
} else {
return true, nil
}
}
return false, nil
}
msg := "Changed source updated for build" msg := "Changed source updated for build"
if change == RequestModificationProjectCreated { if change == RequestModificationProjectCreated {
msg = "Build is started in " + ObsWebHost + "/project/show/" + msg = "Build is started in " + ObsWebHost + "/project/show/" +
@@ -872,7 +829,8 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
if len(stagingConfig.QA) > 0 { if len(stagingConfig.QA) > 0 {
msg = msg + "\nAdditional QA builds: \n" msg = msg + "\nAdditional QA builds: \n"
} }
SetStatus(gitea, org, repo, pr.Head.Sha, status) gitea.SetCommitStatus(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Head.Sha, status)
for _, setup := range stagingConfig.QA { for _, setup := range stagingConfig.QA {
CreateQASubProject(stagingConfig, git, gitea, pr, CreateQASubProject(stagingConfig, git, gitea, pr,
stagingProject, stagingProject,
@@ -896,34 +854,32 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
} }
buildStatus := ProcessBuildStatus(stagingResult, baseResult) buildStatus := ProcessBuildStatus(stagingResult, baseResult)
done := false
switch buildStatus { switch buildStatus {
case BuildStatusSummarySuccess: case BuildStatusSummarySuccess:
status.Status = common.CommitStatus_Success status.Status = common.CommitStatus_Success
done = true
if !IsDryRun { if !IsDryRun {
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, "Build successful") _, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, "Build successful")
if err != nil { if err != nil {
common.LogError(err) common.LogError(err)
} else {
return true, nil
} }
} }
case BuildStatusSummaryFailed: case BuildStatusSummaryFailed:
status.Status = common.CommitStatus_Fail status.Status = common.CommitStatus_Fail
done = true
if !IsDryRun { if !IsDryRun {
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Build failed") _, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Build failed")
if err != nil { if err != nil {
common.LogError(err) common.LogError(err)
} else {
return true, nil
} }
} }
} }
common.LogInfo("Build status:", buildStatus) common.LogInfo("Build status:", buildStatus)
if !IsDryRun { gitea.SetCommitStatus(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Head.Sha, status)
if err = SetStatus(gitea, org, repo, pr.Head.Sha, status); err != nil {
return false, err // waiting for build results -- nothing to do
}
}
return done, nil
} else if err == NonActionableReviewError || err == NoReviewsFoundError { } else if err == NonActionableReviewError || err == NoReviewsFoundError {
return true, nil return true, nil

View File

@@ -20,7 +20,6 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@@ -192,24 +191,15 @@ func main() {
return return
} }
var rescanRepoError error
go func() { go func() {
for { for {
if rescanRepoError = RescanRepositories(); rescanRepoError != nil { if err := RescanRepositories(); err != nil {
common.LogError("Failed to rescan repositories.", err) common.LogError("Failed to rescan repositories.", err)
} }
time.Sleep(time.Minute * 5) time.Sleep(time.Minute * 5)
} }
}() }()
http.HandleFunc("GET /", func(res http.ResponseWriter, req *http.Request) {
if rescanRepoError != nil {
res.WriteHeader(500)
return
}
res.WriteHeader(404)
res.Write([]byte("404 page not found\n"))
})
http.HandleFunc("GET /status/{Project}", func(res http.ResponseWriter, req *http.Request) { http.HandleFunc("GET /status/{Project}", func(res http.ResponseWriter, req *http.Request) {
obsPrj := req.PathValue("Project") obsPrj := req.PathValue("Project")
common.LogInfo(" request: GET /status/" + obsPrj) common.LogInfo(" request: GET /status/" + obsPrj)
@@ -269,33 +259,6 @@ func main() {
res.Write(BuildStatusSvg(nil, &common.PackageBuildStatus{Package: pkg, Code: "unknown"})) res.Write(BuildStatusSvg(nil, &common.PackageBuildStatus{Package: pkg, Code: "unknown"}))
}) })
http.HandleFunc("GET /search", func(res http.ResponseWriter, req *http.Request) {
common.LogInfo("GET /serach?" + req.URL.RawQuery)
queries := req.URL.Query()
if !queries.Has("q") {
res.WriteHeader(400)
return
}
names := queries["q"]
if len(names) != 1 {
res.WriteHeader(400)
return
}
packages := FindPackages(names[0])
data, err := json.MarshalIndent(packages, "", " ")
if err != nil {
res.WriteHeader(500)
common.LogError("Error in marshalling data.", err)
return
}
res.Write(data)
res.Header().Add("content-type", "application/json")
res.WriteHeader(200)
})
http.HandleFunc("GET /buildlog/{Project}/{Package}/{Repository}/{Arch}", func(res http.ResponseWriter, req *http.Request) { http.HandleFunc("GET /buildlog/{Project}/{Package}/{Repository}/{Arch}", func(res http.ResponseWriter, req *http.Request) {
prj := req.PathValue("Project") prj := req.PathValue("Project")
pkg := req.PathValue("Package") pkg := req.PathValue("Package")

View File

@@ -29,15 +29,13 @@ func UpdateResults(r *common.BuildResult) {
RepoStatusLock.Lock() RepoStatusLock.Lock()
defer RepoStatusLock.Unlock() defer RepoStatusLock.Unlock()
updateResultsWithoutLocking(r)
}
func updateResultsWithoutLocking(r *common.BuildResult) {
key := "result." + r.Project + "/" + r.Repository + "/" + r.Arch key := "result." + r.Project + "/" + r.Repository + "/" + r.Arch
common.LogDebug(" + Updating", key)
data, err := redisClient.HGetAll(context.Background(), key).Result() data, err := redisClient.HGetAll(context.Background(), key).Result()
if err != nil { if err != nil {
common.LogError("Failed fetching build results for", key, err) common.LogError("Failed fetching build results for", key, err)
} }
common.LogDebug(" + Update size", len(data))
reset_time := time.Date(1000, 1, 1, 1, 1, 1, 1, time.Local) reset_time := time.Date(1000, 1, 1, 1, 1, 1, 1, time.Local)
for _, pkg := range r.Status { for _, pkg := range r.Status {
@@ -112,27 +110,6 @@ func FindRepoResults(project, repo string) []*common.BuildResult {
return ret return ret
} }
func FindPackages(pkg string) []string {
RepoStatusLock.RLock()
defer RepoStatusLock.RUnlock()
data := make([]string, 0, 100)
for _, repo := range RepoStatus {
for _, status := range repo.Status {
if pkg == status.Package {
entry := repo.Project + "/" + pkg
if idx, found := slices.BinarySearch(data, entry); !found {
data = slices.Insert(data, idx, entry)
if len(data) >= 100 {
return data
}
}
}
}
}
return data
}
func FindAndUpdateProjectResults(project string) []*common.BuildResult { func FindAndUpdateProjectResults(project string) []*common.BuildResult {
res := FindProjectResults(project) res := FindProjectResults(project)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
@@ -184,8 +161,6 @@ func RescanRepositories() error {
RepoStatusLock.Unlock() RepoStatusLock.Unlock()
var count int var count int
projectsLooked := make([]string, 0, 10000)
for { for {
var data []string var data []string
data, cursor, err = redisClient.ScanType(ctx, cursor, "", 1000, "hash").Result() data, cursor, err = redisClient.ScanType(ctx, cursor, "", 1000, "hash").Result()
@@ -194,7 +169,6 @@ func RescanRepositories() error {
return err return err
} }
wg := &sync.WaitGroup{}
RepoStatusLock.Lock() RepoStatusLock.Lock()
for _, repo := range data { for _, repo := range data {
r := strings.Split(repo, "/") r := strings.Split(repo, "/")
@@ -206,28 +180,14 @@ func RescanRepositories() error {
Repository: r[1], Repository: r[1],
Arch: r[2], Arch: r[2],
} }
if pos, found := slices.BinarySearchFunc(RepoStatus, d, common.BuildResultComp); found {
var pos int
var found bool
if pos, found = slices.BinarySearchFunc(RepoStatus, d, common.BuildResultComp); found {
RepoStatus[pos].Dirty = true RepoStatus[pos].Dirty = true
} else { } else {
d.Dirty = true d.Dirty = true
RepoStatus = slices.Insert(RepoStatus, pos, d) RepoStatus = slices.Insert(RepoStatus, pos, d)
count++ count++
} }
// fetch all keys, one per non-maintenance/non-home: projects, for package search
if idx, found := slices.BinarySearch(projectsLooked, d.Project); !found && !strings.Contains(d.Project, ":Maintenance:") && (len(d.Project) < 5 || d.Project[0:5] != "home:") {
projectsLooked = slices.Insert(projectsLooked, idx, d.Project)
wg.Add(1)
go func(r *common.BuildResult) {
updateResultsWithoutLocking(r)
wg.Done()
}(RepoStatus[pos])
}
} }
wg.Wait()
RepoStatusLock.Unlock() RepoStatusLock.Unlock()
if cursor == 0 { if cursor == 0 {

View File

@@ -1,16 +0,0 @@
[Unit]
Description=Staging bot for project git PRs in OBS
After=network-online.target
[Service]
Type=exec
ExecStart=/usr/bin/obs-staging-bot
EnvironmentFile=-/etc/sysconfig/obs-staging-bot.env
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
[Install]
WantedBy=multi-user.target

View File

@@ -1,4 +0,0 @@
package main
// exists only to import this for go.modules
import "go.uber.org/mock/mockgen/model"

1
vendor/modules.txt vendored
View File

@@ -132,7 +132,6 @@ go.opentelemetry.io/otel/trace/embedded
# go.uber.org/mock v0.5.0 # go.uber.org/mock v0.5.0
## explicit; go 1.22 ## explicit; go 1.22
go.uber.org/mock/gomock go.uber.org/mock/gomock
go.uber.org/mock/mockgen/model
# golang.org/x/sync v0.7.0 # golang.org/x/sync v0.7.0
## explicit; go 1.18 ## explicit; go 1.18
golang.org/x/sync/errgroup golang.org/x/sync/errgroup

View File

@@ -33,7 +33,6 @@ JSON
* _ManualMergeOnly_: (true, false) only merge if "merge ok" comment/review by package or project maintainers or reviewers * _ManualMergeOnly_: (true, false) only merge if "merge ok" comment/review by package or project maintainers or reviewers
* _ManualMergeProject_: (true, false) only merge if "merge ok" by project maintainers or reviewers * _ManualMergeProject_: (true, false) only merge if "merge ok" by project maintainers or reviewers
* _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.
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:

View File

@@ -3,7 +3,6 @@ package main
//go:generate mockgen -source=pr_processor.go -destination=mock/pr_processor.go -typed //go:generate mockgen -source=pr_processor.go -destination=mock/pr_processor.go -typed
import ( import (
"encoding/json"
"fmt" "fmt"
"path" "path"
"runtime/debug" "runtime/debug"
@@ -28,10 +27,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
if prset.IsPrjGitPR(pr.PR) { if prset.IsPrjGitPR(pr.PR) {
continue continue
} }
if pr.PR.State != "open" {
// remove PRs that are not open from description
continue
}
org, repo, idx := pr.PRComponents() org, repo, idx := pr.PRComponents()
title_refs = append(title_refs, repo) title_refs = append(title_refs, repo)
@@ -74,7 +69,7 @@ func updateSubmoduleInPR(submodule, headSha string, git common.Git) {
// as long as we can update to newer one later, we are still OK // as long as we can update to newer one later, we are still OK
git.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", submodule) git.GitExec(common.DefaultGitPrj, "submodule", "update", "--init", "--checkout", "--depth", "1", submodule)
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "fetch", "--depth", "1", "origin", headSha)) common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "fetch", "--depth", "1", "origin", headSha))
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "checkout", "-f", headSha)) common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "checkout", headSha))
} }
type PRProcessor struct { type PRProcessor struct {
@@ -104,10 +99,7 @@ func AllocatePRProcessor(req *models.PullRequest, configs common.AutogitConfigs)
return nil, fmt.Errorf("Cannot find config for PR") return nil, fmt.Errorf("Cannot find config for PR")
} }
if common.GetLoggingLevel() >= common.LogLevelDebug { common.LogDebug("found config", config)
cjson, _ := json.Marshal(config)
common.LogDebug("found config:", string(cjson))
}
if config == nil { if config == nil {
common.LogError("Cannot find config for branch '%s'", req.Base.Ref) common.LogError("Cannot find config for branch '%s'", req.Base.Ref)
return nil, fmt.Errorf("Cannot find config for branch '%s'", req.Base.Ref) return nil, fmt.Errorf("Cannot find config for branch '%s'", req.Base.Ref)
@@ -166,10 +158,10 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
submodule_found = true submodule_found = true
if id != prHead { if id != prHead {
ref := fmt.Sprintf(common.PrPattern, org, repo, idx) ref := fmt.Sprintf(common.PrPattern, org, repo, idx)
commitMsg := fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nreferencing PRs:\n", ref) commitMsg := fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "referencing\n", ref)
if revert { if revert {
commitMsg = fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nremoving PRs:\n", ref) commitMsg = fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "removing\n", ref)
} }
updateSubmoduleInPR(submodulePath, prHead, git) updateSubmoduleInPR(submodulePath, prHead, git)
@@ -178,8 +170,6 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead) common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
common.PanicOnError(err) common.PanicOnError(err)
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg)) common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
pr.PR.Head.Sha = id // update the prset
} }
submodule_found = true submodule_found = true
break break
@@ -219,7 +209,7 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
return err return err
} }
if !common.IsDryRun && !pr.config.NoProjectGitPR { if !common.IsDryRun {
if headCommit != newHeadCommit { if headCommit != newHeadCommit {
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", RemoteName, "+HEAD:"+prjGitPRbranch)) common.PanicOnError(git.GitExec(common.DefaultGitPrj, "push", RemoteName, "+HEAD:"+prjGitPRbranch))
} }
@@ -257,7 +247,7 @@ func (pr *PRProcessor) RebaseAndSkipSubmoduleCommits(prset *common.PRSet, branch
for _, s := range statuses { for _, s := range statuses {
if s.SubmoduleChanges != "S..." { if s.SubmoduleChanges != "S..." {
git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort") git.GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort")
return fmt.Errorf("Unexpected conflict in rebase. %v", s) return fmt.Errorf("Unexpected conflict in rebase. %s", s)
} }
} }
conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip") conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip")
@@ -275,12 +265,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
} }
git := pr.git git := pr.git
if len(prset.PRs) == 1 {
git.GitExecOrPanic(common.DefaultGitPrj, "fetch", PrjGitPR.RemoteName, PrjGitPR.PR.Head.Sha)
common.LogDebug("Only project git in PR. Nothing to update.")
return nil
}
PrjGit := PrjGitPR.PR.Base.Repo PrjGit := PrjGitPR.PR.Base.Repo
prjGitPRbranch := PrjGitPR.PR.Head.Name prjGitPRbranch := PrjGitPR.PR.Head.Name
if strings.Contains(prjGitPRbranch, "/") { if strings.Contains(prjGitPRbranch, "/") {
@@ -316,17 +300,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
return err return err
} }
PrjGitTitle, PrjGitBody := PrjGitDescription(prset)
if PrjGitPR.PR.User.UserName == CurrentUser.UserName {
if PrjGitPR.PR.Title != PrjGitTitle || PrjGitPR.PR.Body != PrjGitBody {
common.LogDebug("New title:", PrjGitTitle)
common.LogDebug(PrjGitBody)
}
} else {
// TODO: find our first comment in timeline
}
if !common.IsDryRun { if !common.IsDryRun {
if headCommit != newHeadCommit { if headCommit != newHeadCommit {
params := []string{"push", PrjGitPR.RemoteName, "+HEAD:" + prjGitPRbranch} params := []string{"push", PrjGitPR.RemoteName, "+HEAD:" + prjGitPRbranch}
@@ -337,6 +310,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
} }
// update PR // update PR
PrjGitTitle, PrjGitBody := PrjGitDescription(prset)
if PrjGitPR.PR.Body != PrjGitBody || PrjGitPR.PR.Title != PrjGitTitle { if PrjGitPR.PR.Body != PrjGitBody || PrjGitPR.PR.Title != PrjGitTitle {
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{ Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
RemoveDeadline: true, RemoveDeadline: true,
@@ -345,9 +319,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
}) })
} }
} }
// remove closed PRs from prset
prset.RemoveClosedPRs()
return nil return nil
} }
@@ -373,7 +344,7 @@ 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 {
common.LogDebug("Missing PrjGit. Need to create one under branch", prjGitPRbranch) common.LogDebug("Missing PrjGit. Need to create one...")
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil { if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
return err return err
@@ -450,11 +421,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
if prjGitPR == nil { if prjGitPR == nil {
prjGitPR, err = prset.GetPrjGitPR() prjGitPR, err = prset.GetPrjGitPR()
if err == common.PRSet_PrjGitMissing && config.NoProjectGitPR { if err != nil {
// we could be waiting for other tooling to create the
// project git PR. In meantime, we can assign some
// reviewers here.
} else if err != nil {
common.LogError("Error fetching PrjGitPR:", err) common.LogError("Error fetching PrjGitPR:", err)
return nil return nil
} }
@@ -466,12 +433,11 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
// reset anything that changed that is not part of the prset // reset anything that changed that is not part of the prset
// package removals/additions are *not* counted here // package removals/additions are *not* counted here
org, repo, branch := config.GetPrjGit() org, repo, branch := config.GetPrjGit()
// TODO: this is broken... if pr, err := prset.GetPrjGitPR(); err == nil {
if pr, err := prset.GetPrjGitPR(); err == nil && false {
common.LogDebug("Submodule parse begin") common.LogDebug("Submodule parse begin")
orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.MergeBase) orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.MergeBase)
common.PanicOnError(err) common.PanicOnError(err)
new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, "HEAD") new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.Head.Sha)
common.PanicOnError(err) common.PanicOnError(err)
common.LogDebug("Submodule parse done") common.LogDebug("Submodule parse done")
@@ -479,7 +445,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
updateSubmoduleInPR(submodule, sha, git) updateSubmoduleInPR(submodule, sha, git)
} }
common.LogDebug("Checking we only change linked commits")
for path, commit := range new_subs { for path, commit := range new_subs {
if old, ok := orig_subs[path]; ok && old != commit { if old, ok := orig_subs[path]; ok && old != commit {
found := false found := false
@@ -487,8 +452,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
if pr.PR.Base.Repo.Name == path && commit == pr.PR.Head.Sha { if pr.PR.Base.Repo.Name == path && commit == pr.PR.Head.Sha {
found = true found = true
break break
} else if pr.PR.Base.Repo.Name == path {
common.LogError(path, "-- commits not match", commit, pr.PR.Head.Sha)
} }
} }
if !found { if !found {
@@ -498,29 +461,24 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
} }
stats, err := git.GitStatus(common.DefaultGitPrj) stats, err := git.GitStatus(common.DefaultGitPrj)
common.LogDebug("Check Done", len(stats), "changes")
common.PanicOnError(err) common.PanicOnError(err)
if len(stats) > 0 { if len(stats) > 0 {
git.GitExecOrPanic(common.DefaultGitPrj, "commit", "-a", "-m", "Sync submodule updates with PR-set") git.GitExecOrPanic(common.DefaultGitPrj, "commit", "-a", "-m", "Sync submodule updates with PR-set")
git.GitExecQuietOrPanic(common.DefaultGitPrj, "submodule", "deinit", "--all", "--force") git.GitExecOrPanic(common.DefaultGitPrj, "submodule", "deinit", "--all", "--force")
if !common.IsDryRun { if !common.IsDryRun {
git.GitExecOrPanic(common.DefaultGitPrj, "push") git.GitExecOrPanic(common.DefaultGitPrj, "push")
} }
} }
} }
if prjGitPR != nil { common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
} else {
common.LogInfo("* No prjgit")
}
maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch) maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch)
if err != nil { if err != nil {
return err return err
} }
// handle case where PrjGit PR is only one left and there are no changes, then we can just close the PR // handle case where PrjGit PR is only one left and there are no changes, then we can just close the PR
if len(prset.PRs) == 1 && prjGitPR != nil && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser { if len(prset.PRs) == 1 && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser {
common.LogDebug(" --> checking if superflous PR") common.LogDebug(" --> checking if superflous PR")
diff, err := git.GitDiff(common.DefaultGitPrj, prjGitPR.PR.MergeBase, prjGitPR.PR.Head.Sha) diff, err := git.GitDiff(common.DefaultGitPrj, prjGitPR.PR.MergeBase, prjGitPR.PR.Head.Sha)
if err != nil { if err != nil {
@@ -542,6 +500,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
common.LogDebug(" --> NOT superflous PR") common.LogDebug(" --> NOT superflous PR")
} }
prset.AssignReviewers(Gitea, maintainers)
for _, pr := range prset.PRs { for _, pr := range prset.PRs {
if err := verifyRepositoryConfiguration(pr.PR.Base.Repo); err != nil { if err := verifyRepositoryConfiguration(pr.PR.Base.Repo); err != nil {
common.LogError("Cannot set manual merge... aborting processing") common.LogError("Cannot set manual merge... aborting processing")
@@ -556,9 +515,8 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
if err = prset.Merge(Gitea, git); err != nil { if err = prset.Merge(Gitea, git); err != nil {
common.LogError("merge error:", err) common.LogError("merge error:", err)
} }
} else {
prset.AssignReviewers(Gitea, maintainers)
} }
return err return err
} }