fix(update): drop the ZIP swap owner lock file once settled; test the deferred fork push
(cherry picked from commit f45a0b340a060ea349ebb45b5cd6d98954a7900f) (cherry picked from commit ded3fd987bf02311898515c7e4d4d857d23bf53b)
This commit is contained in:
@@ -440,12 +440,17 @@ _ZIP_STAGING_SUFFIX, _ZIP_OLD_SUFFIX = ".hermes-update-staging", ".hermes-update
|
||||
|
||||
@contextlib.contextmanager
|
||||
def zip_swap_owner_lock(root: Path, *, wait: float = 0.0):
|
||||
"""Yields True while this process owns the ZIP swap lock, False when a live owner holds it."""
|
||||
"""Yields True while this process owns the ZIP swap lock, False when a live owner holds it.
|
||||
|
||||
The lock file is removed again once no journal is left, so a settled install carries no
|
||||
breadcrumb (the swap itself also runs under the update lock; this one only proves liveness)."""
|
||||
lock_path = Path(root) / (ZIP_SWAP_JOURNAL + ".lock")
|
||||
try:
|
||||
fd = os.open(Path(root) / (ZIP_SWAP_JOURNAL + ".lock"), os.O_RDWR | os.O_CREAT, 0o644)
|
||||
fd = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o644)
|
||||
except OSError:
|
||||
yield True # unwritable root: no swap can run there either
|
||||
return
|
||||
owned = False
|
||||
try:
|
||||
deadline = time.monotonic() + wait
|
||||
while not _lock_fd(fd, True):
|
||||
@@ -453,12 +458,16 @@ def zip_swap_owner_lock(root: Path, *, wait: float = 0.0):
|
||||
yield False
|
||||
return
|
||||
time.sleep(0.05)
|
||||
owned = True
|
||||
try:
|
||||
yield True
|
||||
finally:
|
||||
_lock_fd(fd, False)
|
||||
finally:
|
||||
os.close(fd)
|
||||
if owned and not (Path(root) / ZIP_SWAP_JOURNAL).exists():
|
||||
with contextlib.suppress(OSError):
|
||||
lock_path.unlink()
|
||||
|
||||
|
||||
def write_zip_swap_journal(root: Path, phase: str, entries: list) -> None:
|
||||
|
||||
@@ -149,9 +149,11 @@ def test_update_and_upstream_network_calls_disable_terminal_prompts(monkeypatch,
|
||||
assert update_cmd_git._sync_with_upstream_if_needed(["git"], tmp_path, assume_yes=True)
|
||||
from hermes_cli.update_custody import git_subcommand
|
||||
|
||||
# The fork's fast-forward is local (`merge --ff-only upstream/main` after the fetch): no
|
||||
# credential helper ever runs under the checkout lock fd a mutator inherits (R2).
|
||||
network = [(args, kwargs) for args, kwargs in calls if git_subcommand(args) != "rev-parse"]
|
||||
# The fork push runs only after the update is validated (_push_synced_fork), never inside the sync.
|
||||
update_cmd_git._sync_fork_with_upstream(["git"], tmp_path)
|
||||
# The fork's fast-forward is local (`merge --ff-only` after the fetch): no credential helper ever
|
||||
# runs under the checkout lock fd a mutator inherits (R2).
|
||||
network = [(args, kwargs) for args, kwargs in calls if git_subcommand(args) in {"fetch", "pull", "push", "merge"}]
|
||||
assert [git_subcommand(args) for args, _ in network] == ["fetch", "fetch", "merge", "push"]
|
||||
assert network[2][0][-1] == upstream # the counted commit, not a name a local branch can shadow
|
||||
for args, kwargs in network:
|
||||
|
||||
Reference in New Issue
Block a user