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
97 changes: 74 additions & 23 deletions .github/workflows/Registrator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,15 @@ jobs:
end
end

# Keep `valid_bump` in sync with the copy in VersionCheck.yml — the two
# workflows share the same bump-shape rules but cannot share Julia code
# across reusable workflow YAML without a heavier composite-action setup.
function valid_bump(o::VersionNumber, n::VersionNumber)
# Strip-suffix release transition: same M.m.p, old had pre-release, new doesn't.
if n.major == o.major && n.minor == o.minor && n.patch == o.patch &&
!isempty(o.prerelease) && isempty(n.prerelease)
return true, "stripping pre-release suffix"
end
# Only called when n > o
if n.major == o.major && n.minor == o.minor
return (n.patch == o.patch + 1), "expected patch bump by 1"
Expand All @@ -113,6 +121,41 @@ jobs:
end
end

# Resolve the registration route ("general" if the package's UUID is
# already in the General registry, "local" otherwise).
function register_route(uuid)
ENV["JULIA_PKG_SERVER"] = ""
Pkg.Registry.add("General")
general_toml = joinpath(first(DEPOT_PATH), "registries", "General", "Registry.toml")
general = TOML.parsefile(general_toml)
return haskey(get(general, "packages", Dict{String,Any}()), uuid) ? "general" : "local"
end

# Walk the git history of `project_path` on the current ref and return
# the most recent VersionNumber whose `prerelease` is empty (i.e., the
# most recent actually-released version), or `nothing` if there is none.
function find_last_released_version(repo_path, project_path)
log_output = try
readlines(`git -C $repo_path log --pretty=%H HEAD -- $project_path`)
catch
return nothing
end
for sha in log_output
try
toml_text = read(`git -C $repo_path show $sha:$project_path`, String)
v_str = get(TOML.parse(toml_text), "version", "")
isempty(v_str) && continue
v = VersionNumber(v_str)
if isempty(v.prerelease)
return v
end
catch
continue
end
end
return nothing
end

let
subdir = get(ENV, "SUBDIR", "")
project_path = isempty(subdir) ? "Project.toml" : joinpath(subdir, "Project.toml")
Expand Down Expand Up @@ -154,48 +197,56 @@ jobs:
# For issue_comment events, always attempt registration (skip version-change guards).
is_force = get(ENV, "GITHUB_EVENT_NAME", "") == "issue_comment"

# Parse old version once up front; used by both the pre-release skip
# branch (for shape validation) and the normal registration branch.
oldv = isempty(oldv_str) ? nothing : parse_version(oldv_str, "old")

route = "none"
is_breaking = false
skip_reason = ""

if !isempty(oldv_str)
oldv = parse_version(oldv_str, "old")

# Skip registration entirely while the version carries a pre-release
# suffix (e.g., "0.22.0-DEV"): the package is accumulating breaking
# changes toward an eventual release. The release PR strips the
# suffix and is registered normally. is_force (manual /register)
# bypasses this and registers the pre-release version. Even on the
# skip path, validate the bump shape when entering pre-release mode
# so a malformed transition (e.g., 0.21.5 -> 0.99.0-DEV) is caught.
if !isempty(newv.prerelease) && !is_force
if oldv !== nothing && newv > oldv
ok, why = valid_bump(oldv, newv)
ok || error("Invalid version bump: $oldv_str -> $newv_str ($why)")
end
skip_reason = "pre-release version $newv_str; not registering during accumulation"
elseif oldv !== nothing
if newv == oldv && !is_force
route = "none"
skip_reason = "Project.toml version unchanged ($newv_str)"
elseif newv < oldv && !is_force
route = "none"
skip_reason = "Project.toml version decreased ($oldv_str -> $newv_str); skipping registration"
else
if newv > oldv
ok, why = valid_bump(oldv, newv)
is_force || ok || error("Invalid version bump: $oldv_str -> $newv_str ($why)")

# When transitioning out of pre-release (strip-suffix release PR),
# compare against the last actually-released version on this
# branch's history rather than the previous (pre-release) commit
# which would mis-classify the release as non-breaking.
oldv_compare = if !isempty(oldv.prerelease) && isempty(newv.prerelease)
something(find_last_released_version("package", project_path), oldv)
else
oldv
end
# Breaking if: major bump OR (0.x) minor bump
is_breaking = (newv.major > oldv.major) ||
(oldv.major == 0 && newv.major == 0 && newv.minor > oldv.minor)
is_breaking = (newv.major > oldv_compare.major) ||
(oldv_compare.major == 0 && newv.major == 0 && newv.minor > oldv_compare.minor)
end

ENV["JULIA_PKG_SERVER"] = ""
Pkg.Registry.add("General")

general_toml = joinpath(first(DEPOT_PATH), "registries", "General", "Registry.toml")
general = TOML.parsefile(general_toml)
in_general = haskey(get(general, "packages", Dict{String,Any}()), uuid)

route = in_general ? "general" : "local"
route = register_route(uuid)
end
else
# No prior version found to compare against; proceed to route decision.
ENV["JULIA_PKG_SERVER"] = ""
Pkg.Registry.add("General")

general_toml = joinpath(first(DEPOT_PATH), "registries", "General", "Registry.toml")
general = TOML.parsefile(general_toml)
in_general = haskey(get(general, "packages", Dict{String,Any}()), uuid)

route = in_general ? "general" : "local"
route = register_route(uuid)
end

open(ENV["GITHUB_OUTPUT"], "a") do io
Expand Down
31 changes: 30 additions & 1 deletion .github/workflows/VersionCheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ jobs:
run: |
using TOML

# Keep `valid_bump` in sync with the copy in Registrator.yml — the two
# workflows share the same bump-shape rules but cannot share Julia code
# across reusable workflow YAML without a heavier composite-action setup.
function valid_bump(o::VersionNumber, n::VersionNumber)
# Strip-suffix release transition: same M.m.p, old had pre-release, new doesn't.
if n.major == o.major && n.minor == o.minor && n.patch == o.patch &&
!isempty(o.prerelease) && isempty(n.prerelease)
return true, "stripping pre-release suffix"
end
# Only called when n > o
if n.major == o.major && n.minor == o.minor
return (n.patch == o.patch + 1), "expected patch bump by 1"
elseif n.major == o.major
return (n.minor == o.minor + 1 && n.patch == 0), "expected minor bump by 1 with patch reset to 0"
else
return (n.major == o.major + 1 && n.minor == 0 && n.patch == 0), "expected major bump by 1 with minor/patch reset to 0"
end
end

subdir = get(ENV, "SUBDIR", "")
project_path = isempty(subdir) ? "Project.toml" : joinpath(subdir, "Project.toml")

Expand Down Expand Up @@ -103,7 +122,17 @@ jobs:
base_version = VersionNumber(base_project["version"])
current_version = VersionNumber(current_project["version"])

if current_version > base_version
if !isempty(current_version.prerelease) && !isempty(base_version.prerelease) && current_version == base_version
println(
"OK: $project_path version $current_version unchanged from base " *
"(both carry pre-release suffix; accumulating breaking changes)."
)
elseif current_version > base_version
ok, why = valid_bump(base_version, current_version)
ok || error(
"Invalid version bump in $project_path: $base_version (origin/$base_ref) " *
"-> $current_version (PR head): $why."
)
println(
"OK: $project_path version bumped from $base_version " *
"(origin/$base_ref) to $current_version (PR head)."
Expand Down
Loading