Skip to content

Commit

Permalink
feat(git): add better git abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
tanvirtin committed Aug 3, 2024
1 parent 83d048d commit 063ebf3
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 30 deletions.
4 changes: 3 additions & 1 deletion lua/vgit/core/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ local plenary_filetype = require('plenary.filetype')

local fs = {}

fs.sep = Path.path.sep

fs.detect_filetype = plenary_filetype.detect

function fs.make_relative(dirname, filepath)
Expand All @@ -18,7 +20,7 @@ function fs.short_filename(filepath)

for i = #filepath, 1, -1 do
local letter = filepath:sub(i, i)
if letter == '/' then break end
if letter == fs.sep then break end
filename = letter .. filename
end

Expand Down
1 change: 1 addition & 0 deletions lua/vgit/features/buffer/Hunks/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function Hunks:cursor_stage()
if err then return console.debug.error(err) end

loop.free_textlock()
buffer:clear_namespace()
buffer:edit()
end

Expand Down
2 changes: 1 addition & 1 deletion lua/vgit/features/screens/ProjectStashScreen/Store.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function Store:fetch(opts)
self:reset()

local reponame = git_repo.discover()
local logs, err = git_log.list_stash(reponame)
local logs, err = git_log.list(reponame, { stashed = true })
if err then return nil, err end

self.err = nil
Expand Down
107 changes: 107 additions & 0 deletions lua/vgit/git/GitFile.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
local fs = require('vgit.core.fs')
local Diff = require('vgit.core.Diff')
local Object = require('vgit.core.Object')
local git_repo = require('vgit.git.git_repo')
local git_show = require('vgit.git.git_show')
local git_hunks = require('vgit.git.git_hunks')
local git_status = require('vgit.git.git_status')

local GitFile = Object:extend()

function GitFile:constructor(reponame, filename, revision)
if not reponame then error('reponame is required') end
if not filename then error('filename is required') end
if not revision then error('revision is required') end

return {
reponame = reponame,
filename = filename,
revision = revision,
state = {
status = nil,
is_tracked = nil
}
}
end

function GitFile:is_tracked()
if self.state.is_tracked then return self.state.is_tracked end
local is_tracked, err = git_repo.has(self.reponame, self.filename)
if err then return nil, err end

self.state.is_tracked = is_tracked

return is_tracked, err
end

function GitFile:status()
if self.state.status then return self.state.status end

local status, err = git_status.get(
self.reponame,
self.filename,
self.revision ~= 'INDEX' and self.revision or nil
)
if err then return nil, err end
self.state.status = status

return status
end

function GitFile:live_hunks(current_lines)
if not current_lines then return nil, { 'lines is required' } end
if self.revision ~= 'INDEX' then return nil, { 'invalid revision for live hunk' } end
if not self:is_tracked() then return git_hunks.custom(current_lines, { untracked = true }) end

local original_lines, err = fs.read_file(self.filename)
if err then return nil, err end

return git_hunks.live(original_lines, current_lines)
end

function GitFile:diff(shape)
local reponame = self.reponame
local filename = self.filename

if self.revision == 'INDEX' then
local status, err = self:status()
if err then return nil, err end
if not status then return nil, { 'status not found' } end

local lines = nil
local hunks = nil

if status:unmerged() then
if status:has_both('UD') then
lines, err = git_show.lines(reponame, filename, ':2')
if err then return nil, err end
hunks, err = git_hunks.list_hunks(filename, { lines = lines, deleted = true })
if err then return nil, err end
elseif status:has_both('DU') then
lines, err = git_show.lines(reponame, filename, ':3')
if err then return nil, err end
hunks, err = git_hunks.list_hunks(filename, { lines = lines, untracked = true })
if err then return nil, err end
else
lines, err = git_show.lines(reponame, filename, 'HEAD')
if err then return nil, err end
if status:has_either('DD') then
hunks, err = git_hunks.list_hunks(filename, { lines = lines, deleted = true })
else
hunks, err = git_show.lines(reponame, filename, 'HEAD')
end
if err then return nil, err end
end
else
local path = string.format('%s%s%s', self.reponame, fs.sep, self.filename)
lines, err = fs.read_file(path)
if err then return nil, err end
hunks, err = self:live_hunks(lines)
if err then return nil, err end
end

return Diff():generate(hunks, lines, shape)
end
end

return GitFile
8 changes: 8 additions & 0 deletions lua/vgit/git/GitHunk.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ function GitHunk:constructor(header)
return hunk
end

function GitHunk:__tostring()
return table.concat({
string.upper(self.type),
string.format('%s,%s', self.top, self.bot),
string.format('+%s,-%s', self.stat.added, self.stat.removed)
}, '\n')
end

function GitHunk:generate_header(previous, current)
return string.format('@@ -%s,%s +%s,%s @@', previous[1], previous[2], current[1], current[2])
end
Expand Down
2 changes: 1 addition & 1 deletion lua/vgit/git/GitObject.lua
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function GitObject:log(opts)
end

function GitObject:logs()
return git_log.list(self.reponame, self.filename)
return git_log.list(self.reponame, { filename = self.filename })
end

function GitObject:status()
Expand Down
57 changes: 57 additions & 0 deletions lua/vgit/git/GitRepository.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
local fs = require('vgit.core.fs')
local Object = require('vgit.core.Object')
local git_log = require('vgit.git.git_log')
local git_repo = require('vgit.git.git_repo')

local GitRepository = Object:extend()

function GitRepository:constructor(path)
local name, err = git_repo.discover(path)
if err then error(string.format('failed to access .git at "%s"', path)) end

return { name = name }
end

function GitRepository.exists(path)
return git_repo.exists(path)
end

function GitRepository:logs(opts)
opts = opts or {}

return git_log.list(self.name, {
from = opts.from,
count = opts.count,
stashed = opts.stashed,
})
end

function GitRepository:state()
local git_dir = string.format('%s/.git', self.name)

if fs.exists(string.format('%s/rebase-apply/applying', git_dir)) then return 'APPLY-MAILBOX' end
if fs.exists(string.format('%s/rebase-apply/rebasing', git_dir)) then return 'REBASE' end
if fs.exists(string.format('%s/rebase-apply', git_dir)) then return 'APPLY-MAILBOX-REBASE' end
if fs.exists(string.format('%s/rebase-merge/interactive', git_dir)) then return 'REBASE-INTERACTIVE' end
if fs.exists(string.format('%s/rebase-merge', git_dir)) then return 'REBASE' end
if fs.exists(string.format('%s/CHERRY_PICK_HEAD', git_dir)) then
if fs.exists(string.format('%s/sequencer/todo', git_dir)) then return 'CHERRY-PICK-SEQUENCE' end
return 'CHERRY-PICK'
end
if fs.exists(string.format('%s/MERGE_HEAD', git_dir)) then return 'MERGE' end
if fs.exists(string.format('%s/BISECT_LOG', git_dir)) then return 'BISECT' end
if fs.exists(string.format('%s/REVERT_HEAD', git_dir)) then
if fs.exists(string.format('%s/sequencer/todo', git_dir)) then return 'REVERT-SEQUENCE' end
return 'REVERT'
end

return nil
end

function GitRepository:commit(commit_hash) end

function GitRepository:tree(commit_hash) end

function GitRepository:file(commit_hash) end

return GitRepository
4 changes: 4 additions & 0 deletions lua/vgit/git/GitStatus.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ function GitStatus:constructor(status)
}
end

function GitStatus:__tostring()
return self.value
end

function GitStatus:parse(status)
return status:sub(1, 1), status:sub(2, 2)
end
Expand Down
11 changes: 11 additions & 0 deletions lua/vgit/git/GitTree.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local Object = require('vgit.core.Object')

local GitTree = Object:extend()

function GitTree:constructor()
return {}
end

function GitTree:entries(commit_hash) end

return GitTree
63 changes: 36 additions & 27 deletions lua/vgit/git/git_log.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local utils = require('vgit.core.utils')
local GitLog = require('vgit.git.GitLog')
local gitcli = require('vgit.git.gitcli')

Expand All @@ -21,49 +22,57 @@ function git_log.get(reponame, commit)
return GitLog(result[1])
end

function git_log.list(reponame, filename)
function git_log.count(reponame)
if not reponame then return nil, { 'reponame is required' } end

local result, err = gitcli.run({
return gitcli.run({
'-C',
reponame,
'--no-pager',
'log',
'--color=never',
git_log.format,
filename,
'rev-list',
'--count',
'--all',
})

if err then return nil, err end

local logs = {}
for i = 1, #result do
logs[i] = GitLog(result[i])
end

return logs
end

function git_log.list_stash(reponame)
function git_log.list(reponame, opts)
if not reponame then return nil, { 'reponame is required' } end

local result, err = gitcli.run({
opts = opts or {}

local filename = opts.filename
local from = opts.from
local count = opts.count
local stashed = opts.stashed

local args = {
'-C',
reponame,
'--no-pager',
'stash',
'list',
'--color=never',
git_log.format,
})
}

if stashed then
args = utils.list.merge(args, { 'stash', 'list' })
else
args = utils.list.merge(args, { 'log' })
end

args = utils.list.merge(args, { '--color=never', git_log.format })

if from and count then
args = utils.list.merge(args, {
string.format('--skip=%s', filename),
'-n',
count,
})
end

if filename then args = utils.list.merge(args, { filename }) end

local result, err = gitcli.run(args)
if err then return nil, err end

local logs = {}
local rev_count = 0
for i = 1, #result do
rev_count = rev_count + 1
logs[i] = GitLog(result[i], rev_count)
logs[i] = GitLog(result[i])
end

return logs
Expand Down
46 changes: 46 additions & 0 deletions lua/vgit/git/git_status.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,54 @@
local utils = require('vgit.core.utils')
local gitcli = require('vgit.git.gitcli')
local GitStatus = require('vgit.git.GitStatus')

local git_status = {}

function git_status.get(reponame, filename, commit_hash)
if not reponame then return nil, { 'reponame is required' } end
if not filename then return nil, { 'filename is required' } end

local args = {
'-C',
reponame,
'--no-pager',
}

if commit_hash then
args = utils.list.merge(args, {
'diff-tree',
'--no-commit-id',
'--name-status',
'-r',
commit_hash .. "^",
commit_hash,
'--',
filename
})
else
args = utils.list.merge(args, {
'status',
'-u',
'-s',
'--no-renames',
'--ignore-submodules',
'--',
filename
})
end

local result, err = gitcli.run(args)
if err then return nil, err end
if #result == 0 then return nil, { 'no status found' } end

if commit_hash then
local status, path = result[1]:match('(%w+)%s+(.+)')
return GitStatus(string.format('%s %s', status or ' ', path or filename))
end

return GitStatus(result[1])
end

function git_status.ls(reponame, filename)
if not reponame then return nil, { 'reponame is required' } end
local result, err = gitcli.run({
Expand Down
Loading

0 comments on commit 063ebf3

Please sign in to comment.