function jj-convert --description "Convert a Git repo to a jj workspace layout with a colocated main" # --- Detection --- set -l layout "" set -l primary_dir "" set -l primary_branch "" if test -d .bare set layout bare-worktrees else if not test -e .git; and begin # Find which subdirectory has .git as a directory (the primary) for d in */ if test -d "$d/.git" set primary_dir (string trim --right --chars='/' "$d") break end end test -n "$primary_dir" end set layout main-worktrees else if test -d .git set layout standard end if test -z "$layout" echo "error: unrecognized Git layout" echo "Expected one of:" echo " - standard clone (.git/ directory at root)" echo " - worktrees from a primary subdirectory (no .git at root, subdir/.git/ is a directory)" echo " - bare repo with worktrees (.bare/ directory at root)" return 1 end echo "Detected layout: $layout" # --- Dirty check --- # Directories that survive the conversion only need tracked-file checks. # Directories that get destroyed and recreated need full checks (including untracked). switch $layout case standard # Files are moved, not destroyed — only check tracked modifications set -l status_out (git diff --name-only 2>&1; git diff --cached --name-only 2>&1) if test -n "$status_out" echo "error: working tree has uncommitted tracked changes; commit or stash first" git status --short return 1 end case main-worktrees set -l dirty 0 for d in */ set -l name (string trim --right --chars='/' "$d") if not test -e "$name/.git" continue end if test "$name" = "$primary_dir" # Primary survives — only check tracked modifications set -l status_out (git -C "$name" diff --name-only 2>&1; git -C "$name" diff --cached --name-only 2>&1) if test -n "$status_out" echo "error: primary worktree '$name' has uncommitted tracked changes; commit or stash first" git -C "$name" status --short set dirty 1 end else # Non-primary gets destroyed — check everything set -l status_out (git -C "$name" status --porcelain 2>&1) if test -n "$status_out" echo "error: worktree '$name' is dirty; commit, stash, or clean up first" git -C "$name" status --short set dirty 1 end end end if test $dirty -eq 1 return 1 end case bare-worktrees # Detect primary worktree dir so we know which survives set -l bare_primary_branch (git -C .bare symbolic-ref --short HEAD) set -l bare_primary_dir "" set -l current_path "" set -l is_bare 0 for line in (git worktree list --porcelain) if string match -q 'worktree *' "$line" set current_path (string replace 'worktree ' '' "$line") set is_bare 0 else if test "$line" = bare set is_bare 1 else if string match -q 'branch refs/heads/*' "$line" set -l branch (string replace 'branch refs/heads/' '' "$line") if test "$branch" = "$bare_primary_branch"; and test $is_bare -eq 0 set bare_primary_dir (basename "$current_path") end end end set -l dirty 0 for d in */ set -l name (string trim --right --chars='/' "$d") if not test -e "$name/.git" continue end if test "$name" = "$bare_primary_dir" # Primary survives — only check tracked modifications set -l status_out (git -C "$name" diff --name-only 2>&1; git -C "$name" diff --cached --name-only 2>&1) if test -n "$status_out" echo "error: primary worktree '$name' has uncommitted tracked changes; commit or stash first" git -C "$name" status --short set dirty 1 end else # Non-primary gets destroyed — check everything set -l status_out (git -C "$name" status --porcelain 2>&1) if test -n "$status_out" echo "error: worktree '$name' is dirty; commit, stash, or clean up first" git -C "$name" status --short set dirty 1 end end end if test $dirty -eq 1 return 1 end end # --- Conversion --- switch $layout case standard _jj_convert_standard case main-worktrees _jj_convert_main_worktrees $primary_dir case bare-worktrees _jj_convert_bare_worktrees end end function _jj_convert_standard set -l primary_branch (git symbolic-ref --short HEAD) echo "Primary branch: $primary_branch" mkdir main # Move everything except .git and main into main/ for item in (ls -A) if test "$item" != ".git"; and test "$item" != "main" mv "$item" main/ end end mv .git main/ cd main jj git init --git-repo . echo echo "Converted standard clone → jj workspace layout" echo "Primary workspace: main/ (colocated, branch: $primary_branch)" end function _jj_convert_main_worktrees --argument-names primary_dir set -l primary_branch (git -C "$primary_dir" symbolic-ref --short HEAD) echo "Primary directory: $primary_dir" echo "Primary branch: $primary_branch" # Collect non-primary worktree info set -l wt_names set -l wt_branches set -l current_path "" set -l current_branch "" set -l in_primary 0 for line in (git -C "$primary_dir" worktree list --porcelain) if string match -q 'worktree *' "$line" # Save previous entry if it was non-primary if test -n "$current_path"; and test $in_primary -eq 0; and test -n "$current_branch" set -a wt_names (basename "$current_path") set -a wt_branches "$current_branch" end set current_path (string replace 'worktree ' '' "$line") set current_branch "" # Check if this is the primary if test (basename "$current_path") = "$primary_dir" set in_primary 1 else set in_primary 0 end else if string match -q 'branch refs/heads/*' "$line" set current_branch (string replace 'branch refs/heads/' '' "$line") end end # Don't forget the last entry if test -n "$current_path"; and test $in_primary -eq 0; and test -n "$current_branch" set -a wt_names (basename "$current_path") set -a wt_branches "$current_branch" end # Show what we found echo "Non-primary worktrees:" for i in (seq (count $wt_names)) echo " $wt_names[$i] → $wt_branches[$i]" end # Remove non-primary git worktrees for i in (seq (count $wt_names)) echo "Removing git worktree: $wt_names[$i]" git -C "$primary_dir" worktree remove --force "../$wt_names[$i]" end # Initialize jj in the primary cd "$primary_dir" jj git init --git-repo . # Add jj workspaces named by branch for i in (seq (count $wt_branches)) set -l branch $wt_branches[$i] echo "Adding jj workspace: $branch" jj workspace add "../$branch" jj -R "../$branch" new "$branch" end echo echo "Converted main-primary worktrees → jj workspace layout" echo "Primary workspace: $primary_dir/ (colocated, branch: $primary_branch)" for i in (seq (count $wt_branches)) echo "Workspace: $wt_branches[$i]/ (jj-only)" end end function _jj_convert_bare_worktrees set -l primary_branch (git -C .bare symbolic-ref --short HEAD) echo "Primary branch: $primary_branch" # Collect worktree info and find which dir has the primary branch set -l primary_wt_dir "" set -l wt_names set -l wt_branches set -l current_path "" set -l current_branch "" set -l is_bare 0 for line in (git worktree list --porcelain) if string match -q 'worktree *' "$line" # Save previous entry if test -n "$current_path"; and test $is_bare -eq 0; and test -n "$current_branch" set -l name (basename "$current_path") if test "$current_branch" = "$primary_branch" set primary_wt_dir "$name" else set -a wt_names "$name" set -a wt_branches "$current_branch" end end set current_path (string replace 'worktree ' '' "$line") set current_branch "" set is_bare 0 else if test "$line" = bare set is_bare 1 else if string match -q 'branch refs/heads/*' "$line" set current_branch (string replace 'branch refs/heads/' '' "$line") end end # Last entry if test -n "$current_path"; and test $is_bare -eq 0; and test -n "$current_branch" set -l name (basename "$current_path") if test "$current_branch" = "$primary_branch" set primary_wt_dir "$name" else set -a wt_names "$name" set -a wt_branches "$current_branch" end end if test -z "$primary_wt_dir" echo "error: could not find a worktree on the primary branch ($primary_branch)" return 1 end echo "Primary worktree dir: $primary_wt_dir" echo "Non-primary worktrees:" for i in (seq (count $wt_names)) echo " $wt_names[$i] → $wt_branches[$i]" end # Remove non-primary git worktrees for i in (seq (count $wt_names)) echo "Removing git worktree: $wt_names[$i]" git worktree remove --force "$wt_names[$i]" end # Restructure: un-bare, move .bare into the primary worktree dir git -C .bare config core.bare false rm -rf .bare/worktrees rm "$primary_wt_dir/.git" mv .bare "$primary_wt_dir/.git" rm .git # Rebuild the git index (bare repos don't have one) cd "$primary_wt_dir" git reset # Initialize jj jj git init --git-repo . # Add jj workspaces for former worktrees for i in (seq (count $wt_branches)) set -l branch $wt_branches[$i] echo "Adding jj workspace: $branch" jj workspace add "../$branch" jj -R "../$branch" new "$branch" end echo echo "Converted bare repo with worktrees → jj workspace layout" echo "Primary workspace: $primary_wt_dir/ (colocated, branch: $primary_branch)" for i in (seq (count $wt_branches)) echo "Workspace: $wt_branches[$i]/ (jj-only)" end end