Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 160 additions & 3 deletions .github/workflows/test-release-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,20 @@ jobs:
echo "=== Running recovery path ==="

# Read changelog_file from commitizen config (same as action does)
CHANGELOG_FILE=$(grep -A 20 "^\[tool\.commitizen\]" pyproject.toml | grep "^changelog_file" | head -1 | sed 's/.*=\s*"\(.*\)"/\1/' || true)
CHANGELOG_FILE=$(awk -v q="'" '
/^[[:space:]]*\[tool\.commitizen\][[:space:]]*(#.*)?$/ { inblock = 1; next }
inblock && /^[[:space:]]*\[/ { exit }
inblock && /^[[:space:]]*changelog_file[[:space:]]*=/ {
rhs = $0
sub(/^[^=]*=[[:space:]]*/, "", rhs) # rhs now starts at the value
qc = substr(rhs, 1, 1) # opening quote char of the value
if (qc == "\"" || qc == q) { # anchor to the value quote, not any quote in a comment
rest = substr(rhs, 2); p = index(rest, qc)
if (p > 0) { print substr(rest, 1, p - 1); exit }
}
exit
}
' pyproject.toml || true)
if [ -z "$CHANGELOG_FILE" ]; then
CHANGELOG_FILE="CHANGELOG.md"
fi
Expand Down Expand Up @@ -1287,7 +1300,20 @@ jobs:
uv run cz bump --yes --increment minor

# Read changelog file path
CHANGELOG_FILE=$(grep -A 20 "^\[tool\.commitizen\]" pyproject.toml | grep "^changelog_file" | head -1 | sed 's/.*=\s*"\(.*\)"/\1/' || true)
CHANGELOG_FILE=$(awk -v q="'" '
/^[[:space:]]*\[tool\.commitizen\][[:space:]]*(#.*)?$/ { inblock = 1; next }
inblock && /^[[:space:]]*\[/ { exit }
inblock && /^[[:space:]]*changelog_file[[:space:]]*=/ {
rhs = $0
sub(/^[^=]*=[[:space:]]*/, "", rhs) # rhs now starts at the value
qc = substr(rhs, 1, 1) # opening quote char of the value
if (qc == "\"" || qc == q) { # anchor to the value quote, not any quote in a comment
rest = substr(rhs, 2); p = index(rest, qc)
if (p > 0) { print substr(rest, 1, p - 1); exit }
}
exit
}
' pyproject.toml || true)
if [ -z "$CHANGELOG_FILE" ]; then
CHANGELOG_FILE="CHANGELOG.md"
fi
Expand Down Expand Up @@ -1414,7 +1440,20 @@ jobs:

- name: Verify release notes extraction from custom path
run: |
CHANGELOG_FILE=$(grep -A 20 "^\[tool\.commitizen\]" pyproject.toml | grep "^changelog_file" | head -1 | sed 's/.*=\s*"\(.*\)"/\1/' || true)
CHANGELOG_FILE=$(awk -v q="'" '
/^[[:space:]]*\[tool\.commitizen\][[:space:]]*(#.*)?$/ { inblock = 1; next }
inblock && /^[[:space:]]*\[/ { exit }
inblock && /^[[:space:]]*changelog_file[[:space:]]*=/ {
rhs = $0
sub(/^[^=]*=[[:space:]]*/, "", rhs) # rhs now starts at the value
qc = substr(rhs, 1, 1) # opening quote char of the value
if (qc == "\"" || qc == q) { # anchor to the value quote, not any quote in a comment
rest = substr(rhs, 2); p = index(rest, qc)
if (p > 0) { print substr(rest, 1, p - 1); exit }
}
exit
}
' pyproject.toml || true)
if [ -z "$CHANGELOG_FILE" ]; then
CHANGELOG_FILE="CHANGELOG.md"
fi
Expand Down Expand Up @@ -1963,3 +2002,121 @@ jobs:
fi
done
echo "[OK] pip: no lockfile created; fold step correctly skipped"

# Regression test: changelog_file must be found no matter how far below the
# [tool.commitizen] header it sits. The old detection used a fixed
# `grep -A 20` window, so a config with a long version_files array or comment
# block before changelog_file (as in the earthlens uv workspace) fell through
# to the CHANGELOG.md default and failed with "Changelog file not found".
# This fixture places changelog_file ~30 lines below the header on purpose.
test-release-changelog-far-from-header:
name: Test release - changelog_file far below the commitizen header
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Copy test fixture
run: |
cp tests/data/release-github/test-basic-uv/pyproject.toml .
cp tests/data/release-github/test-basic-uv/uv.lock .

- name: Rewrite commitizen config so changelog_file is far below the header
run: |
# Remove ONLY the existing [tool.commitizen] section (header + body up to
# the next table) so this does not depend on it being the last section of
# the shared fixture. Then append a version that pushes changelog_file well
# past 20 lines below the header (reproducing the earthlens layout).
awk '
/^\[tool\.commitizen\]/ { skip = 1; next }
skip && /^\[/ { skip = 0 }
!skip { print }
' pyproject.toml > pyproject.toml.tmp && mv pyproject.toml.tmp pyproject.toml
{
echo '[tool.commitizen]'
echo 'name = "cz_conventional_commits"'
echo 'version = "0.1.0"'
echo 'tag_format = "$version"'
echo 'update_changelog_on_bump = true'
echo 'version_files = ['
echo ' "pyproject.toml:version"'
echo ']'
for i in $(seq 1 25); do
echo "# padding line $i pushing changelog_file past the old grep -A 20 window"
done
echo 'changelog_file = "docs/change-log.md"'
} >> pyproject.toml

mkdir -p docs
echo "# Changelog" > docs/change-log.md

HEADER=$(grep -n '^\[tool\.commitizen\]' pyproject.toml | tail -1 | cut -d: -f1)
CLINE=$(grep -n '^changelog_file' pyproject.toml | tail -1 | cut -d: -f1)
echo "header at line $HEADER, changelog_file at line $CLINE (delta $((CLINE - HEADER)))"
if [ $((CLINE - HEADER)) -le 20 ]; then
echo "::error::test setup invalid: changelog_file is not far enough below the header"
exit 1
fi
echo "[OK] changelog_file sits $((CLINE - HEADER)) lines below the header (> 20)"

- name: Clean up repository tags
run: git tag -l | xargs -r git tag -d

- name: Setup fake git remote for testing
uses: ./.github/workflows/test-helpers/setup-fake-remote

- name: Normalize local branch to main (dispatch-testability)
shell: bash
run: |
git checkout -B main
git config push.default current

- name: Create initial commit with a prior tag
run: |
git config user.name "Test Bot"
git config user.email "test@example.com"

git add pyproject.toml uv.lock docs/change-log.md
git commit -m "chore: initial commit"
git tag 0.1.0
git push origin main
git push origin 0.1.0

echo "feature" > feature.py
git add feature.py
git commit -m "feat: trigger a minor bump"
git push origin main

- name: Run release action
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
increment: 'minor'
prerelease-type: 'none'
package-manager: 'uv'
python-version: '3.12'
install-groups: 'groups: dev docs'
skip-github-release: 'true'

- name: Verify the release used the custom changelog, not the CHANGELOG.md default
run: |
# The action must have found docs/change-log.md. If detection had fallen
# back to CHANGELOG.md the validate step would have failed the job, and no
# CHANGELOG.md should exist.
if [ -f CHANGELOG.md ]; then
echo "::error::CHANGELOG.md exists — detection fell back to the default"
exit 1
fi
if ! git rev-parse 0.2.0 >/dev/null 2>&1; then
echo "::error::tag 0.2.0 was not created"
git tag -l
exit 1
fi
if ! grep -q "0.2.0" docs/change-log.md; then
echo "::error::docs/change-log.md was not updated with the 0.2.0 entry"
cat docs/change-log.md
exit 1
fi
echo "[OK] release found changelog_file far below the header; docs/change-log.md updated"
50 changes: 48 additions & 2 deletions actions/release/github/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,30 @@ runs:

CHANGELOG_FILE=""
if [ -f "pyproject.toml" ]; then
CHANGELOG_FILE=$(grep -A 20 "^\[tool\.commitizen\]" pyproject.toml | grep "^changelog_file" | head -1 | sed 's/.*=\s*"\(.*\)"/\1/' || true)
# Read changelog_file from the [tool.commitizen] section, section-aware:
# scan from the header to the next "[...]" table so the key is found no
# matter how far below the header it sits (e.g. after a long version_files
# array). Tolerates a trailing comment on the header and accepts a double-
# or single-quoted value, anchoring extraction to the value's own opening
# quote so an inline "# comment" (even one containing quotes) is ignored.
# Empty result falls back to CHANGELOG.md below. `q` carries a literal
# single quote so the awk program needs no embedded single quote. Assumes
# the ordinary commitizen layout: scalar keys, no multi-line string or
# nested-array key that starts a line with "[" ahead of changelog_file.
CHANGELOG_FILE=$(awk -v q="'" '
/^[[:space:]]*\[tool\.commitizen\][[:space:]]*(#.*)?$/ { inblock = 1; next }
inblock && /^[[:space:]]*\[/ { exit }
inblock && /^[[:space:]]*changelog_file[[:space:]]*=/ {
rhs = $0
sub(/^[^=]*=[[:space:]]*/, "", rhs) # rhs now starts at the value
qc = substr(rhs, 1, 1) # opening quote char of the value
if (qc == "\"" || qc == q) { # anchor to the value quote, not any quote in a comment
rest = substr(rhs, 2); p = index(rest, qc)
if (p > 0) { print substr(rest, 1, p - 1); exit }
}
exit
}
' pyproject.toml || true)
fi

# Default to CHANGELOG.md if not found
Expand Down Expand Up @@ -303,7 +326,30 @@ runs:
# Read changelog_file from commitizen config (needed for recovery path)
CHANGELOG_FILE=""
if [ -f "pyproject.toml" ]; then
CHANGELOG_FILE=$(grep -A 20 "^\[tool\.commitizen\]" pyproject.toml | grep "^changelog_file" | head -1 | sed 's/.*=\s*"\(.*\)"/\1/' || true)
# Read changelog_file from the [tool.commitizen] section, section-aware:
# scan from the header to the next "[...]" table so the key is found no
# matter how far below the header it sits (e.g. after a long version_files
# array). Tolerates a trailing comment on the header and accepts a double-
# or single-quoted value, anchoring extraction to the value's own opening
# quote so an inline "# comment" (even one containing quotes) is ignored.
# Empty result falls back to CHANGELOG.md below. `q` carries a literal
# single quote so the awk program needs no embedded single quote. Assumes
# the ordinary commitizen layout: scalar keys, no multi-line string or
# nested-array key that starts a line with "[" ahead of changelog_file.
CHANGELOG_FILE=$(awk -v q="'" '
/^[[:space:]]*\[tool\.commitizen\][[:space:]]*(#.*)?$/ { inblock = 1; next }
inblock && /^[[:space:]]*\[/ { exit }
inblock && /^[[:space:]]*changelog_file[[:space:]]*=/ {
rhs = $0
sub(/^[^=]*=[[:space:]]*/, "", rhs) # rhs now starts at the value
qc = substr(rhs, 1, 1) # opening quote char of the value
if (qc == "\"" || qc == q) { # anchor to the value quote, not any quote in a comment
rest = substr(rhs, 2); p = index(rest, qc)
if (p > 0) { print substr(rest, 1, p - 1); exit }
}
exit
}
' pyproject.toml || true)
fi
if [ -z "$CHANGELOG_FILE" ]; then
CHANGELOG_FILE="CHANGELOG.md"
Expand Down
Loading