1
0
Fork 0
mirror of synced 2024-07-15 22:26:44 -04:00
ultimate-vim/sources_non_forked/vim-fugitive/autoload/fugitive.vim
2021-10-28 21:48:21 +02:00

8026 lines
268 KiB
VimL

" 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(tempfile)
endfunction
function! fugitive#readfile(url, ...) abort
let entry = s:PathInfo(a:url)
if entry[2] !=# 'blob'
return []
endif
let temp = s:BlobTemp(a:url)
if empty(temp)
return []
endif
return call('readfile', [temp] + a:000)
endfunction
function! fugitive#writefile(lines, url, ...) abort
let url = type(a:url) ==# type('') ? a:url : ''
let [dir, commit, file] = s:DirCommitFile(url)
let entry = s:PathInfo(url)
if commit =~# '^\d$' && entry[2] !=# 'tree'
let temp = tempname()
if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
call writefile(fugitive#readfile(url, 'b'), temp, 'b')
endif
call call('writefile', [a:lines, temp] + a:000)
let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
let mode = entry[1] !=# '000000' ? entry[1] : '100644'
if hash =~# '^\x\{40,\}$'
let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
if empty(error)
return 0
endif
endif
endif
return call('writefile', [a:lines, a:url] + a:000)
endfunction
let s:globsubs = {
\ '/**/': '/\%([^./][^/]*/\)*',
\ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
\ '**/': '[^/]*\%(/[^./][^/]*\)*',
\ '**': '.*',
\ '/*': '/[^/.][^/]*',
\ '*': '[^/]*',
\ '?': '[^/]'}
function! fugitive#glob(url, ...) abort
let [dirglob, commit, glob] = s:DirCommitFile(a:url)
let append = matchstr(glob, '/*$')
let glob = substitute(glob, '/*$', '', '')
let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
let results = []
for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
continue
endif
let files = items(s:TreeInfo(dir, commit)[0])
if len(append)
call filter(files, 'v:val[1][2] ==# "tree"')
endif
call map(files, 'v:val[0]')
call filter(files, 'v:val =~# pattern')
let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
call sort(files)
call map(files, 'FugitiveVimPath(prepend . v:val . append)')
call extend(results, files)
endfor
if a:0 > 1 && a:2
return results
else
return join(results, "\n")
endif
endfunction
function! fugitive#delete(url, ...) abort
let [dir, commit, file] = s:DirCommitFile(a:url)
if a:0 && len(a:1) || commit !~# '^\d$'
return -1
endif
let entry = s:PathInfo(a:url)
if entry[2] !=# 'blob'
return -1
endif
let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
return len(error) ? -1 : 0
endfunction
" Section: Buffer Object
let s:buffer_prototype = {}
function! fugitive#buffer(...) abort
let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
call extend(buffer, s:buffer_prototype, 'keep')
return buffer
endfunction
function! s:buffer_repo() dict abort
return fugitive#repo(self['#'])
endfunction
function! s:buffer_type(...) dict abort
return 'see per type events at :help fugitive-autocommands'
endfunction
call s:add_methods('buffer', ['repo', 'type'])
" Section: Completion
function! s:FilterEscape(items, ...) abort
let items = copy(a:items)
call map(items, 's:fnameescape(v:val)')
if a:0 && type(a:1) == type('')
let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
call filter(items, 'strpart(v:val, 0, strlen(a:1)) ' . cmp . ' a:1')
endif
return items
endfunction
function! s:GlobComplete(lead, pattern, ...) abort
if a:lead ==# '/'
return []
elseif v:version >= 704
let results = glob(a:lead . a:pattern, a:0 ? a:1 : 0, 1)
else
let results = split(glob(a:lead . a:pattern), "\n")
endif
call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
call map(results, 'v:val[ strlen(a:lead) : -1 ]')
return results
endfunction
function! fugitive#CompletePath(base, ...) abort
let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
let base = strpart(a:base, len(stripped))
if len(stripped) || a:0 < 4
let root = s:Tree(dir)
else
let root = a:4
endif
if root !=# '/' && len(root)
let root .= '/'
endif
if empty(stripped)
let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
let base = strpart(a:base, len(stripped))
endif
if base =~# '^\.git/' && len(dir)
let pattern = s:gsub(base[5:-1], '/', '*&').'*'
let fdir = fugitive#Find('.git/', dir)
let matches = s:GlobComplete(fdir, pattern)
let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
call extend(matches, s:GlobComplete(cdir, pattern))
endif
call s:Uniq(matches)
call map(matches, "'.git/' . v:val")
elseif base =~# '^\~/'
let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
let matches = s:GlobComplete('', base . '*')
elseif len(root)
let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
else
let matches = []
endif
call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
return matches
endfunction
function! fugitive#PathComplete(...) abort
return call('fugitive#CompletePath', a:000)
endfunction
function! s:CompleteHeads(dir) abort
if empty(a:dir)
return []
endif
let dir = fugitive#Find('.git/', a:dir)
return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
\ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
endfunction
function! fugitive#CompleteObject(base, ...) abort
let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
let tree = s:Tree(dir)
let cwd = getcwd()
let subdir = ''
if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
let subdir = strpart(cwd, len(tree) + 1) . '/'
endif
let base = s:Expand(a:base)
if a:base =~# '^!\d*$' && base !~# '^!'
return [base]
elseif base =~# '^\.\=/\|^:(' || base !~# ':'
let results = []
if base =~# '^refs/'
let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
call map(results, 's:fnameescape(v:val)')
elseif base !~# '^\.\=/\|^:('
let heads = s:CompleteHeads(dir)
if filereadable(fugitive#Find('.git/refs/stash', dir))
let heads += ["stash"]
let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
endif
let results += s:FilterEscape(heads, fnameescape(base))
endif
let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
return results
elseif base =~# '^:'
let entries = s:LinesError(['ls-files','--stage'], dir)[0]
if base =~# ':\./'
call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
endif
call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
if base !~# '^:[0-3]\%(:\|$\)'
call filter(entries,'v:val[1] == "0"')
call map(entries,'v:val[2:-1]')
endif
else
let parent = matchstr(base, '.*[:/]')
let entries = s:LinesError(['ls-tree', substitute(parent, ':\zs\./', '\=subdir', '')], dir)[0]
call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
call map(entries,'parent.s:sub(v:val,".*\t","")')
endif
return s:FilterEscape(entries, fnameescape(base))
endfunction
function! s:CompleteSub(subcommand, A, L, P, ...) abort
let pre = strpart(a:L, 0, a:P)
if pre =~# ' -- '
return fugitive#CompletePath(a:A)
elseif a:A =~# '^-' || a:A is# 0
return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
elseif !a:0
return fugitive#CompleteObject(a:A, s:Dir())
elseif type(a:1) == type(function('tr'))
return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
else
return s:FilterEscape(a:1, a:A)
endif
endfunction
function! s:CompleteRevision(A, L, P, ...) abort
return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
endfunction
function! s:CompleteRemote(A, L, P, ...) abort
let dir = a:0 ? a:1 : s:Dir()
let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
if !empty(remote)
let matches = s:LinesError([dir, 'ls-remote', remote])[0]
call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
else
let matches = s:LinesError([dir, 'remote'])[0]
endif
return s:FilterEscape(matches, a:A)
endfunction
" Section: Buffer auto-commands
augroup fugitive_dummy_events
autocmd!
autocmd User Fugitive* "
augroup END
function! s:ReplaceCmd(cmd) abort
let temp = tempname()
let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
if exec_error
throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
endif
setlocal noswapfile