diff --git a/lua/jj/cmd/resolve.lua b/lua/jj/cmd/resolve.lua index b3ab08e..453067f 100644 --- a/lua/jj/cmd/resolve.lua +++ b/lua/jj/cmd/resolve.lua @@ -20,7 +20,9 @@ function M.resolve(opts) -- Extra arguments vim.list_extend(cmd_args, args) -- Append the filestes - vim.list_extend(cmd_args, filesets) + for _, fileset in ipairs(filesets) do + table.insert(cmd_args, utils.escape_fileset(fileset)) + end local escaped_cmd_args = {} for _, arg in ipairs(cmd_args) do diff --git a/lua/jj/cmd/split.lua b/lua/jj/cmd/split.lua index 7b93cc0..557ed8e 100644 --- a/lua/jj/cmd/split.lua +++ b/lua/jj/cmd/split.lua @@ -25,7 +25,7 @@ local function build_split_command(opts) if opts.filesets then for _, fileset in ipairs(opts.filesets) do - table.insert(args, fileset) + table.insert(args, utils.escape_fileset(fileset)) end end @@ -39,8 +39,7 @@ function M.split(opts) return end - local cmd_mod = require("jj.cmd") - opts = vim.tbl_deep_extend("force", cmd_mod.config.split or {}, opts or {}) --[[@as jj.cmd.split.opts]] + opts = opts or {} --[[@as jj.cmd.split.opts]] -- If it's empty do nothing if utils.is_change_empty(opts.rev or "@") then diff --git a/lua/jj/cmd/status.lua b/lua/jj/cmd/status.lua index 29513ed..3d34f3b 100644 --- a/lua/jj/cmd/status.lua +++ b/lua/jj/cmd/status.lua @@ -16,8 +16,8 @@ function M.handle_status_restore() if file_info.is_rename then -- For renamed files, remove the new file and restore the old one from parent revision - local rm_cmd = "rm " .. vim.fn.shellescape(file_info.new_path) - local restore_cmd = "jj restore --from @- " .. vim.fn.shellescape(file_info.old_path) + local rm_cmd = string.format("rm %s", vim.fn.shellescape(file_info.new_path)) + local restore_cmd = string.format("jj restore --from @- %s", utils.escape_fileset(file_info.old_path)) local _, rm_success = runner.execute_command(rm_cmd, "Failed to remove renamed file") if rm_success then @@ -32,7 +32,8 @@ function M.handle_status_restore() end else -- For non-renamed files, use regular restore - local restore_cmd = "jj restore " .. vim.fn.shellescape(file_info.old_path) + utils.notify(utils.escape_fileset(file_info.old_path)) + local restore_cmd = string.format("jj restore %s", utils.escape_fileset(file_info.old_path)) local _, success = runner.execute_command(restore_cmd, "Failed to restore") if success then diff --git a/lua/jj/picker.lua b/lua/jj/picker.lua index aad0111..d8272ec 100644 --- a/lua/jj/picker.lua +++ b/lua/jj/picker.lua @@ -121,7 +121,7 @@ local function get_files() text = line:sub(3), file = file_path, status = change .. " ", - diff_cmd = string.format("jj --no-pager diff %s", vim.fn.shellescape(file_path)), + diff_cmd = string.format("jj --no-pager diff %s", utils.escape_fileset(file_path)), confirm_action = "open_and_diff", } @@ -174,7 +174,7 @@ local function log_history(file_path) "--no-graph", [[ -T 'change_id.shortest() ++ "\t" ++ coalesce(author.name(), "(no author)") ++ "\t" ++ committer.timestamp() ++ "\t" ++ coalesce(description.first_line(), "(no description)") ++ "\n"' ]], }, " ") - local output, ok = runner.execute_command(string.format(format, vim.fn.shellescape(file_path))) + local output, ok = runner.execute_command(string.format(format, utils.escape_fileset(file_path))) if not ok then return end @@ -201,7 +201,16 @@ local function log_history(file_path) time = time_part, description = description, text = string.format("%s %s %s %s", rev, author, short_time, description), - preview_cmd = { "jj", "--no-pager", "diff", file_path, "-r", rev, "--stat", "--git" }, + preview_cmd = { + "jj", + "--no-pager", + "diff", + utils.escape_fileset(file_path), + "-r", + rev, + "--stat", + "--git", + }, confirm_action = "edit_revision", }) end diff --git a/lua/jj/utils.lua b/lua/jj/utils.lua index 0829a4e..e67e860 100644 --- a/lua/jj/utils.lua +++ b/lua/jj/utils.lua @@ -982,4 +982,14 @@ function M.open_first_conflicted_file(revset) end end +--- Build a shell-safe jj fileset argument for a literal path. +--- jj path arguments use fileset syntax, so special characters like `$` +--- must be wrapped in jj string quotes before shell-escaping. +---@param path string +---@return string +function M.escape_fileset(path) + local fileset_literal = string.format('"%s"', path:gsub("\\", "\\\\"):gsub('"', '\\"')) + return vim.fn.shellescape(fileset_literal) +end + return M