Skip to content

Commit c024012

Browse files
committed
feat: add release labeling workflow for PRs and issues
Add automatic labeling of PRs and issues when releases are published. New files: - templates/release-labeler.yml.template - GitHub Actions workflow - references/release-labeling.md - Documentation and setup guide The workflow: - Triggers on release published - Creates `released:vX.Y.Z` label - Labels all PRs merged since previous release - Labels issues closed by those PRs - Adds comments linking to the release This enables easy tracking of which release contains a specific fix or feature, improving communication with users and release management.
1 parent a3612c5 commit c024012

File tree

3 files changed

+347
-1
lines changed

3 files changed

+347
-1
lines changed

skills/github-project/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ description: "GitHub repository setup and configuration. This skill should be us
1212
- Setting up CODEOWNERS
1313
- Troubleshooting "merge is blocked" or "not allowed merge method" errors
1414
- Configuring auto-merge for Dependabot/Renovate
15+
- Setting up release workflows and release labeling
1516

1617
## Usage
1718

@@ -21,7 +22,8 @@ Key references:
2122
- `references/repository-structure.md` - Standard repo layout
2223
- `references/sub-issues.md` - Sub-issues GraphQL API
2324
- `references/dependency-management.md` - Dependabot/Renovate configuration
24-
- `templates/` - Auto-merge workflow templates
25+
- `references/release-labeling.md` - Automatic release labeling for PRs/issues
26+
- `templates/` - Auto-merge and release-labeler workflow templates
2527

2628
## Go Project CI Checklist
2729

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Release Labeling Reference
2+
3+
**Purpose:** Automatically track which PRs and issues shipped in each release using labels.
4+
5+
## Overview
6+
7+
When a release is published, the release-labeler workflow:
8+
1. Creates a label `released:vX.Y.Z`
9+
2. Finds all PRs merged since the previous release
10+
3. Labels those PRs and their linked issues
11+
4. Adds comments linking to the release
12+
13+
## Benefits
14+
15+
| Benefit | Description |
16+
|---------|-------------|
17+
| **Traceability** | Know exactly which release contains a fix or feature |
18+
| **User communication** | Issue reporters see when their request shipped |
19+
| **Release notes** | Easy to generate changelogs from labeled PRs |
20+
| **Audit trail** | Historical record of what shipped when |
21+
22+
## Label Format
23+
24+
```
25+
released:vX.Y.Z
26+
```
27+
28+
- **Prefix:** `released:` (consistent, searchable)
29+
- **Version:** Full semver tag (e.g., `v1.2.3`, `v13.2.0`)
30+
- **Color:** Green (`#0e8a16`) indicating shipped/done
31+
32+
## Setup
33+
34+
### 1. Add the workflow
35+
36+
Copy `templates/release-labeler.yml.template` to `.github/workflows/release-labeler.yml`:
37+
38+
```bash
39+
curl -o .github/workflows/release-labeler.yml \
40+
https://raw.githubusercontent.com/netresearch/github-project-skill/main/skills/github-project/templates/release-labeler.yml.template
41+
```
42+
43+
### 2. Ensure permissions
44+
45+
The workflow needs:
46+
- `issues: write` - To label issues and add comments
47+
- `pull-requests: write` - To label PRs and add comments
48+
- `contents: read` - To compare releases
49+
50+
### 3. Link issues to PRs
51+
52+
For automatic issue labeling, PRs must reference issues using:
53+
- `Fixes #123`
54+
- `Closes #123`
55+
- `Resolves #123`
56+
57+
GitHub automatically creates the linking when these keywords are used.
58+
59+
## How It Works
60+
61+
```
62+
┌─────────────────┐
63+
│ Release v1.2.0 │
64+
│ published │
65+
└────────┬────────┘
66+
67+
68+
┌─────────────────┐
69+
│ Get previous │
70+
│ release (v1.1.0)│
71+
└────────┬────────┘
72+
73+
74+
┌─────────────────┐
75+
│ Find commits │
76+
│ v1.1.0...v1.2.0 │
77+
└────────┬────────┘
78+
79+
80+
┌─────────────────┐
81+
│ Find PRs for │
82+
│ those commits │
83+
└────────┬────────┘
84+
85+
86+
┌─────────────────┐ ┌─────────────────┐
87+
│ Label PRs with │────▶│ Find linked │
88+
│ released:v1.2.0 │ │ issues │
89+
└─────────────────┘ └────────┬────────┘
90+
91+
92+
┌─────────────────┐
93+
│ Label issues │
94+
│ released:v1.2.0 │
95+
└─────────────────┘
96+
```
97+
98+
## Querying Releases
99+
100+
### Find all items in a release
101+
102+
```bash
103+
gh issue list --repo OWNER/REPO --label "released:v1.2.0" --state all
104+
gh pr list --repo OWNER/REPO --label "released:v1.2.0" --state all
105+
```
106+
107+
### GitHub web search
108+
109+
```
110+
https://github.com/OWNER/REPO/issues?q=label:released:v1.2.0
111+
```
112+
113+
### Find which release contains an issue
114+
115+
```bash
116+
gh issue view 123 --json labels --jq '.labels[].name | select(startswith("released:"))'
117+
```
118+
119+
## Manual Labeling
120+
121+
If you need to manually label items for a release:
122+
123+
```bash
124+
# Create label
125+
gh label create "released:v1.2.0" --color 0e8a16 --description "Released in v1.2.0"
126+
127+
# Label PRs
128+
gh pr edit 100 101 102 --add-label "released:v1.2.0"
129+
130+
# Label issues
131+
gh issue edit 50 51 --add-label "released:v1.2.0"
132+
133+
# Add comments
134+
gh issue comment 50 --body "Fixed in [v1.2.0](https://github.com/OWNER/REPO/releases/tag/v1.2.0)"
135+
```
136+
137+
## Integration with Other Tools
138+
139+
### Changelog generation
140+
141+
Use labeled PRs to generate changelogs:
142+
143+
```bash
144+
gh pr list --repo OWNER/REPO --label "released:v1.2.0" --state merged \
145+
--json number,title,labels \
146+
--jq '.[] | "- \(.title) (#\(.number))"'
147+
```
148+
149+
### Release notes automation
150+
151+
Reference labels in release body:
152+
153+
```markdown
154+
## What's Changed
155+
156+
See all changes: [released:v1.2.0](https://github.com/OWNER/REPO/issues?q=label:released:v1.2.0)
157+
```
158+
159+
## Troubleshooting
160+
161+
### PRs not being labeled
162+
163+
1. **Check tag format** - Must be semver (e.g., `v1.2.0`)
164+
2. **Check previous release** - Workflow compares between releases
165+
3. **Check permissions** - Workflow needs `pull-requests: write`
166+
167+
### Issues not being labeled
168+
169+
1. **Check PR linking** - Issues must be linked with `Fixes #123`
170+
2. **Check issue state** - Only closed issues are labeled
171+
3. **Check permissions** - Workflow needs `issues: write`
172+
173+
### First release
174+
175+
For the first release (no previous release to compare), the workflow labels recently merged PRs as a fallback.
176+
177+
## Examples
178+
179+
### Real-world usage
180+
181+
- [netresearch/ofelia](https://github.com/netresearch/ofelia/issues?q=label%3Areleased%3Av0.18.0)
182+
- [netresearch/t3x-rte_ckeditor_image](https://github.com/netresearch/t3x-rte_ckeditor_image/issues?q=label%3Areleased%3Av13.2.0)
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Release Labeler Workflow
2+
# Automatically labels PRs and issues with the release version they shipped in
3+
#
4+
# Features:
5+
# - Creates `released:vX.Y.Z` label on release publish
6+
# - Labels all PRs merged between previous and current release
7+
# - Labels issues that were closed by those PRs
8+
# - Adds comment linking to the release
9+
#
10+
# Usage:
11+
# 1. Copy to .github/workflows/release-labeler.yml
12+
# 2. Ensure GITHUB_TOKEN has issues:write and pull-requests:write permissions
13+
14+
name: Release Labeler
15+
16+
on:
17+
release:
18+
types: [published]
19+
20+
permissions:
21+
contents: read
22+
issues: write
23+
pull-requests: write
24+
25+
jobs:
26+
label-release:
27+
name: Label PRs and Issues
28+
runs-on: ubuntu-latest
29+
steps:
30+
- name: Get release info
31+
id: release
32+
env:
33+
RELEASE_TAG: ${{ github.event.release.tag_name }}
34+
RELEASE_URL: ${{ github.event.release.html_url }}
35+
run: |
36+
echo "tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
37+
echo "url=${RELEASE_URL}" >> $GITHUB_OUTPUT
38+
# Label format: released:v1.2.3
39+
echo "label=released:${RELEASE_TAG}" >> $GITHUB_OUTPUT
40+
41+
- name: Get previous release tag
42+
id: prev_release
43+
env:
44+
GH_TOKEN: ${{ github.token }}
45+
CURRENT_TAG: ${{ steps.release.outputs.tag }}
46+
run: |
47+
# Get the previous release tag (skip current)
48+
PREV_TAG=$(gh api repos/${{ github.repository }}/releases \
49+
--jq "[.[] | select(.tag_name != \"${CURRENT_TAG}\" and .prerelease == false)] | .[0].tag_name // \"\"")
50+
echo "tag=${PREV_TAG}" >> $GITHUB_OUTPUT
51+
echo "Previous release: ${PREV_TAG:-none}"
52+
53+
- name: Create release label
54+
env:
55+
GH_TOKEN: ${{ github.token }}
56+
LABEL_NAME: ${{ steps.release.outputs.label }}
57+
RELEASE_TAG: ${{ steps.release.outputs.tag }}
58+
run: |
59+
# Create label if it doesn't exist (green color: 0e8a16)
60+
gh label create "${LABEL_NAME}" \
61+
--repo ${{ github.repository }} \
62+
--color 0e8a16 \
63+
--description "Released in ${RELEASE_TAG}" \
64+
2>/dev/null || echo "Label already exists"
65+
66+
- name: Find and label merged PRs
67+
env:
68+
GH_TOKEN: ${{ github.token }}
69+
LABEL_NAME: ${{ steps.release.outputs.label }}
70+
RELEASE_URL: ${{ steps.release.outputs.url }}
71+
RELEASE_TAG: ${{ steps.release.outputs.tag }}
72+
PREV_TAG: ${{ steps.prev_release.outputs.tag }}
73+
run: |
74+
# Get merge commits between tags
75+
if [[ -n "${PREV_TAG}" ]]; then
76+
echo "Finding PRs merged between ${PREV_TAG} and ${RELEASE_TAG}"
77+
# Get commits between tags
78+
COMMITS=$(gh api repos/${{ github.repository }}/compare/${PREV_TAG}...${RELEASE_TAG} \
79+
--jq '.commits[].sha' 2>/dev/null || echo "")
80+
else
81+
echo "No previous release found, labeling PRs from last 30 days"
82+
# Fallback: get recent merged PRs
83+
COMMITS=""
84+
fi
85+
86+
# Find merged PRs
87+
if [[ -n "${COMMITS}" ]]; then
88+
# Search for PRs by merge commit
89+
for sha in ${COMMITS}; do
90+
PR_NUM=$(gh api repos/${{ github.repository }}/commits/${sha}/pulls \
91+
--jq '.[0].number // empty' 2>/dev/null || echo "")
92+
if [[ -n "${PR_NUM}" ]]; then
93+
echo "Found PR #${PR_NUM} for commit ${sha:0:7}"
94+
# Add label
95+
gh pr edit ${PR_NUM} --repo ${{ github.repository }} \
96+
--add-label "${LABEL_NAME}" 2>/dev/null || true
97+
# Add comment (skip if already commented)
98+
EXISTING=$(gh pr view ${PR_NUM} --repo ${{ github.repository }} \
99+
--json comments --jq ".comments[].body | select(contains(\"${RELEASE_TAG}\"))" 2>/dev/null || echo "")
100+
if [[ -z "${EXISTING}" ]]; then
101+
gh pr comment ${PR_NUM} --repo ${{ github.repository }} \
102+
--body "Released in [${RELEASE_TAG}](${RELEASE_URL})" 2>/dev/null || true
103+
fi
104+
fi
105+
done
106+
else
107+
# Fallback: find PRs merged to default branch recently
108+
echo "Using fallback: searching recently merged PRs"
109+
gh pr list --repo ${{ github.repository }} --state merged --limit 50 \
110+
--json number,mergedAt --jq '.[].number' | while read PR_NUM; do
111+
gh pr edit ${PR_NUM} --repo ${{ github.repository }} \
112+
--add-label "${LABEL_NAME}" 2>/dev/null || true
113+
done
114+
fi
115+
116+
- name: Find and label closed issues
117+
env:
118+
GH_TOKEN: ${{ github.token }}
119+
LABEL_NAME: ${{ steps.release.outputs.label }}
120+
RELEASE_URL: ${{ steps.release.outputs.url }}
121+
RELEASE_TAG: ${{ steps.release.outputs.tag }}
122+
run: |
123+
# Find issues that reference the release label PRs
124+
# Get PRs with the release label
125+
PR_NUMBERS=$(gh pr list --repo ${{ github.repository }} --state merged \
126+
--label "${LABEL_NAME}" --json number --jq '.[].number')
127+
128+
for PR_NUM in ${PR_NUMBERS}; do
129+
# Get issues linked/closed by this PR
130+
LINKED_ISSUES=$(gh pr view ${PR_NUM} --repo ${{ github.repository }} \
131+
--json closingIssuesReferences --jq '.closingIssuesReferences[].number' 2>/dev/null || echo "")
132+
133+
for ISSUE_NUM in ${LINKED_ISSUES}; do
134+
echo "Labeling issue #${ISSUE_NUM} (closed by PR #${PR_NUM})"
135+
gh issue edit ${ISSUE_NUM} --repo ${{ github.repository }} \
136+
--add-label "${LABEL_NAME}" 2>/dev/null || true
137+
# Add comment
138+
EXISTING=$(gh issue view ${ISSUE_NUM} --repo ${{ github.repository }} \
139+
--json comments --jq ".comments[].body | select(contains(\"${RELEASE_TAG}\"))" 2>/dev/null || echo "")
140+
if [[ -z "${EXISTING}" ]]; then
141+
gh issue comment ${ISSUE_NUM} --repo ${{ github.repository }} \
142+
--body "Fixed in [${RELEASE_TAG}](${RELEASE_URL})" 2>/dev/null || true
143+
fi
144+
done
145+
done
146+
147+
- name: Summary
148+
env:
149+
GH_TOKEN: ${{ github.token }}
150+
LABEL_NAME: ${{ steps.release.outputs.label }}
151+
run: |
152+
PR_COUNT=$(gh pr list --repo ${{ github.repository }} --state merged \
153+
--label "${LABEL_NAME}" --json number --jq 'length')
154+
ISSUE_COUNT=$(gh issue list --repo ${{ github.repository }} --state closed \
155+
--label "${LABEL_NAME}" --json number --jq 'length')
156+
echo "## Release Labeling Complete" >> $GITHUB_STEP_SUMMARY
157+
echo "" >> $GITHUB_STEP_SUMMARY
158+
echo "- **Label:** \`${LABEL_NAME}\`" >> $GITHUB_STEP_SUMMARY
159+
echo "- **PRs labeled:** ${PR_COUNT}" >> $GITHUB_STEP_SUMMARY
160+
echo "- **Issues labeled:** ${ISSUE_COUNT}" >> $GITHUB_STEP_SUMMARY
161+
echo "" >> $GITHUB_STEP_SUMMARY
162+
echo "[View all labeled items](https://github.com/${{ github.repository }}/issues?q=label%3A${LABEL_NAME})" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)