mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-01-13 21:21:03 +00:00
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.
This commit is contained in:
parent
f077b647e7
commit
7f69971116
4 changed files with 579 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
rp "github.com/apricote/releaser-pleaser"
|
||||
"github.com/apricote/releaser-pleaser/internal/commitparser/conventionalcommits"
|
||||
"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/gitlab"
|
||||
"github.com/apricote/releaser-pleaser/internal/log"
|
||||
|
|
@ -26,6 +27,10 @@ func newRunCommand() *cobra.Command {
|
|||
flagRepo string
|
||||
flagExtraFiles string
|
||||
flagUpdaters []string
|
||||
|
||||
flagAPIURL string
|
||||
flagAPIToken string
|
||||
flagUsername string
|
||||
)
|
||||
|
||||
var cmd = &cobra.Command{
|
||||
|
|
@ -68,6 +73,21 @@ func newRunCommand() *cobra.Command {
|
|||
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)
|
||||
}
|
||||
|
|
@ -110,6 +130,10 @@ func newRunCommand() *cobra.Command {
|
|||
cmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
|
||||
cmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
|
||||
|
||||
cmd.PersistentFlags().StringVar(&flagAPIURL, "api-url", "", "")
|
||||
cmd.PersistentFlags().StringVar(&flagAPIToken, "api-token", "", "")
|
||||
cmd.PersistentFlags().StringVar(&flagUsername, "username", "", "")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
|||
7
go.mod
7
go.mod
|
|
@ -5,6 +5,7 @@ go 1.23.2
|
|||
toolchain go1.25.0
|
||||
|
||||
require (
|
||||
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.0.0-00010101000000-000000000000
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/go-git/go-billy/v5 v5.6.2
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
|
|
@ -20,17 +21,21 @@ require (
|
|||
|
||||
require (
|
||||
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/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.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/go-fed/httpsig v1.1.0 // 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/google/go-querystring v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
|
|
@ -49,3 +54,5 @@ require (
|
|||
gopkg.in/warnings.v0 v0.1.2 // 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
|
||||
|
|
|
|||
19
go.sum
19
go.sum
|
|
@ -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/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.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
|
|
@ -27,6 +33,8 @@ 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/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
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/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
|
|
@ -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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
|
|
@ -111,16 +121,23 @@ gitlab.com/gitlab-org/api/client-go v0.142.0 h1:cR8+RhDc7ooH0SiGNhgm3Nf5ZpW5D1R3
|
|||
gitlab.com/gitlab-org/api/client-go v0.142.0/go.mod h1:3YuWlZCirs2TTcaAzM6qNwVHB7WvV67ATb0GGpBCdlQ=
|
||||
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=
|
||||
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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
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/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.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -132,6 +149,8 @@ golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|||
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.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
|
|
|
|||
529
internal/forge/forgejo/forgejo.go
Normal file
529
internal/forge/forgejo/forgejo.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue