Skip to content

Commit

Permalink
feat(update): prompt to install breaking changes
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb committed Jul 5, 2024
1 parent 09fa1a0 commit 6be3fe5
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 18 deletions.
8 changes: 6 additions & 2 deletions lua/rocks/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
--- sync Synchronize installed rocks with rocks.toml.
--- It may take more than one sync to prune all rocks that can be pruned.
--- update Search for updated rocks and install them.
--- Use 'Rocks! update` to skip prompts to install updates
--- with breaking changes.
--- edit Edit the rocks.toml file.
--- pin {rock} Pin {rock} to the installed version.
--- Pinned rocks are ignored by ':Rocks update'.
Expand Down Expand Up @@ -119,8 +121,10 @@ end
---@type { [string]: RocksCmd }
local rocks_command_tbl = {
update = {
impl = function(_)
require("rocks.operations").update()
impl = function(_, opts)
require("rocks.operations").update(nil, {
skip_prompt = opts.bang,
})
end,
},
sync = {
Expand Down
70 changes: 70 additions & 0 deletions lua/rocks/operations/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,74 @@ helpers.is_installed = nio.create(function(rock_name)
return future.wait()
end, 1)

---@class RockUpdate
---@field name rock_name
---@field version vim.Version
---@field target_version vim.Version
---@field pretty string

---@param outdated_rocks table<rock_name, OutdatedRock>
---@return table<rock_name, RockUpdate[]>
function helpers.get_breaking_changes(outdated_rocks)
return vim.iter(outdated_rocks):fold(
{},
---@param acc table<rock_name, OutdatedRock>
---@param key rock_name
---@param rock OutdatedRock
function(acc, key, rock)
local _, version = pcall(vim.version.parse, rock.version)
local _, target_version = pcall(vim.version.parse, rock.target_version)
if
type(version) == "table"
and type(target_version) == "table"
and target_version.major > version.major
then
acc[key] = setmetatable({
name = rock.name,
version = version,
target_version = target_version,
}, {
__tostring = function()
return ("%s %s -> %s"):format(rock.name, tostring(version), tostring(target_version))
end,
})
end
return acc
end
)
end

---@type async fun(breaking_changes: table<rock_name, RockUpdate>, outdated_rocks: table<rock_name, OutdatedRock>): table<rock_name, OutdatedRock>
helpers.prompt_for_breaking_update = nio.create(function(breaking_changes, outdated_rocks)
local pretty_changes = vim.iter(breaking_changes)
:map(function(_, breaking_change)
return tostring(breaking_change)
end)
:totable()
local prompt = ([[
There are potential breaking changes! Update them anyway?
To skip this prompt, run 'Rocks! update'
Breaking changes:
%s
]]):format(table.concat(pretty_changes, "\n"))
nio.scheduler()
local choice = vim.fn.confirm(prompt, "&Yes\n&No", 2, "Question")
if choice == 1 then
return outdated_rocks
end
return vim.iter(outdated_rocks):fold(
{},
---@param acc table<rock_name, OutdatedRock>
---@param key rock_name
---@param rock OutdatedRock
function(acc, key, rock)
if not breaking_changes[key] then
acc[key] = rock
end
return acc
end
)
end, 2)

return helpers
44 changes: 31 additions & 13 deletions lua/rocks/operations/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,15 @@ operations.sync = function(user_rocks, on_complete)
end)
end

---@class rocks.UpdateOpts
---@field skip_prompt? boolean Whether to skip "install breaking changes?" prompts

--- Attempts to update every available rock if it is not pinned.
--- This function invokes a UI.
---@param on_complete? function
operations.update = function(on_complete)
---@param opts? rocks.UpdateOpts
operations.update = function(on_complete, opts)
opts = opts or {}
local progress_handle = progress.handle.create({
title = "Updating",
message = "Checking for updates...",
Expand All @@ -352,22 +357,36 @@ operations.update = function(on_complete)

local user_rocks = parse_rocks_toml()

local outdated_rocks = state.outdated_rocks()
local to_update = vim.iter(state.outdated_rocks()):fold(
{},
-- Filter unpinned rocks
---@param acc table<rock_name, OutdatedRock>
---@param key rock_name
---@param rock OutdatedRock
function(acc, key, rock)
local _, user_rock = get_rock_and_key(user_rocks, rock.name)
if user_rock and not user_rock.pin then
acc[key] = rock
end
return acc
end
)

local breaking_changes = helpers.get_breaking_changes(to_update)
if not opts.skip_prompt and not vim.tbl_isempty(breaking_changes) then
to_update = helpers.prompt_for_breaking_update(breaking_changes, to_update)
end
if config.reinstall_dev_rocks_on_update then
outdated_rocks = add_dev_rocks_for_update(outdated_rocks)
to_update = add_dev_rocks_for_update(to_update)
end
local external_update_handlers = handlers.get_update_handler_callbacks(user_rocks)

local total_update_count = #outdated_rocks + #external_update_handlers
local total_update_count = #to_update + #external_update_handlers

nio.scheduler()

local ct = 0
for name, rock in pairs(outdated_rocks) do
local _, user_rock = get_rock_and_key(user_rocks, rock.name)
if not user_rock or user_rock.pin then
goto skip_update
end
for name, rock in pairs(to_update) do
nio.scheduler()
progress_handle:report({
message = name,
Expand All @@ -392,7 +411,6 @@ operations.update = function(on_complete)
percentage = get_percentage(ct, total_update_count),
})
end
::skip_update::
end
for _, handler in pairs(external_update_handlers) do
local function report_progress(message)
Expand All @@ -407,7 +425,7 @@ operations.update = function(on_complete)
ct = ct + 1
end

if vim.tbl_isempty(outdated_rocks) and vim.tbl_isempty(external_update_handlers) then
if vim.tbl_isempty(to_update) and vim.tbl_isempty(external_update_handlers) then
progress_handle:report({ message = "Nothing to update!", percentage = 100 })
end
-- Update the version for all installed rocks in case rocks.toml is out of date [#380]
Expand Down Expand Up @@ -463,7 +481,7 @@ local function prompt_retry_install_with_dev(arg_list, rock_name, version)
local prompt = rocks[rock_name] and rock_name .. " only has a 'dev' version. Install anyway? "
or "Could not find " .. rock_name .. ". Search for 'dev' version?"
vim.schedule(function()
local choice = vim.fn.confirm(prompt, "y/n", "y", "Question")
local choice = vim.fn.confirm(prompt, "&Yes\n&No", 1, "Question")
if choice == 1 then
arg_list = vim.iter(arg_list)
:filter(function(arg)
Expand Down Expand Up @@ -581,7 +599,6 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim.
-- This should be fixed ASAP.
if not user_rocks.plugins then
local plugins = vim.empty_dict()
---@cast plugins rock_table
user_rocks.plugins = plugins
end

Expand All @@ -603,6 +620,7 @@ Use 'Rocks install {rock_name}' or install rocks-git.nvim.
elseif install_spec.opt or install_spec.pin then
-- toml-edit's metatable can't set a table directly.
-- Each field has to be set individually.
---@diagnostic disable-next-line: missing-fields
user_rocks.plugins[rock_name] = {}
user_rocks.plugins[rock_name].version = installed_rock.version
user_rocks.plugins[rock_name].opt = install_spec.opt
Expand Down
5 changes: 2 additions & 3 deletions lua/rocks/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ state.installed_rocks = nio.create(function()
return rocks
end)

---@type async fun(): {[string]: OutdatedRock}
---@type async fun(): table<rock_name, OutdatedRock>
state.outdated_rocks = nio.create(function()
local rocks = vim.empty_dict()
---@cast rocks {[string]: Rock}
local rocks = vim.empty_dict() --[[ @as table<rock_name, OutdatedRock> ]]

local future = nio.control.future()

Expand Down
11 changes: 11 additions & 0 deletions spec/operations/helpers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,15 @@ describe("operations.helpers", function()
print("GIT2_DIR not set. Skipping install_args test case")
end
end)
it("Detect breaking changes", function()
local result = helpers.get_breaking_changes({
foo = { name = "foo", version = "7.0.0", target_version = "8.0.0" },
bar = { name = "bar", version = "7.0.0", target_version = "7.1.0" },
baz = { name = "baz", version = "7.0.0", target_version = "7.1.1" },
})
assert.is_not_nil(result.foo)
assert.is_nil(result.bar)
assert.is_nil(result.baz)
assert.same("foo 7.0.0 -> 8.0.0", tostring(result.foo))
end)
end)

0 comments on commit 6be3fe5

Please sign in to comment.