mirror of
https://github.com/lewis6991/gitsigns.nvim
synced 2025-02-20 22:47:23 +00:00
Revert "Refactor git commands to be more OOP"
This reverts commit 718d61b712
.
This commit is contained in:
parent
dbd4c8a87d
commit
f11dc80ec4
151
lua/gitsigns.lua
151
lua/gitsigns.lua
@ -28,6 +28,7 @@ local git = require('gitsigns/git')
|
||||
local util = require('gitsigns/util')
|
||||
|
||||
local gs_hunks = require("gitsigns/hunks")
|
||||
local create_patch = gs_hunks.create_patch
|
||||
local process_hunks = gs_hunks.process_hunks
|
||||
local Hunk = gs_hunks.Hunk
|
||||
|
||||
@ -59,8 +60,32 @@ local CacheEntry = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local cache = {}
|
||||
|
||||
local ensure_file_in_index = async(function(bcache)
|
||||
if not bcache.object_name or bcache.has_conflicts then
|
||||
if not bcache.object_name then
|
||||
|
||||
await(git.add_file(bcache.toplevel, bcache.relpath))
|
||||
else
|
||||
|
||||
|
||||
await(git.update_index(bcache.toplevel, bcache.mode_bits, bcache.object_name, bcache.relpath))
|
||||
end
|
||||
|
||||
|
||||
_, bcache.object_name, bcache.mode_bits, bcache.has_conflicts =
|
||||
await(git.file_info(bcache.relpath, bcache.toplevel))
|
||||
end
|
||||
end)
|
||||
|
||||
local function get_cursor_hunk(bufnr, hunks)
|
||||
bufnr = bufnr or current_buf()
|
||||
hunks = hunks or cache[bufnr].hunks
|
||||
@ -122,15 +147,15 @@ local update = async(function(bufnr, bcache)
|
||||
|
||||
await(scheduler())
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local git_obj = bcache.git_obj
|
||||
local stage = bcache.has_conflicts and 1 or 0
|
||||
|
||||
if config.use_internal_diff then
|
||||
if not bcache.staged_text or config._refresh_staged_on_update then
|
||||
bcache.staged_text = await(git_obj:get_staged_text())
|
||||
bcache.staged_text = await(git.get_staged_text(bcache.toplevel, bcache.relpath, stage))
|
||||
end
|
||||
bcache.hunks = diff.run_diff(bcache.staged_text, buftext, config.diff_algorithm)
|
||||
else
|
||||
await(git_obj:get_staged(bcache.staged))
|
||||
await(git.get_staged(bcache.toplevel, bcache.relpath, stage, bcache.staged))
|
||||
bcache.hunks = await(git.run_diff(bcache.staged, buftext, config.diff_algorithm))
|
||||
end
|
||||
bcache.pending_signs = process_hunks(bcache.hunks)
|
||||
@ -141,7 +166,7 @@ local update = async(function(bufnr, bcache)
|
||||
|
||||
apply_win_signs(bufnr, bcache.pending_signs)
|
||||
|
||||
Status:update(bufnr, gs_hunks.get_summary(bcache.hunks, git_obj.abbrev_head))
|
||||
Status:update(bufnr, gs_hunks.get_summary(bcache.hunks, bcache.abbrev_head))
|
||||
|
||||
update_cnt = update_cnt + 1
|
||||
dprint(string.format('updates: %s, jobs: %s', update_cnt, util.job_cnt), bufnr, 'update')
|
||||
@ -165,17 +190,21 @@ local stage_hunk = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage lines. Please add the file to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
local hunk = get_cursor_hunk(bufnr, bcache.hunks)
|
||||
if not hunk then
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks({ hunk }))
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage lines. Please add the file to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
await(ensure_file_in_index(bcache))
|
||||
|
||||
local lines = create_patch(bcache.relpath, { hunk }, bcache.mode_bits)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
table.insert(bcache.staged_diffs, hunk)
|
||||
|
||||
@ -234,15 +263,23 @@ local undo_stage_hunk = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
local hunk = table.remove(bcache.staged_diffs)
|
||||
local hunk = bcache.staged_diffs[#bcache.staged_diffs]
|
||||
|
||||
if not hunk then
|
||||
print("No hunks to undo")
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks({ hunk }, true))
|
||||
local lines = create_patch(bcache.relpath, { hunk }, bcache.mode_bits, true)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
table.remove(bcache.staged_diffs)
|
||||
|
||||
local hunk_signs = process_hunks({ hunk })
|
||||
|
||||
await(scheduler())
|
||||
signs.add(config, bufnr, process_hunks({ hunk }))
|
||||
signs.add(config, bufnr, hunk_signs)
|
||||
end)
|
||||
|
||||
local stage_buffer = void_async(function()
|
||||
@ -260,19 +297,25 @@ local stage_buffer = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
if not util.path_exists(bcache.git_obj.file) then
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage file. Please add it to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks(hunks))
|
||||
await(ensure_file_in_index(bcache))
|
||||
|
||||
local lines = create_patch(bcache.relpath, hunks, bcache.mode_bits)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
for _, hunk in ipairs(hunks) do
|
||||
table.insert(bcache.staged_diffs, hunk)
|
||||
end
|
||||
|
||||
await(scheduler())
|
||||
|
||||
signs.remove(bufnr)
|
||||
|
||||
Status:clear_diff(bufnr)
|
||||
end)
|
||||
|
||||
@ -290,12 +333,17 @@ local reset_buffer_index = void_async(function()
|
||||
|
||||
|
||||
local hunks = bcache.staged_diffs
|
||||
bcache.staged_diffs = {}
|
||||
|
||||
await(bcache.git_obj:unstage_file())
|
||||
await(git.unstage_file(bcache.toplevel, bcache.file))
|
||||
|
||||
|
||||
local hunk_signs = process_hunks(hunks)
|
||||
|
||||
table.remove(bcache.staged_diffs)
|
||||
|
||||
await(scheduler())
|
||||
signs.add(config, bufnr, process_hunks(hunks))
|
||||
|
||||
signs.add(config, bufnr, hunk_signs)
|
||||
end)
|
||||
|
||||
local NavHunkOpts = {}
|
||||
@ -354,7 +402,8 @@ local function detach(bufnr, keep_signs)
|
||||
end
|
||||
|
||||
if not keep_signs then
|
||||
signs.remove(bufnr)
|
||||
|
||||
vim.fn.sign_unplace('gitsigns_ns', { buffer = bufnr })
|
||||
end
|
||||
|
||||
|
||||
@ -391,7 +440,7 @@ uv.fs_realpath(api.nvim_buf_get_name(bufnr)) or
|
||||
end)
|
||||
end
|
||||
|
||||
local function index_handler(cbuf)
|
||||
local function index_update_handler(cbuf)
|
||||
return void_async(function(err)
|
||||
if err then
|
||||
dprint('Index update error: ' .. err, cbuf, 'watcher_cb')
|
||||
@ -399,18 +448,22 @@ local function index_handler(cbuf)
|
||||
end
|
||||
dprint('Index update', cbuf, 'watcher_cb')
|
||||
local bcache = cache[cbuf]
|
||||
local git_obj = bcache.git_obj
|
||||
|
||||
await(git_obj:update_abbrev_head())
|
||||
_, _, bcache.abbrev_head = await(git.get_repo_info(bcache.toplevel))
|
||||
|
||||
await(scheduler())
|
||||
Status:update_head(cbuf, git_obj.abbrev_head)
|
||||
Status:update_head(cbuf, bcache.abbrev_head)
|
||||
|
||||
if not await(git_obj:update_file_info()) then
|
||||
local _, object_name0, mode_bits0, has_conflicts =
|
||||
await(git.file_info(bcache.file, bcache.toplevel))
|
||||
|
||||
if object_name0 == bcache.object_name then
|
||||
dprint('File not changed', cbuf, 'watcher_cb')
|
||||
return
|
||||
end
|
||||
|
||||
bcache.object_name = object_name0
|
||||
bcache.mode_bits = mode_bits0
|
||||
bcache.has_conflicts = has_conflicts
|
||||
bcache.staged_text = nil
|
||||
|
||||
await(update(cbuf, bcache))
|
||||
@ -446,13 +499,14 @@ end
|
||||
local attach = async(function(cbuf)
|
||||
await(scheduler())
|
||||
cbuf = cbuf or current_buf()
|
||||
if cache[cbuf] then
|
||||
if cache[cbuf] ~= nil then
|
||||
dprint('Already attached', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
dprint('Attaching', cbuf, 'attach')
|
||||
|
||||
if api.nvim_buf_line_count(cbuf) > config.max_file_length then
|
||||
local lc = api.nvim_buf_line_count(cbuf)
|
||||
if lc > config.max_file_length then
|
||||
dprint('Exceeds max_file_length', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
@ -476,38 +530,48 @@ local attach = async(function(cbuf)
|
||||
return
|
||||
end
|
||||
|
||||
local git_obj = await(git.obj:new(file))
|
||||
local toplevel, gitdir, abbrev_head = await(git.get_repo_info(file_dir))
|
||||
|
||||
if not git_obj.gitdir then
|
||||
if not gitdir then
|
||||
dprint('Not in git repo', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
await(scheduler())
|
||||
Status:update_head(cbuf, git_obj.abbrev_head)
|
||||
Status:update_head(cbuf, abbrev_head)
|
||||
|
||||
if not util.path_exists(file) or uv.fs_stat(file).type == 'directory' then
|
||||
dprint('Not a file', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
if not git_obj.relpath then
|
||||
|
||||
|
||||
await(scheduler())
|
||||
local staged = os.tmpname()
|
||||
|
||||
local relpath, object_name, mode_bits, has_conflicts =
|
||||
await(git.file_info(file, toplevel))
|
||||
|
||||
if not relpath then
|
||||
dprint('Cannot resolve file in repo', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
await(scheduler())
|
||||
|
||||
cache[cbuf] = {
|
||||
file = file,
|
||||
staged = os.tmpname(),
|
||||
relpath = relpath,
|
||||
object_name = object_name,
|
||||
mode_bits = mode_bits,
|
||||
toplevel = toplevel,
|
||||
gitdir = gitdir,
|
||||
abbrev_head = abbrev_head,
|
||||
username = await(git.command({ 'config', 'user.name' }))[1],
|
||||
has_conflicts = has_conflicts,
|
||||
staged = staged,
|
||||
staged_text = nil,
|
||||
hunks = {},
|
||||
staged_diffs = {},
|
||||
index_watcher = watch_index(cbuf, git_obj.gitdir, index_handler(cbuf)),
|
||||
git_obj = git_obj,
|
||||
index_watcher = watch_index(cbuf, gitdir, index_update_handler(cbuf)),
|
||||
}
|
||||
|
||||
|
||||
@ -592,6 +656,7 @@ function gitsigns_complete(arglead, line)
|
||||
return matches
|
||||
end
|
||||
|
||||
|
||||
local function setup_command()
|
||||
vim.cmd(table.concat({
|
||||
'command!',
|
||||
@ -711,7 +776,7 @@ local blame_line = void_async(function()
|
||||
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
local result = await(bcache.git_obj:run_blame(buftext, lnum))
|
||||
local result = await(git.run_blame(bcache.file, bcache.toplevel, buftext, lnum))
|
||||
|
||||
local date = os.date('%Y-%m-%d %H:%M', tonumber(result['author_time']))
|
||||
local lines = {
|
||||
@ -744,20 +809,20 @@ end
|
||||
local _current_line_blame = void_async(function()
|
||||
local bufnr = current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.git_obj.object_name then
|
||||
if not bcache or not bcache.object_name then
|
||||
return
|
||||
end
|
||||
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
local result = await(bcache.git_obj:run_blame(buftext, lnum))
|
||||
local result = await(git.run_blame(bcache.file, bcache.toplevel, buftext, lnum))
|
||||
|
||||
await(scheduler())
|
||||
|
||||
_current_line_blame_reset(bufnr)
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, lnum - 1, 0, {
|
||||
id = 1,
|
||||
virt_text = config.current_line_blame_formatter(bcache.git_obj.username, result),
|
||||
virt_text = config.current_line_blame_formatter(bcache.username, result),
|
||||
})
|
||||
end)
|
||||
|
||||
|
@ -1,53 +1,12 @@
|
||||
local a = require('plenary/async_lib/async')
|
||||
local JobSpec = require('plenary/job').JobSpec
|
||||
local await = a.await
|
||||
local async = a.async
|
||||
|
||||
local gsd = require("gitsigns/debug")
|
||||
local util = require('gitsigns/util')
|
||||
|
||||
local gs_hunks = require("gitsigns/hunks")
|
||||
local Hunk = gs_hunks.Hunk
|
||||
local hunks = require("gitsigns/hunks")
|
||||
local Hunk = hunks.Hunk
|
||||
|
||||
local uv = vim.loop
|
||||
local startswith = vim.startswith
|
||||
|
||||
local GJobSpec = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local Obj = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local M = {BlameInfo = {}, Version = {}, }
|
||||
|
||||
@ -81,6 +40,11 @@ local M = {BlameInfo = {}, Version = {}, }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -111,30 +75,148 @@ local function check_version(version)
|
||||
return true
|
||||
end
|
||||
|
||||
local command = a.wrap(function(args, spec, callback)
|
||||
local result = {}
|
||||
spec = spec or {}
|
||||
spec.command = 'git'
|
||||
spec.args = { '--no-pager', unpack(args) }
|
||||
spec.on_stdout = spec.on_stdout or function(_, line)
|
||||
table.insert(result, line)
|
||||
end
|
||||
if not spec.supress_stderr then
|
||||
spec.on_stderr = spec.on_stderr or function(err, line)
|
||||
if err then gsd.eprint(err) end
|
||||
if line then gsd.eprint(line) end
|
||||
end
|
||||
end
|
||||
local old_on_exit = spec.on_exit
|
||||
spec.on_exit = function()
|
||||
if old_on_exit then
|
||||
old_on_exit()
|
||||
end
|
||||
callback(result)
|
||||
end
|
||||
util.run_job(spec)
|
||||
M.file_info = a.wrap(function(
|
||||
file,
|
||||
toplevel,
|
||||
callback)
|
||||
|
||||
local relpath
|
||||
local object_name
|
||||
local mode_bits
|
||||
local stage
|
||||
local has_conflict = false
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'ls-files',
|
||||
'--stage',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
file,
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line)
|
||||
local parts = vim.split(line, '\t')
|
||||
if #parts > 1 then
|
||||
relpath = parts[2]
|
||||
local attrs = vim.split(parts[1], '%s+')
|
||||
stage = tonumber(attrs[3])
|
||||
if stage <= 1 then
|
||||
mode_bits = attrs[1]
|
||||
object_name = attrs[2]
|
||||
else
|
||||
has_conflict = true
|
||||
end
|
||||
else
|
||||
relpath = parts[1]
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(relpath, object_name, mode_bits, has_conflict)
|
||||
end,
|
||||
})
|
||||
end, 3)
|
||||
|
||||
M.get_staged = a.wrap(function(
|
||||
toplevel,
|
||||
relpath,
|
||||
stage,
|
||||
output,
|
||||
callback)
|
||||
|
||||
|
||||
|
||||
local outf = io.open(output, 'wb')
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'show',
|
||||
':' .. tostring(stage) .. ':' .. relpath,
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line)
|
||||
outf:write(line)
|
||||
outf:write('\n')
|
||||
end,
|
||||
on_exit = function()
|
||||
outf:close()
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 5)
|
||||
|
||||
M.get_staged_text = a.wrap(function(
|
||||
toplevel,
|
||||
relpath,
|
||||
stage,
|
||||
callback)
|
||||
|
||||
local result = {}
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'show',
|
||||
':' .. tostring(stage) .. ':' .. relpath,
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line)
|
||||
table.insert(result, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(result)
|
||||
end,
|
||||
})
|
||||
end, 4)
|
||||
|
||||
M.run_blame = a.wrap(function(
|
||||
file,
|
||||
toplevel,
|
||||
lines,
|
||||
lnum,
|
||||
callback)
|
||||
|
||||
local results = {}
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'blame',
|
||||
'--contents', '-',
|
||||
'-L', lnum .. ',+1',
|
||||
'--line-porcelain',
|
||||
file,
|
||||
},
|
||||
writer = lines,
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line)
|
||||
table.insert(results, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
local ret = {}
|
||||
if #results == 0 then
|
||||
callback({})
|
||||
return
|
||||
end
|
||||
local header = vim.split(table.remove(results, 1), ' ')
|
||||
ret.sha = header[1]
|
||||
ret.abbrev_sha = string.sub(ret.sha, 1, 8)
|
||||
ret.orig_lnum = tonumber(header[2])
|
||||
ret.final_lnum = tonumber(header[3])
|
||||
for _, l in ipairs(results) do
|
||||
if not startswith(l, '\t') then
|
||||
local cols = vim.split(l, ' ')
|
||||
local key = table.remove(cols, 1):gsub('-', '_')
|
||||
ret[key] = table.concat(cols, ' ')
|
||||
end
|
||||
end
|
||||
callback(ret)
|
||||
end,
|
||||
})
|
||||
end, 5)
|
||||
|
||||
local function process_abbrev_head(gitdir, head_str)
|
||||
if not gitdir then
|
||||
return head_str
|
||||
@ -152,27 +234,132 @@ local function process_abbrev_head(gitdir, head_str)
|
||||
return head_str
|
||||
end
|
||||
|
||||
local get_repo_info = async(function(path)
|
||||
M.get_repo_info = a.wrap(function(
|
||||
path, callback)
|
||||
local out = {}
|
||||
|
||||
|
||||
|
||||
local has_abs_gd = check_version({ 2, 13 })
|
||||
local git_dir_opt = has_abs_gd and '--absolute-git-dir' or '--git-dir'
|
||||
|
||||
local results = await(command({
|
||||
'rev-parse', '--show-toplevel', git_dir_opt, '--abbrev-ref', 'HEAD',
|
||||
}, {
|
||||
supress_stderr = true,
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = { 'rev-parse',
|
||||
'--show-toplevel',
|
||||
git_dir_opt,
|
||||
'--abbrev-ref', 'HEAD',
|
||||
},
|
||||
cwd = path,
|
||||
}))
|
||||
on_stdout = function(_, line)
|
||||
if not has_abs_gd and #out == 1 then
|
||||
line = uv.fs_realpath(line)
|
||||
end
|
||||
table.insert(out, line)
|
||||
end,
|
||||
on_exit = vim.schedule_wrap(function()
|
||||
local toplevel = out[1]
|
||||
local gitdir = out[2]
|
||||
local abbrev_head = process_abbrev_head(gitdir, out[3])
|
||||
callback(toplevel, gitdir, abbrev_head)
|
||||
end),
|
||||
})
|
||||
end, 2)
|
||||
|
||||
local toplevel = results[1]
|
||||
local gitdir = results[2]
|
||||
if not has_abs_gd then
|
||||
gitdir = uv.fs_realpath(gitdir)
|
||||
end
|
||||
local abbrev_head = process_abbrev_head(gitdir, results[3])
|
||||
return toplevel, gitdir, abbrev_head
|
||||
end)
|
||||
M.stage_lines = a.wrap(function(
|
||||
toplevel, lines, callback)
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = { 'apply', '--cached', '--unidiff-zero', '-' },
|
||||
cwd = toplevel,
|
||||
writer = lines,
|
||||
on_stderr = function(_, line)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot stage lines. Command stderr:\n\n' .. s)
|
||||
end
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 3)
|
||||
|
||||
M.add_file = a.wrap(function(
|
||||
toplevel, file, callback)
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = { 'add', '--intent-to-add', file },
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot add file. Command stderr:\n\n' .. s)
|
||||
end
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 3)
|
||||
|
||||
M.unstage_file = a.wrap(function(
|
||||
toplevel, file, callback)
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = { 'reset', file },
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot unstage file. Command stderr:\n\n' .. s)
|
||||
end
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 3)
|
||||
|
||||
M.update_index = a.wrap(function(
|
||||
toplevel,
|
||||
mode_bits,
|
||||
object_name,
|
||||
file,
|
||||
callback)
|
||||
|
||||
local status = true
|
||||
local err = {}
|
||||
local cacheinfo = table.concat({ mode_bits, object_name, file }, ',')
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = { 'update-index', '--add', '--cacheinfo', cacheinfo },
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot update index. Command stderr:\n\n' .. s)
|
||||
end
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 5)
|
||||
|
||||
local function write_to_file(path, text)
|
||||
local f = io.open(path, 'wb')
|
||||
@ -183,14 +370,15 @@ local function write_to_file(path, text)
|
||||
f:close()
|
||||
end
|
||||
|
||||
M.run_diff = async(function(
|
||||
M.run_diff = a.wrap(function(
|
||||
staged,
|
||||
text,
|
||||
diff_algo)
|
||||
diff_algo,
|
||||
callback)
|
||||
|
||||
local results = {}
|
||||
|
||||
local buffile = os.tmpname() .. '_buf'
|
||||
local buffile = staged .. '_buf'
|
||||
write_to_file(buffile, text)
|
||||
|
||||
|
||||
@ -209,196 +397,89 @@ M.run_diff = async(function(
|
||||
|
||||
|
||||
|
||||
await(command({
|
||||
'-c', 'core.safecrlf=false',
|
||||
'diff',
|
||||
'--color=never',
|
||||
'--diff-algorithm=' .. diff_algo,
|
||||
'--patch-with-raw',
|
||||
'--unified=0',
|
||||
staged,
|
||||
buffile,
|
||||
}, {
|
||||
util.run_job({
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'-c', 'core.safecrlf=false',
|
||||
'diff',
|
||||
'--color=never',
|
||||
'--diff-algorithm=' .. diff_algo,
|
||||
'--patch-with-raw',
|
||||
'--unified=0',
|
||||
staged,
|
||||
buffile,
|
||||
},
|
||||
on_stdout = function(_, line)
|
||||
if startswith(line, '@@') then
|
||||
table.insert(results, gs_hunks.parse_diff_line(line))
|
||||
elseif #results > 0 then
|
||||
table.insert(results[#results].lines, line)
|
||||
table.insert(results, hunks.parse_diff_line(line))
|
||||
else
|
||||
if #results > 0 then
|
||||
table.insert(results[#results].lines, line)
|
||||
end
|
||||
end
|
||||
end,
|
||||
}))
|
||||
os.remove(buffile)
|
||||
return results
|
||||
end)
|
||||
on_stderr = function(err, line)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
os.remove(buffile)
|
||||
callback(results)
|
||||
end,
|
||||
})
|
||||
end, 4)
|
||||
|
||||
M.set_version = async(function(version)
|
||||
M.set_version = a.wrap(function(version, callback)
|
||||
if version ~= 'auto' then
|
||||
M.version = parse_version(version)
|
||||
callback()
|
||||
return
|
||||
end
|
||||
local results = await(command({ '--version' }))
|
||||
local line = results[1]
|
||||
assert(startswith(line, 'git version'), 'Unexpected output: ' .. line)
|
||||
local parts = vim.split(line, '%s+')
|
||||
M.version = parse_version(parts[3])
|
||||
end)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
local O = {}
|
||||
|
||||
|
||||
O.command = async(function(self, args, spec)
|
||||
spec = spec or {}
|
||||
spec.cwd = self.toplevel
|
||||
return await(command({ '--git-dir=' .. self.gitdir, unpack(args) }, spec))
|
||||
end)
|
||||
|
||||
O.update_abbrev_head = async(function(self)
|
||||
_, _, self.abbrev_head = await(get_repo_info(self.toplevel))
|
||||
end)
|
||||
|
||||
O.update_file_info = async(function(self)
|
||||
local old_object_name = self.object_name
|
||||
_, self.object_name, self.mode_bits, self.has_conflicts = await(self:file_info())
|
||||
|
||||
return old_object_name ~= self.object_name
|
||||
end)
|
||||
|
||||
O.file_info = async(function(self)
|
||||
local results = await(self:command({
|
||||
'ls-files',
|
||||
'--stage',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
self.file,
|
||||
}))
|
||||
|
||||
local relpath
|
||||
local object_name
|
||||
local mode_bits
|
||||
local stage
|
||||
local has_conflict = false
|
||||
for _, line in ipairs(results) do
|
||||
local parts = vim.split(line, '\t')
|
||||
if #parts > 1 then
|
||||
relpath = parts[2]
|
||||
local attrs = vim.split(parts[1], '%s+')
|
||||
stage = tonumber(attrs[3])
|
||||
if stage <= 1 then
|
||||
mode_bits = attrs[1]
|
||||
object_name = attrs[2]
|
||||
else
|
||||
has_conflict = true
|
||||
end
|
||||
else
|
||||
relpath = parts[1]
|
||||
end
|
||||
end
|
||||
return relpath, object_name, mode_bits, has_conflict
|
||||
end)
|
||||
|
||||
O.unstage_file = async(function(self)
|
||||
await(self:command({ 'reset', self.file }))
|
||||
end)
|
||||
|
||||
|
||||
O.get_staged_text = async(function(self)
|
||||
local stage = self.has_conflicts and 1 or 0
|
||||
return await(self:command({ 'show', ':' .. tostring(stage) .. ':' .. self.relpath }, {
|
||||
supress_stderr = true,
|
||||
}))
|
||||
end)
|
||||
|
||||
|
||||
O.get_staged = async(function(self, output_file)
|
||||
local stage = self.has_conflicts and 1 or 0
|
||||
|
||||
|
||||
local outf = io.open(output_file, 'wb')
|
||||
await(self:command({
|
||||
'show', ':' .. tostring(stage) .. ':' .. self.relpath,
|
||||
}, {
|
||||
supress_stderr = true,
|
||||
util.run_job({
|
||||
command = 'git', args = { '--version' },
|
||||
on_stdout = function(_, line)
|
||||
outf:write(line)
|
||||
outf:write('\n')
|
||||
assert(startswith(line, 'git version'), 'Unexpected output: ' .. line)
|
||||
local parts = vim.split(line, '%s+')
|
||||
M.version = parse_version(parts[3])
|
||||
end,
|
||||
}))
|
||||
outf:close()
|
||||
end)
|
||||
on_stderr = function(err, line)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback()
|
||||
end,
|
||||
})
|
||||
end, 2)
|
||||
|
||||
O.run_blame = async(function(self, lines, lnum)
|
||||
local results = await(self:command({
|
||||
'blame',
|
||||
'--contents', '-',
|
||||
'-L', lnum .. ',+1',
|
||||
'--line-porcelain',
|
||||
self.file,
|
||||
}, {
|
||||
writer = lines,
|
||||
}))
|
||||
if #results == 0 then
|
||||
return {}
|
||||
end
|
||||
local header = vim.split(table.remove(results, 1), ' ')
|
||||
|
||||
local ret = {}
|
||||
ret.sha = header[1]
|
||||
ret.orig_lnum = tonumber(header[2])
|
||||
ret.final_lnum = tonumber(header[3])
|
||||
ret.abbrev_sha = string.sub(ret.sha, 1, 8)
|
||||
for _, l in ipairs(results) do
|
||||
if not startswith(l, '\t') then
|
||||
local cols = vim.split(l, ' ')
|
||||
local key = table.remove(cols, 1):gsub('-', '_')
|
||||
ret[key] = table.concat(cols, ' ')
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
O.ensure_file_in_index = async(function(self)
|
||||
if not self.object_name or self.has_conflicts then
|
||||
if not self.object_name then
|
||||
|
||||
await(self:command({ 'add', '--intent-to-add', self.file }))
|
||||
else
|
||||
|
||||
|
||||
local info = table.concat({ self.mode_bits, self.object_name, self.relpath }, ',')
|
||||
await(self:command({ 'update-index', '--add', '--cacheinfo', info }))
|
||||
end
|
||||
|
||||
|
||||
_, self.object_name, self.mode_bits, self.has_conflicts = await(self:file_info())
|
||||
end
|
||||
end)
|
||||
|
||||
O.stage_hunks = async(function(self, hunks, invert)
|
||||
await(self:ensure_file_in_index())
|
||||
await(self:command({
|
||||
'apply', '--cached', '--unidiff-zero', '-',
|
||||
}, {
|
||||
writer = gs_hunks.create_patch(self.relpath, hunks, self.mode_bits, invert),
|
||||
}))
|
||||
end)
|
||||
|
||||
O.new = a.async(function(self, file)
|
||||
self.file = file
|
||||
self.toplevel, self.gitdir, self.abbrev_head =
|
||||
await(get_repo_info(util.dirname(file)))
|
||||
|
||||
self.relpath, self.object_name, self.mode_bits, self.has_conflicts =
|
||||
await(self:file_info())
|
||||
|
||||
self.username = await(command({ 'config', 'user.name' }))[1]
|
||||
|
||||
return self
|
||||
end)
|
||||
|
||||
M.obj = O
|
||||
M.command = a.wrap(function(args, callback)
|
||||
local result = {}
|
||||
util.run_job({
|
||||
command = 'git', args = args,
|
||||
on_stdout = function(_, line)
|
||||
table.insert(result, line)
|
||||
end,
|
||||
on_stderr = function(err, line)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(result)
|
||||
end,
|
||||
})
|
||||
end, 2)
|
||||
|
||||
return M
|
||||
|
173
teal/gitsigns.tl
173
teal/gitsigns.tl
@ -28,6 +28,7 @@ local git = require('gitsigns/git')
|
||||
local util = require('gitsigns/util')
|
||||
|
||||
local gs_hunks = require("gitsigns/hunks")
|
||||
local create_patch = gs_hunks.create_patch
|
||||
local process_hunks = gs_hunks.process_hunks
|
||||
local Hunk = gs_hunks.Hunk
|
||||
|
||||
@ -50,17 +51,41 @@ local namespace: integer
|
||||
|
||||
local record CacheEntry
|
||||
file : string
|
||||
relpath : string
|
||||
object_name : string
|
||||
mode_bits : string
|
||||
toplevel : string
|
||||
gitdir : string
|
||||
username : string
|
||||
staged : string
|
||||
staged_text : {string}
|
||||
abbrev_head : string
|
||||
has_conflicts : boolean
|
||||
hunks : {Hunk}
|
||||
staged_diffs : {Hunk}
|
||||
pending_signs : {integer:Sign}
|
||||
index_watcher : vim.loop.FSPollObj -- Timer object watching the files index
|
||||
git_obj : git.Obj
|
||||
end
|
||||
|
||||
local cache: {integer:CacheEntry} = {}
|
||||
|
||||
local ensure_file_in_index = async(function(bcache: CacheEntry)
|
||||
if not bcache.object_name or bcache.has_conflicts then
|
||||
if not bcache.object_name then
|
||||
-- If there is no object_name then it is not yet in the index so add it
|
||||
await(git.add_file(bcache.toplevel, bcache.relpath))
|
||||
else
|
||||
-- Update the index with the common ancestor (stage 1) which is what bcache
|
||||
-- stores
|
||||
await(git.update_index(bcache.toplevel, bcache.mode_bits, bcache.object_name, bcache.relpath))
|
||||
end
|
||||
|
||||
-- Update the cache
|
||||
_, bcache.object_name, bcache.mode_bits, bcache.has_conflicts =
|
||||
await(git.file_info(bcache.relpath, bcache.toplevel))
|
||||
end
|
||||
end)
|
||||
|
||||
local function get_cursor_hunk(bufnr: integer, hunks: {Hunk}): Hunk
|
||||
bufnr = bufnr or current_buf()
|
||||
hunks = hunks or cache[bufnr].hunks
|
||||
@ -122,15 +147,15 @@ local update = async(function(bufnr: integer, bcache: CacheEntry)
|
||||
|
||||
await(scheduler())
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local git_obj = bcache.git_obj
|
||||
local stage = bcache.has_conflicts and 1 or 0
|
||||
|
||||
if config.use_internal_diff then
|
||||
if not bcache.staged_text or config._refresh_staged_on_update then
|
||||
bcache.staged_text = await(git_obj:get_staged_text())
|
||||
bcache.staged_text = await(git.get_staged_text(bcache.toplevel, bcache.relpath, stage))
|
||||
end
|
||||
bcache.hunks = diff.run_diff(bcache.staged_text, buftext, config.diff_algorithm)
|
||||
else
|
||||
await(git_obj:get_staged(bcache.staged))
|
||||
await(git.get_staged(bcache.toplevel, bcache.relpath, stage, bcache.staged))
|
||||
bcache.hunks = await(git.run_diff(bcache.staged, buftext, config.diff_algorithm))
|
||||
end
|
||||
bcache.pending_signs = process_hunks(bcache.hunks)
|
||||
@ -141,7 +166,7 @@ local update = async(function(bufnr: integer, bcache: CacheEntry)
|
||||
-- provider as they are drawn.
|
||||
apply_win_signs(bufnr, bcache.pending_signs)
|
||||
|
||||
Status:update(bufnr, gs_hunks.get_summary(bcache.hunks, git_obj.abbrev_head))
|
||||
Status:update(bufnr, gs_hunks.get_summary(bcache.hunks, bcache.abbrev_head))
|
||||
|
||||
update_cnt = update_cnt + 1
|
||||
dprint(string.format('updates: %s, jobs: %s', update_cnt, util.job_cnt), bufnr, 'update')
|
||||
@ -165,17 +190,21 @@ local stage_hunk = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage lines. Please add the file to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
local hunk = get_cursor_hunk(bufnr, bcache.hunks)
|
||||
if not hunk then
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks({hunk}))
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage lines. Please add the file to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
await(ensure_file_in_index(bcache))
|
||||
|
||||
local lines = create_patch(bcache.relpath, {hunk}, bcache.mode_bits)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
table.insert(bcache.staged_diffs, hunk)
|
||||
|
||||
@ -234,15 +263,23 @@ local undo_stage_hunk = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
local hunk = table.remove(bcache.staged_diffs)
|
||||
local hunk = bcache.staged_diffs[#bcache.staged_diffs]
|
||||
|
||||
if not hunk then
|
||||
print("No hunks to undo")
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks({hunk}, true))
|
||||
local lines = create_patch(bcache.relpath, {hunk}, bcache.mode_bits, true)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
table.remove(bcache.staged_diffs)
|
||||
|
||||
local hunk_signs = process_hunks({hunk})
|
||||
|
||||
await(scheduler())
|
||||
signs.add(config, bufnr, process_hunks({hunk}))
|
||||
signs.add(config, bufnr, hunk_signs)
|
||||
end)
|
||||
|
||||
local stage_buffer = void_async(function()
|
||||
@ -257,22 +294,28 @@ local stage_buffer = void_async(function()
|
||||
local hunks = bcache.hunks
|
||||
if #hunks == 0 then
|
||||
print("No unstaged changes in file to stage")
|
||||
return
|
||||
return
|
||||
end
|
||||
|
||||
if not util.path_exists(bcache.git_obj.file) then
|
||||
if not util.path_exists(bcache.file) then
|
||||
print("Error: Cannot stage file. Please add it to the working tree.")
|
||||
return
|
||||
end
|
||||
|
||||
await(bcache.git_obj:stage_hunks(hunks))
|
||||
await(ensure_file_in_index(bcache))
|
||||
|
||||
for _, hunk in ipairs(hunks) do
|
||||
local lines = create_patch(bcache.relpath, hunks, bcache.mode_bits)
|
||||
|
||||
await(git.stage_lines(bcache.toplevel, lines))
|
||||
|
||||
for _,hunk in ipairs(hunks) do
|
||||
table.insert(bcache.staged_diffs, hunk)
|
||||
end
|
||||
|
||||
await(scheduler())
|
||||
|
||||
signs.remove(bufnr)
|
||||
|
||||
Status:clear_diff(bufnr)
|
||||
end)
|
||||
|
||||
@ -283,19 +326,24 @@ local reset_buffer_index = void_async(function()
|
||||
return
|
||||
end
|
||||
|
||||
-- `bcache.staged_diffs` won't contain staged changes outside of current
|
||||
-- neovim session so signs added from this unstage won't be complete They will
|
||||
-- however be fixed by index watcher and properly updated We should implement
|
||||
-- some sort of initial population from git diff, after that this function can
|
||||
-- be improved to check if any staged hunks exists and it can undo changes
|
||||
-- using git apply line by line instead of reseting whole file
|
||||
-- `bcache.staged_diffs` won't contain staged changes outside of current neovim session
|
||||
-- so signs added from this unstage won't be complete
|
||||
-- They will however be fixed by index watcher and properly updated
|
||||
-- We should implement some sort of initial population from git diff,
|
||||
-- after that this function can be improved to check if any staged hunks exists
|
||||
-- and it can undo changes using git apply line by line instead of reseting whole file
|
||||
local hunks = bcache.staged_diffs
|
||||
bcache.staged_diffs = {}
|
||||
|
||||
await(bcache.git_obj:unstage_file())
|
||||
await(git.unstage_file(bcache.toplevel, bcache.file))
|
||||
|
||||
-- Signs need to be generated before the `table.remove` below as it modifies the hunks array
|
||||
local hunk_signs = process_hunks(hunks)
|
||||
|
||||
table.remove(bcache.staged_diffs)
|
||||
|
||||
await(scheduler())
|
||||
signs.add(config, bufnr, process_hunks(hunks))
|
||||
|
||||
signs.add(config, bufnr, hunk_signs)
|
||||
end)
|
||||
|
||||
local record NavHunkOpts
|
||||
@ -341,7 +389,7 @@ end
|
||||
-- When this is called interactively (with no arguments) we want to remove all
|
||||
-- the signs, however if called via a detach event (due to nvim_buf_attach) then
|
||||
-- we don't want to clear the signs in case the buffer is just being updated due
|
||||
-- to the file externally changing. When this happens a detach and attach event
|
||||
-- to the file externally changeing. When this happens a detach and attach event
|
||||
-- happen in sequence and so we keep the old signs to stop the sign column width
|
||||
-- moving about between updates.
|
||||
local function detach(bufnr: integer, keep_signs: boolean)
|
||||
@ -354,7 +402,8 @@ local function detach(bufnr: integer, keep_signs: boolean)
|
||||
end
|
||||
|
||||
if not keep_signs then
|
||||
signs.remove(bufnr) -- Remove all signs
|
||||
-- Remove all the signs
|
||||
vim.fn.sign_unplace('gitsigns_ns', {buffer = bufnr})
|
||||
end
|
||||
|
||||
-- Clear status variables
|
||||
@ -391,7 +440,7 @@ local function get_buf_path(bufnr: integer): string
|
||||
end)
|
||||
end
|
||||
|
||||
local function index_handler(cbuf: integer): function
|
||||
local function index_update_handler(cbuf: integer): function
|
||||
return void_async(function(err: string)
|
||||
if err then
|
||||
dprint('Index update error: '..err, cbuf, 'watcher_cb')
|
||||
@ -399,19 +448,23 @@ local function index_handler(cbuf: integer): function
|
||||
end
|
||||
dprint('Index update', cbuf, 'watcher_cb')
|
||||
local bcache = cache[cbuf]
|
||||
local git_obj = bcache.git_obj
|
||||
|
||||
await(git_obj:update_abbrev_head())
|
||||
_, _, bcache.abbrev_head = await(git.get_repo_info(bcache.toplevel))
|
||||
|
||||
await(scheduler())
|
||||
Status:update_head(cbuf, git_obj.abbrev_head)
|
||||
Status:update_head(cbuf, bcache.abbrev_head)
|
||||
|
||||
if not await(git_obj:update_file_info()) then
|
||||
local _, object_name0, mode_bits0, has_conflicts =
|
||||
await(git.file_info(bcache.file, bcache.toplevel))
|
||||
|
||||
if object_name0 == bcache.object_name then
|
||||
dprint('File not changed', cbuf, 'watcher_cb')
|
||||
return
|
||||
end
|
||||
|
||||
bcache.staged_text = nil -- Invalidate
|
||||
bcache.object_name = object_name0
|
||||
bcache.mode_bits = mode_bits0
|
||||
bcache.has_conflicts = has_conflicts
|
||||
bcache.staged_text = nil -- Invalidate
|
||||
|
||||
await(update(cbuf, bcache))
|
||||
end)
|
||||
@ -446,13 +499,14 @@ end
|
||||
local attach = async(function(cbuf: integer)
|
||||
await(scheduler())
|
||||
cbuf = cbuf or current_buf()
|
||||
if cache[cbuf] then
|
||||
if cache[cbuf] ~= nil then
|
||||
dprint('Already attached', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
dprint('Attaching', cbuf, 'attach')
|
||||
|
||||
if api.nvim_buf_line_count(cbuf) > config.max_file_length then
|
||||
local lc = api.nvim_buf_line_count(cbuf)
|
||||
if lc > config.max_file_length then
|
||||
dprint('Exceeds max_file_length', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
@ -476,38 +530,48 @@ local attach = async(function(cbuf: integer)
|
||||
return
|
||||
end
|
||||
|
||||
local git_obj = await(git.obj:new(file))
|
||||
local toplevel, gitdir, abbrev_head = await(git.get_repo_info(file_dir))
|
||||
|
||||
if not git_obj.gitdir then
|
||||
if not gitdir then
|
||||
dprint('Not in git repo', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
await(scheduler())
|
||||
Status:update_head(cbuf, git_obj.abbrev_head)
|
||||
Status:update_head(cbuf, abbrev_head)
|
||||
|
||||
if not util.path_exists(file) or uv.fs_stat(file).type == 'directory' then
|
||||
dprint('Not a file', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
if not git_obj.relpath then
|
||||
-- On windows os.tmpname() crashes in callback threads so initialise this
|
||||
-- variable on the main thread.
|
||||
await(scheduler())
|
||||
local staged = os.tmpname() -- Temp filename of staged file
|
||||
|
||||
local relpath, object_name, mode_bits, has_conflicts =
|
||||
await(git.file_info(file, toplevel))
|
||||
|
||||
if not relpath then
|
||||
dprint('Cannot resolve file in repo', cbuf, 'attach')
|
||||
return
|
||||
end
|
||||
|
||||
-- On windows os.tmpname() crashes in callback threads so initialise this
|
||||
-- variable on the main thread.
|
||||
await(scheduler())
|
||||
|
||||
cache[cbuf] = {
|
||||
file = file,
|
||||
staged = os.tmpname(),
|
||||
relpath = relpath,
|
||||
object_name = object_name,
|
||||
mode_bits = mode_bits,
|
||||
toplevel = toplevel,
|
||||
gitdir = gitdir,
|
||||
abbrev_head = abbrev_head,
|
||||
username = await(git.command{'config', 'user.name'})[1],
|
||||
has_conflicts = has_conflicts,
|
||||
staged = staged,
|
||||
staged_text = nil,
|
||||
hunks = {},
|
||||
staged_diffs = {},
|
||||
index_watcher = watch_index(cbuf, git_obj.gitdir, index_handler(cbuf)),
|
||||
git_obj = git_obj
|
||||
index_watcher = watch_index(cbuf, gitdir, index_update_handler(cbuf))
|
||||
}
|
||||
|
||||
-- Initial update
|
||||
@ -592,6 +656,7 @@ function gitsigns_complete(arglead: string, line: string): {string}
|
||||
return matches
|
||||
end
|
||||
|
||||
|
||||
local function setup_command()
|
||||
vim.cmd(table.concat({
|
||||
'command!',
|
||||
@ -711,7 +776,7 @@ local blame_line = void_async(function()
|
||||
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
local result = await(bcache.git_obj:run_blame(buftext, lnum))
|
||||
local result = await(git.run_blame(bcache.file, bcache.toplevel, buftext, lnum))
|
||||
|
||||
local date = os.date('%Y-%m-%d %H:%M', tonumber(result['author_time']))
|
||||
local lines = {
|
||||
@ -744,20 +809,20 @@ end
|
||||
local _current_line_blame = void_async(function()
|
||||
local bufnr = current_buf()
|
||||
local bcache = cache[bufnr]
|
||||
if not bcache or not bcache.git_obj.object_name then
|
||||
if not bcache or not bcache.object_name then
|
||||
return
|
||||
end
|
||||
|
||||
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local lnum = api.nvim_win_get_cursor(0)[1]
|
||||
local result = await(bcache.git_obj:run_blame(buftext, lnum))
|
||||
local result = await(git.run_blame(bcache.file, bcache.toplevel, buftext, lnum))
|
||||
|
||||
await(scheduler())
|
||||
|
||||
_current_line_blame_reset(bufnr)
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, lnum-1, 0, {
|
||||
id = 1,
|
||||
virt_text = config.current_line_blame_formatter(bcache.git_obj.username, result),
|
||||
virt_text = config.current_line_blame_formatter(bcache.username, result),
|
||||
})
|
||||
end)
|
||||
|
||||
|
@ -1,54 +1,13 @@
|
||||
local a = require('plenary/async_lib/async')
|
||||
local JobSpec = require('plenary/job').JobSpec
|
||||
local await = a.await
|
||||
local async = a.async
|
||||
|
||||
local gsd = require("gitsigns/debug")
|
||||
local util = require('gitsigns/util')
|
||||
|
||||
local gs_hunks = require("gitsigns/hunks")
|
||||
local Hunk = gs_hunks.Hunk
|
||||
local hunks = require("gitsigns/hunks")
|
||||
local Hunk = hunks.Hunk
|
||||
|
||||
local uv = vim.loop
|
||||
local startswith = vim.startswith
|
||||
|
||||
local record GJobSpec
|
||||
command: string
|
||||
args: {string}
|
||||
cwd: string
|
||||
on_stdout: function
|
||||
on_stderr: function
|
||||
on_exit: function
|
||||
writer: {string}
|
||||
|
||||
-- gitsigns extensions
|
||||
supress_stderr: boolean
|
||||
end
|
||||
|
||||
local record Obj
|
||||
toplevel : string
|
||||
gitdir : string
|
||||
file : string
|
||||
abbrev_head : string
|
||||
username : string
|
||||
relpath : string
|
||||
object_name : string
|
||||
mode_bits : string
|
||||
has_conflicts : boolean
|
||||
|
||||
command : function(Obj, {string}, GJobSpec): a.future1<{string}>
|
||||
update_abbrev_head : function(Obj): a.future0
|
||||
update_file_info : function(Obj): a.future1<boolean>
|
||||
unstage_file : function(Obj, string, string): a.future0
|
||||
get_staged : function(Obj, string): a.future0
|
||||
get_staged_text : function(Obj): a.future1<{string}>
|
||||
run_blame : function(Obj, {string}, number): a.future1<M.BlameInfo>
|
||||
file_info : function(Obj): a.future4<string, string, string, boolean>
|
||||
ensure_file_in_index : function(Obj): a.future0
|
||||
stage_hunks : function(Obj, {Hunk}, boolean): a.future0
|
||||
new : function(Obj, string): a.future1<Obj>
|
||||
end
|
||||
|
||||
local record M
|
||||
record BlameInfo
|
||||
-- Info in header
|
||||
@ -78,13 +37,18 @@ local record M
|
||||
end
|
||||
version: Version
|
||||
|
||||
set_version: function(string): a.future0
|
||||
run_diff : function(string, {string}, string): a.future1<{Hunk}>
|
||||
|
||||
type Obj = Obj
|
||||
|
||||
obj: Obj
|
||||
|
||||
file_info : function(string, string): a.future4<string, string, string, boolean>
|
||||
get_staged : function(string, string, number, string): a.future0
|
||||
get_staged_text : function(string, string, number): a.future1<{string}>
|
||||
run_blame : function(string, string, {string}, number): a.future1<M.BlameInfo>
|
||||
get_repo_info : function(string): a.future3<string,string,string>
|
||||
stage_lines : function(string, {string}): a.future0
|
||||
add_file : function(string, string, string): a.future0
|
||||
unstage_file : function(string, string, string): a.future0
|
||||
update_index : function(string, string, string, string): a.future0
|
||||
run_diff : function(string, {string}, string): a.future1<{Hunk}>
|
||||
set_version : function(string): a.future0
|
||||
command : function({string}): a.future1<{string}>
|
||||
end
|
||||
|
||||
local function parse_version(version: string): M.Version
|
||||
@ -111,30 +75,148 @@ local function check_version(version: {number,number,number}): boolean
|
||||
return true
|
||||
end
|
||||
|
||||
local command = a.wrap(function(args: {string}, spec: GJobSpec, callback: function({string}))
|
||||
local result: {string} = {}
|
||||
spec = spec or {}
|
||||
spec.command = 'git'
|
||||
spec.args = {'--no-pager', unpack(args) }
|
||||
spec.on_stdout = spec.on_stdout or function(_, line: string)
|
||||
table.insert(result, line)
|
||||
end
|
||||
if not spec.supress_stderr then
|
||||
spec.on_stderr = spec.on_stderr or function(err: string, line: string)
|
||||
if err then gsd.eprint(err) end
|
||||
if line then gsd.eprint(line) end
|
||||
M.file_info = a.wrap(function(
|
||||
file: string,
|
||||
toplevel: string,
|
||||
callback: function(string, string, string, boolean)
|
||||
)
|
||||
local relpath: string
|
||||
local object_name: string
|
||||
local mode_bits: string
|
||||
local stage: number
|
||||
local has_conflict: boolean = false
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'ls-files',
|
||||
'--stage',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
file
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line: string)
|
||||
local parts = vim.split(line, '\t')
|
||||
if #parts > 1 then -- tracked file
|
||||
relpath = parts[2]
|
||||
local attrs = vim.split(parts[1], '%s+')
|
||||
stage = tonumber(attrs[3])
|
||||
if stage <= 1 then
|
||||
mode_bits = attrs[1]
|
||||
object_name = attrs[2]
|
||||
else
|
||||
has_conflict = true
|
||||
end
|
||||
else -- untracked file
|
||||
relpath = parts[1]
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(relpath, object_name, mode_bits, has_conflict)
|
||||
end
|
||||
end
|
||||
local old_on_exit = spec.on_exit
|
||||
spec.on_exit = function()
|
||||
if old_on_exit then
|
||||
old_on_exit()
|
||||
end
|
||||
callback(result)
|
||||
end
|
||||
util.run_job(spec as JobSpec)
|
||||
}
|
||||
end, 3)
|
||||
|
||||
M.get_staged = a.wrap(function(
|
||||
toplevel: string,
|
||||
relpath: string,
|
||||
stage: number,
|
||||
output: string,
|
||||
callback: function()
|
||||
)
|
||||
-- On windows 'w' mode use \r\n instead of \n, see:
|
||||
-- https://stackoverflow.com/a/43967013
|
||||
local outf = io.open(output , 'wb')
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'show',
|
||||
':'..tostring(stage)..':'..relpath,
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line: string)
|
||||
outf:write(line)
|
||||
outf:write('\n')
|
||||
end,
|
||||
on_exit = function()
|
||||
outf:close()
|
||||
callback()
|
||||
end
|
||||
}
|
||||
end, 5)
|
||||
|
||||
M.get_staged_text = a.wrap(function(
|
||||
toplevel: string,
|
||||
relpath: string,
|
||||
stage: number,
|
||||
callback: function({string})
|
||||
)
|
||||
local result = {}
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'show',
|
||||
':'..tostring(stage)..':'..relpath,
|
||||
},
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line: string)
|
||||
table.insert(result, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(result)
|
||||
end
|
||||
}
|
||||
end, 4)
|
||||
|
||||
M.run_blame = a.wrap(function(
|
||||
file: string,
|
||||
toplevel: string,
|
||||
lines: {string},
|
||||
lnum: number,
|
||||
callback: function(M.BlameInfo)
|
||||
)
|
||||
local results: {string} = {}
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'blame',
|
||||
'--contents', '-',
|
||||
'-L', lnum..',+1',
|
||||
'--line-porcelain',
|
||||
file
|
||||
},
|
||||
writer = lines,
|
||||
cwd = toplevel,
|
||||
on_stdout = function(_, line: string)
|
||||
table.insert(results, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
local ret: {string:any} = {}
|
||||
if #results == 0 then
|
||||
callback({})
|
||||
return
|
||||
end
|
||||
local header = vim.split(table.remove(results, 1), ' ')
|
||||
ret.sha = header[1]
|
||||
ret.abbrev_sha = string.sub(ret.sha as string, 1, 8)
|
||||
ret.orig_lnum = tonumber(header[2]) as integer
|
||||
ret.final_lnum = tonumber(header[3]) as integer
|
||||
for _, l in ipairs(results) do
|
||||
if not startswith(l, '\t') then
|
||||
local cols = vim.split(l, ' ')
|
||||
local key = table.remove(cols, 1):gsub('-', '_')
|
||||
ret[key] = table.concat(cols, ' ')
|
||||
end
|
||||
end
|
||||
callback(ret as M.BlameInfo)
|
||||
end
|
||||
}
|
||||
end, 5)
|
||||
|
||||
local function process_abbrev_head(gitdir: string, head_str: string): string
|
||||
if not gitdir then
|
||||
return head_str
|
||||
@ -152,27 +234,132 @@ local function process_abbrev_head(gitdir: string, head_str: string): string
|
||||
return head_str
|
||||
end
|
||||
|
||||
local get_repo_info = async(function(path: string): string,string,string
|
||||
M.get_repo_info = a.wrap(function(
|
||||
path: string, callback: function(string,string,string))
|
||||
local out = {}
|
||||
|
||||
-- Does git rev-parse have --absolute-git-dir, added in 2.13:
|
||||
-- https://public-inbox.org/git/20170203024829.8071-16-szeder.dev@gmail.com/
|
||||
local has_abs_gd = check_version{2,13}
|
||||
local git_dir_opt = has_abs_gd and '--absolute-git-dir' or '--git-dir'
|
||||
|
||||
local results = await(command({
|
||||
'rev-parse', '--show-toplevel', git_dir_opt, '--abbrev-ref', 'HEAD',
|
||||
}, {
|
||||
supress_stderr = true,
|
||||
cwd = path
|
||||
}))
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {'rev-parse',
|
||||
'--show-toplevel',
|
||||
git_dir_opt,
|
||||
'--abbrev-ref', 'HEAD',
|
||||
},
|
||||
cwd = path,
|
||||
on_stdout = function(_, line: string)
|
||||
if not has_abs_gd and #out == 1 then
|
||||
line = uv.fs_realpath(line)
|
||||
end
|
||||
table.insert(out, line)
|
||||
end,
|
||||
on_exit = vim.schedule_wrap(function()
|
||||
local toplevel = out[1]
|
||||
local gitdir = out[2]
|
||||
local abbrev_head = process_abbrev_head(gitdir, out[3])
|
||||
callback(toplevel, gitdir, abbrev_head)
|
||||
end)
|
||||
}
|
||||
end, 2)
|
||||
|
||||
local toplevel = results[1]
|
||||
local gitdir = results[2]
|
||||
if not has_abs_gd then
|
||||
gitdir = uv.fs_realpath(gitdir)
|
||||
end
|
||||
local abbrev_head = process_abbrev_head(gitdir, results[3])
|
||||
return toplevel, gitdir, abbrev_head
|
||||
end)
|
||||
M.stage_lines = a.wrap(function(
|
||||
toplevel: string, lines: {string}, callback: function())
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {'apply', '--cached', '--unidiff-zero', '-'},
|
||||
cwd = toplevel,
|
||||
writer = lines,
|
||||
on_stderr = function(_, line: string)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot stage lines. Command stderr:\n\n'..s)
|
||||
end
|
||||
callback()
|
||||
end
|
||||
}
|
||||
end, 3)
|
||||
|
||||
M.add_file = a.wrap(function(
|
||||
toplevel: string, file: string, callback: function())
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {'add', '--intent-to-add', file},
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line: string)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot add file. Command stderr:\n\n'..s)
|
||||
end
|
||||
callback()
|
||||
end
|
||||
}
|
||||
end, 3)
|
||||
|
||||
M.unstage_file = a.wrap(function(
|
||||
toplevel: string, file: string, callback: function())
|
||||
local status = true
|
||||
local err = {}
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {'reset', file},
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line: string)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot unstage file. Command stderr:\n\n'..s)
|
||||
end
|
||||
callback()
|
||||
end
|
||||
}
|
||||
end, 3)
|
||||
|
||||
M.update_index = a.wrap(function(
|
||||
toplevel: string,
|
||||
mode_bits: string,
|
||||
object_name: string,
|
||||
file: string,
|
||||
callback: function()
|
||||
)
|
||||
local status = true
|
||||
local err = {}
|
||||
local cacheinfo = table.concat({mode_bits, object_name, file}, ',')
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {'update-index', '--add', '--cacheinfo', cacheinfo},
|
||||
cwd = toplevel,
|
||||
on_stderr = function(_, line: string)
|
||||
status = false
|
||||
table.insert(err, line)
|
||||
end,
|
||||
on_exit = function()
|
||||
if not status then
|
||||
local s = table.concat(err, '\n')
|
||||
error('Cannot update index. Command stderr:\n\n'..s)
|
||||
end
|
||||
callback()
|
||||
end
|
||||
}
|
||||
end, 5)
|
||||
|
||||
local function write_to_file(path: string, text: {string})
|
||||
local f = io.open(path, 'wb')
|
||||
@ -183,14 +370,15 @@ local function write_to_file(path: string, text: {string})
|
||||
f:close()
|
||||
end
|
||||
|
||||
M.run_diff = async(function(
|
||||
M.run_diff = a.wrap(function(
|
||||
staged: string,
|
||||
text: {string},
|
||||
diff_algo: string
|
||||
): {Hunk}
|
||||
diff_algo: string,
|
||||
callback: function({Hunk})
|
||||
)
|
||||
local results: {Hunk} = {}
|
||||
|
||||
local buffile = os.tmpname()..'_buf'
|
||||
local buffile = staged..'_buf'
|
||||
write_to_file(buffile, text)
|
||||
|
||||
-- Taken from gitgutter, diff.vim:
|
||||
@ -209,7 +397,10 @@ M.run_diff = async(function(
|
||||
-- We can safely ignore the warning, we turn it off by passing the '-c
|
||||
-- "core.safecrlf=false"' argument to git-diff.
|
||||
|
||||
await(command({
|
||||
util.run_job {
|
||||
command = 'git',
|
||||
args = {
|
||||
'--no-pager',
|
||||
'-c', 'core.safecrlf=false',
|
||||
'diff',
|
||||
'--color=never',
|
||||
@ -218,187 +409,77 @@ M.run_diff = async(function(
|
||||
'--unified=0',
|
||||
staged,
|
||||
buffile,
|
||||
}, {
|
||||
},
|
||||
on_stdout = function(_, line: string)
|
||||
if startswith(line, '@@') then
|
||||
table.insert(results, gs_hunks.parse_diff_line(line))
|
||||
elseif #results > 0 then
|
||||
table.insert(results[#results].lines, line)
|
||||
table.insert(results, hunks.parse_diff_line(line))
|
||||
else
|
||||
if #results > 0 then
|
||||
table.insert(results[#results].lines, line)
|
||||
end
|
||||
end
|
||||
end,
|
||||
on_stderr = function(err: string, line: string)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
os.remove(buffile)
|
||||
callback(results)
|
||||
end
|
||||
}))
|
||||
os.remove(buffile)
|
||||
return results
|
||||
end)
|
||||
}
|
||||
end, 4)
|
||||
|
||||
M.set_version = async(function(version: string)
|
||||
M.set_version = a.wrap(function(version: string, callback: function())
|
||||
if version ~= 'auto' then
|
||||
M.version = parse_version(version)
|
||||
callback()
|
||||
return
|
||||
end
|
||||
local results = await(command{'--version'})
|
||||
local line = results[1]
|
||||
assert(startswith(line, 'git version'), 'Unexpected output: '..line)
|
||||
local parts = vim.split(line, '%s+')
|
||||
M.version = parse_version(parts[3])
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Git object methods
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local O: Obj = {}
|
||||
|
||||
--- Run git command the with the objects gitdir and toplevel
|
||||
O.command = async(function(self: Obj, args: {string}, spec: GJobSpec): {string}
|
||||
spec = spec or {}
|
||||
spec.cwd = self.toplevel
|
||||
return await(command({'--git-dir='..self.gitdir, unpack(args)}, spec))
|
||||
end)
|
||||
|
||||
O.update_abbrev_head = async(function(self: Obj)
|
||||
_, _, self.abbrev_head = await(get_repo_info(self.toplevel))
|
||||
end)
|
||||
|
||||
O.update_file_info = async(function(self: Obj): boolean
|
||||
local old_object_name = self.object_name
|
||||
_, self.object_name, self.mode_bits, self.has_conflicts = await(self:file_info())
|
||||
|
||||
return old_object_name ~= self.object_name
|
||||
end)
|
||||
|
||||
O.file_info = async(function(self: Obj): string, string, string, boolean
|
||||
local results = await(self:command({
|
||||
'ls-files',
|
||||
'--stage',
|
||||
'--others',
|
||||
'--exclude-standard',
|
||||
self.file
|
||||
}))
|
||||
|
||||
local relpath: string
|
||||
local object_name: string
|
||||
local mode_bits: string
|
||||
local stage: number
|
||||
local has_conflict: boolean = false
|
||||
for _, line in ipairs(results) do
|
||||
local parts = vim.split(line, '\t')
|
||||
if #parts > 1 then -- tracked file
|
||||
relpath = parts[2]
|
||||
local attrs = vim.split(parts[1], '%s+')
|
||||
stage = tonumber(attrs[3])
|
||||
if stage <= 1 then
|
||||
mode_bits = attrs[1]
|
||||
object_name = attrs[2]
|
||||
else
|
||||
has_conflict = true
|
||||
end
|
||||
else -- untracked file
|
||||
relpath = parts[1]
|
||||
end
|
||||
end
|
||||
return relpath, object_name, mode_bits, has_conflict
|
||||
end)
|
||||
|
||||
O.unstage_file = async(function(self: Obj)
|
||||
await(self:command{'reset', self.file })
|
||||
end)
|
||||
|
||||
--- Get version of file in the index, return array lines
|
||||
O.get_staged_text = async(function(self: Obj): {string}
|
||||
local stage = self.has_conflicts and 1 or 0
|
||||
return await(self:command({'show', ':'..tostring(stage)..':'..self.relpath}, {
|
||||
supress_stderr = true
|
||||
}))
|
||||
end)
|
||||
|
||||
--- Get version of file in the index, write lines to file
|
||||
O.get_staged = async(function(self: Obj, output_file: string)
|
||||
local stage = self.has_conflicts and 1 or 0
|
||||
-- On windows 'w' mode use \r\n instead of \n, see:
|
||||
-- https://stackoverflow.com/a/43967013
|
||||
local outf = io.open(output_file, 'wb')
|
||||
await(self:command({
|
||||
'show', ':'..tostring(stage)..':'..self.relpath
|
||||
}, {
|
||||
supress_stderr = true,
|
||||
util.run_job {
|
||||
command = 'git', args = {'--version'},
|
||||
on_stdout = function(_, line: string)
|
||||
outf:write(line)
|
||||
outf:write('\n')
|
||||
assert(startswith(line, 'git version'), 'Unexpected output: '..line)
|
||||
local parts = vim.split(line, '%s+')
|
||||
M.version = parse_version(parts[3])
|
||||
end,
|
||||
on_stderr = function(err: string, line: string)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback()
|
||||
end
|
||||
}))
|
||||
outf:close()
|
||||
end)
|
||||
}
|
||||
end, 2)
|
||||
|
||||
O.run_blame = async(function(self: Obj, lines: {string}, lnum: number): M.BlameInfo
|
||||
local results = await(self:command({
|
||||
'blame',
|
||||
'--contents', '-',
|
||||
'-L', lnum..',+1',
|
||||
'--line-porcelain',
|
||||
self.file
|
||||
}, {
|
||||
writer = lines,
|
||||
}))
|
||||
if #results == 0 then
|
||||
return {}
|
||||
end
|
||||
local header = vim.split(table.remove(results, 1), ' ')
|
||||
|
||||
local ret: {string:any} = {}
|
||||
ret.sha = header[1]
|
||||
ret.orig_lnum = tonumber(header[2]) as integer
|
||||
ret.final_lnum = tonumber(header[3]) as integer
|
||||
ret.abbrev_sha = string.sub(ret.sha as string, 1, 8)
|
||||
for _, l in ipairs(results) do
|
||||
if not startswith(l, '\t') then
|
||||
local cols = vim.split(l, ' ')
|
||||
local key = table.remove(cols, 1):gsub('-', '_')
|
||||
ret[key] = table.concat(cols, ' ')
|
||||
M.command = a.wrap(function(args: {string}, callback: function({string}))
|
||||
local result: {string} = {}
|
||||
util.run_job {
|
||||
command = 'git', args = args,
|
||||
on_stdout = function(_, line: string)
|
||||
table.insert(result, line)
|
||||
end,
|
||||
on_stderr = function(err: string, line: string)
|
||||
if err then
|
||||
gsd.eprint(err)
|
||||
end
|
||||
if line then
|
||||
gsd.eprint(line)
|
||||
end
|
||||
end,
|
||||
on_exit = function()
|
||||
callback(result)
|
||||
end
|
||||
end
|
||||
return ret as M.BlameInfo
|
||||
end)
|
||||
|
||||
O.ensure_file_in_index = async(function(self: Obj)
|
||||
if not self.object_name or self.has_conflicts then
|
||||
if not self.object_name then
|
||||
-- If there is no object_name then it is not yet in the index so add it
|
||||
await(self:command{'add', '--intent-to-add', self.file})
|
||||
else
|
||||
-- Update the index with the common ancestor (stage 1) which is what bcache
|
||||
-- stores
|
||||
local info = table.concat({self.mode_bits, self.object_name, self.relpath}, ',')
|
||||
await(self:command{'update-index', '--add', '--cacheinfo', info})
|
||||
end
|
||||
|
||||
-- Update file info
|
||||
_, self.object_name, self.mode_bits, self.has_conflicts = await(self:file_info())
|
||||
end
|
||||
end)
|
||||
|
||||
O.stage_hunks = async(function(self: Obj, hunks: {Hunk}, invert: boolean)
|
||||
await(self:ensure_file_in_index())
|
||||
await(self:command({
|
||||
'apply', '--cached', '--unidiff-zero', '-'
|
||||
}, {
|
||||
writer = gs_hunks.create_patch(self.relpath, hunks, self.mode_bits, invert)
|
||||
}))
|
||||
end)
|
||||
|
||||
O.new = a.async(function(self: Obj, file: string): Obj
|
||||
self.file = file
|
||||
self.toplevel, self.gitdir, self.abbrev_head =
|
||||
await(get_repo_info(util.dirname(file)))
|
||||
|
||||
self.relpath, self.object_name, self.mode_bits, self.has_conflicts =
|
||||
await(self:file_info())
|
||||
|
||||
self.username = await(command({'config', 'user.name'}))[1]
|
||||
|
||||
return self
|
||||
end)
|
||||
|
||||
M.obj = O
|
||||
}
|
||||
end, 2)
|
||||
|
||||
return M
|
||||
|
@ -21,7 +21,6 @@ local function check_status(status)
|
||||
end
|
||||
|
||||
local scratch = os.getenv('PJ_ROOT')..'/scratch'
|
||||
local gitdir = scratch..'/.git'
|
||||
local test_file = scratch..'/dummy.txt'
|
||||
local newfile = scratch.."/newfile.txt"
|
||||
|
||||
@ -231,14 +230,14 @@ describe('gitsigns', function()
|
||||
edit(test_file)
|
||||
sleep(10)
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
'attach(1): Attaching',
|
||||
'run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
|
||||
p('run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard '..test_file),
|
||||
p'run_job: git .* config user.name',
|
||||
'run_job: git rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD',
|
||||
'run_job: git --no-pager ls-files --stage --others --exclude-standard '..test_file,
|
||||
"run_job: git config user.name",
|
||||
'watch_index(1): Watching index',
|
||||
'run_job: git --no-pager show :0:dummy.txt',
|
||||
'watcher_cb(1): Index update error: ENOENT',
|
||||
p'run_job: git .* show :0:dummy.txt',
|
||||
'update(1): updates: 1, jobs: 5'
|
||||
}
|
||||
|
||||
@ -295,7 +294,7 @@ describe('gitsigns', function()
|
||||
sleep(20)
|
||||
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
'attach(1): Attaching',
|
||||
'attach(1): In git dir'
|
||||
}
|
||||
@ -311,11 +310,10 @@ describe('gitsigns', function()
|
||||
sleep(20)
|
||||
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
"attach(1): Attaching",
|
||||
"run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
"run_job: git rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
p"run_job: git .* ls%-files .*/dummy_ignored.txt",
|
||||
p"run_job: git .* config user.name",
|
||||
"attach(1): Cannot resolve file in repo",
|
||||
}
|
||||
|
||||
@ -327,11 +325,9 @@ describe('gitsigns', function()
|
||||
sleep(10)
|
||||
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
"attach(1): Attaching",
|
||||
"run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
p("run_job: git .* ls%-files %-%-stage %-%-others %-%-exclude%-standard "..newfile),
|
||||
p"run_job: git .* config user.name",
|
||||
"run_job: git rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
"attach(1): Not a file",
|
||||
}
|
||||
|
||||
@ -343,7 +339,7 @@ describe('gitsigns', function()
|
||||
edit(scratch..'/does/not/exist')
|
||||
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
"attach(1): Attaching",
|
||||
"attach(1): Not a path",
|
||||
}
|
||||
@ -356,7 +352,7 @@ describe('gitsigns', function()
|
||||
it('can run copen', function()
|
||||
command("copen")
|
||||
match_debug_messages {
|
||||
"run_job: git --no-pager --version",
|
||||
"run_job: git --version",
|
||||
"attach(2): Attaching",
|
||||
"attach(2): Non-normal buffer",
|
||||
}
|
||||
@ -597,6 +593,7 @@ describe('gitsigns', function()
|
||||
|
|
||||
]]}
|
||||
|
||||
|
||||
-- Reset
|
||||
feed("mhr")
|
||||
sleep(10)
|
||||
@ -662,20 +659,19 @@ describe('gitsigns', function()
|
||||
command("write")
|
||||
sleep(40)
|
||||
|
||||
|
||||
local messages = {
|
||||
"attach(1): Attaching",
|
||||
"run_job: git --no-pager rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
p"run_job: git .* ls%-files .*",
|
||||
p"run_job: git .* config user.name",
|
||||
"run_job: git rev-parse --show-toplevel --absolute-git-dir --abbrev-ref HEAD",
|
||||
p"run_job: git .* ls%-files .*/newfile.txt",
|
||||
"run_job: git config user.name",
|
||||
"watch_index(1): Watching index",
|
||||
p"run_job: git .* show :0:newfile.txt"
|
||||
"run_job: git --no-pager show :0:newfile.txt",
|
||||
}
|
||||
|
||||
if not advanced_features then
|
||||
table.insert(messages, p'run_job: git .* diff .* /tmp/lua_.* /tmp/lua_.*')
|
||||
end
|
||||
|
||||
local jobs = advanced_features and 8 or 9
|
||||
local jobs = advanced_features and 6 or 7
|
||||
table.insert(messages, "update(1): updates: 1, jobs: "..jobs)
|
||||
|
||||
match_debug_messages(messages)
|
||||
@ -746,7 +742,7 @@ describe('gitsigns', function()
|
||||
<5C written |
|
||||
]]}
|
||||
|
||||
-- Reset
|
||||
-- -- Reset
|
||||
git{"reset"}
|
||||
|
||||
screen:expect{grid=[[
|
||||
|
@ -32,43 +32,37 @@ local record Async
|
||||
type async_fun4_3 = function <A1,A2,A3,A4,R1,R2,R3> (A1,A2,A3,A4) : future3<R1,R2,R3>
|
||||
type async_fun4_4 = function <A1,A2,A3,A4,R1,R2,R3,R4> (A1,A2,A3,A4) : future4<R1,R2,R3,R4>
|
||||
|
||||
type async_fun5_0 = function <A1,A2,A3,A4,A5> (A1,A2,A3,A4,A5) : future0
|
||||
type async_fun5_1 = function <A1,A2,A3,A4,A5,R1> (A1,A2,A3,A4,A5) : future1<R1>
|
||||
type async_fun5_4 = function <A1,A2,A3,A4,A5,R1,R2,R3,R4> (A1,A2,A3,A4,A5) : future4<R1,R2,R3,R4>
|
||||
|
||||
await: function (future0 ): ()
|
||||
await: function<A1> (future1<A1> ): A1
|
||||
await: function<A1,A2> (future2<A1,A2> ): A1,A2
|
||||
await: function<A1,A2,A3> (future3<A1,A2,A3> ): A1,A2,A3
|
||||
await: function<A1,A2,A3,A4> (future4<A1,A2,A3,A4>): A1,A2,A3,A4
|
||||
|
||||
async: function<R1,R2,R3> (function() : R1,R2,R3 ): async_fun0_3 <R1,R2,R3>
|
||||
async: function<R1,R2> (function() : R1,R2 ): async_fun0_2 <R1,R2>
|
||||
async: function<R1> (function() : R1 ): async_fun0_1 <R1>
|
||||
async: function (function() : () ): async_fun0
|
||||
async: function<A1,R1,R2,R3,R4> (function(A1) : R1,R2,R3,R4): async_fun1_4 <A1,R1,R2,R3,R4>
|
||||
async: function<A1,R1,R2,R3> (function(A1) : R1,R2,R3 ): async_fun1_3 <A1,R1,R2,R3>
|
||||
async: function<A1,R1,R2> (function(A1) : R1,R2 ): async_fun1_2 <A1,R1,R2>
|
||||
async: function<A1,R1> (function(A1) : R1 ): async_fun1_1 <A1,R1>
|
||||
async: function<R1> (function() : R1 ): async_fun0_1 <R1>
|
||||
async: function<R1,R2> (function() : R1,R2 ): async_fun0_2 <R1,R2>
|
||||
async: function<R1,R2,R3> (function() : R1,R2,R3 ): async_fun0_3 <R1,R2,R3>
|
||||
async: function<R1,R2,R3,R4> (function() : R1,R2,R3,R4): async_fun0_4 <R1,R2,R3,R4>
|
||||
async: function<A1> (function(A1) : () ): async_fun1 <A1>
|
||||
async: function<A1,A2,R1,R2,R3,R4> (function(A1,A2) : R1,R2,R3,R4): async_fun2_4 <A1,A2,R1,R2,R3,R4>
|
||||
async: function<A1,A2,R1,R2,R3> (function(A1,A2) : R1,R2,R3 ): async_fun2_3 <A1,A2,R1,R2,R3>
|
||||
async: function<A1,A2,R1,R2> (function(A1,A2) : R1,R2 ): async_fun2_2 <A1,A2,R1,R2>
|
||||
async: function<A1,A2,R1> (function(A1,A2) : R1 ): async_fun2_1 <A1,A2,R1>
|
||||
async: function<A1,R1> (function(A1) : R1 ): async_fun1_1 <A1,R1>
|
||||
async: function<A1,R1,R2> (function(A1) : R1,R2 ): async_fun1_2 <A1,R1,R2>
|
||||
async: function<A1,R1,R2,R3> (function(A1) : R1,R2,R3 ): async_fun1_3 <A1,R1,R2,R3>
|
||||
async: function<A1,R1,R2,R3,R4> (function(A1) : R1,R2,R3,R4): async_fun1_4 <A1,R1,R2,R3,R4>
|
||||
async: function<A1,A2> (function(A1,A2) : () ): async_fun2 <A1,A2>
|
||||
async: function<A1,A2,A3,R1,R2,R3,R4> (function(A1,A2,A3) : R1,R2,R3,R4): async_fun3_4 <A1,A2,A3,R1,R2,R3,R4>
|
||||
async: function<A1,A2,A3,R1,R2,R3> (function(A1,A2,A3) : R1,R2,R3 ): async_fun3_3 <A1,A2,A3,R1,R2,R3>
|
||||
async: function<A1,A2,A3,R1,R2> (function(A1,A2,A3) : R1,R2 ): async_fun3_2 <A1,A2,A3,R1,R2>
|
||||
async: function<A1,A2,A3,R1> (function(A1,A2,A3) : R1 ): async_fun3_1 <A1,A2,A3,R1>
|
||||
async: function<A1,A2,R1> (function(A1,A2) : R1 ): async_fun2_1 <A1,A2,R1>
|
||||
async: function<A1,A2,R1,R2> (function(A1,A2) : R1,R2 ): async_fun2_2 <A1,A2,R1,R2>
|
||||
async: function<A1,A2,R1,R2,R3> (function(A1,A2) : R1,R2,R3 ): async_fun2_3 <A1,A2,R1,R2,R3>
|
||||
async: function<A1,A2,R1,R2,R3,R4> (function(A1,A2) : R1,R2,R3,R4): async_fun2_4 <A1,A2,R1,R2,R3,R4>
|
||||
async: function<A1,A2,A3> (function(A1,A2,A3) : () ): async_fun3 <A1,A2,A3>
|
||||
async: function<A1,A2,A3,A4,R1,R2,R3,R4>(function(A1,A2,A3,A4) : R1,R2,R3,R4): async_fun4_4 <A1,A2,A3,A4,R1,R2,R3,R4>
|
||||
async: function<A1,A2,A3,A4,R1,R2,R3> (function(A1,A2,A3,A4) : R1,R2,R3 ): async_fun4_3 <A1,A2,A3,A4,R1,R2,R3>
|
||||
async: function<A1,A2,A3,A4,R1,R2> (function(A1,A2,A3,A4) : R1,R2 ): async_fun4_2 <A1,A2,A3,A4,R1,R2>
|
||||
async: function<A1,A2,A3,A4,R1> (function(A1,A2,A3,A4) : R1 ): async_fun4_1 <A1,A2,A3,A4,R1>
|
||||
async: function<A1,A2,A3,R1> (function(A1,A2,A3) : R1 ): async_fun3_1 <A1,A2,A3,R1>
|
||||
async: function<A1,A2,A3,R1,R2> (function(A1,A2,A3) : R1,R2 ): async_fun3_2 <A1,A2,A3,R1,R2>
|
||||
async: function<A1,A2,A3,R1,R2,R3> (function(A1,A2,A3) : R1,R2,R3 ): async_fun3_3 <A1,A2,A3,R1,R2,R3>
|
||||
async: function<A1,A2,A3,R1,R2,R3,R4> (function(A1,A2,A3) : R1,R2,R3,R4): async_fun3_4 <A1,A2,A3,R1,R2,R3,R4>
|
||||
async: function<A1,A2,A3,A4> (function(A1,A2,A3,A4) : () ): async_fun4 <A1,A2,A3,A4>
|
||||
async: function<A1,A2,A3,A4,A5,R1,R2,R3,R4>(function(A1,A2,A3,A4,A5) : R1,R2,R3,R4): async_fun5_4 <A1,A2,A3,A4,A5,R1,R2,R3,R4>
|
||||
async: function<A1,A2,A3,A4,A5,R1> (function(A1,A2,A3,A4,A5) : R1 ): async_fun5_1 <A1,A2,A3,A4,A5,R1>
|
||||
async: function<A1,A2,A3,A4,A5> (function(A1,A2,A3,A4,A5) : () ): async_fun5_0 <A1,A2,A3,A4,A5>
|
||||
async: function<A1,A2,A3,A4,R1> (function(A1,A2,A3,A4) : R1 ): async_fun4_1 <A1,A2,A3,A4,R1>
|
||||
async: function<A1,A2,A3,A4,R1,R2> (function(A1,A2,A3,A4) : R1,R2 ): async_fun4_2 <A1,A2,A3,A4,R1,R2>
|
||||
async: function<A1,A2,A3,A4,R1,R2,R3> (function(A1,A2,A3,A4) : R1,R2,R3 ): async_fun4_3 <A1,A2,A3,A4,R1,R2,R3>
|
||||
async: function<A1,A2,A3,A4,R1,R2,R3,R4>(function(A1,A2,A3,A4) : R1,R2,R3,R4): async_fun4_4 <A1,A2,A3,A4,R1,R2,R3,R4>
|
||||
|
||||
wrap: function (function( function()) , integer): async_fun0
|
||||
wrap: function<R1> (function( function(R1)) , integer): async_fun0_1 <R1>
|
||||
@ -94,8 +88,7 @@ local record Async
|
||||
wrap: function<A1,A2,A3,A4,R1> (function(A1,A2,A3,A4,function(R1)) , integer): async_fun4_1 <A1,A2,A3,A4,R1>
|
||||
wrap: function<A1,A2,A3,A4,R1,R2> (function(A1,A2,A3,A4,function(R1,R2)) , integer): async_fun4_2 <A1,A2,A3,A4,R1,R2>
|
||||
wrap: function<A1,A2,A3,A4,R1,R2,R3> (function(A1,A2,A3,A4,function(R1,R2,R3)) , integer): async_fun4_3 <A1,A2,A3,A4,R1,R2,R3>
|
||||
wrap: function<A1,A2,A3,A4,A5,R1> (function(A1,A2,A3,A4,A5,function(R1)), integer): async_fun5_1 <A1,A2,A3,A4,A5,R1>
|
||||
wrap: function<A1,A2,A3,A4,A5,R1,R2,R3,R4>(function(A1,A2,A3,A4,A5,function(R1,R2,R3,R4)), integer): async_fun5_4 <A1,A2,A3,A4,A5,R1,R2,R3,R4>
|
||||
wrap: function<A1,A2,A3,A4,R1,R2,R3,R4>(function(A1,A2,A3,A4,function(R1,R2,R3,R4)), integer): async_fun4_4 <A1,A2,A3,A4,R1,R2,R3,R4>
|
||||
|
||||
scheduler: future
|
||||
execute: function
|
||||
|
Loading…
Reference in New Issue
Block a user