Skip to content

Commit

Permalink
fix(version): handle version checks more gracefully
Browse files Browse the repository at this point in the history
- Avoid emitting errors when the version check fails.
- Put version checking into separate module.
- Pull in upstream changes for vim.system.

Fixes: #948
Closes: #960
  • Loading branch information
lewis6991 committed Apr 1, 2024
1 parent 70584ff commit 3cb0f84
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 91 deletions.
2 changes: 1 addition & 1 deletion lua/gitsigns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ M.setup = async.void(function(cfg)

if config._test_mode then
require('gitsigns.attach')._setup()
require('gitsigns.git')._set_version(config._git_version)
require('gitsigns.git.version').check()
end

if config.auto_attach then
Expand Down
3 changes: 2 additions & 1 deletion lua/gitsigns/async.lua
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ end
--- @param argc number: The number of arguments of func. Must be included.
--- @return function: Returns an async function
function M.wrap(func, argc)
assert(argc)
assert(type(func) == 'function')
assert(type(argc) == 'number')
return function(...)
if not M.running() then
return func(...)
Expand Down
10 changes: 9 additions & 1 deletion lua/gitsigns/debug/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ local function eprint(msg, level)
if info then
msg = string.format('(ERROR) %s(%d): %s', info.short_src, info.currentline, msg)
end
M.messages[#M.messages + 1] = msg
M.messages[#M.messages + 1] = debug.traceback(msg)
if M.debug_mode then
error(msg, 3)
end
Expand All @@ -128,4 +128,12 @@ function M.eprintf(fmt, ...)
eprint(fmt:format(...), 1)
end

function M.assert(cond, msg)
if not cond then
eprint(msg, 1)
end

return not cond
end

return M
88 changes: 8 additions & 80 deletions lua/gitsigns/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ local system = require('gitsigns.system').system
local gs_config = require('gitsigns.config')
local config = gs_config.config

local uv = vim.loop
local startswith = vim.startswith
local uv = vim.uv or vim.loop

local dprint = require('gitsigns.debug.log').dprint
local dprintf = require('gitsigns.debug.log').dprintf
local eprint = require('gitsigns.debug.log').eprint
local err = require('gitsigns.message').error
local dprint = log.dprint
local dprintf = log.dprintf
local error_once = require('gitsigns.message').error_once

local check_version = require('gitsigns.git.version').check

local M = {}

--- @type fun(cmd: string[], opts?: SystemOpts): vim.SystemCompleted
--- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted
local asystem = async.wrap(system, 3)

--- @param file string
Expand Down Expand Up @@ -59,84 +58,14 @@ M.Obj = Obj
local Repo = {}
M.Repo = Repo

--- @class (exact) Gitsigns.Version
--- @field major integer
--- @field minor integer
--- @field patch integer

--- @param version string
--- @return Gitsigns.Version
local function parse_version(version)
assert(version:match('%d+%.%d+%.%w+'), 'Invalid git version: ' .. version)
local ret = {}
local parts = vim.split(version, '%.')
ret.major = assert(tonumber(parts[1]))
ret.minor = assert(tonumber(parts[2]))

if parts[3] == 'GIT' then
ret.patch = 0
else
local patch_ver = vim.split(parts[3], '-')
ret.patch = assert(tonumber(patch_ver[1]))
end

return ret
end

--- Usage: check_version{2,3}
--- @param version {[1]: integer, [2]:integer, [3]:integer}
--- @return boolean
local function check_version(version)
if not M.version then
return false
end
if M.version.major < version[1] then
return false
end
if version[2] and M.version.minor < version[2] then
return false
end
if version[3] and M.version.patch < version[3] then
return false
end
return true
end

--- @async
--- @param version string
function M._set_version(version)
if version ~= 'auto' then
M.version = parse_version(version)
return
end

--- @type vim.SystemCompleted
local obj = asystem({ 'git', '--version' })

local line = vim.split(obj.stdout or '', '\n', { plain = true })[1]
if not line then
err("Unable to detect git version as 'git --version' failed to return anything")
eprint(obj.stderr)
return
end
assert(type(line) == 'string', 'Unexpected output: ' .. line)
assert(startswith(line, 'git version'), 'Unexpected output: ' .. line)
local parts = vim.split(line, '%s+')
M.version = parse_version(parts[3])
end

--- @class Gitsigns.Git.JobSpec : SystemOpts
--- @class Gitsigns.Git.JobSpec : vim.SystemOpts
--- @field command? string
--- @field ignore_error? boolean

--- @param args string[]
--- @param spec? Gitsigns.Git.JobSpec
--- @return string[] stdout, string? stderr
local git_command = async.create(function(args, spec)
if not M.version then
M._set_version(config._git_version)
end

spec = spec or {}

local cmd = {
Expand Down Expand Up @@ -275,8 +204,7 @@ function M.get_repo_info(path, cmd, gitdir, toplevel)
local has_abs_gd = check_version({ 2, 13 })
local git_dir_opt = has_abs_gd and '--absolute-git-dir' or '--git-dir'

-- Wait for internal scheduler to settle before running command
-- https://github.com/lewis6991/gitsigns.nvim/pull/215
-- Wait for internal scheduler to settle before running command (#215)
scheduler()

local args = {}
Expand Down
101 changes: 101 additions & 0 deletions lua/gitsigns/git/version.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
local async = require('gitsigns.async')
local gs_config = require('gitsigns.config')

local log = require('gitsigns.debug.log')
local err = require('gitsigns.message').error
local system = require('gitsigns.system').system

local M = {}

--- @type fun(cmd: string[], opts?: vim.SystemOpts): vim.SystemCompleted
local asystem = async.wrap(system, 3)

--- @class (exact) Gitsigns.Version
--- @field major integer
--- @field minor integer
--- @field patch integer

--- @param version string
--- @return Gitsigns.Version
local function parse_version(version)
assert(version:match('%d+%.%d+%.%w+'), 'Invalid git version: ' .. version)
local ret = {}
local parts = vim.split(version, '%.')
ret.major = assert(tonumber(parts[1]))
ret.minor = assert(tonumber(parts[2]))

if parts[3] == 'GIT' then
ret.patch = 0
else
local patch_ver = vim.split(parts[3], '-')
ret.patch = assert(tonumber(patch_ver[1]))
end

return ret
end

local function set_version()
local version = gs_config.config._git_version
if version ~= 'auto' then
local ok, ret = pcall(parse_version, version)
if ok then
M.version = ret
else
err(ret --[[@as string]])
end
return
end

--- @type vim.SystemCompleted
local obj = asystem({ 'git', '--version' })
async.scheduler()

local line = vim.split(obj.stdout or '', '\n')[1]
if not line then
err("Unable to detect git version as 'git --version' failed to return anything")
log.eprint(obj.stderr)
return
end

-- Sometime 'git --version' returns an empty string (#948)
if log.assert(type(line) == 'string', 'Unexpected output: ' .. line) then
return
end

if log.assert(vim.startswith(line, 'git version'), 'Unexpected output: ' .. line) then
return
end

local parts = vim.split(line, '%s+')
M.version = parse_version(parts[3])
end

--- Usage: check_version{2,3}
--- @param version {[1]: integer, [2]:integer, [3]:integer}?
--- @return boolean
function M.check(version)
if not M.version then
set_version()
end

if not M.version then
return false
end

if not version then
return false
end

if M.version.major < version[1] then
return false
end
if version[2] and M.version.minor < version[2] then
return false
end
if version[3] and M.version.patch < version[3] then
return false
end
return true
end

return M
20 changes: 12 additions & 8 deletions lua/gitsigns/system/compat.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ local function close_handles(state)
close_handle(state.timer)
end

--- @class Pckr.SystemObj : vim.SystemObj
--- @class Gitsigns.SystemObj : vim.SystemObj
--- @field private _state vim.SystemState
local SystemObj = {}

Expand Down Expand Up @@ -59,14 +59,14 @@ function SystemObj:wait(timeout)

local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end)
end, nil, true)

if not done then
-- Send sigkill since this cannot be caught
self:_timeout(SIG.KILL)
vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
end)
end, nil, true)
end

return state.result
Expand Down Expand Up @@ -140,9 +140,13 @@ local function setup_input(input)
return assert(uv.new_pipe(false)), towrite
end

local environ = vim.fn.environ()
environ['NVIM'] = vim.v.servername
environ['NVIM_LISTEN_ADDRESS'] = nil
--- @return table<string,string>
local function base_env()
local env = vim.fn.environ() --- @type table<string,string>
env['NVIM'] = vim.v.servername
env['NVIM_LISTEN_ADDRESS'] = nil
return env
end

--- uv.spawn will completely overwrite the environment
--- when we just want to modify the existing one, so
Expand All @@ -156,7 +160,7 @@ local function setup_env(env, clear_env)
end

--- @type table<string,string|number>
env = vim.tbl_extend('force', environ, env or {})
env = vim.tbl_extend('force', base_env(), env or {})

local renv = {} --- @type string[]
for k, v in pairs(env) do
Expand Down Expand Up @@ -261,7 +265,7 @@ end
--- Run a system command
---
--- @param cmd string[]
--- @param opts? SystemOpts
--- @param opts? vim.SystemOpts
--- @param on_exit? fun(out: vim.SystemCompleted)
--- @return vim.SystemObj
local function system(cmd, opts, on_exit)
Expand Down

0 comments on commit 3cb0f84

Please sign in to comment.