Spotted by @sir-sigurd while reviewing #5180.
In Package._push (api/python/quilt3/packages.py):
pkg = self.__class__()
pkg._meta = self._meta # same dict object, not a copy
pkg._set_commit_message(message)
_set_commit_message does self._meta.update({'message': msg}), so it rewrites the metadata of the package push() was called on, not just the new one.
Observable effect
push() silently changes the caller's top hash:
pkg = quilt3.Package()
before = pkg.top_hash # no 'message' key
pkg.push("Quilt/test", "s3://bucket")
after = pkg.top_hash # meta now carries 'message', so a different hash
assert before == after # fails
This bit me while writing regression tests for #5180: a candidate hash computed before a push does not match the hash that gets published, because message (even None) lands in the shared dict in between. Tests have to read the hash back off the returned package to work around it.
Fix
Copy the dict: pkg._meta = copy.deepcopy(self._meta) (or a shallow copy plus a copied user_meta).
Worth doing on its own rather than inside #5180, since it changes the top hash of a package object across a push and could shift hashes that tests or callers depend on.
Spotted by @sir-sigurd while reviewing #5180.
In
Package._push(api/python/quilt3/packages.py):_set_commit_messagedoesself._meta.update({'message': msg}), so it rewrites the metadata of the packagepush()was called on, not just the new one.Observable effect
push()silently changes the caller's top hash:This bit me while writing regression tests for #5180: a candidate hash computed before a push does not match the hash that gets published, because
message(evenNone) lands in the shared dict in between. Tests have to read the hash back off the returned package to work around it.Fix
Copy the dict:
pkg._meta = copy.deepcopy(self._meta)(or a shallow copy plus a copieduser_meta).Worth doing on its own rather than inside #5180, since it changes the top hash of a package object across a push and could shift hashes that tests or callers depend on.