feat: muse#54 item 4 — remote reflog sharing via push/pull
Reflogs were 100% local-only until now: a fresh clone started with zero ref-movement history. Implements remote sharing by mirroring the existing version-tags remote-sync precedent (muse#57 item 4) rather than embedding reflogs in the content-addressed mpack format, since reflog entries -- like version tags -- are per-ref metadata, not content-addressed objects.
Muse CLI: - `muse push --reflog` (or `push.reflog=true` config, new [push] key alongside the existing `tags`) sends this branch's local reflog entries via transport.push_reflog(), oldest-first, resolved before the "up to date" early return so entries sync even with no new commits (same reasoning as --tags). - `muse pull` always best-effort fetches via transport.fetch_reflog() and merges incoming entries into a NEW remote-tracking namespace, `.muse/logs/refs/remotes/<remote>/<branch>` (new path helpers reflog_remotes_dir/reflog_remote_path) -- never into the local branch's own reflog. This is what "namespaced by remote" means: mirrors git's own separation between a branch's reflog and its remote-tracking ref's reflog, so a remote's history can merge in without colliding with purely local activity. - muse/core/reflog.py: reflog_entries_to_wire() normalizes IDs to canonical sha256:<hex> form at the wire boundary; merge_remote_reflog_entries() does idempotent (exact-raw-line-comparison) append-only merging; read_remote_reflog() mirrors read_reflog()'s contract for the new namespace. - muse/core/transport.py: push_reflog()/fetch_reflog() on both the Protocol and HttpTransport, same POST/GET-with-msgpack-body shape as push_version_tags()/fetch_version_tags(). - `muse reflog --remote <name> --branch <branch>` reads the merged remote-tracking log back; --remote requires --branch (a remote's reflog is tracked per-branch) and is mutually exclusive with --tag/--all; remote names are checked for path-traversal characters before use in a filesystem path, same security model as --branch. - _PushJson gained always-present reflog_pushed/reflog_skipped fields (0/0 when --reflog isn't used) rather than a combinatorial subclass alongside the existing _PushWithTagsJson, since --tags and --reflog are independent flags that can combine four ways.
MuseHub server (~/ecosystem/musehub, shipped as a companion commit): - New `musehub_reflog_entries` table (migration 0080), keyed by a content-hash `entry_id` rather than a mutable (repo_id, branch) pointer like version tags -- re-pushing an already-stored entry is a true `ON CONFLICT DO NOTHING` no-op, since reflog entries are immutable history with nothing to conflict (unlike a tag, which can legitimately re-point to a different commit). - `musehub/services/musehub_reflog.py`: store_reflog_entries()/ list_reflog_entries(), sole owner of the table. - `wire_push_reflog`/`wire_get_reflog` routes on the existing bare-path wire.py router, using the new BranchQueryParam validator (branch isn't part of the URL path template here, unlike the existing Path-based BranchParam used elsewhere). The read route additionally calls _assert_readable (a gap the version-tags read route has today, deliberately not copied here since reflog entries can reveal private branch activity).
Covered by tests/test_reflog_remote_sharing.py (15 cases: wire normalization, core merge idempotency/isolation-per-remote/no-local- pollution, CLI --reflog flag + config flag + transport-not-called when disabled, pull merge + non-fatal fetch-failure handling, and `reflog --remote` read-back including validation errors) plus tests/test_musehub_reflog_service.py (6 cases, musehub repo: round-trip, dedup-skip, per-branch isolation, ordering, empty-batch, missing-new_id).
Confirmed genuine RED by reverting every touched muse-side file and re-running (ImportError: reflog_remote_path doesn't exist), then GREEN with the fix restored plus the full targeted regression suite (296 muse-side tests across push/pull/reflog/config areas, zero regressions; 80 musehub-side tests including migrations + ORM/schema-match checks). Live-verified end-to-end against the real local MuseHub hub (containers restarted, migration 0080 auto-applied, schema check passed): created a scratch repo, pushed two commits' reflog entries with --reflog (first attempt stored 2, idempotent re-push correctly skipped 2), then pulled from a fresh clone and confirmed the remote-tracking log (.muse/logs/refs/remotes/local/main) received the exact entries while the local branch's own reflog stayed untouched. Scratch repo deleted after verification.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Semantic Changes
96 symbols
Files Changed
+1
~8
1231 in snapshot
0 comments
muse hub commit comment sha256:bbaa4948a0e72cfa68f5d62f76f176f0c11153b513826d6fc84df500a11db79c --body "your comment"
No comments yet. Be the first to start the discussion.