refactor(wt): extract cmd.remove module

Amolith created

Assisted-by: Claude Opus 4.5 via Amp

Change summary

src/main.lua          | 135 -------------------------------------------
src/wt/cmd/remove.lua | 137 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 140 insertions(+), 132 deletions(-)

Detailed changes

src/main.lua 🔗

@@ -36,7 +36,6 @@ local detect_cloned_default_branch = git_mod.detect_cloned_default_branch
 local get_default_branch = git_mod.get_default_branch
 local parse_branch_remotes = git_mod.parse_branch_remotes
 local parse_worktree_list = git_mod.parse_worktree_list
-local branch_checked_out_at = git_mod.branch_checked_out_at
 
 local config_mod = require("wt.config")
 local resolve_url_template = config_mod.resolve_url_template
@@ -60,6 +59,9 @@ local cmd_fetch = fetch_mod.cmd_fetch
 local list_mod = require("wt.cmd.list")
 local cmd_list = list_mod.cmd_list
 
+local remove_mod = require("wt.cmd.remove")
+local cmd_remove = remove_mod.cmd_remove
+
 ---@param args string[]
 local function cmd_clone(args)
 	-- Parse arguments: <url> [--remote name]... [--own]
@@ -582,137 +584,6 @@ local function cmd_add(args)
 	print(target_path)
 end
 
----Check if cwd is inside (or equal to) a given path
----@param target string
----@return boolean
-local function cwd_inside_path(target)
-	local cwd = get_cwd()
-	if not cwd then
-		return false
-	end
-	return path_inside(cwd, target)
-end
-
----Get the bare repo's HEAD branch
----@param git_dir string
----@return string|nil branch name, nil on error
-local function get_bare_head(git_dir)
-	local output, code = run_cmd("GIT_DIR=" .. git_dir .. " git symbolic-ref --short HEAD")
-	if code ~= 0 then
-		return nil
-	end
-	return (output:gsub("%s+$", ""))
-end
-
----@param args string[]
-local function cmd_remove(args)
-	-- Parse arguments: <branch> [-b] [-f]
-	local branch = nil
-	local delete_branch = false
-	local force = false
-
-	for _, a in ipairs(args) do
-		if a == "-b" then
-			delete_branch = true
-		elseif a == "-f" then
-			force = true
-		elseif not branch then
-			branch = a
-		else
-			die("unexpected argument: " .. a)
-		end
-	end
-
-	if not branch then
-		die("usage: wt r <branch> [-b] [-f]")
-		return
-	end
-
-	local root, err = find_project_root()
-	if not root then
-		die(err --[[@as string]])
-		return
-	end
-
-	local git_dir = root .. "/.bare"
-
-	-- Find worktree by querying git for actual location (not computed from config)
-	local wt_output, wt_code = run_cmd("GIT_DIR=" .. git_dir .. " git worktree list --porcelain")
-	if wt_code ~= 0 then
-		die("failed to list worktrees", EXIT_SYSTEM_ERROR)
-		return
-	end
-
-	local worktrees = parse_worktree_list(wt_output)
-	local target_path = nil
-	for _, wt in ipairs(worktrees) do
-		if wt.branch == branch then
-			target_path = wt.path
-			break
-		end
-	end
-
-	if not target_path then
-		die("no worktree found for branch '" .. branch .. "'")
-		return
-	end
-
-	-- Error if cwd is inside the worktree
-	if cwd_inside_path(target_path) then
-		die("cannot remove worktree while inside it")
-	end
-
-	-- Check for uncommitted changes
-	if not force then
-		local status_out = run_cmd("git -C " .. target_path .. " status --porcelain")
-		if status_out ~= "" then
-			die("worktree has uncommitted changes (use -f to force)")
-		end
-	end
-
-	-- Remove worktree
-	local remove_cmd = "GIT_DIR=" .. git_dir .. " git worktree remove"
-	if force then
-		remove_cmd = remove_cmd .. " --force"
-	end
-	remove_cmd = remove_cmd .. " -- " .. target_path
-
-	local output, code = run_cmd(remove_cmd)
-	if code ~= 0 then
-		die("failed to remove worktree: " .. output, EXIT_SYSTEM_ERROR)
-	end
-
-	-- Delete branch if requested
-	if delete_branch then
-		-- Check if branch is bare repo's HEAD
-		local bare_head = get_bare_head(git_dir)
-		if bare_head and bare_head == branch then
-			io.stderr:write("warning: cannot delete branch '" .. branch .. "' (it's the bare repo's HEAD)\n")
-			print("Worktree removed; branch retained")
-			return
-		end
-
-		-- Check if branch is checked out elsewhere
-		local checked_out = branch_checked_out_at(git_dir, branch)
-		if checked_out then
-			die("cannot delete branch '" .. branch .. "': checked out at " .. checked_out)
-		end
-
-		-- Delete branch
-		local delete_flag = force and "-D" or "-d"
-		local del_output, del_code = run_cmd("GIT_DIR=" .. git_dir .. " git branch " .. delete_flag .. " " .. branch)
-		if del_code ~= 0 then
-			io.stderr:write("warning: failed to delete branch: " .. del_output .. "\n")
-			print("Worktree removed; branch retained")
-			return
-		end
-
-		print("Worktree and branch '" .. branch .. "' removed")
-	else
-		print("Worktree removed")
-	end
-end
-
 ---List directory entries (excluding . and ..)
 ---@param path string
 ---@return string[]

src/wt/cmd/remove.lua 🔗

@@ -0,0 +1,137 @@
+-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+--
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+local exit = require("wt.exit")
+local shell = require("wt.shell")
+local git = require("wt.git")
+local path_mod = require("wt.path")
+
+---@class wt.cmd.remove
+local M = {}
+
+---Check if cwd is inside (or equal to) a given path
+---@param target string
+---@return boolean
+local function cwd_inside_path(target)
+	local cwd = shell.get_cwd()
+	if not cwd then
+		return false
+	end
+	return path_mod.path_inside(cwd, target)
+end
+
+---Get the bare repo's HEAD branch
+---@param git_dir string
+---@return string|nil branch name, nil on error
+local function get_bare_head(git_dir)
+	local output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git symbolic-ref --short HEAD")
+	if code ~= 0 then
+		return nil
+	end
+	return (output:gsub("%s+$", ""))
+end
+
+---Remove a worktree and optionally its branch
+---@param args string[]
+function M.cmd_remove(args)
+	local branch = nil
+	local delete_branch = false
+	local force = false
+
+	for _, a in ipairs(args) do
+		if a == "-b" then
+			delete_branch = true
+		elseif a == "-f" then
+			force = true
+		elseif not branch then
+			branch = a
+		else
+			shell.die("unexpected argument: " .. a)
+		end
+	end
+
+	if not branch then
+		shell.die("usage: wt r <branch> [-b] [-f]")
+		return
+	end
+
+	local root, err = git.find_project_root()
+	if not root then
+		shell.die(err --[[@as string]])
+		return
+	end
+
+	local git_dir = root .. "/.bare"
+
+	local wt_output, wt_code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git worktree list --porcelain")
+	if wt_code ~= 0 then
+		shell.die("failed to list worktrees", exit.EXIT_SYSTEM_ERROR)
+		return
+	end
+
+	local worktrees = git.parse_worktree_list(wt_output)
+	local target_path = nil
+	for _, wt in ipairs(worktrees) do
+		if wt.branch == branch then
+			target_path = wt.path
+			break
+		end
+	end
+
+	if not target_path then
+		shell.die("no worktree found for branch '" .. branch .. "'")
+		return
+	end
+
+	if cwd_inside_path(target_path) then
+		shell.die("cannot remove worktree while inside it")
+	end
+
+	if not force then
+		local status_out = shell.run_cmd("git -C " .. target_path .. " status --porcelain")
+		if status_out ~= "" then
+			shell.die("worktree has uncommitted changes (use -f to force)")
+		end
+	end
+
+	local remove_cmd = "GIT_DIR=" .. git_dir .. " git worktree remove"
+	if force then
+		remove_cmd = remove_cmd .. " --force"
+	end
+	remove_cmd = remove_cmd .. " -- " .. target_path
+
+	local output, code = shell.run_cmd(remove_cmd)
+	if code ~= 0 then
+		shell.die("failed to remove worktree: " .. output, exit.EXIT_SYSTEM_ERROR)
+	end
+
+	if delete_branch then
+		local bare_head = get_bare_head(git_dir)
+		if bare_head and bare_head == branch then
+			io.stderr:write("warning: cannot delete branch '" .. branch .. "' (it's the bare repo's HEAD)\n")
+			print("Worktree removed; branch retained")
+			return
+		end
+
+		local checked_out = git.branch_checked_out_at(git_dir, branch)
+		if checked_out then
+			shell.die("cannot delete branch '" .. branch .. "': checked out at " .. checked_out)
+		end
+
+		local delete_flag = force and "-D" or "-d"
+		local del_output, del_code =
+			shell.run_cmd("GIT_DIR=" .. git_dir .. " git branch " .. delete_flag .. " " .. branch)
+		if del_code ~= 0 then
+			io.stderr:write("warning: failed to delete branch: " .. del_output .. "\n")
+			print("Worktree removed; branch retained")
+			return
+		end
+
+		print("Worktree and branch '" .. branch .. "' removed")
+	else
+		print("Worktree removed")
+	end
+end
+
+return M