Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): new updater with stable and nightly channels #590

Merged
merged 1 commit into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions lua/core/autocmds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ if is_available "feline.nvim" then
})
end

create_command("AstroUpdate", astronvim.update, { desc = "Update AstroNvim" })
create_command("AstroVersion", astronvim.version, { desc = "Check AstroNvim Version" })
create_command("AstroUpdate", astronvim.updater.update, { desc = "Update AstroNvim" })
create_command("AstroVersion", astronvim.updater.version, { desc = "Check AstroNvim Version" })
create_command("ToggleHighlightURL", astronvim.toggle_url_match, { desc = "Toggle URL Highlights" })
7 changes: 7 additions & 0 deletions lua/core/plugins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ local astro_plugins = {
},
}

if astronvim.updater.snapshot then
for plugin, options in pairs(astro_plugins) do
local pin = astronvim.updater.snapshot[plugin:match "/([^/]*)$"]
options.commit = pin and pin.commit or options.commit
end
end

local user_plugin_opts = astronvim.user_plugin_opts
local packer = astronvim.initialize_packer()
packer.startup {
Expand Down
86 changes: 86 additions & 0 deletions lua/core/utils/git.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
local git = { url = "https://github.com/" }

function git.cmd(args, ...)
return astronvim.cmd("git -C " .. vim.fn.stdpath "config" .. " " .. args, ...)
end

function git.fetch(remote, ...)
return git.cmd("fetch " .. remote, ...)
end

function git.pull(...)
return git.cmd("pull --rebase", ...)
end

function git.checkout(dest, ...)
return git.cmd("checkout " .. dest, ...)
end

function git.hard_reset(dest, ...)
return git.cmd("reset --hard " .. dest, ...)
end

function git.branch_contains(remote, branch, commit, ...)
return git.cmd("merge-base --is-ancestor " .. commit .. " " .. remote .. "/" .. branch, ...) ~= nil
end

function git.remote_add(remote, url, ...)
return git.cmd("remote add " .. remote .. " " .. url, ...)
end

function git.remote_update(remote, url, ...)
return git.cmd("remote set-url " .. remote .. " " .. url, ...)
end

function git.remote_url(remote, ...)
return astronvim.trim_or_nil(git.cmd("remote get-url " .. remote, ...))
end

function git.current_version(...)
return astronvim.trim_or_nil(git.cmd("describe --tags", ...))
end

function git.current_branch(...)
return astronvim.trim_or_nil(git.cmd("rev-parse --abbrev-ref HEAD", ...))
end

function git.local_head(...)
return astronvim.trim_or_nil(git.cmd("rev-parse HEAD", ...))
end

function git.remote_head(remote, branch, ...)
return astronvim.trim_or_nil(git.cmd("rev-list -n 1 " .. remote .. "/" .. branch, ...))
end

function git.tag_commit(tag, ...)
return astronvim.trim_or_nil(git.cmd("rev-list -n 1 " .. tag, ...))
end

function git.get_commit_range(start_hash, end_hash, ...)
local log = git.cmd("log --no-merges --pretty='format:[%h] %s' " .. start_hash .. ".." .. end_hash, ...)
return log and vim.fn.split(log, "\n") or {}
end

function git.get_versions(search, ...)
local tags = git.cmd("tag -l --sort=version:refname '" .. (search == "latest" and "v*" or search) .. "'", ...)
return tags and vim.fn.split(tags, "\n") or {}
end

function git.latest_version(versions, ...)
versions = versions and versions or git.get_versions(...)
return versions[#versions]
end

function git.parse_remote_url(str)
return vim.fn.match(str, astronvim.url_matcher) == -1
and git.url .. str .. (vim.fn.match(str, "/") == -1 and "/AstroNvim.git" or ".git")
or str
end

function git.breaking_changes(commits)
return vim.tbl_filter(function(v)
return vim.fn.match(v, "\\[.*\\]\\s\\+\\w\\+\\((\\w\\+)\\)\\?!:") ~= -1
end, commits)
end

return git
68 changes: 32 additions & 36 deletions lua/core/utils.lua → lua/core/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ astronvim.user_settings = load_module_file "user.init"
astronvim.default_compile_path = stdpath "config" .. "/lua/packer_compiled.lua"
astronvim.base_notification = { title = "AstroNvim" }
astronvim.user_terminals = {}
astronvim.url_matcher =
"\\v\\c%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)%([&:#*@~%_\\-=?!+;/0-9a-z]+%(%([.;/?]|[.][.]+)[&:#*@~%_\\-=?!+/0-9a-z]+|:\\d+|,%(%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)@![0-9a-z]+))*|\\([&:#*@~%_\\-=?!+;/.0-9a-z]*\\)|\\[[&:#*@~%_\\-=?!+;/.0-9a-z]*\\]|\\{%([&:#*@~%_\\-=?!+;/.0-9a-z]*|\\{[&:#*@~%_\\-=?!+;/.0-9a-z]*})\\})+"

local function func_or_extend(overrides, default, extend)
if extend then
Expand All @@ -50,6 +52,27 @@ function astronvim.conditional_func(func, condition, ...)
end
end

function astronvim.trim_or_nil(str)
return type(str) == "string" and vim.trim(str) or nil
end

function astronvim.echo(messages)
messages = messages or { { "\n" } }
if type(messages) == "table" then
vim.api.nvim_echo(messages, false, {})
end
end

function astronvim.confirm_prompt(messages)
if messages then
astronvim.echo(messages)
end
local confirmed = string.lower(vim.fn.input "(y/n) ") == "y"
astronvim.echo()
astronvim.echo()
return confirmed
end

local function user_setting_table(module)
local settings = astronvim.user_settings or {}
for tbl in string.gmatch(module, "([^%.]+)") do
Expand Down Expand Up @@ -231,11 +254,7 @@ end
function astronvim.set_url_match()
astronvim.delete_url_match()
if vim.g.highlighturl_enabled then
vim.fn.matchadd(
"HighlightURL",
"\\v\\c%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)%([&:#*@~%_\\-=?!+;/0-9a-z]+%(%([.;/?]|[.][.]+)[&:#*@~%_\\-=?!+/0-9a-z]+|:\\d+|,%(%(%(h?ttps?|ftp|file|ssh|git)://|[a-z]+[@][a-z]+[.][a-z]+:)@![0-9a-z]+))*|\\([&:#*@~%_\\-=?!+;/.0-9a-z]*\\)|\\[[&:#*@~%_\\-=?!+;/.0-9a-z]*\\]|\\{%([&:#*@~%_\\-=?!+;/.0-9a-z]*|\\{[&:#*@~%_\\-=?!+;/.0-9a-z]*})\\})+",
15
)
vim.fn.matchadd("HighlightURL", astronvim.url_matcher, 15)
end
end

Expand All @@ -244,38 +263,15 @@ function astronvim.toggle_url_match()
astronvim.set_url_match()
end

function astronvim.update()
(require "plenary.job")
:new({
command = "git",
args = { "pull", "--ff-only" },
cwd = stdpath "config",
on_exit = function(_, return_val)
if return_val == 0 then
vim.notify("Updated!", "info", astronvim.base_notification)
else
vim.notify("Update failed! Please try pulling manually.", "error", astronvim.base_notification)
end
end,
})
:sync()
function astronvim.cmd(cmd, show_error)
local result = vim.fn.system(cmd)
local success = vim.api.nvim_get_vvar "shell_error" == 0
if not success and (show_error == nil and true or show_error) then
vim.api.nvim_err_writeln("Error running command: " .. cmd .. "\nError message:\n" .. result)
end
return success and result or nil
end

function astronvim.version()
(require "plenary.job")
:new({
command = "git",
args = { "describe", "--tags" },
cwd = stdpath "config",
on_exit = function(out, return_val)
if return_val == 0 then
vim.notify("Version: " .. out:result()[1], "info", astronvim.base_notification)
else
vim.notify("Error retrieving version", "error", astronvim.base_notification)
end
end,
})
:start()
end
require "core.utils.updater"

return astronvim
160 changes: 160 additions & 0 deletions lua/core/utils/updater.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
local fn = vim.fn
local git = require "core.utils.git"
local options = astronvim.user_plugin_opts(
"updater",
{ remote = "origin", branch = "main", channel = "nightly", pin_plugins = true, show_changelog = true }
)

astronvim.updater = { options = options }
options.pin_plugins = options.pin_plugins == true and options.channel == "stable" and git.current_version()
or options.pin_plugins
if type(options.pin_plugins) == "string" then
local loaded, snapshot_file = pcall(fn.readfile, fn.stdpath "config" .. "/snapshots/" .. options.pin_plugins)
if loaded then
local _, snapshot = pcall(fn.json_decode, snapshot_file)
astronvim.updater.snapshot = type(snapshot) == "table" and snapshot or nil
end
end

function astronvim.updater.version()
local version = git.current_version()
if version then
vim.notify("Version: " .. version, "info", astronvim.base_notification)
end
end

local function echo_cancelled()
astronvim.echo { { "Update cancelled", "WarningMsg" } }
end

local function pretty_changelog(commits)
local changelog = {}
for _, commit in ipairs(commits) do
local hash, type, title = commit:match "(%[.*%])(.*:)(.*)"
if hash and type and title then
vim.list_extend(changelog, { { hash, "DiffText" }, { type, "Typedef" }, { title, "Title" }, { "\n" } })
end
end
return changelog
end

function astronvim.updater.update()
for remote, entry in pairs(options.remotes and options.remotes or {}) do
local url = git.parse_remote_url(entry)
local current_url = git.remote_url(remote, false)
local check_needed = false
if not current_url then
git.remote_add(remote, url)
check_needed = true
elseif
current_url ~= url
and astronvim.confirm_prompt {
{ "Remote " },
{ remote, "Title" },
{ " is currently set to " },
{ current_url, "WarningMsg" },
{ "\nWould you like us to set it to " },
{ url, "String" },
{ "?" },
}
then
git.remote_update(remote, url)
check_needed = true
end
if check_needed and git.remote_url(remote, false) ~= url then
vim.api.nvim_err_writeln("Error setting up remote " .. remote .. " to " .. url)
return
end
end
local is_stable = options.channel == "stable"
options.branch = is_stable and "main" or options.branch
if not git.fetch(options.remote) then
vim.api.nvim_err_writeln("Error fetching remote: " .. options.remote)
end
local local_branch = (options.remote == "origin" and "" or (options.remote .. "_")) .. options.branch
if git.current_branch() ~= local_branch then
astronvim.echo {
{ "Switching to branch: " },
{ options.remote .. "/" .. options.branch .. "\n\n", "String" },
}
if not git.checkout(local_branch, false) then
git.checkout("-b " .. local_branch .. " " .. options.remote .. "/" .. options.branch, false)
end
end
if git.current_branch() ~= local_branch then
vim.api.nvim_err_writeln("Error checking out branch: " .. options.remote .. "/" .. options.branch)
return
end
local source = git.local_head() -- calculate current commit
local target -- calculate target commit
if is_stable then -- if stable get tag commit
options.version = git.latest_version(git.get_versions(options.version or "latest"))
target = git.tag_commit(options.version)
elseif options.commit then -- if commit specified use it
target = git.branch_contains(options.remote, options.branch, options.commit) and options.commit or nil
else -- get most recent commit
target = git.remote_head(options.remote, options.branch)
end
if source and target then -- continue if current and target commits were found
if source == target then
astronvim.echo { { "No updates available", "String" } }
elseif -- prompt user if they want to accept update
not options.skip_prompts
and not astronvim.confirm_prompt {
{ "Update available to ", "Title" },
{ is_stable and options.version or target, "String" },
{ "\nContinue?" },
}
then
echo_cancelled()
return
else -- perform update
local changelog = git.get_commit_range(source, target)
local breaking = git.breaking_changes(changelog)
local breaking_prompt = { { "Update contains the following breaking changes:\n", "WarningMsg" } }
vim.list_extend(breaking_prompt, pretty_changelog(breaking))
vim.list_extend(breaking_prompt, { { "\nWould you like to continue?" } })
if #breaking > 0 and not options.skip_prompts and not astronvim.confirm_prompt(breaking_prompt) then
echo_cancelled()
return
end
local function attempt_update() -- helper function to attempt an update
if is_stable or options.commit then
return git.checkout(target, false)
else
return git.pull(false)
end
end
local updated = attempt_update()
if
not updated
and not options.skip_prompts
and not astronvim.confirm_prompt {
{ "Unable to pull due to local modifications to base files.\n", "ErrorMsg" },
{ "Reset local files and continue?" },
}
then
echo_cancelled()
return
elseif not updated then
git.hard_reset(source)
updated = attempt_update()
end
if not updated then
vim.api.nvim_err_writeln "Error ocurred performing update"
return
end
local summary = {
{ "AstroNvim updated successfully to ", "Title" },
{ git.current_version(), "String" },
{ "!\n", "Title" },
{ "Please restart and run :PackerSync.\n\n", "WarningMsg" },
}
if #changelog > 0 then
vim.list_extend(summary, { { "Changelog:\n" } })
vim.list_extend(summary, pretty_changelog(changelog))
end
astronvim.echo(summary)
end
end
end
1 change: 1 addition & 0 deletions snapshots/v1.3.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"telescope-media-files.nvim": {"commit": "513e4ee"}, "nvim-dap": {"commit": "60ffb68"}, "FixCursorHold.nvim": {"commit": "1bfb32e"}, "telescope-fzf-native.nvim": {"commit": "f0dba7d"}, "packer.nvim": {"commit": "4dedd3b"}, "nvim-notify": {"commit": "c6ca279"}, "nvim-lspconfig": {"commit": "c5dae15"}, "vim-table-mode": {"commit": "f47287d"}, "telescope-hop.nvim": {"commit": "337a1e9"}, "cmp-emoji": {"commit": "19075c3"}, "nvim-lastplace": {"commit": "30fe710"}, "null-ls.nvim": {"commit": "7b8560d"}, "which-key.nvim": {"commit": "bd4411a"}, "telescope-file-browser.nvim": {"commit": "4df59cc"}, "bufferline.nvim": {"commit": "e2b1e99"}, "nvim-autopairs": {"commit": "b9cc0a2"}, "zk-nvim": {"commit": "58fee43"}, "cmp_luasnip": {"commit": "a9de941"}, "friendly-snippets": {"commit": "974d792"}, "neovim-session-manager": {"commit": "f0b0501"}, "nvim-colorizer.lua": {"commit": "36c610a"}, "vim-simple-todo": {"commit": "e2eead5"}, "cmp-pandoc-references": {"commit": "2c808df"}, "gitsigns.nvim": {"commit": "27aeb2e"}, "SchemaStore.nvim": {"commit": "af73c24"}, "nvim-ts-autotag": {"commit": "044a05c"}, "sqls.nvim": {"commit": "6592b0c"}, "telescope.nvim": {"commit": "ad19bf6"}, "telescope-project.nvim": {"commit": "4658d78"}, "headlines.nvim": {"commit": "347ef03"}, "nvim-ts-context-commentstring": {"commit": "8834375"}, "nvim-toggleterm.lua": {"commit": "93c2f2c"}, "mini.nvim": {"commit": "2e209f3"}, "vim-wakatime": {"commit": "f86ca61"}, "nvim-treesitter-textobjects": {"commit": "b1e850b"}, "cmp-nvim-lsp": {"commit": "affe808"}, "LuaSnip": {"commit": "52f4aed"}, "neo-tree.nvim": {"commit": "a917174"}, "nvim-dap-ui": {"commit": "e5c3274"}, "popup.nvim": {"commit": "b7404d3"}, "nvim-web-devicons": {"commit": "8d2c533"}, "zen-mode.nvim": {"commit": "f1cc53d"}, "nvim-treesitter": {"commit": "889e901"}, "nui.nvim": {"commit": "abdbfab"}, "impatient.nvim": {"commit": "bcc2250"}, "feline.nvim": {"commit": "1ea4267"}, "hop.nvim": {"commit": "75f73d3"}, "neogen": {"commit": "a4b2fd5"}, "nabla.nvim": {"commit": "8d499bd"}, "nvim-lsp-installer": {"commit": "0d61e14"}, "nightfox.nvim": {"commit": "e602aca"}, "syntax-tree-surfer": {"commit": "2fe8d72"}, "indent-o-matic": {"commit": "0eff7e8"}, "cmp-calc": {"commit": "f7efc20"}, "smart-splits.nvim": {"commit": "8841720"}, "plenary.nvim": {"commit": "54b2e3d"}, "nvim-cmp": {"commit": "033a817"}, "aerial.nvim": {"commit": "ece90c4"}, "nvim-send-to-term": {"commit": "e0aa448"}, "telescope-bibtex.nvim": {"commit": "cd2640e"}, "cmp-latex-symbols": {"commit": "29dc9e5"}, "nvim-ts-rainbow": {"commit": "18cb3a4"}, "cmp-path": {"commit": "466b6b8"}, "cmp-buffer": {"commit": "12463cf"}}