forked from adamm/autogits
Compare commits
2 Commits
Author | SHA256 | Date | |
---|---|---|---|
|
ff217cef01
|
||
|
61c59ed3e1
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
node_modules
|
||||
*.obscpio
|
||||
autogits-tmp.tar.zst
|
||||
*.osc
|
||||
*.conf
|
||||
|
15
_service
Normal file
15
_service
Normal 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>
|
||||
|
146
autogits.spec
146
autogits.spec
@@ -22,7 +22,6 @@ Release: 0
|
||||
Summary: GitWorkflow utilities
|
||||
License: GPL-2.0-or-later
|
||||
URL: https://src.opensuse.org/adamm/autogits
|
||||
BuildRequires: git
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: go
|
||||
%{?systemd_ordering}
|
||||
@@ -32,85 +31,55 @@ Git Workflow tooling and utilities enabling automated handing of OBS projects
|
||||
as git repositories
|
||||
|
||||
|
||||
%package -n autogits-devel-importer
|
||||
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
|
||||
%package -n gitea-events-rabbitmq-publisher
|
||||
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
|
||||
with a topic
|
||||
<scope>.src.$organization.$webhook_type.[$webhook_action_type]
|
||||
|
||||
|
||||
%package -n autogits-gitea-status-proxy
|
||||
Summary: gitea-status-proxy
|
||||
%package -n doc
|
||||
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
|
||||
|
||||
%description -n autogits-group-review
|
||||
%description -n group-review
|
||||
Is used to handle reviews associated with groups defined in the
|
||||
ProjectGit.
|
||||
|
||||
|
||||
%package -n autogits-obs-forward-bot
|
||||
Summary: obs-forward-bot
|
||||
|
||||
%description -n autogits-obs-forward-bot
|
||||
|
||||
|
||||
%package -n autogits-obs-staging-bot
|
||||
%package -n obs-staging-bot
|
||||
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.
|
||||
|
||||
|
||||
%package -n autogits-obs-status-service
|
||||
%package -n obs-status-service
|
||||
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
|
||||
|
||||
|
||||
%package -n autogits-workflow-direct
|
||||
%package -n workflow-direct
|
||||
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
|
||||
|
||||
|
||||
%package -n autogits-workflow-pr
|
||||
%package -n workflow-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
|
||||
|
||||
|
||||
@@ -119,24 +88,12 @@ Keeps ProjectGit PR in-sync with a PackageGit PR
|
||||
cp -r /home/abuild/rpmbuild/SOURCES/* ./
|
||||
|
||||
%build
|
||||
go build \
|
||||
-C devel-importer \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C hujson \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C gitea-events-rabbitmq-publisher \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C gitea_status_proxy \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C group-review \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C obs-forward-bot \
|
||||
-buildmode=pie
|
||||
go build \
|
||||
-C obs-staging-bot \
|
||||
-buildmode=pie
|
||||
@@ -150,104 +107,59 @@ go build \
|
||||
-C workflow-pr \
|
||||
-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 -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 -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 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 -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 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
|
||||
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
|
||||
|
||||
%post -n autogits-gitea-events-rabbitmq-publisher
|
||||
%post -n gitea-events-rabbitmq-publisher
|
||||
%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
|
||||
|
||||
%postun -n autogits-gitea-events-rabbitmq-publisher
|
||||
%postun -n gitea-events-rabbitmq-publisher
|
||||
%service_del_postun gitea-events-rabbitmq-publisher.service
|
||||
|
||||
%pre -n autogits-obs-staging-bot
|
||||
%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
|
||||
%files -n gitea-events-rabbitmq-publisher
|
||||
%license COPYING
|
||||
%doc gitea-events-rabbitmq-publisher/README.md
|
||||
%{_bindir}/gitea-events-rabbitmq-publisher
|
||||
%{_unitdir}/gitea-events-rabbitmq-publisher.service
|
||||
|
||||
%files -n autogits-gitea-status-proxy
|
||||
%files -n doc
|
||||
%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
|
||||
%doc group-review/README.md
|
||||
%{_bindir}/group-review
|
||||
|
||||
%files -n autogits-obs-forward-bot
|
||||
%license COPYING
|
||||
%{_bindir}/obs-forward-bot
|
||||
|
||||
%files -n autogits-obs-staging-bot
|
||||
%files -n obs-staging-bot
|
||||
%license COPYING
|
||||
%doc obs-staging-bot/README.md
|
||||
%{_bindir}/obs-staging-bot
|
||||
%{_unitdir}/obs-staging-bot.service
|
||||
|
||||
%files -n autogits-obs-status-service
|
||||
%files -n obs-status-service
|
||||
%license COPYING
|
||||
%doc obs-status-service/README.md
|
||||
%{_bindir}/obs-status-service
|
||||
|
||||
%files -n autogits-workflow-direct
|
||||
%files -n workflow-direct
|
||||
%license COPYING
|
||||
%doc workflow-direct/README.md
|
||||
%{_bindir}/workflow-direct
|
||||
|
||||
%files -n autogits-workflow-pr
|
||||
%files -n workflow-pr
|
||||
%license COPYING
|
||||
%doc workflow-pr/README.md
|
||||
%{_bindir}/workflow-pr
|
||||
|
@@ -39,7 +39,7 @@ func parsePrLine(line string) (BasicPR, error) {
|
||||
repo := strings.SplitN(org[1], "!", 2)
|
||||
ret.Repo = repo[0]
|
||||
if len(repo) != 2 {
|
||||
repo = strings.SplitN(org[1], "#", 2)
|
||||
repo := strings.SplitN(org[1], "#", 2)
|
||||
ret.Repo = repo[0]
|
||||
}
|
||||
if len(repo) != 2 {
|
||||
|
@@ -34,7 +34,7 @@ func TestAssociatedPRScanner(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"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{
|
||||
{Org: "test", Repo: "foo", Num: 4},
|
||||
{Org: "test", Repo: "goo", Num: 5},
|
||||
@@ -107,7 +107,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
|
||||
[]common.BasicPR{
|
||||
{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",
|
||||
@@ -119,7 +119,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
|
||||
{Org: "b", 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",
|
||||
@@ -133,7 +133,7 @@ func TestAppendingPRsToDescription(t *testing.T) {
|
||||
{Org: "a1", Repo: "c", Num: 101},
|
||||
{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",
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
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
|
||||
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
|
||||
}
|
||||
|
@@ -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) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -120,21 +119,20 @@ func TestProjectGitParser(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "repo only",
|
||||
prjgit: "repo.git#master",
|
||||
prjgit: "repo.git",
|
||||
org: "org",
|
||||
branch: "br",
|
||||
res: [3]string{"org", "repo.git", "master"},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
org: "org",
|
||||
prjgit: "org/_ObsPrj#master",
|
||||
res: [3]string{"org", common.DefaultGitPrj, "master"},
|
||||
name: "default",
|
||||
org: "org",
|
||||
res: [3]string{"org", common.DefaultGitPrj, "master"},
|
||||
},
|
||||
{
|
||||
name: "repo with branch",
|
||||
org: "org2",
|
||||
prjgit: "org2/repo.git#somebranch",
|
||||
prjgit: "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",
|
||||
org: "org3",
|
||||
prjgit: "oorg/foo.bar#master",
|
||||
prjgit: "oorg/foo.bar#",
|
||||
res: [3]string{"oorg", "foo.bar", "master"},
|
||||
},
|
||||
{
|
||||
name: "only branch defined",
|
||||
org: "org3",
|
||||
prjgit: "org3/_ObsPrj#mybranch",
|
||||
prjgit: "#mybranch",
|
||||
res: [3]string{"org3", "_ObsPrj", "mybranch"},
|
||||
},
|
||||
{
|
||||
name: "only org and branch defined",
|
||||
org: "org3",
|
||||
prjgit: "org1/_ObsPrj#mybranch",
|
||||
prjgit: "org1/#mybranch",
|
||||
res: [3]string{"org1", "_ObsPrj", "mybranch"},
|
||||
},
|
||||
{
|
||||
name: "empty org and repo",
|
||||
org: "org3",
|
||||
prjgit: "org3/repo#master",
|
||||
prjgit: "/repo#",
|
||||
res: [3]string{"org3", "repo", "master"},
|
||||
},
|
||||
}
|
||||
|
@@ -67,7 +67,6 @@ type Git interface {
|
||||
GitExecOrPanic(cwd string, params ...string)
|
||||
GitExec(cwd string, params ...string) error
|
||||
GitExecWithOutput(cwd string, params ...string) (string, error)
|
||||
GitExecQuietOrPanic(cwd string, params ...string)
|
||||
|
||||
GitDiffLister
|
||||
}
|
||||
@@ -77,8 +76,7 @@ type GitHandlerImpl struct {
|
||||
GitCommiter string
|
||||
GitEmail string
|
||||
|
||||
lock *sync.Mutex
|
||||
quiet bool
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
remoteBranch := "HEAD"
|
||||
if len(branch) == 0 && remoteUrlComp != nil && remoteUrlComp.Commit != "HEAD" {
|
||||
if len(branch) == 0 && remoteUrlComp != nil {
|
||||
branch = remoteUrlComp.Commit
|
||||
remoteBranch = branch
|
||||
} 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
|
||||
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)
|
||||
}
|
||||
/*
|
||||
|
||||
refsBytes, err := os.ReadFile(path.Join(e.GitPath, repo, ".git/refs/remotes", remoteName, "HEAD"))
|
||||
if err != nil {
|
||||
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("branch", branch)
|
||||
}
|
||||
*/
|
||||
|
||||
args := []string{"fetch", "--prune", remoteName, branch}
|
||||
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
|
||||
args = slices.Insert(args, 1, "--unshallow")
|
||||
@@ -276,7 +274,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl 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 {
|
||||
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)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if !e.quiet {
|
||||
LogDebug(string(out))
|
||||
}
|
||||
LogDebug(string(out))
|
||||
if err != nil {
|
||||
LogError("git", cmd.Args, " error:", 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
|
||||
}
|
||||
|
||||
func (e *GitHandlerImpl) GitExecQuietOrPanic(cwd string, params ...string) {
|
||||
e.quiet = true
|
||||
e.GitExecOrPanic(cwd, params...)
|
||||
e.quiet = false
|
||||
return
|
||||
}
|
||||
|
||||
type ChanIO struct {
|
||||
ch chan byte
|
||||
}
|
||||
|
@@ -24,13 +24,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
transport "github.com/go-openapi/runtime/client"
|
||||
@@ -184,6 +182,7 @@ type Gitea interface {
|
||||
GiteaCommitStatusGetter
|
||||
GiteaCommitStatusSetter
|
||||
GiteaSetRepoOptions
|
||||
GiteaTimelineFetcher
|
||||
|
||||
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
|
||||
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
|
||||
@@ -200,32 +199,7 @@ type Gitea interface {
|
||||
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 {
|
||||
headers *GiteaHeaderInterceptor
|
||||
transport *transport.Runtime
|
||||
client *apiclient.GiteaAPI
|
||||
}
|
||||
@@ -238,9 +212,7 @@ func AllocateGiteaTransport(giteaUrl string) Gitea {
|
||||
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 = r.headers
|
||||
r.transport.DefaultAuthentication = transport.BearerToken(giteaToken)
|
||||
|
||||
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) {
|
||||
var page int64
|
||||
var page, limit int64
|
||||
|
||||
prs := make([]*models.PullRequest, 0)
|
||||
limit = 20
|
||||
state := "open"
|
||||
|
||||
for {
|
||||
@@ -329,18 +302,16 @@ func (gitea *GiteaTransport) GetPullRequests(org, repo string) ([]*models.PullRe
|
||||
WithOwner(org).
|
||||
WithRepo(repo).
|
||||
WithState(&state).
|
||||
WithPage(&page),
|
||||
WithPage(&page).
|
||||
WithLimit(&limit),
|
||||
gitea.transport.DefaultAuthentication)
|
||||
|
||||
if err != nil {
|
||||
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)
|
||||
if len(prs) >= gitea.headers.Length {
|
||||
if len(req.Payload) < int(limit) {
|
||||
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) {
|
||||
var page int64
|
||||
page := int64(1)
|
||||
limit := int64(10)
|
||||
var res []*models.CommitStatus
|
||||
|
||||
for {
|
||||
page++
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if len(r.Payload) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
res = append(res, r.Payload...)
|
||||
if len(res) >= gitea.headers.Length {
|
||||
if len(r.Payload) < int(limit) {
|
||||
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) {
|
||||
limit := int64(20)
|
||||
var page int64
|
||||
var allReviews []*models.PullReview
|
||||
|
||||
for {
|
||||
page++
|
||||
reviews, err := gitea.client.Repository.RepoListPullReviews(
|
||||
repository.NewRepoListPullReviewsParams().
|
||||
WithDefaults().
|
||||
WithOwner(org).
|
||||
WithRepo(project).
|
||||
WithIndex(PRnum).
|
||||
WithPage(&page),
|
||||
WithPage(&page).
|
||||
WithLimit(&limit),
|
||||
gitea.transport.DefaultAuthentication,
|
||||
)
|
||||
|
||||
@@ -427,13 +397,11 @@ func (gitea *GiteaTransport) GetPullRequestReviews(org, project string, PRnum in
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(reviews.Payload) == 0 {
|
||||
break
|
||||
}
|
||||
allReviews = slices.Concat(allReviews, reviews.Payload)
|
||||
if len(allReviews) >= gitea.headers.Length {
|
||||
if len(reviews.Payload) < int(limit) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
return allReviews, nil
|
||||
@@ -501,6 +469,7 @@ const (
|
||||
)
|
||||
|
||||
func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) {
|
||||
bigLimit := int64(20)
|
||||
ret := make([]*models.NotificationThread, 0, 100)
|
||||
|
||||
for page := int64(1); ; page++ {
|
||||
@@ -508,6 +477,7 @@ func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]
|
||||
WithDefaults().
|
||||
WithSubjectType([]string{Type}).
|
||||
WithStatusTypes([]string{"unread"}).
|
||||
WithLimit(&bigLimit).
|
||||
WithPage(&page)
|
||||
|
||||
if since != nil {
|
||||
@@ -520,11 +490,8 @@ func (gitea *GiteaTransport) GetNotifications(Type string, since *time.Time) ([]
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list.Payload) == 0 {
|
||||
break
|
||||
}
|
||||
ret = slices.Concat(ret, list.Payload)
|
||||
if len(ret) >= gitea.headers.Length {
|
||||
if len(list.Payload) < int(bigLimit) {
|
||||
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) {
|
||||
limit := int64(20)
|
||||
t := true
|
||||
|
||||
if page <= 0 {
|
||||
@@ -543,6 +511,7 @@ func (gitea *GiteaTransport) GetDoneNotifications(Type string, page int64) ([]*m
|
||||
WithAll(&t).
|
||||
WithSubjectType([]string{Type}).
|
||||
WithStatusTypes([]string{"read"}).
|
||||
WithLimit(&limit).
|
||||
WithPage(&page),
|
||||
gitea.transport.DefaultAuthentication)
|
||||
if err != nil {
|
||||
@@ -595,12 +564,9 @@ func (gitea *GiteaTransport) GetOrganizationRepositories(orgName string) ([]*mod
|
||||
if len(ret.Payload) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
repos = append(repos, ret.Payload...)
|
||||
page++
|
||||
|
||||
if len(repos) >= gitea.headers.Length {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return repos, nil
|
||||
@@ -814,18 +780,15 @@ func (gitea *GiteaTransport) GetTimeline(org, repo string, idx int64) ([]*models
|
||||
|
||||
resCount = len(res.Payload)
|
||||
LogDebug("page:", page, "len:", resCount)
|
||||
if resCount == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
|
||||
retData = append(retData, res.Payload...)
|
||||
if len(retData) >= gitea.headers.Length {
|
||||
break
|
||||
for _, d := range res.Payload {
|
||||
if d != nil {
|
||||
retData = append(retData, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return time.Time(b.Created).Compare(time.Time(a.Created))
|
||||
})
|
||||
|
@@ -63,10 +63,6 @@ func SetLoggingLevel(ll LogLevel) {
|
||||
logLevel = ll
|
||||
}
|
||||
|
||||
func GetLoggingLevel() LogLevel {
|
||||
return logLevel
|
||||
}
|
||||
|
||||
func SetLoggingLevelFromString(ll string) error {
|
||||
switch ll {
|
||||
case "info":
|
||||
|
27
common/pr.go
27
common/pr.go
@@ -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")
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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.Name == prjGitRepo {
|
||||
|
||||
_, prs := ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(item.RefIssue.Body)))
|
||||
for _, pr := range prs {
|
||||
if pr.Org == org && pr.Repo == repo && pr.Num == num {
|
||||
lines := SplitLines(item.RefIssue.Body)
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == prRefLine {
|
||||
LogDebug("Found PrjGit PR in Timeline:", issue.Index)
|
||||
|
||||
// found prjgit PR in timeline. Return it
|
||||
@@ -281,12 +282,6 @@ func (rs *PRSet) AssignReviewers(gitea GiteaReviewFetcherAndRequester, maintaine
|
||||
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 {
|
||||
configReviewers := ParseReviewers(rs.Config.Reviewers)
|
||||
|
||||
@@ -385,8 +380,7 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||
}
|
||||
prjgit := prjgit_info.PR
|
||||
|
||||
_, _, prjgitBranch := rs.Config.GetPrjGit()
|
||||
remote, err := git.GitClone(DefaultGitPrj, prjgitBranch, prjgit.Base.Repo.SSHURL)
|
||||
remote, err := git.GitClone(DefaultGitPrj, rs.Config.Branch, prjgit.Base.Repo.SSHURL)
|
||||
PanicOnError(err)
|
||||
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
|
||||
for _, s := range status {
|
||||
if s.Status == GitStatus_Unmerged {
|
||||
panic("Can't handle conflicts yet")
|
||||
if s.Path != ".gitmodules" {
|
||||
return err
|
||||
}
|
||||
@@ -506,15 +499,7 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
||||
if rs.IsPrjGitPR(prinfo.PR) {
|
||||
continue
|
||||
}
|
||||
br := rs.Config.Branch
|
||||
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)
|
||||
prinfo.RemoteName, err = git.GitClone(repo.Name, rs.Config.Branch, repo.SSHURL)
|
||||
PanicOnError(err)
|
||||
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
|
||||
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
|
||||
|
@@ -48,13 +48,11 @@ func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
|
||||
}
|
||||
|
||||
func TestPR(t *testing.T) {
|
||||
return
|
||||
|
||||
baseConfig := common.AutogitConfig{
|
||||
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
|
||||
Branch: "branch",
|
||||
Organization: "foo",
|
||||
GitProjectName: "foo/barPrj#master",
|
||||
GitProjectName: "barPrj",
|
||||
}
|
||||
|
||||
type prdata struct {
|
||||
@@ -640,7 +638,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "No reviewers",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{},
|
||||
@@ -650,7 +648,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "One project reviewer only",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1"},
|
||||
@@ -660,7 +658,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "One project reviewer and one pkg reviewer only",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
@@ -670,7 +668,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "No need to get reviews of submitter",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "submitter"},
|
||||
@@ -680,7 +678,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "Reviews are done",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
@@ -714,7 +712,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "Stale review is not done, re-request it",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "org/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{"-user1", "user2"},
|
||||
@@ -746,7 +744,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
||||
{
|
||||
name: "Stale optional review is not done, re-request it",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "prg/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
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",
|
||||
config: common.AutogitConfig{
|
||||
GitProjectName: "org/repo#main",
|
||||
GitProjectName: "repo",
|
||||
Organization: "org",
|
||||
Branch: "main",
|
||||
Reviewers: []string{},
|
||||
@@ -925,7 +923,7 @@ func TestPRMerge(t *testing.T) {
|
||||
|
||||
config := &common.AutogitConfig{
|
||||
Organization: "org",
|
||||
GitProjectName: "org/prj#master",
|
||||
GitProjectName: "prj",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
@@ -164,7 +164,7 @@ func (l *RabbitConnection) ConnectAndProcessRabbitMQ(ch chan<- RabbitMessage) {
|
||||
for {
|
||||
err := l.ProcessRabbitMQ(ch)
|
||||
if err != nil {
|
||||
LogError("Error in RabbitMQ connection:", err)
|
||||
LogError("Error in RabbitMQ connection. %#v", err)
|
||||
LogInfo("Reconnecting in 2 seconds...")
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
@@ -99,12 +99,12 @@ func (gitea *RabbitMQGiteaEventsProcessor) ProcessRabbitMessage(msg RabbitMessag
|
||||
req, err := ParseRequestJSON(reqType, msg.Body)
|
||||
if err != nil {
|
||||
LogError("Error parsing request JSON:", err)
|
||||
return nil
|
||||
} else {
|
||||
LogDebug("processing req", req.Type)
|
||||
// h.Request = req
|
||||
ProcessEvent(handler, req)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
4
go.mod
4
go.mod
@@ -10,10 +10,8 @@ require (
|
||||
github.com/go-openapi/validate v0.24.0
|
||||
github.com/opentracing/opentracing-go v1.2.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
|
||||
go.uber.org/mock v0.5.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -32,9 +30,11 @@ require (
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // 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.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@@ -1,9 +1,5 @@
|
||||
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/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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@@ -125,16 +125,6 @@ func FindAcceptableReviewInTimeline(user string, timeline []*models.TimelineComm
|
||||
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) {
|
||||
if err := gitea.UnrequestReview(org, repo, id, users...); err != nil {
|
||||
common.LogError("Can't remove reviewrs after a review:", err)
|
||||
@@ -174,22 +164,6 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
||||
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
|
||||
for _, reviewer := range pr.RequestedReviewers {
|
||||
if reviewer != nil && reviewer.UserName == groupName {
|
||||
@@ -199,32 +173,42 @@ func ProcessPR(pr *models.PullRequest) error {
|
||||
}
|
||||
if !found {
|
||||
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)
|
||||
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" {
|
||||
// dismiss the 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)
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch timeline to review. %w", err)
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
groupConfig, err := config.GetReviewGroup(groupName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to fetch review group. %w", err)
|
||||
common.LogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
// submitter cannot be reviewer
|
||||
@@ -236,31 +220,27 @@ func ProcessPR(pr *models.PullRequest) error {
|
||||
if review := FindAcceptableReviewInTimeline(reviewer, timeline, reviews); review != nil {
|
||||
if ReviewAccepted(review.Body) {
|
||||
if !common.IsDryRun {
|
||||
text := reviewer + " approved a review on behalf of " + groupName
|
||||
if review := FindOurLastReviewInTimeline(timeline); review == nil || review.Body != text {
|
||||
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, text)
|
||||
if err != nil {
|
||||
common.LogError(" -> failed to write approval comment", err)
|
||||
gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+reviewer)
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
if !common.IsDryRun {
|
||||
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
||||
common.LogDebug(" Cannot set notification as read", err)
|
||||
}
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
}
|
||||
}
|
||||
common.LogInfo(" -> approved by", reviewer)
|
||||
common.LogInfo(" review at", review.Created)
|
||||
return nil
|
||||
return
|
||||
} else if ReviewRejected(review.Body) {
|
||||
if !common.IsDryRun {
|
||||
text := reviewer + " requested changes on behalf of " + groupName + ". See " + review.HTMLURL
|
||||
if review := FindOurLastReviewInTimeline(timeline); review == nil || review.Body != text {
|
||||
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer)
|
||||
if err != nil {
|
||||
common.LogError(" -> failed to write rejecting comment", err)
|
||||
}
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer)
|
||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
||||
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
||||
common.LogDebug(" Cannot set notification as read", err)
|
||||
}
|
||||
}
|
||||
common.LogInfo(" -> declined by", reviewer)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,22 +270,15 @@ func ProcessPR(pr *models.PullRequest) error {
|
||||
}
|
||||
|
||||
if !found_help_comment && !common.IsDryRun {
|
||||
helpComment := fmt.Sprintln("Review by", groupName, "represents a group of reviewers:", strings.Join(requestReviewers, ", "), ".\n\n"+
|
||||
"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.")
|
||||
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.")
|
||||
if slices.Contains(groupConfig.Reviewers, pr.User.UserName) {
|
||||
helpComment = helpComment + "\n\n" +
|
||||
"Submitter is member of this review group, hence they are excluded from being one of the reviewers here"
|
||||
helpComment = helpComment + "\n\n" + fmt.Sprintln("Submitter is member of this review group, hence they are excluded from being one of the reviewers here")
|
||||
}
|
||||
gitea.AddComment(pr, helpComment)
|
||||
}
|
||||
|
||||
return ReviewNotFinished
|
||||
}
|
||||
|
||||
func PeriodReviewCheck() {
|
||||
func PeriodReviewCheck(gitea common.Gitea) {
|
||||
notifications, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
if err != nil {
|
||||
common.LogError(" Error fetching unread notifications: %w", err)
|
||||
@@ -314,15 +287,14 @@ func PeriodReviewCheck() {
|
||||
|
||||
for _, notification := range notifications {
|
||||
ProcessNotifications(notification, gitea)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var gitea common.Gitea
|
||||
|
||||
func main() {
|
||||
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")
|
||||
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")
|
||||
logging := flag.String("logging", "info", "Logging level: [none, error, info, debug]")
|
||||
flag.BoolVar(&common.IsDryRun, "dry", false, "Dry run, no effect. For debugging")
|
||||
@@ -359,7 +331,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gitea = common.AllocateGiteaTransport(*giteaUrl)
|
||||
gitea := common.AllocateGiteaTransport(*giteaUrl)
|
||||
configs, err = common.ResolveWorkflowConfigs(gitea, configData)
|
||||
if err != nil {
|
||||
common.LogError("Cannot parse workflow configs:", err)
|
||||
@@ -403,13 +375,10 @@ func main() {
|
||||
config_modified: make(chan *common.AutogitConfig),
|
||||
}
|
||||
|
||||
process_issue_pr := IssueCommentProcessor{}
|
||||
|
||||
configUpdates := &common.RabbitMQGiteaEventsProcessor{
|
||||
Orgs: []string{},
|
||||
Handlers: map[string]common.RequestProcessor{
|
||||
common.RequestType_Push: &config_update,
|
||||
common.RequestType_IssueComment: &process_issue_pr,
|
||||
common.RequestType_Push: &config_update,
|
||||
},
|
||||
}
|
||||
configUpdates.Connection().RabbitURL = u
|
||||
@@ -446,7 +415,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
PeriodReviewCheck()
|
||||
PeriodReviewCheck(gitea)
|
||||
time.Sleep(time.Duration(*interval * int64(time.Minute)))
|
||||
}
|
||||
}
|
||||
|
@@ -7,25 +7,6 @@ import (
|
||||
"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 {
|
||||
config_modified chan *common.AutogitConfig
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -263,7 +263,7 @@ func ProcessRepoBuildStatus(results, ref []*common.PackageBuildStatus) (status B
|
||||
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 ...")
|
||||
err := FetchPrGit(git, pr)
|
||||
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(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)
|
||||
}
|
||||
meta, err := ObsClient.GetProjectMeta(buildPrj)
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", buildPrj, ". Err:", 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))
|
||||
}
|
||||
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)
|
||||
// QE wants it published ... also we should not hardcode it here, since
|
||||
// it is configurable via the :PullRequest project
|
||||
// meta.PublicFlags = common.Flags{Contents: "<disable/>"}
|
||||
// QE wants it published ... also we should not hardcode it here, since
|
||||
// it is configurable via the :PullRequest project
|
||||
// meta.PublicFlags = common.Flags{Contents: "<disable/>"}
|
||||
|
||||
meta.Groups = nil
|
||||
meta.Persons = nil
|
||||
@@ -425,8 +414,7 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
|
||||
var state RequestModification = RequestModificationSourceChanged
|
||||
if meta == nil {
|
||||
// new build
|
||||
common.LogDebug(" Staging master:", config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject)
|
||||
if err != nil {
|
||||
return RequestModificationNoChange, err
|
||||
}
|
||||
@@ -440,8 +428,6 @@ func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea comm
|
||||
} else {
|
||||
err = ObsClient.SetProjectMeta(meta)
|
||||
if err != nil {
|
||||
x, _ := xml.MarshalIndent(meta, "", " ")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
common.LogError("cannot create meta project:", 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 {
|
||||
common.LogInfo("Cooldown period for cleanup of", thread.Subject.HTMLURL)
|
||||
common.LogInfo("Cooldown period for cleanup of", thread.URL)
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
dir, err := os.MkdirTemp(os.TempDir(), BotName)
|
||||
common.PanicOnError(err)
|
||||
@@ -827,12 +805,7 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
if !rebuild_all {
|
||||
common.LogInfo("No package changes detected. Ignoring")
|
||||
if !IsDryRun {
|
||||
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, "No package changes, not rebuilding project by default, accepting change")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
_, err = gitea.AddReviewComment(pr, common.ReviewStateComment, "No package changes. Not rebuilding project by default")
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
@@ -848,22 +821,6 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
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"
|
||||
if change == RequestModificationProjectCreated {
|
||||
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 {
|
||||
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 {
|
||||
CreateQASubProject(stagingConfig, git, gitea, pr,
|
||||
stagingProject,
|
||||
@@ -896,34 +854,32 @@ func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, e
|
||||
}
|
||||
buildStatus := ProcessBuildStatus(stagingResult, baseResult)
|
||||
|
||||
done := false
|
||||
switch buildStatus {
|
||||
case BuildStatusSummarySuccess:
|
||||
status.Status = common.CommitStatus_Success
|
||||
done = true
|
||||
if !IsDryRun {
|
||||
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, "Build successful")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
case BuildStatusSummaryFailed:
|
||||
status.Status = common.CommitStatus_Fail
|
||||
done = true
|
||||
if !IsDryRun {
|
||||
_, err := gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Build failed")
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
common.LogInfo("Build status:", buildStatus)
|
||||
if !IsDryRun {
|
||||
if err = SetStatus(gitea, org, repo, pr.Head.Sha, status); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return done, nil
|
||||
gitea.SetCommitStatus(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Head.Sha, status)
|
||||
|
||||
// waiting for build results -- nothing to do
|
||||
|
||||
} else if err == NonActionableReviewError || err == NoReviewsFoundError {
|
||||
return true, nil
|
||||
|
@@ -20,7 +20,6 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -192,24 +191,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
var rescanRepoError error
|
||||
go func() {
|
||||
for {
|
||||
if rescanRepoError = RescanRepositories(); rescanRepoError != nil {
|
||||
if err := RescanRepositories(); err != nil {
|
||||
common.LogError("Failed to rescan repositories.", err)
|
||||
}
|
||||
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) {
|
||||
obsPrj := req.PathValue("Project")
|
||||
common.LogInfo(" request: GET /status/" + obsPrj)
|
||||
@@ -269,33 +259,6 @@ func main() {
|
||||
|
||||
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) {
|
||||
prj := req.PathValue("Project")
|
||||
pkg := req.PathValue("Package")
|
||||
|
@@ -29,15 +29,13 @@ func UpdateResults(r *common.BuildResult) {
|
||||
RepoStatusLock.Lock()
|
||||
defer RepoStatusLock.Unlock()
|
||||
|
||||
updateResultsWithoutLocking(r)
|
||||
}
|
||||
|
||||
func updateResultsWithoutLocking(r *common.BuildResult) {
|
||||
key := "result." + r.Project + "/" + r.Repository + "/" + r.Arch
|
||||
common.LogDebug(" + Updating", key)
|
||||
data, err := redisClient.HGetAll(context.Background(), key).Result()
|
||||
if err != nil {
|
||||
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)
|
||||
for _, pkg := range r.Status {
|
||||
@@ -112,27 +110,6 @@ func FindRepoResults(project, repo string) []*common.BuildResult {
|
||||
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 {
|
||||
res := FindProjectResults(project)
|
||||
wg := &sync.WaitGroup{}
|
||||
@@ -184,8 +161,6 @@ func RescanRepositories() error {
|
||||
RepoStatusLock.Unlock()
|
||||
var count int
|
||||
|
||||
projectsLooked := make([]string, 0, 10000)
|
||||
|
||||
for {
|
||||
var data []string
|
||||
data, cursor, err = redisClient.ScanType(ctx, cursor, "", 1000, "hash").Result()
|
||||
@@ -194,7 +169,6 @@ func RescanRepositories() error {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
RepoStatusLock.Lock()
|
||||
for _, repo := range data {
|
||||
r := strings.Split(repo, "/")
|
||||
@@ -206,28 +180,14 @@ func RescanRepositories() error {
|
||||
Repository: r[1],
|
||||
Arch: r[2],
|
||||
}
|
||||
|
||||
var pos int
|
||||
var found bool
|
||||
if pos, found = slices.BinarySearchFunc(RepoStatus, d, common.BuildResultComp); found {
|
||||
if pos, found := slices.BinarySearchFunc(RepoStatus, d, common.BuildResultComp); found {
|
||||
RepoStatus[pos].Dirty = true
|
||||
} else {
|
||||
d.Dirty = true
|
||||
RepoStatus = slices.Insert(RepoStatus, pos, d)
|
||||
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()
|
||||
|
||||
if cursor == 0 {
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
1
vendor/modules.txt
vendored
@@ -132,7 +132,6 @@ go.opentelemetry.io/otel/trace/embedded
|
||||
# go.uber.org/mock v0.5.0
|
||||
## explicit; go 1.22
|
||||
go.uber.org/mock/gomock
|
||||
go.uber.org/mock/mockgen/model
|
||||
# golang.org/x/sync v0.7.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sync/errgroup
|
||||
|
@@ -33,7 +33,6 @@ JSON
|
||||
* _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
|
||||
* _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.
|
||||
example:
|
||||
|
@@ -3,7 +3,6 @@ package main
|
||||
//go:generate mockgen -source=pr_processor.go -destination=mock/pr_processor.go -typed
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime/debug"
|
||||
@@ -28,10 +27,6 @@ func PrjGitDescription(prset *common.PRSet) (title string, desc string) {
|
||||
if prset.IsPrjGitPR(pr.PR) {
|
||||
continue
|
||||
}
|
||||
if pr.PR.State != "open" {
|
||||
// remove PRs that are not open from description
|
||||
continue
|
||||
}
|
||||
org, repo, idx := pr.PRComponents()
|
||||
|
||||
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
|
||||
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), "checkout", "-f", headSha))
|
||||
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "checkout", headSha))
|
||||
}
|
||||
|
||||
type PRProcessor struct {
|
||||
@@ -104,10 +99,7 @@ func AllocatePRProcessor(req *models.PullRequest, configs common.AutogitConfigs)
|
||||
return nil, fmt.Errorf("Cannot find config for PR")
|
||||
}
|
||||
|
||||
if common.GetLoggingLevel() >= common.LogLevelDebug {
|
||||
cjson, _ := json.Marshal(config)
|
||||
common.LogDebug("found config:", string(cjson))
|
||||
}
|
||||
common.LogDebug("found config", config)
|
||||
if config == nil {
|
||||
common.LogError("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
|
||||
if id != prHead {
|
||||
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 {
|
||||
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)
|
||||
@@ -178,8 +170,6 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
|
||||
common.LogDebug("submodule", repo, " hash:", id, " -> ", prHead)
|
||||
common.PanicOnError(err)
|
||||
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
|
||||
|
||||
pr.PR.Head.Sha = id // update the prset
|
||||
}
|
||||
submodule_found = true
|
||||
break
|
||||
@@ -219,7 +209,7 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.IsDryRun && !pr.config.NoProjectGitPR {
|
||||
if !common.IsDryRun {
|
||||
if headCommit != newHeadCommit {
|
||||
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 {
|
||||
if s.SubmoduleChanges != "S..." {
|
||||
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")
|
||||
@@ -275,12 +265,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
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
|
||||
prjGitPRbranch := PrjGitPR.PR.Head.Name
|
||||
if strings.Contains(prjGitPRbranch, "/") {
|
||||
@@ -316,17 +300,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
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 headCommit != newHeadCommit {
|
||||
params := []string{"push", PrjGitPR.RemoteName, "+HEAD:" + prjGitPRbranch}
|
||||
@@ -337,6 +310,7 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
}
|
||||
|
||||
// update PR
|
||||
PrjGitTitle, PrjGitBody := PrjGitDescription(prset)
|
||||
if PrjGitPR.PR.Body != PrjGitBody || PrjGitPR.PR.Title != PrjGitTitle {
|
||||
Gitea.UpdatePullRequest(PrjGit.Owner.UserName, PrjGit.Name, PrjGitPR.PR.Index, &models.EditPullRequestOption{
|
||||
RemoveDeadline: true,
|
||||
@@ -345,9 +319,6 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// remove closed PRs from prset
|
||||
prset.RemoveClosedPRs()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -373,7 +344,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
|
||||
prjGitPR, err := prset.GetPrjGitPR()
|
||||
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 {
|
||||
return err
|
||||
@@ -450,11 +421,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
|
||||
if prjGitPR == nil {
|
||||
prjGitPR, err = prset.GetPrjGitPR()
|
||||
if err == common.PRSet_PrjGitMissing && config.NoProjectGitPR {
|
||||
// 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 {
|
||||
if err != nil {
|
||||
common.LogError("Error fetching PrjGitPR:", err)
|
||||
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
|
||||
// package removals/additions are *not* counted here
|
||||
org, repo, branch := config.GetPrjGit()
|
||||
// TODO: this is broken...
|
||||
if pr, err := prset.GetPrjGitPR(); err == nil && false {
|
||||
if pr, err := prset.GetPrjGitPR(); err == nil {
|
||||
common.LogDebug("Submodule parse begin")
|
||||
orig_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, pr.PR.MergeBase)
|
||||
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.LogDebug("Submodule parse done")
|
||||
|
||||
@@ -479,7 +445,6 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
updateSubmoduleInPR(submodule, sha, git)
|
||||
}
|
||||
|
||||
common.LogDebug("Checking we only change linked commits")
|
||||
for path, commit := range new_subs {
|
||||
if old, ok := orig_subs[path]; ok && old != commit {
|
||||
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 {
|
||||
found = true
|
||||
break
|
||||
} else if pr.PR.Base.Repo.Name == path {
|
||||
common.LogError(path, "-- commits not match", commit, pr.PR.Head.Sha)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
@@ -498,29 +461,24 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
}
|
||||
|
||||
stats, err := git.GitStatus(common.DefaultGitPrj)
|
||||
common.LogDebug("Check Done", len(stats), "changes")
|
||||
common.PanicOnError(err)
|
||||
if len(stats) > 0 {
|
||||
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 {
|
||||
git.GitExecOrPanic(common.DefaultGitPrj, "push")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prjGitPR != nil {
|
||||
common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
|
||||
} else {
|
||||
common.LogInfo("* No prjgit")
|
||||
}
|
||||
common.LogDebug(" num of reviewers:", len(prjGitPR.PR.RequestedReviewers))
|
||||
maintainers, err := common.FetchProjectMaintainershipData(Gitea, org, repo, branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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")
|
||||
diff, err := git.GitDiff(common.DefaultGitPrj, prjGitPR.PR.MergeBase, prjGitPR.PR.Head.Sha)
|
||||
if err != nil {
|
||||
@@ -542,6 +500,7 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
||||
common.LogDebug(" --> NOT superflous PR")
|
||||
}
|
||||
|
||||
prset.AssignReviewers(Gitea, maintainers)
|
||||
for _, pr := range prset.PRs {
|
||||
if err := verifyRepositoryConfiguration(pr.PR.Base.Repo); err != nil {
|
||||
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 {
|
||||
common.LogError("merge error:", err)
|
||||
}
|
||||
} else {
|
||||
prset.AssignReviewers(Gitea, maintainers)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user