1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-03 05:20:04 +08:00
SpaceVim/bundle/gina.vim/autoload/vital/__gina__/Git.vim

182 lines
5.4 KiB
VimL

function! s:_vital_loaded(V) abort
let s:INI = a:V.import('Text.INI')
let s:Path = a:V.import('System.Filepath')
let s:String = a:V.import('Data.String')
let s:Store = a:V.import('System.Store')
endfunction
function! s:_vital_depends() abort
return [
\ 'Text.INI',
\ 'System.Filepath',
\ 'Data.String',
\ 'System.Store',
\]
endfunction
function! s:new(path) abort
let path = s:Path.remove_last_separator(expand(a:path))
let dirpath = isdirectory(path) ? path : fnamemodify(path, ':p:h')
let dirpath = simplify(s:Path.abspath(s:Path.realpath(path)))
" Find worktree
let dgit = finddir('.git', fnameescape(dirpath) . ';')
let dgit = empty(dgit) ? '' : fnamemodify(dgit, ':p:h')
let fgit = findfile('.git', fnameescape(dirpath) . ';')
let fgit = empty(fgit) ? '' : fnamemodify(fgit, ':p')
let worktree = len(dgit) > len(fgit) ? dgit : fgit
let worktree = empty(worktree) ? '' : fnamemodify(worktree, ':h')
if empty(worktree)
return {}
endif
" Find repository
let repository = s:Path.join(worktree, '.git')
if filereadable(repository)
" A '.git' may be a file which was created by '--separate-git-dir' option
let lines = readfile(repository)
if empty(lines)
throw printf(
\ 'vital: Git: An invalid .git file has found at "%s".',
\ repository,
\)
endif
let gitdir = matchstr(lines[0], '^gitdir:\s*\zs.\+$')
let is_abs = s:Path.is_absolute(gitdir)
let repository = is_abs ? gitdir : repository[:-5] . gitdir
let repository = empty(repository) ? '' : fnamemodify(repository, ':p:h')
endif
" Find commondir
let commondir = ''
if filereadable(s:Path.join(repository, 'commondir'))
let commondir = readfile(s:Path.join(repository, 'commondir'))[0]
let commondir = s:Path.join(repository, commondir)
endif
let git = {
\ 'worktree': simplify(s:Path.realpath(worktree)),
\ 'repository': simplify(s:Path.realpath(repository)),
\ 'commondir': simplify(s:Path.realpath(commondir)),
\}
lockvar git.worktree
lockvar git.repository
lockvar git.commondir
return git
endfunction
function! s:abspath(git, path) abort
let relpath = s:Path.realpath(expand(a:path))
if s:Path.is_absolute(relpath)
return relpath
endif
return s:Path.join(a:git.worktree, relpath)
endfunction
function! s:relpath(git, path) abort
let abspath = s:Path.realpath(expand(a:path))
if s:Path.is_relative(abspath)
return abspath
endif
let pattern = s:String.escape_pattern(a:git.worktree . s:Path.separator())
return abspath =~# '^' . pattern
\ ? matchstr(abspath, '^' . pattern . '\zs.*')
\ : abspath
endfunction
function! s:resolve(git, path) abort
let path = s:Path.realpath(a:path)
let path1 = s:Path.join(a:git.repository, path)
let path2 = empty(a:git.commondir)
\ ? ''
\ : s:Path.join(a:git.commondir, path)
return filereadable(path1) || isdirectory(path1)
\ ? path1
\ : filereadable(path2) || isdirectory(path2)
\ ? path2
\ : path1
endfunction
" The search paths are documented at
" https://git-scm.com/docs/git-rev-parse
function! s:ref(git, refname) abort
let refname = a:refname ==# '@' ? 'HEAD' : a:refname
let candidates = [
\ refname,
\ printf('refs/%s', refname),
\ printf('refs/tags/%s', refname),
\ printf('refs/heads/%s', refname),
\ printf('refs/remotes/%s', refname),
\ printf('refs/remotes/%s/HEAD', refname),
\]
let packed_refs = s:_get_packed_refs(a:git)
for candidate in candidates
let ref = s:_get_reference(a:git, candidate, packed_refs)
if !empty(ref)
return ref
endif
endfor
return {}
endfunction
" Private --------------------------------------------------------------------
function! s:_get_packed_refs(git) abort
let path = s:resolve(a:git, 'packed-refs')
if !filereadable(path)
return []
endif
let store = s:Store.of(path)
let packed_refs = store.get('packed-refs', [])
if !empty(packed_refs)
return packed_refs
endif
let packed_refs = readfile(path)
call store.set('packed-refs', packed_refs)
return packed_refs
endfunction
function! s:_get_reference(git, refname, packed_refs) abort
let ref = s:_get_reference_trad(a:git, a:refname, a:packed_refs)
if !empty(ref)
return ref
endif
return s:_get_reference_packed(a:git, a:refname, a:packed_refs)
endfunction
function! s:_get_reference_trad(git, refname, packed_refs) abort
let path = s:resolve(a:git, a:refname)
if !filereadable(path)
return {}
endif
let content = get(readfile(path), 0, '')
if content =~# '^ref:'
return s:_get_reference(
\ a:git,
\ matchstr(content, '^ref:\s\+\zs.\+'),
\ a:packed_refs,
\)
endif
let name = matchstr(a:refname, '^refs/\%(heads\|remotes\|tags\)/\zs.*')
return {
\ 'name': empty(name) ? content[:7] : name,
\ 'path': a:refname,
\ 'hash': content,
\}
endfunction
function! s:_get_reference_packed(git, refname, packed_refs) abort
let m = matchstr(a:packed_refs, '\s' . a:refname . '$')
if empty(m)
return {}
endif
let m = matchlist(m, '^\([0-9a-f]\+\)\s\+\(.*\)$')
let refname = m[2]
let name = matchstr(refname, '^refs/\%(heads\|remotes\|tags\)/\zs.*')
return {
\ 'name': name,
\ 'path': refname,
\ 'hash': m[1],
\}
endfunction