Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor: break up git.lua #1081

Merged
merged 1 commit into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lua/gitsigns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ local function get_gitdir_and_head()
end
end

local info = require('gitsigns.git').get_repo_info(cwd)
local info = require('gitsigns.git').Repo.get_info(cwd)

return info.gitdir, info.abbrev_head
end
Expand Down Expand Up @@ -89,7 +89,7 @@ local update_cwd_head = async.create(function()
100,
async.create(function()
local git = require('gitsigns.git')
local new_head = git.get_repo_info(cwd).abbrev_head
local new_head = git.Repo.get_info(cwd).abbrev_head
async.scheduler()
vim.g.gitsigns_head = new_head
end)
Expand Down
310 changes: 5 additions & 305 deletions lua/gitsigns/git.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
local async = require('gitsigns.async')
local log = require('gitsigns.debug.log')
local util = require('gitsigns.util')

local system = require('gitsigns.system').system
local scheduler = require('gitsigns.async').scheduler

local uv = vim.uv or vim.loop
local Repo = require('gitsigns.git.repo')

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

local M = {}

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

--- @param file string
--- @return boolean
Expand Down Expand Up @@ -41,82 +35,7 @@ local Obj = {}

M.Obj = Obj

--- @class Gitsigns.RepoInfo
--- @field gitdir string
--- @field toplevel string
--- @field detached boolean
--- @field abbrev_head string

--- @class Gitsigns.Repo : Gitsigns.RepoInfo
---
--- Username configured for the repo.
--- Needed for to determine "You" in current line blame.
--- @field username string
local Repo = {}
M.Repo = Repo

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

--- @async
--- @param args string[]
--- @param spec? Gitsigns.Git.JobSpec
--- @return string[] stdout, string? stderr
local function git_command(args, spec)
spec = spec or {}

local cmd = {
'git',
'--no-pager',
'--no-optional-locks',
'--literal-pathspecs',
'-c',
'gc.auto=0', -- Disable auto-packing which emits messages to stderr
unpack(args),
}

if spec.text == nil then
spec.text = true
end

-- Fix #895. Only needed for Nvim 0.9 and older
spec.clear_env = true

--- @type vim.SystemCompleted
local obj = asystem(cmd, spec)

if not spec.ignore_error and obj.code > 0 then
log.eprintf(
"Received exit code %d when running command\n'%s':\n%s",
obj.code,
table.concat(cmd, ' '),
obj.stderr
)
end

local stdout_lines = vim.split(obj.stdout or '', '\n')

if spec.text then
-- If stdout ends with a newline, then remove the final empty string after
-- the split
if stdout_lines[#stdout_lines] == '' then
stdout_lines[#stdout_lines] = nil
end
end

if log.verbose then
log.vprintf('%d lines:', #stdout_lines)
for i = 1, math.min(10, #stdout_lines) do
log.vprintf('\t%s', stdout_lines[i])
end
end

if obj.stderr == '' then
obj.stderr = nil
end

return stdout_lines, obj.stderr
end
local git_command = require('gitsigns.git.cmd')

--- @async
--- @param file_cmp string
Expand All @@ -142,194 +61,6 @@ function M.diff(file_cmp, file_buf, indent_heuristic, diff_algo)
})
end

--- @async
--- @param gitdir? string
--- @param head_str string
--- @param cwd string
--- @return string
local function process_abbrev_head(gitdir, head_str, cwd)
if not gitdir then
return head_str
end
if head_str == 'HEAD' then
local short_sha = git_command({ 'rev-parse', '--short', 'HEAD' }, {
ignore_error = true,
cwd = cwd,
})[1] or ''
if log.debug_mode and short_sha ~= '' then
short_sha = 'HEAD'
end
if
util.path_exists(gitdir .. '/rebase-merge')
or util.path_exists(gitdir .. '/rebase-apply')
then
return short_sha .. '(rebasing)'
end
return short_sha
end
return head_str
end

local has_cygpath = jit and jit.os == 'Windows' and vim.fn.executable('cygpath') == 1

--- @param path? string
--- @return string?
local function normalize_path(path)
if path and has_cygpath and not uv.fs_stat(path) then
-- If on windows and path isn't recognizable as a file, try passing it
-- through cygpath
path = asystem({ 'cygpath', '-aw', path }).stdout
end
return path
end

--- @async
--- @param cwd string
--- @param gitdir? string
--- @param toplevel? string
--- @return Gitsigns.RepoInfo
function M.get_repo_info(cwd, gitdir, toplevel)
-- 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 })

-- Wait for internal scheduler to settle before running command (#215)
scheduler()

local args = {}

if gitdir then
vim.list_extend(args, { '--git-dir', gitdir })
end

if toplevel then
vim.list_extend(args, { '--work-tree', toplevel })
end

vim.list_extend(args, {
'rev-parse',
'--show-toplevel',
has_abs_gd and '--absolute-git-dir' or '--git-dir',
'--abbrev-ref',
'HEAD',
})

local results = git_command(args, {
ignore_error = true,
cwd = toplevel or cwd,
})

local toplevel_r = normalize_path(results[1])
local gitdir_r = normalize_path(results[2])

if gitdir_r and not has_abs_gd then
gitdir_r = assert(uv.fs_realpath(gitdir_r))
end

return {
toplevel = toplevel_r,
gitdir = gitdir_r,
abbrev_head = process_abbrev_head(gitdir_r, results[3], cwd),
detached = toplevel_r and gitdir_r ~= toplevel_r .. '/.git',
}
end

--------------------------------------------------------------------------------
-- Git repo object methods
--------------------------------------------------------------------------------

--- Run git command the with the objects gitdir and toplevel
--- @async
--- @param args string[]
--- @param spec? Gitsigns.Git.JobSpec
--- @return string[] stdout, string? stderr
function Repo:command(args, spec)
spec = spec or {}
spec.cwd = self.toplevel

local args1 = { '--git-dir', self.gitdir }

if self.detached then
vim.list_extend(args1, { '--work-tree', self.toplevel })
end

vim.list_extend(args1, args)

return git_command(args1, spec)
end

--- @return string[]
function Repo:files_changed()
--- @type string[]
local results = self:command({ 'status', '--porcelain', '--ignore-submodules' })

local ret = {} --- @type string[]
for _, line in ipairs(results) do
if line:sub(1, 2):match('^.M') then
ret[#ret + 1] = line:sub(4, -1)
end
end
return ret
end

--- @param encoding string
--- @return boolean
local function iconv_supported(encoding)
-- TODO(lewis6991): needs https://github.com/neovim/neovim/pull/21924
if vim.startswith(encoding, 'utf-16') then
return false
elseif vim.startswith(encoding, 'utf-32') then
return false
end
return true
end

--- Get version of file in the index, return array lines
--- @param object string
--- @param encoding? string
--- @return string[] stdout, string? stderr
function Repo:get_show_text(object, encoding)
local stdout, stderr = self:command({ 'show', object }, { text = false, ignore_error = true })

if encoding and encoding ~= 'utf-8' and iconv_supported(encoding) then
for i, l in ipairs(stdout) do
stdout[i] = vim.iconv(l, encoding, 'utf-8')
end
end

return stdout, stderr
end

--- @async
function Repo:update_abbrev_head()
self.abbrev_head = M.get_repo_info(self.toplevel).abbrev_head
end

--- @async
--- @param dir string
--- @param gitdir? string
--- @param toplevel? string
--- @return Gitsigns.Repo
function Repo.new(dir, gitdir, toplevel)
local self = setmetatable({}, { __index = Repo })

local info = M.get_repo_info(dir, gitdir, toplevel)
for k, v in
pairs(info --[[@as table<string,any>]])
do
---@diagnostic disable-next-line:no-unknown
self[k] = v
end

self.username = self:command({ 'config', 'user.name' }, { ignore_error = true })[1]

return self
end

--------------------------------------------------------------------------------
-- Git object methods
--------------------------------------------------------------------------------

--- @param revision? string
function Obj:update_revision(revision)
self.revision = util.norm_base(revision)
Expand Down Expand Up @@ -525,35 +256,6 @@ function Obj:unstage_file()
autocmd_changed(self.file)
end

--- @class Gitsigns.CommitInfo
--- @field author string
--- @field author_mail string
--- @field author_time integer
--- @field author_tz string
--- @field committer string
--- @field committer_mail string
--- @field committer_time integer
--- @field committer_tz string
--- @field summary string
--- @field sha string
--- @field abbrev_sha string
--- @field boundary? true

--- @class Gitsigns.BlameInfoPublic: Gitsigns.BlameInfo, Gitsigns.CommitInfo
--- @field body? string[]
--- @field hunk_no? integer
--- @field num_hunks? integer
--- @field hunk? string[]
--- @field hunk_head? string

--- @class Gitsigns.BlameInfo
--- @field orig_lnum integer
--- @field final_lnum integer
--- @field commit Gitsigns.CommitInfo
--- @field filename string
--- @field previous_filename? string
--- @field previous_sha? string

--- @param lines string[]
--- @param lnum? integer
--- @param revision? string
Expand Down Expand Up @@ -585,15 +287,13 @@ end
--- Stage 'lines' as the entire contents of the file
--- @param lines string[]
function Obj:stage_lines(lines)
local stdout = self.repo:command({
local new_object = self.repo:command({
'hash-object',
'-w',
'--path',
self.relpath,
'--stdin',
}, { stdin = lines })

local new_object = stdout[1]
}, { stdin = lines })[1]

self.repo:command({
'update-index',
Expand Down
Loading
Loading