@@ -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[]
@@ -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