mirror of
https://github.com/zoriya/jj.nvim.git
synced 2026-08-15 23:53:18 +00:00
feat(log): add bookmark deletion command (#106)
This commit is contained in:
@@ -47,6 +47,7 @@ local split_module = require("jj.cmd.split")
|
||||
--- @field open_pr? string|string[]
|
||||
--- @field open_pr_list? string|string[]
|
||||
--- @field bookmark? string|string[]
|
||||
--- @field bookmark_del? string|string[]
|
||||
--- @field rebase? string|string[]
|
||||
--- @field rebase_mode? jj.cmd.rebase.keymaps
|
||||
--- @field squash? string|string[]
|
||||
@@ -164,6 +165,7 @@ M.config = {
|
||||
open_pr = "o",
|
||||
open_pr_list = "<S-o>",
|
||||
bookmark = "b",
|
||||
bookmark_del = "B",
|
||||
rebase = "r",
|
||||
rebase_mode = {
|
||||
onto = { "<CR>", "o" },
|
||||
|
||||
+112
-52
@@ -578,29 +578,35 @@ function M.handle_log_fetch()
|
||||
end
|
||||
|
||||
--- Handle pushing from `jj log` buffer.
|
||||
--- Askip the user for wich local bookmark to push.
|
||||
--- Ask the user which local bookmark to push.
|
||||
function M.handle_log_push_from_all()
|
||||
local bookkmarks = utils.get_all_bookmarks()
|
||||
local bookmarks = utils.get_all_bookmarks_with_status()
|
||||
local cmd = "jj git push"
|
||||
if not bookkmarks or #bookkmarks == 0 then
|
||||
if not bookmarks or #bookmarks == 0 then
|
||||
utils.notify("No bookmarks found to push", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui.select(bookkmarks, {
|
||||
vim.ui.select(bookmarks, {
|
||||
prompt = "Select bookmark to push: ",
|
||||
format_item = function(item)
|
||||
if item.is_deleted then
|
||||
return item.name .. " (deleted)"
|
||||
end
|
||||
return item.name
|
||||
end,
|
||||
}, function(choice)
|
||||
if choice then
|
||||
local push_cmd = string.format("%s -b %s", cmd, choice)
|
||||
utils.notify(string.format("Pushing bookmark `%s`...", choice), vim.log.levels.INFO)
|
||||
local push_cmd = string.format("%s -b %s", cmd, choice.name)
|
||||
utils.notify(string.format("Pushing bookmark `%s`...", choice.name), vim.log.levels.INFO)
|
||||
runner.execute_command_async(push_cmd, function(output)
|
||||
if output and string.find(output, "Nothing changed%.") then
|
||||
utils.notify("Nothing changed.", vim.log.levels.INFO)
|
||||
else
|
||||
utils.notify(string.format("Successfully pushed bookmark `%s`", choice), vim.log.levels.INFO)
|
||||
utils.notify(string.format("Successfully pushed bookmark `%s`", choice.name), vim.log.levels.INFO)
|
||||
M.log({})
|
||||
end
|
||||
end, string.format("Error pushing bookmark `%s`", choice))
|
||||
end, string.format("Error pushing bookmark `%s`", choice.name))
|
||||
else
|
||||
return
|
||||
end
|
||||
@@ -614,14 +620,9 @@ function M.handle_log_push_bookmark()
|
||||
return
|
||||
end
|
||||
|
||||
-- If we found a revfision get it's bookmark and push it
|
||||
local bookmark, success = runner.execute_command(
|
||||
string.format("jj log -r %s -T 'bookmarks' --no-graph", revset),
|
||||
string.format("Error retrieving bookmark for `%s`", revset),
|
||||
nil,
|
||||
false
|
||||
)
|
||||
if not success or not bookmark then
|
||||
local bookmarks = utils.get_bookmarks_for_rev(revset)
|
||||
if not bookmarks or #bookmarks == 0 then
|
||||
utils.notify("No bookmark found for revision", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -637,33 +638,28 @@ function M.handle_log_push_bookmark()
|
||||
end, string.format("Error pushing bookmark for `%s`", revset))
|
||||
end
|
||||
|
||||
-- If there's a * trim it (bookmarks with modifications have *)
|
||||
bookmark = bookmark:gsub("%*", ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
|
||||
if bookmark == "" then
|
||||
utils.notify("No bookmark found for revision", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- If there are multiple bookmarks user must choose
|
||||
if bookmark:find(" ") then
|
||||
-- Split by whitespace
|
||||
local bookmarks = {}
|
||||
bookmarks = vim.split(bookmark, "%s+", { trimempty = true })
|
||||
table.insert(bookmarks, "[All]")
|
||||
if #bookmarks > 1 then
|
||||
table.insert(bookmarks, { name = "[All]", is_deleted = false })
|
||||
|
||||
vim.ui.select(bookmarks, {
|
||||
prompt = "Which bookmark do you want to push?",
|
||||
format_item = function(item)
|
||||
if item.is_deleted then
|
||||
return item.name .. " (deleted)"
|
||||
end
|
||||
return item.name
|
||||
end,
|
||||
}, function(choice)
|
||||
if choice then
|
||||
local cmd = "jj git push"
|
||||
if choice == "[All]" then
|
||||
if choice.name == "[All]" then
|
||||
-- Push all bookmarks
|
||||
cmd = string.format("%s --all", cmd)
|
||||
utils.notify("Pushing `ALL` bookmarks", vim.log.levels.INFO)
|
||||
else
|
||||
utils.notify(string.format("Pushing bookmark `%s`...", choice), vim.log.levels.INFO)
|
||||
cmd = string.format("%s -b %s", cmd, choice)
|
||||
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
|
||||
@@ -672,9 +668,9 @@ function M.handle_log_push_bookmark()
|
||||
end)
|
||||
else
|
||||
-- If there's only one bookmark simply push it
|
||||
-- Push the bookmark from the revset found
|
||||
local cmd = string.format("jj git push -b %s", bookmark)
|
||||
utils.notify(string.format("Pushing bookmark `%s`...", bookmark), vim.log.levels.INFO)
|
||||
local b = bookmarks[1].name
|
||||
local cmd = string.format("jj git push -b %s", b)
|
||||
utils.notify(string.format("Pushing bookmark `%s`...", b), vim.log.levels.INFO)
|
||||
push(cmd)
|
||||
end
|
||||
end
|
||||
@@ -732,27 +728,86 @@ function M.handle_log_open_pr(list_bookmarks)
|
||||
end
|
||||
|
||||
-- Get the bookmark for this revision
|
||||
local bookmark, success = runner.execute_command(
|
||||
string.format("jj log -r %s -T 'bookmarks' --no-graph", revset),
|
||||
string.format("Error retrieving bookmark for `%s`", revset),
|
||||
nil,
|
||||
false
|
||||
)
|
||||
|
||||
if not success or not bookmark then
|
||||
return
|
||||
end
|
||||
|
||||
-- Trim and clean the bookmark (remove asterisks and whitespace)
|
||||
bookmark = bookmark:match("^%*?(.-)%*?$"):gsub("%s+", "")
|
||||
|
||||
if bookmark == "" then
|
||||
local bookmarks = utils.get_bookmarks_for_rev(revset)
|
||||
if not bookmarks or #bookmarks == 0 then
|
||||
utils.notify("[OPEN PR] No bookmark found for revision", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Open the PR using the utility function
|
||||
utils.open_pr_for_bookmark(bookmark)
|
||||
if #bookmarks > 1 then
|
||||
vim.ui.select(bookmarks, {
|
||||
prompt = "Which bookmark do you want to open PR for?",
|
||||
format_item = function(item)
|
||||
if item.is_deleted then
|
||||
return item.name .. " (deleted)"
|
||||
end
|
||||
return item.name
|
||||
end,
|
||||
}, function(choice)
|
||||
if choice then
|
||||
utils.open_pr_for_bookmark(choice.name)
|
||||
end
|
||||
end)
|
||||
else
|
||||
-- Open the PR using the utility function
|
||||
utils.open_pr_for_bookmark(bookmarks[1].name)
|
||||
end
|
||||
end
|
||||
|
||||
function M.handle_log_bookmark_del()
|
||||
local revset = get_revset()
|
||||
if not revset or revset == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local curr_bookmarks = utils.get_bookmarks_for_rev(revset)
|
||||
if not curr_bookmarks or #curr_bookmarks == 0 then
|
||||
utils.notify("No bookmark found for revision", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@param b_names string[] Bookmark names to delete
|
||||
local function delete_bookmarks(b_names)
|
||||
local escaped_list = {}
|
||||
for _, b in ipairs(b_names) do
|
||||
table.insert(escaped_list, vim.fn.shellescape(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()
|
||||
utils.notify(string.format("Deleted bookmark `%s`", b_str), vim.log.levels.INFO)
|
||||
M.log({})
|
||||
end, string.format("Error deleting bookmark `%s`", b_str))
|
||||
end
|
||||
|
||||
table.insert(curr_bookmarks, { name = "[All]", is_deleted = false })
|
||||
|
||||
vim.ui.select(curr_bookmarks, {
|
||||
prompt = "Which bookmark do you want to delete?",
|
||||
format_item = function(item)
|
||||
if item.is_deleted then
|
||||
return item.name .. " (deleted)"
|
||||
end
|
||||
return item.name
|
||||
end,
|
||||
}, function(choice)
|
||||
if choice then
|
||||
if choice.name == "[All]" then
|
||||
local all_names = vim.iter(curr_bookmarks)
|
||||
:filter(function(b)
|
||||
return b.name ~= "[All]"
|
||||
end)
|
||||
:map(function(b)
|
||||
return b.name
|
||||
end)
|
||||
:totable()
|
||||
delete_bookmarks(all_names)
|
||||
else
|
||||
delete_bookmarks({ choice.name })
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Create or move bookmark at revision under cursor in `jj log` buffer
|
||||
@@ -1191,6 +1246,11 @@ function M.log_keymaps()
|
||||
handler = M.handle_log_bookmark,
|
||||
modes = { "n" },
|
||||
},
|
||||
bookmark_del = {
|
||||
desc = "Delete bookmark at revision under cursor",
|
||||
handler = M.handle_log_bookmark_del,
|
||||
modes = { "n" },
|
||||
},
|
||||
rebase = {
|
||||
desc = "Rebase bookmark(s)",
|
||||
handler = M.handle_log_rebase,
|
||||
|
||||
@@ -370,6 +370,84 @@ function M.get_all_bookmarks()
|
||||
return bookmarks
|
||||
end
|
||||
|
||||
--- Get all bookmarks in the repository, including deleted ones
|
||||
--- @return {name: string, is_deleted: boolean}[] bookmarks List of bookmarks
|
||||
function M.get_all_bookmarks_with_status()
|
||||
local bookmarks_output, success = runner.execute_command(
|
||||
[[jj bookmark list -T 'if(!self.remote(), name ++ if(!self.present(), " (deleted)", "") ++ "\n")' --quiet]],
|
||||
"Failed to get bookmarks",
|
||||
nil,
|
||||
true
|
||||
)
|
||||
|
||||
if not success or not bookmarks_output then
|
||||
return {}
|
||||
end
|
||||
|
||||
local bookmarks = {}
|
||||
local seen = {}
|
||||
for line in bookmarks_output:gmatch("[^\n]+") do
|
||||
local trimmed = vim.trim(line)
|
||||
if trimmed ~= "" then
|
||||
local is_deleted = trimmed:match(" %(deleted%)$") ~= nil
|
||||
local name = trimmed:gsub(" %(deleted%)$", "")
|
||||
if not seen[name] then
|
||||
table.insert(bookmarks, { name = name, is_deleted = is_deleted })
|
||||
seen[name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return bookmarks
|
||||
end
|
||||
|
||||
--- Get unique base bookmark names from a string of space-separated bookmarks.
|
||||
--- Handles the output of the template `bookmarks.map(|b| b.name() ++ "::" ++ b.present()).join(" ")`
|
||||
--- Strips asterisks, strips @remote, and deduplicates.
|
||||
--- @param bookmark_str string
|
||||
--- @return {name: string, is_deleted: boolean}[] List of unique bookmark objects
|
||||
function M.parse_bookmark_names(bookmark_str)
|
||||
local raw_items = vim.split(bookmark_str, "%s+", { trimempty = true })
|
||||
local unique_bookmarks = {}
|
||||
local seen = {}
|
||||
|
||||
for _, item in ipairs(raw_items) do
|
||||
local parts = vim.split(item, "::", { plain = true })
|
||||
if #parts == 2 then
|
||||
-- Strip asterisks and @remote
|
||||
local base_name = parts[1]:gsub("%*", ""):gsub("@.*$", "")
|
||||
local is_deleted = parts[2] == "false"
|
||||
|
||||
if base_name ~= "" and not seen[base_name] then
|
||||
table.insert(unique_bookmarks, { name = base_name, is_deleted = is_deleted })
|
||||
seen[base_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return unique_bookmarks
|
||||
end
|
||||
|
||||
--- Get unique base bookmark names and statuses for a given revset
|
||||
--- @param revset string
|
||||
--- @return {name: string, is_deleted: boolean}[]|nil ret List of bookmark objects, or nil on failure
|
||||
function M.get_bookmarks_for_rev(revset)
|
||||
-- Retrieve name and deleted status
|
||||
local cmd = string.format(
|
||||
[[jj log -r %s -T 'bookmarks.map(|b| b.name() ++ "::" ++ b.present()).join(" ")' --no-graph]],
|
||||
vim.fn.shellescape(revset)
|
||||
)
|
||||
|
||||
local output, success =
|
||||
runner.execute_command(cmd, string.format("Error retrieving bookmark for `%s`", revset), nil, false)
|
||||
|
||||
if not success or not output then
|
||||
return nil
|
||||
end
|
||||
|
||||
return M.parse_bookmark_names(output)
|
||||
end
|
||||
|
||||
--- Get all tags in a repository
|
||||
--- @return string[] List of bookmarks, or empty list if none found
|
||||
function M.get_all_tags()
|
||||
|
||||
Reference in New Issue
Block a user