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
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,11 @@ caller's `pylock.toml`.

`nab-git-commit` is a temporary input of `refresh-release-lock`, `validate-news`, `check-tagged-source`,
`prepare-towncrier-release.yaml`, `upgrade-release-tools.yaml`, and `validate-python-release.yaml`. It is empty by
default. When set, it must be a full 40-character commit SHA, and Nab is installed from
`git+https://github.com/notatallshaw/nab@<sha>` instead of the release. That commit gets no cooldown: `exclude-newer`
applies only to its dependencies. It exists until a Nab release carries the needed fix and will be removed then.
default. When set, it must be a full 40-character commit SHA, and the whole Nab workspace is installed from
`git+https://github.com/notatallshaw/nab@<sha>` instead of the release: `nab` from the root, and `nab-index`,
`nab-markersets`, `nab-project`, `nab-provider`, and `nab-resolver` from their `#subdirectory=` paths, because the root
pins them to an unreleased version. That commit gets no cooldown: `exclude-newer` applies only to its dependencies. It
exists until a Nab release carries the needed fix and will be removed then.

## Check pull request news

Expand Down Expand Up @@ -178,10 +180,12 @@ configurations, which no guard checks. `prepare-towncrier` and `validate-release
anywhere, a `.pre-commit-config.yml`, and a `.pre-commit-config.yaml` below the root before they run `prek`.

The shared prepare and tagged validator run `refresh-release-lock` in `check` mode before release checks or publishing.
`check-tagged-source` bundles the tagged checks for a custom tag publisher: it requires `GITHUB_REF` to be the tag of
the checkout on `main` and checks the locks. It runs no hooks. It takes the secondary lock inputs below and needs a
full-history checkout. For a secondary Nab lock, pass its path, selected groups, selection type, and matrix inputs to
both workflows. A stale primary or secondary lock stops the release before artifacts are built.
`check-tagged-source` bundles the tagged checks for a custom tag publisher: on a tag ref it requires the tag to name
both `GITHUB_SHA` and the checkout, on `main`, and checks the locks. Any other event, such as a `workflow_dispatch` from
`main` with a `tag` input, must check out the commit that `inputs.tag` names, on `main`. It runs no hooks. It takes the
secondary lock inputs below and needs a full-history checkout. For a secondary Nab lock, pass its path, selected groups,
selection type, and matrix inputs to both workflows. A stale primary or secondary lock stops the release before
artifacts are built.

For example, a tagged source job that also publishes from a PGO lock checks both files before installing build tools:

Expand Down
3 changes: 3 additions & 0 deletions changelog.d/+dispatch-tag-check.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Let `check-tagged-source` accept a `workflow_dispatch` or other non-tag run, such as one from `main` with a `tag` input,
when the checkout is the commit that tag names and that commit is on `main`. Tag runs still require the tag,
`GITHUB_SHA`, and the checkout to match.
2 changes: 2 additions & 0 deletions changelog.d/+nab-workspace.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Install the whole Nab workspace from the `nab-git-commit` commit: `nab` and its `nab-index`, `nab-markersets`,
`nab-project`, `nab-provider`, and `nab-resolver` sub-packages, which the root pins to an unreleased version.
13 changes: 9 additions & 4 deletions check-tagged-source/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ runs:
- name: Verify the exact release tag
shell: bash
env:
TAG: ${{ github.ref_name }}
TAG: ${{ github.event.inputs.tag || github.ref_name }}
run: |
set -euo pipefail
test "$GITHUB_REF" = "refs/tags/$TAG"
git check-ref-format "refs/tags/$TAG"
test "$(git rev-parse "refs/tags/$TAG^{commit}")" = "$GITHUB_SHA"
test "$GITHUB_SHA" = "$(git rev-parse HEAD)"
commit=$(git rev-parse "refs/tags/$TAG^{commit}")
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
test "$GITHUB_REF" = "refs/tags/$TAG"
test "$commit" = "$GITHUB_SHA"
test "$GITHUB_SHA" = "$(git rev-parse HEAD)"
else
test "$commit" = "$(git rev-parse HEAD)"
fi
git merge-base --is-ancestor HEAD origin/main
- uses: $/refresh-release-lock
with:
Expand Down
12 changes: 9 additions & 3 deletions refresh-release-lock/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,15 @@ runs:
NAB_GIT_COMMIT: ${{ inputs.nab-git-commit }}
run: |
set -euo pipefail
requirement="nab>=0.0.22"
if [[ -n "$NAB_GIT_COMMIT" ]]; then requirement=git+https://github.com/notatallshaw/nab@$NAB_GIT_COMMIT; fi
"$GITHUB_ACTION_PATH/../tools/install_live_tools.sh" "$RUNNER_TEMP/lock-tools" "$requirement"
requirements=("nab>=0.0.22")
if [[ -n "$NAB_GIT_COMMIT" ]]; then
source=git+https://github.com/notatallshaw/nab@$NAB_GIT_COMMIT
requirements=("nab @ $source")
for package in nab-index nab-markersets nab-project nab-provider nab-resolver; do
requirements+=("$package @ $source#subdirectory=$package")
done
fi
"$GITHUB_ACTION_PATH/../tools/install_live_tools.sh" "$RUNNER_TEMP/lock-tools" "${requirements[@]}"
- name: Resolve all project groups and extras
shell: bash
env:
Expand Down
28 changes: 20 additions & 8 deletions tests/refresh_release_lock/test_refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,30 @@ def test_refresh_rejects_invalid_nab_commit(tmp_path: Path, action_scripts: Path
assert "nab-git-commit must be a full 40-character commit SHA" in result.stderr


_NAB_COMMIT: Final = "0123456789abcdef0123456789abcdef01234567"
_NAB_SOURCE: Final = f"git+https://github.com/notatallshaw/nab@{_NAB_COMMIT}"


@pytest.mark.parametrize(
("commit", "requirement"),
("commit", "requirements"),
[
pytest.param("", "nab>=0.0.22", id="release"),
pytest.param("0123456789abcdef0123456789abcdef01234567", None, id="git-commit"),
pytest.param("", ["nab>=0.0.22"], id="release"),
pytest.param(
_NAB_COMMIT,
[
f"nab @ {_NAB_SOURCE}",
f"nab-index @ {_NAB_SOURCE}#subdirectory=nab-index",
f"nab-markersets @ {_NAB_SOURCE}#subdirectory=nab-markersets",
f"nab-project @ {_NAB_SOURCE}#subdirectory=nab-project",
f"nab-provider @ {_NAB_SOURCE}#subdirectory=nab-provider",
f"nab-resolver @ {_NAB_SOURCE}#subdirectory=nab-resolver",
],
id="git-commit-workspace",
),
],
)
def test_refresh_installs_selected_nab(
tmp_path: Path, action_scripts: Path, commit: str, requirement: str | None
tmp_path: Path, action_scripts: Path, commit: str, requirements: list[str]
) -> None:
installer = tmp_path / "tools/install_live_tools.sh"
installer.parent.mkdir()
Expand All @@ -251,10 +266,7 @@ def test_refresh_installs_selected_nab(
)

assert result.returncode == 0, result.stderr
assert (tmp_path / "installed").read_text().splitlines() == [
f"{tmp_path}/lock-tools",
requirement or f"git+https://github.com/notatallshaw/nab@{commit}",
]
assert (tmp_path / "installed").read_text().splitlines() == [f"{tmp_path}/lock-tools", *requirements]


@pytest.mark.parametrize(
Expand Down
54 changes: 51 additions & 3 deletions tests/validate_python_release/test_tagged.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ def test_validator_rejects_backend_hash_mismatch(
[
pytest.param("lightweight", "refs/tags/0.2.0", True, id="lightweight-tag"),
pytest.param("annotated", "refs/tags/0.2.0", True, id="annotated-tag"),
pytest.param("lightweight", "refs/heads/0.2.0", False, id="branch-event-on-tagged-commit"),
pytest.param("branch", "refs/tags/0.2.0", False, id="same-name-branch"),
pytest.param("off-main", "refs/tags/0.2.0", False, id="tagged-off-main-commit"),
],
Expand Down Expand Up @@ -163,6 +162,55 @@ def test_validator_requires_exact_tag(
assert (result.returncode == 0) is accepted, result.stderr


@pytest.mark.parametrize(
("location", "accepted"),
[
pytest.param("check-tagged-source/action.yml", True, id="source-check"),
pytest.param("validate-python-release:build", False, id="build"),
pytest.param("release:source", False, id="self-release"),
],
)
def test_validator_branch_event_on_tagged_commit(repository: Path, location: str, *, accepted: bool) -> None:
run(repository, "git", "tag", "0.2.0")

result = _run_tag_check(repository, location, "refs/heads/main", run(repository, "git", "rev-parse", "HEAD").stdout)

assert (result.returncode == 0) is accepted, result.stderr


def test_source_check_rejects_dispatch_off_the_tag(repository: Path) -> None:
run(repository, "git", "tag", "0.2.0")
run(repository, "git", "commit", "--allow-empty", "-m", "Later change")
run(repository, "git", "push", "origin", "main")

result = _run_tag_check(
repository,
"check-tagged-source/action.yml",
"refs/heads/main",
run(repository, "git", "rev-parse", "HEAD").stdout,
)

assert result.returncode != 0


def test_source_check_reads_the_dispatched_tag() -> None:
assert (
_tag_check("check-tagged-source/action.yml")["env"]["TAG"]
== "${{ github.event.inputs.tag || github.ref_name }}"
)


def _run_tag_check(repository: Path, location: str, event_ref: str, event_sha: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["bash", "-c", _tag_check(location)["run"]],
cwd=repository,
env=os.environ | {"GITHUB_REF": event_ref, "TAG": "0.2.0", "GITHUB_SHA": event_sha.strip()},
text=True,
capture_output=True,
check=False,
)


@pytest.mark.parametrize(
("tag", "filenames", "accepted"),
[
Expand Down Expand Up @@ -225,11 +273,11 @@ def test_validator_requires_tag_at_the_event_commit(repository: Path, location:
assert result.returncode != 0


def _tag_check(location: str) -> dict[str, str]:
def _tag_check(location: str) -> dict[str, Any]:
root = Path(__file__).resolve().parents[2]
if location.endswith(".yml"):
steps = yaml.safe_load((root / location).read_text())["runs"]["steps"]
else:
workflow, job = location.split(":")
steps = yaml.safe_load((root / ".github/workflows" / f"{workflow}.yaml").read_text())["jobs"][job]["steps"]
return cast("dict[str, str]", next(step for step in steps if step.get("name") == "Verify the exact release tag"))
return cast("dict[str, Any]", next(step for step in steps if step.get("name") == "Verify the exact release tag"))
Loading