@@ -102,6 +102,101 @@ end
return M
]]
+_EMBEDDED_MODULES["wt.path"] = [[-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+--
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+---@class wt.path
+local M = {}
+
+---Split path into components
+---@param path string
+---@return string[]
+function M.split_path(path)
+ local parts = {}
+ for part in path:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ return parts
+end
+
+---Calculate relative path from one absolute path to another
+---@param from string absolute path of starting directory
+---@param to string absolute path of target
+---@return string relative path
+function M.relative_path(from, to)
+ if from == to then
+ return "./"
+ end
+
+ local from_parts = M.split_path(from)
+ local to_parts = M.split_path(to)
+
+ local common = 0
+ for i = 1, math.min(#from_parts, #to_parts) do
+ if from_parts[i] == to_parts[i] then
+ common = i
+ else
+ break
+ end
+ end
+
+ local up_count = #from_parts - common
+ local result = {}
+
+ for _ = 1, up_count do
+ table.insert(result, "..")
+ end
+
+ for i = common + 1, #to_parts do
+ table.insert(result, to_parts[i])
+ end
+
+ if #result == 0 then
+ return "./"
+ end
+
+ return table.concat(result, "/")
+end
+
+---Convert branch name to worktree path based on style
+---@param root string project root path
+---@param branch string branch name
+---@param style string "nested" or "flat"
+---@param separator? string separator for flat style (default "_")
+---@return string worktree path
+function M.branch_to_path(root, branch, style, separator)
+ if style == "flat" then
+ local sep = separator or "_"
+ local escaped_sep = sep:gsub("%%", "%%%%")
+ local flat_name = branch:gsub("/", escaped_sep)
+ return root .. "/" .. flat_name
+ end
+ -- nested style (default): preserve slashes
+ return root .. "/" .. branch
+end
+
+---Check if path_a is inside (or equal to) path_b
+---@param path_a string the path to check
+---@param path_b string the container path
+---@return boolean
+function M.path_inside(path_a, path_b)
+ -- Normalize: ensure no trailing slash for comparison
+ path_b = path_b:gsub("/$", "")
+ path_a = path_a:gsub("/$", "")
+ return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/"
+end
+
+---Escape special Lua pattern characters in a string
+---@param str string
+---@return string
+function M.escape_pattern(str)
+ return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
+end
+
+return M
+]]
+
if _VERSION < "Lua 5.2" then
io.stderr:write("error: wt requires Lua 5.2 or later\n")
@@ -119,6 +214,13 @@ local run_cmd_silent = shell.run_cmd_silent
local get_cwd = shell.get_cwd
local die = shell.die
+local path_mod = require("wt.path")
+local branch_to_path = path_mod.branch_to_path
+local split_path = path_mod.split_path
+local relative_path = path_mod.relative_path
+local path_inside = path_mod.path_inside
+local escape_pattern = path_mod.escape_pattern
+
---Walk up from cwd looking for .git file pointing to .bare, or .bare/ directory
---@return string|nil root
---@return string|nil error
@@ -398,23 +500,6 @@ local function get_default_branch()
return "main"
end
----Convert branch name to worktree path
----@param root string
----@param branch string
----@param style string "nested" or "flat"
----@param separator? string separator for flat style
----@return string
-local function branch_to_path(root, branch, style, separator)
- if style == "flat" then
- local sep = separator or "_"
- local escaped_sep = sep:gsub("%%", "%%%%")
- local flat_name = branch:gsub("/", escaped_sep)
- return root .. "/" .. flat_name
- end
- -- nested style (default): preserve slashes
- return root .. "/" .. branch
-end
-
---Load global config from ~/.config/wt/config.lua
---@return {branch_path_style?: string, flat_separator?: string, remotes?: table<string, string>, default_remotes?: string[]|string}
local function load_global_config()
@@ -481,56 +566,6 @@ local function load_project_config(root)
return result
end
----Split path into components
----@param path string
----@return string[]
-local function split_path(path)
- local parts = {}
- for part in path:gmatch("[^/]+") do
- table.insert(parts, part)
- end
- return parts
-end
-
----Calculate relative path from one absolute path to another
----@param from string absolute path of starting directory
----@param to string absolute path of target
----@return string relative path
-local function relative_path(from, to)
- if from == to then
- return "./"
- end
-
- local from_parts = split_path(from)
- local to_parts = split_path(to)
-
- local common = 0
- for i = 1, math.min(#from_parts, #to_parts) do
- if from_parts[i] == to_parts[i] then
- common = i
- else
- break
- end
- end
-
- local up_count = #from_parts - common
- local result = {}
-
- for _ = 1, up_count do
- table.insert(result, "..")
- end
-
- for i = common + 1, #to_parts do
- table.insert(result, to_parts[i])
- end
-
- if #result == 0 then
- return "./"
- end
-
- return table.concat(result, "/")
-end
-
---Check if cwd is inside a worktree (has .git file, not at project root)
---@param root string
---@return string|nil source_worktree path if inside worktree, nil if at project root
@@ -571,13 +606,6 @@ local function branch_exists_local(git_dir, branch)
return run_cmd_silent("GIT_DIR=" .. git_dir .. " git show-ref --verify --quiet refs/heads/" .. branch)
end
----Escape special Lua pattern characters in a string
----@param str string
----@return string
-local function escape_pattern(str)
- return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
-end
-
---Parse git branch -r output to extract remotes containing a branch
---@param output string git branch -r output
---@param branch string branch name to find
@@ -1291,17 +1319,6 @@ local function cmd_add(args)
print(target_path)
end
----Check if path_a is inside (or equal to) path_b
----@param path_a string the path to check
----@param path_b string the container path
----@return boolean
-local function path_inside(path_a, path_b)
- -- Normalize: ensure no trailing slash for comparison
- path_b = path_b:gsub("/$", "")
- path_a = path_a:gsub("/$", "")
- return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/"
-end
-
---Check if cwd is inside (or equal to) a given path
---@param target string
---@return boolean
@@ -20,6 +20,13 @@ local run_cmd_silent = shell.run_cmd_silent
local get_cwd = shell.get_cwd
local die = shell.die
+local path_mod = require("wt.path")
+local branch_to_path = path_mod.branch_to_path
+local split_path = path_mod.split_path
+local relative_path = path_mod.relative_path
+local path_inside = path_mod.path_inside
+local escape_pattern = path_mod.escape_pattern
+
---Walk up from cwd looking for .git file pointing to .bare, or .bare/ directory
---@return string|nil root
---@return string|nil error
@@ -299,23 +306,6 @@ local function get_default_branch()
return "main"
end
----Convert branch name to worktree path
----@param root string
----@param branch string
----@param style string "nested" or "flat"
----@param separator? string separator for flat style
----@return string
-local function branch_to_path(root, branch, style, separator)
- if style == "flat" then
- local sep = separator or "_"
- local escaped_sep = sep:gsub("%%", "%%%%")
- local flat_name = branch:gsub("/", escaped_sep)
- return root .. "/" .. flat_name
- end
- -- nested style (default): preserve slashes
- return root .. "/" .. branch
-end
-
---Load global config from ~/.config/wt/config.lua
---@return {branch_path_style?: string, flat_separator?: string, remotes?: table<string, string>, default_remotes?: string[]|string}
local function load_global_config()
@@ -382,56 +372,6 @@ local function load_project_config(root)
return result
end
----Split path into components
----@param path string
----@return string[]
-local function split_path(path)
- local parts = {}
- for part in path:gmatch("[^/]+") do
- table.insert(parts, part)
- end
- return parts
-end
-
----Calculate relative path from one absolute path to another
----@param from string absolute path of starting directory
----@param to string absolute path of target
----@return string relative path
-local function relative_path(from, to)
- if from == to then
- return "./"
- end
-
- local from_parts = split_path(from)
- local to_parts = split_path(to)
-
- local common = 0
- for i = 1, math.min(#from_parts, #to_parts) do
- if from_parts[i] == to_parts[i] then
- common = i
- else
- break
- end
- end
-
- local up_count = #from_parts - common
- local result = {}
-
- for _ = 1, up_count do
- table.insert(result, "..")
- end
-
- for i = common + 1, #to_parts do
- table.insert(result, to_parts[i])
- end
-
- if #result == 0 then
- return "./"
- end
-
- return table.concat(result, "/")
-end
-
---Check if cwd is inside a worktree (has .git file, not at project root)
---@param root string
---@return string|nil source_worktree path if inside worktree, nil if at project root
@@ -472,13 +412,6 @@ local function branch_exists_local(git_dir, branch)
return run_cmd_silent("GIT_DIR=" .. git_dir .. " git show-ref --verify --quiet refs/heads/" .. branch)
end
----Escape special Lua pattern characters in a string
----@param str string
----@return string
-local function escape_pattern(str)
- return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
-end
-
---Parse git branch -r output to extract remotes containing a branch
---@param output string git branch -r output
---@param branch string branch name to find
@@ -1192,17 +1125,6 @@ local function cmd_add(args)
print(target_path)
end
----Check if path_a is inside (or equal to) path_b
----@param path_a string the path to check
----@param path_b string the container path
----@return boolean
-local function path_inside(path_a, path_b)
- -- Normalize: ensure no trailing slash for comparison
- path_b = path_b:gsub("/$", "")
- path_a = path_a:gsub("/$", "")
- return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/"
-end
-
---Check if cwd is inside (or equal to) a given path
---@param target string
---@return boolean
@@ -0,0 +1,93 @@
+-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+--
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+---@class wt.path
+local M = {}
+
+---Split path into components
+---@param path string
+---@return string[]
+function M.split_path(path)
+ local parts = {}
+ for part in path:gmatch("[^/]+") do
+ table.insert(parts, part)
+ end
+ return parts
+end
+
+---Calculate relative path from one absolute path to another
+---@param from string absolute path of starting directory
+---@param to string absolute path of target
+---@return string relative path
+function M.relative_path(from, to)
+ if from == to then
+ return "./"
+ end
+
+ local from_parts = M.split_path(from)
+ local to_parts = M.split_path(to)
+
+ local common = 0
+ for i = 1, math.min(#from_parts, #to_parts) do
+ if from_parts[i] == to_parts[i] then
+ common = i
+ else
+ break
+ end
+ end
+
+ local up_count = #from_parts - common
+ local result = {}
+
+ for _ = 1, up_count do
+ table.insert(result, "..")
+ end
+
+ for i = common + 1, #to_parts do
+ table.insert(result, to_parts[i])
+ end
+
+ if #result == 0 then
+ return "./"
+ end
+
+ return table.concat(result, "/")
+end
+
+---Convert branch name to worktree path based on style
+---@param root string project root path
+---@param branch string branch name
+---@param style string "nested" or "flat"
+---@param separator? string separator for flat style (default "_")
+---@return string worktree path
+function M.branch_to_path(root, branch, style, separator)
+ if style == "flat" then
+ local sep = separator or "_"
+ local escaped_sep = sep:gsub("%%", "%%%%")
+ local flat_name = branch:gsub("/", escaped_sep)
+ return root .. "/" .. flat_name
+ end
+ -- nested style (default): preserve slashes
+ return root .. "/" .. branch
+end
+
+---Check if path_a is inside (or equal to) path_b
+---@param path_a string the path to check
+---@param path_b string the container path
+---@return boolean
+function M.path_inside(path_a, path_b)
+ -- Normalize: ensure no trailing slash for comparison
+ path_b = path_b:gsub("/$", "")
+ path_a = path_a:gsub("/$", "")
+ return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/"
+end
+
+---Escape special Lua pattern characters in a string
+---@param str string
+---@return string
+function M.escape_pattern(str)
+ return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
+end
+
+return M