muse branch -c and -m: reflog copy and rename parity with git
muse branch -c / -m — reflog parity with git for copy and rename
Background
muse branch supports the full git flag surface — create, copy (-c/-C), rename (-m/-M), and delete (-d/-D). Branch create and delete are already handled correctly: create writes a branch: Created from <source> reflog entry; delete removes the reflog file. Copy and rename both have gaps relative to git's behaviour.
What git branch -c does that muse does not
git branch -c [<src>] <dest> does two things:
- Creates
<dest>at the same commit as<src>. - Copies the source branch's reflog (
.git/logs/refs/heads/<src>) to the destination (.git/logs/refs/heads/<dest>), so the new branch appears to have inherited the full history of pointer movements from the source.
Muse's implementation (lines 636–680 of muse/cli/commands/branch.py) does step 1 but skips step 2 entirely, and also writes no reflog entry for the newly created branch. A branch created by copy has an empty reflog and no trace in the destination log that it came from anywhere.
What git branch -m does that muse does not
git branch -m [<old>] <new> does two things:
- Moves the branch ref (
refs/heads/<old>→refs/heads/<new>). - Moves the reflog file (
.git/logs/refs/heads/<old>→.git/logs/refs/heads/<new>), preserving the full history under the new name. If the renamed branch wasHEAD, it also writes a new entry to the HEAD log recording the rename.
Muse's implementation (lines 540–635 of muse/cli/commands/branch.py) does step 1 but leaves the old log file at the old path (orphaned), creates no log at the new path, and writes no reflog entry. After muse branch -m old new, muse reflog --branch new returns no entries even though the branch may have a rich history.
Clarification: -c does not check out
git branch -c and muse's -c both create without checking out — this is correct and intentional. Checkout-on-create is the job of muse checkout -b and muse switch -c, both of which already work correctly.
Goal
After this issue is complete:
muse branch -c src dstcopiessrc's reflog todstand writes abranch: Copied from srcentry at the top of the destination log.muse branch -m old newmoves the reflog file fromoldtonewand writes abranch: Renamed from oldentry at the top of the destination log.- When
muse branch -mrenames the currently checked-out branch, abranch: Renamed from old to newentry is also written to the HEAD log. muse branch -C(force copy) andmuse branch -M(force rename) carry the same reflog semantics as their non-force counterparts.- All operations are safe with respect to partial failure: log files are written atomically and the branch ref is never left in an inconsistent state with the log.
Phases
Phase 1 — muse branch -c/-C: copy the reflog
When copying a branch, copy the source reflog and prepend a new entry marking the origin.
Semantics
# Before
.muse/logs/refs/heads/dev ← 12 entries (full dev history)
.muse/logs/refs/heads/feat/x ← does not exist
muse branch -c dev feat/x
# After
.muse/logs/refs/heads/dev ← unchanged (12 entries)
.muse/logs/refs/heads/feat/x ← 13 entries: 12 copied from dev +
new entry "branch: Copied from dev"
prepended at the top (newest-first means
it will appear as @{0} in muse reflog)
The new entry at the top uses old_id = <null> (copy has no predecessor on the destination ref) and new_id = <tip of src>.
Deliverables
BC_01—muse branch -c src dstcopies.muse/logs/refs/heads/srcto.muse/logs/refs/heads/dst; the copy is byte-for-byte identical except for the prepended entryBC_02— The prepended entry has operationbranch: Copied from <src>,new_id = tip of src,old_id = null IDBC_03—muse reflog --branch dst --jsonafter copy returnsentrieswith the prepended entry at index 0 and the source history followingBC_04—muse branch -C src dst(force copy, overwriting an existing branch) replaces the destination's reflog with the copied log; the previous destination log is not preservedBC_05— If the source branch has no reflog (new repo, never committed), copy writes just the single origin entry with no prior historyBC_06— Reflog copy is atomic: written to a tmp file and renamed over the destination — if the process is interrupted after the ref write but before the log write, the branch is still valid (just missing the log)BC_07— Integration test: dev branch with 5 reflog entries →muse branch -c dev backup/dev→muse reflog --branch backup/dev --jsonshows 6 entries (5 copied + 1 origin marker)
Test file: tests/test_cmd_branch_copy_reflog.py
Phase 2 — muse branch -m/-M: move the reflog
When renaming a branch, move its reflog file and write a rename entry.
Semantics
# Before
.muse/logs/refs/heads/feat/long-name ← 8 entries
muse branch -m feat/long-name feat/short
# After
.muse/logs/refs/heads/feat/long-name ← deleted
.muse/logs/refs/heads/feat/short ← 9 entries: 8 moved + new rename entry
If the renamed branch is currently checked-out (HEAD points at it), also write to the HEAD log:
# HEAD log gains:
sha256:<tip> sha256:<tip> gabriel <ts> +0000\tbranch: Renamed from feat/long-name to feat/short
Note: old and new ID are identical (the commit didn't change, only the branch name).
Deliverables
BC_08—muse branch -m old newmoves.muse/logs/refs/heads/oldto.muse/logs/refs/heads/new; old path is removedBC_09— The new log has a prepended entry with operationbranch: Renamed from <old>, same new_id and old_id (both equal to the branch tip)BC_10—muse reflog --branch new --jsonafter rename shows all prior entries (with updated path only — content unchanged) plus the rename entry at index 0BC_11— When renaming the currently checked-out branch, abranch: Renamed from <old> to <new>entry is appended to the HEAD logBC_12—muse branch -M old new(force rename) carries identical reflog semantics; the destination's prior log (if any) is discardedBC_13— Move is atomic: source log is not removed until the destination log write succeeds; if the destination write fails the source log is preservedBC_14— Parent directories for the destination path are created if needed (e.g. renamingdevtoarchive/old/dev)BC_15— Integration test: rename a 5-entry branch →muse reflog --branch new --jsonshows 6 entries → rename the currently checked-out branch → HEAD log gains one entry
Test file: tests/test_cmd_branch_rename_reflog.py
Phase 3 — muse branch operation entry standardisation
Audit all append_reflog calls in branch.py and checkout.py to ensure operation strings are consistent and complete.
Current state (from code inspection)
| Operation | append_reflog called? | Operation string |
|---|---|---|
branch <name> (create) |
✅ | branch: Created from <source> |
branch -c (copy) |
❌ | — |
branch -m (rename) |
❌ | — |
branch -d (delete) |
n/a — log file removed | — |
checkout -b |
✅ (via checkout.py) | branch: created from <source> |
After Phase 3, every branch mutation (copy, rename) writes a reflog entry in addition to Phases 1 and 2. The operation string format across all branch operations is standardised:
| Operation | Standard string |
|---|---|
| Create | branch: created from <source> |
| Copy | branch: copied from <src> |
| Rename | branch: renamed from <old> |
Deliverables
BC_16— Operation strings standardised to lowercase-first, present-tense form acrossbranch.pyandcheckout.pyBC_17— Audit test: for each branch mutation,muse reflog --jsoncontains an entry whoseoperationstarts withbranch:and is in the correct standard formBC_18— No regression: existing tests intests/test_cmd_branch.pyandtests/test_cmd_reflog*.pyremain green
Test file: additions to tests/test_cmd_branch.py and existing reflog test files
Acceptance Criteria
muse branch -c src dst→muse reflog --branch dstshows the source's full prior history plus abranch: copied from srcentry.muse branch -m old new→muse reflog --branch newshows the branch's full prior history plus abranch: renamed from oldentry; the old log path is gone.- Renaming the checked-out branch writes to both the branch log and the HEAD log.
- All 18 test IDs (
BC_01–BC_18) are green. muse code test --jsonfor changed files reports no regressions.
Out of Scope
- Remote branch copy/rename (no reflog is maintained for remote-tracking refs).
muse branch --deletelog archival — the current behaviour (delete the log on branch delete) is intentional.- Reflog expiry for orphaned log files — covered by the companion reflog lifecycle issue.
muse switch -c/muse checkout -b— these already write correct reflog entries.