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
+49 -43
View File
@@ -10,6 +10,7 @@ package.path = package.path .. ";lua/?.lua;lua/?/init.lua"
local parser = require("jj.core.parser")
local utils = require("jj.utils")
local jj_args = require("jj.core.args")
local tests_passed = 0
local tests_failed = 0
@@ -529,37 +530,33 @@ print("\n=== Running build_log_cmd tests ===\n")
local log = require("jj.cmd.log")
run_test("build_log_cmd: raw_flags with --no-pager does not duplicate it", function()
local cmd = log.build_log_cmd({ raw_flags = "--no-pager --limit 18" })
-- Should contain exactly one --no-pager
local _, count = cmd:gsub("%-%-no%-pager", "")
assert_equals(1, count, "Expected exactly one --no-pager")
assert_equals("jj log --no-pager --limit 18", cmd)
local cmd = log.build_log_cmd({ raw_flags = { "--no-pager", "--limit", "18" } })
assert_table_equals({ "jj", "log", "--no-pager", "--limit", "18" }, cmd)
end)
run_test("build_log_cmd: raw_flags without --no-pager works normally", function()
local cmd = log.build_log_cmd({ raw_flags = "--limit 18" })
assert_equals("jj log --no-pager --limit 18", cmd)
local cmd = log.build_log_cmd({ raw_flags = { "--limit", "18" } })
assert_table_equals({ "jj", "log", "--no-pager", "--limit", "18" }, cmd)
end)
run_test("build_log_cmd: raw_flags that is only --no-pager", function()
local cmd = log.build_log_cmd({ raw_flags = "--no-pager" })
assert_equals("jj log --no-pager", cmd)
local cmd = log.build_log_cmd({ raw_flags = { "--no-pager" } })
assert_table_equals({ "jj", "log", "--no-pager" }, cmd)
end)
run_test("build_log_cmd: structured opts with limit", function()
local cmd = log.build_log_cmd({ limit = 10 })
assert_equals(true, cmd:find("--limit 10") ~= nil, "Expected --limit 10 in command")
assert_equals(true, cmd:find("--no%-pager") ~= nil, "Expected --no-pager in command")
assert_table_equals({ "jj", "log", "--no-pager", "--limit", "10" }, cmd)
end)
run_test("build_log_cmd: structured opts with revisions", function()
local cmd = log.build_log_cmd({ revisions = "main" })
assert_equals(true, cmd:find("--revisions main") ~= nil, "Expected --revisions main in command")
assert_table_equals({ "jj", "log", "--no-pager", "--revisions", "main" }, cmd)
end)
run_test("build_log_cmd: default opts produces valid command", function()
local cmd = log.build_log_cmd({})
assert_equals(true, cmd:find("^jj log %-%-no%-pager") ~= nil, "Expected command to start with jj log --no-pager")
assert_table_equals({ "jj", "log", "--no-pager" }, cmd)
end)
print("\n=== Running resolve arg parsing tests ===\n")
@@ -623,14 +620,14 @@ end)
local resolve = require("jj.cmd.resolve")
run_test("resolve: shellescapes args for external execution", function()
run_test("resolve: passes jj-quoted argv for external execution", function()
local runner = require("jj.core.runner")
local original_execute_command_async = runner.execute_command_async
local original_execute_argv_async = runner.execute_async
local original_notify = utils.notify
local original_ensure_jj = utils.ensure_jj
local captured_cmd = nil
runner.execute_command_async = function(cmd)
runner.execute_async = function(cmd)
captured_cmd = cmd
end
utils.notify = function() end
@@ -645,13 +642,19 @@ run_test("resolve: shellescapes args for external execution", function()
filesets = { "dir with spaces/", "glob:*" },
external = true,
})
assert_equals(
"'jj' 'resolve' '--revision' 'abc 123' '--tool' 'my tool' 'dir with spaces/' 'glob:*'",
captured_cmd
)
assert_table_equals({
"jj",
"resolve",
"--revision",
"abc 123",
"--tool",
"my tool",
jj_args.fileset("dir with spaces/"),
jj_args.fileset("glob:*"),
}, captured_cmd)
end)
runner.execute_command_async = original_execute_command_async
runner.execute_async = original_execute_argv_async
utils.notify = original_notify
utils.ensure_jj = original_ensure_jj
if not ok then
@@ -659,7 +662,7 @@ run_test("resolve: shellescapes args for external execution", function()
end
end)
run_test("resolve: passes argv for floating execution", function()
run_test("resolve: passes jj-quoted filesets for floating execution", function()
local terminal = require("jj.ui.terminal")
local original_run_floating = terminal.run_floating
local original_notify = utils.notify
@@ -687,8 +690,8 @@ run_test("resolve: passes argv for floating execution", function()
"abc 123",
"--tool",
"my tool",
"dir with spaces/",
"glob:*",
jj_args.fileset("dir with spaces/"),
jj_args.fileset("glob:*"),
}, captured_cmd)
end)
@@ -704,10 +707,10 @@ print("\n=== Running utils helper tests ===\n")
run_test("is_change_conflicted: returns true when jj reports conflict", function()
local runner = require("jj.core.runner")
local original_execute_command = runner.execute_command
local original_execute_argv = runner.execute
runner.execute_command = function(cmd, error_prefix, input, silent)
assert_equals("jj log --no-graph -r 'abc123' -T 'conflict' --quiet", cmd)
runner.execute = function(argv, error_prefix, input, silent)
assert_table_equals({ "jj", "log", "--no-graph", "-r", "abc123", "-T", "conflict", "--quiet" }, argv)
assert_equals("Error checking if revset has conflicts", error_prefix)
assert_is_nil(input)
assert_equals(true, silent)
@@ -717,7 +720,7 @@ run_test("is_change_conflicted: returns true when jj reports conflict", function
local ok, err = pcall(function()
assert_equals(true, utils.is_change_conflicted("abc123"))
end)
runner.execute_command = original_execute_command
runner.execute = original_execute_argv
if not ok then
error(err)
end
@@ -725,16 +728,16 @@ end)
run_test("is_change_conflicted: returns false when jj reports no conflict", function()
local runner = require("jj.core.runner")
local original_execute_command = runner.execute_command
local original_execute_argv = runner.execute
runner.execute_command = function()
runner.execute = function()
return "false\n", true
end
local ok, err = pcall(function()
assert_equals(false, utils.is_change_conflicted("abc123"))
end)
runner.execute_command = original_execute_command
runner.execute = original_execute_argv
if not ok then
error(err)
end
@@ -742,16 +745,16 @@ end)
run_test("is_change_conflicted: returns false when jj command fails", function()
local runner = require("jj.core.runner")
local original_execute_command = runner.execute_command
local original_execute_argv = runner.execute
runner.execute_command = function()
runner.execute = function()
return nil, false
end
local ok, err = pcall(function()
assert_equals(false, utils.is_change_conflicted("abc123"))
end)
runner.execute_command = original_execute_command
runner.execute = original_execute_argv
if not ok then
error(err)
end
@@ -870,8 +873,9 @@ print("\n=== Running get_file_content tests ===\n")
run_test("get_file_content: reads existing file content", function()
local runner = require("jj.core.runner")
local original = runner.execute_command_raw
runner.execute_command_raw = function()
local original = runner.execute_raw
runner.execute_raw = function(cmd)
assert_table_equals({ "jj", "file", "show", "-r", "abc123", jj_args.fileset("src/file.py") }, cmd)
return "a\nb\n", true, ""
end
local ok_test, err = pcall(function()
@@ -881,7 +885,7 @@ run_test("get_file_content: reads existing file content", function()
assert_equals(true, ok)
assert_equals(false, absent)
end)
runner.execute_command_raw = original
runner.execute_raw = original
if not ok_test then
error(err)
end
@@ -889,8 +893,9 @@ end)
run_test("get_file_content: absent path in revision reports absent (not a read error)", function()
local runner = require("jj.core.runner")
local original = runner.execute_command_raw
runner.execute_command_raw = function()
local original = runner.execute_raw
runner.execute_raw = function(cmd)
assert_table_equals({ "jj", "file", "show", "-r", "abc123", jj_args.fileset("src/new_file.py") }, cmd)
return nil, false, "Error: No such path: src/new_file.py\n"
end
local ok_test, err = pcall(function()
@@ -900,7 +905,7 @@ run_test("get_file_content: absent path in revision reports absent (not a read e
assert_equals(false, ok)
assert_equals(true, absent)
end)
runner.execute_command_raw = original
runner.execute_raw = original
if not ok_test then
error(err)
end
@@ -908,8 +913,9 @@ end)
run_test("get_file_content: genuine read error returns failure without absent", function()
local runner = require("jj.core.runner")
local original = runner.execute_command_raw
runner.execute_command_raw = function()
local original = runner.execute_raw
runner.execute_raw = function(cmd)
assert_table_equals({ "jj", "file", "show", "-r", "nope", jj_args.fileset("src/file.py") }, cmd)
return nil, false, "Error: Revision `nope` doesn't exist\n"
end
local ok_test, err = pcall(function()
@@ -917,7 +923,7 @@ run_test("get_file_content: genuine read error returns failure without absent",
assert_equals(false, ok)
assert_equals(false, absent)
end)
runner.execute_command_raw = original
runner.execute_raw = original
if not ok_test then
error(err)
end