Skip to content

Commit 5ceb6c9

Browse files
committed
doc: add a basic release process
1. A simple checklist. 2. A script to generate release notes. I'm explicitly ignoring anything like a branching model for the moment. We don't have a long RC process so that shouldn't be required (yet).
1 parent 0b6144a commit 5ceb6c9

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed

.github/ISSUE_TEMPLATE/release.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
name: 'Libp2p Release'
3+
about: 'Start a new libp2p release.'
4+
---
5+
6+
## 🗺 What's left for release
7+
8+
<List of items with remaining PRs and/or Issues to be considered for this release>
9+
10+
## 🔦 Highlights
11+
12+
< top highlights for this release notes >
13+
14+
## Changelog
15+
16+
< changelog generated by scripts/mkreleaselog >
17+
18+
## ✅ Release Checklist
19+
20+
- [ ] **Stage 0 - Automated Testing**
21+
- [ ] Fork a new `release-vX.Y.Z` branch from `master`, freezing master.
22+
- [ ] Make sure local tests are passing.
23+
- [ ] **Stage 1 - Upstream Testing**
24+
- Create testing branches in upstream repos with the new go-libp2p release and run CI/tests.
25+
- [ ] [filecoin-project/lotus](https://github.com/filecoin-project/lotus)
26+
- [ ] [ipfs/go-bitswap](https://github.com/ipfs/go-bitswap)
27+
- [ ] [ipfs/go-ipfs](https://github.com/ipfs/go-ipfs)
28+
- [ ] [libp2p/go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht)
29+
- [ ] [libp2p/go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub)
30+
- _(someday)_ Run upstream testground tests. Unfortunately, this is too time consuming at the moment.
31+
- _(someday)_ Run bitswap testground tests.
32+
- _(someday)_ Run DHT testground tests.
33+
- [ ] **Stage 2 - Infrastructure Testing**
34+
- Where:
35+
- [ ] A go-ipfs gateway.
36+
- [ ] A go-ipfs bootstrapper.
37+
- [ ] A go-ipfs preload node.
38+
- [ ] A hydra booster.
39+
- [ ] A Filecoin bootstrap node.
40+
- [ ] Calibration net
41+
- What:
42+
- Look at pprof profile dumps, especially CPU profiles and heap allocation profiles, noting any significant changes.
43+
- Make sure nothing crashes.
44+
- [ ] **Stage 3 - Release**
45+
- [ ] Merge the release PR.
46+
- [ ] Tag the release on master.
47+
- [ ] Publish the release through the GitHub UI, adding the release notes. Some users rely on this to receive notifications of new releases.
48+
- [ ] Announce the release on the [discuss.libp2p.io](https://discuss.libp2p.io).
49+
- [ ] **Stage 4 - Update Upstream**
50+
- [ ] Update the upstream testing branches to the final release and create PRs.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ If you experience any issues migrating from gx to gomod, please [join the discus
110110

111111
`go test ./...` will run all tests in the repo.
112112

113+
### Releasing
114+
115+
Please start a release by opening a new [Libp2p Release](https://github.com/libp2p/go-libp2p/issues/new?assignees=&labels=kind/tracking&template=release.md) issue.
116+
113117
### Packages
114118

115119
> This table is generated using the module [`package-table`](https://github.com/ipfs-shipyard/package-table) with `package-table --data=package-list.json`.

docs/releases.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# `go-libp2p` Release Flow
2+
3+
## Release Philosophy
4+
5+
`go-libp2p` releases on an as-needed basis, either to ship new features or bug fixes.
6+
7+
1. Create an actual release process.
8+
2. Port the go-ipfs changelog script over.
9+
4. Ship a test version of go-ipfs with the new libp2p release to:
10+
1. A gateway.
11+
2. A bootstrapper.
12+
3. A preload node.
13+
5. Cut a final release.
14+
6. Announce on discuss.libp2p.io.

scripts/mkreleaselog

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#!/bin/zsh
2+
#set -x
3+
set -euo pipefail
4+
export GO111MODULE=on
5+
export GOPATH="$(go env GOPATH)"
6+
7+
alias jq="jq --unbuffered"
8+
9+
AUTHORS=(
10+
# orgs
11+
ipfs
12+
ipld
13+
libp2p
14+
multiformats
15+
filecoin-project
16+
ipfs-shipyard
17+
18+
# Authors of personal repos used by go-libp2p that should be mentioned in the
19+
# release notes.
20+
whyrusleeping
21+
Kubuxu
22+
jbenet
23+
Stebalien
24+
marten-seemann
25+
hsanjuan
26+
lucas-clemente
27+
warpfork
28+
)
29+
30+
[[ -n "${REPO_FILTER+x}" ]] || REPO_FILTER="github.com/(${$(printf "|%s" "${AUTHORS[@]}"):1})"
31+
32+
[[ -n "${IGNORED_FILES+x}" ]] || IGNORED_FILES='^\(\.gx\|package\.json\|\.travis\.yml\|go.mod\|go\.sum|\.github|\.circleci\)$'
33+
34+
NL=$'\n'
35+
36+
ROOT_DIR="$(git rev-parse --show-toplevel)"
37+
38+
msg() {
39+
echo "$*" >&2
40+
}
41+
42+
statlog() {
43+
local module="$1"
44+
local rpath="$GOPATH/src/$(strip_version "$module")"
45+
local start="${2:-}"
46+
local end="${3:-HEAD}"
47+
local mailmap_file="$rpath/.mailmap"
48+
if ! [[ -e "$mailmap_file" ]]; then
49+
mailmap_file="$ROOT_DIR/.mailmap"
50+
fi
51+
52+
git -C "$rpath" -c mailmap.file="$mailmap_file" log --use-mailmap --shortstat --no-merges --pretty="tformat:%H%n%aN%n%aE" "$start..$end" | while
53+
read hash
54+
read name
55+
read email
56+
read _ # empty line
57+
read changes
58+
do
59+
changed=0
60+
insertions=0
61+
deletions=0
62+
while read count event; do
63+
if [[ "$event" =~ ^file ]]; then
64+
changed=$count
65+
elif [[ "$event" =~ ^insertion ]]; then
66+
insertions=$count
67+
elif [[ "$event" =~ ^deletion ]]; then
68+
deletions=$count
69+
else
70+
echo "unknown event $event" >&2
71+
exit 1
72+
fi
73+
done<<<"${changes//,/$NL}"
74+
75+
jq -n \
76+
--arg "hash" "$hash" \
77+
--arg "name" "$name" \
78+
--arg "email" "$email" \
79+
--argjson "changed" "$changed" \
80+
--argjson "insertions" "$insertions" \
81+
--argjson "deletions" "$deletions" \
82+
'{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}'
83+
done
84+
}
85+
86+
# Returns a stream of deps changed between $1 and $2.
87+
dep_changes() {
88+
{
89+
<"$1"
90+
<"$2"
91+
} | jq -s 'JOIN(INDEX(.[0][]; .Path); .[1][]; .Path; {Path: .[0].Path, Old: (.[1] | del(.Path)), New: (.[0] | del(.Path))}) | select(.New.Version != .Old.Version)'
92+
}
93+
94+
# resolve_commits resolves a git ref for each version.
95+
resolve_commits() {
96+
jq '. + {Ref: (.Version|capture("^((?<ref1>.*)\\+incompatible|v.*-(0\\.)?[0-9]{14}-(?<ref2>[a-f0-9]{12})|(?<ref3>v.*))$") | .ref1 // .ref2 // .ref3)}'
97+
}
98+
99+
pr_link() {
100+
local repo="$1"
101+
local prnum="$2"
102+
local ghname="${repo##github.com/}"
103+
printf -- "[%s#%s](https://%s/pull/%s)" "$ghname" "$prnum" "$repo" "$prnum"
104+
}
105+
106+
# Generate a release log for a range of commits in a single repo.
107+
release_log() {
108+
setopt local_options BASH_REMATCH
109+
110+
local module="$1"
111+
local start="$2"
112+
local end="${3:-HEAD}"
113+
local repo="$(strip_version "$1")"
114+
local dir="$GOPATH/src/$repo"
115+
116+
local commit pr
117+
git -C "$dir" log \
118+
--format='tformat:%H %s' \
119+
--first-parent \
120+
"$start..$end" |
121+
while read commit subject; do
122+
# Skip gx-only PRs.
123+
git -C "$dir" diff-tree --no-commit-id --name-only "$commit^" "$commit" |
124+
grep -v "${IGNORED_FILES}" >/dev/null || continue
125+
126+
if [[ "$subject" =~ '^Merge pull request #([0-9]+) from' ]]; then
127+
local prnum="${BASH_REMATCH[2]}"
128+
local desc="$(git -C "$dir" show --summary --format='tformat:%b' "$commit" | head -1)"
129+
printf -- "- %s (%s)\n" "$desc" "$(pr_link "$repo" "$prnum")"
130+
elif [[ "$subject" =~ '\(#([0-9]+)\)$' ]]; then
131+
local prnum="${BASH_REMATCH[2]}"
132+
printf -- "- %s (%s)\n" "$subject" "$(pr_link "$repo" "$prnum")"
133+
else
134+
printf -- "- %s\n" "$subject"
135+
fi
136+
done
137+
}
138+
139+
indent() {
140+
sed -e 's/^/ /'
141+
}
142+
143+
mod_deps() {
144+
go list -mod=mod -json -m all | jq 'select(.Version != null)'
145+
}
146+
147+
ensure() {
148+
local repo="$(strip_version "$1")"
149+
local commit="$2"
150+
local rpath="$GOPATH/src/$repo"
151+
if [[ ! -d "$rpath" ]]; then
152+
msg "Cloning $repo..."
153+
git clone "http://$repo" "$rpath" >&2
154+
fi
155+
156+
if ! git -C "$rpath" rev-parse --verify "$commit" >/dev/null; then
157+
msg "Fetching $repo..."
158+
git -C "$rpath" fetch --all >&2
159+
fi
160+
161+
git -C "$rpath" rev-parse --verify "$commit" >/dev/null || return 1
162+
}
163+
164+
statsummary() {
165+
jq -s 'group_by(.Author)[] | {Author: .[0].Author, Commits: (. | length), Insertions: (map(.Insertions) | add), Deletions: (map(.Deletions) | add), Files: (map(.Files) | add)}' |
166+
jq '. + {Lines: (.Deletions + .Insertions)}'
167+
}
168+
169+
strip_version() {
170+
local repo="$1"
171+
if [[ "$repo" =~ '.*/v[0-9]+$' ]]; then
172+
repo="$(dirname "$repo")"
173+
fi
174+
echo "$repo"
175+
}
176+
177+
recursive_release_log() {
178+
local start="${1:-$(git tag -l | sort -V | grep -v -- '-rc' | grep 'v'| tail -n1)}"
179+
local end="${2:-$(git rev-parse HEAD)}"
180+
local repo_root="$(git rev-parse --show-toplevel)"
181+
local module="$(go list -m)"
182+
local dir="$(go list -m -f '{{.Dir}}')"
183+
184+
if [[ "${GOPATH}/${module}" -ef "${dir}" ]]; then
185+
echo "This script requires the target module and all dependencies to live in a GOPATH."
186+
return 1
187+
fi
188+
189+
(
190+
local result=0
191+
local workspace="$(mktemp -d)"
192+
trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT
193+
cd "$workspace"
194+
195+
echo "Computing old deps..." >&2
196+
git -C "$repo_root" show "$start:go.mod" >go.mod
197+
mod_deps | resolve_commits | jq -s > old_deps.json
198+
199+
echo "Computing new deps..." >&2
200+
git -C "$repo_root" show "$end:go.mod" >go.mod
201+
mod_deps | resolve_commits | jq -s > new_deps.json
202+
203+
rm -f go.mod go.sum
204+
205+
printf -- "Generating Changelog for %s %s..%s\n" "$module" "$start" "$end" >&2
206+
207+
printf -- "- %s:\n" "$module"
208+
release_log "$module" "$start" "$end" | indent
209+
210+
211+
statlog "$module" "$start" "$end" > statlog.json
212+
213+
dep_changes old_deps.json new_deps.json |
214+
jq --arg filter "$REPO_FILTER" 'select(.Path | match($filter))' |
215+
# Compute changelogs
216+
jq -r '"\(.Path) \(.New.Version) \(.New.Ref) \(.Old.Version) \(.Old.Ref // "")"' |
217+
while read module new new_ref old old_ref; do
218+
if ! ensure "$module" "$new_ref"; then
219+
result=1
220+
local changelog="failed to fetch repo"
221+
else
222+
statlog "$module" "$old_ref" "$new_ref" >> statlog.json
223+
local changelog="$(release_log "$module" "$old_ref" "$new_ref")"
224+
fi
225+
if [[ -n "$changelog" ]]; then
226+
printf -- "- %s (%s -> %s):\n" "$module" "$old" "$new"
227+
echo "$changelog" | indent
228+
fi
229+
done
230+
231+
echo
232+
echo "Contributors"
233+
echo
234+
235+
echo "| Contributor | Commits | Lines ± | Files Changed |"
236+
echo "|-------------|---------|---------|---------------|"
237+
statsummary <statlog.json |
238+
jq -s 'sort_by(.Lines) | reverse | .[]' |
239+
jq -r '"| \(.Author) | \(.Commits) | +\(.Insertions)/-\(.Deletions) | \(.Files) |"'
240+
return "$status"
241+
)
242+
}
243+
244+
recursive_release_log "$@"

0 commit comments

Comments
 (0)