refactor(wt): extract cmd.new module

Amolith created

Assisted-by: Claude Opus 4.5 via Amp

Change summary

src/main.lua       | 168 -------------------------------------------
src/wt/cmd/new.lua | 181 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 184 insertions(+), 165 deletions(-)

Detailed changes

src/main.lua 🔗

@@ -31,7 +31,6 @@ local git_mod = require("wt.git")
 local find_project_root = git_mod.find_project_root
 local detect_source_worktree = git_mod.detect_source_worktree
 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
 
@@ -63,6 +62,9 @@ local cmd_remove = remove_mod.cmd_remove
 local add_mod = require("wt.cmd.add")
 local cmd_add = add_mod.cmd_add
 
+local new_mod = require("wt.cmd.new")
+local cmd_new = new_mod.cmd_new
+
 ---@param args string[]
 local function cmd_clone(args)
 	-- Parse arguments: <url> [--remote name]... [--own]
@@ -316,170 +318,6 @@ local function cmd_clone(args)
 	end
 end
 
----@param args string[]
-local function cmd_new(args)
-	-- Parse arguments: <project-name> [--remote name]...
-	local project_name = nil
-	---@type string[]
-	local remote_flags = {}
-
-	local i = 1
-	while i <= #args do
-		local a = args[i]
-		if a == "--remote" then
-			if not args[i + 1] then
-				die("--remote requires a name")
-			end
-			table.insert(remote_flags, args[i + 1])
-			i = i + 1
-		elseif not project_name then
-			project_name = a
-		else
-			die("unexpected argument: " .. a)
-		end
-		i = i + 1
-	end
-
-	if not project_name then
-		die("usage: wt n <project-name> [--remote name]...")
-		return
-	end
-
-	-- Check if project directory already exists
-	local cwd = get_cwd()
-	if not cwd then
-		die("failed to get current directory", EXIT_SYSTEM_ERROR)
-	end
-	local project_path = cwd .. "/" .. project_name
-	local check = io.open(project_path, "r")
-	if check then
-		check:close()
-		die("directory already exists: " .. project_path)
-	end
-
-	-- Load global config
-	local global_config = load_global_config()
-
-	-- Determine which remotes to use
-	---@type string[]
-	local selected_remotes = {}
-
-	if #remote_flags > 0 then
-		-- Use explicitly provided remotes
-		selected_remotes = remote_flags
-	elseif global_config.default_remotes then
-		if type(global_config.default_remotes) == "table" then
-			selected_remotes = global_config.default_remotes
-		elseif global_config.default_remotes == "prompt" then
-			-- Prompt with gum choose
-			if global_config.remotes then
-				local keys = {}
-				for k in pairs(global_config.remotes) do
-					table.insert(keys, k)
-				end
-				table.sort(keys)
-				if #keys > 0 then
-					local input = table.concat(keys, "\n")
-					local cmd = "echo '" .. input .. "' | gum choose --no-limit"
-					local output, code = run_cmd(cmd)
-					if code == 0 and output ~= "" then
-						for line in output:gmatch("[^\n]+") do
-							table.insert(selected_remotes, line)
-						end
-					end
-				end
-			end
-		end
-	elseif global_config.remotes then
-		-- No default_remotes configured, prompt if remotes exist
-		local keys = {}
-		for k in pairs(global_config.remotes) do
-			table.insert(keys, k)
-		end
-		table.sort(keys)
-		if #keys > 0 then
-			local input = table.concat(keys, "\n")
-			local cmd = "echo '" .. input .. "' | gum choose --no-limit"
-			local output, code = run_cmd(cmd)
-			if code == 0 and output ~= "" then
-				for line in output:gmatch("[^\n]+") do
-					table.insert(selected_remotes, line)
-				end
-			end
-		end
-	end
-
-	-- Create project structure
-	local bare_path = project_path .. "/.bare"
-	local output, code = run_cmd("mkdir -p " .. bare_path)
-	if code ~= 0 then
-		die("failed to create directory: " .. output, EXIT_SYSTEM_ERROR)
-	end
-
-	output, code = run_cmd("git init --bare " .. bare_path)
-	if code ~= 0 then
-		die("failed to init bare repo: " .. output, EXIT_SYSTEM_ERROR)
-	end
-
-	-- Write .git file pointing to .bare
-	local git_file_handle = io.open(project_path .. "/.git", "w")
-	if not git_file_handle then
-		die("failed to create .git file", EXIT_SYSTEM_ERROR)
-		return
-	end
-	git_file_handle:write("gitdir: ./.bare\n")
-	git_file_handle:close()
-
-	-- Add remotes
-	local git_dir = bare_path
-	for _, remote_name in ipairs(selected_remotes) do
-		local template = global_config.remotes and global_config.remotes[remote_name]
-		if template then
-			local url = resolve_url_template(template, project_name)
-			output, code = run_cmd("GIT_DIR=" .. git_dir .. " git remote add " .. remote_name .. " " .. url)
-			if code ~= 0 then
-				io.stderr:write("warning: failed to add remote '" .. remote_name .. "': " .. output .. "\n")
-			else
-				-- Configure fetch refspec for the remote
-				run_cmd(
-					"GIT_DIR="
-						.. git_dir
-						.. " git config remote."
-						.. remote_name
-						.. ".fetch '+refs/heads/*:refs/remotes/"
-						.. remote_name
-						.. "/*'"
-				)
-			end
-		else
-			io.stderr:write("warning: remote '" .. remote_name .. "' not found in config\n")
-		end
-	end
-
-	-- Detect default branch
-	local default_branch = get_default_branch()
-
-	-- Load config for path style
-	local style = global_config.branch_path_style or "nested"
-	local separator = global_config.flat_separator
-	local worktree_path = branch_to_path(project_path, default_branch, style, separator)
-
-	-- Create orphan worktree
-	output, code =
-		run_cmd("GIT_DIR=" .. git_dir .. " git worktree add --orphan -b " .. default_branch .. " -- " .. worktree_path)
-	if code ~= 0 then
-		die("failed to create worktree: " .. output, EXIT_SYSTEM_ERROR)
-	end
-
-	-- Print summary
-	print("Created project: " .. project_path)
-	print("Default branch:  " .. default_branch)
-	print("Worktree:        " .. worktree_path)
-	if #selected_remotes > 0 then
-		print("Remotes:         " .. table.concat(selected_remotes, ", "))
-	end
-end
-
 ---List directory entries (excluding . and ..)
 ---@param path string
 ---@return string[]

src/wt/cmd/new.lua 🔗

@@ -0,0 +1,181 @@
+-- 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")
+local config = require("wt.config")
+
+---@class wt.cmd.new
+local M = {}
+
+---Create a new project with bare repo structure
+---@param args string[]
+function M.cmd_new(args)
+	-- Parse arguments: <project-name> [--remote name]...
+	local project_name = nil
+	---@type string[]
+	local remote_flags = {}
+
+	local i = 1
+	while i <= #args do
+		local a = args[i]
+		if a == "--remote" then
+			if not args[i + 1] then
+				shell.die("--remote requires a name")
+			end
+			table.insert(remote_flags, args[i + 1])
+			i = i + 1
+		elseif not project_name then
+			project_name = a
+		else
+			shell.die("unexpected argument: " .. a)
+		end
+		i = i + 1
+	end
+
+	if not project_name then
+		shell.die("usage: wt n <project-name> [--remote name]...")
+		return
+	end
+
+	-- Check if project directory already exists
+	local cwd = shell.get_cwd()
+	if not cwd then
+		shell.die("failed to get current directory", exit.EXIT_SYSTEM_ERROR)
+	end
+	---@cast cwd string
+	local project_path = cwd .. "/" .. project_name
+	local check = io.open(project_path, "r")
+	if check then
+		check:close()
+		shell.die("directory already exists: " .. project_path)
+	end
+
+	-- Load global config
+	local global_config = config.load_global_config()
+
+	-- Determine which remotes to use
+	---@type string[]
+	local selected_remotes = {}
+
+	if #remote_flags > 0 then
+		-- Use explicitly provided remotes
+		selected_remotes = remote_flags
+	elseif global_config.default_remotes then
+		if type(global_config.default_remotes) == "table" then
+			selected_remotes = global_config.default_remotes --[[@as string[] ]]
+		elseif global_config.default_remotes == "prompt" then
+			-- Prompt with gum choose
+			if global_config.remotes then
+				local keys = {}
+				for k in pairs(global_config.remotes) do
+					table.insert(keys, k)
+				end
+				table.sort(keys)
+				if #keys > 0 then
+					local input = table.concat(keys, "\n")
+					local cmd = "echo '" .. input .. "' | gum choose --no-limit"
+					local output, code = shell.run_cmd(cmd)
+					if code == 0 and output ~= "" then
+						for line in output:gmatch("[^\n]+") do
+							table.insert(selected_remotes, line)
+						end
+					end
+				end
+			end
+		end
+	elseif global_config.remotes then
+		-- No default_remotes configured, prompt if remotes exist
+		local keys = {}
+		for k in pairs(global_config.remotes) do
+			table.insert(keys, k)
+		end
+		table.sort(keys)
+		if #keys > 0 then
+			local input = table.concat(keys, "\n")
+			local cmd = "echo '" .. input .. "' | gum choose --no-limit"
+			local output, code = shell.run_cmd(cmd)
+			if code == 0 and output ~= "" then
+				for line in output:gmatch("[^\n]+") do
+					table.insert(selected_remotes, line)
+				end
+			end
+		end
+	end
+
+	-- Create project structure
+	local bare_path = project_path .. "/.bare"
+	local output, code = shell.run_cmd("mkdir -p " .. bare_path)
+	if code ~= 0 then
+		shell.die("failed to create directory: " .. output, exit.EXIT_SYSTEM_ERROR)
+	end
+
+	output, code = shell.run_cmd("git init --bare " .. bare_path)
+	if code ~= 0 then
+		shell.die("failed to init bare repo: " .. output, exit.EXIT_SYSTEM_ERROR)
+	end
+
+	-- Write .git file pointing to .bare
+	local git_file_handle = io.open(project_path .. "/.git", "w")
+	if not git_file_handle then
+		shell.die("failed to create .git file", exit.EXIT_SYSTEM_ERROR)
+		return
+	end
+	git_file_handle:write("gitdir: ./.bare\n")
+	git_file_handle:close()
+
+	-- Add remotes
+	local git_dir = bare_path
+	for _, remote_name in ipairs(selected_remotes) do
+		local template = global_config.remotes and global_config.remotes[remote_name]
+		if template then
+			local url = config.resolve_url_template(template, project_name)
+			output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git remote add " .. remote_name .. " " .. url)
+			if code ~= 0 then
+				io.stderr:write("warning: failed to add remote '" .. remote_name .. "': " .. output .. "\n")
+			else
+				-- Configure fetch refspec for the remote
+				shell.run_cmd(
+					"GIT_DIR="
+						.. git_dir
+						.. " git config remote."
+						.. remote_name
+						.. ".fetch '+refs/heads/*:refs/remotes/"
+						.. remote_name
+						.. "/*'"
+				)
+			end
+		else
+			io.stderr:write("warning: remote '" .. remote_name .. "' not found in config\n")
+		end
+	end
+
+	-- Detect default branch
+	local default_branch = git.get_default_branch()
+
+	-- Load config for path style
+	local style = global_config.branch_path_style or "nested"
+	local separator = global_config.flat_separator
+	local worktree_path = path_mod.branch_to_path(project_path, default_branch, style, separator)
+
+	-- Create orphan worktree
+	output, code = shell.run_cmd(
+		"GIT_DIR=" .. git_dir .. " git worktree add --orphan -b " .. default_branch .. " -- " .. worktree_path
+	)
+	if code ~= 0 then
+		shell.die("failed to create worktree: " .. output, exit.EXIT_SYSTEM_ERROR)
+	end
+
+	-- Print summary
+	print("Created project: " .. project_path)
+	print("Default branch:  " .. default_branch)
+	print("Worktree:        " .. worktree_path)
+	if #selected_remotes > 0 then
+		print("Remotes:         " .. table.concat(selected_remotes, ", "))
+	end
+end
+
+return M