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