1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
name: ci
on:
push:
branches:
pull_request:
branches:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
# Force repo-only config loading so the suite never picks up a
# runner's user-level ~/.config/gwm/config.toml (issue #190 opt-out).
GWM_NO_GLOBAL_CONFIG: "1"
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
fmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: cargo fmt --check
run: cargo fmt --all -- --check
clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: cargo clippy
run: cargo clippy --all-targets --all-features -- -D warnings
msrv:
name: msrv (declared floor, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
# Same matrix as `test`, and for the same reason: a single-platform run
# sees neither the `[target."cfg(windows)".dependencies]` block nor the
# `#[cfg(windows)]` code, so a Windows-only dependency raising its floor
# (or a Windows-only use of a newer std API) would keep this job green on
# Linux while `cargo install` breaks for those users. That is the #491 bug
# again, one axis over.
#
# The clippy job catches a *std API* newer than the declared floor
# (`clippy::incompatible_msrv`), but nothing covered a **dependency**
# whose floor is higher than ours: `Cargo.toml` said 1.86 for a whole
# release line while the graph needed 1.95, silently (issue #491).
# This job installs exactly the declared floor and both resolves and
# compiles the committed lockfile against it, which is why `--locked`
# is load-bearing: cargo's `rust-version` gate fires at resolve time
# for the crates that declare a floor, and the compile that follows
# is the only thing that catches one declaring nothing at all (which
# is what `rusqlite`'s `libsqlite3-sys` does). The toolchain is read
# out of `Cargo.toml` rather than hardcoded so the job cannot drift
# from the manifest either.
env:
# Overrides the workflow-wide `-D warnings`. A warning only an older
# toolchain emits (or an `unknown_lints` for a lint added after it)
# says nothing about the MSRV and must not paint this job red.
RUSTFLAGS: ""
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: read the declared MSRV
id: msrv
# `windows-latest` defaults to PowerShell, which has no grep/cut.
shell: bash
run: |
version=$(grep -m1 '^rust-version = ' Cargo.toml | cut -d'"' -f2)
[ -n "$version" ] || { echo "::error::no rust-version in Cargo.toml"; exit 1; }
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "declared MSRV: $version"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.version }}
- uses: Swatinem/rust-cache@v2
- name: cargo check at the declared floor
run: cargo check --all-targets --locked
# `daemon` is default-on, so the check above never compiles the
# `#[cfg(not(all(any(unix, windows), feature = "daemon")))]` arms of
# `cmd_daemon` / `cmd_statusline`. That build is documented as supported
# (docs/3.cli/1.reference.md), so it gets the same floor guarantee.
- name: cargo check at the declared floor (no default features)
run: cargo check --all-targets --locked --no-default-features
test:
name: test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: configure git identity (libgit2 commit needs one)
run: |
git config --global user.email "ci@gwm.test"
git config --global user.name "ci"
- name: cargo build
run: cargo build --verbose
- name: cargo test
run: cargo test --verbose
hook-smoke:
name: pre-commit hook smoke
runs-on: ubuntu-latest
# Cheap regression net for .githooks/pre-commit. Runs in parallel with
# the heavy `test` job — no Rust toolchain needed, just shell + git.
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: shellcheck pre-commit hook
uses: ludeeus/action-shellcheck@master
with:
scandir: ./.githooks
severity: error
- name: hook is executable
run: test -x .githooks/pre-commit
- name: hook short-circuits on empty index
run: |
scratch=$(mktemp -d)
(
cd "$scratch"
git init -q
git config user.email "ci@gwm.test"
git config user.name "ci"
cp "$GITHUB_WORKSPACE/.githooks/pre-commit" pre-commit
sh pre-commit
)
- name: gate 2 detects .gwm.toml and skips when gwm absent
run: |
scratch=$(mktemp -d)
(
cd "$scratch"
git init -q
git config user.email "ci@gwm.test"
git config user.name "ci"
cp "$GITHUB_WORKSPACE/.githooks/pre-commit" pre-commit
echo "[bootstrap]" > .gwm.toml
git add .gwm.toml
out=$(PATH="/usr/bin:/bin" sh pre-commit 2>&1)
echo "$out"
echo "$out" | grep -q "gwm not in PATH" \
|| (echo "FAIL: gate 2 should print 'gwm not in PATH' skip message" && exit 1)
)
audit:
name: cargo audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: install cargo-audit
run: cargo install cargo-audit --locked
# `--deny warnings` makes warning-class advisories (unmaintained /
# unsound / yanked) fail the job, not just outright vulnerabilities.
# Plain `cargo audit` exits 0 on those, which is why RUSTSEC-2025-0068
# (serde_yml, unsound + unmaintained) slipped past for ~9 months even
# before accounting for `continue-on-error`. Accepted advisories go in
# `audit.toml` (`[advisories] ignore = […]`) with a rationale so each
# is a conscious decision (issue #340).
- name: cargo audit
run: cargo audit --deny warnings
doctor:
name: gwm doctor (advisory)
runs-on: ubuntu-latest
needs: test
# Restrict to the `dev` integration branch only — `main` is meant to be
# stable, the doctor exists to catch in-development regressions before
# they reach a release. Without this guard the job would also run on
# every push/PR targeting `main` (the workflow header lists both).
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/dev') ||
(github.event_name == 'pull_request' && github.base_ref == 'dev')
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: configure git identity
run: |
git config --global user.email "ci@gwm.test"
git config --global user.name "ci"
# Build the binary once with the same release profile gh actions
# cache benefits from, then ask it to diagnose this very repo.
# Advisory: a non-zero exit means we want eyes on the report, but
# not a blocked merge. `lazygit` is intentionally absent on the
# runner so a Warning here is the floor, not a regression.
- name: cargo build
run: cargo build --release --quiet
- name: gwm doctor
run: ./target/release/gwm doctor
continue-on-error: true