mirror of https://github.com/amix/vimrc.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8025 lines
268 KiB
8025 lines
268 KiB
" Location: autoload/fugitive.vim
|
|
" Maintainer: Tim Pope <http://tpo.pe/>
|
|
|
|
" The functions contained within this file are for internal use only. For the
|
|
" official API, see the commented functions in plugin/fugitive.vim.
|
|
|
|
if exists('g:autoloaded_fugitive')
|
|
finish
|
|
endif
|
|
let g:autoloaded_fugitive = 1
|
|
|
|
" Section: Utility
|
|
|
|
function! s:function(name) abort
|
|
return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
|
|
endfunction
|
|
|
|
function! s:sub(str,pat,rep) abort
|
|
return substitute(a:str,'\v\C'.a:pat,a:rep,'')
|
|
endfunction
|
|
|
|
function! s:gsub(str,pat,rep) abort
|
|
return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
|
|
endfunction
|
|
|
|
function! s:Uniq(list) abort
|
|
let i = 0
|
|
let seen = {}
|
|
while i < len(a:list)
|
|
let str = string(a:list[i])
|
|
if has_key(seen, str)
|
|
call remove(a:list, i)
|
|
else
|
|
let seen[str] = 1
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
return a:list
|
|
endfunction
|
|
|
|
function! s:JoinChomp(list) abort
|
|
if empty(a:list[-1])
|
|
return join(a:list[0:-2], "\n")
|
|
else
|
|
return join(a:list, "\n")
|
|
endif
|
|
endfunction
|
|
|
|
function! s:winshell() abort
|
|
return has('win32') && &shellcmdflag !~# '^-'
|
|
endfunction
|
|
|
|
function! s:WinShellEsc(arg) abort
|
|
if type(a:arg) == type([])
|
|
return join(map(copy(a:arg), 's:WinShellEsc(v:val)'))
|
|
elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
|
|
return a:arg
|
|
else
|
|
return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
|
|
endif
|
|
endfunction
|
|
|
|
function! s:shellesc(arg) abort
|
|
if type(a:arg) == type([])
|
|
return join(map(copy(a:arg), 's:shellesc(v:val)'))
|
|
elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
|
|
return a:arg
|
|
elseif s:winshell()
|
|
return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
|
|
else
|
|
return shellescape(a:arg)
|
|
endif
|
|
endfunction
|
|
|
|
let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
|
|
function! s:fnameescape(file) abort
|
|
if type(a:file) == type([])
|
|
return join(map(copy(a:file), 's:fnameescape(v:val)'))
|
|
elseif exists('*fnameescape')
|
|
return fnameescape(a:file)
|
|
else
|
|
return escape(a:file, s:fnameescape)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:throw(string) abort
|
|
throw 'fugitive: '.a:string
|
|
endfunction
|
|
|
|
function! s:VersionCheck() abort
|
|
if v:version < 703
|
|
return 'return ' . string('echoerr "fugitive: Vim 7.3 or newer required"')
|
|
elseif empty(fugitive#GitVersion())
|
|
let exe = get(s:GitCmd(), 0, '')
|
|
if len(exe) && !executable(exe)
|
|
return 'return ' . string('echoerr "fugitive: cannot find ' . string(exe) . ' in PATH"')
|
|
endif
|
|
return 'return ' . string('echoerr "fugitive: cannot execute Git"')
|
|
elseif !fugitive#GitVersion(1, 8, 5)
|
|
return 'return ' . string('echoerr "fugitive: Git 1.8.5 or newer required"')
|
|
else
|
|
return ''
|
|
endif
|
|
endfunction
|
|
|
|
let s:worktree_error = "core.worktree is required when using an external Git dir"
|
|
function! s:DirCheck(...) abort
|
|
let vcheck = s:VersionCheck()
|
|
if !empty(vcheck)
|
|
return vcheck
|
|
endif
|
|
let dir = call('FugitiveGitDir', a:000)
|
|
if !empty(dir) && FugitiveWorkTree(dir, 1) is# 0
|
|
return 'return ' . string('echoerr "fugitive: ' . s:worktree_error . '"')
|
|
elseif !empty(dir)
|
|
return ''
|
|
elseif empty(bufname(''))
|
|
return 'return ' . string('echoerr "fugitive: working directory does not belong to a Git repository"')
|
|
else
|
|
return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
|
|
endif
|
|
endfunction
|
|
|
|
function! s:Mods(mods, ...) abort
|
|
let mods = substitute(a:mods, '\C<mods>', '', '')
|
|
let mods = mods =~# '\S$' ? mods . ' ' : mods
|
|
if a:0 && mods !~# '\<\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
|
|
if a:1 ==# 'Edge'
|
|
if mods =~# '\<vertical\>' ? &splitright : &splitbelow
|
|
let mods = 'botright ' . mods
|
|
else
|
|
let mods = 'topleft ' . mods
|
|
endif
|
|
else
|
|
let mods = a:1 . ' ' . mods
|
|
endif
|
|
endif
|
|
return substitute(mods, '\s\+', ' ', 'g')
|
|
endfunction
|
|
|
|
if exists('+shellslash')
|
|
function! s:Slash(path) abort
|
|
return tr(a:path, '\', '/')
|
|
endfunction
|
|
else
|
|
function! s:Slash(path) abort
|
|
return a:path
|
|
endfunction
|
|
endif
|
|
|
|
function! s:Resolve(path) abort
|
|
let path = resolve(a:path)
|
|
if has('win32')
|
|
let path = FugitiveVimPath(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
|
|
endif
|
|
return path
|
|
endfunction
|
|
|
|
function! s:FileIgnoreCase(for_completion) abort
|
|
return (exists('+fileignorecase') && &fileignorecase)
|
|
\ || (a:for_completion && exists('+wildignorecase') && &wildignorecase)
|
|
endfunction
|
|
|
|
function! s:cpath(path, ...) abort
|
|
if s:FileIgnoreCase(0)
|
|
let path = FugitiveVimPath(tolower(a:path))
|
|
else
|
|
let path = FugitiveVimPath(a:path)
|
|
endif
|
|
return a:0 ? path ==# s:cpath(a:1) : path
|
|
endfunction
|
|
|
|
let s:executables = {}
|
|
|
|
function! s:executable(binary) abort
|
|
if !has_key(s:executables, a:binary)
|
|
let s:executables[a:binary] = executable(a:binary)
|
|
endif
|
|
return s:executables[a:binary]
|
|
endfunction
|
|
|
|
if !exists('s:temp_scripts')
|
|
let s:temp_scripts = {}
|
|
endif
|
|
function! s:TempScript(...) abort
|
|
let body = join(a:000, "\n")
|
|
if !has_key(s:temp_scripts, body)
|
|
let s:temp_scripts[body] = tempname() . '.sh'
|
|
endif
|
|
let temp = s:temp_scripts[body]
|
|
if !filereadable(temp)
|
|
call writefile(['#!/bin/sh'] + a:000, temp)
|
|
endif
|
|
return FugitiveGitPath(temp)
|
|
endfunction
|
|
|
|
function! s:DoAutocmd(...) abort
|
|
if v:version >= 704 || (v:version == 703 && has('patch442'))
|
|
return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
|
|
elseif &modelines > 0
|
|
return 'try|set modelines=0|' . join(map(copy(a:000), "'doautocmd ' . v:val"), '|') . '|finally|set modelines=' . &modelines . '|endtry'
|
|
else
|
|
return join(map(copy(a:000), "'doautocmd ' . v:val"), '|')
|
|
endif
|
|
endfunction
|
|
|
|
let s:nowait = v:version >= 704 ? '<nowait>' : ''
|
|
|
|
function! s:Map(mode, lhs, rhs, ...) abort
|
|
let maps = []
|
|
for mode in split(a:mode, '\zs')
|
|
let skip = 0
|
|
let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
|
|
let head = a:lhs
|
|
let tail = ''
|
|
let keys = get(g:, mode.'remap', {})
|
|
if type(keys) == type([])
|
|
continue
|
|
endif
|
|
while !empty(head)
|
|
if has_key(keys, head)
|
|
let head = keys[head]
|
|
let skip = empty(head)
|
|
break
|
|
endif
|
|
let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
|
|
let head = substitute(head, '<[^<>]*>$\|.$', '', '')
|
|
endwhile
|
|
if !skip && (flags !~# '<unique>' || empty(mapcheck(head.tail, mode)))
|
|
call add(maps, mode.'map <buffer>' . s:nowait . substitute(flags, '<unique>', '', '') . ' ' . head.tail . ' ' . a:rhs)
|
|
if a:0 > 1 && a:2
|
|
let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
|
|
\ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
|
|
endif
|
|
endif
|
|
endfor
|
|
exe join(maps, '|')
|
|
return ''
|
|
endfunction
|
|
|
|
function! fugitive#Autowrite() abort
|
|
if &autowrite || &autowriteall
|
|
try
|
|
if &confirm
|
|
let reconfirm = 1
|
|
setglobal noconfirm
|
|
endif
|
|
silent! wall
|
|
finally
|
|
if exists('reconfirm')
|
|
setglobal confirm
|
|
endif
|
|
endtry
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! fugitive#Wait(job_or_jobs, ...) abort
|
|
let jobs = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
|
|
call map(jobs, 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
|
|
call filter(jobs, 'type(v:val) !=# type("")')
|
|
let timeout_ms = a:0 ? a:1 : -1
|
|
if exists('*jobwait')
|
|
call map(copy(jobs), 'chanclose(v:val, "stdin")')
|
|
call jobwait(jobs, timeout_ms)
|
|
if len(jobs) && has('nvim-0.5')
|
|
sleep 1m
|
|
endif
|
|
else
|
|
let sleep = has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
|
|
for job in jobs
|
|
if ch_status(job) !=# 'closed'
|
|
call ch_close_in(job)
|
|
endif
|
|
endfor
|
|
let i = 0
|
|
for job in jobs
|
|
while ch_status(job) !=# 'closed' || job_status(job) ==# 'run'
|
|
if i == timeout_ms
|
|
break
|
|
endif
|
|
let i += 1
|
|
exe sleep
|
|
endwhile
|
|
endfor
|
|
endif
|
|
return a:job_or_jobs
|
|
endfunction
|
|
|
|
function! s:JobVimExit(dict, callback, temp, job, status) abort
|
|
let a:dict.exit_status = a:status
|
|
let a:dict.stderr = readfile(a:temp . '.err', 'b')
|
|
call delete(a:temp . '.err')
|
|
let a:dict.stdout = readfile(a:temp . '.out', 'b')
|
|
call delete(a:temp . '.out')
|
|
call delete(a:temp . '.in')
|
|
call remove(a:dict, 'job')
|
|
call call(a:callback[0], [a:dict] + a:callback[1:-1])
|
|
endfunction
|
|
|
|
function! s:JobNvimExit(dict, callback, job, data, type) dict abort
|
|
let a:dict.stdout = self.stdout
|
|
let a:dict.stderr = self.stderr
|
|
let a:dict.exit_status = a:data
|
|
call remove(a:dict, 'job')
|
|
call call(a:callback[0], [a:dict] + a:callback[1:-1])
|
|
endfunction
|
|
|
|
function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
|
|
let dict = a:0 ? a:1 : {}
|
|
let cb = len(a:callback) ? a:callback : [function('len')]
|
|
if exists('*jobstart')
|
|
call extend(a:jopts, {
|
|
\ 'stdout_buffered': v:true,
|
|
\ 'stderr_buffered': v:true,
|
|
\ 'on_exit': function('s:JobNvimExit', [dict, cb])})
|
|
let dict.job = jobstart(a:argv, a:jopts)
|
|
if !empty(a:stdin)
|
|
call chansend(dict.job, a:stdin)
|
|
call chanclose(dict.job, 'stdin')
|
|
endif
|
|
elseif exists('*job_start')
|
|
let temp = tempname()
|
|
call extend(a:jopts, {
|
|
\ 'out_io': 'file',
|
|
\ 'out_name': temp . '.out',
|
|
\ 'err_io': 'file',
|
|
\ 'err_name': temp . '.err',
|
|
\ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
|
|
if a:stdin ==# ['']
|
|
let a:jopts.in_io = 'null'
|
|
elseif !empty(a:stdin)
|
|
let a:jopts.in_io = 'file'
|
|
let a:jopts.in_name = temp . '.in'
|
|
call writefile(a:stdin, a:jopts.in_name, 'b')
|
|
endif
|
|
let dict.job = job_start(a:argv, a:jopts)
|
|
elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
|
|
throw 'fugitive: Vim 8 or higher required to use ' . &shell
|
|
else
|
|
let cmd = s:shellesc(a:argv)
|
|
let outfile = tempname()
|
|
try
|
|
let dict.stderr = split(system(' (' . cmd . ' >' . outfile . ') ', join(a:stdin, "\n")), "\n", 1)
|
|
let dict.exit_status = v:shell_error
|
|
let dict.stdout = readfile(outfile, 'b')
|
|
call call(cb[0], [dict] + cb[1:-1])
|
|
finally
|
|
call delete(outfile)
|
|
endtry
|
|
endif
|
|
if empty(a:callback)
|
|
call fugitive#Wait(dict)
|
|
endif
|
|
return dict
|
|
endfunction
|
|
|
|
function! s:add_methods(namespace, method_names) abort
|
|
for name in a:method_names
|
|
let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
|
|
endfor
|
|
endfunction
|
|
|
|
" Section: Git
|
|
|
|
let s:run_jobs = (exists('*job_start') || exists('*jobstart')) && exists('*bufwinid')
|
|
|
|
function! s:GitCmd() abort
|
|
if !exists('g:fugitive_git_executable')
|
|
return ['git']
|
|
elseif type(g:fugitive_git_executable) == type([])
|
|
return g:fugitive_git_executable
|
|
else
|
|
let dquote = '"\%([^"]\|""\|\\"\)*"\|'
|
|
let string = g:fugitive_git_executable
|
|
let list = []
|
|
if string =~# '^\w\+='
|
|
call add(list, '/usr/bin/env')
|
|
endif
|
|
while string =~# '\S'
|
|
let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] |]\)\+')
|
|
let string = strpart(string, len(arg))
|
|
let arg = substitute(arg, '^\s\+', '', '')
|
|
let arg = substitute(arg,
|
|
\ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
|
|
\ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
|
|
call add(list, arg)
|
|
endwhile
|
|
return list
|
|
endif
|
|
endfunction
|
|
|
|
function! s:GitShellCmd() abort
|
|
if !exists('g:fugitive_git_executable')
|
|
return 'git'
|
|
elseif type(g:fugitive_git_executable) == type([])
|
|
return s:shellesc(g:fugitive_git_executable)
|
|
else
|
|
return g:fugitive_git_executable
|
|
endif
|
|
endfunction
|
|
|
|
function! s:UserCommandCwd(dir) abort
|
|
let tree = s:Tree(a:dir)
|
|
return len(tree) ? FugitiveVimPath(tree) : getcwd()
|
|
endfunction
|
|
|
|
function! s:UserCommandList(...) abort
|
|
if !fugitive#GitVersion(1, 8, 5)
|
|
throw 'fugitive: Git 1.8.5 or higher required'
|
|
endif
|
|
if !exists('g:fugitive_git_command')
|
|
let git = s:GitCmd()
|
|
elseif type(g:fugitive_git_command) == type([])
|
|
let git = g:fugitive_git_command
|
|
else
|
|
let git = split(g:fugitive_git_command, '\s\+')
|
|
endif
|
|
let flags = []
|
|
if a:0 && type(a:1) == type({})
|
|
let git = copy(get(a:1, 'git', git))
|
|
let flags = get(a:1, 'flags', flags)
|
|
let dir = a:1.git_dir
|
|
elseif a:0
|
|
let dir = s:GitDir(a:1)
|
|
else
|
|
let dir = ''
|
|
endif
|
|
if len(dir)
|
|
let tree = s:Tree(dir)
|
|
if empty(tree)
|
|
call add(git, '--git-dir=' . FugitiveGitPath(dir))
|
|
else
|
|
if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
|
|
call add(git, '--git-dir=' . FugitiveGitPath(dir))
|
|
endif
|
|
if !s:cpath(tree, getcwd())
|
|
call extend(git, ['-C', FugitiveGitPath(tree)])
|
|
endif
|
|
endif
|
|
endif
|
|
return git + flags
|
|
endfunction
|
|
|
|
let s:git_versions = {}
|
|
function! fugitive#GitVersion(...) abort
|
|
let git = s:GitShellCmd()
|
|
if !has_key(s:git_versions, git)
|
|
let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
|
|
endif
|
|
if !a:0
|
|
return s:git_versions[git]
|
|
endif
|
|
let components = split(s:git_versions[git], '\D\+')
|
|
if empty(components)
|
|
return -1
|
|
endif
|
|
for i in range(len(a:000))
|
|
if a:000[i] > +get(components, i)
|
|
return 0
|
|
elseif a:000[i] < +get(components, i)
|
|
return 1
|
|
endif
|
|
endfor
|
|
return a:000[i] ==# get(components, i)
|
|
endfunction
|
|
|
|
let s:commondirs = {}
|
|
function! fugitive#CommonDir(dir) abort
|
|
if empty(a:dir)
|
|
return ''
|
|
endif
|
|
if !has_key(s:commondirs, a:dir)
|
|
if getfsize(a:dir . '/HEAD') < 10
|
|
let s:commondirs[a:dir] = ''
|
|
elseif filereadable(a:dir . '/commondir')
|
|
let cdir = get(readfile(a:dir . '/commondir', 1), 0, '')
|
|
if cdir =~# '^/\|^\a:/'
|
|
let s:commondirs[a:dir] = s:Slash(FugitiveVimPath(cdir))
|
|
else
|
|
let s:commondirs[a:dir] = simplify(a:dir . '/' . cdir)
|
|
endif
|
|
else
|
|
let s:commondirs[a:dir] = a:dir
|
|
endif
|
|
endif
|
|
return s:commondirs[a:dir]
|
|
endfunction
|
|
|
|
function! s:Dir(...) abort
|
|
return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
|
|
endfunction
|
|
|
|
function! s:GitDir(...) abort
|
|
return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
|
|
endfunction
|
|
|
|
function! s:DirUrlPrefix(...) abort
|
|
return 'fugitive://' . call('s:GitDir', a:000) . '//'
|
|
endfunction
|
|
|
|
function! s:Tree(...) abort
|
|
return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
|
|
endfunction
|
|
|
|
function! s:HasOpt(args, ...) abort
|
|
let args = a:args[0 : index(a:args, '--')]
|
|
let opts = copy(a:000)
|
|
if type(opts[0]) == type([])
|
|
if empty(args) || index(opts[0], args[0]) == -1
|
|
return 0
|
|
endif
|
|
call remove(opts, 0)
|
|
endif
|
|
for opt in opts
|
|
if index(args, opt) != -1
|
|
return 1
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
|
|
if !a:explicit
|
|
call insert(a:cmd, '--literal-pathspecs')
|
|
endif
|
|
let split = index(a:cmd, '--')
|
|
for i in range(split < 0 ? len(a:cmd) : split)
|
|
if type(a:cmd[i]) == type(0)
|
|
if a:literal
|
|
let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
|
|
else
|
|
let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
|
|
endif
|
|
endif
|
|
endfor
|
|
if split < 0
|
|
return a:cmd
|
|
endif
|
|
for i in range(split + 1, len(a:cmd) - 1)
|
|
if type(a:cmd[i]) == type(0)
|
|
if a:literal
|
|
let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
|
|
else
|
|
let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
|
|
endif
|
|
elseif !a:explicit
|
|
let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
|
|
endif
|
|
endfor
|
|
return a:cmd
|
|
endfunction
|
|
|
|
function! s:PrepareEnv(env, dir) abort
|
|
if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
|
|
let index_dir = substitute($GIT_INDEX_FILE, '[^/]\+$', '', '')
|
|
let our_dir = fugitive#Find('.git/', a:dir)
|
|
if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(FugitiveVimPath(index_dir)), our_dir)
|
|
let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
|
|
endif
|
|
endif
|
|
if len($GIT_WORK_TREE)
|
|
let a:env['GIT_WORK_TREE'] = '.'
|
|
endif
|
|
endfunction
|
|
|
|
let s:prepare_env = {
|
|
\ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
|
|
\ 'core.editor': 'GIT_EDITOR',
|
|
\ 'core.askpass': 'GIT_ASKPASS',
|
|
\ }
|
|
function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
|
|
if !fugitive#GitVersion(1, 8, 5)
|
|
throw 'fugitive: Git 1.8.5 or higher required'
|
|
endif
|
|
let git = s:GitCmd()
|
|
if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'git_dir') && has_key(a:1, 'flags') && has_key(a:1, 'args')
|
|
let cmd = a:1.flags + a:1.args
|
|
let dir = a:1.git_dir
|
|
if has_key(a:1, 'git')
|
|
let git = a:1.git
|
|
endif
|
|
let env = get(a:1, 'env', {})
|
|
else
|
|
let list_args = []
|
|
let cmd = []
|
|
for arg in a:000
|
|
if type(arg) ==# type([])
|
|
call extend(list_args, arg)
|
|
else
|
|
call add(cmd, arg)
|
|
endif
|
|
endfor
|
|
call extend(cmd, list_args)
|
|
let env = {}
|
|
endif
|
|
let autoenv = {}
|
|
let explicit_pathspec_option = 0
|
|
let literal_pathspecs = 1
|
|
let i = 0
|
|
let arg_count = 0
|
|
while i < len(cmd)
|
|
if type(cmd[i]) == type({})
|
|
if has_key(cmd[i], 'git_dir')
|
|
let dir = cmd[i].git_dir
|
|
elseif has_key(cmd[i], 'dir')
|
|
let dir = cmd[i].dir
|
|
endif
|
|
if has_key(cmd[i], 'git')
|
|
let git = cmd[i].git
|
|
endif
|
|
if has_key(cmd[i], 'env')
|
|
call extend(env, cmd[i].env)
|
|
endif
|
|
call remove(cmd, i)
|
|
elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
|
|
let dir = remove(cmd, i)
|
|
elseif cmd[i] =~# '^--git-dir='
|
|
let dir = remove(cmd, i)[10:-1]
|
|
elseif type(cmd[i]) ==# type(0)
|
|
let dir = s:Dir(remove(cmd, i))
|
|
elseif cmd[i] ==# '-c' && len(cmd) > i + 1
|
|
let key = matchstr(cmd[i+1], '^[^=]*')
|
|
if has_key(s:prepare_env, tolower(key))
|
|
let var = s:prepare_env[tolower(key)]
|
|
let val = matchstr(cmd[i+1], '=\zs.*')
|
|
let autoenv[var] = val
|
|
endif
|
|
let i += 2
|
|
elseif cmd[i] =~# '^--.*pathspecs$'
|
|
let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
|
|
let explicit_pathspec_option = 1
|
|
let i += 1
|
|
elseif cmd[i] !~# '^-'
|
|
let arg_count = len(cmd) - i
|
|
break
|
|
else
|
|
let i += 1
|
|
endif
|
|
endwhile
|
|
if !exists('dir')
|
|
let dir = s:Dir()
|
|
endif
|
|
call extend(autoenv, env)
|
|
call s:PrepareEnv(autoenv, dir)
|
|
if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
|
|
let autoenv.GPG_TTY = ''
|
|
endif
|
|
call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
|
|
return [s:GitDir(dir), env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
|
|
endfunction
|
|
|
|
function! s:BuildEnvPrefix(env) abort
|
|
let pre = ''
|
|
let env = items(a:env)
|
|
if empty(env)
|
|
return ''
|
|
elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
|
|
return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
|
|
elseif s:winshell()
|
|
return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
|
|
else
|
|
return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
|
|
endif
|
|
endfunction
|
|
|
|
function! s:JobOpts(cmd, env) abort
|
|
if empty(a:env)
|
|
return [a:cmd, {}]
|
|
elseif has('patch-8.2.0239') ||
|
|
\ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
|
|
\ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
|
|
return [a:cmd, {'env': a:env}]
|
|
endif
|
|
let envlist = map(items(a:env), 'join(v:val, "=")')
|
|
if !has('win32')
|
|
return [['/usr/bin/env'] + envlist + a:cmd, {}]
|
|
else
|
|
let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
|
|
if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
|
|
return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
|
|
else
|
|
return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
function! s:PrepareJob(opts) abort
|
|
let dict = {'argv': a:opts.argv}
|
|
if has_key(a:opts, 'env')
|
|
let dict.env = a:opts.env
|
|
endif
|
|
let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
|
|
if has_key(a:opts, 'cwd')
|
|
if has('patch-8.0.0902')
|
|
let jopts.cwd = a:opts.cwd
|
|
let dict.cwd = a:opts.cwd
|
|
else
|
|
throw 'fugitive: cwd unsupported'
|
|
endif
|
|
endif
|
|
return [argv, jopts, dict]
|
|
endfunction
|
|
|
|
function! fugitive#PrepareJob(...) abort
|
|
if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
|
|
return s:PrepareJob(a:1)
|
|
endif
|
|
let [dir, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
|
|
let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
|
|
if len(user_env)
|
|
let dict.env = user_env
|
|
endif
|
|
let cmd = flags + args
|
|
let tree = s:Tree(dir)
|
|
if empty(tree) || index(cmd, '--') == len(cmd) - 1
|
|
let dict.cwd = getcwd()
|
|
call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
|
|
else
|
|
let dict.cwd = FugitiveVimPath(tree)
|
|
call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
|
|
if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
|
|
call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
|
|
endif
|
|
endif
|
|
call extend(cmd, git, 'keep')
|
|
return s:JobOpts(cmd, exec_env) + [dict]
|
|
endfunction
|
|
|
|
function! fugitive#Execute(...) abort
|
|
let cb = copy(a:000)
|
|
let cmd = []
|
|
let stdin = []
|
|
while len(cb) && type(cb[0]) !=# type(function('tr'))
|
|
if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
|
|
if type(cb[0].stdin) == type([])
|
|
call extend(stdin, cb[0].stdin)
|
|
elseif type(cb[0].stdin) == type('')
|
|
call extend(stdin, readfile(cb[0].stdin, 'b'))
|
|
endif
|
|
if len(keys(cb[0])) == 1
|
|
call remove(cb, 0)
|
|
continue
|
|
endif
|
|
endif
|
|
call add(cmd, remove(cb, 0))
|
|
endwhile
|
|
let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
|
|
return s:JobExecute(argv, jopts, stdin, cb, dict)
|
|
endfunction
|
|
|
|
function! s:BuildShell(dir, env, git, args) abort
|
|
let cmd = copy(a:args)
|
|
let tree = s:Tree(a:dir)
|
|
let pre = s:BuildEnvPrefix(a:env)
|
|
if empty(tree) || index(cmd, '--') == len(cmd) - 1
|
|
call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
|
|
else
|
|
call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
|
|
if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
|
|
call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
|
|
endif
|
|
endif
|
|
return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
|
|
endfunction
|
|
|
|
function! s:JobNvimCallback(lines, job, data, type) abort
|
|
let a:lines[-1] .= remove(a:data, 0)
|
|
call extend(a:lines, a:data)
|
|
endfunction
|
|
|
|
function! s:SystemList(cmd) abort
|
|
let exit = []
|
|
if exists('*jobstart')
|
|
let lines = ['']
|
|
let jopts = {
|
|
\ 'on_stdout': function('s:JobNvimCallback', [lines]),
|
|
\ 'on_stderr': function('s:JobNvimCallback', [lines]),
|
|
\ 'on_exit': { j, code, _ -> add(exit, code) }}
|
|
let job = jobstart(a:cmd, jopts)
|
|
call chanclose(job, 'stdin')
|
|
call jobwait([job])
|
|
if empty(lines[-1])
|
|
call remove(lines, -1)
|
|
endif
|
|
return [lines, exit[0]]
|
|
elseif exists('*job_start')
|
|
let lines = []
|
|
let jopts = {
|
|
\ 'out_cb': { j, str -> add(lines, str) },
|
|
\ 'err_cb': { j, str -> add(lines, str) },
|
|
\ 'exit_cb': { j, code -> add(exit, code) }}
|
|
let job = job_start(a:cmd, jopts)
|
|
call ch_close_in(job)
|
|
let sleep = has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
|
|
while ch_status(job) !=# 'closed' || job_status(job) ==# 'run'
|
|
exe sleep
|
|
endwhile
|
|
return [lines, exit[0]]
|
|
else
|
|
let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
|
|
let lines = split(output, "\n", 1)
|
|
if empty(lines[-1])
|
|
call remove(lines, -1)
|
|
endif
|
|
return [lines, v:shell_error]
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#ShellCommand(...) abort
|
|
let [dir, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
|
|
return s:BuildShell(dir, env, git, flags + args)
|
|
endfunction
|
|
|
|
function! fugitive#Prepare(...) abort
|
|
return call('fugitive#ShellCommand', a:000)
|
|
endfunction
|
|
|
|
function! s:SystemError(cmd, ...) abort
|
|
let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
|
|
try
|
|
if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
|
|
let shellredir = &shellredir
|
|
if &shell =~# 'csh'
|
|
set shellredir=>&
|
|
else
|
|
set shellredir=>%s\ 2>&1
|
|
endif
|
|
endif
|
|
if exists('+guioptions') && &guioptions =~# '!'
|
|
let guioptions = &guioptions
|
|
set guioptions-=!
|
|
endif
|
|
let out = call('system', [cmd] + a:000)
|
|
return [out, v:shell_error]
|
|
catch /^Vim\%((\a\+)\)\=:E484:/
|
|
let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
|
|
call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
|
|
call map(opts, 'v:val."=".eval("&".v:val)')
|
|
call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
|
|
finally
|
|
if exists('shellredir')
|
|
let &shellredir = shellredir
|
|
endif
|
|
if exists('guioptions')
|
|
let &guioptions = guioptions
|
|
endif
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:ChompStderr(...) abort
|
|
let r = call('fugitive#Execute', a:000)
|
|
return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
|
|
endfunction
|
|
|
|
function! s:ChompDefault(default, ...) abort
|
|
let r = call('fugitive#Execute', a:000)
|
|
return r.exit_status ? a:default : s:JoinChomp(r.stdout)
|
|
endfunction
|
|
|
|
function! s:LinesError(...) abort
|
|
let r = call('fugitive#Execute', a:000)
|
|
if empty(r.stdout[-1])
|
|
call remove(r.stdout, -1)
|
|
endif
|
|
return [r.exit_status ? [] : r.stdout, r.exit_status]
|
|
endfunction
|
|
|
|
function! s:NullError(cmd) abort
|
|
let r = fugitive#Execute(a:cmd)
|
|
let list = r.exit_status ? [] : split(tr(join(r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
|
|
return [list, s:JoinChomp(r.stderr), r.exit_status]
|
|
endfunction
|
|
|
|
function! s:TreeChomp(...) abort
|
|
let r = call('fugitive#Execute', a:000)
|
|
if !r.exit_status
|
|
return s:JoinChomp(r.stdout)
|
|
endif
|
|
throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
|
|
endfunction
|
|
|
|
function! s:StdoutToFile(out, cmd, ...) abort
|
|
let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
|
|
let exit = []
|
|
if exists('*jobstart')
|
|
call extend(jopts, {
|
|
\ 'stdout_buffered': v:true,
|
|
\ 'stderr_buffered': v:true,
|
|
\ 'on_exit': { j, code, _ -> add(exit, code) }})
|
|
let job = jobstart(argv, jopts)
|
|
if a:0
|
|
call chansend(job, a:1)
|
|
endif
|
|
call chanclose(job, 'stdin')
|
|
call jobwait([job])
|
|
if len(a:out)
|
|
call writefile(jopts.stdout, a:out, 'b')
|
|
endif
|
|
return [join(jopts.stderr, "\n"), exit[0]]
|
|
elseif exists('*job_start')
|
|
try
|
|
let err = tempname()
|
|
call extend(jopts, {
|
|
\ 'out_io': len(a:out) ? 'file' : 'null',
|
|
\ 'out_name': a:out,
|
|
\ 'err_io': 'file',
|
|
\ 'err_name': err,
|
|
\ 'exit_cb': { j, code -> add(exit, code) }})
|
|
let job = job_start(argv, jopts)
|
|
if a:0
|
|
call ch_sendraw(job, a:1)
|
|
endif
|
|
call ch_close_in(job)
|
|
while ch_status(job) !=# 'closed' || job_status(job) ==# 'run'
|
|
exe has('patch-8.2.2366') ? 'sleep! 1m' : 'sleep 1m'
|
|
endwhile
|
|
return [join(readfile(err, 'b'), "\n"), exit[0]]
|
|
finally
|
|
call delete(err)
|
|
endtry
|
|
elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
|
|
throw 'fugitive: Vim 8 or higher required to use ' . &shell
|
|
else
|
|
let cmd = fugitive#ShellCommand(a:cmd)
|
|
return s:SystemError(' (' . cmd . ' >' . a:out . ') ')
|
|
endif
|
|
endfunction
|
|
|
|
let s:head_cache = {}
|
|
|
|
function! fugitive#Head(...) abort
|
|
let dir = a:0 > 1 ? a:2 : s:Dir()
|
|
if empty(dir)
|
|
return ''
|
|
endif
|
|
let file = fugitive#Find('.git/HEAD', dir)
|
|
let ftime = getftime(file)
|
|
if ftime == -1
|
|
return ''
|
|
elseif ftime != get(s:head_cache, file, [-1])[0]
|
|
let s:head_cache[file] = [ftime, readfile(file)[0]]
|
|
endif
|
|
let head = s:head_cache[file][1]
|
|
let len = a:0 ? a:1 : 0
|
|
if head =~# '^ref: '
|
|
if len < 0
|
|
return strpart(head, 5)
|
|
else
|
|
return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
|
|
endif
|
|
elseif head =~# '^\x\{40,\}$'
|
|
return len < 0 ? head : strpart(head, 0, len)
|
|
else
|
|
return ''
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#RevParse(rev, ...) abort
|
|
let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
|
|
if hash =~# '^\x\{40,\}$'
|
|
return hash
|
|
endif
|
|
throw 'fugitive: failed to parse revision ' . a:rev
|
|
endfunction
|
|
|
|
" Section: Git config
|
|
|
|
function! s:ConfigTimestamps(dir, dict) abort
|
|
let files = ['/etc/gitconfig', '~/.gitconfig',
|
|
\ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
|
|
if len(a:dir)
|
|
call add(files, fugitive#Find('.git/config', a:dir))
|
|
endif
|
|
call extend(files, get(a:dict, 'include.path', []))
|
|
return join(map(files, 'getftime(expand(v:val))'), ',')
|
|
endfunction
|
|
|
|
function! s:ConfigCallback(r, into) abort
|
|
let dict = a:into[1]
|
|
if has_key(dict, 'job')
|
|
call remove(dict, 'job')
|
|
endif
|
|
let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
|
|
for line in lines
|
|
let key = matchstr(line, "^[^\n]*")
|
|
if !has_key(dict, key)
|
|
let dict[key] = []
|
|
endif
|
|
if len(key) ==# len(line)
|
|
call add(dict[key], 1)
|
|
else
|
|
call add(dict[key], strpart(line, len(key) + 1))
|
|
endif
|
|
endfor
|
|
let callbacks = remove(dict, 'callbacks')
|
|
lockvar! dict
|
|
let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
|
|
for callback in callbacks
|
|
call call(callback[0], [dict] + callback[1:-1])
|
|
endfor
|
|
endfunction
|
|
|
|
let s:config_prototype = {}
|
|
|
|
let s:config = {}
|
|
function! fugitive#ExpireConfig(...) abort
|
|
if !a:0 || a:1 is# 0
|
|
let s:config = {}
|
|
else
|
|
let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
|
|
if len(key) && has_key(s:config, key)
|
|
call remove(s:config, key)
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#Config(...) abort
|
|
let name = ''
|
|
let default = get(a:, 3, '')
|
|
if a:0 && type(a:1) == type(function('tr'))
|
|
let dir = s:Dir()
|
|
let callback = a:000
|
|
elseif a:0 > 1 && type(a:2) == type(function('tr'))
|
|
if type(a:1) == type({}) && has_key(a:1, 'GetAll')
|
|
if has_key(a:1, 'callbacks')
|
|
call add(a:1.callbacks, a:000[1:-1])
|
|
else
|
|
call call(a:2, [a:1] + a:000[2:-1])
|
|
endif
|
|
return a:1
|
|
else
|
|
let dir = s:Dir(a:1)
|
|
let callback = a:000[1:-1]
|
|
endif
|
|
elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
|
|
return get(fugitive#ConfigGetAll(a:1, a:2), 0, default)
|
|
elseif a:0 >= 2
|
|
let dir = s:Dir(a:2)
|
|
let name = a:1
|
|
elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
|
|
return a:1
|
|
elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
|
|
let dir = s:Dir()
|
|
let name = a:1
|
|
elseif a:0 == 1
|
|
let dir = s:Dir(a:1)
|
|
else
|
|
let dir = s:Dir()
|
|
endif
|
|
let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
|
|
let git_dir = s:GitDir(dir)
|
|
let dir_key = len(git_dir) ? git_dir : '_'
|
|
let [ts, dict] = get(s:config, dir_key, ['new', {}])
|
|
if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
|
|
let dict = copy(s:config_prototype)
|
|
let dict.git_dir = git_dir
|
|
let into = ['running', dict]
|
|
let dict.callbacks = []
|
|
let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
|
|
if has_key(exec, 'job')
|
|
let dict.job = exec.job
|
|
endif
|
|
let s:config[dir_key] = into
|
|
endif
|
|
if !exists('l:callback')
|
|
call fugitive#Wait(dict)
|
|
elseif has_key(dict, 'callbacks')
|
|
call add(dict.callbacks, callback)
|
|
else
|
|
call call(callback[0], [dict] + callback[1:-1])
|
|
endif
|
|
return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
|
|
endfunction
|
|
|
|
function! fugitive#ConfigGetAll(name, ...) abort
|
|
if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
|
|
let config = fugitive#Config(a:name)
|
|
let name = a:1
|
|
else
|
|
let config = fugitive#Config(a:0 ? a:1 : s:Dir())
|
|
let name = a:name
|
|
endif
|
|
let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
|
|
call fugitive#Wait(config)
|
|
return name =~# '\.' ? copy(get(config, name, [])) : []
|
|
endfunction
|
|
|
|
function! fugitive#ConfigGetRegexp(pattern, ...) abort
|
|
if type(a:pattern) !=# type('')
|
|
let config = fugitive#Config(a:name)
|
|
let pattern = a:0 ? a:1 : '.*'
|
|
else
|
|
let config = fugitive#Config(a:0 ? a:1 : s:Dir())
|
|
let pattern = a:pattern
|
|
endif
|
|
call fugitive#Wait(config)
|
|
let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
|
|
if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
|
|
return filtered
|
|
endif
|
|
let transformed = {}
|
|
for [k, v] in items(filtered)
|
|
let k = matchstr(k, pattern)
|
|
if len(k)
|
|
let transformed[k] = v
|
|
endif
|
|
endfor
|
|
return transformed
|
|
endfunction
|
|
|
|
function! s:config_GetAll(name) dict abort
|
|
let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
|
|
call fugitive#Wait(self)
|
|
return name =~# '\.' ? copy(get(self, name, [])) : []
|
|
endfunction
|
|
|
|
function! s:config_Get(name, ...) dict abort
|
|
return get(self.GetAll(a:name), 0, a:0 ? a:1 : '')
|
|
endfunction
|
|
|
|
function! s:config_GetRegexp(pattern) dict abort
|
|
return fugitive#ConfigGetRegexp(self, a:pattern)
|
|
endfunction
|
|
|
|
call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
|
|
|
|
function! s:RemoteDefault(dir) abort
|
|
let head = FugitiveHead(0, a:dir)
|
|
let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
|
|
let i = 10
|
|
while remote ==# '.' && i > 0
|
|
let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
|
|
let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
|
|
let i -= 1
|
|
endwhile
|
|
return remote =~# '^\.\=$' ? 'origin' : remote
|
|
endfunction
|
|
|
|
function! s:SshParseHost(value) abort
|
|
let patterns = []
|
|
let negates = []
|
|
for host in split(a:value, '\s\+')
|
|
let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
|
|
if pattern[0] ==# '!'
|
|
call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
|
|
else
|
|
call add(patterns, pattern)
|
|
endif
|
|
endfor
|
|
return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
|
|
endfunction
|
|
|
|
function! s:SshParseConfig(into, root, file, ...) abort
|
|
if !filereadable(a:file)
|
|
return a:into
|
|
endif
|
|
let host = a:0 ? a:1 : '^\%(.*\)$'
|
|
for line in readfile(a:file)
|
|
let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
|
|
let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
|
|
if key ==# 'match'
|
|
let host = value ==# 'all' ? '^\%(.*\)$' : ''
|
|
elseif key ==# 'host'
|
|
let host = s:SshParseHost(value)
|
|
elseif key ==# 'include'
|
|
call s:SshParseInclude(a:into, a:root, host, value)
|
|
elseif len(key) && len(host)
|
|
call extend(a:into, {key: []}, 'keep')
|
|
call add(a:into[key], [host, value])
|
|
endif
|
|
endfor
|
|
return a:into
|
|
endfunction
|
|
|
|
function! s:SshParseInclude(into, root, host, value) abort
|
|
for glob in split(a:value)
|
|
if glob !~# '^/'
|
|
let glob = a:root . glob
|
|
endif
|
|
for file in split(glob(glob), "\n")
|
|
call s:SshParseConfig(a:into, a:root, file, a:host)
|
|
endfor
|
|
endfor
|
|
endfunction
|
|
|
|
unlet! s:ssh_config
|
|
function! fugitive#SshConfig(host, ...) abort
|
|
if !exists('s:ssh_config')
|
|
let s:ssh_config = {}
|
|
for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
|
|
call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
|
|
endfor
|
|
endif
|
|
let host_config = {}
|
|
for key in a:0 ? a:1 : keys(s:ssh_config)
|
|
for [host_pattern, value] in get(s:ssh_config, key, [])
|
|
if a:host =~# host_pattern
|
|
let host_config[key] = value
|
|
break
|
|
endif
|
|
endfor
|
|
endfor
|
|
return host_config
|
|
endfunction
|
|
|
|
function! fugitive#SshHostAlias(authority) abort
|
|
let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
|
|
let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
|
|
if empty(user)
|
|
let user = get(c, 'user', '')
|
|
endif
|
|
if empty(port)
|
|
let port = get(c, 'port', '')
|
|
endif
|
|
return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
|
|
endfunction
|
|
|
|
function! s:CurlResponse(result) abort
|
|
let a:result.headers = {}
|
|
for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
|
|
let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
|
|
if len(header)
|
|
let k = tolower(header[1])
|
|
if has_key(a:result.headers, k)
|
|
let a:result.headers[k] .= ', ' . header[2]
|
|
else
|
|
let a:result.headers[k] = header[2]
|
|
endif
|
|
elseif empty(line)
|
|
break
|
|
endif
|
|
endfor
|
|
endfunction
|
|
|
|
let s:remote_headers = {}
|
|
|
|
function! fugitive#RemoteHttpHeaders(remote) abort
|
|
let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
|
|
if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
|
|
return {}
|
|
endif
|
|
if !has_key(s:remote_headers, remote)
|
|
let url = remote . '/info/refs?service=git-upload-pack'
|
|
let exec = s:JobExecute(
|
|
\ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
|
|
\ url], {}, [], [function('s:CurlResponse')], {})
|
|
call fugitive#Wait(exec)
|
|
let s:remote_headers[remote] = exec.headers
|
|
endif
|
|
return s:remote_headers[remote]
|
|
endfunction
|
|
|
|
function! s:UrlParse(url) abort
|
|
let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
|
|
if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
|
|
let url = {'scheme': 'ssh', 'authority': scp_authority,
|
|
\ 'path': strpart(a:url, len(scp_authority) + 1)}
|
|
elseif empty(a:url)
|
|
let url = {'scheme': '', 'authority': '', 'path': ''}
|
|
else
|
|
let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/.*\)\=\%(#\|$\)')
|
|
if empty(match)
|
|
let url = {'scheme': 'file', 'authority': '', 'path': a:url}
|
|
else
|
|
let url = {'scheme': match[1], 'authority': match[2]}
|
|
let url.path = empty(match[3]) ? '/' : match[3]
|
|
endif
|
|
endif
|
|
if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
|
|
let url.path = strpart(url.path, 1)
|
|
endif
|
|
return url
|
|
endfunction
|
|
|
|
function! s:RemoteResolve(url, flags) abort
|
|
let remote = s:UrlParse(a:url)
|
|
if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
|
|
let headers = fugitive#RemoteHttpHeaders(remote.scheme . '://' . remote.authority . remote.path)
|
|
let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
|
|
if len(loc)
|
|
let remote = s:UrlParse(loc)
|
|
else
|
|
let remote.headers = headers
|
|
endif
|
|
elseif remote.scheme ==# 'ssh'
|
|
let remote.authority = fugitive#SshHostAlias(remote.authority)
|
|
endif
|
|
return remote
|
|
endfunction
|
|
|
|
function! s:ConfigLengthSort(i1, i2) abort
|
|
return len(a:i2[0]) - len(a:i1[0])
|
|
endfunction
|
|
|
|
function! s:RemoteCallback(config, into, flags, cb) abort
|
|
if a:into.remote_name =~# '^\.\=$'
|
|
let a:into.remote_name = s:RemoteDefault(a:config)
|
|
endif
|
|
let url = a:into.remote_name
|
|
|
|
if url ==# '.git'
|
|
let url = s:GitDir(a:config)
|
|
elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
|
|
let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
|
|
endif
|
|
let instead_of = []
|
|
for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
|
|
for v in vs
|
|
call add(instead_of, [v, k])
|
|
endfor
|
|
endfor
|
|
call sort(instead_of, 's:ConfigLengthSort')
|
|
for [orig, replacement] in instead_of
|
|
if strpart(url, 0, len(orig)) ==# orig
|
|
let url = replacement . strpart(url, len(orig))
|
|
break
|
|
endif
|
|
endfor
|
|
if index(a:flags, ':noresolve') < 0
|
|
call extend(a:into, s:RemoteResolve(url, a:flags))
|
|
else
|
|
call extend(a:into, s:UrlParse(url))
|
|
endif
|
|
let a:into.user = matchstr(a:into.authority, '.\{-\}\ze@', '', '')
|
|
let a:into.host = substitute(a:into.authority, '.\{-\}@', '', '')
|
|
let a:into.hostname = substitute(a:into.host, ':\d\+$', '', '')
|
|
let a:into.port = matchstr(a:into.host, ':\zs\d\+$', '', '')
|
|
if a:into.path =~# '^/'
|
|
let a:into.url = a:into.scheme . '://' . a:into.authority . a:into.path
|
|
elseif a:into.path =~# '^\~'
|
|
let a:into.url = a:into.scheme . '://' . a:into.authority . '/' . a:into.path
|
|
elseif a:into.scheme ==# 'ssh' && a:into.authority !~# ':'
|
|
let a:into.url = a:into.authority . ':' . a:into.path
|
|
else
|
|
let a:into.url = url
|
|
endif
|
|
if len(a:cb)
|
|
call call(a:cb[0], [a:into] + a:cb[1:-1])
|
|
endif
|
|
endfunction
|
|
|
|
function! s:Remote(dir, remote, flags, cb) abort
|
|
let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
|
|
let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
|
|
if len(a:cb)
|
|
return config
|
|
else
|
|
call fugitive#Wait(config)
|
|
return into
|
|
endif
|
|
endfunction
|
|
|
|
function! s:RemoteParseArgs(args) abort
|
|
" Extract ':noresolve' style flags and an optional callback
|
|
let args = []
|
|
let flags = []
|
|
let cb = copy(a:args)
|
|
while len(cb)
|
|
if type(cb[0]) ==# type(function('tr'))
|
|
break
|
|
elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
|
|
call add(flags, remove(cb, 0))
|
|
else
|
|
call add(args, remove(cb, 0))
|
|
endif
|
|
endwhile
|
|
|
|
" From the remaining 0-2 arguments, extract the remote and Git config
|
|
let remote = ''
|
|
if empty(args)
|
|
let dir_or_config = s:Dir()
|
|
elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
|
|
let dir_or_config = s:Dir()
|
|
let remote = args[0]
|
|
elseif len(args) == 1
|
|
let dir_or_config = args[0]
|
|
if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
|
|
let remote = args[0].remote_name
|
|
endif
|
|
elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
|
|
let dir_or_config = args[1]
|
|
let remote = args[0]
|
|
else
|
|
let dir_or_config = args[0]
|
|
let remote = args[1]
|
|
endif
|
|
return [dir_or_config, remote, flags, cb]
|
|
endfunction
|
|
|
|
function! fugitive#Remote(...) abort
|
|
let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
|
|
return s:Remote(dir_or_config, remote, flags, cb)
|
|
endfunction
|
|
|
|
function! s:RemoteUrlCallback(remote, callback) abort
|
|
return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
|
|
endfunction
|
|
|
|
function! fugitive#RemoteUrl(...) abort
|
|
let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
|
|
if len(cb)
|
|
let cb = [function('s:RemoteUrlCallback'), cb]
|
|
endif
|
|
let remote = s:Remote(dir_or_config, remote, flags, cb)
|
|
return get(remote, 'url', remote)
|
|
endfunction
|
|
|
|
" Section: Quickfix
|
|
|
|
function! s:QuickfixGet(nr, ...) abort
|
|
if a:nr < 0
|
|
return call('getqflist', a:000)
|
|
else
|
|
return call('getloclist', [a:nr] + a:000)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:QuickfixSet(nr, ...) abort
|
|
if a:nr < 0
|
|
return call('setqflist', a:000)
|
|
else
|
|
return call('setloclist', [a:nr] + a:000)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:QuickfixCreate(nr, opts) abort
|
|
if has('patch-7.4.2200')
|
|
call s:QuickfixSet(a:nr, [], ' ', a:opts)
|
|
else
|
|
call s:QuickfixSet(a:nr, [], ' ')
|
|
endif
|
|
endfunction
|
|
|
|
function! s:QuickfixOpen(nr, mods) abort
|
|
let mods = substitute(s:Mods(a:mods), '\<tab\>', '', '')
|
|
return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
|
|
endfunction
|
|
|
|
function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
|
|
call s:BlurStatus()
|
|
let opts = {'title': a:title, 'context': {'items': []}}
|
|
call s:QuickfixCreate(a:nr, opts)
|
|
let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
|
|
silent exe s:DoAutocmd('QuickFixCmdPre ' . event)
|
|
let winnr = winnr()
|
|
exe s:QuickfixOpen(a:nr, a:mods)
|
|
if winnr != winnr()
|
|
wincmd p
|
|
endif
|
|
|
|
let buffer = []
|
|
let lines = s:SystemList(a:cmd)[0]
|
|
for line in lines
|
|
call extend(buffer, call(a:callback, a:000 + [line]))
|
|
if len(buffer) >= 20
|
|
let contexts = map(copy(buffer), 'get(v:val, "context", {})')
|
|
lockvar contexts
|
|
call extend(opts.context.items, contexts)
|
|
unlet contexts
|
|
call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
|
|
if a:mods !~# '\<silent\>'
|
|
redraw
|
|
endif
|
|
endif
|
|
endfor
|
|
call extend(buffer, call(a:callback, a:000 + [0]))
|
|
call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
|
|
lockvar opts.context.items
|
|
call s:QuickfixSet(a:nr, buffer, 'a')
|
|
|
|
silent exe s:DoAutocmd('QuickFixCmdPost ' . event)
|
|
if a:first && len(s:QuickfixGet(a:nr))
|
|
return (a:nr < 0 ? 'cfirst' : 'lfirst')
|
|
else
|
|
return 'exe'
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#Cwindow() abort
|
|
if &buftype == 'quickfix'
|
|
cwindow
|
|
else
|
|
botright cwindow
|
|
if &buftype == 'quickfix'
|
|
wincmd p
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
" Section: Repository Object
|
|
|
|
let s:repo_prototype = {}
|
|
let s:repos = {}
|
|
|
|
function! fugitive#repo(...) abort
|
|
let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
|
|
if dir !=# ''
|
|
if has_key(s:repos, dir)
|
|
let repo = get(s:repos, dir)
|
|
else
|
|
let repo = {'git_dir': dir}
|
|
let s:repos[dir] = repo
|
|
endif
|
|
return extend(repo, s:repo_prototype, 'keep')
|
|
endif
|
|
call s:throw('not a Git repository')
|
|
endfunction
|
|
|
|
function! s:repo_dir(...) dict abort
|
|
return join([self.git_dir]+a:000,'/')
|
|
endfunction
|
|
|
|
function! s:repo_tree(...) dict abort
|
|
let dir = s:Tree(self.git_dir)
|
|
if dir ==# ''
|
|
call s:throw('no work tree')
|
|
else
|
|
return join([dir]+a:000,'/')
|
|
endif
|
|
endfunction
|
|
|
|
function! s:repo_bare() dict abort
|
|
if self.dir() =~# '/\.git$'
|
|
return 0
|
|
else
|
|
return s:Tree(self.git_dir) ==# ''
|
|
endif
|
|
endfunction
|
|
|
|
function! s:repo_find(object) dict abort
|
|
return fugitive#Find(a:object, self.git_dir)
|
|
endfunction
|
|
|
|
function! s:repo_translate(rev) dict abort
|
|
return s:Slash(fugitive#Find(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
|
|
endfunction
|
|
|
|
function! s:repo_head(...) dict abort
|
|
return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
|
|
endfunction
|
|
|
|
call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
|
|
|
|
function! s:repo_git_command(...) dict abort
|
|
throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
|
|
endfunction
|
|
|
|
function! s:repo_git_chomp(...) dict abort
|
|
return s:sub(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '')
|
|
endfunction
|
|
|
|
function! s:repo_git_chomp_in_tree(...) dict abort
|
|
return call(self.git_chomp, a:000, self)
|
|
endfunction
|
|
|
|
function! s:repo_rev_parse(rev) dict abort
|
|
return fugitive#RevParse(a:rev, self.git_dir)
|
|
endfunction
|
|
|
|
call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
|
|
|
|
function! s:repo_superglob(base) dict abort
|
|
return map(fugitive#CompleteObject(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
|
|
endfunction
|
|
|
|
call s:add_methods('repo',['superglob'])
|
|
|
|
function! s:repo_config(name) dict abort
|
|
return FugitiveConfigGet(a:name, self.git_dir)
|
|
endfunction
|
|
|
|
function! s:repo_user() dict abort
|
|
let username = self.config('user.name')
|
|
let useremail = self.config('user.email')
|
|
return username.' <'.useremail.'>'
|
|
endfunction
|
|
|
|
call s:add_methods('repo',['config', 'user'])
|
|
|
|
" Section: File API
|
|
|
|
function! s:DirCommitFile(path) abort
|
|
let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40,\}\|[0-3]\)\(/.*\)\=$')
|
|
if empty(vals)
|
|
return ['', '', '']
|
|
endif
|
|
return [s:Dir(vals[1])] + vals[2:3]
|
|
endfunction
|
|
|
|
function! s:DirRev(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
|
|
endfunction
|
|
|
|
let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
|
|
function! s:MergeHead(dir) abort
|
|
let dir = fugitive#Find('.git/', a:dir)
|
|
for head in s:merge_heads
|
|
if filereadable(dir . head)
|
|
return head
|
|
endif
|
|
endfor
|
|
return ''
|
|
endfunction
|
|
|
|
function! s:Owner(path, ...) abort
|
|
let dir = a:0 ? s:Dir(a:1) : s:Dir()
|
|
if empty(dir)
|
|
return ''
|
|
endif
|
|
let actualdir = fugitive#Find('.git/', dir)
|
|
let [pdir, commit, file] = s:DirCommitFile(a:path)
|
|
if s:cpath(dir, pdir)
|
|
if commit =~# '^\x\{40,\}$'
|
|
return commit
|
|
elseif commit ==# '2'
|
|
return '@'
|
|
elseif commit ==# '0'
|
|
return ''
|
|
endif
|
|
let merge_head = s:MergeHead(dir)
|
|
if empty(merge_head)
|
|
return ''
|
|
endif
|
|
if commit ==# '3'
|
|
return merge_head
|
|
elseif commit ==# '1'
|
|
return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
|
|
endif
|
|
endif
|
|
let path = fnamemodify(a:path, ':p')
|
|
if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
|
|
return strpart(path, len(actualdir))
|
|
endif
|
|
let refs = fugitive#Find('.git/refs', dir)
|
|
if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
|
|
return strpart(path, len(refs) - 4)
|
|
endif
|
|
return ''
|
|
endfunction
|
|
|
|
function! fugitive#Real(url) abort
|
|
if empty(a:url)
|
|
return ''
|
|
endif
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
if len(dir)
|
|
let tree = s:Tree(dir)
|
|
return FugitiveVimPath((len(tree) ? tree : dir) . file)
|
|
endif
|
|
let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
|
|
if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
|
|
let url = {pre}Real(a:url)
|
|
else
|
|
let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
|
|
endif
|
|
return FugitiveVimPath(empty(url) ? a:url : url)
|
|
endfunction
|
|
|
|
function! fugitive#Path(url, ...) abort
|
|
if empty(a:url)
|
|
return ''
|
|
endif
|
|
let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
|
|
let tree = s:Tree(dir)
|
|
if !a:0
|
|
return fugitive#Real(a:url)
|
|
elseif a:1 =~# '\.$'
|
|
let path = s:Slash(fugitive#Real(a:url))
|
|
let cwd = getcwd()
|
|
let lead = ''
|
|
while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
|
|
if s:cpath(cwd . '/', path[0 : len(cwd)])
|
|
if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
|
|
break
|
|
endif
|
|
return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
|
|
endif
|
|
let cwd = fnamemodify(cwd, ':h')
|
|
let lead .= '../'
|
|
endwhile
|
|
return a:1[0:-2] . path
|
|
endif
|
|
let url = a:url
|
|
let temp_state = s:TempState(url)
|
|
if has_key(temp_state, 'origin_bufnr')
|
|
let url = bufname(temp_state.origin_bufnr)
|
|
endif
|
|
let url = s:Slash(fnamemodify(url, ':p'))
|
|
if url =~# '/$' && s:Slash(a:url) !~# '/$'
|
|
let url = url[0:-2]
|
|
endif
|
|
let [argdir, commit, file] = s:DirCommitFile(a:url)
|
|
if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
|
|
let file = ''
|
|
elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
|
|
let file = '/.git'.url[strlen(dir) : -1]
|
|
elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
|
|
let file = url[len(tree) : -1]
|
|
elseif s:cpath(url) ==# s:cpath(tree)
|
|
let file = '/'
|
|
endif
|
|
if empty(file) && a:1 =~# '^$\|^[.:]/$'
|
|
return FugitiveGitPath(fugitive#Real(a:url))
|
|
endif
|
|
return substitute(file, '^/', a:1, '')
|
|
endfunction
|
|
|
|
function! s:Relative(...) abort
|
|
return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
|
|
endfunction
|
|
|
|
function! fugitive#Find(object, ...) abort
|
|
if type(a:object) == type(0)
|
|
let name = bufname(a:object)
|
|
return FugitiveVimPath(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
|
|
elseif a:object =~# '^[~$]'
|
|
let prefix = matchstr(a:object, '^[~$]\i*')
|
|
let owner = expand(prefix)
|
|
return FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
|
|
endif
|
|
let rev = s:Slash(a:object)
|
|
if rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
|
|
return FugitiveVimPath(a:object)
|
|
elseif rev =~# '^\.\.\=\%(/\|$\)'
|
|
return FugitiveVimPath(simplify(getcwd() . '/' . a:object))
|
|
endif
|
|
let dir = call('s:GitDir', a:000)
|
|
if empty(dir)
|
|
let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
|
|
let dir = FugitiveExtractGitDir(file)
|
|
if empty(dir)
|
|
return ''
|
|
endif
|
|
endif
|
|
let tree = s:Tree(dir)
|
|
let urlprefix = s:DirUrlPrefix(dir)
|
|
let base = len(tree) ? tree : urlprefix . '0'
|
|
if rev ==# '.git'
|
|
let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
|
|
elseif rev =~# '^\.git/'
|
|
let f = strpart(rev, 5)
|
|
let fdir = dir . '/'
|
|
let cdir = fugitive#CommonDir(dir) . '/'
|
|
if f =~# '^\.\./\.\.\%(/\|$\)'
|
|
let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
|
|
elseif f =~# '^\.\.\%(/\|$\)'
|
|
let f = base . f[2:-1]
|
|
elseif cdir !=# fdir && (
|
|
\ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
|
|
\ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
|
|
\ getftime(FugitiveVimPath(fdir . f)) < 0 && getftime(FugitiveVimPath(cdir . f)) >= 0)
|
|
let f = simplify(cdir . f)
|
|
else
|
|
let f = simplify(fdir . f)
|
|
endif
|
|
elseif rev ==# ':/'
|
|
let f = tree
|
|
elseif rev =~# '^\.\%(/\|$\)'
|
|
let f = base . rev[1:-1]
|
|
elseif rev =~# '^::\%(/\|\a\+\:\)'
|
|
let f = rev[2:-1]
|
|
elseif rev =~# '^::\.\.\=\%(/\|$\)'
|
|
let f = simplify(getcwd() . '/' . rev[2:-1])
|
|
elseif rev =~# '^::'
|
|
let f = base . '/' . rev[2:-1]
|
|
elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
|
|
let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
|
|
if s:cpath(base . '/', (f . '/')[0 : len(base)])
|
|
let f = urlprefix . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
|
|
else
|
|
let altdir = FugitiveExtractGitDir(f)
|
|
if len(altdir) && !s:cpath(dir, altdir)
|
|
return fugitive#Find(a:object, altdir)
|
|
endif
|
|
endif
|
|
elseif rev =~# '^:[0-3]:'
|
|
let f = urlprefix . rev[1] . '/' . rev[3:-1]
|
|
elseif rev ==# ':'
|
|
let fdir = dir . '/'
|
|
let f = fdir . 'index'
|
|
if len($GIT_INDEX_FILE)
|
|
let index_dir = substitute($GIT_INDEX_FILE, '[^/]\+$', '', '')
|
|
if s:cpath(index_dir, fdir)
|
|
let f = FugitiveVimPath($GIT_INDEX_FILE)
|
|
elseif s:cpath(resolve(FugitiveVimPath(index_dir)), fdir)
|
|
let f = resolve(FugitiveVimPath($GIT_INDEX_FILE))
|
|
endif
|
|
endif
|
|
elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
|
|
let f = matchstr(rev, ')\zs.*')
|
|
if f=~# '^\.\.\=\%(/\|$\)'
|
|
let f = simplify(getcwd() . '/' . f)
|
|
elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
|
|
let f = base . '/' . f
|
|
endif
|
|
elseif rev =~# '^:/\@!'
|
|
let f = urlprefix . '0/' . rev[1:-1]
|
|
else
|
|
if !exists('f')
|
|
let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
|
|
let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
|
|
if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
|
|
let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
|
|
if s:cpath(base . '/', (file . '/')[0 : len(base)])
|
|
let file = '/' . strpart(file, len(base) + 1)
|
|
else
|
|
let altdir = FugitiveExtractGitDir(file)
|
|
if len(altdir) && !s:cpath(dir, altdir)
|
|
return fugitive#Find(a:object, altdir)
|
|
endif
|
|
return file
|
|
endif
|
|
endif
|
|
let commits = split(commit, '\.\.\.-\@!', 1)
|
|
if len(commits) == 2
|
|
call map(commits, 'empty(v:val) ? "@" : v:val')
|
|
let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
|
|
endif
|
|
if commit !~# '^[0-9a-f]\{40,\}$\|^$'
|
|
let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
|
|
if empty(commit) && len(file)
|
|
let commit = repeat('0', 40)
|
|
endif
|
|
endif
|
|
if len(commit)
|
|
let f = urlprefix . commit . file
|
|
else
|
|
let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
|
|
endif
|
|
endif
|
|
endif
|
|
return FugitiveVimPath(f)
|
|
endfunction
|
|
|
|
function! s:Generate(object, ...) abort
|
|
let dir = a:0 ? a:1 : s:Dir()
|
|
let f = fugitive#Find(a:object, dir)
|
|
if !empty(f)
|
|
return f
|
|
elseif a:object ==# ':/'
|
|
return len(dir) ? FugitiveVimPath(s:DirUrlPrefix(dir) . '0') : '.'
|
|
endif
|
|
let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*')
|
|
return fnamemodify(FugitiveVimPath(len(file) ? file : a:object), ':p')
|
|
endfunction
|
|
|
|
function! s:DotRelative(path, ...) abort
|
|
let cwd = a:0 ? a:1 : getcwd()
|
|
let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
|
|
if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
|
|
return '.' . strpart(path, len(cwd))
|
|
endif
|
|
return a:path
|
|
endfunction
|
|
|
|
function! fugitive#Object(...) abort
|
|
let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
|
|
let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
|
|
if s:cpath(dir) !=# s:cpath(fdir)
|
|
let rev = ''
|
|
endif
|
|
let tree = s:Tree(dir)
|
|
let full = a:0 ? a:1 : s:BufName('%')
|
|
let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
|
|
if empty(rev) && empty(tree)
|
|
return FugitiveGitPath(full)
|
|
elseif empty(rev)
|
|
let rev = fugitive#Path(full, './', dir)
|
|
if rev =~# '^\./.git\%(/\|$\)'
|
|
return FugitiveGitPath(full)
|
|
endif
|
|
endif
|
|
if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
|
|
return rev
|
|
else
|
|
return FugitiveGitPath(tree . rev[1:-1])
|
|
endif
|
|
endfunction
|
|
|
|
let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
|
|
let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
|
|
let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
|
|
|
|
function! s:BufName(var) abort
|
|
if a:var ==# '%'
|
|
return bufname(get(s:TempState(), 'origin_bufnr', ''))
|
|
elseif a:var =~# '^#\d*$'
|
|
let nr = get(s:TempState(bufname(+a:var[1:-1])), 'origin_bufnr', '')
|
|
return bufname(nr ? nr : +a:var[1:-1])
|
|
else
|
|
return expand(a:var)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:ExpandVarLegacy(str) abort
|
|
if get(g:, 'fugitive_legacy_quoting', 0)
|
|
return substitute(a:str, '\\\ze[%#!]', '', 'g')
|
|
else
|
|
return a:str
|
|
endif
|
|
endfunction
|
|
|
|
function! s:ExpandVar(other, var, flags, esc, ...) abort
|
|
let cwd = a:0 ? a:1 : getcwd()
|
|
if a:other =~# '^\'
|
|
return a:other[1:-1]
|
|
elseif a:other =~# '^'''
|
|
return s:ExpandVarLegacy(substitute(a:other[1:-2], "''", "'", "g"))
|
|
elseif a:other =~# '^"'
|
|
return s:ExpandVarLegacy(substitute(a:other[1:-2], '""', '"', "g"))
|
|
elseif a:other =~# '^!'
|
|
let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
|
|
let owner = s:Owner(buffer)
|
|
return len(owner) ? owner : '@'
|
|
elseif a:other =~# '^\~[~.]$'
|
|
return s:Slash(getcwd())
|
|
elseif len(a:other)
|
|
return expand(a:other)
|
|
elseif a:var ==# '<cfile>'
|
|
let bufnames = [expand('<cfile>')]
|
|
if v:version >= 704 && get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
|
|
try
|
|
let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
|
|
if bufnames[0] ==# "\<C-R>\<C-F>"
|
|
let bufnames = [expand('<cfile>')]
|
|
endif
|
|
catch
|
|
endtry
|
|
endif
|
|
elseif a:var =~# '^<'
|
|
let bufnames = [s:BufName(a:var)]
|
|
elseif a:var ==# '##'
|
|
let bufnames = map(argv(), 'fugitive#Real(v:val)')
|
|
else
|
|
let bufnames = [fugitive#Real(s:BufName(a:var))]
|
|
endif
|
|
let files = []
|
|
for bufname in bufnames
|
|
let flags = a:flags
|
|
let file = s:DotRelative(bufname, cwd)
|
|
while len(flags)
|
|
let flag = matchstr(flags, s:flag)
|
|
let flags = strpart(flags, len(flag))
|
|
if flag ==# ':.'
|
|
let file = s:DotRelative(fugitive#Real(file), cwd)
|
|
else
|
|
let file = fnamemodify(file, flag)
|
|
endif
|
|
endwhile
|
|
let file = s:Slash(file)
|
|
if file =~# '^fugitive://'
|
|
let [dir, commit, file_candidate] = s:DirCommitFile(file)
|
|
let tree = s:Tree(dir)
|
|
if len(tree) && len(file_candidate)
|
|
let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
|
|
\ s:DotRelative(tree . file_candidate)
|
|
elseif empty(file_candidate) && commit !~# '^.$'
|
|
let file = commit
|
|
endif
|
|
endif
|
|
call add(files, len(a:esc) ? shellescape(file) : file)
|
|
endfor
|
|
return join(files, "\1")
|
|
endfunction
|
|
|
|
function! s:Expand(rev, ...) abort
|
|
if a:rev =~# '^>\=:[0-3]$'
|
|
let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
|
|
elseif a:rev =~# '^>\%(:\=/\)\=$'
|
|
let file = '%'
|
|
elseif a:rev ==# '>:'
|
|
let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
|
|
elseif a:rev =~# '^>[> ]\@!'
|
|
let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
|
|
let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
|
|
if prefix !=# rev
|
|
let file = rev
|
|
else
|
|
let file = len(expand('%')) ? rev . ':%' : '%'
|
|
endif
|
|
else
|
|
let file = a:rev
|
|
endif
|
|
return substitute(file,
|
|
\ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~.]\)\|' . s:expand,
|
|
\ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
|
|
endfunction
|
|
|
|
function! fugitive#Expand(object) abort
|
|
return substitute(a:object,
|
|
\ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~.]\)\|' . s:expand,
|
|
\ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
|
|
endfunction
|
|
|
|
function! s:SplitExpandChain(string, ...) abort
|
|
let list = []
|
|
let string = a:string
|
|
let dquote = '"\%([^"]\|""\|\\"\)*"\|'
|
|
let cwd = a:0 ? a:1 : getcwd()
|
|
while string =~# '\S'
|
|
if string =~# '^\s*|'
|
|
return [list, substitute(string, '^\s*', '', '')]
|
|
endif
|
|
let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] |]\)\+')
|
|
let string = strpart(string, len(arg))
|
|
let arg = substitute(arg, '^\s\+', '', '')
|
|
if !exists('seen_separator')
|
|
let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
|
|
\ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
|
|
endif
|
|
let arg = substitute(arg,
|
|
\ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
|
|
\ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
|
|
call extend(list, split(arg, "\1", 1))
|
|
if arg ==# '--'
|
|
let seen_separator = 1
|
|
endif
|
|
endwhile
|
|
return [list, '']
|
|
endfunction
|
|
|
|
let s:trees = {}
|
|
let s:indexes = {}
|
|
function! s:TreeInfo(dir, commit) abort
|
|
if a:commit =~# '^:\=[0-3]$'
|
|
let index = get(s:indexes, a:dir, [])
|
|
let newftime = getftime(fugitive#Find('.git/index', a:dir))
|
|
if get(index, 0, -1) < newftime
|
|
let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
|
|
let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
|
|
if exec_error
|
|
return [{}, -1]
|
|
endif
|
|
for line in lines
|
|
let [info, filename] = split(line, "\t")
|
|
let [mode, sha, stage] = split(info, '\s\+')
|
|
let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
|
|
while filename =~# '/'
|
|
let filename = substitute(filename, '/[^/]*$', '', '')
|
|
let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
|
|
endwhile
|
|
endfor
|
|
endif
|
|
return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
|
|
elseif a:commit =~# '^\x\{40,\}$'
|
|
if !has_key(s:trees, a:dir)
|
|
let s:trees[a:dir] = {}
|
|
endif
|
|
if !has_key(s:trees[a:dir], a:commit)
|
|
let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
|
|
if empty(ftime)
|
|
let s:trees[a:dir][a:commit] = [{}, -1]
|
|
return s:trees[a:dir][a:commit]
|
|
endif
|
|
let s:trees[a:dir][a:commit] = [{}, +ftime]
|
|
let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
|
|
if exec_error
|
|
return s:trees[a:dir][a:commit]
|
|
endif
|
|
for line in lines
|
|
let [info, filename] = split(line, "\t")
|
|
let [mode, type, sha, size] = split(info, '\s\+')
|
|
let s:trees[a:dir][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
|
|
endfor
|
|
endif
|
|
return s:trees[a:dir][a:commit]
|
|
endif
|
|
return [{}, -1]
|
|
endfunction
|
|
|
|
function! s:PathInfo(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
if empty(dir) || !get(g:, 'fugitive_file_api', 1)
|
|
return [-1, '000000', '', '', -1]
|
|
endif
|
|
let path = substitute(file[1:-1], '/*$', '', '')
|
|
let [tree, ftime] = s:TreeInfo(dir, commit)
|
|
let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
|
|
if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
|
|
return [-1, '000000', '', '', -1]
|
|
else
|
|
return entry
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#simplify(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
if empty(dir)
|
|
return ''
|
|
endif
|
|
if file =~# '/\.\.\%(/\|$\)'
|
|
let tree = s:Tree(dir)
|
|
if len(tree)
|
|
let path = simplify(tree . file)
|
|
if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
|
|
return FugitiveVimPath(path)
|
|
endif
|
|
endif
|
|
endif
|
|
return FugitiveVimPath('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
|
|
endfunction
|
|
|
|
function! fugitive#resolve(url) abort
|
|
let url = fugitive#simplify(a:url)
|
|
if url =~? '^fugitive:'
|
|
return url
|
|
else
|
|
return resolve(url)
|
|
endif
|
|
endfunction
|
|
|
|
function! fugitive#getftime(url) abort
|
|
return s:PathInfo(a:url)[0]
|
|
endfunction
|
|
|
|
function! fugitive#getfsize(url) abort
|
|
let entry = s:PathInfo(a:url)
|
|
if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
|
|
let dir = s:DirCommitFile(a:url)[0]
|
|
let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
|
|
endif
|
|
return entry[4]
|
|
endfunction
|
|
|
|
function! fugitive#getftype(url) abort
|
|
return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
|
|
endfunction
|
|
|
|
function! fugitive#filereadable(url) abort
|
|
return s:PathInfo(a:url)[2] ==# 'blob'
|
|
endfunction
|
|
|
|
function! fugitive#filewritable(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
|
|
return 0
|
|
endif
|
|
return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
|
|
endfunction
|
|
|
|
function! fugitive#isdirectory(url) abort
|
|
return s:PathInfo(a:url)[2] ==# 'tree'
|
|
endfunction
|
|
|
|
function! fugitive#getfperm(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
let perm = getfperm(dir)
|
|
let fperm = s:PathInfo(a:url)[1]
|
|
if fperm ==# '040000'
|
|
let fperm = '000755'
|
|
endif
|
|
if fperm !~# '[15]'
|
|
let perm = tr(perm, 'x', '-')
|
|
endif
|
|
if fperm !~# '[45]$'
|
|
let perm = tr(perm, 'rw', '--')
|
|
endif
|
|
if commit !~# '^\d$'
|
|
let perm = tr(perm, 'w', '-')
|
|
endif
|
|
return perm ==# '---------' ? '' : perm
|
|
endfunction
|
|
|
|
function! s:UpdateIndex(dir, info) abort
|
|
let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
|
|
let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
|
|
return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
|
|
endfunction
|
|
|
|
function! fugitive#setfperm(url, perm) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
let entry = s:PathInfo(a:url)
|
|
let perm = fugitive#getfperm(a:url)
|
|
if commit !~# '^\d$' || entry[2] !=# 'blob' ||
|
|
\ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
|
|
return -2
|
|
endif
|
|
let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
|
|
return len(error) ? -1 : 0
|
|
endfunction
|
|
|
|
if !exists('s:blobdirs')
|
|
let s:blobdirs = {}
|
|
endif
|
|
function! s:BlobTemp(url) abort
|
|
let [dir, commit, file] = s:DirCommitFile(a:url)
|
|
if empty(file)
|
|
return ''
|
|
endif
|
|
if !has_key(s:blobdirs, dir)
|
|
let s:blobdirs[dir] = tempname()
|
|
endif
|
|
let tempfile = s:blobdirs[dir] . '/' . commit . file
|
|
let tempparent = fnamemodify(tempfile, ':h')
|
|
if !isdirectory(tempparent)
|
|
call mkdir(tempparent, 'p')
|
|
elseif isdirectory(tempfile)
|
|
if commit =~# '^\d$' && has('patch-7.4.1107')
|
|
call delete(tempfile, 'rf')
|
|
else
|
|
return ''
|
|
endif
|
|
endif
|
|
if commit =~# '^\d$' || !filereadable(tempfile)
|
|
let rev = s:DirRev(a:url)[1]
|
|
let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
|
|
let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
|
|
if exec_error
|
|
call delete(tempfile)
|
|
return ''
|
|
endif
|
|
endif
|
|
return s:Resolve(tem |