forked from adamm/autogits
Compare commits
49 Commits
Author | SHA256 | Date | |
---|---|---|---|
|
6d04ab367a
|
||
|
e662f56310
|
||
|
64a512e45f
|
||
|
b1b60108b2
|
||
|
f0600ec0c5
|
||
f2089f99fc | |||
10ea3a8f8f | |||
9faa6ead49 | |||
29cce5741a | |||
804e542c3f | |||
72899162b0 | |||
168a419bbe | |||
6a71641295 | |||
5addde0a71 | |||
90ea1c9463 | |||
a4fb3e6151 | |||
e2abbfcc63 | |||
f6cb35acca | |||
f4386c3d12 | |||
f8594af8c6 | |||
b8ef69a5a7 | |||
c980b9f84d | |||
4651440457 | |||
7d58882ed8 | |||
e90ba95869 | |||
1015e79026 | |||
833cb8b430 | |||
a882ae283f | |||
305e90b254 | |||
c80683182d | |||
51cd4da97b | |||
cf71fe49d6 | |||
85a9a81804 | |||
72b979b587 | |||
bb4350519b | |||
62658e23a7 | |||
6a1f92af12 | |||
24ed21ce7d | |||
46a187a60e | |||
e0c7ea44ea | |||
f013180c4b | |||
b96b784b38 | |||
6864e95404 | |||
0ba4652595 | |||
8d0047649a | |||
2f180c264e | |||
7b87c4fd73 | |||
7d2233dd4a | |||
c30ae5750b |
30
.gitea/workflows/go-generate-check.yaml
Normal file
30
.gitea/workflows/go-generate-check.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
25
.gitea/workflows/go-generate-push.yaml
Normal file
25
.gitea/workflows/go-generate-push.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
30
.gitea/workflows/go-vendor-check.yaml
Normal file
30
.gitea/workflows/go-vendor-check.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
26
.gitea/workflows/go-vendor-push.yaml
Normal file
26
.gitea/workflows/go-vendor-push.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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,5 +1,2 @@
|
|||||||
node_modules
|
|
||||||
*.obscpio
|
|
||||||
autogits-tmp.tar.zst
|
|
||||||
*.osc
|
*.osc
|
||||||
*.conf
|
*.conf
|
||||||
|
15
_service
15
_service
@@ -1,15 +0,0 @@
|
|||||||
<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,6 +22,7 @@ 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}
|
||||||
@@ -31,55 +32,85 @@ Git Workflow tooling and utilities enabling automated handing of OBS projects
|
|||||||
as git repositories
|
as git repositories
|
||||||
|
|
||||||
|
|
||||||
%package -n gitea-events-rabbitmq-publisher
|
%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
|
||||||
Summary: Publishes Gitea webhook data via RabbitMQ
|
Summary: Publishes Gitea webhook data via RabbitMQ
|
||||||
|
|
||||||
%description -n gitea-events-rabbitmq-publisher
|
%description -n autogits-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 doc
|
%package -n autogits-gitea-status-proxy
|
||||||
Summary: Common documentation files
|
Summary: gitea-status-proxy
|
||||||
|
|
||||||
%description -n doc
|
%description -n autogits-gitea-status-proxy
|
||||||
Common documentation files
|
|
||||||
|
|
||||||
|
|
||||||
%package -n group-review
|
%package -n autogits-group-review
|
||||||
Summary: Reviews of groups defined in ProjectGit
|
Summary: Reviews of groups defined in ProjectGit
|
||||||
|
|
||||||
%description -n group-review
|
%description -n autogits-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 obs-staging-bot
|
%package -n autogits-obs-forward-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 obs-staging-bot
|
%description -n autogits-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 obs-status-service
|
%package -n autogits-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 obs-status-service
|
%description -n autogits-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 workflow-direct
|
%package -n autogits-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 workflow-direct
|
%description -n autogits-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 workflow-pr
|
%package -n autogits-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 workflow-pr
|
%description -n autogits-workflow-pr
|
||||||
Keeps ProjectGit PR in-sync with a PackageGit PR
|
Keeps ProjectGit PR in-sync with a PackageGit PR
|
||||||
|
|
||||||
|
|
||||||
@@ -88,12 +119,24 @@ 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
|
||||||
@@ -107,59 +150,104 @@ 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 gitea-events-rabbitmq-publisher
|
%pre -n autogits-gitea-events-rabbitmq-publisher
|
||||||
%service_add_pre gitea-events-rabbitmq-publisher.service
|
%service_add_pre gitea-events-rabbitmq-publisher.service
|
||||||
|
|
||||||
%post -n gitea-events-rabbitmq-publisher
|
%post -n autogits-gitea-events-rabbitmq-publisher
|
||||||
%service_add_post gitea-events-rabbitmq-publisher.service
|
%service_add_post gitea-events-rabbitmq-publisher.service
|
||||||
|
|
||||||
%preun -n gitea-events-rabbitmq-publisher
|
%preun -n autogits-gitea-events-rabbitmq-publisher
|
||||||
%service_del_preun gitea-events-rabbitmq-publisher.service
|
%service_del_preun gitea-events-rabbitmq-publisher.service
|
||||||
|
|
||||||
%postun -n gitea-events-rabbitmq-publisher
|
%postun -n autogits-gitea-events-rabbitmq-publisher
|
||||||
%service_del_postun gitea-events-rabbitmq-publisher.service
|
%service_del_postun gitea-events-rabbitmq-publisher.service
|
||||||
|
|
||||||
%files -n gitea-events-rabbitmq-publisher
|
%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
|
||||||
%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 doc
|
%files -n autogits-gitea-status-proxy
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc doc/README.md
|
%{_bindir}/gitea_status_proxy
|
||||||
%doc doc/workflows.md
|
|
||||||
|
|
||||||
%files -n group-review
|
%files -n autogits-group-review
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc group-review/README.md
|
%doc group-review/README.md
|
||||||
%{_bindir}/group-review
|
%{_bindir}/group-review
|
||||||
|
|
||||||
%files -n obs-staging-bot
|
%files -n autogits-obs-forward-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 obs-status-service
|
%files -n autogits-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 workflow-direct
|
%files -n autogits-workflow-direct
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-direct/README.md
|
%doc workflow-direct/README.md
|
||||||
%{_bindir}/workflow-direct
|
%{_bindir}/workflow-direct
|
||||||
|
|
||||||
%files -n workflow-pr
|
%files -n autogits-workflow-pr
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc workflow-pr/README.md
|
%doc workflow-pr/README.md
|
||||||
%{_bindir}/workflow-pr
|
%{_bindir}/workflow-pr
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -62,6 +62,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@@ -109,6 +109,7 @@ 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
|
||||||
@@ -119,7 +120,7 @@ func TestProjectGitParser(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "repo only",
|
name: "repo only",
|
||||||
prjgit: "repo.git",
|
prjgit: "repo.git#master",
|
||||||
org: "org",
|
org: "org",
|
||||||
branch: "br",
|
branch: "br",
|
||||||
res: [3]string{"org", "repo.git", "master"},
|
res: [3]string{"org", "repo.git", "master"},
|
||||||
@@ -127,12 +128,13 @@ func TestProjectGitParser(t *testing.T) {
|
|||||||
{
|
{
|
||||||
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: "repo.git#somebranch",
|
prjgit: "org2/repo.git#somebranch",
|
||||||
res: [3]string{"org2", "repo.git", "somebranch"},
|
res: [3]string{"org2", "repo.git", "somebranch"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -149,25 +151,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#",
|
prjgit: "oorg/foo.bar#master",
|
||||||
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: "#mybranch",
|
prjgit: "org3/_ObsPrj#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/#mybranch",
|
prjgit: "org1/_ObsPrj#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: "/repo#",
|
prjgit: "org3/repo#master",
|
||||||
res: [3]string{"org3", "repo", "master"},
|
res: [3]string{"org3", "repo", "master"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,7 @@ 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,6 +78,7 @@ type GitHandlerImpl struct {
|
|||||||
GitEmail string
|
GitEmail string
|
||||||
|
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
|
quiet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GitHandlerImpl) GetPath() string {
|
func (s *GitHandlerImpl) GetPath() string {
|
||||||
@@ -211,7 +213,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 {
|
if len(branch) == 0 && remoteUrlComp != nil && remoteUrlComp.Commit != "HEAD" {
|
||||||
branch = remoteUrlComp.Commit
|
branch = remoteUrlComp.Commit
|
||||||
remoteBranch = branch
|
remoteBranch = branch
|
||||||
} else if len(branch) > 0 {
|
} else if len(branch) > 0 {
|
||||||
@@ -240,12 +242,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.GitExecOrPanic(repo, "submodule", "deinit", "--all", "--force")
|
e.GitExecQuietOrPanic(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)
|
||||||
@@ -264,7 +266,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")
|
||||||
@@ -274,7 +276,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", "--branches", "--hash", branchName)
|
id, err := e.GitExecWithOutput(gitDir, "show-ref", "--heads", "--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)
|
||||||
}
|
}
|
||||||
@@ -361,7 +363,9 @@ 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)
|
||||||
@@ -370,6 +374,13 @@ 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
|
||||||
}
|
}
|
||||||
|
@@ -24,11 +24,13 @@ 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"
|
||||||
@@ -182,7 +184,6 @@ 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)
|
||||||
@@ -199,7 +200,32 @@ 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
|
||||||
}
|
}
|
||||||
@@ -212,7 +238,9 @@ 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)
|
||||||
@@ -287,10 +315,9 @@ 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, limit int64
|
var page int64
|
||||||
|
|
||||||
prs := make([]*models.PullRequest, 0)
|
prs := make([]*models.PullRequest, 0)
|
||||||
limit = 20
|
|
||||||
state := "open"
|
state := "open"
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -302,16 +329,18 @@ 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(req.Payload) < int(limit) {
|
if len(prs) >= gitea.headers.Length {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,21 +349,23 @@ 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) {
|
||||||
page := int64(1)
|
var page int64
|
||||||
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).WithLimit(&limit),
|
repository.NewRepoListStatusesParams().WithDefaults().WithOwner(org).WithRepo(repo).WithSha(hash).WithPage(&page),
|
||||||
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(r.Payload) < int(limit) {
|
if len(res) >= gitea.headers.Length {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,19 +408,18 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -397,11 +427,13 @@ func (gitea *GiteaTransport) GetPullRequestReviews(org, project string, PRnum in
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
allReviews = slices.Concat(allReviews, reviews.Payload)
|
if len(reviews.Payload) == 0 {
|
||||||
if len(reviews.Payload) < int(limit) {
|
break
|
||||||
|
}
|
||||||
|
allReviews = slices.Concat(allReviews, reviews.Payload)
|
||||||
|
if len(allReviews) >= gitea.headers.Length {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
page++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return allReviews, nil
|
return allReviews, nil
|
||||||
@@ -469,7 +501,6 @@ 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++ {
|
||||||
@@ -477,7 +508,6 @@ 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 {
|
||||||
@@ -490,8 +520,11 @@ 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(list.Payload) < int(bigLimit) {
|
if len(ret) >= gitea.headers.Length {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,7 +533,6 @@ 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 {
|
||||||
@@ -511,7 +543,6 @@ 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 {
|
||||||
@@ -564,9 +595,12 @@ 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
|
||||||
@@ -780,15 +814,18 @@ 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++
|
||||||
|
|
||||||
for _, d := range res.Payload {
|
retData = append(retData, res.Payload...)
|
||||||
if d != nil {
|
if len(retData) >= gitea.headers.Length {
|
||||||
retData = append(retData, d)
|
break
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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))
|
||||||
})
|
})
|
||||||
|
@@ -63,6 +63,10 @@ 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":
|
||||||
|
27
common/pr.go
27
common/pr.go
@@ -63,7 +63,6 @@ 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)
|
||||||
@@ -79,9 +78,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 {
|
||||||
|
|
||||||
lines := SplitLines(item.RefIssue.Body)
|
_, prs := ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(item.RefIssue.Body)))
|
||||||
for _, line := range lines {
|
for _, pr := range prs {
|
||||||
if strings.TrimSpace(line) == prRefLine {
|
if pr.Org == org && pr.Repo == repo && pr.Num == num {
|
||||||
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
|
||||||
@@ -282,6 +281,12 @@ 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)
|
||||||
|
|
||||||
@@ -380,7 +385,8 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
}
|
}
|
||||||
prjgit := prjgit_info.PR
|
prjgit := prjgit_info.PR
|
||||||
|
|
||||||
remote, err := git.GitClone(DefaultGitPrj, rs.Config.Branch, prjgit.Base.Repo.SSHURL)
|
_, _, prjgitBranch := rs.Config.GetPrjGit()
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -409,6 +415,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -499,7 +506,15 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
|
|||||||
if rs.IsPrjGitPR(prinfo.PR) {
|
if rs.IsPrjGitPR(prinfo.PR) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prinfo.RemoteName, err = git.GitClone(repo.Name, rs.Config.Branch, repo.SSHURL)
|
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)
|
||||||
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)
|
||||||
|
@@ -48,11 +48,13 @@ 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: "barPrj",
|
GitProjectName: "foo/barPrj#master",
|
||||||
}
|
}
|
||||||
|
|
||||||
type prdata struct {
|
type prdata struct {
|
||||||
@@ -638,7 +640,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "No reviewers",
|
name: "No reviewers",
|
||||||
config: common.AutogitConfig{
|
config: common.AutogitConfig{
|
||||||
GitProjectName: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{},
|
Reviewers: []string{},
|
||||||
@@ -648,7 +650,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "One project reviewer only",
|
name: "One project reviewer only",
|
||||||
config: common.AutogitConfig{
|
config: common.AutogitConfig{
|
||||||
GitProjectName: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1"},
|
Reviewers: []string{"-user1"},
|
||||||
@@ -658,7 +660,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: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1", "user2"},
|
Reviewers: []string{"-user1", "user2"},
|
||||||
@@ -668,7 +670,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: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1", "submitter"},
|
Reviewers: []string{"-user1", "submitter"},
|
||||||
@@ -678,7 +680,7 @@ func TestPRAssignReviewers(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Reviews are done",
|
name: "Reviews are done",
|
||||||
config: common.AutogitConfig{
|
config: common.AutogitConfig{
|
||||||
GitProjectName: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1", "user2"},
|
Reviewers: []string{"-user1", "user2"},
|
||||||
@@ -712,7 +714,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: "repo",
|
GitProjectName: "org/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1", "user2"},
|
Reviewers: []string{"-user1", "user2"},
|
||||||
@@ -744,7 +746,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: "repo",
|
GitProjectName: "prg/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{"-user1", "user2", "~bot"},
|
Reviewers: []string{"-user1", "user2", "~bot"},
|
||||||
@@ -852,7 +854,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: "repo",
|
GitProjectName: "org/repo#main",
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
Reviewers: []string{},
|
Reviewers: []string{},
|
||||||
@@ -923,7 +925,7 @@ func TestPRMerge(t *testing.T) {
|
|||||||
|
|
||||||
config := &common.AutogitConfig{
|
config := &common.AutogitConfig{
|
||||||
Organization: "org",
|
Organization: "org",
|
||||||
GitProjectName: "prj",
|
GitProjectName: "org/prj#master",
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@@ -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. %#v", err)
|
LogError("Error in RabbitMQ connection:", err)
|
||||||
LogInfo("Reconnecting in 2 seconds...")
|
LogInfo("Reconnecting in 2 seconds...")
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
@@ -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
4
go.mod
@@ -10,8 +10,10 @@ 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 (
|
||||||
@@ -30,11 +32,9 @@ 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
4
go.sum
@@ -1,5 +1,9 @@
|
|||||||
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=
|
||||||
|
@@ -125,6 +125,16 @@ 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)
|
||||||
@@ -164,6 +174,22 @@ 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 {
|
||||||
@@ -173,42 +199,32 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
|||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
common.LogInfo(" review is not requested for", groupName)
|
common.LogInfo(" review is not requested for", groupName)
|
||||||
if !common.IsDryRun {
|
return nil
|
||||||
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 {
|
||||||
common.LogError("Cannot find config for:", fmt.Sprintf("%s/%s!%s", org, repo, pr.Base.Name))
|
return fmt.Errorf("Cannot find config for: %s", pr.URL)
|
||||||
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")
|
||||||
if !common.IsDryRun {
|
return nil
|
||||||
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 {
|
||||||
common.LogInfo(" ** No reviews associated with request:", subject.URL, "Error:", err)
|
return fmt.Errorf("Failed to fetch reviews for: %v: %w", pr.URL, 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 {
|
||||||
common.LogError(err)
|
return fmt.Errorf("Failed to fetch timeline to review. %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
groupConfig, err := config.GetReviewGroup(groupName)
|
groupConfig, err := config.GetReviewGroup(groupName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.LogError(err)
|
return fmt.Errorf("Failed to fetch review group. %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// submitter cannot be reviewer
|
// submitter cannot be reviewer
|
||||||
@@ -220,27 +236,31 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
|||||||
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 {
|
||||||
gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+reviewer)
|
text := reviewer + " approved a review on behalf of " + groupName
|
||||||
UnrequestReviews(gitea, org, repo, id, requestReviewers)
|
if review := FindOurLastReviewInTimeline(timeline); review == nil || review.Body != text {
|
||||||
if !common.IsDryRun {
|
_, err := gitea.AddReviewComment(pr, common.ReviewStateApproved, text)
|
||||||
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
if err != nil {
|
||||||
common.LogDebug(" Cannot set notification as read", err)
|
common.LogError(" -> failed to write approval comment", 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
|
return nil
|
||||||
} else if ReviewRejected(review.Body) {
|
} else if ReviewRejected(review.Body) {
|
||||||
if !common.IsDryRun {
|
if !common.IsDryRun {
|
||||||
gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Changes requested. See review by: "+reviewer)
|
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)
|
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)
|
common.LogInfo(" -> declined by", reviewer)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,15 +290,22 @@ func ProcessNotifications(notification *models.NotificationThread, gitea common.
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ", "), ". 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.")
|
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.")
|
||||||
if slices.Contains(groupConfig.Reviewers, pr.User.UserName) {
|
if slices.Contains(groupConfig.Reviewers, pr.User.UserName) {
|
||||||
helpComment = helpComment + "\n\n" + fmt.Sprintln("Submitter is member of this review group, hence they are excluded from being one of the reviewers here")
|
helpComment = helpComment + "\n\n" +
|
||||||
|
"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(gitea common.Gitea) {
|
func PeriodReviewCheck() {
|
||||||
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)
|
||||||
@@ -287,14 +314,15 @@ func PeriodReviewCheck(gitea common.Gitea) {
|
|||||||
|
|
||||||
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", 5, "Notification polling interval in minutes (min 1 min)")
|
interval := flag.Int64("interval", 10, "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")
|
||||||
@@ -331,7 +359,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)
|
||||||
@@ -375,10 +403,13 @@ 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
|
||||||
@@ -415,7 +446,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PeriodReviewCheck(gitea)
|
PeriodReviewCheck()
|
||||||
time.Sleep(time.Duration(*interval * int64(time.Minute)))
|
time.Sleep(time.Duration(*interval * int64(time.Minute)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,25 @@ 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
|
||||||
}
|
}
|
||||||
|
27
hujson/main.go
Normal file
27
hujson/main.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
return BuildStatusSummarySuccess, SomeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string) (*common.ProjectMeta, error) {
|
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||||
common.LogDebug("repo content fetching ...")
|
common.LogDebug("repo content fetching ...")
|
||||||
err := FetchPrGit(git, pr)
|
err := FetchPrGit(git, pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -289,7 +289,15 @@ func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullReque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta, err := ObsClient.GetProjectMeta(buildPrj)
|
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)
|
||||||
|
}
|
||||||
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
|
||||||
@@ -314,6 +322,9 @@ 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
|
||||||
@@ -414,7 +425,8 @@ 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
|
||||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject)
|
common.LogDebug(" Staging master:", config.StagingProject)
|
||||||
|
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return RequestModificationNoChange, err
|
return RequestModificationNoChange, err
|
||||||
}
|
}
|
||||||
@@ -428,6 +440,8 @@ 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
|
||||||
}
|
}
|
||||||
@@ -584,7 +598,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.URL)
|
common.LogInfo("Cooldown period for cleanup of", thread.Subject.HTMLURL)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,6 +636,14 @@ 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)
|
||||||
@@ -805,7 +827,12 @@ 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.ReviewStateComment, "No package changes. Not rebuilding project by default")
|
_, 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
@@ -821,6 +848,22 @@ 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/" +
|
||||||
@@ -829,8 +872,7 @@ 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"
|
||||||
}
|
}
|
||||||
gitea.SetCommitStatus(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Head.Sha, status)
|
SetStatus(gitea, org, repo, 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,
|
||||||
@@ -854,32 +896,34 @@ 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)
|
||||||
gitea.SetCommitStatus(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Head.Sha, status)
|
if !IsDryRun {
|
||||||
|
if err = SetStatus(gitea, org, repo, pr.Head.Sha, status); err != nil {
|
||||||
// waiting for build results -- nothing to do
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return done, nil
|
||||||
|
|
||||||
} else if err == NonActionableReviewError || err == NoReviewsFoundError {
|
} else if err == NonActionableReviewError || err == NoReviewsFoundError {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
@@ -20,6 +20,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -191,15 +192,24 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rescanRepoError error
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if err := RescanRepositories(); err != nil {
|
if rescanRepoError = RescanRepositories(); rescanRepoError != 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)
|
||||||
@@ -259,6 +269,33 @@ 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")
|
||||||
|
@@ -29,13 +29,15 @@ 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 {
|
||||||
@@ -110,6 +112,27 @@ 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{}
|
||||||
@@ -161,6 +184,8 @@ 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()
|
||||||
@@ -169,6 +194,7 @@ 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, "/")
|
||||||
@@ -180,14 +206,28 @@ 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 {
|
||||||
|
16
systemd/obs-staging-bot.service
Normal file
16
systemd/obs-staging-bot.service
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[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
|
||||||
|
|
||||||
|
|
4
test/main.go
Normal file
4
test/main.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
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,6 +132,7 @@ 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
|
||||||
|
@@ -33,6 +33,7 @@ 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:
|
||||||
|
@@ -3,6 +3,7 @@ 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"
|
||||||
@@ -27,6 +28,10 @@ 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)
|
||||||
@@ -69,7 +74,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", headSha))
|
common.PanicOnError(git.GitExec(path.Join(common.DefaultGitPrj, submodule), "checkout", "-f", headSha))
|
||||||
}
|
}
|
||||||
|
|
||||||
type PRProcessor struct {
|
type PRProcessor struct {
|
||||||
@@ -99,7 +104,10 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
common.LogDebug("found config", config)
|
if common.GetLoggingLevel() >= common.LogLevelDebug {
|
||||||
|
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)
|
||||||
@@ -158,10 +166,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, "referencing\n", ref)
|
commitMsg := fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nreferencing PRs:\n", ref)
|
||||||
|
|
||||||
if revert {
|
if revert {
|
||||||
commitMsg = fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "removing\n", ref)
|
commitMsg = fmt.Sprintln("auto-created for", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nremoving PRs:\n", ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSubmoduleInPR(submodulePath, prHead, git)
|
updateSubmoduleInPR(submodulePath, prHead, git)
|
||||||
@@ -170,6 +178,8 @@ 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
|
||||||
@@ -209,7 +219,7 @@ func (pr *PRProcessor) CreatePRjGitPR(prjGitPRbranch string, prset *common.PRSet
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !common.IsDryRun {
|
if !common.IsDryRun && !pr.config.NoProjectGitPR {
|
||||||
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))
|
||||||
}
|
}
|
||||||
@@ -247,7 +257,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. %s", s)
|
return fmt.Errorf("Unexpected conflict in rebase. %v", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip")
|
conflict = git.GitExec(common.DefaultGitPrj, "rebase", "--skip")
|
||||||
@@ -265,6 +275,12 @@ 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, "/") {
|
||||||
@@ -300,6 +316,17 @@ 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}
|
||||||
@@ -310,7 +337,6 @@ 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,
|
||||||
@@ -319,6 +345,9 @@ func (pr *PRProcessor) UpdatePrjGitPR(prset *common.PRSet) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove closed PRs from prset
|
||||||
|
prset.RemoveClosedPRs()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +373,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...")
|
common.LogDebug("Missing PrjGit. Need to create one under branch", prjGitPRbranch)
|
||||||
|
|
||||||
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
if err = pr.CreatePRjGitPR(prjGitPRbranch, prset); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -421,7 +450,11 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
|
|||||||
|
|
||||||
if prjGitPR == nil {
|
if prjGitPR == nil {
|
||||||
prjGitPR, err = prset.GetPrjGitPR()
|
prjGitPR, err = prset.GetPrjGitPR()
|
||||||
if err != nil {
|
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 {
|
||||||
common.LogError("Error fetching PrjGitPR:", err)
|
common.LogError("Error fetching PrjGitPR:", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -433,11 +466,12 @@ 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()
|
||||||
if pr, err := prset.GetPrjGitPR(); err == nil {
|
// TODO: this is broken...
|
||||||
|
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, pr.PR.Head.Sha)
|
new_subs, err := git.GitSubmoduleList(common.DefaultGitPrj, "HEAD")
|
||||||
common.PanicOnError(err)
|
common.PanicOnError(err)
|
||||||
common.LogDebug("Submodule parse done")
|
common.LogDebug("Submodule parse done")
|
||||||
|
|
||||||
@@ -445,6 +479,7 @@ 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
|
||||||
@@ -452,6 +487,8 @@ 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 {
|
||||||
@@ -461,24 +498,29 @@ 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.GitExecOrPanic(common.DefaultGitPrj, "submodule", "deinit", "--all", "--force")
|
git.GitExecQuietOrPanic(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 && prset.PRs[0] == prjGitPR && prjGitPR.PR.User.UserName == prset.BotUser {
|
if len(prset.PRs) == 1 && prjGitPR != nil && 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 {
|
||||||
@@ -500,7 +542,6 @@ 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")
|
||||||
@@ -515,8 +556,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user