diff --git a/README.md b/README.md index b99454f..1417073 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,9 @@ in every repository. The first five appear in the changelog; the others are left unless they are breaking (`!`). A [gitmoji](https://gitmoji.dev) after the type (`feat: ✨ add X`, or `:sparkles:`) is optional and stripped from the changelog. The changelog starts at the newest final tag, so a repository tags `v0.0.0` where it adopts -the process. +the process. Each entry ends with an absolute link to its PR (`[#12](https://github.com/…/pull/12)`), +built from `GITHUB_REPOSITORY`: GitHub doesn't autolink `#12` in `CHANGELOG.md`, nor reliably +in release notes. The `just changelog` recipes take the repository from the `origin` remote. ## Adopting release-tools @@ -349,7 +351,9 @@ changelog: set -euo pipefail ref=$(sed -nE 's|.*Algorithmiq/release-tools/.github/workflows/release.yml@([^[:space:]]+).*|\1|p' \ .github/workflows/release.yml) - uvx --from {{ git_cliff }} git-cliff --unreleased \ + # Link the PR numbers like the release PR does: GitHub Actions sets GITHUB_REPOSITORY. + repo=$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##') + GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$repo}" uvx --from {{ git_cliff }} git-cliff --unreleased \ --config-url "https://raw.githubusercontent.com/Algorithmiq/release-tools/$ref/cliff.toml" ``` @@ -369,7 +373,10 @@ changelog: config="$(mktemp -d)/cliff.toml" gh api -H "Accept: application/vnd.github.raw" \ "repos/Algorithmiq/release-tools/contents/cliff.toml?ref=$ref" > "$config" - uvx --from {{ git_cliff }} git-cliff --unreleased --config "$config" + # Link the PR numbers like the release PR does: GitHub Actions sets GITHUB_REPOSITORY. + repo=$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##') + GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$repo}" uvx --from {{ git_cliff }} git-cliff --unreleased \ + --config "$config" ``` ### PR template diff --git a/cliff.toml b/cliff.toml index 867f81e..d58da44 100644 --- a/cliff.toml +++ b/cliff.toml @@ -12,7 +12,13 @@ All notable changes to this project are listed here. # Lets `--prepend` replace the header instead of repeating it. header_marker = "" # Literal string ('''), so regex backslashes don't need escaping. +# +# A squash-merged PR's number, `(#N)` at the end of its title, becomes an absolute link to +# the PR on GitHub, for the repository named in GITHUB_REPOSITORY (set by GitHub Actions to +# the calling repository). GitHub doesn't autolink `#N` in files such as CHANGELOG.md, and +# not reliably in release notes either. Without GITHUB_REPOSITORY it stays `(#N)`. body = ''' +{%- set repo = get_env(name="GITHUB_REPOSITORY", default="") %} {%- if version %} ## {{ version | trim_start_matches(pat="v") }} ({{ timestamp | date(format="%Y-%m-%d") }}) {%- else %} @@ -21,7 +27,10 @@ body = ''' {% for entry in commits | commit_groups(groups=commit_parsers_groups) %} ### {{ entry.group }} {% for commit in entry.commits %} -- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}{{ commit.message | upper_first }} +- {% if commit.scope %}**{{ commit.scope }}:** {% endif %} +{%- if repo %}{{ commit.message | upper_first | replace_regex(from="\(#(\d+)\)$", to="([#$1](https://github.com/" ~ repo ~ "/pull/$1))") }} +{%- else %}{{ commit.message | upper_first }} +{%- endif %} {%- if commit.breaking or entry.group == "Deprecations" %} {%- set parts = commit.body | default(value="") | split_regex(pat="(?m)^##\s*Migration\s*$") %} {%- if parts | length > 1 %} diff --git a/examples/stubs/justfile b/examples/stubs/justfile index 6bce348..aa59cdf 100644 --- a/examples/stubs/justfile +++ b/examples/stubs/justfile @@ -6,5 +6,7 @@ changelog: set -euo pipefail ref=$(sed -nE 's|.*Algorithmiq/release-tools/.github/workflows/release.yml@([^[:space:]]+).*|\1|p' \ .github/workflows/release.yml) - uvx --from {{ git_cliff }} git-cliff --unreleased \ + # Link the PR numbers like the release PR does: GitHub Actions sets GITHUB_REPOSITORY. + repo=$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##') + GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$repo}" uvx --from {{ git_cliff }} git-cliff --unreleased \ --config-url "https://raw.githubusercontent.com/Algorithmiq/release-tools/$ref/cliff.toml" diff --git a/examples/stubs/justfile-internal b/examples/stubs/justfile-internal index ea9f587..0bac73a 100644 --- a/examples/stubs/justfile-internal +++ b/examples/stubs/justfile-internal @@ -10,4 +10,7 @@ changelog: config="$(mktemp -d)/cliff.toml" gh api -H "Accept: application/vnd.github.raw" \ "repos/Algorithmiq/release-tools/contents/cliff.toml?ref=$ref" > "$config" - uvx --from {{ git_cliff }} git-cliff --unreleased --config "$config" + # Link the PR numbers like the release PR does: GitHub Actions sets GITHUB_REPOSITORY. + repo=$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##') + GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-$repo}" uvx --from {{ git_cliff }} git-cliff --unreleased \ + --config "$config" diff --git a/justfile b/justfile index 859f5e7..bd72cbf 100644 --- a/justfile +++ b/justfile @@ -32,4 +32,4 @@ test *ARGS: # Preview the changelog entry of the next release of this repository. changelog: - uv run --no-sync git-cliff --unreleased + GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-Algorithmiq/release-tools}" uv run --no-sync git-cliff --unreleased diff --git a/tests/conftest.py b/tests/conftest.py index f3cfbf8..58792c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,6 +53,15 @@ def remote_tags(self) -> list[str]: return sorted(line.split("refs/tags/")[1] for line in lines) +@pytest.fixture(autouse=True) +def no_github_repository(monkeypatch: pytest.MonkeyPatch) -> None: + """Unset GITHUB_REPOSITORY, which GitHub Actions also sets when it runs the tests. + + cliff.toml links PR numbers to that repository; tests that want links set it. + """ + monkeypatch.delenv("GITHUB_REPOSITORY", raising=False) + + @pytest.fixture def repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Repo: """Return an empty clone with a local bare origin, as the current directory. diff --git a/tests/test_cliff.py b/tests/test_cliff.py index cb028f5..2306a5e 100644 --- a/tests/test_cliff.py +++ b/tests/test_cliff.py @@ -230,3 +230,29 @@ def test_trailing_key_value_lines_are_dropped_as_trailers(repo: Repo) -> None: assert " Rename the call." in notes assert "Before:" not in notes assert "After:" not in notes + + +def test_pr_numbers_link_to_the_repository_of_github_repository( + repo: Repo, monkeypatch: pytest.MonkeyPatch +) -> None: + repo.commit("chore: start") + repo.git("tag", "v0.0.0") + repo.commit("feat: ✨ add X (#12)") + repo.commit("fix(io): handle #3 and (#4) in names (#13)") + repo.commit("fix: pushed without a PR") + monkeypatch.setenv("GITHUB_REPOSITORY", "Algorithmiq/example") + notes = cliff("--unreleased").splitlines() + url = "https://github.com/Algorithmiq/example/pull" + # Only the PR number squash-merging appends becomes a link; the rest stays as it is. + assert [line for line in notes if line.startswith("- ")] == [ + f"- Add X ([#12]({url}/12))", + f"- **io:** Handle #3 and (#4) in names ([#13]({url}/13))", + "- Pushed without a PR", + ] + + +def test_pr_numbers_stay_plain_without_github_repository(repo: Repo) -> None: + repo.commit("chore: start") + repo.git("tag", "v0.0.0") + repo.commit("feat: ✨ add X (#12)") + assert "- Add X (#12)" in cliff("--unreleased").splitlines()