detached 620ae64

ci: add buf lint, breaking and drift checks

commit
620ae64c77d1ebef37a401afc750c253981653af
author
brice · · 2026-10-10 06:19 UTC
parent
b7fa16d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +57 -3
M .forgejo/workflows/ci.yml +28 -0
@@ -41,3 +41,31 @@ jobs:
4141 - run: go test -race ./...
4242 - name: Architecture
4343 run: go tool hexcheck -fail-on warn ./...
44+
45+ proto:
46+ name: Protobuf and generated code
47+ runs-on: ubuntu-latest
48+ steps:
49+ - uses: actions/checkout@v6
50+ with:
51+ # buf breaking compares with the base branch.
52+ fetch-depth: 0
53+ - uses: actions/setup-go@v5
54+ with:
55+ go-version-file: go.mod
56+ cache: true
57+ - run: go tool buf lint
58+ - name: Breaking changes
59+ if: github.event_name == 'pull_request'
60+ # Skipped by the script while the base branch has no proto/ directory.
61+ run: scripts/buf-breaking.sh "origin/${{ github.base_ref }}"
62+ - name: Generated protobuf code is up to date
63+ run: |
64+ go tool buf generate
65+ git diff --exit-code -- gen/
66+ test -z "$(git status --porcelain -- gen/)"
67+ - name: Generated mocks are up to date
68+ run: |
69+ go generate ./internal/ports
70+ git diff --exit-code -- internal/ports/mocks/
71+ test -z "$(git status --porcelain -- internal/ports/mocks/)"
@@ -41,3 +41,31 @@ jobs:
41 - run: go test -race ./...41 - run: go test -race ./...
42 - name: Architecture42 - name: Architecture
43 run: go tool hexcheck -fail-on warn ./...43 run: go tool hexcheck -fail-on warn ./...
44+
45+ proto:
46+ name: Protobuf and generated code
47+ runs-on: ubuntu-latest
48+ steps:
49+ - uses: actions/checkout@v6
50+ with:
51+ # buf breaking compares with the base branch.
52+ fetch-depth: 0
53+ - uses: actions/setup-go@v5
54+ with:
55+ go-version-file: go.mod
56+ cache: true
57+ - run: go tool buf lint
58+ - name: Breaking changes
59+ if: github.event_name == 'pull_request'
60+ # Skipped by the script while the base branch has no proto/ directory.
61+ run: scripts/buf-breaking.sh "origin/${{ github.base_ref }}"
62+ - name: Generated protobuf code is up to date
63+ run: |
64+ go tool buf generate
65+ git diff --exit-code -- gen/
66+ test -z "$(git status --porcelain -- gen/)"
67+ - name: Generated mocks are up to date
68+ run: |
69+ go generate ./internal/ports
70+ git diff --exit-code -- internal/ports/mocks/
71+ test -z "$(git status --porcelain -- internal/ports/mocks/)"
M cmd/ermite/main_test.go +9 -1
@@ -4,6 +4,7 @@ package main
44
55 import (
66 "bytes"
7+ "context"
78 "errors"
89 "io"
910 "net"
@@ -20,6 +21,8 @@ import (
2021
2122 "github.com/stretchr/testify/assert"
2223 "github.com/stretchr/testify/require"
24+
25+ "bnema.dev/ermite/internal/adapters/gitcli"
2326 )
2427
2528 const adminToken = "0123456789abcdef0123456789abcdef-secret"
@@ -178,9 +181,14 @@ func TestServeMissingGitNamesVariable(t *testing.T) {
178181 }
179182
180183 func TestServeHealthAndSigterm(t *testing.T) {
181- if _, err := exec.LookPath("git"); err != nil {
184+ gitBin, err := exec.LookPath("git")
185+ if err != nil {
182186 t.Skip("git not found")
183187 }
188+ probe := gitcli.New(gitcli.Config{Bin: gitBin, DataDir: t.TempDir(), Path: os.Getenv("PATH")})
189+ if _, err := probe.CheckVersion(context.Background()); err != nil {
190+ t.Skipf("git is not usable by ermite: %v", err)
191+ }
184192 data := t.TempDir()
185193 // State from a previous run: run/ is recreated, an orphan repository is
186194 // registered, a staging leftover is removed.
@@ -4,6 +4,7 @@ package main
4 4
5 import (5 import (
6 "bytes"6 "bytes"
7+ "context"
7 "errors"8 "errors"
8 "io"9 "io"
9 "net"10 "net"
@@ -20,6 +21,8 @@ import (
20 21
21 "github.com/stretchr/testify/assert"22 "github.com/stretchr/testify/assert"
22 "github.com/stretchr/testify/require"23 "github.com/stretchr/testify/require"
24+
25+ "bnema.dev/ermite/internal/adapters/gitcli"
23 )26 )
24 27
25 const adminToken = "0123456789abcdef0123456789abcdef-secret"28 const adminToken = "0123456789abcdef0123456789abcdef-secret"
@@ -178,9 +181,14 @@ func TestServeMissingGitNamesVariable(t *testing.T) {
178 }181 }
179 182
180 func TestServeHealthAndSigterm(t *testing.T) {183 func TestServeHealthAndSigterm(t *testing.T) {
181- if _, err := exec.LookPath("git"); err != nil {184+ gitBin, err := exec.LookPath("git")
185+ if err != nil {
182 t.Skip("git not found")186 t.Skip("git not found")
183 }187 }
188+ probe := gitcli.New(gitcli.Config{Bin: gitBin, DataDir: t.TempDir(), Path: os.Getenv("PATH")})
189+ if _, err := probe.CheckVersion(context.Background()); err != nil {
190+ t.Skipf("git is not usable by ermite: %v", err)
191+ }
184 data := t.TempDir()192 data := t.TempDir()
185 // State from a previous run: run/ is recreated, an orphan repository is193 // State from a previous run: run/ is recreated, an orphan repository is
186 // registered, a staging leftover is removed.194 // registered, a staging leftover is removed.
M go.mod +1 -1
@@ -12,6 +12,7 @@ tool (
1212 require (
1313 bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c3
1414 github.com/bnema/zerowrap v1.4.1
15+ github.com/spf13/cobra v1.10.2
1516 github.com/stretchr/testify v1.12.1
1617 golang.org/x/sys v0.49.0
1718 google.golang.org/protobuf v1.36.12
@@ -96,7 +97,6 @@ require (
9697 github.com/segmentio/asm v1.2.1 // indirect
9798 github.com/segmentio/encoding v0.5.4 // indirect
9899 github.com/sirupsen/logrus v1.10.2 // indirect
99- github.com/spf13/cobra v1.10.2 // indirect
100100 github.com/spf13/pflag v1.0.10 // indirect
101101 github.com/stretchr/objx v0.5.3 // indirect
102102 github.com/tetratelabs/wazero v1.12.0 // indirect
@@ -12,6 +12,7 @@ tool (
12 require (12 require (
13 bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c313 bnema.dev/ermite-kv v0.0.0-20261010031554-5e09b48f81c3
14 github.com/bnema/zerowrap v1.4.114 github.com/bnema/zerowrap v1.4.1
15+ github.com/spf13/cobra v1.10.2
15 github.com/stretchr/testify v1.12.116 github.com/stretchr/testify v1.12.1
16 golang.org/x/sys v0.49.017 golang.org/x/sys v0.49.0
17 google.golang.org/protobuf v1.36.1218 google.golang.org/protobuf v1.36.12
@@ -96,7 +97,6 @@ require (
96 github.com/segmentio/asm v1.2.1 // indirect97 github.com/segmentio/asm v1.2.1 // indirect
97 github.com/segmentio/encoding v0.5.4 // indirect98 github.com/segmentio/encoding v0.5.4 // indirect
98 github.com/sirupsen/logrus v1.10.2 // indirect99 github.com/sirupsen/logrus v1.10.2 // indirect
99- github.com/spf13/cobra v1.10.2 // indirect
100 github.com/spf13/pflag v1.0.10 // indirect100 github.com/spf13/pflag v1.0.10 // indirect
101 github.com/stretchr/objx v0.5.3 // indirect101 github.com/stretchr/objx v0.5.3 // indirect
102 github.com/tetratelabs/wazero v1.12.0 // indirect102 github.com/tetratelabs/wazero v1.12.0 // indirect
M internal/adapters/httpserver/server.go +0 -1
@@ -118,4 +118,3 @@ func DirWritable(dir string) Check {
118118 return nil
119119 }
120120 }
121-
@@ -118,4 +118,3 @@ func DirWritable(dir string) Check {
118 return nil118 return nil
119 }119 }
120 }120 }
121-
A scripts/buf-breaking.sh +19 -0
@@ -0,0 +1,19 @@
1+#!/bin/sh
2+# Check the protobuf schema against the base branch with buf breaking.
3+# Usage: scripts/buf-breaking.sh [base-ref] (default: origin/main)
4+#
5+# The check is skipped while the base has no proto/ directory (the change that
6+# introduces the schema has nothing to be compared with).
7+set -eu
8+
9+base="${1:-origin/main}"
10+
11+if ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then
12+ echo "buf breaking: base $base not found, fetch it first" >&2
13+ exit 1
14+fi
15+if ! git cat-file -e "$base:proto" 2>/dev/null; then
16+ echo "buf breaking: $base has no proto/ directory, nothing to compare"
17+ exit 0
18+fi
19+GOWORK=off go tool buf breaking --against ".git#ref=$base"
@@ -0,0 +1,19 @@
1+#!/bin/sh
2+# Check the protobuf schema against the base branch with buf breaking.
3+# Usage: scripts/buf-breaking.sh [base-ref] (default: origin/main)
4+#
5+# The check is skipped while the base has no proto/ directory (the change that
6+# introduces the schema has nothing to be compared with).
7+set -eu
8+
9+base="${1:-origin/main}"
10+
11+if ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then
12+ echo "buf breaking: base $base not found, fetch it first" >&2
13+ exit 1
14+fi
15+if ! git cat-file -e "$base:proto" 2>/dev/null; then
16+ echo "buf breaking: $base has no proto/ directory, nothing to compare"
17+ exit 0
18+fi
19+GOWORK=off go tool buf breaking --against ".git#ref=$base"

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 44

↑↓ moveenter openesc close