Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ jobs:
with:
go-version-file: go.mod
cache: true
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.11.4
version: v2.13.1

tidy:
name: Go mod tidy
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
with:
# gitleaks scans the full history by default; fetch all commits.
fetch-depth: 0
- uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2.3.9
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
env:
# github.token is required; gitleaks uses it to post scan summaries.
GITHUB_TOKEN: ${{ github.token }}
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ All notable changes to **go-github-kit** are documented in this file.
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.9.0] - 2026-09-01

Maintenance release. Moves the module to Go 1.27, applies `go fix ./...`,
and refreshes the pinned GitHub Actions. No exported signature, type shape,
sentinel or message string changed, and no library behaviour changed.

This raises the consumer floor: the `go` directive moves from `1.26` to
`1.27`, so a consumer building on Go 1.26 must upgrade. That is the only
consumer-visible effect of this release. A `toolchain` directive in a
dependency is still ignored, so the `go1.27.0` pin imposes nothing
downstream.

### Changed

- `go.mod` and `examples/go.mod`: `go 1.26` to `go 1.27`, and
`toolchain go1.26.6` to `toolchain go1.27.0`. The directive is a minor
floor, not a patch-level one, so `GOTOOLCHAIN=local` builds keep working
on any 1.27 patch.
- `retry`: the four `errors.As` calls in `IsTransientNetErr` and
`isPermanentNetErr` that only tested for a match, and never read the
bound variable, are now `errors.AsType`. This is the rewrite `go fix`
applies; `errors.AsType` has been available since Go 1.26, so the floor
rise above is not caused by it. The predicates classify exactly the same
errors as before. The two remaining `errors.As` calls are unchanged,
because both use the value they bind.
- `etag/algo_test.go` reformatted. Go 1.27's `gofmt` changed how it groups
alignment runs in map literals, and this file is the only one in the
tree it repositions. Test data and assertions are untouched.
- Test-only: `atomic.AddInt32`/`atomic.LoadInt32` over an `int32` replaced
with `atomic.Int32` methods in the `etag`, `pages` and `ratelimit` tests,
again from `go fix`.

### CI

- `golangci-lint` pinned version bumped from v2.11.4 to v2.13.1. This is
required by the toolchain bump rather than routine: v2.11.4 predates
Go 1.27 and carries a Go 1.26 formatter, so it would reject the
`etag/algo_test.go` reformat above and fail the lint job.
- `gitleaks/gitleaks-action` bumped from v2.3.9 to v3.0.0. v3 is a runtime
migration from Node 20 to Node 24 with no change to inputs, outputs or
behaviour. GitHub removes Node 20 from hosted runners on 2026-09-16,
after which v2 stops running at all.
- `actions/checkout` (v7.0.1), `actions/setup-go` (v7.0.0),
`actions/upload-artifact` (v7.0.1), `codecov/codecov-action` (v7.0.0) and
`golangci/golangci-lint-action` (v9.3.0) were each checked against their
latest release and are already pinned to it; no SHA changed. The
`golangci-lint-action` pin comment now names the exact version, v9.3.0,
matching every other pin in the tree.

## [1.8.0] - 2026-08-20

Ergonomics release for go-github v87 and later. Adds `ghkit.Adapt`, which
Expand Down Expand Up @@ -858,6 +907,7 @@ and rotating PATs alike.
- `golang.org/x/oauth2` v0.36.0
- `golang.org/x/time` v0.15.0

[1.9.0]: https://github.com/pcanilho/go-github-kit/releases/tag/v1.9.0
[1.8.0]: https://github.com/pcanilho/go-github-kit/releases/tag/v1.8.0
[1.7.0]: https://github.com/pcanilho/go-github-kit/releases/tag/v1.7.0
[1.6.2]: https://github.com/pcanilho/go-github-kit/releases/tag/v1.6.2
Expand Down
12 changes: 6 additions & 6 deletions etag/algo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,12 @@ func TestETag_NormalisePath(t *testing.T) {
"/repos/google/go-github": "/repos/{o}/{r}",
"/repos/google/go-github/commits/abc1234567": "/repos/{o}/{r}/commits/{sha}",
"/repos/google/go-github/compare/main...feature": "/repos/{o}/{r}/compare/{base...head}",
testPathOctocat: "/users/{u}",
"/orgs/github": "/orgs/{o}",
"/app/installations/12345": "/app/installations/{id}",
"/meta": "/meta",
"/gists/1234": "/gists/_", // unknown-route fallback
"/unmapped": "unknown",
testPathOctocat: "/users/{u}",
"/orgs/github": "/orgs/{o}",
"/app/installations/12345": "/app/installations/{id}",
"/meta": "/meta",
"/gists/1234": "/gists/_", // unknown-route fallback
"/unmapped": "unknown",
}
for in, want := range cases {
if got := normalisePath(in); got != want {
Expand Down
6 changes: 3 additions & 3 deletions etag/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@ func TestETag_LinkHeaderPreservedOn304Replay(t *testing.T) {

func TestETag_WriteInvalidation_404(t *testing.T) {
body := []byte("x")
var count int32
var count atomic.Int32
var lastIfNoneMatch atomic.Value // string
lastIfNoneMatch.Store("")
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
n := atomic.AddInt32(&count, 1)
n := count.Add(1)
lastIfNoneMatch.Store(r.Header.Get("If-None-Match"))
switch n {
case 1:
Expand Down Expand Up @@ -418,7 +418,7 @@ func TestETag_WriteInvalidation_404(t *testing.T) {
if err := r3.Body.Close(); err != nil {
t.Fatalf("post-invalidation body close: %v", err)
}
if got := atomic.LoadInt32(&count); got != 3 {
if got := count.Load(); got != 3 {
t.Fatalf("expected 3 upstream requests after invalidation; got %d", got)
}
if got := lastIfNoneMatch.Load().(string); got != "" {
Expand Down
4 changes: 2 additions & 2 deletions examples/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/pcanilho/go-github-kit/examples

go 1.26
go 1.27

toolchain go1.26.6
toolchain go1.27.0

require (
github.com/bradleyfalzon/ghinstallation/v2 v2.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/pcanilho/go-github-kit

go 1.26
go 1.27

toolchain go1.26.6
toolchain go1.27.0

require (
github.com/gofri/go-github-ratelimit/v2 v2.0.2
Expand Down
6 changes: 3 additions & 3 deletions pages/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ func BenchmarkPages_AsTyped(b *testing.B) {
// changes to caching behaviour show up here, not just in the latency
// numbers above.
func BenchmarkPages_HandlerHits(b *testing.B) {
var hits int32
var hits atomic.Int32
var srv *httptest.Server
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&hits, 1)
hits.Add(1)
page := 1
if p := r.URL.Query().Get("page"); p != "" {
page, _ = strconv.Atoi(p)
Expand All @@ -145,5 +145,5 @@ func BenchmarkPages_HandlerHits(b *testing.B) {
for range b.N {
drainPages(b, hc, srv.URL+"/items")
}
b.ReportMetric(float64(atomic.LoadInt32(&hits))/float64(b.N), "hits/walk")
b.ReportMetric(float64(hits.Load())/float64(b.N), "hits/walk")
}
18 changes: 9 additions & 9 deletions pages/pages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,11 @@ func TestPages_304PreservesLink(t *testing.T) {
// second range over the same URL hits the 304 path on every page;
// the etag layer's merged-headers code must preserve the Link
// header so the iterator still walks page 2.
var hits int32
var hits atomic.Int32
var srv *httptest.Server
mux := http.NewServeMux()
mux.HandleFunc("/items", func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&hits, 1)
hits.Add(1)
page := 1
if p := r.URL.Query().Get("page"); p != "" {
page, _ = strconv.Atoi(p)
Expand Down Expand Up @@ -261,15 +261,15 @@ func TestPages_304PreservesLink(t *testing.T) {
if got := walk(); got != 2 {
t.Fatalf("first walk pageCount = %d, want 2", got)
}
firstHits := atomic.LoadInt32(&hits)
firstHits := hits.Load()

// Second walk: each page should round-trip but resolve to 304. The
// Link header survives the 304 path, so the iterator still walks 2
// pages.
if got := walk(); got != 2 {
t.Fatalf("second walk pageCount = %d, want 2", got)
}
if got := atomic.LoadInt32(&hits); got != firstHits*2 {
if got := hits.Load(); got != firstHits*2 {
t.Errorf("hits after second walk = %d, want %d (one round-trip per page)", got, firstHits*2)
}
}
Expand Down Expand Up @@ -346,11 +346,11 @@ func TestAs_DecodeError(t *testing.T) {
// closeCounter wraps a body so the test can assert close behaviour.
type closeCounter struct {
io.Reader
closes int32
closes atomic.Int32
}

func (c *closeCounter) Close() error {
atomic.AddInt32(&c.closes, 1)
c.closes.Add(1)
return nil
}

Expand Down Expand Up @@ -381,7 +381,7 @@ func TestAs_BodyClosedAfterDecode(t *testing.T) {
if elems != 2 {
t.Errorf("elems = %d, want 2", elems)
}
if got := atomic.LoadInt32(&cc.closes); got != 1 {
if got := cc.closes.Load(); got != 1 {
t.Errorf("body closes = %d, want 1", got)
}
}
Expand All @@ -407,7 +407,7 @@ func TestAs_BodyClosedOnDecodePanic(t *testing.T) {
// Recover the panic; we only care that the body got closed
// before unwinding past the iterator.
_ = recover()
if got := atomic.LoadInt32(&cc.closes); got != 1 {
if got := cc.closes.Load(); got != 1 {
t.Errorf("body closes after panic = %d, want 1", got)
}
}()
Expand Down Expand Up @@ -449,7 +449,7 @@ func TestAs_BodyClosedOnCallerBreak(t *testing.T) {
}
// The body for the page we touched must be closed even though we
// broke before consuming all elements.
if got := atomic.LoadInt32(&cc.closes); got != 1 {
if got := cc.closes.Load(); got != 1 {
t.Errorf("body closes = %d, want 1", got)
}
}
Expand Down
4 changes: 2 additions & 2 deletions ratelimit/ratelimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func (s *syncBuf) String() string {
// observe the primary-limit callback firing.
func rlPrimaryServer(t *testing.T, limitResponses int) *httptest.Server {
t.Helper()
var seen int32
var seen atomic.Int32
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if int(atomic.AddInt32(&seen, 1)) <= limitResponses {
if int(seen.Add(1)) <= limitResponses {
w.Header().Set("X-RateLimit-Remaining", "0")
w.Header().Set("X-RateLimit-Reset", "1")
w.Header().Set("X-RateLimit-Resource", "core")
Expand Down
12 changes: 4 additions & 8 deletions retry/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,10 @@ func IsTransientNetErr(err error) bool {
if isPermanentNetErr(err) {
return false
}
var opErr *net.OpError
if errors.As(err, &opErr) {
if _, ok := errors.AsType[*net.OpError](err); ok {
return true
}
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
if _, ok := errors.AsType[*net.DNSError](err); ok {
// IsNotFound was filtered out above; remaining DNS errors (server
// failure, timeout) are transient.
return true
Expand All @@ -162,12 +160,10 @@ func isPermanentNetErr(err error) bool {
if errors.Is(err, syscall.ECONNREFUSED) {
return true
}
var unknownAuth x509.UnknownAuthorityError
if errors.As(err, &unknownAuth) {
if _, ok := errors.AsType[x509.UnknownAuthorityError](err); ok {
return true
}
var hostnameErr *x509.HostnameError
if errors.As(err, &hostnameErr) {
if _, ok := errors.AsType[*x509.HostnameError](err); ok {
return true
}
var certInvalid x509.CertificateInvalidError
Expand Down