Skip to content

Commit 88d8a86

Browse files
authored
Merge pull request #152 from galaxyproject/dev_updates
More dev process updates - including history generation stuff, better uv usage, etc...
2 parents 0ce4095 + 6c3470d commit 88d8a86

File tree

3 files changed

+130
-10
lines changed

3 files changed

+130
-10
lines changed

.claude/commands/ready-release.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Walk me through readying a gxformat2 release. Run each step, check for problems, and pause if anything looks wrong.
2+
3+
## Steps
4+
5+
1. **Check git status** - ensure working tree is clean, no missing files.
6+
7+
2. **Verify version** - read `gxformat2/__init__.py` and confirm `__version__` is a `.devN` variant of the intended release. Show me the current version and ask me to confirm the target release version.
8+
9+
3. **Setup venv** - check `.venv` exists. If not, run `make setup-venv`. Confirm dev-requirements are installed.
10+
11+
4. **Check UPSTREAM remote** - the Makefile defaults `UPSTREAM` to `galaxyproject`. Check if `$UPSTREAM` is set in the environment; if not, check if a git remote named `galaxyproject` exists. If it doesn't, check for `origin` or `upstream` remotes pointing to `galaxyproject/gxformat2` and offer to create a `galaxyproject` alias via `git remote add galaxyproject <url>`. This must be resolved before `make release` can push.
12+
13+
5. **Add history** - run `make add-history` to pull contributions into HISTORY.rst under the .dev0 entry. Show me the new HISTORY.rst additions for review.
14+
15+
6. **Lint** - run `make clean && make lint`. Report any failures.
16+
17+
7. **Review** - show me a summary of outstanding uncommitted changes (if any) and ask if I want to commit them before proceeding.
18+
19+
8. **Release** - after I confirm, run `make release` which does: commit-version, new-version, release-artifacts, push-release. This tags, bumps to next dev version, and pushes upstream.
20+
21+
Stop after each step and report status before moving to the next.

Makefile

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ VENV?=.venv
99
# Source virtualenv to execute command (flake8, sphinx, twine, etc...)
1010
IN_VENV=if [ -f $(VENV)/bin/activate ]; then . $(VENV)/bin/activate; fi;
1111
# TODO: add this upstream as a remote if it doesn't already exist.
12-
UPSTREAM?=jmchilton
12+
UPSTREAM?=galaxyproject
1313
SOURCE_DIR?=gxformat2
1414
BUILD_SCRIPTS_DIR=scripts
1515
DEV_RELEASE?=0
@@ -44,16 +44,12 @@ clean-test: ## remove test and coverage artifacts
4444
rm -fr htmlcov/
4545

4646
setup-venv: ## setup a development virutalenv in current directory
47-
if [ ! -d $(VENV) ]; then \
48-
if command -v uv > /dev/null 2>&1; then \
49-
uv venv $(VENV); \
50-
else \
51-
python3 -m venv $(VENV); \
52-
fi; \
53-
fi
5447
if command -v uv > /dev/null 2>&1; then \
55-
$(IN_VENV) uv pip install -r requirements.txt && uv pip install -r dev-requirements.txt; \
48+
uv sync --group test --group lint --group mypy; \
5649
else \
50+
if [ ! -d $(VENV) ]; then \
51+
python3 -m venv $(VENV); \
52+
fi; \
5753
$(IN_VENV) pip install -r requirements.txt && pip install -r dev-requirements.txt; \
5854
fi
5955

@@ -137,7 +133,7 @@ push-release: ## Push a tagged release to github
137133
release: release-local push-release ## package, review, and upload a release
138134

139135
add-history: ## Reformat HISTORY.rst with data from Github's API
140-
$(IN_VENV) python $(BUILD_SCRIPTS_DIR)/bootstrap_history.py $(ITEM)
136+
$(IN_VENV) python $(BUILD_SCRIPTS_DIR)/bootstrap_history.py --acknowledgements
141137

142138
mypy:
143139
MYPYPATH=mypy-stubs mypy gxformat2

scripts/bootstrap_history.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# Little script to make HISTORY.rst more easy to format properly, lots TODO
33
# pull message down and embed, use arg parse, handle multiple, etc...
44
import os
5+
import re
6+
import subprocess
57
import sys
68
import textwrap
79
from urllib.parse import urljoin
@@ -21,6 +23,76 @@
2123
PROJECT_API = f"https://api.github.com/repos/{PROJECT_AUTHOR}/{PROJECT_NAME}/"
2224

2325

26+
def get_last_release_tag():
27+
"""Get the last release tag based on the current version in __init__.py"""
28+
version = project.__version__
29+
# Remove .dev0 suffix if present
30+
if ".dev" in version:
31+
version = version.split(".dev")[0]
32+
33+
# Parse version components
34+
parts = version.split(".")
35+
if len(parts) >= 3:
36+
major, minor, patch = parts[:3]
37+
# Decrement patch version to get last release
38+
last_patch = max(0, int(patch) - 1)
39+
return f"{major}.{minor}.{last_patch}"
40+
return version
41+
42+
43+
def get_merge_commits_since_tag(tag):
44+
"""Get merge commits since the specified tag"""
45+
try:
46+
result = subprocess.run(
47+
["git", "log", "--merges", "--oneline", f"{tag}..HEAD"], capture_output=True, text=True, check=True
48+
)
49+
return result.stdout.strip().split("\n") if result.stdout.strip() else []
50+
except subprocess.CalledProcessError:
51+
return []
52+
53+
54+
def extract_pr_info_from_merge(merge_line):
55+
"""Extract PR number and author from merge commit message"""
56+
# Match pattern: "Merge pull request #1234 from author/branch"
57+
match = re.match(r"[a-f0-9]+\s+Merge pull request #(\d+) from ([^/]+)/", merge_line)
58+
if match:
59+
pr_number = match.group(1)
60+
author = match.group(2)
61+
return pr_number, author
62+
return None, None
63+
64+
65+
def generate_acknowledgements():
66+
"""Generate acknowledgement lines for merge commits since last release"""
67+
tag = get_last_release_tag()
68+
merge_commits = get_merge_commits_since_tag(tag)
69+
70+
acknowledgements = []
71+
for merge in merge_commits:
72+
if merge.strip():
73+
pr_number, author = extract_pr_info_from_merge(merge)
74+
if pr_number and author:
75+
try:
76+
# Get PR details from GitHub API
77+
api_url = urljoin(PROJECT_API, f"pulls/{pr_number}")
78+
req = requests.get(api_url).json()
79+
title = req.get("title", "")
80+
login = req["user"]["login"]
81+
82+
# Format acknowledgement line
83+
title_clean = title.rstrip(".")
84+
ack_line = f"* {title_clean} (thanks to `@{login}`_). `Pull Request {pr_number}`_"
85+
acknowledgements.append(ack_line)
86+
87+
# Add GitHub link
88+
github_link = f".. _Pull Request {pr_number}: {PROJECT_URL}/pull/{pr_number}"
89+
acknowledgements.append(github_link)
90+
except Exception as e:
91+
print(f"Error processing PR {pr_number}: {e}", file=sys.stderr)
92+
93+
return acknowledgements
94+
95+
2496
def main(argv):
2597
history_path = os.path.join(PROJECT_DIRECTORY, "HISTORY.rst")
2698
with open(history_path, encoding="utf-8") as fh:
@@ -30,6 +102,37 @@ def extend(from_str, line):
30102
from_str += "\n"
31103
return history.replace(from_str, from_str + line + "\n")
32104

105+
# Check if we should generate acknowledgements for merge commits
106+
if len(argv) > 1 and argv[1] == "--acknowledgements":
107+
acknowledgements = generate_acknowledgements()
108+
if acknowledgements:
109+
print("Generated acknowledgement lines:")
110+
111+
# Find the unreleased section (current dev version)
112+
current_version = project.__version__
113+
unreleased_section_marker = f"---------------------\n{current_version}\n---------------------"
114+
115+
for ack in acknowledgements:
116+
if ack.startswith("*"):
117+
print(ack)
118+
# Place acknowledgement lines in the unreleased section
119+
history = extend(unreleased_section_marker, ack)
120+
elif ack.startswith(".."):
121+
print(ack)
122+
history = extend(".. github_links", ack)
123+
124+
with open(history_path, "w", encoding="utf-8") as fh:
125+
fh.write(history)
126+
print(f"\nAcknowledgements added to {history_path}")
127+
else:
128+
print("No merge commits found since last release.")
129+
return
130+
131+
if len(argv) < 2:
132+
print("Usage: python bootstrap_history.py <identifier> [message]")
133+
print(" or: python bootstrap_history.py --acknowledgements")
134+
return
135+
33136
ident = argv[1]
34137

35138
message = ""

0 commit comments

Comments
 (0)