mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	fix(pack): make newer version suggestions more robust
Problem: New version suggestions in update confirmation buffer might include semver tags that were committed later but for versions that are not greater than current. Like if versions committed in order `v0.2.0` - `v0.3.0` - `v0.2.1` - `v0.3.1`, then when on `v0.3.0` both `v0.2.1` and `v0.3.1` are suggested, but only the latter is newer as a version. This is because those tags are computed with post-processed `git tag --list --contains HEAD`. Solution: Compute all semver tags and filter only those greater than the latest version available at HEAD.
This commit is contained in:
		| @@ -178,16 +178,9 @@ end | |||||||
|  |  | ||||||
| --- @async | --- @async | ||||||
| --- @param cwd string | --- @param cwd string | ||||||
| --- @param opts? { contains?: string, points_at?: string } |  | ||||||
| --- @return string[] | --- @return string[] | ||||||
| local function git_get_tags(cwd, opts) | local function git_get_tags(cwd) | ||||||
|   local cmd = { 'tag', '--list', '--sort=-v:refname' } |   local cmd = { 'tag', '--list', '--sort=-v:refname' } | ||||||
|   if opts and opts.contains then |  | ||||||
|     vim.list_extend(cmd, { '--contains', opts.contains }) |  | ||||||
|   end |  | ||||||
|   if opts and opts.points_at then |  | ||||||
|     vim.list_extend(cmd, { '--points-at', opts.points_at }) |  | ||||||
|   end |  | ||||||
|   return vim.split(git_cmd(cmd, cwd), '\n') |   return vim.split(git_cmd(cmd, cwd), '\n') | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -212,6 +205,12 @@ local function is_version(x) | |||||||
|   return type(x) == 'string' or (type(x) == 'table' and pcall(x.has, x, '1')) |   return type(x) == 'string' or (type(x) == 'table' and pcall(x.has, x, '1')) | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --- @param x string | ||||||
|  | --- @return boolean | ||||||
|  | local function is_semver(x) | ||||||
|  |   return vim.version.parse(x) ~= nil | ||||||
|  | end | ||||||
|  |  | ||||||
| local function is_nonempty_string(x) | local function is_nonempty_string(x) | ||||||
|   return type(x) == 'string' and x ~= '' |   return type(x) == 'string' and x ~= '' | ||||||
| end | end | ||||||
| @@ -451,6 +450,21 @@ local function confirm_install(plug_list) | |||||||
|   return res |   return res | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --- @param tags string[] | ||||||
|  | --- @param version_range vim.VersionRange | ||||||
|  | local function get_last_semver_tag(tags, version_range) | ||||||
|  |   local last_tag, last_ver_tag --- @type string, vim.Version | ||||||
|  |   for _, tag in ipairs(tags) do | ||||||
|  |     local ver_tag = vim.version.parse(tag) | ||||||
|  |     if ver_tag then | ||||||
|  |       if version_range:has(ver_tag) and (not last_ver_tag or ver_tag > last_ver_tag) then | ||||||
|  |         last_tag, last_ver_tag = tag, ver_tag | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |   return last_tag | ||||||
|  | end | ||||||
|  |  | ||||||
| --- @async | --- @async | ||||||
| --- @param p vim.pack.Plug | --- @param p vim.pack.Plug | ||||||
| local function resolve_version(p) | local function resolve_version(p) | ||||||
| @@ -491,19 +505,10 @@ local function resolve_version(p) | |||||||
|   --- @cast version vim.VersionRange |   --- @cast version vim.VersionRange | ||||||
|  |  | ||||||
|   -- Choose the greatest/last version among all matching semver tags |   -- Choose the greatest/last version among all matching semver tags | ||||||
|   local last_ver_tag --- @type vim.Version |   p.info.version_str = get_last_semver_tag(tags, version) | ||||||
|   local semver_tags = {} --- @type string[] |  | ||||||
|   for _, tag in ipairs(tags) do |  | ||||||
|     local ver_tag = vim.version.parse(tag) |  | ||||||
|     if ver_tag then |  | ||||||
|       semver_tags[#semver_tags + 1] = tag |  | ||||||
|       if version:has(ver_tag) and (not last_ver_tag or ver_tag > last_ver_tag) then |  | ||||||
|         p.info.version_str, last_ver_tag = tag, ver_tag |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   if p.info.version_str == nil then |   if p.info.version_str == nil then | ||||||
|  |     local semver_tags = vim.tbl_filter(is_semver, tags) | ||||||
|  |     table.sort(semver_tags, vim.version.gt) | ||||||
|     local err = 'No versions fit constraint. Relax it or switch to branch. Available:' |     local err = 'No versions fit constraint. Relax it or switch to branch. Available:' | ||||||
|       .. list_in_line('Versions', semver_tags) |       .. list_in_line('Versions', semver_tags) | ||||||
|       .. list_in_line('Branches', branches) |       .. list_in_line('Branches', branches) | ||||||
| @@ -590,6 +595,7 @@ local function infer_update_details(p) | |||||||
|   local sha_head = assert(p.info.sha_head) |   local sha_head = assert(p.info.sha_head) | ||||||
|   local sha_target = assert(p.info.sha_target) |   local sha_target = assert(p.info.sha_target) | ||||||
|  |  | ||||||
|  |   -- Try showing log of changes (if any) | ||||||
|   if sha_head ~= sha_target then |   if sha_head ~= sha_target then | ||||||
|     -- `--topo-order` makes showing divergent branches nicer |     -- `--topo-order` makes showing divergent branches nicer | ||||||
|     -- `--decorate-refs` shows only tags near commits (not `origin/main`, etc.) |     -- `--decorate-refs` shows only tags near commits (not `origin/main`, etc.) | ||||||
| @@ -600,21 +606,29 @@ local function infer_update_details(p) | |||||||
|       '--decorate-refs=refs/tags', |       '--decorate-refs=refs/tags', | ||||||
|       sha_head .. '...' .. sha_target, |       sha_head .. '...' .. sha_target, | ||||||
|     }, p.path) |     }, p.path) | ||||||
|   else |  | ||||||
|     p.info.update_details = table.concat(git_get_tags(p.path, { contains = sha_target }), '\n') |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   if p.info.sha_head ~= p.info.sha_target or p.info.update_details == '' then |  | ||||||
|     return |     return | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   -- Remove tags pointing at target (there might be several) |   -- Suggest newer semver tags (i.e. greater than greatest past semver tag) | ||||||
|   local cur_tags = git_get_tags(p.path, { points_at = sha_target }) |   local all_semver_tags = vim.tbl_filter(is_semver, git_get_tags(p.path)) | ||||||
|   local new_tags_arr = vim.split(p.info.update_details, '\n') |   if #all_semver_tags == 0 then | ||||||
|   local function is_not_cur_tag(s) |     return | ||||||
|     return not vim.tbl_contains(cur_tags, s) |  | ||||||
|   end |   end | ||||||
|   p.info.update_details = table.concat(vim.tbl_filter(is_not_cur_tag, new_tags_arr), '\n') |  | ||||||
|  |   local older_tags = git_cmd({ 'tag', '--list', '--no-contains', sha_head }, p.path) | ||||||
|  |   local cur_tags = git_cmd({ 'tag', '--list', '--points-at', sha_head }, p.path) | ||||||
|  |   local past_tags = vim.split(older_tags, '\n') | ||||||
|  |   vim.list_extend(past_tags, vim.split(cur_tags, '\n')) | ||||||
|  |  | ||||||
|  |   local any_version = vim.version.range('*') --[[@as vim.VersionRange]] | ||||||
|  |   local last_version = get_last_semver_tag(past_tags, any_version) | ||||||
|  |  | ||||||
|  |   local newer_semver_tags = vim.tbl_filter(function(x) --- @param x string | ||||||
|  |     return vim.version.gt(x, last_version) | ||||||
|  |   end, all_semver_tags) | ||||||
|  |  | ||||||
|  |   table.sort(newer_semver_tags, vim.version.gt) | ||||||
|  |   p.info.update_details = table.concat(newer_semver_tags, '\n') | ||||||
| end | end | ||||||
|  |  | ||||||
| --- Map from plugin path to its data. | --- Map from plugin path to its data. | ||||||
| @@ -735,7 +749,7 @@ local function compute_feedback_lines_single(p) | |||||||
|  |  | ||||||
|     if p.info.update_details ~= '' then |     if p.info.update_details ~= '' then | ||||||
|       local details = p.info.update_details:gsub('\n', '\n• ') |       local details = p.info.update_details:gsub('\n', '\n• ') | ||||||
|       parts[#parts + 1] = '\n\nAvailable newer tags:\n• ' .. details |       parts[#parts + 1] = '\n\nAvailable newer versions:\n• ' .. details | ||||||
|     end |     end | ||||||
|   else |   else | ||||||
|     parts[#parts + 1] = table.concat({ |     parts[#parts + 1] = table.concat({ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Evgeni Chasnovski
					Evgeni Chasnovski