feat(remove): cleanup empty parent directories

Amolith created

After removing a nested worktree like feature/auth/oauth2, removes the
now-empty parent directories up to project root.

Assisted-by: Claude Opus 4.5 via Amp

Change summary

src/wt/cmd/remove.lua | 14 ++++++++++++++
src/wt/path.lua       |  9 +++++++++
src/wt/shell.lua      |  7 +++++++
3 files changed, 30 insertions(+)

Detailed changes

src/wt/cmd/remove.lua 🔗

@@ -32,6 +32,18 @@ local function get_bare_head(git_dir)
 	return (output:gsub("%s+$", ""))
 end
 
+---Remove empty parent directories between target and project root
+---@param target_path string the removed worktree path
+---@param project_root string the project root (stop here)
+local function cleanup_empty_parents(target_path, project_root)
+	project_root = project_root:gsub("/$", "")
+	local parent = path_mod.parent_dir(target_path)
+	while parent and parent ~= project_root and path_mod.path_inside(parent, project_root) do
+		shell.run_cmd_silent("rmdir " .. shell.quote(parent))
+		parent = path_mod.parent_dir(parent)
+	end
+end
+
 ---Remove a worktree and optionally its branch
 ---@param args string[]
 function M.cmd_remove(args)
@@ -106,6 +118,8 @@ function M.cmd_remove(args)
 		shell.die("failed to remove worktree: " .. output, exit.EXIT_SYSTEM_ERROR)
 	end
 
+	cleanup_empty_parents(target_path, root)
+
 	if delete_branch then
 		local bare_head = get_bare_head(git_dir)
 		if bare_head and bare_head == branch then

src/wt/path.lua 🔗

@@ -90,4 +90,13 @@ function M.escape_pattern(str)
 	return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
 end
 
+---Get the parent directory of a path
+---@param path string
+---@return string|nil parent or nil if path has no parent
+function M.parent_dir(path)
+	path = path:gsub("/$", "")
+	local parent = path:match("(.+)/[^/]+$")
+	return parent
+end
+
 return M

src/wt/shell.lua 🔗

@@ -52,4 +52,11 @@ function M.die(msg, code)
 	os.exit(code or exit.EXIT_USER_ERROR)
 end
 
+---Quote a string for safe shell use
+---@param str string
+---@return string
+function M.quote(str)
+	return "'" .. str:gsub("'", "'\\''") .. "'"
+end
+
 return M