Compare commits

...

80 commits
v0.7.0 ... main

Author SHA1 Message Date
renovate[bot]
944b70cee9
deps: update dependency golangci-lint to v2.8.0 (#325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-08 01:43:29 +00:00
renovate[bot]
2a1a08057b
deps: update module github.com/yuin/goldmark to v1.7.16 (#324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-06 10:02:37 +00:00
renovate[bot]
60cbffba9d
deps: update module github.com/yuin/goldmark to v1.7.15 (#323)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 21:42:52 +00:00
renovate[bot]
6ef1405140
deps: update module github.com/yuin/goldmark to v1.7.14 (#322)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-04 14:32:11 +00:00
renovate[bot]
d9c8d3e5af
deps: update dependency ko-build/ko to v0.18.1 (#320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 16:47:59 +00:00
renovate[bot]
b48c9a654f
deps: update dependency rust-lang/mdbook to v0.5.2 (#319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 22:50:29 +00:00
renovate[bot]
163eaf31a6
deps: update dependency golangci-lint to v2.7.2 (#316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-07 18:13:31 +00:00
renovate[bot]
804cf8040a
deps: update dependency golangci-lint to v2.7.1 (#313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 18:00:02 +00:00
renovate[bot]
60d9aa3982
deps: update module github.com/go-git/go-billy/v5 to v5.7.0 (#315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-06 13:00:46 +00:00
renovate[bot]
8d6175c13b
deps: update module github.com/spf13/cobra to v1.10.2 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 03:50:01 +00:00
renovate[bot]
6dfa96a9ba
deps: update dependency golangci-lint to v2.7.0 (#311)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 21:01:35 +00:00
renovate[bot]
fb2a0b8167
deps: update dependency go to v1.25.5 (#310)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 17:37:09 +00:00
renovate[bot]
db611e5cc2
deps: update module gitlab.com/gitlab-org/api/client-go to v0.161.1 (#309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 21:30:33 +00:00
renovate[bot]
b451c08634
deps: update module gitlab.com/gitlab-org/api/client-go to v0.160.2 (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 12:50:46 +00:00
renovate[bot]
9592a6a975
deps: update module github.com/go-git/go-git/v5 to v5.16.4 (#307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 02:08:18 +00:00
renovate[bot]
e5bccc9fb9
deps: update module golang.org/x/crypto to v0.45.0 [security] (#306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 09:57:10 +00:00
renovate[bot]
995f4beb9a
deps: update dependency rust-lang/mdbook to v0.5.1 (#305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 05:39:33 +00:00
renovate[bot]
ec851d7511
deps: update module gitlab.com/gitlab-org/api/client-go to v0.160.1 (#304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 21:04:09 +00:00
renovate[bot]
ad845e61c7
deps: update dependency rust-lang/mdbook to v0.5.0 (#303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 06:50:50 +00:00
renovate[bot]
0f040ff8e7
deps: update dependency golangci-lint to v2.6.2 (#301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 18:13:08 +00:00
renovate[bot]
6dd0424029
deps: update module gitlab.com/gitlab-org/api/client-go to v0.160.0 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 00:00:03 +00:00
renovate[bot]
291581ef6d
deps: update dependency go to v1.25.4 (#299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 21:07:16 +00:00
renovate[bot]
29a033103d
deps: update module gitlab.com/gitlab-org/api/client-go to v0.159.0 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-05 01:05:09 +00:00
renovate[bot]
c1c2111e03
deps: update dependency golangci-lint to v2.6.1 (#297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 12:51:40 +00:00
renovate[bot]
c4796a546e
deps: update module gitlab.com/gitlab-org/api/client-go to v0.158.0 (#296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 11:31:58 +00:00
renovate[bot]
cb92e2b67f
deps: update dependency golangci-lint to v2.6.0 (#295)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 01:06:35 +00:00
renovate[bot]
23e9d06c6e
deps: update module gitlab.com/gitlab-org/api/client-go to v0.157.1 (#294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 16:15:14 +00:00
renovate[bot]
0129c78abc
deps: update dependency go to v1.25.3 (#293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 22:16:30 +00:00
renovate[bot]
226548adfa
deps: update module gitlab.com/gitlab-org/api/client-go to v0.157.0 (#292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 18:11:57 +00:00
renovate[bot]
0d2efe0a6d
deps: update module gitlab.com/gitlab-org/api/client-go to v0.156.0 (#291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 08:51:53 +00:00
renovate[bot]
cead3fb5c1
deps: update module gitlab.com/gitlab-org/api/client-go to v0.155.0 (#290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 02:00:41 +00:00
renovate[bot]
918249a0d3
deps: update module gitlab.com/gitlab-org/api/client-go to v0.154.0 (#289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 15:03:19 +00:00
renovate[bot]
f24b69e8fa
deps: update module gitlab.com/gitlab-org/api/client-go to v0.153.0 (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 09:01:44 +00:00
renovate[bot]
d95b779f83
deps: update dependency go to v1.25.2 (#287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 04:15:40 +00:00
renovate[bot]
e32838e3d0
deps: update module gitlab.com/gitlab-org/api/client-go to v0.152.0 (#286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-07 00:46:25 +00:00
renovate[bot]
8607cb6f71
deps: update module github.com/go-git/go-git/v5 to v5.16.3 (#284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-05 21:47:34 +00:00
renovate[bot]
ff899fe9e8
deps: update module gitlab.com/gitlab-org/api/client-go to v0.151.0 (#283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-05 00:49:57 +00:00
renovate[bot]
0d16c770d3
deps: update module gitlab.com/gitlab-org/api/client-go to v0.150.0 (#281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 10:02:04 +00:00
renovate[bot]
21e3fdbcad
deps: update module gitlab.com/gitlab-org/api/client-go to v0.149.0 (#280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 01:14:18 +00:00
renovate[bot]
71364599d8
deps: update module gitlab.com/gitlab-org/api/client-go to v0.148.1 (#279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-26 17:51:38 +00:00
fa27415be5
chore(main): release v0.7.1 (#278) 2025-09-26 05:36:27 +00:00
Jonas L.
b0c50518b3
fix: no html escaping for changelog template (#277) 2025-09-25 12:40:35 +02:00
Jonas L.
612928a382
fix: using code blocks within release-notes (#275)
Increase the number of code blocks backticks to 4 for the release note prefix and suffix, to allow users to embed their own code blocks using only 3 backticks.
2025-09-25 12:25:35 +02:00
renovate[bot]
93bb42e781
deps: update module gitlab.com/gitlab-org/api/client-go to v0.148.0 (#274)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-23 11:10:35 +00:00
renovate[bot]
63a8b91b34
deps: update module gitlab.com/gitlab-org/api/client-go to v0.147.1 (#273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 16:42:57 +00:00
renovate[bot]
d0a44e3fb8
deps: update module gitlab.com/gitlab-org/api/client-go to v0.147.0 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 10:09:57 +00:00
renovate[bot]
63acf9aa3a
deps: update dependency golangci-lint to v2.5.0 (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-21 20:45:11 +00:00
renovate[bot]
250cc6c2aa
deps: update module gitlab.com/gitlab-org/api/client-go to v0.146.0 (#270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 14:32:34 +00:00
renovate[bot]
ef94c3c8f7
deps: update module gitlab.com/gitlab-org/api/client-go to v0.145.0 (#269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 10:51:32 +00:00
renovate[bot]
f300bbb6b0
deps: update actions/checkout digest to 08eba0b (#264) 2025-09-15 14:47:12 +02:00
renovate[bot]
cc627599db
deps: update codecov/codecov-action digest to 5a10915 (#266)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 14:46:37 +02:00
renovate[bot]
8c6b99560c
deps: update module codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 to v2.2.0 (#268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 09:39:48 +00:00
renovate[bot]
2c1d29f639
deps: update module gitlab.com/gitlab-org/api/client-go to v0.144.1 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 01:31:08 +00:00
fcf7906149
test: add e2e tests with local Forgejo instance (#201)
* feat(forge): add new forge for forgejo

We only support repositories hosted on Forgejo instances, but not
Forgejo Actions or Woodpecker as CI solutions for now.

* test(e2e): introduce e2e test framework with local forgejo
2025-09-13 12:00:54 +02:00
renovate[bot]
afef176e37
deps: update module gitlab.com/gitlab-org/api/client-go to v0.144.0 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-12 15:20:21 +00:00
renovate[bot]
e3d2cfa6b8
deps: update codecov/codecov-action digest to 5a10915 (#253) 2025-09-11 13:30:36 +02:00
renovate[bot]
6b9738bcea
deps: update module gitlab.com/gitlab-org/api/client-go to v0.143.3 (#260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 06:39:09 +00:00
renovate[bot]
e7950cfbc1
deps: update module gitlab.com/gitlab-org/api/client-go to v0.143.2 (#259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 13:03:16 +00:00
renovate[bot]
bccfa93a15
deps: update module gitlab.com/gitlab-org/api/client-go to v0.143.1 (#258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 22:14:06 +00:00
renovate[bot]
3f9108e702
deps: update module gitlab.com/gitlab-org/api/client-go to v0.143.0 (#257)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 12:07:27 +00:00
renovate[bot]
5ea1798a68
deps: update module github.com/spf13/cobra to v1.10.1 (#256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 21:37:52 +00:00
renovate[bot]
de80649838
deps: update module gitlab.com/gitlab-org/api/client-go to v0.142.6 (#255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 21:37:38 +00:00
renovate[bot]
f765abbb92
deps: update dependency go to v1.25.1 (#254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-06 18:51:22 +00:00
renovate[bot]
132f62d82d
deps: update module gitlab.com/gitlab-org/api/client-go to v0.142.5 (#252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 10:33:26 +00:00
renovate[bot]
1d2f74752b
deps: update module gitlab.com/gitlab-org/api/client-go to v0.142.4 (#251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 07:46:08 +00:00
renovate[bot]
84b3acbe4d
deps: update module github.com/stretchr/testify to v1.11.1 (#250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 12:05:21 +00:00
renovate[bot]
c4610b8e5e
deps: update module gitlab.com/gitlab-org/api/client-go to v0.142.2 (#249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-26 18:07:17 +00:00
renovate[bot]
ce56ac0cd1
deps: update module gitlab.com/gitlab-org/api/client-go to v0.142.1 (#248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 05:34:37 +00:00
renovate[bot]
69be23dd6f
deps: update module github.com/stretchr/testify to v1.11.0 (#247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-24 22:11:38 +00:00
852c08ed3d
ci: fix ko settings after using mise (#246) 2025-08-24 17:39:10 +02:00
e1afa22e0a
ci: mdbooks binary is missing (#245) 2025-08-24 17:16:40 +02:00
f077b647e7
ci: separate renovate manager for toml (#244) 2025-08-24 15:07:58 +00:00
44b76e55f8
ci: allow regex manager in toml files for mise (#243) 2025-08-24 14:56:11 +00:00
e83a7c9a23
ci: mise cleanup (#242)
- Renovate does not find the "github:*" dependencies in `mise.toml`
- The `mdbooks` tools was still installed manually with our own action,
  this is removed and mise is used instead.
2025-08-24 14:52:01 +00:00
renovate[bot]
563885899c
deps: update actions/checkout action to v5 (#239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-24 16:46:49 +02:00
renovate[bot]
16ba2c6b09
deps: update module github.com/google/go-github/v72 to v74 (#241)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-24 16:46:16 +02:00
renovate[bot]
e6c8f3f93b
deps: update actions/upload-pages-artifact action to v4 (#240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-24 16:45:41 +02:00
e6503da93a
refactor(cmd): use factories instead of global cobra command structs (#238)
This enables us to create new commands for e2e tests.
2025-08-24 14:44:05 +00:00
renovate[bot]
5b5b29c0b5
deps: update dependency go to v1.25.0 (#222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-24 13:50:59 +00:00
c768260a2e
chore: use mise to install all tools in CI and locally (#237)
Makes sure that I have the same versions locally as CI
2025-08-24 15:49:23 +02:00
44 changed files with 1418 additions and 327 deletions

View file

@ -1,16 +0,0 @@
name: "Setup mdbook"
inputs:
version:
description: "mdbook version"
runs:
using: composite
steps:
- name: Setup mdbook
shell: bash
env:
url: https://github.com/rust-lang/mdbook/releases/download/${{ inputs.version }}/mdbook-${{ inputs.version }}-x86_64-unknown-linux-gnu.tar.gz
run: |
mkdir mdbook
curl -sSL "$url" | tar -xz --directory=./mdbook
echo `pwd`/mdbook >> $GITHUB_PATH

View file

@ -69,6 +69,15 @@
': (?<currentValue>.+) # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: lookupName=(?<packageName>[^\\s]+))?(?: versioning=(?<versioning>[a-z-]+))?(?: extractVersion=(?<extractVersion>[^\\s]+))?', ': (?<currentValue>.+) # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: lookupName=(?<packageName>[^\\s]+))?(?: versioning=(?<versioning>[a-z-]+))?(?: extractVersion=(?<extractVersion>[^\\s]+))?',
], ],
}, },
{
customType: 'regex',
managerFilePatterns: [
'/.+\\.toml$/'
],
matchStrings: [
'= "(?<currentValue>.+)" # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: lookupName=(?<packageName>[^\\s]+))?(?: versioning=(?<versioning>[a-z-]+))?(?: extractVersion=(?<extractVersion>[^\\s]+))?',
],
}
], ],
postUpdateOptions: [ postUpdateOptions: [
'gomodUpdateImportPaths', 'gomodUpdateImportPaths',

View file

@ -2,7 +2,7 @@ name: ci
on: on:
push: push:
branches: [main] branches: [ main ]
pull_request: pull_request:
jobs: jobs:
@ -10,40 +10,36 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Set up Go - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- name: Run golangci-lint - name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8 uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with: with:
version: v2.4.0 # renovate: datasource=github-releases depName=golangci/golangci-lint install-mode: none
args: --timeout 5m args: --timeout 5m
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Set up Go - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- name: Run tests - name: Run tests
run: go test -v -race -coverpkg=./... -coverprofile=coverage.txt ./... run: go test -v -race -coverpkg=./... -coverprofile=coverage.txt ./...
- name: Upload results to Codecov - name: Upload results to Codecov
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: unit
go-mod-tidy: test-e2e-forgejo:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
@ -53,6 +49,30 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
# We can not use "jobs.<job>.services".
# We want to mount the config file, which is only available after "Checkout".
- name: Start Forgejo
working-directory: test/e2e/forgejo
run: docker compose up --wait
- name: Run tests
run: go test -tags e2e_forgejo -v -race -coverpkg=./... -coverprofile=coverage.txt ./test/e2e/forgejo
- name: Upload results to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: e2e
go-mod-tidy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Run go mod tidy - name: Run go mod tidy
run: go mod tidy run: go mod tidy

View file

@ -13,13 +13,11 @@ jobs:
id-token: write # To update the deployment status id-token: write # To update the deployment status
steps: steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
lfs: "true" lfs: "true"
- uses: ./.github/actions/setup-mdbook - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
with:
version: v0.4.52 # renovate: datasource=github-releases depName=rust-lang/mdbook
- name: Build Book - name: Build Book
working-directory: docs working-directory: docs
@ -29,7 +27,7 @@ jobs:
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
with: with:
# Upload entire repository # Upload entire repository
path: "docs/book" path: "docs/book"

View file

@ -11,7 +11,7 @@ jobs:
REMOTE: mirror REMOTE: mirror
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
# Need all to fetch all tags so we can push them # Need all to fetch all tags so we can push them
fetch-depth: 0 fetch-depth: 0

View file

@ -14,12 +14,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Set up Go - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with: - name: Prepare ko
go-version-file: go.mod run: |
echo "${{ github.token }}" | ko login ghcr.io --username "dummy" --password-stdin
repo=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
echo "KO_DOCKER_REPO=ghcr.io/${repo}"
echo "KO_DOCKER_REPO=ghcr.io/${repo}" >> $GITHUB_ENV
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- run: ko build --bare --tags ${{ github.ref_name }} github.com/apricote/releaser-pleaser/cmd/rp - run: ko build --bare --tags ${{ github.ref_name }} github.com/apricote/releaser-pleaser/cmd/rp

View file

@ -2,7 +2,7 @@ name: releaser-pleaser
on: on:
push: push:
branches: [main] branches: [ main ]
# Using pull_request_target to avoid tainting the actual release PR with code from open feature pull requests # Using pull_request_target to avoid tainting the actual release PR with code from open feature pull requests
pull_request_target: pull_request_target:
types: types:
@ -17,7 +17,7 @@ concurrency:
group: releaser-pleaser group: releaser-pleaser
cancel-in-progress: true cancel-in-progress: true
permissions: {} permissions: { }
jobs: jobs:
releaser-pleaser: releaser-pleaser:
@ -25,23 +25,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with: with:
ref: main ref: main
- name: Set up Go - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
# Build container image from current commit and replace image ref in `action.yml` # Build container image from current commit and replace image ref in `action.yml`
# Without this, any new flags in `action.yml` would break the job in this repository until the new # Without this, any new flags in `action.yml` would break the job in this repository until the new
# version is released. But a new version can only be released if this job works. # version is released. But a new version can only be released if this job works.
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- run: ko build --bare --local --platform linux/amd64 --tags ci github.com/apricote/releaser-pleaser/cmd/rp - run: ko build --bare --local --platform linux/amd64 --tags ci github.com/apricote/releaser-pleaser/cmd/rp
- run: mkdir -p .github/actions/releaser-pleaser - run: "sed -i 's|image: .*$|image: docker://ko.local:ci|g' action.yml"
- run: "sed -i 's|image: .*$|image: docker://ghcr.io/apricote/releaser-pleaser:ci|g' action.yml"
# Dogfood the action to make sure it works for users. # Dogfood the action to make sure it works for users.
- name: releaser-pleaser - name: releaser-pleaser

View file

@ -48,6 +48,10 @@ linters:
- name: exported - name: exported
disabled: true disabled: true
gomoddirectives:
replace-allow-list:
- codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2
formatters: formatters:
enable: enable:
- gci - gci

View file

@ -1,5 +1,12 @@
# Changelog # Changelog
## [v0.7.1](https://github.com/apricote/releaser-pleaser/releases/tag/v0.7.1)
### Bug Fixes
- using code blocks within release-notes (#275)
- no html escaping for changelog template (#277)
## [v0.7.0](https://github.com/apricote/releaser-pleaser/releases/tag/v0.7.0) ## [v0.7.0](https://github.com/apricote/releaser-pleaser/releases/tag/v0.7.0)
### Highlights :sparkles: ### Highlights :sparkles:

View file

@ -25,7 +25,7 @@ inputs:
outputs: { } outputs: { }
runs: runs:
using: 'docker' using: 'docker'
image: docker://ghcr.io/apricote/releaser-pleaser:v0.7.0 # x-releaser-pleaser-version image: docker://ghcr.io/apricote/releaser-pleaser:v0.7.1 # x-releaser-pleaser-version
args: args:
- run - run
- --forge=github - --forge=github

View file

@ -7,21 +7,23 @@ import (
"os/signal" "os/signal"
"runtime/debug" "runtime/debug"
"syscall" "syscall"
"time"
"github.com/lmittmann/tint"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var logger *slog.Logger func NewRootCmd() *cobra.Command {
var cmd = &cobra.Command{
Use: "rp",
Short: "",
Long: ``,
Version: version(),
SilenceUsage: true, // Makes it harder to find the actual error
SilenceErrors: true, // We log manually with slog
}
var rootCmd = &cobra.Command{ cmd.AddCommand(newRunCommand())
Use: "rp",
Short: "", return cmd
Long: ``,
Version: version(),
SilenceUsage: true, // Makes it harder to find the actual error
SilenceErrors: true, // We log manually with slog
} }
func version() string { func version() string {
@ -66,24 +68,13 @@ func Execute() {
// Make sure to stop listening on signals after receiving the first signal to hand control of the signal back // Make sure to stop listening on signals after receiving the first signal to hand control of the signal back
// to the runtime. The Go runtime implements a "force shutdown" if the signal is received again. // to the runtime. The Go runtime implements a "force shutdown" if the signal is received again.
<-ctx.Done() <-ctx.Done()
logger.InfoContext(ctx, "Received shutdown signal, stopping...") slog.InfoContext(ctx, "Received shutdown signal, stopping...")
stop() stop()
}() }()
err := rootCmd.ExecuteContext(ctx) err := NewRootCmd().ExecuteContext(ctx)
if err != nil { if err != nil {
logger.ErrorContext(ctx, err.Error()) slog.ErrorContext(ctx, err.Error())
os.Exit(1) os.Exit(1)
} }
} }
func init() {
logger = slog.New(
tint.NewHandler(os.Stderr, &tint.Options{
Level: slog.LevelDebug,
TimeFormat: time.RFC3339,
}),
)
slog.SetDefault(logger)
}

View file

@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
"log/slog"
"slices" "slices"
"strings" "strings"
@ -10,105 +11,130 @@ import (
rp "github.com/apricote/releaser-pleaser" rp "github.com/apricote/releaser-pleaser"
"github.com/apricote/releaser-pleaser/internal/commitparser/conventionalcommits" "github.com/apricote/releaser-pleaser/internal/commitparser/conventionalcommits"
"github.com/apricote/releaser-pleaser/internal/forge" "github.com/apricote/releaser-pleaser/internal/forge"
"github.com/apricote/releaser-pleaser/internal/forge/forgejo"
"github.com/apricote/releaser-pleaser/internal/forge/github" "github.com/apricote/releaser-pleaser/internal/forge/github"
"github.com/apricote/releaser-pleaser/internal/forge/gitlab" "github.com/apricote/releaser-pleaser/internal/forge/gitlab"
"github.com/apricote/releaser-pleaser/internal/log"
"github.com/apricote/releaser-pleaser/internal/updater" "github.com/apricote/releaser-pleaser/internal/updater"
"github.com/apricote/releaser-pleaser/internal/versioning" "github.com/apricote/releaser-pleaser/internal/versioning"
) )
var runCmd = &cobra.Command{ func newRunCommand() *cobra.Command {
Use: "run", var (
RunE: run, flagForge string
} flagBranch string
flagOwner string
flagRepo string
flagExtraFiles string
flagUpdaters []string
var ( flagAPIURL string
flagForge string flagAPIToken string
flagBranch string flagUsername string
flagOwner string
flagRepo string
flagExtraFiles string
flagUpdaters []string
)
func init() {
rootCmd.AddCommand(runCmd)
runCmd.PersistentFlags().StringVar(&flagForge, "forge", "", "")
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
runCmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
}
func run(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
var err error
logger.DebugContext(ctx, "run called",
"forge", flagForge,
"branch", flagBranch,
"owner", flagOwner,
"repo", flagRepo,
) )
var f forge.Forge var cmd = &cobra.Command{
Use: "run",
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
logger := log.GetLogger(cmd.ErrOrStderr())
forgeOptions := forge.Options{ var err error
Repository: flagRepo,
BaseBranch: flagBranch, logger.DebugContext(ctx, "run called",
"forge", flagForge,
"branch", flagBranch,
"owner", flagOwner,
"repo", flagRepo,
)
var f forge.Forge
forgeOptions := forge.Options{
Repository: flagRepo,
BaseBranch: flagBranch,
}
switch flagForge {
case "gitlab":
logger.DebugContext(ctx, "using forge GitLab")
f, err = gitlab.New(logger, &gitlab.Options{
Options: forgeOptions,
Path: fmt.Sprintf("%s/%s", flagOwner, flagRepo),
})
if err != nil {
slog.ErrorContext(ctx, "failed to create client", "err", err)
return fmt.Errorf("failed to create gitlab client: %w", err)
}
case "github":
logger.DebugContext(ctx, "using forge GitHub")
f = github.New(logger, &github.Options{
Options: forgeOptions,
Owner: flagOwner,
Repo: flagRepo,
})
case "forgejo":
logger.DebugContext(ctx, "using forge Forgejo")
f, err = forgejo.New(logger, &forgejo.Options{
Options: forgeOptions,
Owner: flagOwner,
Repo: flagRepo,
APIURL: flagAPIURL,
APIToken: flagAPIToken,
Username: flagUsername,
})
if err != nil {
logger.ErrorContext(ctx, "failed to create client", "err", err)
return fmt.Errorf("failed to create forgejo client: %w", err)
}
default:
return fmt.Errorf("unknown --forge: %s", flagForge)
}
extraFiles := parseExtraFiles(flagExtraFiles)
updaterNames := parseUpdaters(flagUpdaters)
updaters := []updater.Updater{}
for _, name := range updaterNames {
switch name {
case "generic":
updaters = append(updaters, updater.Generic(extraFiles))
case "changelog":
updaters = append(updaters, updater.Changelog())
case "packagejson":
updaters = append(updaters, updater.PackageJson())
default:
return fmt.Errorf("unknown updater: %s", name)
}
}
releaserPleaser := rp.New(
f,
logger,
flagBranch,
conventionalcommits.NewParser(logger),
versioning.SemVer,
extraFiles,
updaters,
)
return releaserPleaser.Run(ctx)
},
} }
switch flagForge { cmd.PersistentFlags().StringVar(&flagForge, "forge", "", "")
case "gitlab": cmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
logger.DebugContext(ctx, "using forge GitLab") cmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
f, err = gitlab.New(logger, &gitlab.Options{ cmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
Options: forgeOptions, cmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
Path: fmt.Sprintf("%s/%s", flagOwner, flagRepo), cmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
})
if err != nil {
logger.ErrorContext(ctx, "failed to create client", "err", err)
return fmt.Errorf("failed to create gitlab client: %w", err)
}
case "github":
logger.DebugContext(ctx, "using forge GitHub")
f = github.New(logger, &github.Options{
Options: forgeOptions,
Owner: flagOwner,
Repo: flagRepo,
})
default:
return fmt.Errorf("unknown --forge: %s", flagForge)
}
extraFiles := parseExtraFiles(flagExtraFiles) cmd.PersistentFlags().StringVar(&flagAPIURL, "api-url", "", "")
cmd.PersistentFlags().StringVar(&flagAPIToken, "api-token", "", "")
cmd.PersistentFlags().StringVar(&flagUsername, "username", "", "")
updaterNames := parseUpdaters(flagUpdaters) return cmd
updaters := []updater.Updater{}
for _, name := range updaterNames {
switch name {
case "generic":
updaters = append(updaters, updater.Generic(extraFiles))
case "changelog":
updaters = append(updaters, updater.Changelog())
case "packagejson":
updaters = append(updaters, updater.PackageJson())
default:
return fmt.Errorf("unknown updater: %s", name)
}
}
releaserPleaser := rp.New(
f,
logger,
flagBranch,
conventionalcommits.NewParser(logger),
versioning.SemVer,
extraFiles,
updaters,
)
return releaserPleaser.Run(ctx)
} }
func parseExtraFiles(input string) []string { func parseExtraFiles(input string) []string {

View file

@ -2,6 +2,7 @@ package main
import ( import (
"github.com/apricote/releaser-pleaser/cmd/rp/cmd" "github.com/apricote/releaser-pleaser/cmd/rp/cmd"
_ "github.com/apricote/releaser-pleaser/internal/log"
) )
func main() { func main() {

2
codecov.yaml Normal file
View file

@ -0,0 +1,2 @@
ignore:
- "test"

View file

@ -43,17 +43,17 @@ The release pull request description has text fields where maintainers can add t
When you edit the description, make sure to put your desired content into the code blocks named `rp-prefix` and `rp-suffix`. Only the content of these blocks is considered. When you edit the description, make sure to put your desired content into the code blocks named `rp-prefix` and `rp-suffix`. Only the content of these blocks is considered.
> ```rp-prefix > ~~~~rp-prefix
> ### Prefix > ### Prefix
> >
> This will be shown as the Prefix. > This will be shown as the Prefix.
> ``` > ~~~~
> >
> ```rp-suffix > ~~~~rp-suffix
> ### Suffix > ### Suffix
> >
> This will be shown as the Suffix. > This will be shown as the Suffix.
> ``` > ~~~~
To match the style of the auto-generated release notes, you should start any headings at level 3 (`### Title`). To match the style of the auto-generated release notes, you should start any headings at level 3 (`### Title`).

View file

@ -30,17 +30,17 @@ Any text in code blocks with these languages is being added to the start or end
**Examples**: **Examples**:
```rp-prefix ~~~~rp-prefix
#### Awesome new feature! #### Awesome new feature!
This text is at the start of the release notes. This text is at the start of the release notes.
``` ~~~~
```rp-suffix ~~~~rp-suffix
#### Version Compatibility #### Version Compatibility
And this at the end. And this at the end.
``` ~~~~
### Status ### Status

34
go.mod
View file

@ -1,36 +1,41 @@
module github.com/apricote/releaser-pleaser module github.com/apricote/releaser-pleaser
go 1.23.2 go 1.24.0
toolchain go1.24.6 toolchain go1.25.5
require ( require (
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0
github.com/blang/semver/v4 v4.0.0 github.com/blang/semver/v4 v4.0.0
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.7.0
github.com/go-git/go-git/v5 v5.16.2 github.com/go-git/go-git/v5 v5.16.4
github.com/google/go-github/v72 v72.0.0 github.com/google/go-github/v74 v74.0.0
github.com/leodido/go-conventionalcommits v0.12.0 github.com/leodido/go-conventionalcommits v0.12.0
github.com/lmittmann/tint v1.1.2 github.com/lmittmann/tint v1.1.2
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/teekennedy/goldmark-markdown v0.5.1 github.com/teekennedy/goldmark-markdown v0.5.1
github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark v1.7.16
gitlab.com/gitlab-org/api/client-go v0.142.0 gitlab.com/gitlab-org/api/client-go v0.161.1
) )
require ( require (
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/42wim/httpsig v1.2.3 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
@ -39,13 +44,16 @@ require (
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.9 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
replace codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 => codeberg.org/apricote/forgejo-sdk/forgejo/v2 v2.1.2-0.20250615152743-47d3f0434561

72
go.sum
View file

@ -1,5 +1,9 @@
codeberg.org/apricote/forgejo-sdk/forgejo/v2 v2.1.2-0.20250615152743-47d3f0434561 h1:ZFGmrGQ7cd2mbSLrfjrj3COwPKFfKM6sDO/IsrGDW7w=
codeberg.org/apricote/forgejo-sdk/forgejo/v2 v2.1.2-0.20250615152743-47d3f0434561/go.mod h1:2i9GsyawlJtVMO5pTS/Om5uo2O3JN/eCjGWy5v15NGg=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@ -19,6 +23,8 @@ github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL
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=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@ -27,21 +33,23 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM= github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg= github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@ -50,6 +58,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -91,50 +101,60 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/teekennedy/goldmark-markdown v0.5.1 h1:2lIlJ3AcIwaD1wFl4dflJSJFMhRTKEsEj+asVsu6M/0= github.com/teekennedy/goldmark-markdown v0.5.1 h1:2lIlJ3AcIwaD1wFl4dflJSJFMhRTKEsEj+asVsu6M/0=
github.com/teekennedy/goldmark-markdown v0.5.1/go.mod h1:so260mNSPELuRyynZY18719dRYlD+OSnAovqsyrOMOM= github.com/teekennedy/goldmark-markdown v0.5.1/go.mod h1:so260mNSPELuRyynZY18719dRYlD+OSnAovqsyrOMOM=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
gitlab.com/gitlab-org/api/client-go v0.142.0 h1:cR8+RhDc7ooH0SiGNhgm3Nf5ZpW5D1R3DLshfAXJZmQ= gitlab.com/gitlab-org/api/client-go v0.161.1 h1:XX0EtVGL6cGEdNy9xnJ96CSciIzjCwAVsayItHY1YyU=
gitlab.com/gitlab-org/api/client-go v0.142.0/go.mod h1:3YuWlZCirs2TTcaAzM6qNwVHB7WvV67ATb0GGpBCdlQ= gitlab.com/gitlab-org/api/client-go v0.161.1/go.mod h1:YqKcnxyV9OPAL5U99mpwBVEgBPz1PK/3qwqq/3h6bao=
go.abhg.dev/goldmark/toc v0.11.0 h1:IRixVy3/yVPKvFBc37EeBPi8XLTXrtH6BYaonSjkF8o= go.abhg.dev/goldmark/toc v0.11.0 h1:IRixVy3/yVPKvFBc37EeBPi8XLTXrtH6BYaonSjkF8o=
go.abhg.dev/goldmark/toc v0.11.0/go.mod h1:XMFIoI1Sm6dwF9vKzVDOYE/g1o5BmKXghLG8q/wJNww= go.abhg.dev/goldmark/toc v0.11.0/go.mod h1:XMFIoI1Sm6dwF9vKzVDOYE/g1o5BmKXghLG8q/wJNww=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -3,9 +3,9 @@ package changelog
import ( import (
"bytes" "bytes"
_ "embed" _ "embed"
"html/template"
"log" "log"
"log/slog" "log/slog"
"text/template"
"github.com/apricote/releaser-pleaser/internal/commitparser" "github.com/apricote/releaser-pleaser/internal/commitparser"
"github.com/apricote/releaser-pleaser/internal/markdown" "github.com/apricote/releaser-pleaser/internal/markdown"

View file

@ -8,6 +8,7 @@ import (
"github.com/apricote/releaser-pleaser/internal/commitparser" "github.com/apricote/releaser-pleaser/internal/commitparser"
"github.com/apricote/releaser-pleaser/internal/git" "github.com/apricote/releaser-pleaser/internal/git"
"github.com/apricote/releaser-pleaser/internal/testdata"
) )
func ptr[T any](input T) *T { func ptr[T any](input T) *T {
@ -143,16 +144,9 @@ func Test_NewChangelogEntry(t *testing.T) {
}, },
version: "1.0.0", version: "1.0.0",
link: "https://example.com/1.0.0", link: "https://example.com/1.0.0",
prefix: "### Breaking Changes", prefix: testdata.MustReadFileString(t, "prefix.txt"),
}, },
want: `## [1.0.0](https://example.com/1.0.0) want: testdata.MustReadFileString(t, "changelog-entry-prefix.txt"),
### Breaking Changes
### Bug Fixes
- Foobar!
`,
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
{ {
@ -167,18 +161,9 @@ func Test_NewChangelogEntry(t *testing.T) {
}, },
version: "1.0.0", version: "1.0.0",
link: "https://example.com/1.0.0", link: "https://example.com/1.0.0",
suffix: "### Compatibility\n\nThis version is compatible with flux-compensator v2.2 - v2.9.", suffix: testdata.MustReadFileString(t, "suffix.txt"),
}, },
want: `## [1.0.0](https://example.com/1.0.0) want: testdata.MustReadFileString(t, "changelog-entry-suffix.txt"),
### Bug Fixes
- Foobar!
### Compatibility
This version is compatible with flux-compensator v2.2 - v2.9.
`,
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
} }

View file

@ -0,0 +1,529 @@
package forgejo
import (
"context"
"fmt"
"log/slog"
"slices"
"strings"
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
"github.com/blang/semver/v4"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/apricote/releaser-pleaser/internal/forge"
"github.com/apricote/releaser-pleaser/internal/git"
"github.com/apricote/releaser-pleaser/internal/pointer"
"github.com/apricote/releaser-pleaser/internal/releasepr"
)
const ()
var _ forge.Forge = &Forgejo{}
type Forgejo struct {
options *Options
client *forgejo.Client
log *slog.Logger
}
func (f *Forgejo) RepoURL() string {
return fmt.Sprintf("%s/%s/%s", f.options.APIURL, f.options.Owner, f.options.Repo)
}
func (f *Forgejo) CloneURL() string {
return fmt.Sprintf("%s.git", f.RepoURL())
}
func (f *Forgejo) ReleaseURL(version string) string {
return fmt.Sprintf("%s/releases/tag/%s", f.RepoURL(), version)
}
func (f *Forgejo) PullRequestURL(id int) string {
return fmt.Sprintf("%s/pulls/%d", f.RepoURL(), id)
}
func (f *Forgejo) GitAuth() transport.AuthMethod {
return &http.BasicAuth{
Username: f.options.Username,
Password: f.options.APIToken,
}
}
func (f *Forgejo) CommitAuthor(ctx context.Context) (git.Author, error) {
f.log.DebugContext(ctx, "getting commit author from current token user")
user, _, err := f.client.GetMyUserInfo()
if err != nil {
return git.Author{}, err
}
// TODO: Same for other forges?
name := user.FullName
if name == "" {
name = user.UserName
}
return git.Author{
Name: name,
Email: user.Email,
}, nil
}
func (f *Forgejo) LatestTags(ctx context.Context) (git.Releases, error) {
f.log.DebugContext(ctx, "listing all tags in forgejo repository")
tags, err := all(func(listOptions forgejo.ListOptions) ([]*forgejo.Tag, *forgejo.Response, error) {
return f.client.ListRepoTags(f.options.Owner, f.options.Repo,
forgejo.ListRepoTagsOptions{ListOptions: listOptions},
)
})
if err != nil {
return git.Releases{}, err
}
var releases git.Releases
for _, fTag := range tags {
tag := &git.Tag{
Hash: fTag.Commit.SHA,
Name: fTag.Name,
}
version, err := semver.Parse(strings.TrimPrefix(tag.Name, "v"))
if err != nil {
f.log.WarnContext(
ctx, "unable to parse tag as semver, skipping",
"tag.name", tag.Name,
"tag.hash", tag.Hash,
"error", err,
)
continue
}
if releases.Latest == nil {
releases.Latest = tag
}
if len(version.Pre) == 0 {
// Stable version tag
// We return once we have found the latest stable tag, not needed to look at every single tag.
releases.Stable = tag
break
}
}
return releases, nil
}
func (f *Forgejo) CommitsSince(ctx context.Context, tag *git.Tag) ([]git.Commit, error) {
var repositoryCommits []*forgejo.Commit
var err error
if tag != nil {
repositoryCommits, err = f.commitsSinceTag(ctx, tag)
} else {
repositoryCommits, err = f.commitsSinceInit(ctx)
}
if err != nil {
return nil, err
}
var commits = make([]git.Commit, 0, len(repositoryCommits))
for _, fCommit := range repositoryCommits {
commit := git.Commit{
Hash: fCommit.SHA,
Message: fCommit.RepoCommit.Message,
}
commit.PullRequest, err = f.prForCommit(ctx, commit)
if err != nil {
return nil, fmt.Errorf("failed to check for commit pull request: %w", err)
}
commits = append(commits, commit)
}
return commits, nil
}
func (f *Forgejo) commitsSinceTag(_ context.Context, tag *git.Tag) ([]*forgejo.Commit, error) {
head := f.options.BaseBranch
log := f.log.With("base", tag.Hash, "head", head)
log.Debug("comparing commits")
compare, _, err := f.client.CompareCommits(
f.options.Owner, f.options.Repo,
tag.Hash, head)
if err != nil {
return nil, err
}
return compare.Commits, nil
}
func (f *Forgejo) commitsSinceInit(_ context.Context) ([]*forgejo.Commit, error) {
head := f.options.BaseBranch
log := f.log.With("head", head)
log.Debug("listing all commits")
repositoryCommits, err := all(
func(listOptions forgejo.ListOptions) ([]*forgejo.Commit, *forgejo.Response, error) {
return f.client.ListRepoCommits(
f.options.Owner, f.options.Repo,
forgejo.ListCommitOptions{
ListOptions: listOptions,
SHA: f.options.BaseBranch,
})
})
if err != nil {
return nil, err
}
return repositoryCommits, nil
}
func (f *Forgejo) prForCommit(_ context.Context, commit git.Commit) (*git.PullRequest, error) {
// We naively look up the associated PR for each commit through the "List pull requests associated with a commit"
// endpoint. This requires len(commits) requests.
// Using the "List pull requests" endpoint might be faster, as it allows us to fetch 100 arbitrary PRs per request,
// but worst case we need to look up all PRs made in the repository ever.
f.log.Debug("fetching pull requests associated with commit", "commit.hash", commit.Hash)
pullRequest, _, err := f.client.GetCommitPullRequest(
f.options.Owner, f.options.Repo,
commit.Hash,
)
if err != nil {
if strings.HasPrefix(err.Error(), "pull request does not exist") {
return nil, nil
}
return nil, err
}
return forgejoPRToPullRequest(pullRequest), nil
}
func (f *Forgejo) EnsureLabelsExist(_ context.Context, labels []releasepr.Label) error {
f.log.Debug("fetching labels on repo")
fLabels, err := all(func(listOptions forgejo.ListOptions) ([]*forgejo.Label, *forgejo.Response, error) {
return f.client.ListRepoLabels(
f.options.Owner, f.options.Repo,
forgejo.ListLabelsOptions{ListOptions: listOptions})
})
if err != nil {
return err
}
for _, label := range labels {
if !slices.ContainsFunc(fLabels, func(fLabel *forgejo.Label) bool {
return fLabel.Name == label.Name
}) {
f.log.Info("creating label in repository", "label.name", label.Name)
_, _, err = f.client.CreateLabel(
f.options.Owner, f.options.Repo,
forgejo.CreateLabelOption{
Name: label.Name,
Color: label.Color,
Description: label.Description,
},
)
if err != nil {
return err
}
}
}
return nil
}
func (f *Forgejo) PullRequestForBranch(_ context.Context, branch string) (*releasepr.ReleasePullRequest, error) {
prs, err := all(
func(listOptions forgejo.ListOptions) ([]*forgejo.PullRequest, *forgejo.Response, error) {
return f.client.ListRepoPullRequests(
f.options.Owner, f.options.Repo,
forgejo.ListPullRequestsOptions{
ListOptions: listOptions,
State: forgejo.StateOpen,
},
)
},
)
if err != nil {
return nil, err
}
for _, pr := range prs {
if pr.Base.Ref == f.options.BaseBranch && pr.Head.Ref == branch {
return forgejoPRToReleasePullRequest(pr), nil
}
}
return nil, nil
}
func (f *Forgejo) CreatePullRequest(ctx context.Context, pr *releasepr.ReleasePullRequest) error {
fPR, _, err := f.client.CreatePullRequest(
f.options.Owner, f.options.Repo,
forgejo.CreatePullRequestOption{
Title: pr.Title,
Head: pr.Head,
Base: f.options.BaseBranch,
Body: pr.Description,
},
)
if err != nil {
return err
}
// TODO: String ID?
pr.ID = int(fPR.ID)
err = f.SetPullRequestLabels(ctx, pr, []releasepr.Label{}, pr.Labels)
if err != nil {
return err
}
return nil
}
func (f *Forgejo) UpdatePullRequest(_ context.Context, pr *releasepr.ReleasePullRequest) error {
_, _, err := f.client.EditPullRequest(
f.options.Owner, f.options.Repo,
int64(pr.ID), forgejo.EditPullRequestOption{
Title: pr.Title,
Body: pr.Description,
},
)
if err != nil {
return err
}
return nil
}
func (f *Forgejo) SetPullRequestLabels(_ context.Context, pr *releasepr.ReleasePullRequest, remove, add []releasepr.Label) error {
allLabels, err := all(
func(listOptions forgejo.ListOptions) ([]*forgejo.Label, *forgejo.Response, error) {
return f.client.ListRepoLabels(f.options.Owner, f.options.Repo, forgejo.ListLabelsOptions{ListOptions: listOptions})
},
)
if err != nil {
return err
}
findLabel := func(labelName string) *forgejo.Label {
for _, fLabel := range allLabels {
if fLabel.Name == labelName {
return fLabel
}
}
return nil
}
for _, label := range remove {
fLabel := findLabel(label.Name)
if fLabel == nil {
return fmt.Errorf("unable to remove label %q, not found in API", label.Name)
}
_, err = f.client.DeleteIssueLabel(
f.options.Owner, f.options.Repo,
int64(pr.ID), fLabel.ID,
)
if err != nil {
return err
}
}
addIDs := make([]int64, 0, len(add))
for _, label := range add {
fLabel := findLabel(label.Name)
if fLabel == nil {
return fmt.Errorf("unable to add label %q, not found in API", label.Name)
}
addIDs = append(addIDs, fLabel.ID)
}
_, _, err = f.client.AddIssueLabels(
f.options.Owner, f.options.Repo,
int64(pr.ID), forgejo.IssueLabelsOption{Labels: addIDs},
)
if err != nil {
return err
}
return nil
}
func (f *Forgejo) ClosePullRequest(_ context.Context, pr *releasepr.ReleasePullRequest) error {
_, _, err := f.client.EditPullRequest(
f.options.Owner, f.options.Repo,
int64(pr.ID), forgejo.EditPullRequestOption{
State: pointer.Pointer(forgejo.StateClosed),
},
)
if err != nil {
return err
}
return nil
}
func (f *Forgejo) PendingReleases(_ context.Context, pendingLabel releasepr.Label) ([]*releasepr.ReleasePullRequest, error) {
fPRs, err := all(func(listOptions forgejo.ListOptions) ([]*forgejo.PullRequest, *forgejo.Response, error) {
return f.client.ListRepoPullRequests(
f.options.Owner, f.options.Repo,
forgejo.ListPullRequestsOptions{
// Filtering by Label ID is possible in the API, but not implemented in the Go SDK.
State: forgejo.StateClosed,
ListOptions: listOptions,
})
})
if err != nil {
// "The target couldn't be found." means that the repo does not have pull requests activated.
return nil, err
}
prs := make([]*releasepr.ReleasePullRequest, 0, len(fPRs))
for _, pr := range fPRs {
pending := slices.ContainsFunc(pr.Labels, func(l *forgejo.Label) bool {
return l.Name == pendingLabel.Name
})
if !pending {
continue
}
// pr.Merged is always nil :(
if !pr.HasMerged {
// Closed and not merged
continue
}
prs = append(prs, forgejoPRToReleasePullRequest(pr))
}
return prs, nil
}
func (f *Forgejo) CreateRelease(_ context.Context, commit git.Commit, title, changelog string, preRelease, latest bool) error {
// latest can not be set through the API
_, _, err := f.client.CreateRelease(
f.options.Owner, f.options.Repo,
forgejo.CreateReleaseOption{
TagName: title,
Target: commit.Hash,
Title: title,
Note: changelog,
IsPrerelease: preRelease,
},
)
if err != nil {
return err
}
return nil
}
func all[T any](f func(listOptions forgejo.ListOptions) ([]T, *forgejo.Response, error)) ([]T, error) {
results := make([]T, 0)
page := 1
for {
pageResults, resp, err := f(forgejo.ListOptions{Page: page})
if err != nil {
return nil, err
}
results = append(results, pageResults...)
if page == resp.LastPage || resp.LastPage == 0 {
return results, nil
}
page = resp.NextPage
}
}
func forgejoPRToPullRequest(pr *forgejo.PullRequest) *git.PullRequest {
return &git.PullRequest{
ID: int(pr.ID),
Title: pr.Title,
Description: pr.Body,
}
}
func forgejoPRToReleasePullRequest(pr *forgejo.PullRequest) *releasepr.ReleasePullRequest {
labels := make([]releasepr.Label, 0, len(pr.Labels))
for _, label := range pr.Labels {
labelName := label.Name
if i := slices.IndexFunc(releasepr.KnownLabels, func(label releasepr.Label) bool {
return label.Name == labelName
}); i >= 0 {
labels = append(labels, releasepr.KnownLabels[i])
}
}
var releaseCommit *git.Commit
if pr.MergedCommitID != nil {
releaseCommit = &git.Commit{Hash: *pr.MergedCommitID}
}
return &releasepr.ReleasePullRequest{
PullRequest: *forgejoPRToPullRequest(pr),
Labels: labels,
Head: pr.Head.Ref,
ReleaseCommit: releaseCommit,
}
}
func (g *Options) autodiscover() {
// TODO
}
func (g *Options) ClientOptions() []forgejo.ClientOption {
options := []forgejo.ClientOption{}
if g.APIToken != "" {
options = append(options, forgejo.SetToken(g.APIToken))
}
return options
}
type Options struct {
forge.Options
Owner string
Repo string
APIURL string
Username string
APIToken string
}
func New(log *slog.Logger, options *Options) (*Forgejo, error) {
options.autodiscover()
client, err := forgejo.NewClient(options.APIURL, options.ClientOptions()...)
if err != nil {
return nil, err
}
client.SetUserAgent("releaser-pleaser")
f := &Forgejo{
options: options,
client: client,
log: log.With("forge", "forgejo"),
}
return f, nil
}

View file

@ -12,7 +12,7 @@ import (
"github.com/blang/semver/v4" "github.com/blang/semver/v4"
"github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/google/go-github/v72/github" "github.com/google/go-github/v74/github"
"github.com/apricote/releaser-pleaser/internal/forge" "github.com/apricote/releaser-pleaser/internal/forge"
"github.com/apricote/releaser-pleaser/internal/git" "github.com/apricote/releaser-pleaser/internal/git"

23
internal/log/log.go Normal file
View file

@ -0,0 +1,23 @@
package log
import (
"io"
"log/slog"
"os"
"time"
"github.com/lmittmann/tint"
)
func GetLogger(w io.Writer) *slog.Logger {
return slog.New(
tint.NewHandler(w, &tint.Options{
Level: slog.LevelDebug,
TimeFormat: time.RFC3339,
}),
)
}
func init() {
slog.SetDefault(GetLogger(os.Stderr))
}

View file

@ -15,18 +15,18 @@ If you want to modify the proposed release, add you overrides here. You can lear
This will be added to the start of the release notes. This will be added to the start of the release notes.
```rp-prefix ~~~~rp-prefix
{{- if .Overrides.Prefix }} {{- if .Overrides.Prefix }}
{{ .Overrides.Prefix }}{{ end }} {{ .Overrides.Prefix }}{{ end }}
``` ~~~~
### Suffix / End ### Suffix / End
This will be added to the end of the release notes. This will be added to the end of the release notes.
```rp-suffix ~~~~rp-suffix
{{- if .Overrides.Suffix }} {{- if .Overrides.Suffix }}
{{ .Overrides.Suffix }}{{ end }} {{ .Overrides.Suffix }}{{ end }}
``` ~~~~
</details> </details>

View file

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/apricote/releaser-pleaser/internal/git" "github.com/apricote/releaser-pleaser/internal/git"
"github.com/apricote/releaser-pleaser/internal/testdata"
"github.com/apricote/releaser-pleaser/internal/versioning" "github.com/apricote/releaser-pleaser/internal/versioning"
) )
@ -37,20 +38,24 @@ func TestReleasePullRequest_GetOverrides(t *testing.T) {
name: "prefix in description", name: "prefix in description",
pr: ReleasePullRequest{ pr: ReleasePullRequest{
PullRequest: git.PullRequest{ PullRequest: git.PullRequest{
Description: "```rp-prefix\n## Foo\n\n- Cool thing\n```", Description: testdata.MustReadFileString(t, "description-prefix.txt"),
}, },
}, },
want: ReleaseOverrides{Prefix: "## Foo\n\n- Cool thing"}, want: ReleaseOverrides{
Prefix: testdata.MustReadFileString(t, "prefix.txt"),
},
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
{ {
name: "suffix in description", name: "suffix in description",
pr: ReleasePullRequest{ pr: ReleasePullRequest{
PullRequest: git.PullRequest{ PullRequest: git.PullRequest{
Description: "```rp-suffix\n## Compatibility\n\nNo compatibility guarantees.\n```", Description: testdata.MustReadFileString(t, "description-suffix.txt"),
}, },
}, },
want: ReleaseOverrides{Suffix: "## Compatibility\n\nNo compatibility guarantees."}, want: ReleaseOverrides{
Suffix: testdata.MustReadFileString(t, "suffix.txt"),
},
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
} }
@ -80,30 +85,10 @@ func TestReleasePullRequest_ChangelogText(t *testing.T) {
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
{ {
name: "with section", name: "with section",
description: `# Foobar description: testdata.MustReadFileString(t, "changelog.txt"),
want: testdata.MustReadFileString(t, "changelog-content.txt"),
<!-- section-start changelog --> wantErr: assert.NoError,
This is the changelog
## Awesome
### New
#### Changes
<!-- section-end changelog -->
Suffix Things
`,
want: `This is the changelog
## Awesome
### New
#### Changes
`,
wantErr: assert.NoError,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -178,75 +163,17 @@ func TestReleasePullRequest_SetDescription(t *testing.T) {
name: "no overrides", name: "no overrides",
changelogEntry: `## v1.0.0`, changelogEntry: `## v1.0.0`,
overrides: ReleaseOverrides{}, overrides: ReleaseOverrides{},
want: `<!-- section-start changelog --> want: testdata.MustReadFileString(t, "description-no-overrides.txt"),
## v1.0.0 wantErr: assert.NoError,
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
` + "```" + `rp-prefix
` + "```" + `
### Suffix / End
This will be added to the end of the release notes.
` + "```" + `rp-suffix
` + "```" + `
</details>
`,
wantErr: assert.NoError,
}, },
{ {
name: "existing overrides", name: "existing overrides",
changelogEntry: `## v1.0.0`, changelogEntry: `## v1.0.0`,
overrides: ReleaseOverrides{ overrides: ReleaseOverrides{
Prefix: "This release is awesome!", Prefix: testdata.MustReadFileString(t, "prefix.txt"),
Suffix: "Fooo", Suffix: testdata.MustReadFileString(t, "suffix.txt"),
}, },
want: `<!-- section-start changelog --> want: testdata.MustReadFileString(t, "description-overrides.txt"),
## v1.0.0
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
` + "```" + `rp-prefix
This release is awesome!
` + "```" + `
### Suffix / End
This will be added to the end of the release notes.
` + "```" + `rp-suffix
Fooo
` + "```" + `
</details>
`,
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
} }

View file

@ -0,0 +1,7 @@
This is the changelog
## Awesome
### New
#### Changes

View file

@ -0,0 +1,19 @@
## [1.0.0](https://example.com/1.0.0)
## Foo
- Cool thing
```go
// Some code example
func IsPositive(number int) error {
if number < 0 {
return fmt.Errorf("number %d is negative", number)
}
return nil
}
```
### Bug Fixes
- Foobar!

View file

@ -0,0 +1,9 @@
## [1.0.0](https://example.com/1.0.0)
### Bug Fixes
- Foobar!
## Compatibility
This version is compatible with flux-compensator v2.2 - v2.9.

13
internal/testdata/changelog.txt vendored Normal file
View file

@ -0,0 +1,13 @@
# Foobar
<!-- section-start changelog -->
This is the changelog
## Awesome
### New
#### Changes
<!-- section-end changelog -->
Suffix Things

View file

@ -0,0 +1,28 @@
<!-- section-start changelog -->
## v1.0.0
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
~~~~rp-prefix
~~~~
### Suffix / End
This will be added to the end of the release notes.
~~~~rp-suffix
~~~~
</details>

View file

@ -0,0 +1,44 @@
<!-- section-start changelog -->
## v1.0.0
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
~~~~rp-prefix
## Foo
- Cool thing
```go
// Some code example
func IsPositive(number int) error {
if number < 0 {
return fmt.Errorf("number %d is negative", number)
}
return nil
}
```
~~~~
### Suffix / End
This will be added to the end of the release notes.
~~~~rp-suffix
## Compatibility
This version is compatible with flux-compensator v2.2 - v2.9.
~~~~
</details>

View file

@ -0,0 +1,41 @@
<!-- section-start changelog -->
## v1.0.0
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
~~~~rp-prefix
## Foo
- Cool thing
```go
// Some code example
func IsPositive(number int) error {
if number < 0 {
return fmt.Errorf("number %d is negative", number)
}
return nil
}
```
~~~~
### Suffix / End
This will be added to the end of the release notes.
~~~~rp-suffix
~~~~
</details>

View file

@ -0,0 +1,31 @@
<!-- section-start changelog -->
## v1.0.0
<!-- section-end changelog -->
---
<details>
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
## Release Notes
### Prefix / Start
This will be added to the start of the release notes.
~~~~rp-prefix
~~~~
### Suffix / End
This will be added to the end of the release notes.
~~~~rp-suffix
## Compatibility
This version is compatible with flux-compensator v2.2 - v2.9.
~~~~
</details>

13
internal/testdata/prefix.txt vendored Normal file
View file

@ -0,0 +1,13 @@
## Foo
- Cool thing
```go
// Some code example
func IsPositive(number int) error {
if number < 0 {
return fmt.Errorf("number %d is negative", number)
}
return nil
}
```

3
internal/testdata/suffix.txt vendored Normal file
View file

@ -0,0 +1,3 @@
## Compatibility
This version is compatible with flux-compensator v2.2 - v2.9.

19
internal/testdata/testdata.go vendored Normal file
View file

@ -0,0 +1,19 @@
package testdata
import (
"embed"
"testing"
)
//go:embed *.txt
var testdata embed.FS
func MustReadFileString(t *testing.T, name string) string {
t.Helper()
content, err := testdata.ReadFile(name)
if err != nil {
t.Fatal(err)
}
return string(content)
}

25
mise.toml Normal file
View file

@ -0,0 +1,25 @@
[tools]
go = "1.25.5"
golangci-lint = "2.8.0"
goreleaser = "v2.9.0"
mdbook = "v0.5.2" # renovate: datasource=github-releases depName=rust-lang/mdbook
ko = "v0.18.1" # renovate: datasource=github-releases depName=ko-build/ko
[settings]
# Experimental features are needed for the Go backend
experimental = true
[tasks.lint]
run = "golangci-lint run"
[tasks.test]
run = "go test -v -race ./..."
[tasks.test-e2e]
run = "go test -tags e2e_forgejo -v -race ./test/e2e/forgejo"
[tasks.e2e-forgejo-start]
run = "docker compose --project-directory ./test/e2e/forgejo up -d --wait"
[tasks.e2e-forgejo-stop]
run = "docker compose --project-directory ./test/e2e/forgejo down"

View file

@ -44,7 +44,7 @@ releaser-pleaser:
resource_group: releaser-pleaser resource_group: releaser-pleaser
image: image:
name: ghcr.io/apricote/releaser-pleaser:v0.7.0 # x-releaser-pleaser-version name: ghcr.io/apricote/releaser-pleaser:v0.7.1 # x-releaser-pleaser-version
entrypoint: [ "" ] entrypoint: [ "" ]
variables: variables:
GITLAB_TOKEN: $[[ inputs.token ]] GITLAB_TOKEN: $[[ inputs.token ]]

19
test/e2e/forge.go Normal file
View file

@ -0,0 +1,19 @@
package e2e
import (
"context"
"testing"
)
type TestForge interface {
Init(ctx context.Context, runID string) error
CreateRepo(t *testing.T, opts CreateRepoOpts) (*Repository, error)
RunArguments() []string
}
type CreateRepoOpts struct {
Name string
Description string
DefaultBranch string
}

23
test/e2e/forgejo/app.ini Normal file
View file

@ -0,0 +1,23 @@
WORK_PATH = /data/gitea
[database]
DB_TYPE = sqlite3
PATH = /data/gitea/forgejo.db
[security]
INSTALL_LOCK = true
SECRET_KEY = releaser-pleaser
INTERNAL_TOKEN = releaser-pleaser
[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = true
[server]
DOMAIN = localhost
HTTP_PORT = 3000
ROOT_URL = http://localhost:3000/
[oauth2]
JWT_SECRET = rTD-FL2n_aBB6v4AOcr5lBvwgZ6PSr3HGZAuNH6nMu8

View file

@ -0,0 +1,16 @@
services:
forgejo:
image: codeberg.org/forgejo/forgejo:11
ports:
- '3000:3000'
- '222:22'
volumes:
- data:/data/gitea
- ./app.ini:/data/gitea/conf/app.ini:ro
healthcheck:
test: ["CMD", "curl", "localhost:3000/api/healthz"]
volumes:
data:

113
test/e2e/forgejo/forge.go Normal file
View file

@ -0,0 +1,113 @@
package forgejo
import (
"context"
"fmt"
"log/slog"
"os/exec"
"strings"
"testing"
"codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2"
"github.com/apricote/releaser-pleaser/test/e2e"
)
const (
TestAPIURL = "http://localhost:3000"
TestUserNameTemplate = "rp-%s"
TestUserPassword = "releaser-pleaser"
TestUserEmailTemplate = "releaser-pleaser-%s@example.com"
TestTokenName = "rp"
TestTokenScopes = "write:user,write:issue,write:repository"
)
type TestForge struct {
username string
token string
client *forgejo.Client
}
func (f *TestForge) Init(ctx context.Context, runID string) error {
if err := f.initUser(ctx, runID); err != nil {
return err
}
if err := f.initClient(ctx); err != nil {
return err
}
return nil
}
func (f *TestForge) initUser(ctx context.Context, runID string) error {
f.username = fmt.Sprintf(TestUserNameTemplate, runID)
//gosec:disable G204
if output, err := exec.CommandContext(ctx,
"docker", "compose", "exec", "--user=1000", "forgejo",
"forgejo", "admin", "user", "create",
"--username", f.username,
"--password", TestUserPassword,
"--email", fmt.Sprintf(TestUserEmailTemplate, runID),
"--must-change-password=false",
).CombinedOutput(); err != nil {
slog.Debug("create forgejo user output", "output", output)
return fmt.Errorf("failed to create forgejo user: %w", err)
}
//gosec:disable G204
token, err := exec.CommandContext(ctx,
"docker", "compose", "exec", "--user=1000", "forgejo",
"forgejo", "admin", "user", "generate-access-token",
"--username", f.username,
"--token-name", TestTokenName,
"--scopes", TestTokenScopes,
"--raw",
).Output()
if err != nil {
return fmt.Errorf("failed to create forgejo token: %w", err)
}
f.token = strings.TrimSpace(string(token))
return nil
}
func (f *TestForge) initClient(ctx context.Context) (err error) {
f.client, err = forgejo.NewClient(TestAPIURL,
forgejo.SetToken(f.token),
forgejo.SetUserAgent("releaser-pleaser-e2e-tests"),
forgejo.SetContext(ctx),
// forgejo.SetDebugMode(),
)
return err
}
func (f *TestForge) CreateRepo(t *testing.T, opts e2e.CreateRepoOpts) (*e2e.Repository, error) {
t.Helper()
repo, _, err := f.client.CreateRepo(forgejo.CreateRepoOption{
Name: opts.Name,
Description: opts.Description,
DefaultBranch: opts.DefaultBranch,
Readme: "Default",
AutoInit: true,
})
if err != nil {
return nil, err
}
return &e2e.Repository{
Name: repo.Name,
}, nil
}
func (f *TestForge) RunArguments() []string {
return []string{"--forge=forgejo",
fmt.Sprintf("--owner=%s", f.username),
fmt.Sprintf("--api-url=%s", TestAPIURL),
fmt.Sprintf("--api-token=%s", f.token),
fmt.Sprintf("--username=%s", f.username),
}
}

View file

@ -0,0 +1,39 @@
//go:build e2e_forgejo
package forgejo
import (
"context"
"log/slog"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/apricote/releaser-pleaser/test/e2e"
)
var (
f *e2e.Framework
)
func TestMain(m *testing.M) {
ctx := context.Background()
var err error
f, err = e2e.NewFramework(ctx, &TestForge{})
if err != nil {
slog.Error("failed to set up test framework", "err", err)
}
os.Exit(m.Run())
}
func TestCreateRepository(t *testing.T) {
_ = f.NewRepository(t, t.Name())
}
func TestRun(t *testing.T) {
repo := f.NewRepository(t, t.Name())
require.NoError(t, f.Run(t, repo, []string{}))
}

96
test/e2e/framework.go Normal file
View file

@ -0,0 +1,96 @@
package e2e
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/apricote/releaser-pleaser/cmd/rp/cmd"
)
const (
TestDefaultBranch = "main"
)
func randomString() string {
randomBytes := make([]byte, 4)
if _, err := rand.Read(randomBytes); err != nil {
panic(err)
}
return hex.EncodeToString(randomBytes)
}
type Framework struct {
runID string
forge TestForge
}
func NewFramework(ctx context.Context, forge TestForge) (*Framework, error) {
f := &Framework{
runID: randomString(),
forge: forge,
}
err := forge.Init(ctx, f.runID)
if err != nil {
return nil, err
}
return f, nil
}
type Repository struct {
Name string
}
func (f *Framework) NewRepository(t *testing.T, name string) *Repository {
t.Helper()
r := &Repository{
Name: fmt.Sprintf("%s-%s-%s", name, f.runID, randomString()),
}
repo, err := f.forge.CreateRepo(t, CreateRepoOpts{
Name: r.Name,
Description: name,
DefaultBranch: TestDefaultBranch,
})
require.NoError(t, err)
require.NotNil(t, repo)
return r
}
func (f *Framework) Run(t *testing.T, r *Repository, extraFiles []string) error {
t.Helper()
ctx := t.Context()
rootCmd := cmd.NewRootCmd()
rootCmd.SetArgs(append([]string{
"run",
fmt.Sprintf("--repo=%s", r.Name),
fmt.Sprintf("--extra-files=%q", strings.Join(extraFiles, "\n")),
}, f.forge.RunArguments()...))
var stdout, stderr bytes.Buffer
rootCmd.SetOut(&stdout)
rootCmd.SetErr(&stderr)
err := rootCmd.ExecuteContext(ctx)
stdoutString := stdout.String()
stderrString := stderr.String()
t.Log(stdoutString)
t.Log(stderrString)
return err
}