@@ -92,6 +92,31 @@ local function git_dir_at_root(root)
9292 return M ._git_dir
9393end
9494
95+ local function read_trimmed_file (path )
96+ if type (path ) ~= ' string' or path == ' ' then
97+ return nil
98+ end
99+
100+ local stat = vim .uv .fs_stat (path )
101+ if not stat or stat .type ~= ' file' then
102+ return nil
103+ end
104+
105+ local file = io.open (path , ' r' )
106+ if not file then
107+ return nil
108+ end
109+
110+ local content = file :read (' *a' )
111+ file :close ()
112+ content = vim .trim (content or ' ' )
113+ if content == ' ' then
114+ return nil
115+ end
116+
117+ return content
118+ end
119+
95120local function format_timespec (spec )
96121 if type (spec ) == ' table' then
97122 return tostring (spec .sec or 0 ) .. ' :' .. tostring (spec .nsec or 0 )
@@ -619,6 +644,151 @@ function M.git_dir()
619644 return git_dir_at_root (M .repo_root ())
620645end
621646
647+ function M .get_unmerged_stage_entries (filepath )
648+ if type (filepath ) ~= ' string' or filepath == ' ' then
649+ return {}
650+ end
651+
652+ local ok , output = M .run_git_capture ({ ' ls-files' , ' -u' , ' --' , filepath })
653+ if not ok then
654+ return {}
655+ end
656+
657+ local entries = {}
658+ for line in output :gmatch (' [^\n ]+' ) do
659+ local mode , oid , stage , path = line :match (' ^(%d+)%s+([0-9a-f]+)%s+(%d)%s+(.+)$' )
660+ if mode and oid and stage and path then
661+ entries [tonumber (stage )] = {
662+ mode = mode ,
663+ oid = oid ,
664+ stage = tonumber (stage ),
665+ path = path ,
666+ }
667+ end
668+ end
669+
670+ return entries
671+ end
672+
673+ local function ref_name_label (ref )
674+ local ok , output = M .run_git_capture ({ ' name-rev' , ' --name-only' , ' --always' , ref }, {
675+ allowed_codes = { 0 , 128 },
676+ })
677+ if not ok then
678+ return nil
679+ end
680+
681+ local label = vim .trim (output )
682+ if label == ' ' or label == ' undefined' or label == ref then
683+ return nil
684+ end
685+
686+ if label :match (' ^refs/heads/' ) then
687+ return label :gsub (' ^refs/heads/' , ' ' )
688+ end
689+
690+ return label
691+ end
692+
693+ local function short_ref_oid (ref )
694+ local ok , output = M .run_git_capture ({ ' rev-parse' , ' --short' , ref }, {
695+ allowed_codes = { 0 , 128 },
696+ })
697+ if not ok then
698+ return nil
699+ end
700+
701+ local oid = vim .trim (output )
702+ if oid == ' ' then
703+ return nil
704+ end
705+
706+ return oid
707+ end
708+
709+ local function ref_display (ref )
710+ if type (ref ) ~= ' string' or ref == ' ' then
711+ return nil
712+ end
713+
714+ local label = ref_name_label (ref )
715+ if label and label ~= ref then
716+ return ref .. ' (' .. label .. ' )'
717+ end
718+
719+ local oid = short_ref_oid (ref )
720+ if oid then
721+ return ref .. ' (' .. oid .. ' )'
722+ end
723+
724+ return ref
725+ end
726+
727+ function M .get_operation_context ()
728+ local git_dir = M .git_dir ()
729+ if not git_dir then
730+ return {
731+ kind = nil ,
732+ prefix = nil ,
733+ ours_ref = ' HEAD' ,
734+ ours_display = ref_display (' HEAD' ) or ' HEAD' ,
735+ theirs_ref = nil ,
736+ theirs_display = nil ,
737+ }
738+ end
739+
740+ local context = {
741+ kind = nil ,
742+ prefix = nil ,
743+ ours_ref = ' HEAD' ,
744+ ours_display = ref_display (' HEAD' ) or ' HEAD' ,
745+ theirs_ref = nil ,
746+ theirs_display = nil ,
747+ }
748+
749+ if vim .uv .fs_stat (git_dir .. ' /rebase-merge' ) or vim .uv .fs_stat (git_dir .. ' /rebase-apply' ) then
750+ context .kind = ' rebase'
751+ context .prefix = ' Rebasing'
752+ if vim .uv .fs_stat (git_dir .. ' /REBASE_HEAD' ) then
753+ context .theirs_ref = ' REBASE_HEAD'
754+ context .theirs_display = ref_display (' REBASE_HEAD' ) or ' REBASE_HEAD'
755+ else
756+ local onto = read_trimmed_file (git_dir .. ' /rebase-merge/onto' )
757+ or read_trimmed_file (git_dir .. ' /rebase-apply/onto' )
758+ if onto then
759+ context .theirs_ref = onto
760+ context .theirs_display = short_ref_oid (onto ) or onto
761+ end
762+ end
763+ return context
764+ end
765+
766+ if vim .uv .fs_stat (git_dir .. ' /MERGE_HEAD' ) then
767+ context .kind = ' merge'
768+ context .theirs_ref = ' MERGE_HEAD'
769+ context .theirs_display = ref_display (' MERGE_HEAD' ) or ' MERGE_HEAD'
770+ return context
771+ end
772+
773+ if vim .uv .fs_stat (git_dir .. ' /CHERRY_PICK_HEAD' ) then
774+ context .kind = ' cherry_pick'
775+ context .prefix = ' Cherry-picking'
776+ context .theirs_ref = ' CHERRY_PICK_HEAD'
777+ context .theirs_display = ref_display (' CHERRY_PICK_HEAD' ) or ' CHERRY_PICK_HEAD'
778+ return context
779+ end
780+
781+ if vim .uv .fs_stat (git_dir .. ' /REVERT_HEAD' ) then
782+ context .kind = ' revert'
783+ context .prefix = ' Reverting'
784+ context .theirs_ref = ' REVERT_HEAD'
785+ context .theirs_display = ref_display (' REVERT_HEAD' ) or ' REVERT_HEAD'
786+ return context
787+ end
788+
789+ return context
790+ end
791+
622792function M .repo_watch_paths ()
623793 local git_dir = M .git_dir ()
624794 if not git_dir then
@@ -657,30 +827,22 @@ end
657827--- Retrieve file content at a specific git ref.
658828--- @param filepath string Path relative to repo root
659829--- @param ref string | nil " HEAD" , " :" (index ), or nil (working tree / disk )
660- --- @return string[] Lines of file content
661- function M .get_file_content (filepath , ref )
830+ --- @return string Raw file text
831+ function M .get_file_text (filepath , ref )
662832 if ref == nil then
663833 -- Read from working tree (disk)
664834 local root = M .repo_root ()
665835 if not root then
666- return {}
836+ return ' '
667837 end
668838 local full_path = root .. ' /' .. filepath
669- local f = io.open (full_path , ' r ' )
839+ local f = io.open (full_path , ' rb ' )
670840 if not f then
671- return {}
841+ return ' '
672842 end
673843 local content = f :read (' *a' )
674844 f :close ()
675- local lines = {}
676- for line in (content .. ' \n ' ):gmatch (' (.-)\n ' ) do
677- table.insert (lines , line )
678- end
679- -- Remove trailing empty line from the split
680- if # lines > 0 and lines [# lines ] == ' ' then
681- table.remove (lines )
682- end
683- return lines
845+ return content or ' '
684846 end
685847
686848 -- ref is "HEAD" or ":" (index)
@@ -693,11 +855,19 @@ function M.get_file_content(filepath, ref)
693855
694856 local result = vim .fn .system (' git show ' .. vim .fn .shellescape (git_ref ) .. ' 2>/dev/null' )
695857 if vim .v .shell_error ~= 0 then
858+ return ' '
859+ end
860+
861+ return result or ' '
862+ end
863+
864+ local function split_content_lines (text )
865+ if type (text ) ~= ' string' or text == ' ' then
696866 return {}
697867 end
698868
699869 local lines = {}
700- for line in (result .. ' \n ' ):gmatch (' (.-)\n ' ) do
870+ for line in (text .. ' \n ' ):gmatch (' (.-)\n ' ) do
701871 table.insert (lines , line )
702872 end
703873 if # lines > 0 and lines [# lines ] == ' ' then
@@ -706,6 +876,14 @@ function M.get_file_content(filepath, ref)
706876 return lines
707877end
708878
879+ --- Retrieve file content at a specific git ref.
880+ --- @param filepath string Path relative to repo root
881+ --- @param ref string | nil " HEAD" , " :" (index ), or nil (working tree / disk )
882+ --- @return string[] Lines of file content
883+ function M .get_file_content (filepath , ref )
884+ return split_content_lines (M .get_file_text (filepath , ref ))
885+ end
886+
709887function M .entry_paths (file )
710888 local paths = {}
711889 local seen = {}
0 commit comments