feat(josh-sync): rename --subfolder now git mv's the actual directory

Previously --subfolder only repointed .josh-sync.yml, leaving the working
tree out of sync with the new path until a later filter-change
reconciliation happened to catch it. Now validates the old subfolder
exists and is tracked by git (proof it's actually managed by josh-sync)
and the new one doesn't already exist, then git mv's it — staged, not
committed, alongside the config edit. Idempotent on resume. Bumps to
v2.4.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNXgcFWVgSh3wkQS55cCX6
This commit is contained in:
2026-07-17 11:58:55 +01:00
co-authored by Claude Sonnet 5
parent be4aef588d
commit e5403e4300
6 changed files with 217 additions and 9 deletions
+66
View File
@@ -356,6 +356,11 @@ state_branch_commit_count() {
write_billing_config "$subrepo_bare"
parse_config ".josh-sync.yml"
mkdir -p services/billing
echo "content" > services/billing/file.txt
git add -A
git commit -q -m "seed billing subfolder"
seed_state_commit "" "billing/main.json" '{"last_forward":{"mono_sha":"abc","josh_filter":":/services/billing"}}'
# --force: the seed_state_commit above just wrote a "recent" commit under
@@ -379,4 +384,65 @@ state_branch_commit_count() {
msg=$(git log "origin/${STATE_BRANCH}" -1 --format=%s)
[ "$msg" = "state: rename billing -> payments" ]
[ ! -e services/billing ]
[ -f services/payments/file.txt ]
}
# ─── rename_target: subfolder move (git mv in the monorepo tree) ──
@test "rename_target git mv's the subfolder and dies if the old path isn't tracked" {
setup_state_monorepo
local subrepo_bare
subrepo_bare=$(make_bare_repo "billing-sub")
write_billing_config "$subrepo_bare"
parse_config ".josh-sync.yml"
# services/billing was never created/tracked — not "managed by josh-sync".
run rename_target "billing" "" "services/relocated" "" ".josh-sync.yml" false true false
[ "$status" -ne 0 ]
[[ "$output" == *"does not exist"* ]]
}
@test "rename_target dies when the new subfolder path already exists" {
setup_state_monorepo
local subrepo_bare
subrepo_bare=$(make_bare_repo "billing-sub")
write_billing_config "$subrepo_bare"
parse_config ".josh-sync.yml"
mkdir -p services/billing services/relocated
echo "old" > services/billing/file.txt
echo "existing" > services/relocated/other.txt
git add -A
git commit -q -m "seed"
run rename_target "billing" "" "services/relocated" "" ".josh-sync.yml" false true false
[ "$status" -ne 0 ]
[[ "$output" == *"already exists"* ]]
# nothing should have moved
[ -f services/billing/file.txt ]
}
@test "rename_target moves the subfolder via git mv, staged but not committed" {
setup_state_monorepo
local subrepo_bare
subrepo_bare=$(make_bare_repo "billing-sub")
write_billing_config "$subrepo_bare"
parse_config ".josh-sync.yml"
mkdir -p services/billing
echo "content" > services/billing/file.txt
git add -A
git commit -q -m "seed"
rename_target "billing" "" "services/relocated" "" ".josh-sync.yml" false true false
[ ! -e services/billing ]
[ -f services/relocated/file.txt ]
staged=$(git diff --cached --name-only)
[[ "$staged" == *"relocated/file.txt"* ]]
# Not committed — mirrors the config edit, left for the user to review.
head_files=$(git show --name-only --format= HEAD)
[[ "$head_files" != *"relocated"* ]]
}