refactor!: Migrate runners cmd from string to argv (#134)

Migrate command execution to structured argv lists across the plugin, replacing shell-string command construction with `vim.system` argv execution. The runner API now exposes argv-based execution as the default path:

   - `runner.execute_command` -> `runner.execute`
   - `runner.execute_command_async` -> `runner.execute_async`
   - `runner.execute_command_raw` -> `runner.execute_raw`
   - `runner.execute_command_raw_async` -> `runner.execute_raw_async`
   - `runner.execute_command_sync` was removed; use `runner.execute` and handle the result directly

BREAKING CHANGES:
   - `cmd.new({ args = ... })` now requires `args` as `string[]` argv tokens, not a single string.
   - `cmd.log({ ... })` now uses `raw_flags` as `string[]` for raw passthrough flags; `raw = "..."` is no longer supported.
   - Direct users of `jj.core.runner` must migrate from the old `execute_command*` string helpers to the new argv-based `execute*` helpers.
This commit is contained in:
Nicolas GB
2026-07-07 18:25:30 +02:00
committed by GitHub
parent c724290141
commit 7f2786ff16
17 changed files with 708 additions and 540 deletions
+10 -7
View File
@@ -19,21 +19,24 @@ local default_describe_opts = {
--- @param revset? string The revision to describe
--- @param sync? boolean Whether to execute command synchronously
local function execute_describe(description, revset, sync)
local cmd = "jj describe"
local cmd = {
"jj",
"describe",
}
if revset then
cmd = cmd .. " -r " .. revset
table.insert(cmd, "-r")
table.insert(cmd, revset)
end
cmd = cmd .. " --stdin"
table.insert(cmd, "--stdin")
-- Use --stdin to properly handle multi-line and special characters
if sync then
runner.execute_command_sync(cmd, function()
utils.notify("Description set.", vim.log.levels.INFO)
end, "Failed to describe", description)
runner.execute(cmd, "Failed to describe", description)
utils.notify("Description set.", vim.log.levels.INFO)
return
end
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Description set.", vim.log.levels.INFO)
end, "Failed to describe", description)
end
+81 -79
View File
@@ -354,7 +354,7 @@ end
--- @class jj.cmd.new_opts
--- @field show_log? boolean Whether or not to display the log command after creating a new
--- @field with_input? boolean Whether or not to use nvim input to decide the parent of the new commit
--- @field args? string The arguments to append to the new command
--- @field args? string[] The arguments to append to the new command
-- Jujutsu new
--- @param opts? jj.cmd.new_opts
@@ -365,9 +365,9 @@ function M.new(opts)
opts = opts or {}
--- @param cmd string
--- @param cmd string[]
local function execute_new(cmd)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Command `new` was succesful.", vim.log.levels.INFO)
-- Show the updated log if the user requested it
if opts.show_log then
@@ -386,15 +386,15 @@ function M.new(opts)
prompt = "Parent(s) of the new change [default: @]",
}, function(input)
if input then
execute_new(string.format("jj new %s", input))
execute_new({ "jj", "new", input })
end
terminal.close_terminal_buffer()
end)
else
-- Otherwise follow a classic flow for inputing
local cmd = "jj new"
local cmd = { "jj", "new" }
if opts.args then
cmd = string.format("jj new %s", opts.args)
vim.list_extend(cmd, opts.args)
end
execute_new(cmd)
@@ -416,7 +416,7 @@ function M.edit()
default = "",
}, function(input)
if input then
runner.execute_command_async(string.format("jj edit %s", input), function()
runner.execute_async({ "jj", "edit", input }, function()
utils.reload_changed_file_buffers()
M.log({})
end, "Error editing change")
@@ -432,8 +432,8 @@ function M.squash()
return
end
local cmd = "jj squash"
runner.execute_command_async(cmd, function()
local cmd = { "jj", "squash" }
runner.execute_async(cmd, function()
utils.notify("Command `squash` was succesful.", vim.log.levels.INFO)
if terminal.is_log_buffer_open() then
M.log()
@@ -505,9 +505,9 @@ function M.rebase()
default = "trunk()",
}, function(input)
if input then
local cmd = string.format("jj rebase -d '%s'", input)
local cmd = { "jj", "rebase", "-d", input }
utils.notify(string.format("Beginning rebase on %s", input), vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Rebase successful.", vim.log.levels.INFO)
M.log({})
end, "Error rebasing")
@@ -539,8 +539,8 @@ function M.bookmark_create(opts)
default = "@",
}, function(revset)
revset = revset or "@"
local cmd = string.format("jj b c %s -r %s", input, revset)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "bookmark", "create", input, "-r", revset }
runner.execute_async(cmd, function()
utils.notify(
string.format("Bookmark `%s` created successfully for %s", input, revset),
vim.log.levels.INFO
@@ -575,8 +575,8 @@ function M.bookmark_move()
default = "@",
}, function(revset)
if revset then
local cmd = string.format("jj b m %s --to %s -B", choice, revset)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "bookmark", "move", choice, "--to", revset, "-B" }
runner.execute_async(cmd, function()
utils.notify(
string.format("Bookmark `%s` moved successfully to %s", choice, revset),
vim.log.levels.INFO
@@ -604,8 +604,8 @@ function M.bookmark_delete()
prompt = "Bookmark name: ",
}, function(input)
if input then
local cmd = string.format("jj b d %s", input)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "bookmark", "delete", input }
runner.execute_async(cmd, function()
utils.notify(string.format("Bookmark `%s` deleted successfully.", input), vim.log.levels.INFO)
M.log({})
end, "Error deleting bookmark")
@@ -631,16 +631,13 @@ function M.bookmark_track()
vim.ui.select(bookmarks, { prompt = "Which bookmark do you want to track?" }, function(choice)
if choice then
runner.execute_command_async(
string.format("jj bookmark track %s --quiet", vim.fn.shellescape(choice)),
function()
utils.notify(string.format("Bookmark `%s` is now tracked.", choice))
if log_open then
M.log()
end
end,
"Could not track bookmark"
)
local cmd = { "jj", "bookmark", "track", choice, "--quiet" }
runner.execute_async(cmd, function()
utils.notify(string.format("Bookmark `%s` is now tracked.", choice))
if log_open then
M.log()
end
end, "Could not track bookmark")
end
end)
end
@@ -661,16 +658,13 @@ function M.bookmark_forget()
vim.ui.select(bookmarks, { prompt = "Which bookmark do you want to forget?" }, function(choice)
if choice then
runner.execute_command_async(
string.format("jj bookmark forget %s --quiet", vim.fn.shellescape(choice)),
function()
utils.notify(string.format("Bookmark `%s` is now untracked.", choice))
if log_open then
M.log()
end
end,
"Could not forget bookmark"
)
local cmd = { "jj", "bookmark", "forget", choice, "--quiet" }
runner.execute_async(cmd, function()
utils.notify(string.format("Bookmark `%s` is now untracked.", choice))
if log_open then
M.log()
end
end, "Could not forget bookmark")
end
end)
end
@@ -681,8 +675,8 @@ function M.undo()
return
end
local cmd = "jj undo"
runner.execute_command_async(cmd, function()
local cmd = { "jj", "undo" }
runner.execute_async(cmd, function()
utils.notify("Command `undo` was succesful.", vim.log.levels.INFO)
if terminal.is_log_buffer_open() then
M.log({})
@@ -696,8 +690,8 @@ function M.redo()
return
end
local cmd = "jj redo"
runner.execute_command_async(cmd, function()
local cmd = { "jj", "redo" }
runner.execute_async(cmd, function()
utils.notify("Command `redo` was succesful.", vim.log.levels.INFO)
if terminal.is_log_buffer_open() then
M.log({})
@@ -717,8 +711,8 @@ function M.abandon()
default = "",
}, function(input)
if input then
local cmd = string.format("jj abandon %s", input)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "abandon", input }
runner.execute_async(cmd, function()
utils.notify("Change abandoned successfully.", vim.log.levels.INFO)
M.log({})
end, "Error abandoning change")
@@ -753,8 +747,8 @@ function M.fetch()
end,
}, function(choice)
if choice then
local cmd = string.format("jj git fetch --remote %s", choice.name)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "git", "fetch", "--remote", choice.name }
runner.execute_async(cmd, function()
utils.notify(string.format("Fetching from %s...", choice), vim.log.levels.INFO)
if log_open then
M.log({})
@@ -764,9 +758,9 @@ function M.fetch()
end)
else
-- Only one remote, fetch from it directly
local cmd = "jj git fetch"
local cmd = { "jj", "git", "fetch" }
utils.notify("Fetching from remote...", vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Successfully fetched from remote", vim.log.levels.INFO)
if log_open then
M.log({})
@@ -839,23 +833,23 @@ function M.push(opts)
local notify_msg = "Pushing bookmarks `ALL` bookmarks"
local cmd = "jj git push"
local cmd = { "jj", "git", "push" }
if opts.bookmark then
notify_msg = string.format("Pushing bookmark `%s`", opts.bookmark)
cmd = string.format("%s --bookmark %s", cmd, opts.bookmark)
vim.list_extend(cmd, { "--bookmark", opts.bookmark })
elseif opts.deleted then
notify_msg = "Pushing deleted bookmarks"
cmd = cmd .. " --deleted"
table.insert(cmd, "--deleted")
end
if opts.remote then
cmd = string.format("%s --remote %s", cmd, opts.remote)
vim.list_extend(cmd, { "--remote", opts.remote })
notify_msg = string.format("%s to remote `%s`", notify_msg, opts.remote)
end
utils.notify(notify_msg .. "...", vim.log.levels.INFO, 1000)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Successfully pushed to remote", vim.log.levels.INFO)
if log_open then
M.log({})
@@ -894,13 +888,21 @@ function M.open_pr(opts)
end
-- Get the bookmark from the current change (@)
local bookmark, success =
runner.execute_command("jj log -r @ --no-graph -T 'bookmarks'", "Failed to get current bookmark", nil, true)
local bookmark, success = runner.execute(
{ "jj", "log", "-r", "@", "--no-graph", "-T", "bookmarks" },
"Failed to get current bookmark",
nil,
true
)
if not success or not bookmark or bookmark:match("^%s*$") then
-- If no bookmark on @, try @-
bookmark, success =
runner.execute_command("jj log -r @- --no-graph -T 'bookmarks'", "Failed to get parent bookmark", nil, true)
bookmark, success = runner.execute(
{ "jj", "log", "-r", "@-", "--no-graph", "-T", "bookmarks" },
"Failed to get parent bookmark",
nil,
true
)
if not success or not bookmark or bookmark:match("^%s*$") then
utils.notify("No bookmark found on @ or @- commits. Cannot open PR.", vim.log.levels.ERROR)
@@ -925,8 +927,8 @@ function M.commit(description)
local should_refresh = terminal.is_log_buffer_open()
if description and description ~= "" then
local cmd = "jj commit --message " .. vim.fn.shellescape(description)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "commit", "--message", description }
runner.execute_async(cmd, function()
utils.notify("Committed.", vim.log.levels.INFO)
if should_refresh then
vim.schedule(function()
@@ -942,8 +944,8 @@ function M.commit(description)
M.status()
vim.ui.input({ prompt = "Description: ", default = "" }, function(input)
if input and not input:match("^%s*$") then
local cmd = "jj commit --message " .. vim.fn.shellescape(input)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "commit", "--message", input }
runner.execute_async(cmd, function()
utils.notify("Committed.", vim.log.levels.INFO)
if should_refresh then
vim.schedule(function()
@@ -980,8 +982,8 @@ function M.commit(description)
utils.notify("Description cannot be empty", vim.log.levels.ERROR)
return
end
local cmd = "jj commit --message " .. vim.fn.shellescape(trimmed_description)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "commit", "--message", trimmed_description }
runner.execute_async(cmd, function()
utils.notify("Committed.", vim.log.levels.INFO)
if should_refresh then
vim.schedule(function()
@@ -1020,8 +1022,8 @@ function M.tag_set(rev)
-- Ask the user for the tag name
vim.ui.input({ prompt = "Tag name: ", default = "" }, function(input)
if input and not input:match("^%s*$") then
local cmd = string.format("jj tag set %s -r %s", input, rev)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "tag", "set", input, "-r", rev }
runner.execute_async(cmd, function()
utils.notify(string.format("Tag `%s` set on `%s`.", input, rev), vim.log.levels.INFO)
if should_refresh then
vim.schedule(function()
@@ -1044,8 +1046,8 @@ function M.tag_delete(tag)
-- If the tag is provided, delete it directly without asking the user
if tag then
local cmd = string.format("jj tag delete %s", tag)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "tag", "delete", tag }
runner.execute_async(cmd, function()
utils.notify(string.format("Tag `%s` deleted.", tag), vim.log.levels.INFO)
if terminal.is_log_buffer_open() then
vim.schedule(function()
@@ -1070,8 +1072,8 @@ function M.tag_delete(tag)
vim.ui.select(tags, { prompt = "Select tag to delete: " }, function(choice)
if choice then
local cmd = string.format("jj tag delete %s", choice)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "tag", "delete", choice }
runner.execute_async(cmd, function()
utils.notify(string.format("Tag `%s` deleted.", choice), vim.log.levels.INFO)
if should_refresh then
vim.schedule(function()
@@ -1126,8 +1128,8 @@ function M.tag_push()
prompt = "Select tag to push: ",
}, function(tag_choice)
if tag_choice then
local cmd = string.format("git push %s %s", choice.name, tag_choice)
runner.execute_command_async(cmd, function()
local cmd = { "git", "push", choice.name, tag_choice }
runner.execute_async(cmd, function()
utils.notify(
string.format("Tag `%s` pushed successfully to remote `%s`.", tag_choice, choice.name),
vim.log.levels.INFO
@@ -1156,8 +1158,8 @@ function M.tag_push()
prompt = "Select tag to push: ",
}, function(tag_choice)
if tag_choice then
local cmd = string.format("git push %s %s", remotes[1].name, tag_choice)
runner.execute_command_async(cmd, function()
local cmd = { "git", "push", remotes[1].name, tag_choice }
runner.execute_async(cmd, function()
utils.notify(
string.format("Tag `%s` pushed successfully to remote `%s`.", tag_choice, remotes[1].name),
vim.log.levels.INFO
@@ -1225,13 +1227,13 @@ function M.fetch_pr(opts)
end
local ref = string.format("pull/%s/head:pr-%s-%d", pr, pr, count)
local cmd = string.format("git fetch origin %s", ref)
local cmd = { "git", "fetch", "origin", ref }
runner.execute_command_async(
runner.execute_async(
cmd,
function()
-- If we successfully pulled the PR, notify the user and refresh the log if it's open
runner.execute_command_async("jj git import", function()
runner.execute_async({ "jj", "git", "import" }, function()
utils.notify(
string.format("PR #%s fetched as pr-%s-%d.", pr, pr, count),
vim.log.levels.INFO
@@ -1323,8 +1325,8 @@ function M.j(args)
local cmd = nil
if #args == 0 then
local default_cmd_str, success = runner.execute_command(
"jj config list ui.default-command",
local default_cmd_str, success = runner.execute(
{ "jj", "config", "list", "ui.default-command" },
"Error getting user's default command",
nil,
true
@@ -1364,7 +1366,7 @@ function M.j(args)
end
end,
new = function()
M.new({ show_log = true, args = remaining_args_str, with_input = false })
M.new({ show_log = true, args = remaining_args, with_input = false })
end,
rebase = function()
M.rebase()
@@ -1376,7 +1378,7 @@ function M.j(args)
M.redo()
end,
log = function()
M.log({ raw_flags = remaining_args_str ~= "" and remaining_args_str or nil })
M.log({ raw_flags = #remaining_args > 0 and remaining_args or nil })
end,
split = function()
local opts = {
+95 -76
View File
@@ -22,7 +22,7 @@ local HIGHLIGHT_RANGE = 2 -- Revision line + description line
--- @field no_graph? boolean
--- @field limit? uinteger
--- @field revisions? string
--- @field raw_flags? string
--- @field raw_flags? string[]
---@type jj.cmd.log_opts
local default_log_opts = { summary = false, reversed = false, no_graph = false, limit = nil, raw_flags = nil }
@@ -298,29 +298,39 @@ end
--- Build the jj log command string from options
--- @param opts? jj.cmd.log_opts Optional command options
--- @return string The full jj log command
--- @return string[] The full jj log command
function M.build_log_cmd(opts)
local jj_cmd = "jj log --no-pager"
local jj_cmd = {
"jj",
"log",
"--no-pager",
}
local merged_opts = vim.tbl_extend("force", default_log_opts, opts or {})
if merged_opts.raw_flags then
-- Strip --no-pager from raw_flags since it's already in the base command
local flags = vim.trim(merged_opts.raw_flags:gsub("%-%-no%-pager", ""):gsub("%s+", " "))
if flags ~= "" then
return string.format("%s %s", jj_cmd, flags)
for _, flag in ipairs(merged_opts.raw_flags) do
-- Strip --no-pager from raw_flags since it's already in the base command
if flag ~= "--no-pager" then
table.insert(jj_cmd, flag)
end
end
return jj_cmd
end
for key, value in pairs(merged_opts) do
key = key:gsub("_", "-")
if key == "limit" and value then
jj_cmd = string.format("%s --%s %d", jj_cmd, key, value)
elseif key == "revisions" and value then
jj_cmd = string.format("%s --%s %s", jj_cmd, key, value)
elseif value then
jj_cmd = string.format("%s --%s", jj_cmd, key)
end
if merged_opts.summary then
table.insert(jj_cmd, "--summary")
end
if merged_opts.reversed then
table.insert(jj_cmd, "--reversed")
end
if merged_opts.no_graph then
table.insert(jj_cmd, "--no-graph")
end
if merged_opts.limit then
vim.list_extend(jj_cmd, { "--limit", tostring(merged_opts.limit) })
end
if merged_opts.revisions then
vim.list_extend(jj_cmd, { "--revisions", merged_opts.revisions })
end
return jj_cmd
@@ -384,22 +394,21 @@ function M.handle_log_new(flag, ignore_immut)
local cfg = flag_map[flag] or flag_map.default
-- Build command parts
local cmd_parts = { "jj", "new" }
local cmd = { "jj", "new" }
if cfg.opt ~= "" then
-- For -A flag, each revset needs its own -A prefix
for rev in revsets:gmatch("%S+") do
table.insert(cmd_parts, cfg.opt)
table.insert(cmd_parts, rev)
table.insert(cmd, cfg.opt)
table.insert(cmd, rev)
end
else
table.insert(cmd_parts, revsets)
table.insert(cmd, revsets)
end
if ignore_immut then
table.insert(cmd_parts, "--ignore-immutable")
table.insert(cmd, "--ignore-immutable")
end
local cmd = table.concat(cmd_parts, " ")
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(string.format(cfg.ok, revsets), vim.log.levels.INFO)
-- Refresh the log buffer after creating the change.
require("jj.cmd").log()
@@ -480,18 +489,15 @@ function M.handle_log_edit(ignore_immut, close_on_exit)
-- If we found a revision, edit it.
-- Build command parts.
local cmd_parts = { "jj", "edit" }
local cmd = { "jj", "edit" }
if ignore_immut then
table.insert(cmd_parts, "--ignore-immutable")
table.insert(cmd, "--ignore-immutable")
end
table.insert(cmd_parts, revset)
-- Build cmd string
local cmd = table.concat(cmd_parts, " ")
table.insert(cmd, revset)
-- Try to execute cmd
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.reload_changed_file_buffers()
-- Close the terminal buffer
@@ -521,18 +527,15 @@ function M.handle_log_abandon(ignore_immut)
-- If we found revision(s), abandon it.
-- Build command parts.
local cmd_parts = { "jj", "abandon" }
local cmd = { "jj", "abandon" }
if ignore_immut then
table.insert(cmd_parts, "--ignore-immutable")
table.insert(cmd, "--ignore-immutable")
end
table.insert(cmd_parts, revsets)
-- Build cmd string
local cmd = table.concat(cmd_parts, " ")
table.insert(cmd, revsets)
-- Try to execute cmd
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
local text = "Abandoned change: `%s`"
if revsets:find(" ", 1) then
text = "Abandoned changes: `%s`"
@@ -559,8 +562,8 @@ function M.handle_log_fetch()
end,
}, function(choice)
if choice then
local cmd = string.format("jj git fetch --remote %s", choice.name)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "git", "fetch", "--remote", choice.name }
runner.execute_async(cmd, function()
utils.notify(string.format("Fetching from %s...", choice), vim.log.levels.INFO)
M.log({})
end, "Error fetching from remote")
@@ -568,9 +571,9 @@ function M.handle_log_fetch()
end)
else
-- Only one remote, fetch from it directly
local cmd = "jj git fetch"
local cmd = { "jj", "git", "fetch" }
utils.notify("Fetching from remote...", vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify("Successfully fetched from remote", vim.log.levels.INFO)
M.log({})
end, "Error fetching from remote")
@@ -581,7 +584,7 @@ end
--- Ask the user which local bookmark to push.
function M.handle_log_push_from_all()
local bookmarks = utils.get_all_bookmarks_with_status()
local cmd = "jj git push"
local cmd = { "jj", "git", "push" }
if not bookmarks or #bookmarks == 0 then
utils.notify("No bookmarks found to push", vim.log.levels.ERROR)
return
@@ -597,9 +600,11 @@ function M.handle_log_push_from_all()
end,
}, function(choice)
if choice then
local push_cmd = string.format("%s -b %s", cmd, choice.name)
-- Add the bookmark name to the command
vim.list_extend(cmd, { "-b", choice.name })
utils.notify(string.format("Pushing bookmark `%s`...", choice.name), vim.log.levels.INFO)
runner.execute_command_async(push_cmd, function(output)
runner.execute_async(cmd, function(output)
if output and string.find(output, "Nothing changed%.") then
utils.notify("Nothing changed.", vim.log.levels.INFO)
else
@@ -627,8 +632,9 @@ function M.handle_log_push_bookmark()
end
-- Function to push the bookmark, takes the full command as argument
--- @param cmd string[] The command to execute
local function push(cmd)
runner.execute_command_async(cmd, function(output)
runner.execute_async(cmd, function(output)
if output and string.find(output, "Nothing changed%.") then
utils.notify("Nothing changed.", vim.log.levels.INFO)
else
@@ -652,14 +658,14 @@ function M.handle_log_push_bookmark()
end,
}, function(choice)
if choice then
local cmd = "jj git push"
local cmd = { "jj", "git", "push" }
if choice.name == "[All]" then
-- Push all bookmarks
cmd = string.format("%s --all", cmd)
table.insert(cmd, "--all")
utils.notify("Pushing `ALL` bookmarks", vim.log.levels.INFO)
else
vim.list_extend(cmd, { "-b", choice.name })
utils.notify(string.format("Pushing bookmark `%s`...", choice.name), vim.log.levels.INFO)
cmd = string.format("%s -b %s", cmd, choice.name)
end
push(cmd)
else
@@ -669,7 +675,7 @@ function M.handle_log_push_bookmark()
else
-- If there's only one bookmark simply push it
local b = bookmarks[1].name
local cmd = string.format("jj git push -b %s", b)
local cmd = { "jj", "git", "push", "-b", b }
utils.notify(string.format("Pushing bookmark `%s`...", b), vim.log.levels.INFO)
push(cmd)
end
@@ -768,14 +774,12 @@ function M.handle_log_bookmark_del()
---@param b_names string[] Bookmark names to delete
local function delete_bookmarks(b_names)
local escaped_list = {}
local cmd = { "jj", "bookmark", "delete" }
for _, b in ipairs(b_names) do
table.insert(escaped_list, vim.fn.shellescape(b))
table.insert(cmd, b)
end
local escaped_bookmarks = table.concat(escaped_list, " ")
local cmd = string.format("jj bookmark delete %s", escaped_bookmarks)
local b_str = table.concat(b_names, " ")
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(string.format("Deleted bookmark `%s`", b_str), vim.log.levels.INFO)
M.log({})
end, string.format("Error deleting bookmark `%s`", b_str))
@@ -830,8 +834,8 @@ function M.handle_log_bookmark()
-- Prompt for new bookmark name
vim.ui.input({ prompt = "Enter new bookmark name: ", default = prefix }, function(input)
if input and input ~= "" then
local cmd = string.format("jj bookmark create %s -r %s", input, revset)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "bookmark", "create", input, "-r", revset }
runner.execute_async(cmd, function()
utils.notify(
string.format("Created bookmark `%s` at `%s`", input, revset),
vim.log.levels.INFO
@@ -842,8 +846,8 @@ function M.handle_log_bookmark()
end)
else
-- Move existing bookmark to the revision
local cmd = string.format("jj bookmark move %s --to %s -B", choice, revset)
runner.execute_command_async(cmd, function()
local cmd = { "jj", "bookmark", "move", choice, "--to", revset, "-B" }
runner.execute_async(cmd, function()
utils.notify(string.format("Moved bookmark `%s` to `%s`", choice, revset), vim.log.levels.INFO)
M.log({})
end, "Error moving bookmark")
@@ -907,9 +911,9 @@ function M.handle_log_quick_squash()
return
end
local cmd = string.format("jj squash -r %s -u --ignore-immutable", revset)
local cmd = { "jj", "squash", "-r", revset, "-u", "--ignore-immutable" }
utils.notify(string.format("Squashing `%s` into it's parent...", revset), vim.log.levels.INFO)
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(string.format("Successfully squashed `%s` into it's parent", revset), vim.log.levels.INFO)
M.log({})
end, string.format("Error squashing `%s` into it's parent", revset))
@@ -1050,12 +1054,12 @@ function M.handle_summary_edit(revset, ignore_immut)
-- Close the log buffer
terminal.close_terminal_buffer()
local cmd = string.format("jj edit %s", revset)
local cmd = { "jj", "edit", revset }
if ignore_immut then
cmd = cmd .. " --ignore-immutable"
table.insert(cmd, "--ignore-immutable")
end
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.reload_changed_file_buffers()
utils.notify(string.format("Editing revset: `%s`", revset), vim.log.levels.INFO)
-- Open the file in the current window
@@ -1119,7 +1123,7 @@ end
--- Build the summary command for a revset
--- @param revset string The revision to show summary for
--- @return string The jj log command
--- @return string[] The jj log command
local function build_summary_cmd(revset)
local template = '"Commit ID: " ++ commit_id ++ "\\n" '
.. '++ "Change ID: " ++ change_id ++ "\\n" '
@@ -1128,7 +1132,15 @@ local function build_summary_cmd(revset)
.. "++ description "
.. '++ "\\n" ++ self.diff().summary()'
return string.format("jj log -r %s --no-graph -T '%s'", revset, template)
return {
"jj",
"log",
"-r",
revset,
"--no-graph",
"-T",
template,
}
end
--- Handle showing summary tooltip for revision under cursor
@@ -1155,7 +1167,7 @@ function M.handle_log_summary()
local cmd = build_summary_cmd(revset)
-- Run command synchronously first to calculate dimensions
local output, success = runner.execute_command(cmd, nil, nil, false)
local output, success = runner.execute(cmd, nil, nil, false)
if not success or not output then
return
end
@@ -1614,15 +1626,22 @@ function M.handle_rebase_execute(mode, ignore_immut)
end
utils.notify(string.format("Rebasing...", revsets, mode, destination_revset), vim.log.levels.INFO, 500)
local cmd = string.format("jj rebase -r '%s' %s %s", revsets, mode_flat, destination_revset)
local cmd = {
"jj",
"rebase",
"-r",
revsets,
mode_flat,
destination_revset,
}
-- If ignore_immut is true, add the flag
-- This is not currently exposed in keymaps but could be in the future
if ignore_immut then
cmd = cmd .. " --ignore-immutable"
table.insert(cmd, "--ignore-immutable")
end
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(
string.format("Rebased `%s` %s `%s` successfully", revsets, mode, destination_revset),
vim.log.levels.INFO
@@ -1653,19 +1672,19 @@ function M.handle_squash_execute(mode, ignore_immut)
utils.notify(string.format("Squashing...", revsets, mode, destination_revset), vim.log.levels.INFO, 500)
-- U flag to keep destination's message
local cmd = string.format("jj squash -f '%s' -u", revsets)
local cmd = { "jj", "squash", "-f", revsets, "-u" }
if mode == "into" then
cmd = cmd .. string.format(" -t %s", destination_revset)
vim.list_extend(cmd, { "-t", destination_revset })
end
-- If ignore_immut is true, add the flag
-- This is not currently exposed in keymaps but could be in the future
if ignore_immut then
cmd = cmd .. " --ignore-immutable"
table.insert(cmd, "--ignore-immutable")
end
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(
string.format("Squashed `%s` into `%s` successfully", revsets, destination_revset),
vim.log.levels.INFO
@@ -1702,15 +1721,15 @@ function M.handle_duplicate_execute(mode, ignore_immut)
end
utils.notify(string.format("Duplicating...", revsets, mode, destination_revset), vim.log.levels.INFO, 500)
local cmd = string.format("jj duplicate %s %s %s", revsets, mode_flat, destination_revset)
local cmd = { "jj", "duplicate", revsets, mode_flat, destination_revset }
-- If ignore_immut is true, add the flag
-- This is not currently exposed in keymaps but could be in the future
if ignore_immut then
cmd = cmd .. " --ignore-immutable"
table.insert(cmd, "--ignore-immutable")
end
runner.execute_command_async(cmd, function()
runner.execute_async(cmd, function()
utils.notify(
string.format("Duplicated `%s` %s `%s` successfully", revsets, mode, destination_revset),
vim.log.levels.INFO
+9 -13
View File
@@ -1,6 +1,7 @@
local M = {}
local utils = require("jj.utils")
local jj_args = require("jj.core.args")
local terminal = require("jj.ui.terminal")
local runner = require("jj.core.runner")
@@ -16,27 +17,22 @@ function M.resolve(opts)
return
end
local cmd_args = { "jj", "resolve", "--revision", rev }
local cmd = { "jj", "resolve", "--revision", rev }
-- Extra arguments
vim.list_extend(cmd_args, args)
-- Append the filestes
for _, fileset in ipairs(filesets) do
table.insert(cmd_args, utils.escape_fileset(fileset))
end
vim.list_extend(cmd, args)
local escaped_cmd_args = {}
for _, arg in ipairs(cmd_args) do
table.insert(escaped_cmd_args, vim.fn.shellescape(arg))
-- Append filesets as jj string literal
for _, fileset in ipairs(filesets) do
table.insert(cmd, jj_args.fileset(fileset))
end
local shell_cmd = table.concat(escaped_cmd_args, " ")
utils.notify(string.format("Resolving conflicts in change `%s`...", rev), vim.log.levels.INFO)
-- If external is set, run the command asynchronously and invoke the on_exit callback if provided
if opts.external then
-- Run the command asynchronously and notify the user of the result
runner.execute_command_async(
shell_cmd,
runner.execute_async(
cmd,
function(output)
if output and output ~= "" then
utils.notify(output, vim.log.levels.INFO)
@@ -56,7 +52,7 @@ function M.resolve(opts)
)
else
-- Otherwise, run in a floating terminal
terminal.run_floating(cmd_args, nil, {
terminal.run_floating(cmd, nil, {
title = " JJ Resolve ",
modifiable = true,
keep_modifiable = true,
+8 -2
View File
@@ -2,7 +2,11 @@ local M = {}
local utils = require("jj.utils")
local terminal = require("jj.ui.terminal")
local jj_args = require("jj.core.args")
--- Build the jj split command based on the provided options.
---@param opts jj.cmd.split.opts
---@return string[] The constructed jj split command.
local function build_split_command(opts)
local args = { "jj", "split" }
@@ -25,11 +29,11 @@ local function build_split_command(opts)
if opts.filesets then
for _, fileset in ipairs(opts.filesets) do
table.insert(args, utils.escape_fileset(fileset))
table.insert(args, jj_args.fileset(fileset))
end
end
return table.concat(args, " ")
return args
end
--- Split natively
@@ -47,6 +51,8 @@ function M.split(opts)
return
end
--- Run the jj split command in a floating terminal
---@param cmd string[]
local function run_split(cmd)
terminal.run_floating(cmd, nil, {
title = " JJ Split ",
+19 -20
View File
@@ -5,6 +5,7 @@ local utils = require("jj.utils")
local runner = require("jj.core.runner")
local parser = require("jj.core.parser")
local terminal = require("jj.ui.terminal")
local jj_args = require("jj.core.args")
--- Handle restoring a file from the jj status buffer
--- Supports both renamed and non-renamed files
@@ -15,29 +16,27 @@ function M.handle_status_restore()
end
if file_info.is_rename then
-- For renamed files, remove the new file and restore the old one from parent revision
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 restore_cmd = {
"jj",
"restore",
"--from",
"@-",
jj_args.fileset(file_info.old_path),
jj_args.fileset(file_info.new_path),
}
local _, rm_success = runner.execute_command(rm_cmd, "Failed to remove renamed file")
if rm_success then
local _, restore_success = runner.execute_command(restore_cmd, "Failed to restore original file")
if restore_success then
utils.notify(
"Reverted rename: " .. file_info.new_path .. " -> " .. file_info.old_path,
vim.log.levels.INFO
)
require("jj.cmd").status()
end
local _, restore_success = runner.execute(restore_cmd, "Failed to restore original file")
if restore_success then
utils.notify("Reverted rename: " .. file_info.new_path .. " -> " .. file_info.old_path, vim.log.levels.INFO)
require("jj.cmd").status()
end
else
-- For non-renamed files, use regular restore
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 restore_cmd = { "jj", "restore", jj_args.fileset(file_info.old_path) }
local _, success = runner.execute_command(restore_cmd, "Failed to restore")
local _, success = runner.execute(restore_cmd, "Failed to restore")
if success then
utils.notify("Restored: " .. file_info.old_path, vim.log.levels.INFO)
utils.notify("Restored: `" .. file_info.old_path .. "`", vim.log.levels.INFO)
require("jj.cmd").status()
end
end
@@ -95,16 +94,16 @@ function M.status(opts)
return
end
local cmd_str = "jj status --no-pager"
local cmd = { "jj", "status", "--no-pager" }
if opts and opts.notify then
local output, success = runner.execute_command(cmd_str, "Failed to get status")
local output, success = runner.execute(cmd, "Failed to get status")
if success then
utils.notify(output and output or "", vim.log.levels.INFO)
end
else
-- Default behavior: show in buffer
terminal.run(cmd_str, M.status_keymaps())
terminal.run(cmd, M.status_keymaps())
end
end