Skip to content

Commit 1f3d45d

Browse files
committed
wip
1 parent 49e29cd commit 1f3d45d

File tree

2 files changed

+257
-50
lines changed

2 files changed

+257
-50
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# action.yml
2+
name: "Publish coverage badge"
3+
description: "Create/update coverage/<branch>/badge.svg (+ optional report.html) on the 'coverage' branch."
4+
author: "you"
5+
inputs:
6+
coverage:
7+
description: "Coverage percentage (e.g. 83 or 83%)"
8+
required: true
9+
report:
10+
description: "Optional path to an HTML coverage report file to publish as report.html"
11+
required: false
12+
runs:
13+
using: "composite"
14+
steps:
15+
- name: Sanity / git identity
16+
shell: bash
17+
run: |
18+
set -euo pipefail
19+
git config --global --add safe.directory "$GITHUB_WORKSPACE"
20+
git config --local user.email "[email protected]"
21+
git config --local user.name "GitHub Action"
22+
23+
- name: Detect current branch (actual branch, not tag)
24+
id: branch
25+
shell: bash
26+
run: |
27+
set -euo pipefail
28+
# Prefer PR source branch, then ref_name if it's a branch, else default branch of origin
29+
BRANCH="${GITHUB_HEAD_REF:-}"
30+
if [[ -z "$BRANCH" ]]; then
31+
if [[ "${GITHUB_REF_TYPE:-}" == "branch" && -n "${GITHUB_REF_NAME:-}" ]]; then
32+
BRANCH="${GITHUB_REF_NAME}"
33+
else
34+
# Fallback to remote default branch (origin/HEAD -> refs/remotes/origin/<default>)
35+
BRANCH="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@' || true)"
36+
fi
37+
fi
38+
if [[ -z "$BRANCH" || "$BRANCH" == "HEAD" ]]; then
39+
# Ultimate fallback: try current branch name
40+
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
41+
fi
42+
if [[ -z "$BRANCH" || "$BRANCH" == "HEAD" ]]; then
43+
echo "Could not determine branch name." >&2
44+
exit 1
45+
fi
46+
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
47+
48+
- name: Ensure 'coverage' branch exists (create orphan if needed)
49+
shell: bash
50+
run: |
51+
set -euo pipefail
52+
BRANCH="${{ steps.branch.outputs.branch }}"
53+
# Does 'coverage' branch exist on origin?
54+
if git ls-remote --exit-code --heads origin coverage >/dev/null 2>&1; then
55+
echo "'coverage' branch exists."
56+
else
57+
echo "Creating orphan 'coverage' branch…"
58+
git checkout "${BRANCH}"
59+
git checkout --orphan coverage
60+
# remove index entries (keep working tree)
61+
if [[ -n "$(git ls-files)" ]]; then
62+
git rm --cached $(git ls-files)
63+
fi
64+
echo '# Coverage branch' > README.md
65+
git add README.md
66+
git commit -m 'Add README.md'
67+
# Push with auth via checkout action's token/remote config
68+
git push origin coverage
69+
git checkout --force "${BRANCH}"
70+
fi
71+
72+
- name: Check out coverage branch (worktree)
73+
shell: bash
74+
run: |
75+
set -euo pipefail
76+
# Use a worktree so we don't disturb current checkout
77+
rm -rf coverage
78+
git worktree prune || true
79+
git worktree add -B coverage coverage origin/coverage
80+
echo "Using worktree at ./coverage"
81+
82+
- name: Prepare coverage/<branch> directory
83+
shell: bash
84+
run: |
85+
set -euo pipefail
86+
BRANCH="${{ steps.branch.outputs.branch }}"
87+
mkdir -p "coverage/coverage/${BRANCH}"
88+
89+
- name: Parse coverage input and generate SVG badge
90+
shell: bash
91+
env:
92+
INPUT_COVERAGE: ${{ inputs.coverage }}
93+
run: |
94+
set -euo pipefail
95+
# Trim trailing % and whitespace
96+
RAW="$INPUT_COVERAGE"
97+
PCT="$(echo "$RAW" | sed 's/[[:space:]]//g; s/%$//')"
98+
# Validate numeric (integer or float)
99+
if ! [[ "$PCT" =~ ^([0-9]+([.][0-9]+)?)$ ]]; then
100+
echo "Invalid coverage value: '$RAW' (expected a number like 83 or 83%)." >&2
101+
exit 1
102+
fi
103+
# Clamp to [0,100]
104+
PCT=$(awk -v v="$PCT" 'BEGIN{ if (v<0) v=0; if (v>100) v=100; printf("%.2f", v) }')
105+
# Convert percentage -> hue (0 = red, 120 = green) for smooth red->orange->yellow->green
106+
# HSL: h in [0,120], s=1, l=0.4 (nice contrast). Convert to RGB then hex.
107+
read R G B HEX < <(awk -v p="$PCT" '
108+
function hsl2rgb(h,s,l, c,x,m,r,g,b,R,G,B) {
109+
c=(1 - ((2*l-1)<0?-(2*l-1):(2*l-1))) * s
110+
hprime=h/60.0
111+
x=c*(1 - ((hprime%2)-1<0? -((hprime%2)-1) : ((hprime%2)-1)))
112+
if (0<=hprime && hprime<1){r=c; g=x; b=0}
113+
else if (1<=hprime && hprime<2){r=x; g=c; b=0}
114+
else if (2<=hprime && hprime<3){r=0; g=c; b=x}
115+
else if (3<=hprime && hprime<4){r=0; g=x; b=c}
116+
else if (4<=hprime && hprime<5){r=x; g=0; b=c}
117+
else if (5<=hprime && hprime<6){r=c; g=0; b=x}
118+
else {r=0; g=0; b=0}
119+
m=l - c/2.0
120+
R=int((r+m)*255+0.5); G=int((g+m)*255+0.5); B=int((b+m)*255+0.5)
121+
return sprintf("%d %d %d #%02X%02X%02X", R,G,B,R,G,B)
122+
}
123+
BEGIN{
124+
h=120.0*(p/100.0); s=1.0; l=0.40;
125+
print hsl2rgb(h,s,l);
126+
}')
127+
echo "Computed color $HEX for ${PCT}%"
128+
# Badge layout (simple, fixed width). Left label black, right value colored.
129+
LABEL="coverage"
130+
VALUE="$(printf "%.0f" "$PCT")%"
131+
LEFT_W=76
132+
RIGHT_W=66
133+
WIDTH=$((LEFT_W+RIGHT_W))
134+
SVG="$(cat <<EOF
135+
<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="20" role="img" aria-label="${LABEL}: ${VALUE}">
136+
<linearGradient id="s" x2="0" y2="100%">
137+
<stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
138+
<stop offset="1" stop-opacity=".1"/>
139+
</linearGradient>
140+
<mask id="m"><rect width="${WIDTH}" height="20" rx="3" fill="#fff"/></mask>
141+
<g mask="url(#m)">
142+
<rect width="${LEFT_W}" height="20" fill="#000"/>
143+
<rect x="${LEFT_W}" width="${RIGHT_W}" height="20" fill="${HEX}"/>
144+
<rect width="${WIDTH}" height="20" fill="url(#s)"/>
145+
</g>
146+
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
147+
<text x="$((LEFT_W/2))" y="15">${LABEL}</text>
148+
<text x="$((LEFT_W + RIGHT_W/2))" y="15">${VALUE}</text>
149+
</g>
150+
</svg>
151+
EOF
152+
)"
153+
echo "$SVG" > "coverage/coverage/${{ steps.branch.outputs.branch }}/badge.svg"
154+
155+
- name: Copy optional report.html
156+
if: ${{ inputs.report != '' }}
157+
shell: bash
158+
run: |
159+
set -euo pipefail
160+
BRANCH="${{ steps.branch.outputs.branch }}"
161+
SRC="${{ inputs.report }}"
162+
if [[ ! -f "$SRC" ]]; then
163+
echo "Report file not found at: $SRC" >&2
164+
exit 1
165+
fi
166+
cp -f "$SRC" "coverage/coverage/${BRANCH}/report.html"
167+
168+
- name: Commit & push changes to coverage branch
169+
shell: bash
170+
run: |
171+
set -euo pipefail
172+
BRANCH="${{ steps.branch.outputs.branch }}"
173+
pushd coverage >/dev/null
174+
git config --local user.email "[email protected]"
175+
git config --local user.name "GitHub Action"
176+
test ! -f "coverage/${BRANCH}/badge.svg" || git add "coverage/${BRANCH}/badge.svg"
177+
test ! -f "coverage/${BRANCH}/report.html" || git add "coverage/${BRANCH}/report.html"
178+
if [[ -n "$(git status --porcelain)" ]]; then
179+
git commit -m "update"
180+
git push origin HEAD:coverage
181+
else
182+
echo "No changes to commit."
183+
fi
184+
popd >/dev/null
185+
186+
- name: Summary
187+
shell: bash
188+
run: |
189+
set -euo pipefail
190+
BRANCH="${{ steps.branch.outputs.branch }}"
191+
echo "Published:"
192+
echo " - coverage/${BRANCH}/badge.svg"
193+
if [[ -f "coverage/coverage/${BRANCH}/report.html" ]]; then
194+
echo " - coverage/${BRANCH}/report.html"
195+
fi

.github/workflows/build.yml

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ jobs:
3535
- name: Test
3636
run: go test -tags debug -bench=. -coverprofile=coverage.out ./...
3737

38+
- name: Coverage
39+
id: coverage
40+
run: |
41+
echo "COVERAGE=$(go tool cover -func=coverage.out | tail -n 1 | tr -s '\t' | cut -f 3)" >> $GITHUB_OUTPUT
42+
go tool cover -html=coverage.out -o=coveragereport.out
43+
3844
- name: Run Gosec Security Scanner
3945
uses: securego/gosec@master
4046
with:
@@ -46,6 +52,12 @@ jobs:
4652
- name: Go report card
4753
uses: creekorful/[email protected]
4854

55+
- name: Publish badge (and optional report)
56+
uses: ./.github/actions/gitcoverage
57+
with:
58+
coverage: ${{ steps.coverage.outputs.coverage }}
59+
report: "coveragereport.out"
60+
4961
# Generate code coverage badge and push it to the 'coverage' branch.
5062
# Reference it in your README.md like this:
5163
#
@@ -66,53 +78,53 @@ jobs:
6678
# git checkout --orphan coverage && git rm --cached $(git ls-files) && echo '# Coverage branch' > README.md
6779
# git add README.md && git commit -m 'Add README.md' && git push origin coverage
6880
# git checkout --force main
69-
- name: Extract branch name
70-
run: echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
71-
id: extract_branch
72-
- uses: actions/checkout@v3
73-
with:
74-
ref: coverage
75-
path: coverage
76-
- name: Ensure directories for this branch exist
77-
env:
78-
BRANCH: ${{ steps.extract_branch.outputs.branch }}
79-
run: mkdir -p coverage/${BRANCH}
80-
- name: Extract code coverage
81-
id: coverage
82-
env:
83-
BRANCH: ${{ steps.extract_branch.outputs.branch }}
84-
# Change this to extract the coverage percentage (without the percent sign) for your language into COVERAGE.
85-
# Here we are using a 'coverage.out' file generated by 'go test -coverprofile=coverage.out ./...'
86-
# You may also generate a HTML report and place at 'coverage/${BRANCH}/report.html'.
87-
run: |
88-
echo "COVERAGE=$(go tool cover -func=coverage.out | tail -n 1 | tr -s '\t' | cut -f 3 | rev | cut -c2- | rev)" >> $GITHUB_OUTPUT
89-
go tool cover -html=coverage.out -o=coverage/${BRANCH}/report.html
90-
- name: Generate the badge SVG image
91-
uses: emibcn/[email protected]
92-
with:
93-
label: 'coverage'
94-
status: ${{ steps.coverage.outputs.coverage }}%
95-
path: coverage/${{ steps.extract_branch.outputs.branch }}/badge.svg
96-
color: ${{
97-
steps.coverage.outputs.coverage > 95 && 'green' ||
98-
steps.coverage.outputs.coverage > 90 && 'yellow,green,green' ||
99-
steps.coverage.outputs.coverage > 80 && 'yellow,green' ||
100-
steps.coverage.outputs.coverage > 70 && 'yellow' ||
101-
steps.coverage.outputs.coverage > 60 && 'orange,yellow' ||
102-
steps.coverage.outputs.coverage > 50 && 'orange' ||
103-
steps.coverage.outputs.coverage > 40 && 'red,orange' ||
104-
steps.coverage.outputs.coverage > 30 && 'red,red,orange' ||
105-
steps.coverage.outputs.coverage > 20 && 'red,red,red,orange' ||
106-
'red' }}
107-
- name: Commit coverage files
108-
env:
109-
BRANCH: ${{ steps.extract_branch.outputs.branch }}
110-
run: |
111-
pushd coverage
112-
git config --local user.email "[email protected]"
113-
git config --local user.name "GitHub Action"
114-
test ! -f "${BRANCH}/badge.svg" || git add "${BRANCH}/badge.svg"
115-
test ! -f "${BRANCH}/report.html" || git add "${BRANCH}/report.html"
116-
test -z "$(git status --porcelain)" || git commit -m "update"
117-
git push
118-
popd
81+
# - name: Extract branch name
82+
# run: echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_OUTPUT
83+
# id: extract_branch
84+
# - uses: actions/checkout@v3
85+
# with:
86+
# ref: coverage
87+
# path: coverage
88+
# - name: Ensure directories for this branch exist
89+
# env:
90+
# BRANCH: ${{ steps.extract_branch.outputs.branch }}
91+
# run: mkdir -p coverage/${BRANCH}
92+
# - name: Extract code coverage
93+
# id: coverage
94+
# env:
95+
# BRANCH: ${{ steps.extract_branch.outputs.branch }}
96+
# # Change this to extract the coverage percentage (without the percent sign) for your language into COVERAGE.
97+
# # Here we are using a 'coverage.out' file generated by 'go test -coverprofile=coverage.out ./...'
98+
# # You may also generate a HTML report and place at 'coverage/${BRANCH}/report.html'.
99+
# run: |
100+
# echo "COVERAGE=$(go tool cover -func=coverage.out | tail -n 1 | tr -s '\t' | cut -f 3 | rev | cut -c2- | rev)" >> $GITHUB_OUTPUT
101+
# go tool cover -html=coverage.out -o=coverage/${BRANCH}/report.html
102+
# - name: Generate the badge SVG image
103+
# uses: emibcn/[email protected]
104+
# with:
105+
# label: 'coverage'
106+
# status: ${{ steps.coverage.outputs.coverage }}%
107+
# path: coverage/${{ steps.extract_branch.outputs.branch }}/badge.svg
108+
# color: ${{
109+
# steps.coverage.outputs.coverage > 95 && 'green' ||
110+
# steps.coverage.outputs.coverage > 90 && 'yellow,green,green' ||
111+
# steps.coverage.outputs.coverage > 80 && 'yellow,green' ||
112+
# steps.coverage.outputs.coverage > 70 && 'yellow' ||
113+
# steps.coverage.outputs.coverage > 60 && 'orange,yellow' ||
114+
# steps.coverage.outputs.coverage > 50 && 'orange' ||
115+
# steps.coverage.outputs.coverage > 40 && 'red,orange' ||
116+
# steps.coverage.outputs.coverage > 30 && 'red,red,orange' ||
117+
# steps.coverage.outputs.coverage > 20 && 'red,red,red,orange' ||
118+
# 'red' }}
119+
# - name: Commit coverage files
120+
# env:
121+
# BRANCH: ${{ steps.extract_branch.outputs.branch }}
122+
# run: |
123+
# pushd coverage
124+
# git config --local user.email "[email protected]"
125+
# git config --local user.name "GitHub Action"
126+
# test ! -f "${BRANCH}/badge.svg" || git add "${BRANCH}/badge.svg"
127+
# test ! -f "${BRANCH}/report.html" || git add "${BRANCH}/report.html"
128+
# test -z "$(git status --porcelain)" || git commit -m "update"
129+
# git push
130+
# popd

0 commit comments

Comments
 (0)