add.lua

  1-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2--
  3-- SPDX-License-Identifier: GPL-3.0-or-later
  4
  5local exit = require("wt.exit")
  6local shell = require("wt.shell")
  7local git = require("wt.git")
  8local path_mod = require("wt.path")
  9local config = require("wt.config")
 10local hooks = require("wt.hooks")
 11
 12---@class wt.cmd.add
 13local M = {}
 14
 15---Add a worktree for an existing or new branch
 16---@param args string[]
 17function M.cmd_add(args)
 18	-- Parse arguments: <branch> [-b [<start-point>]]
 19	---@type string|nil
 20	local branch = nil
 21	local create_branch = false
 22	---@type string|nil
 23	local start_point = nil
 24
 25	local i = 1
 26	while i <= #args do
 27		local a = args[i]
 28		if a == "-b" then
 29			create_branch = true
 30			-- Check if next arg is start-point (not another flag)
 31			if args[i + 1] and not args[i + 1]:match("^%-") then
 32				start_point = args[i + 1]
 33				i = i + 1
 34			end
 35		elseif not branch then
 36			branch = a
 37		else
 38			shell.die("unexpected argument: " .. a)
 39		end
 40		i = i + 1
 41	end
 42
 43	if not branch then
 44		shell.die("usage: wt a <branch> [-b [<start-point>]]")
 45		return
 46	end
 47
 48	local root, err = git.find_project_root()
 49	if not root then
 50		shell.die(err --[[@as string]])
 51		return
 52	end
 53
 54	local git_dir = root .. "/.bare"
 55	local source_worktree = git.detect_source_worktree(root)
 56
 57	-- Load config for path style
 58	local global_config = config.load_global_config()
 59	local style = global_config.branch_path_style or "nested"
 60	local separator = global_config.flat_separator or "_"
 61
 62	local target_path = path_mod.branch_to_path(root, branch, style, separator)
 63
 64	-- Check if target already exists
 65	local check = io.open(target_path .. "/.git", "r")
 66	if check then
 67		check:close()
 68		shell.die("worktree already exists at " .. target_path)
 69	end
 70
 71	local output, code
 72	if create_branch then
 73		-- Create new branch with worktree
 74		if start_point then
 75			output, code = shell.run_cmd(
 76				"GIT_DIR="
 77					.. git_dir
 78					.. " git worktree add -b "
 79					.. branch
 80					.. " -- "
 81					.. target_path
 82					.. " "
 83					.. start_point
 84			)
 85		else
 86			output, code =
 87				shell.run_cmd("GIT_DIR=" .. git_dir .. " git worktree add -b " .. branch .. " -- " .. target_path)
 88		end
 89	else
 90		-- Check if branch exists locally or on remotes
 91		local exists_local = git.branch_exists_local(git_dir, branch)
 92		local remotes = git.find_branch_remotes(git_dir, branch)
 93
 94		if not exists_local and #remotes == 0 then
 95			shell.die("branch '" .. branch .. "' not found locally or on any remote (use -b to create)")
 96		end
 97
 98		if #remotes > 1 then
 99			shell.die("branch '" .. branch .. "' exists on multiple remotes: " .. table.concat(remotes, ", "))
100		end
101
102		output, code = shell.run_cmd("GIT_DIR=" .. git_dir .. " git worktree add -- " .. target_path .. " " .. branch)
103	end
104
105	if code ~= 0 then
106		shell.die("failed to add worktree: " .. output, exit.EXIT_SYSTEM_ERROR)
107	end
108
109	-- Run hooks if we have a source worktree
110	local project_config = config.load_project_config(root)
111	if source_worktree then
112		if project_config.hooks then
113			hooks.run_hooks(source_worktree, target_path, project_config.hooks, root)
114		end
115	elseif project_config.hooks then
116		io.stderr:write("warning: hooks skipped (run from inside a worktree to apply hooks)\n")
117	end
118
119	print(target_path)
120end
121
122return M