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
33 changes: 31 additions & 2 deletions src/Serialization/Upgrades/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,15 @@ function upgrade_recursive(upgrade::Function, s::UpgradeState, dict::AbstractDic
elseif type_name == "Tuple"
upgraded_entries = Dict{Symbol, Any}[]
upgraded_entry = nothing

# catch empty tuples here before zip
!haskey(dict, :data) && return dict
for (type, entry) in zip(dict[:_type][:params], dict[:data])
upgraded_entry = upgrade(s, Dict{Symbol, Any}(:_type => type, :data => entry))
push!(upgraded_entries, upgraded_entry)
end
dict[:_type][:params] = [u_e[:_type] for u_e in upgraded_entries]
dict[:data] = [u_e[:data] for u_e in upgraded_entries]

elseif type_name == "NamedTuple"
upgraded_entries = Dict{Symbol, Any}[]
upgraded_entry = nothing
Expand Down Expand Up @@ -314,6 +316,32 @@ include("1.8.0+1.jl")
const upgrade_scripts = collect(upgrade_scripts_set)
sort!(upgrade_scripts; by=version)

# For DEV versions like "1.8.0-DEV-1-hash", the n_upgrades count tells us how many
# upgrade scripts for that base version existed when the file was saved — meaning those
# scripts were already applied. Map to the version of the n-th script so upgrade()
# correctly skips already-applied scripts.
#
# Julia parses "1.8.0-DEV-1-hash" with prerelease = ("DEV-1-hash",) as a single
# identifier, so we split on "-" to extract n_upgrades.
function effective_upgrade_version(format_version::VersionNumber)
pre = format_version.prerelease
isempty(pre) && return format_version

# pre[1] has format "DEV-<n_upgrades>-<hash>" or "DEV-<hash>" (n_upgrades=0)
pre_str = string(only(pre))
startswith(pre_str, "DEV-") || return format_version

parts = split(pre_str, "-")
length(parts) < 3 && return format_version # no n_upgrades encoded

n_upgrades = tryparse(Int, string(parts[2]))
(isnothing(n_upgrades) || iszero(n_upgrades)) && return format_version

base = Base.thispatch(format_version)
base_num = findfirst(s -> version(s) == base, upgrade_scripts)
return version(upgrade_scripts[base_num+n_upgrades-1])
end

################################################################################
# Loading with upgrade checks on dict
const backref_sym = Symbol("#backref")
Expand All @@ -327,9 +355,10 @@ has been achieved.
"""
function upgrade(format_version::VersionNumber, dict::AbstractDict{Symbol, Any})
upgraded_dict = dict
eff_version = effective_upgrade_version(format_version)
for upgrade_script in upgrade_scripts
script_version = version(upgrade_script)
if format_version < script_version
if eff_version < script_version
# TODO: use a macro from Hecke that will allow user to suppress
# such a message
@debug("upgrading serialized data....",
Expand Down
7 changes: 6 additions & 1 deletion src/Serialization/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,12 @@ function load(io::IO; params::Any = nothing, type::Any = nothing,
file_version = load_node(s) do obj
serialization_version_info(obj)
end
if file_version < VERSION_NUMBER
# A file needs upgrading iff some upgrade script targets a version newer than the
# file's effective version. Gate on the newest upgrade script rather than on
# VERSION_NUMBER: for DEV builds the file's version string carries a commit hash
# (e.g. "1.8.0-DEV-<hash>"), and comparing that against VERSION_NUMBER orders the two
# by hash characters rather than by upgrade state, which can wrongly skip upgrades.
if effective_upgrade_version(file_version) < version(last(upgrade_scripts))
# we need a mutable dictionary
jsondict = copy(s.obj)
jsondict = upgrade(file_version, jsondict)
Expand Down
2 changes: 1 addition & 1 deletion test/Serialization/upgrades/setup_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if !isdefined(Main, :serialization_upgrade_test_path) ||
!isfile(joinpath(Main.serialization_upgrade_test_path, "LICENSE.md"))


serialization_upgrade_test_path = let commit_hash = "e3473ad614217bc19fad7f809de2d863984be157"
serialization_upgrade_test_path = let commit_hash = "46416710289058b38fd69fecb0809905e7b667bb"
tarball = Downloads.download("https://github.com/oscar-system/serialization-upgrade-tests/archive/$(commit_hash).tar.gz")

destpath = open(CodecZlib.GzipDecompressorStream, tarball) do io
Expand Down
Loading