1 " Location: autoload/fugitive.vim
2 " Maintainer: Tim Pope <http://tpo.pe/>
4 if exists('g:autoloaded_fugitive')
7 let g:autoloaded_fugitive = 1
9 if !exists('g:fugitive_git_executable')
10 let g:fugitive_git_executable = 'git'
11 elseif g:fugitive_git_executable =~# '^\w\+='
12 let g:fugitive_git_executable = 'env ' . g:fugitive_git_executable
17 function! s:function(name) abort
18 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
21 function! s:sub(str,pat,rep) abort
22 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
25 function! s:gsub(str,pat,rep) abort
26 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
29 function! s:Uniq(list) abort
33 let str = string(a:list[i])
35 call remove(a:list, i)
44 function! s:winshell() abort
45 return has('win32') && &shellcmdflag =~# '^/\|^-Command$'
48 function! s:shellesc(arg) abort
49 if type(a:arg) == type([])
50 return join(map(copy(a:arg), 's:shellesc(v:val)'))
51 elseif a:arg =~ '^[A-Za-z0-9_/:.-]\+$'
54 return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
56 return shellescape(a:arg)
60 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
61 function! s:fnameescape(file) abort
62 if type(a:file) == type([])
63 return join(map(copy(a:file), 's:fnameescape(v:val)'))
64 elseif exists('*fnameescape')
65 return fnameescape(a:file)
67 return escape(a:file, s:fnameescape)
71 function! s:throw(string) abort
72 throw 'fugitive: '.a:string
75 function! s:DirCheck(...) abort
76 if !empty(a:0 ? s:Dir(a:1) : s:Dir())
78 elseif empty(bufname(''))
79 return 'return ' . string('echoerr "fugitive: blank buffer unsupported (edit a file from a repository)"')
81 return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
85 function! s:Mods(mods, ...) abort
86 let mods = substitute(a:mods, '\C<mods>', '', '')
87 let mods = mods =~# '\S$' ? mods . ' ' : mods
88 if a:0 && mods !~# '\<\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
89 let mods = a:1 . ' ' . mods
91 return substitute(mods, '\s\+', ' ', 'g')
94 function! s:Slash(path) abort
95 if exists('+shellslash')
96 return tr(a:path, '\', '/')
102 function! s:Resolve(path) abort
103 let path = resolve(a:path)
105 let path = FugitiveVimPath(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
110 function! s:cpath(path, ...) abort
111 if exists('+fileignorecase') && &fileignorecase
112 let path = FugitiveVimPath(tolower(a:path))
114 let path = FugitiveVimPath(a:path)
116 return a:0 ? path ==# s:cpath(a:1) : path
119 function! s:Cd(...) abort
120 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'
128 exe cd s:fnameescape(a:1)
129 return cd . ' ' . s:fnameescape(cwd)
132 let s:executables = {}
134 function! s:executable(binary) abort
135 if !has_key(s:executables, a:binary)
136 let s:executables[a:binary] = executable(a:binary)
138 return s:executables[a:binary]
141 function! s:DoAutocmd(cmd) abort
142 if v:version >= 704 || (v:version == 703 && has('patch442'))
143 return 'doautocmd <nomodeline>' . a:cmd
144 elseif &modelines > 0
145 return 'try|set modelines=0|doautocmd ' . a:cmd . '|finally|set modelines=' . &modelines . '|endtry'
147 return 'doautocmd ' . a:cmd
151 let s:nowait = v:version >= 704 ? '<nowait>' : ''
153 function! s:Map(mode, lhs, rhs, ...) abort
154 for mode in split(a:mode, '\zs')
155 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
158 let keys = get(g:, mode.'remap', {})
159 if type(keys) == type([])
163 if has_key(keys, head)
164 let head = keys[head]
170 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
171 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
173 if flags !~# '<unique>' || empty(mapcheck(head.tail, mode))
174 exe mode.'map <buffer>' s:nowait flags head.tail a:rhs
176 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
177 \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
185 function! s:QuickfixGet(nr, ...) abort
187 return call('getqflist', a:000)
189 return call('getloclist', [a:nr] + a:000)
193 function! s:QuickfixSet(nr, ...) abort
195 return call('setqflist', a:000)
197 return call('setloclist', [a:nr] + a:000)
201 function! s:QuickfixCreate(nr, opts) abort
202 if has('patch-7.4.2200')
203 call s:QuickfixSet(a:nr, [], ' ', a:opts)
205 call s:QuickfixSet(a:nr, [], ' ')
209 function! s:QuickfixStream(nr, title, cmd, first, callback, ...) abort
210 call s:QuickfixCreate(a:nr, {'title': a:title})
212 exe a:nr < 0 ? 'copen' : 'lopen'
218 let lines = split(s:SystemError(s:shellesc(a:cmd))[0], "\n")
220 call extend(buffer, call(a:callback, a:000 + [line]))
222 call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
226 call s:QuickfixSet(a:nr, extend(buffer, call(a:callback, a:000 + [0])), 'a')
228 if a:first && len(s:QuickfixGet(a:nr))
230 return a:nr < 0 ? 'cfirst' : 'lfirst'
238 function! s:UserCommandList(...) abort
239 let git = split(get(g:, 'fugitive_git_command', g:fugitive_git_executable), '\s\+')
240 let dir = a:0 ? s:Dir(a:1) : ''
242 let tree = s:Tree(dir)
244 call add(git, '--git-dir=' . FugitiveGitPath(dir))
245 elseif len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
246 if fugitive#GitVersion(1, 8, 5)
247 call extend(git, ['-C', FugitiveGitPath(tree)])
249 throw 'fugitive: Git 1.8.5 or higher required to change directory'
256 function! s:UserCommand(...) abort
257 return s:shellesc(call('s:UserCommandList', a:0 ? [a:1] : []) + (a:0 ? a:2 : []))
260 let s:git_versions = {}
261 function! fugitive#GitVersion(...) abort
262 if !has_key(s:git_versions, g:fugitive_git_executable)
263 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), '\d[^[:space:]]\+')
266 return s:git_versions[g:fugitive_git_executable]
268 let components = split(s:git_versions[g:fugitive_git_executable], '\D\+')
272 for i in range(len(a:000))
273 if a:000[i] > +get(components, i)
275 elseif a:000[i] < +get(components, i)
279 return a:000[i] ==# get(components, i)
282 let s:commondirs = {}
283 function! fugitive#CommonDir(dir) abort
287 if !has_key(s:commondirs, a:dir)
288 if getfsize(a:dir . '/HEAD') < 10
289 let s:commondirs[a:dir] = ''
290 elseif filereadable(a:dir . '/commondir')
291 let cdir = get(readfile(a:dir . '/commondir', 1), 0, '')
292 if cdir =~# '^/\|^\a:/'
293 let s:commondirs[a:dir] = s:Slash(FugitiveVimPath(cdir))
295 let s:commondirs[a:dir] = simplify(a:dir . '/' . cdir)
298 let s:commondirs[a:dir] = a:dir
301 return s:commondirs[a:dir]
304 function! s:Dir(...) abort
305 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
308 function! s:Tree(...) abort
309 return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
312 function! s:HasOpt(args, ...) abort
313 let args = a:args[0 : index(a:args, '--')]
314 let opts = copy(a:000)
315 if type(opts[0]) == type([])
316 if empty(args) || index(opts[0], args[0]) == -1
322 if index(args, opt) != -1
328 function! s:PreparePathArgs(cmd, dir, literal) abort
329 let literal_supported = fugitive#GitVersion(1, 9)
330 if a:literal && literal_supported
331 call insert(a:cmd, '--literal-pathspecs')
333 let split = index(a:cmd, '--')
334 for i in range(split < 0 ? len(a:cmd) : split)
335 if type(a:cmd[i]) == type(0)
336 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
342 for i in range(split + 1, len(a:cmd) - 1)
343 if type(a:cmd[i]) == type(0)
344 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
346 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
347 elseif !literal_supported
348 let a:cmd[i] = substitute(a:cmd[i], '^:\%(/\|([^)]*)\)\=:\=', './', '')
354 let s:prepare_env = {
355 \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
356 \ 'core.editor': 'GIT_EDITOR',
357 \ 'core.askpass': 'GIT_ASKPASS',
359 function! fugitive#PrepareDirEnvArgv(...) abort
360 if a:0 && type(a:1) ==# type([])
361 let cmd = a:000[1:-1] + a:1
363 let cmd = copy(a:000)
368 if cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
369 let dir = remove(cmd, 0)
370 elseif cmd[i] =~# '^--git-dir='
371 let dir = remove(cmd, 0)[10:-1]
372 elseif type(cmd[i]) ==# type(0)
373 let dir = s:Dir(remove(cmd, i))
374 elseif cmd[i] ==# '-c' && len(cmd) > i + 1
375 let key = matchstr(cmd[i+1], '^[^=]*')
376 if has_key(s:prepare_env, tolower(key)) || key !~# '\.'
377 let var = get(s:prepare_env, tolower(key), key)
378 let val = matchstr(cmd[i+1], '=\zs.*')
381 if fugitive#GitVersion(1, 8) && cmd[i+1] =~# '\.'
384 call remove(cmd, i, i + 1)
386 elseif cmd[i] =~# '^--.*pathspecs$'
387 let explicit_pathspec_option = 1
388 if fugitive#GitVersion(1, 9)
393 elseif cmd[i] !~# '^-'
402 call s:PreparePathArgs(cmd, dir, !exists('explicit_pathspec_option'))
403 return [dir, env, cmd]
406 function! s:BuildShell(dir, env, args) abort
407 let cmd = copy(a:args)
408 let tree = s:Tree(a:dir)
410 for [var, val] in items(a:env)
412 let pre .= 'set ' . var . '=' . s:shellesc(val) . '& '
414 let pre = (len(pre) ? pre : 'env ') . var . '=' . s:shellesc(val) . ' '
417 if empty(tree) || index(cmd, '--') == len(cmd) - 1
418 call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
419 elseif fugitive#GitVersion(1, 8, 5)
420 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
422 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? '& ' : '; ') . pre
424 return pre . g:fugitive_git_executable . ' ' . join(map(cmd, 's:shellesc(v:val)'))
427 function! fugitive#Prepare(...) abort
428 let [dir, env, argv] = call('fugitive#PrepareDirEnvArgv', a:000)
429 return s:BuildShell(dir, env, argv)
432 function! s:SystemError(cmd, ...) abort
434 if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
435 let shellredir = &shellredir
439 set shellredir=>%s\ 2>&1
442 let out = call('system', [type(a:cmd) ==# type([]) ? fugitive#Prepare(a:cmd) : a:cmd] + a:000)
443 return [out, v:shell_error]
444 catch /^Vim\%((\a\+)\)\=:E484:/
445 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
446 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
447 call map(opts, 'v:val."=".eval("&".v:val)')
448 call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
450 if exists('shellredir')
451 let &shellredir = shellredir
456 function! s:ChompError(...) abort
457 let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
458 return [s:sub(out, '\n$', ''), exec_error]
461 function! s:ChompDefault(default, ...) abort
462 let [out, exec_error] = call('s:ChompError', a:000)
463 return exec_error ? a:default : out
466 function! s:LinesError(...) abort
467 let [out, exec_error] = call('s:ChompError', a:000)
468 return [len(out) && !exec_error ? split(out, "\n", 1) : [], exec_error]
471 function! s:NullError(...) abort
472 let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
473 return [exec_error ? [] : split(out, "\1"), exec_error ? substitute(out, "\n$", "", "") : '', exec_error]
476 function! s:TreeChomp(...) abort
477 let cmd = call('fugitive#Prepare', a:000)
478 let [out, exec_error] = s:SystemError(cmd)
479 let out = s:sub(out, '\n$', '')
483 throw 'fugitive: error running `' . cmd . '`: ' . out
486 function! s:EchoExec(...) abort
487 echo call('s:ChompError', a:000)[0]
488 call fugitive#ReloadStatus(-1, 1)
492 function! fugitive#Head(...) abort
493 let dir = a:0 > 1 ? a:2 : s:Dir()
494 if empty(dir) || !filereadable(fugitive#Find('.git/HEAD', dir))
497 let head = readfile(fugitive#Find('.git/HEAD', dir))[0]
499 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
500 elseif head =~# '^\x\{40,\}$'
501 let len = a:0 ? a:1 : 0
502 return len < 0 ? head : len ? head[0:len-1] : ''
508 function! fugitive#RevParse(rev, ...) abort
509 let [hash, exec_error] = s:ChompError([a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
510 if !exec_error && hash =~# '^\x\{40,\}$'
513 throw 'fugitive: rev-parse '.a:rev.': '.hash
516 function! s:ConfigTimestamps(dir, dict) abort
517 let files = ['/etc/gitconfig', '~/.gitconfig',
518 \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
520 call add(files, fugitive#Find('.git/config', a:dir))
522 call extend(files, get(a:dict, 'include.path', []))
523 return join(map(files, 'getftime(expand(v:val))'), ',')
527 function! fugitive#Config(...) abort
530 if a:0 >= 2 && type(a:2) == type({})
531 let name = substitute(a:1, '^[^.]\+\|[^.]\+$', '\L&', 'g')
532 return len(a:1) ? get(get(a:2, name, []), 0, '') : a:2
536 elseif a:0 == 1 && type(a:1) == type({})
538 elseif a:0 == 1 && a:1 =~# '^[[:alnum:]-]\+\.'
543 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
544 let key = len(dir) ? dir : '_'
545 if has_key(s:config, key) && s:config[key][0] ==# s:ConfigTimestamps(dir, s:config[key][1])
546 let dict = s:config[key][1]
549 let [lines, message, exec_error] = s:NullError([dir, 'config', '--list', '-z'])
554 let key = matchstr(line, "^[^\n]*")
555 if !has_key(dict, key)
558 call add(dict[key], strpart(line, len(key) + 1))
560 let s:config[dir] = [s:ConfigTimestamps(dir, dict), dict]
563 return len(name) ? get(get(dict, name, []), 0, '') : dict
566 function! s:Remote(dir) abort
567 let head = FugitiveHead(0, a:dir)
568 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
570 while remote ==# '.' && i > 0
571 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
572 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
575 return remote =~# '^\.\=$' ? 'origin' : remote
578 function! fugitive#RemoteUrl(...) abort
579 let dir = a:0 > 1 ? a:2 : s:Dir()
580 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
581 if !fugitive#GitVersion(2, 7)
582 return fugitive#Config('remote.' . remote . '.url')
584 return s:ChompDefault('', [dir, 'remote', 'get-url', remote, '--'])
587 " Section: Repository Object
589 function! s:add_methods(namespace, method_names) abort
590 for name in a:method_names
591 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
595 let s:repo_prototype = {}
598 function! fugitive#repo(...) abort
599 let dir = a:0 ? s:Dir(a:1) : (len(s:Dir()) ? s:Dir() : FugitiveExtractGitDir(expand('%:p')))
601 if has_key(s:repos, dir)
602 let repo = get(s:repos, dir)
604 let repo = {'git_dir': dir}
605 let s:repos[dir] = repo
607 return extend(repo, s:repo_prototype, 'keep')
609 call s:throw('not a Git repository')
612 function! s:repo_dir(...) dict abort
613 return join([self.git_dir]+a:000,'/')
616 function! s:repo_tree(...) dict abort
617 let dir = s:Tree(self.git_dir)
619 call s:throw('no work tree')
621 return join([dir]+a:000,'/')
625 function! s:repo_bare() dict abort
626 if self.dir() =~# '/\.git$'
629 return s:Tree(self.git_dir) ==# ''
633 function! s:repo_find(object) dict abort
634 return fugitive#Find(a:object, self.git_dir)
637 function! s:repo_translate(rev) dict abort
638 return s:Slash(fugitive#Find(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
641 function! s:repo_head(...) dict abort
642 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
645 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
647 function! s:repo_prepare(...) dict abort
648 return call('fugitive#Prepare', [self.git_dir] + a:000)
651 function! s:repo_git_command(...) dict abort
652 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
653 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
656 function! s:repo_git_chomp(...) dict abort
657 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
658 let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
659 return s:sub(system(output), '\n$', '')
662 function! s:repo_git_chomp_in_tree(...) dict abort
663 let cdback = s:Cd(self.tree())
665 return call(self.git_chomp, a:000, self)
671 function! s:repo_rev_parse(rev) dict abort
672 return fugitive#RevParse(a:rev, self.git_dir)
675 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
677 function! s:repo_superglob(base) dict abort
678 return map(fugitive#CompleteObject(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
681 call s:add_methods('repo',['superglob'])
683 function! s:repo_config(name) dict abort
684 return fugitive#Config(a:name, self.git_dir)
687 function! s:repo_user() dict abort
688 let username = self.config('user.name')
689 let useremail = self.config('user.email')
690 return username.' <'.useremail.'>'
693 call s:add_methods('repo',['config', 'user'])
697 function! s:DirCommitFile(path) abort
698 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40,\}\|[0-3]\)\(/.*\)\=$')
705 function! s:DirRev(url) abort
706 let [dir, commit, file] = s:DirCommitFile(a:url)
707 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
710 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
711 function! s:MergeHead(...) abort
712 let dir = fugitive#Find('.git/', a:0 ? a:1 : s:Dir())
713 for head in s:merge_heads
714 if filereadable(dir . head)
721 function! s:Owner(path, ...) abort
722 let dir = a:0 ? a:1 : s:Dir()
726 let actualdir = fugitive#Find('.git/', dir)
727 let [pdir, commit, file] = s:DirCommitFile(a:path)
728 if s:cpath(dir, pdir)
729 if commit =~# '^\x\{40,\}$'
731 elseif commit ==# '2'
733 elseif commit ==# '0'
736 let merge_head = s:MergeHead()
741 return merge_head . '^{}'
742 elseif commit ==# '1'
743 return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
746 let path = fnamemodify(a:path, ':p')
747 if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
748 return strpart(path, len(actualdir))
750 let refs = fugitive#Find('.git/refs', dir)
751 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
752 return strpart(path, len(refs) - 4)
757 function! fugitive#Real(url) abort
761 let [dir, commit, file] = s:DirCommitFile(a:url)
763 let tree = s:Tree(dir)
764 return FugitiveVimPath((len(tree) ? tree : dir) . file)
766 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
767 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
768 let url = {pre}Real(a:url)
770 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
772 return FugitiveVimPath(empty(url) ? a:url : url)
775 function! fugitive#Path(url, ...) abort
779 let dir = a:0 > 1 ? a:2 : s:Dir()
780 let tree = s:Tree(dir)
782 return fugitive#Real(a:url)
784 let path = s:Slash(fugitive#Real(a:url))
787 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
788 if s:cpath(cwd . '/', path[0 : len(cwd)])
789 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
792 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
794 let cwd = fnamemodify(cwd, ':h')
797 return a:1[0:-2] . path
800 let temp_state = s:TempState(url)
801 if has_key(temp_state, 'bufnr')
802 let url = bufname(temp_state.bufnr)
804 let url = s:Slash(fnamemodify(url, ':p'))
805 if url =~# '/$' && s:Slash(a:url) !~# '/$'
808 let [argdir, commit, file] = s:DirCommitFile(a:url)
809 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
811 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
812 let file = '/.git'.url[strlen(dir) : -1]
813 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
814 let file = url[len(tree) : -1]
815 elseif s:cpath(url) ==# s:cpath(tree)
818 if empty(file) && a:1 =~# '^$\|^[.:]/$'
819 return FugitiveGitPath(fugitive#Real(a:url))
821 return substitute(file, '^/', a:1, '')
824 function! s:Relative(...) abort
825 return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
828 function! fugitive#Find(object, ...) abort
829 if type(a:object) == type(0)
830 let name = bufname(a:object)
831 return FugitiveVimPath(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
832 elseif a:object =~# '^[~$]'
833 let prefix = matchstr(a:object, '^[~$]\i*')
834 let owner = expand(prefix)
835 return FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
836 elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
837 return FugitiveVimPath(a:object)
838 elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
839 return FugitiveVimPath(simplify(getcwd() . '/' . a:object))
841 let dir = a:0 ? a:1 : s:Dir()
843 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
844 let dir = FugitiveExtractGitDir(file)
846 return fnamemodify(FugitiveVimPath(len(file) ? file : a:object), ':p')
849 let rev = s:Slash(a:object)
850 let tree = s:Tree(dir)
851 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
853 let f = len(tree) ? tree . '/.git' : dir
854 elseif rev =~# '^\.git/'
855 let f = substitute(rev, '^\.git', '', '')
856 let cdir = fugitive#CommonDir(dir)
857 if f =~# '^/\.\./\.\.\%(/\|$\)'
858 let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
859 elseif f =~# '^/\.\.\%(/\|$\)'
860 let f = base . f[3:-1]
861 elseif cdir !=# dir && (
862 \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
863 \ f !~# '^/\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
864 \ getftime(FugitiveVimPath(dir . f)) < 0 && getftime(FugitiveVimPath(cdir . f)) >= 0)
865 let f = simplify(cdir . f)
867 let f = simplify(dir . f)
871 elseif rev =~# '^\.\%(/\|$\)'
872 let f = base . rev[1:-1]
873 elseif rev =~# '^::\%(/\|\a\+\:\)'
875 elseif rev =~# '^::\.\.\=\%(/\|$\)'
876 let f = simplify(getcwd() . '/' . rev[2:-1])
878 let f = base . '/' . rev[2:-1]
879 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
880 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
881 if s:cpath(base . '/', (f . '/')[0 : len(base)])
882 let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
884 let altdir = FugitiveExtractGitDir(f)
885 if len(altdir) && !s:cpath(dir, altdir)
886 return fugitive#Find(a:object, altdir)
889 elseif rev =~# '^:[0-3]:'
890 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
892 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
893 let f = fnamemodify($GIT_INDEX_FILE, ':p')
895 let f = fugitive#Find('.git/index', dir)
897 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
898 let f = matchstr(rev, ')\zs.*')
899 if f=~# '^\.\.\=\%(/\|$\)'
900 let f = simplify(getcwd() . '/' . f)
901 elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
902 let f = base . '/' . f
904 elseif rev =~# '^:/\@!'
905 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
908 let commit = substitute(matchstr(rev, '^[^:.-][^:]*\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
909 let file = substitute(matchstr(rev, '^[^:.-][^:]*\zs:.*'), '^:', '/', '')
910 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
911 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
912 if s:cpath(base . '/', (file . '/')[0 : len(base)])
913 let file = '/' . strpart(file, len(base) + 1)
915 let altdir = FugitiveExtractGitDir(file)
916 if len(altdir) && !s:cpath(dir, altdir)
917 return fugitive#Find(a:object, altdir)
922 let commits = split(commit, '\.\.\.-\@!', 1)
924 call map(commits, 'empty(v:val) || v:val ==# "@" ? "HEAD" : v:val')
925 let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
927 if commit !~# '^[0-9a-f]\{40,\}$'
928 let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit, '--']), '\<[0-9a-f]\{40,\}\>')
931 let f = 'fugitive://' . dir . '//' . commit . file
933 let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
937 return FugitiveVimPath(f)
940 function! s:Generate(rev, ...) abort
941 return fugitive#Find(a:rev, a:0 ? a:1 : s:Dir())
944 function! s:DotRelative(path, ...) abort
945 let cwd = a:0 ? a:1 : getcwd()
946 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
947 if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
948 return '.' . strpart(path, len(cwd))
953 function! fugitive#Object(...) abort
954 let dir = a:0 > 1 ? a:2 : s:Dir()
955 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
956 if s:cpath(dir) !=# s:cpath(fdir)
959 let tree = s:Tree(dir)
960 let full = a:0 ? a:1 : @%
961 let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
962 if empty(rev) && empty(tree)
963 return FugitiveGitPath(full)
965 let rev = fugitive#Path(full, './', dir)
966 if rev =~# '^\./.git\%(/\|$\)'
967 return FugitiveGitPath(full)
970 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
973 return FugitiveGitPath(tree . rev[1:-1])
977 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
978 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
979 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
981 function! s:BufName(var) abort
983 return bufname(get(s:TempState(), 'bufnr', ''))
984 elseif a:var =~# '^#\d*$'
985 let nr = get(s:TempState(bufname(+a:var[1:-1])), 'bufnr', '')
986 return bufname(nr ? nr : +a:var[1:-1])
992 function! s:ExpandVarLegacy(str) abort
993 if get(g:, 'fugitive_legacy_quoting', 1)
994 return substitute(a:str, '\\\ze[%#!]', '', 'g')
1000 function! s:ExpandVar(other, var, flags, esc, ...) abort
1001 let cwd = a:0 ? a:1 : getcwd()
1003 return a:other[1:-1]
1004 elseif a:other =~# '^'''
1005 return s:ExpandVarLegacy(substitute(a:other[1:-2], "''", "'", "g"))
1006 elseif a:other =~# '^"'
1007 return s:ExpandVarLegacy(substitute(a:other[1:-2], '""', '"', "g"))
1008 elseif a:other =~# '^!'
1009 let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
1010 let owner = s:Owner(buffer)
1011 return len(owner) ? owner : '@'
1014 let file = s:DotRelative(fugitive#Real(s:BufName(a:var)), cwd)
1016 let flag = matchstr(flags, s:flag)
1017 let flags = strpart(flags, len(flag))
1019 let file = s:DotRelative(file, cwd)
1021 let file = fnamemodify(file, flag)
1024 let file = s:Slash(file)
1025 return (len(a:esc) ? shellescape(file) : file)
1028 function! s:Expand(rev, ...) abort
1029 if a:rev =~# '^:[0-3]$'
1030 let file = len(expand('%')) ? a:rev . ':%' : '%'
1031 elseif a:rev ==# '>'
1033 elseif a:rev =~# '^>[~^]'
1034 let file = len(expand('%')) ? '!' . a:rev[1:-1] . ':%' : '%'
1035 elseif a:rev =~# '^>[> ]\@!'
1036 let file = len(expand('%')) ? a:rev[1:-1] . ':%' : '%'
1040 return substitute(file,
1041 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1042 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd())', 'g')
1045 function! fugitive#Expand(object) abort
1046 return substitute(a:object,
1047 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1048 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
1051 function! s:ExpandSplit(string, ...) abort
1053 let string = a:string
1054 let handle_bar = a:0 && a:1
1055 let dquote = handle_bar ? '"\%([^"]\|""\|\\"\)*"\|' : ''
1056 let cwd = a:0 > 1 ? a:2 : getcwd()
1057 while string =~# '\S'
1058 if handle_bar && string =~# '^\s*|'
1059 return [list, substitute(string, '^\s*', '', '')]
1061 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] ' . (handle_bar ? '|' : '') . ']\)\+')
1062 let string = strpart(string, len(arg))
1063 let arg = substitute(arg, '^\s\+', '', '')
1064 if !exists('seen_separator')
1065 let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
1066 \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
1068 let arg = substitute(arg,
1069 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1070 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
1073 let seen_separator = 1
1076 return handle_bar ? [list, ''] : list
1079 function! s:SplitExpand(string, ...) abort
1080 return s:ExpandSplit(a:string, 0, a:0 ? a:1 : getcwd())
1083 function! s:SplitExpandChain(string, ...) abort
1084 return s:ExpandSplit(a:string, 1, a:0 ? a:1 : getcwd())
1089 function! s:TreeInfo(dir, commit) abort
1090 if a:commit =~# '^:\=[0-3]$'
1091 let index = get(s:indexes, a:dir, [])
1092 let newftime = getftime(fugitive#Find('.git/index', a:dir))
1093 if get(index, 0, -1) < newftime
1094 let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
1095 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
1100 let [info, filename] = split(line, "\t")
1101 let [mode, sha, stage] = split(info, '\s\+')
1102 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
1103 while filename =~# '/'
1104 let filename = substitute(filename, '/[^/]*$', '', '')
1105 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
1109 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
1110 elseif a:commit =~# '^\x\{40,\}$'
1111 if !has_key(s:trees, a:dir)
1112 let s:trees[a:dir] = {}
1114 if !has_key(s:trees[a:dir], a:commit)
1115 let [ftime, exec_error] = s:ChompError([a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
1117 let s:trees[a:dir][a:commit] = [{}, -1]
1118 return s:trees[a:dir][a:commit]
1120 let s:trees[a:dir][a:commit] = [{}, +ftime]
1121 let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
1123 return s:trees[a:dir][a:commit]
1126 let [info, filename] = split(line, "\t")
1127 let [mode, type, sha, size] = split(info, '\s\+')
1128 let s:trees[a:dir][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
1131 return s:trees[a:dir][a:commit]
1136 function! s:PathInfo(url) abort
1137 let [dir, commit, file] = s:DirCommitFile(a:url)
1138 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
1139 return [-1, '000000', '', '', -1]
1141 let path = substitute(file[1:-1], '/*$', '', '')
1142 let [tree, ftime] = s:TreeInfo(dir, commit)
1143 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
1144 if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
1145 return [-1, '000000', '', '', -1]
1151 function! fugitive#simplify(url) abort
1152 let [dir, commit, file] = s:DirCommitFile(a:url)
1156 if file =~# '/\.\.\%(/\|$\)'
1157 let tree = s:Tree(dir)
1159 let path = simplify(tree . file)
1160 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
1161 return FugitiveVimPath(path)
1165 return FugitiveVimPath('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
1168 function! fugitive#resolve(url) abort
1169 let url = fugitive#simplify(a:url)
1170 if url =~? '^fugitive:'
1177 function! fugitive#getftime(url) abort
1178 return s:PathInfo(a:url)[0]
1181 function! fugitive#getfsize(url) abort
1182 let entry = s:PathInfo(a:url)
1183 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
1184 let dir = s:DirCommitFile(a:url)[0]
1185 let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
1190 function! fugitive#getftype(url) abort
1191 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
1194 function! fugitive#filereadable(url) abort
1195 return s:PathInfo(a:url)[2] ==# 'blob'
1198 function! fugitive#filewritable(url) abort
1199 let [dir, commit, file] = s:DirCommitFile(a:url)
1200 if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
1203 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
1206 function! fugitive#isdirectory(url) abort
1207 return s:PathInfo(a:url)[2] ==# 'tree'
1210 function! fugitive#getfperm(url) abort
1211 let [dir, commit, file] = s:DirCommitFile(a:url)
1212 let perm = getfperm(dir)
1213 let fperm = s:PathInfo(a:url)[1]
1214 if fperm ==# '040000'
1215 let fperm = '000755'
1218 let perm = tr(perm, 'x', '-')
1220 if fperm !~# '[45]$'
1221 let perm = tr(perm, 'rw', '--')
1223 if commit !~# '^\d$'
1224 let perm = tr(perm, 'w', '-')
1226 return perm ==# '---------' ? '' : perm
1229 function! fugitive#setfperm(url, perm) abort
1230 let [dir, commit, file] = s:DirCommitFile(a:url)
1231 let entry = s:PathInfo(a:url)
1232 let perm = fugitive#getfperm(a:url)
1233 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
1234 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
1237 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1238 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])[1]
1239 return exec_error ? -1 : 0
1242 function! s:TempCmd(out, cmd) abort
1245 let cmd = (type(a:cmd) == type([]) ? fugitive#Prepare(a:cmd) : a:cmd)
1246 let redir = ' > ' . a:out
1248 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
1249 return s:SystemError('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
1250 elseif &shell =~# 'fish'
1251 return s:SystemError(' begin;' . prefix . cmd . redir . ';end ')
1253 return s:SystemError(' (' . prefix . cmd . redir . ') ')
1258 if !exists('s:blobdirs')
1261 function! s:BlobTemp(url) abort
1262 let [dir, commit, file] = s:DirCommitFile(a:url)
1266 if !has_key(s:blobdirs, dir)
1267 let s:blobdirs[dir] = tempname()
1269 let tempfile = s:blobdirs[dir] . '/' . commit . file
1270 let tempparent = fnamemodify(tempfile, ':h')
1271 if !isdirectory(tempparent)
1272 call mkdir(tempparent, 'p')
1274 if commit =~# '^\d$' || !filereadable(tempfile)
1275 let rev = s:DirRev(a:url)[1]
1276 let exec_error = s:TempCmd(tempfile, [dir, 'cat-file', 'blob', rev])[1]
1278 call delete(tempfile)
1282 return s:Resolve(tempfile)
1285 function! fugitive#readfile(url, ...) abort
1286 let entry = s:PathInfo(a:url)
1287 if entry[2] !=# 'blob'
1290 let temp = s:BlobTemp(a:url)
1294 return call('readfile', [temp] + a:000)
1297 function! fugitive#writefile(lines, url, ...) abort
1298 let url = type(a:url) ==# type('') ? a:url : ''
1299 let [dir, commit, file] = s:DirCommitFile(url)
1300 let entry = s:PathInfo(url)
1301 if commit =~# '^\d$' && entry[2] !=# 'tree'
1302 let temp = tempname()
1303 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1304 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1306 call call('writefile', [a:lines, temp] + a:000)
1307 let [hash, exec_error] = s:ChompError([dir, 'hash-object', '-w', temp])
1308 let mode = len(entry[1]) ? entry[1] : '100644'
1309 if !exec_error && hash =~# '^\x\{40,\}$'
1310 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1311 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])[1]
1317 return call('writefile', [a:lines, a:url] + a:000)
1321 \ '/**/': '/\%([^./][^/]*/\)*',
1322 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1323 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1325 \ '/*': '/[^/.][^/]*',
1328 function! fugitive#glob(url, ...) abort
1329 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1330 let append = matchstr(glob, '/*$')
1331 let glob = substitute(glob, '/*$', '', '')
1332 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1334 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1335 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
1338 let files = items(s:TreeInfo(dir, commit)[0])
1340 call filter(files, 'v:val[1][2] ==# "tree"')
1342 call map(files, 'v:val[0]')
1343 call filter(files, 'v:val =~# pattern')
1344 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1346 call map(files, 'FugitiveVimPath(prepend . v:val . append)')
1347 call extend(results, files)
1352 return join(results, "\n")
1356 function! fugitive#delete(url, ...) abort
1357 let [dir, commit, file] = s:DirCommitFile(a:url)
1358 if a:0 && len(a:1) || commit !~# '^\d$'
1361 let entry = s:PathInfo(a:url)
1362 if entry[2] !=# 'blob'
1365 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1366 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])[1]
1367 return exec_error ? -1 : 0
1370 " Section: Buffer Object
1372 let s:buffer_prototype = {}
1374 function! fugitive#buffer(...) abort
1375 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1376 call extend(buffer, s:buffer_prototype, 'keep')
1380 function! s:buffer_repo() dict abort
1381 return fugitive#repo(self['#'])
1384 function! s:buffer_type(...) dict abort
1385 return 'see b:fugitive_type'
1388 call s:add_methods('buffer', ['repo', 'type'])
1390 " Section: Completion
1392 function! s:FilterEscape(items, ...) abort
1393 let items = copy(a:items)
1394 if a:0 && type(a:1) == type('')
1395 call filter(items, 'strpart(v:val, 0, strlen(a:1)) ==# a:1')
1397 return map(items, 's:fnameescape(v:val)')
1400 function! s:GlobComplete(lead, pattern) abort
1403 elseif v:version >= 704
1404 let results = glob(a:lead . a:pattern, 0, 1)
1406 let results = split(glob(a:lead . a:pattern), "\n")
1408 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1409 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1413 function! fugitive#CompletePath(base, ...) abort
1414 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1415 let tree = s:Tree(dir) . '/'
1416 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1417 let base = substitute(a:base, strip, '', '')
1418 if base =~# '^\.git/'
1419 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1420 let matches = s:GlobComplete(dir . '/', pattern)
1421 let cdir = fugitive#CommonDir(dir)
1422 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1423 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1425 call s:Uniq(matches)
1426 call map(matches, "'.git/' . v:val")
1427 elseif base =~# '^\~/'
1428 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1429 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1430 let matches = s:GlobComplete('', base . '*')
1431 elseif len(tree) > 1
1432 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1436 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1440 function! fugitive#PathComplete(...) abort
1441 return call('fugitive#CompletePath', a:000)
1444 function! s:CompleteHeads(dir) abort
1445 let dir = fugitive#Find('.git/', a:dir)
1446 return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
1447 \ sort(s:LinesError('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')[0])
1450 function! fugitive#CompleteObject(base, ...) abort
1451 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1453 let tree = s:Tree(dir) . '/'
1455 if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1456 let subdir = strpart(cwd, len(tree)) . '/'
1459 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1461 if a:base =~# '^refs/'
1462 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1463 elseif a:base !~# '^\.\=/\|^:('
1464 let heads = s:CompleteHeads(dir)
1465 if filereadable(fugitive#Find('.git/refs/stash', dir))
1466 let heads += ["stash"]
1467 let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
1469 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1470 let results += heads
1472 call map(results, 's:fnameescape(v:val)')
1474 let results += a:0 == 1 ? fugitive#CompletePath(a:base, dir) : fugitive#CompletePath(a:base)
1478 elseif a:base =~# '^:'
1479 let entries = s:LinesError(['ls-files','--stage'], dir)[0]
1480 if a:base =~# ':\./'
1481 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1483 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1484 if a:base !~# '^:[0-3]\%(:\|$\)'
1485 call filter(entries,'v:val[1] == "0"')
1486 call map(entries,'v:val[2:-1]')
1490 let tree = matchstr(a:base, '.*[:/]')
1491 let entries = s:LinesError(['ls-tree', substitute(tree, ':\zs\./', '\=subdir', '')], dir)[0]
1492 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1493 call map(entries,'tree.s:sub(v:val,".*\t","")')
1496 return s:FilterEscape(entries, a:base)
1499 function! s:CompleteSub(subcommand, A, L, P, ...) abort
1500 let pre = strpart(a:L, 0, a:P)
1502 return fugitive#CompletePath(a:A)
1503 elseif a:A =~# '^-' || a:A is# 0
1504 return s:FilterEscape(split(s:ChompDefault('', a:subcommand, '--git-completion-helper'), ' '), a:A)
1506 return fugitive#CompleteObject(a:A, s:Dir())
1507 elseif type(a:1) == type(function('tr'))
1508 return call(a:1, [a:A, a:L, a:P])
1510 return s:FilterEscape(a:1, a:A)
1514 function! s:CompleteRevision(A, L, P, ...) abort
1515 return s:FilterEscape(s:CompleteHeads(s:Dir()), a:A)
1518 function! s:CompleteRemote(A, L, P) abort
1519 let remote = matchstr(a:L, '\u\w*[! ] *\zs\S\+\ze ')
1521 let matches = s:LinesError('ls-remote', remote)[0]
1522 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1523 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1525 let matches = s:LinesError('remote')[0]
1527 return s:FilterEscape(matches, a:A)
1530 " Section: Buffer auto-commands
1532 function! s:ReplaceCmd(cmd) abort
1533 let temp = tempname()
1534 let [err, exec_error] = s:TempCmd(temp, a:cmd)
1536 call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1538 let temp = s:Resolve(temp)
1539 let fn = expand('%:p')
1540 silent exe 'keepalt file '.temp
1541 let modelines = &modelines
1544 silent keepjumps noautocmd edit!
1546 let &modelines = modelines
1548 silent exe 'keepalt file '.s:fnameescape(fn)
1549 catch /^Vim\%((\a\+)\)\=:E302:/
1552 if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1553 silent execute 'bwipeout '.bufnr('$')
1558 function! s:QueryLog(refspec) abort
1559 let lines = s:LinesError(['log', '-n', '256', '--format=%h%x09%s', a:refspec, '--'])[0]
1560 call map(lines, 'split(v:val, "\t")')
1561 call map(lines, '{"type": "Log", "commit": v:val[0], "subject": v:val[-1]}')
1565 function! s:FormatLog(dict) abort
1566 return a:dict.commit . ' ' . a:dict.subject
1569 function! s:FormatRebase(dict) abort
1570 return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
1573 function! s:FormatFile(dict) abort
1574 return a:dict.status . ' ' . a:dict.filename
1577 function! s:Format(val) abort
1578 if type(a:val) == type({})
1579 return s:Format{a:val.type}(a:val)
1580 elseif type(a:val) == type([])
1581 return map(copy(a:val), 's:Format(v:val)')
1587 function! s:AddHeader(key, value) abort
1592 while !empty(getline(before))
1595 call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
1596 if before == 1 && line('$') == 2
1597 silent keepjumps 2delete _
1601 function! s:AddSection(label, lines, ...) abort
1602 let note = a:0 ? a:1 : ''
1603 if empty(a:lines) && empty(note)
1606 call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
1609 function! fugitive#BufReadStatus() abort
1610 let amatch = s:Slash(expand('%:p'))
1611 let b:fugitive_type = 'index'
1612 unlet! b:fugitive_reltime
1614 silent doautocmd BufReadPre
1615 let cmd = [fnamemodify(amatch, ':h')]
1616 setlocal noro ma nomodeline buftype=nowrite
1617 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : fugitive#Find('.git/index'), ':p')) !=# s:cpath(amatch)
1618 let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1621 let b:fugitive_files = {'Staged': {}, 'Unstaged': {}}
1622 let [staged, unstaged, untracked] = [[], [], []]
1624 if fugitive#GitVersion(2, 11)
1625 let cmd += ['status', '--porcelain=v2', '-bz']
1626 let [output, message, exec_error] = s:NullError(cmd)
1628 throw 'fugitive: ' . message
1631 let i = match(output, '^[^#]')
1632 let head = matchlist(output[:i], '^# branch\.head \zs.*$')[0]
1633 let pull = get(matchlist(output[:i], '^# branch\.upstream \zs.*$'), 0, '')
1636 elseif head ==# '(detached)'
1637 let head = matchlist(output[:i], '^# branch\.oid \zs.*$')[0][:10]
1643 while i < len(output)
1644 let line = output[i]
1646 call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1]})
1647 elseif line[0] !=# '#'
1649 let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
1651 let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
1655 let file = output[i] . ' -> ' . matchstr(file, ' \zs.*')
1657 let sub = matchstr(line, '^[12u] .. \zs....')
1659 call add(staged, {'type': 'File', 'status': line[2], 'filename': file, 'sub': sub})
1662 call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'sub': sub})
1668 let cmd += ['status', '--porcelain', '-bz']
1669 let [output, message, exec_error] = s:NullError(cmd)
1671 throw 'fugitive: ' . message
1674 let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
1676 if head =~# '\.\.\.'
1677 let [head, pull] = split(head, '\.\.\.')
1679 elseif head ==# 'HEAD' || empty(head)
1680 let head = FugitiveHead(11)
1687 while i < len(output)
1688 let line = output[i]
1689 let file = line[3:-1]
1695 if line[0:1] =~# '[RC]'
1696 let files = output[i] . ' -> ' . file
1699 if line[0] !~# '[ ?!#]'
1700 call add(staged, {'type': 'File', 'status': line[0], 'filename': files, 'sub': ''})
1702 if line[0:1] ==# '??'
1703 call add(untracked, {'type': 'File', 'status': line[1], 'filename': files})
1704 elseif line[1] !~# '[ !#]'
1705 call add(unstaged, {'type': 'File', 'status': line[1], 'filename': files, 'sub': ''})
1711 let b:fugitive_files['Staged'][dict.filename] = dict
1713 for dict in unstaged
1714 let b:fugitive_files['Unstaged'][dict.filename] = dict
1717 let config = fugitive#Config()
1719 let pull_type = 'Pull'
1721 let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
1723 let rebase = fugitive#Config('pull.rebase', config)
1725 if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
1726 let pull_type = 'Rebase'
1727 elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
1728 let pull_type = 'Merge'
1732 let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
1733 if empty(push_remote)
1734 let push_remote = fugitive#Config('remote.pushDefault', config)
1736 let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
1742 let unpulled = s:QueryLog(head . '..' . pull)
1747 let unpushed = s:QueryLog(push . '..' . head)
1752 if isdirectory(fugitive#Find('.git/rebase-merge/'))
1753 let rebasing_dir = fugitive#Find('.git/rebase-merge/')
1754 elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
1755 let rebasing_dir = fugitive#Find('.git/rebase-apply/')
1759 let rebasing_head = 'detached HEAD'
1760 if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
1761 let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
1763 let lines = readfile(rebasing_dir . 'git-rebase-todo')
1765 let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
1771 if getfsize(rebasing_dir . 'done') > 0
1772 let done = readfile(rebasing_dir . 'done')
1773 call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
1774 let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
1775 let lines = done + lines
1779 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
1780 if len(match) && match[1] !~# 'exec\|merge\|label'
1781 call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
1786 let diff = {'Staged': [], 'Unstaged': []}
1788 let diff['Staged'] =
1789 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'])[0]
1792 let diff['Unstaged'] =
1793 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix'])[0]
1795 let b:fugitive_diff = diff
1796 let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
1797 let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
1799 silent keepjumps %delete_
1801 call s:AddHeader('Head', head)
1802 call s:AddHeader(pull_type, pull)
1804 call s:AddHeader('Push', push)
1806 call s:AddSection('Rebasing ' . rebasing_head, rebasing)
1807 call s:AddSection('Untracked', untracked)
1808 call s:AddSection('Unstaged', unstaged)
1809 let unstaged_end = len(unstaged) ? line('$') : 0
1810 call s:AddSection('Staged', staged)
1811 let staged_end = len(staged) ? line('$') : 0
1812 call s:AddSection('Unpushed to ' . push, unpushed)
1813 call s:AddSection('Unpulled from ' . pull, unpulled)
1815 setlocal nomodified readonly noswapfile
1816 silent doautocmd BufReadPost
1817 setlocal nomodifiable
1818 if &bufhidden ==# ''
1819 setlocal bufhidden=delete
1821 let b:dispatch = ':Gfetch --all'
1822 call fugitive#MapJumps()
1823 call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1824 call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
1825 call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
1826 call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
1827 call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
1828 call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
1829 call s:Map('n', 'U', ":exe <SID>EchoExec('reset', '-q')<CR>", '<silent>')
1830 call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
1831 call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
1832 call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
1833 call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
1834 call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
1835 call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
1836 call s:Map('n', 'C', ":<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>", '<silent>')
1837 call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1838 call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
1839 call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
1840 call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
1841 call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
1842 call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1843 call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1844 call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1845 call s:Map('n', 'D', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<Bar>redraw<Bar>echohl WarningMsg<Bar> echo ':Gstatus D is deprecated in favor of dd'<Bar>echohl NONE<CR>", '<silent>')
1846 call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
1847 call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1848 call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1849 call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
1850 call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
1851 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
1852 call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
1853 call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1854 call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, P for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
1855 call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1856 call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
1857 call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1858 if empty(mapcheck('q', 'n'))
1859 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<Bar>echohl WarningMsg<Bar>echo ':Gstatus q is deprecated in favor of gq or the built-in <Lt>C-W>q'<Bar>echohl NONE<CR>
1861 call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
1862 call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
1863 call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1864 call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1865 call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
1866 call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1867 call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
1868 call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1869 call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
1870 call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
1871 setlocal filetype=fugitive
1873 for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
1874 while len(getline(lnum))
1875 let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
1876 if has_key(expanded[section], filename)
1877 call s:StageInline('show', lnum)
1883 let b:fugitive_reltime = reltime()
1886 return 'echoerr ' . string(v:exception)
1890 function! fugitive#FileReadCmd(...) abort
1891 let amatch = a:0 ? a:1 : expand('<amatch>')
1892 let [dir, rev] = s:DirRev(amatch)
1893 let line = a:0 > 1 ? a:2 : line("'[")
1895 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1897 if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
1898 let cmd = fugitive#Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1900 let cmd = fugitive#Prepare(dir, 'cat-file', '-p', rev)
1902 return line . 'read !' . escape(cmd, '!#%')
1905 function! fugitive#FileWriteCmd(...) abort
1906 let tmp = tempname()
1907 let amatch = a:0 ? a:1 : expand('<amatch>')
1908 let autype = a:0 > 1 ? 'Buf' : 'File'
1909 if exists('#' . autype . 'WritePre')
1910 execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
1913 let [dir, commit, file] = s:DirCommitFile(amatch)
1914 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1915 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1917 silent execute "'[,']write !".fugitive#Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1918 let sha1 = readfile(tmp)[0]
1919 let old_mode = matchstr(s:SystemError([dir, 'ls-files', '--stage', '.' . file])[0], '^\d\+')
1921 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1923 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1924 let [error, exec_error] = s:SystemError([dir, 'update-index', '--index-info'], info . "\n")
1927 if exists('#' . autype . 'WritePost')
1928 execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
1932 return 'echoerr '.string('fugitive: '.error)
1939 function! fugitive#BufReadCmd(...) abort
1940 let amatch = a:0 ? a:1 : expand('<amatch>')
1942 let [dir, rev] = s:DirRev(amatch)
1944 return 'echo "Invalid Fugitive URL"'
1947 let b:fugitive_type = 'stage'
1949 let [b:fugitive_type, exec_error] = s:ChompError([dir, 'cat-file', '-t', rev])
1950 if exec_error && rev =~# '^:0'
1951 let sha = s:ChompDefault('', dir, 'write-tree', '--prefix=' . rev[3:-1])
1952 let exec_error = empty(sha)
1953 let b:fugitive_type = exec_error ? '' : 'tree'
1956 let error = b:fugitive_type
1957 unlet b:fugitive_type
1959 if empty(&bufhidden)
1960 setlocal bufhidden=delete
1963 let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
1964 return 'silent doautocmd BufNewFile'
1966 setlocal readonly nomodifiable
1967 return 'silent doautocmd BufNewFile|echo ' . string(error)
1969 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1970 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1972 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1973 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1977 if b:fugitive_type !=# 'blob'
1981 setlocal noreadonly modifiable
1982 let pos = getpos('.')
1983 silent keepjumps %delete_
1987 silent doautocmd BufReadPre
1988 if b:fugitive_type ==# 'tree'
1989 let b:fugitive_display_format = b:fugitive_display_format % 2
1990 if b:fugitive_display_format
1991 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1994 let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
1996 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1998 elseif b:fugitive_type ==# 'tag'
1999 let b:fugitive_display_format = b:fugitive_display_format % 2
2000 if b:fugitive_display_format
2001 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2003 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
2005 elseif b:fugitive_type ==# 'commit'
2006 let b:fugitive_display_format = b:fugitive_display_format % 2
2007 if b:fugitive_display_format
2008 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2010 call s:ReplaceCmd([dir, 'show', '--no-color', '-m', '--first-parent', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
2011 keepjumps call search('^parent ')
2012 if getline('.') ==# 'parent '
2013 silent keepjumps delete_
2015 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
2017 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2019 silent keepjumps delete_
2021 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
2024 elseif b:fugitive_type ==# 'stage'
2025 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
2026 elseif b:fugitive_type ==# 'blob'
2027 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2030 keepjumps call setpos('.',pos)
2031 setlocal nomodified noswapfile
2032 let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
2033 let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
2034 if empty(&bufhidden)
2035 setlocal bufhidden=delete
2037 let &l:modifiable = modifiable
2038 if b:fugitive_type !=# 'blob'
2039 setlocal filetype=git foldmethod=syntax
2040 call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2041 call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2043 call fugitive#MapJumps()
2047 return 'silent ' . s:DoAutocmd('BufReadPost') .
2048 \ (modifiable ? '' : '|setl nomodifiable')
2050 return 'echoerr ' . string(v:exception)
2054 function! fugitive#BufWriteCmd(...) abort
2055 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
2058 function! fugitive#SourceCmd(...) abort
2059 let amatch = a:0 ? a:1 : expand('<amatch>')
2060 let temp = s:BlobTemp(amatch)
2062 return 'noautocmd source ' . s:fnameescape(amatch)
2064 if !exists('g:virtual_scriptnames')
2065 let g:virtual_scriptnames = {}
2067 let g:virtual_scriptnames[temp] = amatch
2068 return 'source ' . s:fnameescape(temp)
2071 " Section: Temp files
2073 if !exists('s:temp_files')
2074 let s:temp_files = {}
2077 function! s:TempState(...) abort
2078 return get(s:temp_files, s:cpath(fnamemodify(a:0 ? a:1 : @%, ':p')), {})
2081 function! s:TempReadPre(file) abort
2082 if has_key(s:temp_files, s:cpath(a:file))
2083 let dict = s:temp_files[s:cpath(a:file)]
2085 setlocal bufhidden=delete nobuflisted
2086 setlocal buftype=nowrite
2087 if has_key(dict, 'modifiable')
2088 let &l:modifiable = dict.modifiable
2091 let b:git_dir = dict.dir
2092 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
2097 function! s:TempReadPost(file) abort
2098 if has_key(s:temp_files, s:cpath(a:file))
2099 let dict = s:temp_files[s:cpath(a:file)]
2100 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
2101 let &l:filetype = dict.filetype
2103 setlocal foldmarker=<<<<<<<,>>>>>>>
2104 if empty(mapcheck('q', 'n'))
2105 nnoremap <buffer> <silent> q :<C-U>bdelete<Bar>echohl WarningMsg<Bar>echo "Temp file q is deprecated in favor of the built-in <Lt>C-W>q"<Bar>echohl NONE<CR>
2108 call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
2114 augroup fugitive_temp
2116 autocmd BufReadPre * exe s:TempReadPre( expand('<amatch>:p'))
2117 autocmd BufReadPost * exe s:TempReadPost(expand('<amatch>:p'))
2122 function! fugitive#Command(line1, line2, range, bang, mods, arg) abort
2124 let [args, after] = s:SplitExpandChain(a:arg, s:Tree(dir))
2126 let cmd = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
2127 return (empty(cmd) ? 'exe' : cmd) . after
2129 let alias = get(s:Aliases(dir), args[0], '!')
2130 if get(args, 1, '') !=# '--help' && alias !~# '^!\|[\"'']' && !filereadable(s:ExecPath() . '/git-' . args[0])
2131 \ && !(has('win32') && filereadable(s:ExecPath() . '/git-' . args[0] . '.exe'))
2132 call remove(args, 0)
2133 call extend(args, split(alias, '\s\+'), 'keep')
2135 let name = substitute(args[0], '\%(^\|-\)\(\l\)', '\u\1', 'g')
2136 if exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
2139 return 'exe ' . string(s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, args[1:-1])) . after
2141 return 'echoerr ' . string(v:exception)
2144 if a:bang || args[0] =~# '^-P$\|^--no-pager$\|diff\%(tool\)\@!\|log\|^show$' ||
2145 \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
2146 \ (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
2147 return s:OpenExec((a:line2 > 0 ? a:line2 : '') . (a:line2 ? 'split' : 'edit'), a:mods, args, dir) . after
2149 if s:HasOpt(args, ['add', 'checkout', 'commit', 'stage', 'stash', 'reset'], '-p', '--patch') ||
2150 \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive') ||
2151 \ index(['--paginate', '-p'], args[0]) >= 0
2152 let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
2153 let assign = len(dir) ? '|let b:git_dir = ' . string(dir) : ''
2155 if &autowrite || &autowriteall | silent! wall | endif
2156 return mods . (a:line2 ? 'split' : 'edit') . ' term://' . s:fnameescape(s:UserCommand(dir, args)) . assign . '|startinsert' . after
2157 elseif has('terminal')
2158 if &autowrite || &autowriteall | silent! wall | endif
2159 return 'exe ' . string(mods . 'terminal ' . (a:line2 ? '' : '++curwin ') . join(map(s:UserCommandList(dir) + args, 's:fnameescape(v:val)'))) . assign . after
2162 if has('gui_running') && !has('win32')
2163 call insert(args, '--no-pager')
2166 if has('nvim') && executable('env')
2167 let pre .= 'env GIT_TERMINAL_PROMPT=0 '
2169 return 'exe ' . string('noautocmd !' . escape(pre . s:UserCommand(dir, args), '!#%')) .
2170 \ '|call fugitive#ReloadStatus(' . string(dir) . ', 1)' .
2174 let s:exec_paths = {}
2175 function! s:ExecPath() abort
2176 if !has_key(s:exec_paths, g:fugitive_git_executable)
2177 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
2179 return s:exec_paths[g:fugitive_git_executable]
2182 function! s:Subcommands() abort
2183 let exec_path = s:ExecPath()
2184 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
2188 function! s:Aliases(dir) abort
2189 if !has_key(s:aliases, a:dir)
2190 let s:aliases[a:dir] = {}
2191 let lines = s:NullError([a:dir, 'config', '-z', '--get-regexp', '^alias[.]'])[0]
2193 let s:aliases[a:dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
2196 return s:aliases[a:dir]
2199 function! fugitive#Complete(lead, ...) abort
2200 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
2201 let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
2202 let subcmd = matchstr(pre, '\u\w*[! ] *\zs[[:alnum:]-]\+\ze ')
2204 let results = sort(s:Subcommands() + keys(s:Aliases(dir)))
2205 elseif pre =~# ' -- '
2206 return fugitive#CompletePath(a:lead, dir)
2207 elseif a:lead =~# '^-'
2208 let results = split(s:ChompDefault('', dir, subcmd, '--git-completion-helper'), ' ')
2210 return fugitive#CompleteObject(a:lead, dir)
2212 return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
2215 " Section: :Gcd, :Glcd
2217 function! fugitive#CdComplete(A, L, P) abort
2218 return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
2221 function! fugitive#Cd(path, ...) abort
2222 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
2223 if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
2226 let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
2228 return (a:0 && a:1 ? 'lcd ' : 'cd ') . s:fnameescape(FugitiveVimPath(path))
2233 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
2234 let dir = a:0 ? a:1 : s:Dir()
2237 let mods = s:Mods(a:mods, &splitbelow ? 'botright' : 'topleft')
2238 let file = fugitive#Find(':', dir)
2239 let arg = ' +setl\ foldmethod=syntax\ foldlevel=1\|let\ w:fugitive_status=FugitiveGitDir() ' .
2240 \ s:fnameescape(file)
2241 for winnr in range(1, winnr('$'))
2242 if s:cpath(file, fnamemodify(bufname(winbufnr(winnr)), ':p'))
2244 call s:ReloadStatus()
2246 call s:ExpireStatus(dir)
2247 exe winnr . 'wincmd w'
2249 let w:fugitive_status = dir
2255 return mods . 'edit' . (a:bang ? '!' : '') . arg
2257 return mods . 'pedit' . arg . '|wincmd P'
2259 return mods . (a:count > 0 ? a:count : '') . 'split' . arg
2262 return 'echoerr ' . string(v:exception)
2267 function! s:StageJump(offset, section, ...) abort
2268 let line = search('^\%(' . a:section . '\)', 'nw')
2270 let line = search('^\%(' . a:1 . '\)', 'nw')
2275 for i in range(a:offset)
2276 call search(s:file_commit_pattern . '\|^$', 'W')
2277 if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
2278 call search(s:file_commit_pattern . '\|^$', 'W')
2280 if empty(getline('.'))
2284 call s:StageReveal()
2286 call s:StageReveal()
2293 function! s:StageSeek(info, fallback) abort
2295 if empty(info.section)
2298 let line = search('^' . info.section, 'wn')
2300 for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
2301 let line = search('^' . section, 'wn')
2303 return line + (info.index > 0 ? 1 : 0)
2309 while len(getline(line))
2310 let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
2312 \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
2313 \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
2314 \ filename ==# info.filename)
2318 if getline(line+1) !~# '^@'
2319 exe s:StageInline('show', line)
2321 if getline(line+1) !~# '^@'
2324 let type = info.sigil ==# '-' ? '-' : '+'
2326 while offset < info.offset
2328 if getline(line) =~# '^@'
2329 let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
2330 elseif getline(line) =~# '^[ ' . type . ']'
2332 elseif getline(line) !~# '^[ @\+-]'
2339 let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
2340 if len(commit) && commit ==# info.commit
2346 let i += getline(line) !~# '^[ @\+-]'
2349 return exists('backup') ? backup : line - 1
2352 function! s:DoAutocmdChanged(dir) abort
2353 let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
2354 if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
2358 let g:fugitive_event = dir
2359 exe s:DoAutocmd('User FugitiveChanged')
2361 unlet! g:fugitive_event
2362 " Force statusline reload with the buffer's Git dir
2368 function! s:ReloadStatusBuffer(...) abort
2369 if get(b:, 'fugitive_type', '') !=# 'index'
2372 let original_lnum = a:0 ? a:1 : line('.')
2373 let info = s:StageInfo(original_lnum)
2374 call fugitive#BufReadStatus()
2375 exe s:StageSeek(info, original_lnum)
2380 function! s:ReloadStatus(...) abort
2381 call s:ExpireStatus(-1)
2382 call s:ReloadStatusBuffer(a:0 ? a:1 : line('.'))
2383 exe s:DoAutocmdChanged(-1)
2387 let s:last_time = reltime()
2388 if !exists('s:last_times')
2389 let s:last_times = {}
2392 function! s:ExpireStatus(bufnr) abort
2394 let s:last_time = reltime()
2397 let dir = s:Dir(a:bufnr)
2399 let s:last_times[s:cpath(dir)] = reltime()
2404 function! FugitiveReloadCheck() abort
2405 let t = b:fugitive_reltime
2406 return [t, reltimestr(reltime(s:last_time, t)),
2407 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t))]
2410 function! s:ReloadWinStatus(...) abort
2411 if get(b:, 'fugitive_type', '') !=# 'index' || &modified
2414 if !exists('b:fugitive_reltime')
2415 exe s:ReloadStatusBuffer()
2418 let t = b:fugitive_reltime
2419 if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
2420 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t)) =~# '-\|\d\{10\}\.'
2421 exe s:ReloadStatusBuffer()
2425 function! s:ReloadTabStatus(...) abort
2426 let mytab = tabpagenr()
2427 let tab = a:0 ? a:1 : mytab
2428 for winnr in range(1, tabpagewinnr(tab, '$'))
2429 if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
2430 execute 'tabnext '.tab
2432 execute winnr.'wincmd w'
2433 let restorewinnr = 1
2436 call s:ReloadWinStatus()
2438 if exists('restorewinnr')
2442 execute 'tabnext '.mytab
2446 unlet! t:fugitive_reload_status
2449 function! fugitive#ReloadStatus(...) abort
2450 call s:ExpireStatus(a:0 ? a:1 : -1)
2451 if a:0 > 1 ? a:2 : 1
2453 let t:fugitive_reload_status = t
2454 for tabnr in exists('*settabvar') ? range(1, tabpagenr('$')) : []
2455 call settabvar(tabnr, 'fugitive_reload_status', t)
2457 call s:ReloadTabStatus()
2458 exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
2460 call s:ReloadWinStatus()
2465 function! fugitive#EfmDir(...) abort
2466 let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
2467 let dir = substitute(dir, '%%', '%', 'g')
2468 let dir = substitute(dir, '\\\ze[\,]', '', 'g')
2472 augroup fugitive_status
2474 autocmd BufWritePost * call fugitive#ReloadStatus(-1, 0)
2475 autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#ReloadStatus(-2, 0)
2476 autocmd BufDelete * nested
2477 \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
2478 \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
2479 \ call fugitive#ReloadStatus(+expand('<abuf>'), 1) |
2481 \ call fugitive#ReloadStatus(-2, 0) |
2484 autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
2485 \ call fugitive#ReloadStatus(fugitive#EfmDir(), 1)
2487 autocmd FocusGained * call fugitive#ReloadStatus(-2, 0)
2489 autocmd BufEnter index,index.lock
2490 \ call s:ReloadWinStatus()
2492 \ if exists('t:fugitive_reload_status') |
2493 \ call s:ReloadTabStatus() |
2497 function! s:StageInfo(...) abort
2498 let lnum = a:0 ? a:1 : line('.')
2499 let sigil = matchstr(getline(lnum), '^[ @\+-]')
2502 let type = sigil ==# '-' ? '-' : '+'
2503 while lnum > 0 && getline(lnum) !~# '^@'
2504 if getline(lnum) =~# '^[ '.type.']'
2509 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2510 while getline(lnum) =~# '^[ @\+-]'
2514 let slnum = lnum + 1
2517 while len(getline(slnum - 1)) && empty(section)
2519 let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2520 if empty(section) && getline(slnum) !~# '^[ @\+-]'
2524 let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2525 return {'section': section,
2526 \ 'heading': getline(slnum),
2530 \ 'relative': reverse(split(text, ' -> ')),
2531 \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2532 \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2533 \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2534 \ 'sub': get(get(get(b:fugitive_files, section, {}), text, {}), 'sub', ''),
2538 function! s:Selection(arg1, ...) abort
2540 let arg1 = line('.')
2542 elseif a:arg1 ==# 'v'
2543 let arg1 = line("'<")
2544 let arg2 = line("'>")
2547 let arg2 = a:0 ? a:1 : 0
2551 let last = first - arg2 - 1
2557 while getline(first) =~# '^$\|^[A-Z][a-z]'
2560 if first > last || &filetype !=# 'fugitive'
2564 while getline(flnum) =~# '^[ @\+-]'
2567 let slnum = flnum + 1
2570 while len(getline(slnum - 1)) && empty(section)
2572 let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2573 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2579 \ 'heading': heading,
2580 \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2588 let line = getline(flnum)
2589 let lnum = first - (arg1 == flnum ? 0 : 1)
2590 let root = s:Tree() . '/'
2592 if line =~# '^\u\l\+\ze.* (\d\+)$'
2593 let template.heading = getline(lnum)
2594 let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2595 let template.index = 0
2596 elseif line =~# '^[ @\+-]'
2597 let template.index -= 1
2598 if !results[-1].patch
2599 let results[-1].patch = lnum
2601 let results[-1].lnum = lnum
2602 elseif line =~# '^[A-Z?] '
2603 let filename = matchstr(line, '^[A-Z?] \zs.*')
2604 call add(results, extend(deepcopy(template), {
2606 \ 'filename': filename,
2607 \ 'relative': reverse(split(filename, ' -> ')),
2608 \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2609 \ 'status': matchstr(line, '^[A-Z?]'),
2611 elseif line =~# '^\x\x\x\+ '
2612 call add(results, extend({
2614 \ 'commit': matchstr(line, '^\x\x\x\+'),
2615 \ }, template, 'keep'))
2616 elseif line =~# '^\l\+ \x\x\x\+ '
2617 call add(results, extend({
2619 \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2620 \ 'status': matchstr(line, '^\l\+'),
2621 \ }, template, 'keep'))
2624 let template.index += 1
2625 let line = getline(lnum)
2627 if len(results) && results[0].patch && arg2 == 0
2628 while getline(results[0].patch) =~# '^[ \+-]'
2629 let results[0].patch -= 1
2631 while getline(results[0].lnum + 1) =~# '^[ \+-]'
2632 let results[0].lnum += 1
2638 function! s:StageArgs(visual) abort
2641 for record in s:Selection(a:visual ? 'v' : 'n')
2642 if len(record.commit)
2643 call add(commits, record.commit)
2645 call extend(paths, record.paths)
2647 if s:cpath(s:Tree(), getcwd())
2648 call map(paths, 'fugitive#Path(v:val, "./")')
2650 return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2653 function! s:Do(action, visual) abort
2654 let line = getline('.')
2656 if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
2657 let header = matchstr(line, '^\S\+\ze:')
2658 if len(header) && exists('*s:Do' . a:action . header . 'Header')
2659 let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2661 let section = matchstr(line, '^\S\+')
2662 if exists('*s:Do' . a:action . section . 'Heading')
2663 let reload = s:Do{a:action}{section}Heading(line) > 0
2666 return reload ? s:ReloadStatus() : ''
2668 let selection = s:Selection(a:visual ? 'v' : 'n')
2672 call filter(selection, 'v:val.section ==# selection[0].section')
2676 for record in selection
2677 if exists('*s:Do' . a:action . record.section)
2678 let status = s:Do{a:action}{record.section}(record)
2685 let reload = reload || (status > 0)
2688 execute record.lnum + 1
2692 return 'echoerr ' . string(v:exception)
2695 execute s:ReloadStatus()
2697 if exists('success')
2698 call s:StageReveal()
2704 function! s:StageReveal() abort
2706 let begin = line('.')
2707 if getline(begin) =~# '^@'
2709 while getline(end) =~# '^[ \+-]'
2712 elseif getline(begin) =~# '^commit '
2714 while end < line('$') && getline(end + 1) !~# '^commit '
2717 elseif getline(begin) =~# s:section_pattern
2719 while len(getline(end + 1))
2724 while line('.') > line('w0') + &scrolloff && end > line('w$')
2725 execute "normal! \<C-E>"
2730 let s:file_pattern = '^[A-Z?] .\|^diff --'
2731 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2732 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2734 function! s:NextHunk(count) abort
2735 if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2736 exe s:StageInline('show')
2738 for i in range(a:count)
2739 if &filetype ==# 'fugitive'
2740 call search(s:file_pattern . '\|^@', 'W')
2741 if getline('.') =~# s:file_pattern
2742 exe s:StageInline('show')
2743 if getline(line('.') + 1) =~# '^@'
2748 call search('^@@', 'W')
2751 call s:StageReveal()
2755 function! s:PreviousHunk(count) abort
2756 for i in range(a:count)
2757 if &filetype ==# 'fugitive'
2758 let lnum = search(s:file_pattern . '\|^@','Wbn')
2759 call s:StageInline('show', lnum)
2760 call search('^? .\|^@','Wb')
2762 call search('^@@', 'Wb')
2765 call s:StageReveal()
2769 function! s:NextFile(count) abort
2770 for i in range(a:count)
2771 exe s:StageInline('hide')
2772 if !search(s:file_pattern, 'W')
2776 exe s:StageInline('hide')
2780 function! s:PreviousFile(count) abort
2781 exe s:StageInline('hide')
2782 for i in range(a:count)
2783 if !search(s:file_pattern, 'Wb')
2786 exe s:StageInline('hide')
2791 function! s:NextItem(count) abort
2792 for i in range(a:count)
2793 if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2794 call search('^commit ', 'W')
2797 call s:StageReveal()
2801 function! s:PreviousItem(count) abort
2802 for i in range(a:count)
2803 if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2804 call search('^commit ', 'Wbe')
2807 call s:StageReveal()
2811 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2812 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2814 function! s:NextSection(count) abort
2815 let orig = line('.')
2816 if getline('.') !~# '^commit '
2819 for i in range(a:count)
2820 if !search(s:section_commit_pattern, 'W')
2824 if getline('.') =~# s:section_commit_pattern
2825 call s:StageReveal()
2826 return getline('.') =~# s:section_pattern ? '+' : ':'
2832 function! s:PreviousSection(count) abort
2833 let orig = line('.')
2834 if getline('.') !~# '^commit '
2837 for i in range(a:count)
2838 if !search(s:section_commit_pattern . '\|\%^', 'bW')
2842 if getline('.') =~# s:section_commit_pattern || line('.') == 1
2843 call s:StageReveal()
2844 return getline('.') =~# s:section_pattern ? '+' : ':'
2850 function! s:NextSectionEnd(count) abort
2852 if empty(getline('.'))
2855 for i in range(a:count)
2856 if !search(s:section_commit_pattern, 'W')
2860 return search('^.', 'Wb')
2863 function! s:PreviousSectionEnd(count) abort
2865 for i in range(a:count)
2866 if search(s:section_commit_pattern, 'Wb') <= 1
2876 return search('^.', 'Wb')
2879 function! s:PatchSearchExpr(reverse) abort
2880 let line = getline('.')
2881 if col('.') ==# 1 && line =~# '^[+-]'
2882 if line =~# '^[+-]\{3\} '
2883 let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2885 let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2888 return '?' . escape(pattern, '/') . "\<CR>"
2890 return '/' . escape(pattern, '/?') . "\<CR>"
2893 return a:reverse ? '#' : '*'
2896 function! s:StageInline(mode, ...) abort
2897 if &filetype !=# 'fugitive'
2900 let lnum1 = a:0 ? a:1 : line('.')
2901 let lnum = lnum1 + 1
2902 if a:0 > 1 && a:2 == 0
2903 let info = s:StageInfo(lnum - 1)
2904 if empty(info.paths) && len(info.section)
2905 while len(getline(lnum))
2914 while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2917 let info = s:StageInfo(lnum)
2918 if !has_key(b:fugitive_diff, info.section)
2921 if getline(lnum + 1) =~# '^[ @\+-]'
2922 let lnum2 = lnum + 1
2923 while getline(lnum2 + 1) =~# '^[ @\+-]'
2926 if a:mode !=# 'show'
2927 setlocal modifiable noreadonly
2928 exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2929 call remove(b:fugitive_expanded[info.section], info.filename)
2930 setlocal nomodifiable readonly nomodified
2934 if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2941 for line in b:fugitive_diff[info.section]
2942 if mode ==# 'await' && line[0] ==# '@'
2943 let mode = 'capture'
2945 if mode !=# 'head' && line !~# '^[ @\+-]'
2951 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2953 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2955 elseif mode ==# 'capture'
2956 call add(diff, line)
2957 elseif line[0] ==# '@'
2963 setlocal modifiable noreadonly
2964 silent call append(lnum, diff)
2965 let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2966 setlocal nomodifiable readonly nomodified
2972 function! s:NextExpandedHunk(count) abort
2973 for i in range(a:count)
2974 call s:StageInline('show', line('.'), 1)
2975 call search(s:file_pattern . '\|^@','W')
2980 function! s:StageDiff(diff) abort
2981 let lnum = line('.')
2982 let info = s:StageInfo(lnum)
2983 let prefix = info.offset > 0 ? '+' . info.offset : ''
2984 if info.sub =~# '^S'
2985 if info.section ==# 'Staged'
2986 return 'Git! diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
2987 elseif info.sub =~# '^SC'
2988 return 'Git! diff --no-ext-diff --submodule=log -- ' . info.paths[0]
2990 return 'Git! diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
2992 elseif empty(info.paths) && info.section ==# 'Staged'
2993 return 'Git! diff --no-ext-diff --cached'
2994 elseif empty(info.paths)
2995 return 'Git! diff --no-ext-diff'
2996 elseif len(info.paths) > 1
2997 execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2998 return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2999 elseif info.section ==# 'Staged' && info.sigil ==# '-'
3000 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3001 return a:diff . '! :0:%'
3002 elseif info.section ==# 'Staged'
3003 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3004 return a:diff . '! @:%'
3005 elseif info.sigil ==# '-'
3006 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
3007 return a:diff . '! :(top)%'
3009 execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
3014 function! s:StageDiffEdit() abort
3015 let info = s:StageInfo(line('.'))
3016 let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
3017 if info.section ==# 'Staged'
3018 return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
3019 elseif info.status ==# '?'
3020 call s:TreeChomp('add', '--intent-to-add', '--', arg)
3021 return s:ReloadStatus()
3023 return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
3027 function! s:StageApply(info, reverse, extra) abort
3028 if a:info.status ==# 'R'
3029 call s:throw('fugitive: patching renamed file not yet supported')
3031 let cmd = ['apply', '-p0', '--recount'] + a:extra
3033 let start = info.patch
3035 let lines = getline(start, end)
3036 if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
3039 while getline(end) =~# '^[-+ ]'
3041 if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3042 call add(lines, ' ' . getline(end)[1:-1])
3045 while start > 0 && getline(start) !~# '^@'
3047 if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3048 call insert(lines, ' ' . getline(start)[1:-1])
3049 elseif getline(start) =~# '^@'
3050 call insert(lines, getline(start))
3054 throw 'fugitive: cold not find hunk'
3055 elseif getline(start) !~# '^@@ '
3056 throw 'fugitive: cannot apply conflict hunk'
3058 let i = b:fugitive_expanded[info.section][info.filename][0]
3060 while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
3061 call add(head, b:fugitive_diff[info.section][i])
3064 call extend(lines, head, 'keep')
3065 let temp = tempname()
3066 call writefile(lines, temp)
3068 call add(cmd, '--reverse')
3070 call extend(cmd, ['--', temp])
3071 let [output, exec_error] = s:ChompError(cmd)
3075 call s:throw(output)
3078 function! s:StageDelete(lnum1, lnum2, count) abort
3082 for info in s:Selection(a:lnum1, a:lnum2)
3083 if empty(info.paths)
3086 let sub = get(get(get(b:fugitive_files, info.section, {}), info.filename, {}), 'sub')
3088 if info.status ==# 'A'
3091 if info.section ==# 'Staged'
3092 call s:TreeChomp('reset', '--', info.paths[0])
3094 if info.status =~# '[MD]'
3095 call s:TreeChomp('submodule', 'update', '--', info.paths[0])
3096 call add(restore, ':Git -C ' . info.relative[0] . ' checkout -')
3100 if info.status ==# 'D'
3101 let undo = 'Gremove'
3102 elseif info.paths[0] =~# '/$'
3103 let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
3106 let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
3109 call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3110 elseif info.status ==# '?'
3111 call s:TreeChomp('clean', '-f', '--', info.paths[0])
3113 call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3115 call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3116 elseif info.status =~# '[ADU]' &&
3117 \ get(b:fugitive_files[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, {'status': ''}).status =~# '[AU]'
3118 call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3119 elseif info.status ==# 'U'
3120 call s:TreeChomp('rm', '--', info.paths[0])
3121 elseif info.status ==# 'A'
3122 call s:TreeChomp('rm', '-f', '--', info.paths[0])
3123 elseif info.section ==# 'Unstaged'
3124 call s:TreeChomp('checkout', '--', info.paths[0])
3126 call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3128 call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
3131 let err .= '|echoerr ' . string(v:exception)
3136 exe s:ReloadStatus()
3137 call s:StageReveal()
3139 return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3141 return 'checktime|redraw' . err
3145 function! s:StageIgnore(lnum1, lnum2, count) abort
3147 for info in s:Selection(a:lnum1, a:lnum2)
3148 call extend(paths, info.relative)
3150 call map(paths, '"/" . v:val')
3151 exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3152 let last = line('$')
3153 if last == 1 && empty(getline(1))
3154 call setline(last, paths)
3156 call append(last, paths)
3162 function! s:DoToggleHeadHeader(value) abort
3163 exe 'edit' s:fnameescape(s:Dir())
3164 call search('\C^index$', 'wc')
3167 function! s:DoStageUnpushedHeading(heading) abort
3168 let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3172 let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3173 call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3176 function! s:DoToggleUnpushedHeading(heading) abort
3177 return s:DoStageUnpushedHeading(a:heading)
3180 function! s:DoStageUnpushed(record) abort
3181 let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3185 let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3186 call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3189 function! s:DoToggleUnpushed(record) abort
3190 return s:DoStageUnpushed(a:record)
3193 function! s:DoUnstageUnpulledHeading(heading) abort
3194 call feedkeys(':Grebase')
3197 function! s:DoToggleUnpulledHeading(heading) abort
3198 call s:DoUnstageUnpulledHeading(a:heading)
3201 function! s:DoUnstageUnpulled(record) abort
3202 call feedkeys(':Grebase ' . a:record.commit)
3205 function! s:DoToggleUnpulled(record) abort
3206 call s:DoUnstageUnpulled(a:record)
3209 function! s:DoUnstageUnpushed(record) abort
3210 call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3213 function! s:DoToggleStagedHeading(...) abort
3214 call s:TreeChomp('reset', '-q')
3218 function! s:DoUnstageStagedHeading(heading) abort
3219 return s:DoToggleStagedHeading(a:heading)
3222 function! s:DoToggleUnstagedHeading(...) abort
3223 call s:TreeChomp('add', '-u')
3227 function! s:DoStageUnstagedHeading(heading) abort
3228 return s:DoToggleUnstagedHeading(a:heading)
3231 function! s:DoToggleUntrackedHeading(...) abort
3232 call s:TreeChomp('add', '.')
3236 function! s:DoStageUntrackedHeading(heading) abort
3237 return s:DoToggleUntrackedHeading(a:heading)
3240 function! s:DoToggleStaged(record) abort
3242 return s:StageApply(a:record, 1, ['--cached'])
3244 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3249 function! s:DoUnstageStaged(record) abort
3250 return s:DoToggleStaged(a:record)
3253 function! s:DoToggleUnstaged(record) abort
3254 if a:record.patch && a:record.status !=# 'A'
3255 return s:StageApply(a:record, 0, ['--cached'])
3257 call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3262 function! s:DoStageUnstaged(record) abort
3263 return s:DoToggleUnstaged(a:record)
3266 function! s:DoUnstageUnstaged(record) abort
3267 if a:record.status ==# 'A'
3268 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3275 function! s:DoToggleUntracked(record) abort
3276 call s:TreeChomp(['add', '--'] + a:record.paths)
3280 function! s:DoStageUntracked(record) abort
3281 return s:DoToggleUntracked(a:record)
3284 function! s:StagePatch(lnum1,lnum2) abort
3289 for lnum in range(a:lnum1,a:lnum2)
3290 let info = s:StageInfo(lnum)
3291 if empty(info.paths) && info.section ==# 'Staged'
3292 return 'Git reset --patch'
3293 elseif empty(info.paths) && info.section ==# 'Unstaged'
3294 return 'Git add --patch'
3295 elseif empty(info.paths) && info.section ==# 'Untracked'
3296 return 'Git add --interactive'
3297 elseif empty(info.paths)
3301 if info.section ==# 'Staged'
3302 let reset += info.relative
3303 elseif info.section ==# 'Untracked'
3304 let intend += info.paths
3305 elseif info.status !~# '^D'
3306 let add += info.relative
3311 call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3314 execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3317 execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3320 return 'echoerr ' . string(v:exception)
3322 return s:ReloadStatus()
3325 " Section: :Gcommit, :Grevert
3327 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3328 let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3329 let status = len(status) ? status . '|' : ''
3331 return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3333 return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3337 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3338 let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3339 let dir = a:0 ? a:1 : s:Dir()
3340 let tree = s:Tree(dir)
3341 let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3342 let outfile = tempname()
3345 let command = 'set GIT_EDITOR=false& '
3347 let command = 'env GIT_EDITOR=false '
3351 while get(argv, i, '--') !=# '--'
3352 if argv[i] =~# '^-[apzsneiovq].'
3353 call insert(argv, argv[i][0:1])
3354 let argv[i+1] = '-' . argv[i+1][2:-1]
3359 let command .= s:UserCommand(dir, ['commit'] + argv)
3360 if (&autowrite || &autowriteall) && !a:0
3363 if s:HasOpt(argv, '-i', '--interactive')
3364 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3365 elseif s:HasOpt(argv, '-p', '--patch')
3366 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3368 let [error_string, exec_error] = s:TempCmd(outfile, command)
3369 let errors = split(error_string, "\n")
3371 if !has('gui_running')
3375 echo join(errors, "\n")
3376 if filereadable(outfile)
3377 echo join(readfile(outfile), "\n")
3379 call fugitive#ReloadStatus(dir, 1)
3382 let error = get(errors,-2,get(errors,-1,'!'))
3383 if error =~# 'false''\=\.$'
3385 while get(argv, i, '--') !=# '--'
3386 if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3387 call remove(argv, i)
3388 elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3389 call remove(argv, i, i + 1)
3391 if argv[i] =~# '^--cleanup\>'
3397 call insert(argv, '--no-signoff', i)
3398 call insert(argv, '--no-interactive', i)
3399 call insert(argv, '--no-edit', i)
3400 if !exists('cleanup')
3401 call insert(argv, '--cleanup=strip')
3403 call extend(argv, ['-F', msgfile], 'keep')
3404 if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3405 execute mods . 'keepalt edit' s:fnameescape(msgfile)
3406 elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3407 execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3409 execute mods . 'keepalt split' s:fnameescape(msgfile)
3411 let b:fugitive_commit_arguments = argv
3412 setlocal bufhidden=wipe filetype=gitcommit
3414 elseif empty(errors)
3415 let out = readfile(outfile)
3416 echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3419 echo join(errors, "\n")
3424 return 'echoerr ' . string(v:exception)
3426 call delete(outfile)
3430 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3432 let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3433 let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3434 let [out, exec_error] = s:SystemError(cmd)
3435 call fugitive#ReloadStatus(dir, 1)
3436 if no_commit || exec_error
3437 return 'echo ' . string(substitute(out, "\n$", '', ''))
3439 return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3442 function! fugitive#CommitComplete(A, L, P) abort
3443 if a:A =~# '^--fixup=\|^--squash='
3444 let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3445 let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3447 call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3448 call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3451 return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3454 return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3459 function! fugitive#RevertComplete(A, L, P) abort
3460 return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3463 function! s:FinishCommit() abort
3464 let buf = +expand('<abuf>')
3465 let args = getbufvar(buf, 'fugitive_commit_arguments')
3467 call setbufvar(buf, 'fugitive_commit_arguments', [])
3468 if getbufvar(buf, 'fugitive_commit_rebase')
3469 call setbufvar(buf, 'fugitive_commit_rebase', 0)
3470 let s:rebase_continue = s:Dir(buf)
3472 return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3477 " Section: :Gmerge, :Grebase, :Gpull
3479 function! fugitive#MergeComplete(A, L, P) abort
3480 return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3483 function! fugitive#RebaseComplete(A, L, P) abort
3484 return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3487 function! fugitive#PullComplete(A, L, P) abort
3488 return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3491 function! s:RebaseSequenceAborter() abort
3492 if !exists('s:rebase_sequence_aborter')
3493 let temp = tempname() . '.sh'
3496 \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3497 \ 'mv "$1.fugitive" "$1"'],
3499 let s:rebase_sequence_aborter = temp
3501 return s:rebase_sequence_aborter
3504 function! fugitive#Cwindow() abort
3505 if &buftype == 'quickfix'
3509 if &buftype == 'quickfix'
3515 let s:common_efm = ''
3517 \ . '%+Eusage:%.%#,'
3518 \ . '%+Eerror:%.%#,'
3519 \ . '%+Efatal:%.%#,'
3520 \ . '%-G%.%#%\e[K%.%#,'
3521 \ . '%-G%.%#%\r%.%\+'
3523 let s:rebase_abbrevs = {
3537 function! s:RebaseEdit(cmd, dir) abort
3538 let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3540 if filereadable(rebase_todo)
3541 let new = readfile(rebase_todo)
3545 for i in range(len(new))
3546 if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3547 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3549 let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3551 let shortened_sha = strpart(sha, 0, sha_length)
3552 let shas[shortened_sha] = sha
3553 let new[i] = substitute(new[i], sha, shortened_sha, '')
3556 call writefile(new, rebase_todo)
3558 return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3561 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3562 let dir = a:0 ? a:1 : s:Dir()
3564 let mods = s:Mods(a:mods)
3565 if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3566 let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3567 let out = system(cmd)[0:-2]
3568 for file in ['end', 'msgnum']
3569 let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3570 if !filereadable(file)
3571 return 'echoerr ' . string("fugitive: " . out)
3573 call writefile([readfile(file)[0] - 1], file)
3575 call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3579 return s:RebaseEdit(mods . 'split', dir)
3580 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3581 return s:RebaseEdit(mods . 'split', dir)
3582 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3583 let rdir = fugitive#Find('.git/rebase-merge', dir)
3584 let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3585 if exec_error && isdirectory(rdir)
3586 if getfsize(rdir . '/amend') <= 0
3587 return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3588 elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3589 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3593 let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3596 let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3598 call add(argv, a:cmd)
3600 if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3601 call add(argv, '--edit')
3603 if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3604 call add(argv, '--interactive')
3606 call extend(argv, args)
3608 let [mp, efm] = [&l:mp, &l:efm]
3610 let cdback = s:Cd(s:Tree(dir))
3611 let &l:errorformat = ''
3612 \ . '%-Gerror:%.%#false''.,'
3613 \ . '%-G%.%# ''git commit'' %.%#,'
3614 \ . '%+Emerge:%.%#,'
3615 \ . s:common_efm . ','
3616 \ . '%+ECannot %.%#: You have unstaged changes.,'
3617 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3618 \ . '%+EThere is no tracking information for the current branch.,'
3619 \ . '%+EYou are not currently on a branch. Please specify which,'
3620 \ . '%+I %#git rebase --continue,'
3621 \ . 'CONFLICT (%m): %f deleted in %.%#,'
3622 \ . 'CONFLICT (%m): Merge conflict in %f,'
3623 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3624 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3625 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3626 \ . '%+ECONFLICT %.%#,'
3627 \ . '%+EKONFLIKT %.%#,'
3628 \ . '%+ECONFLIT %.%#,'
3629 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3630 \ . "%+E\u51b2\u7a81 %.%#,"
3632 if a:cmd =~# '^merge' && empty(args) &&
3633 \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3634 \ !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3635 let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3637 let cmd = s:UserCommand(dir, argv)
3639 if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3640 let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3641 let $GIT_SEQUENCE_EDITOR = 'true'
3643 let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3645 if !empty($GIT_EDITOR) || has('win32')
3646 let old_editor = $GIT_EDITOR
3647 let $GIT_EDITOR = 'false'
3649 let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3651 if !has('patch-8.1.0334') && has('terminal') && &autowrite
3652 let autowrite_was_set = 1
3656 let &l:makeprg = cmd
3657 silent noautocmd make!
3658 catch /^Vim\%((\a\+)\)\=:E211/
3659 let err = v:exception
3661 if exists('autowrite_was_set')
3665 let [&l:mp, &l:efm] = [mp, efm]
3666 if exists('old_editor')
3667 let $GIT_EDITOR = old_editor
3669 if exists('old_sequence_editor')
3670 let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3674 call fugitive#ReloadStatus(dir, 1)
3675 if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3676 if a:cmd =~# '^rebase' &&
3677 \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3678 \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3679 \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3681 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3682 elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3684 return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3687 let qflist = getqflist()
3692 let e.pattern = '^<<<<<<<'
3695 call fugitive#Cwindow()
3697 call setqflist(qflist, 'r')
3703 return exists('err') ? 'echoerr '.string(err) : 'exe'
3706 function! s:RebaseClean(file) abort
3707 if !filereadable(a:file)
3710 let old = readfile(a:file)
3712 for i in range(len(new))
3713 let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3715 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3716 let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3717 if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3718 let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3722 call writefile(new, a:file)
3727 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3728 return s:MergeRebase('merge', a:bang, a:mods, a:args)
3731 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3732 return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3735 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3736 return s:MergeRebase('pull', a:bang, a:mods, a:args)
3739 augroup fugitive_merge
3741 autocmd VimLeavePre,BufDelete git-rebase-todo
3742 \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3743 \ call s:RebaseClean(expand('<afile>')) |
3744 \ if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3745 \ let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3748 autocmd BufEnter * nested
3749 \ if exists('s:rebase_continue') |
3750 \ exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3754 " Section: :Ggrep, :Glog
3756 if !exists('g:fugitive_summary_format')
3757 let g:fugitive_summary_format = '%s'
3760 function! fugitive#GrepComplete(A, L, P) abort
3761 return s:CompleteSub('grep', a:A, a:L, a:P)
3764 function! fugitive#LogComplete(A, L, P) abort
3765 return s:CompleteSub('log', a:A, a:L, a:P)
3768 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3769 let entry = {'valid': 1}
3770 let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3772 let entry.module = match[1]
3773 let entry.lnum = +match[2]
3774 let entry.col = +match[3]
3775 let entry.text = match[4]
3776 elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3777 return {'text': a:line}
3779 let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3780 if len(entry.module)
3781 let entry.text = 'Binary file'
3785 if empty(entry.module) && a:name_only
3786 let entry.module = a:line
3788 if empty(entry.module)
3789 return {'text': a:line}
3791 if entry.module !~# ':'
3792 let entry.filename = a:prefix . entry.module
3794 let entry.filename = fugitive#Find(entry.module, a:dir)
3799 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3802 let listnr = a:line1 == 0 ? a:line1 : a:line2
3803 let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3804 if fugitive#GitVersion(2, 19)
3805 call add(cmd, '--column')
3807 let tree = s:Tree(dir)
3808 if type(a:args) == type([])
3809 let [args, after] = [a:args, '']
3811 let [args, after] = s:SplitExpandChain(a:args, tree)
3813 let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3814 let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3815 let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3817 exe listnr 'wincmd w'
3822 call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3823 let tempfile = tempname()
3824 let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
3825 silent exe s:DoAutocmd('QuickFixCmdPre ' . event)
3826 exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3827 \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3828 let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3829 call s:QuickfixSet(listnr, list, 'a')
3830 silent exe s:DoAutocmd('QuickFixCmdPost ' . event)
3831 if !has('gui_running')
3834 if !a:bang && !empty(list)
3835 return (listnr < 0 ? 'c' : 'l').'first' . after
3841 function! s:LogFlushQueue(state) abort
3842 let queue = remove(a:state, 'queue')
3843 if a:state.child_found
3844 call remove(queue, 0)
3846 if len(queue) && queue[-1] ==# {'text': ''}
3847 call remove(queue, -1)
3852 function! s:LogParse(state, dir, line) abort
3853 if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3856 let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3858 let a:state.context = 'commit'
3859 let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3860 let a:state.base_module = len(list[1]) ? list[1] : list[2]
3861 let a:state.message = list[3]
3862 if has_key(a:state, 'diffing')
3863 call remove(a:state, 'diffing')
3865 let queue = s:LogFlushQueue(a:state)
3866 let a:state.queue = [{
3868 \ 'filename': a:state.base . a:state.target,
3869 \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3870 \ 'text': a:state.message}]
3871 let a:state.child_found = 0
3873 elseif type(a:line) == type(0)
3874 return s:LogFlushQueue(a:state)
3875 elseif a:line =~# '^diff'
3876 let a:state.context = 'diffhead'
3877 elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3878 let a:state.diffing = a:line[5:-1]
3879 elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3880 let a:state.context = 'hunk'
3881 if empty(a:state.target) || a:state.target ==# a:state.diffing
3882 let a:state.child_found = 1
3883 call add(a:state.queue, {
3885 \ 'filename': a:state.base . a:state.diffing,
3886 \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3887 \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3888 \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3890 elseif a:state.follow &&
3891 \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3892 let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3894 let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3895 if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3896 let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3899 if !get(a:state, 'ignore_summary')
3900 call add(a:state.queue, {'text': a:line})
3902 elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3903 call add(a:state.queue, {'text': a:line})
3908 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
3911 let listnr = a:type =~# '^l' ? 0 : -1
3912 let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3913 let split = index(args, '--')
3915 let paths = args[split : -1]
3916 let args = args[0 : split - 1]
3923 if a:line1 == 0 && a:count
3924 let path = fugitive#Path(bufname(a:count), '/', dir)
3926 let path = fugitive#Path(@%, '/', dir)
3932 let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3933 if path =~# '^/\.git\%(/\|$\)\|^$'
3936 let range = "0," . (a:count ? a:count : bufnr(''))
3937 let extra = ['.' . path]
3938 if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3939 let state.follow = 1
3940 if !s:HasOpt(args, '--follow')
3941 call insert(args, '--follow')
3943 if !s:HasOpt(args, '--summary')
3944 call insert(args, '--summary')
3945 let state.ignore_summary = 1
3949 if !s:HasOpt(args, '--merges', '--no-merges')
3950 call insert(args, '--no-merges')
3952 call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3954 if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3955 let owner = s:Owner(@%, dir)
3957 call add(args, owner)
3963 if s:HasOpt(args, '-g', '--walk-reflogs')
3964 let format = "%gd\t%H %gs"
3966 let format = "%h\t%H " . g:fugitive_summary_format
3968 let cmd = ['--no-pager']
3969 if fugitive#GitVersion(1, 9)
3970 call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3972 call extend(cmd, ['log', '-U0', '--no-patch'])
3975 \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3976 \ args + paths + extra)
3977 let state.target = path
3978 let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3979 if empty(paths + extra) && empty(a:type) && len(s:Relative('/'))
3980 let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3982 return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3985 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3987 function! s:UsableWin(nr) abort
3988 return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3989 \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3990 \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3991 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3994 function! s:OpenParse(args, wants_cmd) abort
3997 let args = copy(a:args)
3999 if args[0] =~# '^++'
4000 call add(opts, ' ' . escape(remove(args, 0), ' |"'))
4001 elseif a:wants_cmd && args[0] =~# '^+'
4002 call add(cmds, remove(args, 0)[1:-1])
4008 let file = join(args)
4009 elseif empty(expand('%'))
4011 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
4017 let efile = s:Expand(file)
4018 let url = fugitive#Find(efile, dir)
4020 if a:wants_cmd && file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
4021 let line = line('.')
4022 if expand('%:p') !=# url
4023 let diffcmd = 'diff'
4024 let from = s:DirRev(@%)[1]
4025 let to = s:DirRev(url)[1]
4026 if empty(from) && empty(to)
4027 let diffcmd = 'diff-files'
4028 let args = ['--', expand('%:p'), url]
4030 let args = [from, '--', url]
4032 let args = [to, '--', expand('%:p')]
4035 let args = [from, to]
4037 let [res, exec_error] = s:LinesError([dir, diffcmd, '-U0'] + args)
4039 call filter(res, 'v:val =~# "^@@ "')
4040 call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
4041 call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
4042 if exists('reverse')
4043 call map(res, 'v:val[2:3] + v:val[0:1]')
4045 call filter(res, 'v:val[0] < '.line('.'))
4046 let hunk = get(res, -1, [0,0,0,0])
4047 if hunk[0] + hunk[1] > line('.')
4048 let line = hunk[2] + max([1 - hunk[3], 0])
4050 let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
4054 call insert(cmds, line)
4057 let pre = join(opts, '')
4059 let pre .= ' +' . escape(join(map(cmds, '"exe ".string(v:val)'), '|'), ' |"')
4061 let pre .= ' +' . escape(cmds[0], ' |"')
4066 function! s:DiffClose() abort
4067 let mywinnr = winnr()
4068 for winnr in [winnr('#')] + range(winnr('$'),1,-1)
4069 if winnr != mywinnr && getwinvar(winnr,'&diff')
4070 execute winnr.'wincmd w'
4080 function! s:BlurStatus() abort
4081 if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
4082 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
4084 exe winnrs[0].'wincmd w'
4094 function! s:OpenExec(cmd, mods, args, ...) abort
4095 let dir = a:0 ? s:Dir(a:1) : s:Dir()
4096 let temp = tempname()
4097 let columns = get(g:, 'fugitive_columns', 80)
4101 let env = 'set COLUMNS=' . columns . '& '
4103 let env = 'env COLUMNS=' . columns . ' '
4105 silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
4106 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
4108 let temp = s:Resolve(temp)
4109 let first = join(readfile(temp, '', 2), "\n")
4110 if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
4111 let filetype = 'man'
4113 let filetype = 'git'
4115 let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
4119 silent execute s:Mods(a:mods) . a:cmd temp
4120 call fugitive#ReloadStatus(dir, 1)
4121 return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
4124 function! fugitive#Open(cmd, bang, mods, arg, args) abort
4126 return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
4129 let mods = s:Mods(a:mods)
4131 let [file, pre] = s:OpenParse(a:args, 1)
4133 return 'echoerr ' . string(v:exception)
4135 if file !~# '^\a\a\+:'
4136 let file = s:sub(file, '/$', '')
4141 return mods . a:cmd . pre . ' ' . s:fnameescape(file)
4144 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, args) abort
4145 let mods = s:Mods(a:mods)
4148 let delete = 'silent 1,' . line('$') . 'delete_|'
4149 let after = line('$')
4151 let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4157 let args = s:SplitExpand(a:arg, s:Tree(dir))
4158 silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4159 execute delete . 'diffupdate'
4160 call fugitive#ReloadStatus(dir, 1)
4161 return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4164 let [file, pre] = s:OpenParse(a:args, 0)
4166 return 'echoerr ' . string(v:exception)
4168 if file =~# '^fugitive:' && after is# 0
4169 return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4172 exe after . 'foldopen!'
4174 return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4177 function! fugitive#EditComplete(A, L, P) abort
4179 return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
4181 return fugitive#CompleteObject(a:A, a:L, a:P)
4185 function! fugitive#ReadComplete(A, L, P) abort
4187 return fugitive#Complete(a:A, a:L, a:P)
4189 return fugitive#EditComplete(a:A, a:L, a:P)
4193 " Section: :Gwrite, :Gwq
4195 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, args) abort
4196 if exists('b:fugitive_commit_arguments')
4197 return 'write|bdelete'
4198 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4200 elseif get(b:, 'fugitive_type', '') ==# 'index'
4202 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4203 let filename = getline(4)[6:-1]
4206 setlocal buftype=nowrite
4207 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4208 let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4210 let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4220 return 'Gedit '.fnameescape(filename)
4223 let mytab = tabpagenr()
4224 let mybufnr = bufnr('')
4226 let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4228 return 'echoerr ' . string(v:exception)
4231 return 'echoerr '.string('fugitive: cannot determine file path')
4233 if file =~# '^fugitive:'
4234 return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4237 let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4238 if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4239 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4240 return 'echoerr v:errmsg'
4243 for nr in range(1,bufnr('$'))
4244 if fnamemodify(bufname(nr),':p') ==# file
4249 if treebufnr > 0 && treebufnr != bufnr('')
4250 let temp = tempname()
4251 silent execute 'keepalt %write '.temp
4252 for tab in [mytab] + range(1,tabpagenr('$'))
4253 for winnr in range(1,tabpagewinnr(tab,'$'))
4254 if tabpagebuflist(tab)[winnr-1] == treebufnr
4255 execute 'tabnext '.tab
4257 execute winnr.'wincmd w'
4258 let restorewinnr = 1
4261 let lnum = line('.')
4262 let last = line('$')
4263 silent execute '$read '.temp
4264 silent execute '1,'.last.'delete_'
4270 if exists('restorewinnr')
4273 execute 'tabnext '.mytab
4280 call writefile(readfile(temp,'b'),file,'b')
4283 execute 'write! '.s:fnameescape(file)
4287 let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4289 let [error, exec_error] = s:ChompError(['add', '--', file])
4292 let v:errmsg = 'fugitive: '.error
4293 return 'echoerr v:errmsg'
4295 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4299 let one = s:Generate(':1:'.file)
4300 let two = s:Generate(':2:'.file)
4301 let three = s:Generate(':3:'.file)
4302 for nr in range(1,bufnr('$'))
4303 let name = fnamemodify(bufname(nr), ':p')
4304 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4305 execute nr.'bdelete'
4310 let zero = s:Generate(':0:'.file)
4311 silent exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
4312 for tab in range(1,tabpagenr('$'))
4313 for winnr in range(1,tabpagewinnr(tab,'$'))
4314 let bufnr = tabpagebuflist(tab)[winnr-1]
4315 let bufname = fnamemodify(bufname(bufnr), ':p')
4316 if bufname ==# zero && bufnr != mybufnr
4317 execute 'tabnext '.tab
4319 execute winnr.'wincmd w'
4320 let restorewinnr = 1
4323 let lnum = line('.')
4324 let last = line('$')
4325 silent execute '$read '.s:fnameescape(file)
4326 silent execute '1,'.last.'delete_'
4331 if exists('restorewinnr')
4334 execute 'tabnext '.mytab
4340 call fugitive#ReloadStatus(-1, 1)
4344 function! fugitive#WqCommand(...) abort
4345 let bang = a:4 ? '!' : ''
4346 if exists('b:fugitive_commit_arguments')
4349 let result = call('fugitive#WriteCommand', a:000)
4350 if result =~# '^\%(write\|wq\|echoerr\)'
4351 return s:sub(result,'^write','wq')
4353 return result.'|quit'.bang
4357 augroup fugitive_commit
4359 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4362 " Section: :Gpush, :Gfetch
4364 function! fugitive#PushComplete(A, L, P) abort
4365 return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4368 function! fugitive#FetchComplete(A, L, P) abort
4369 return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4372 function! s:AskPassArgs(dir) abort
4373 if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4374 \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4375 if s:executable(s:ExecPath() . '/git-gui--askpass')
4376 return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4377 elseif s:executable('ssh-askpass')
4378 return ['-c', 'core.askPass=ssh-askpass']
4384 function! s:Dispatch(bang, cmd, args) abort
4386 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4388 let b:current_compiler = 'git'
4389 let &l:errorformat = s:common_efm .
4390 \ ',%\&git_dir=' . escape(substitute(dir, '%', '%%', 'g'), '\,')
4391 let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4392 if exists(':Make') == 2
4396 if !has('patch-8.1.0334') && has('terminal') && &autowrite
4397 let autowrite_was_set = 1
4401 silent noautocmd make!
4403 return 'call fugitive#Cwindow()|silent ' . s:DoAutocmd('ShellCmdPost')
4406 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4407 if empty(cc) | unlet! b:current_compiler | endif
4408 if exists('autowrite_was_set')
4414 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4415 return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4418 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4419 return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4424 augroup fugitive_diff
4426 autocmd BufWinLeave *
4427 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4428 \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4430 autocmd BufWinEnter *
4431 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4432 \ call s:diffoff() |
4436 function! s:can_diffoff(buf) abort
4437 return getwinvar(bufwinnr(a:buf), '&diff') &&
4438 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4441 function! fugitive#CanDiffoff(buf) abort
4442 return s:can_diffoff(bufnr(a:buf))
4445 function! s:diff_modifier(count) abort
4446 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4447 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4449 elseif &diffopt =~# 'vertical'
4451 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4458 function! s:diff_window_count() abort
4460 for nr in range(1,winnr('$'))
4461 let c += getwinvar(nr,'&diff')
4466 function! s:diff_restore() abort
4467 let restore = 'setlocal nodiff noscrollbind'
4468 \ . ' scrollopt=' . &l:scrollopt
4469 \ . (&l:wrap ? ' wrap' : ' nowrap')
4470 \ . ' foldlevel=999'
4471 \ . ' foldmethod=' . &l:foldmethod
4472 \ . ' foldcolumn=' . &l:foldcolumn
4473 \ . ' foldlevel=' . &l:foldlevel
4474 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4475 if has('cursorbind')
4476 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4481 function! s:diffthis() abort
4483 let w:fugitive_diff_restore = s:diff_restore()
4488 function! s:diffoff() abort
4489 if exists('w:fugitive_diff_restore')
4490 execute w:fugitive_diff_restore
4491 unlet w:fugitive_diff_restore
4497 function! s:diffoff_all(dir) abort
4498 let curwin = winnr()
4499 for nr in range(1,winnr('$'))
4500 if getwinvar(nr,'&diff')
4502 execute nr.'wincmd w'
4503 let restorewinnr = 1
4505 if s:Dir() ==# a:dir
4510 execute curwin.'wincmd w'
4513 function! s:CompareAge(mine, theirs) abort
4514 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4515 let mine = substitute(a:mine, '^:', '', '')
4516 let theirs = substitute(a:theirs, '^:', '', '')
4517 let my_score = get(scores, ':'.mine, 0)
4518 let their_score = get(scores, ':'.theirs, 0)
4519 if my_score || their_score
4520 return my_score < their_score ? -1 : my_score != their_score
4521 elseif mine ==# theirs
4524 let base = s:TreeChomp('merge-base', mine, theirs)
4527 elseif base ==# theirs
4530 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4531 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4532 return my_time < their_time ? -1 : my_time != their_time
4535 function! s:IsConflicted() abort
4536 return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4539 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, args) abort
4540 let args = copy(a:args)
4542 if get(args, 0) =~# '^+'
4543 let post = remove(args, 0)[1:-1]
4545 if exists(':DiffGitCached') && empty(args)
4546 return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4548 let commit = s:DirCommitFile(@%)[1]
4549 if a:mods =~# '\<tab\>'
4550 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4551 let pre = 'tab split'
4553 let mods = 'keepalt ' . a:mods
4556 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4557 if (empty(args) || args[0] ==# ':') && a:keepfocus
4559 if empty(commit) && s:IsConflicted()
4560 let parents = [s:Relative(':2:'), s:Relative(':3:')]
4561 elseif empty(commit)
4562 let parents = [s:Relative(':0:')]
4563 elseif commit =~# '^\d\=$'
4564 let parents = [s:Relative('HEAD:')]
4565 elseif commit =~# '^\x\x\+$'
4566 let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4567 call map(parents, 's:Relative(v:val . ":")')
4571 if exists('parents') && len(parents) > 1
4573 let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4575 execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4576 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4580 call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4581 let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4582 for i in range(len(parents)-1, 1, -1)
4583 execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4584 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4588 call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4596 let arg = join(args, ' ')
4601 let file = s:Relative()
4604 let file = s:Relative(':0:')
4605 elseif arg =~# '^:\d$'
4607 let file = s:Relative(arg . ':')
4610 let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4612 return 'echoerr ' . string(v:exception)
4615 elseif exists('parents') && len(parents)
4616 let file = parents[-1]
4618 let file = s:Relative()
4619 elseif s:IsConflicted()
4620 let file = s:Relative(':1:')
4621 let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4624 let file = s:Relative(':0:')
4626 let spec = s:Generate(file)
4627 if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4628 let spec = FugitiveVimPath(spec . s:Relative('/'))
4631 let restore = s:diff_restore()
4632 let w:fugitive_diff_restore = restore
4633 if len(spec) && s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4634 let mods = s:Mods(mods, 'rightbelow')
4636 let mods = s:Mods(mods, 'leftabove')
4638 let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4639 if &diffopt =~# 'vertical'
4640 let diffopt = &diffopt
4641 set diffopt-=vertical
4643 execute mods 'diffsplit' s:fnameescape(spec)
4644 let &l:readonly = &l:readonly
4646 let w:fugitive_diff_restore = restore
4648 if getwinvar('#', '&diff')
4655 return 'echoerr ' . string(v:exception)
4657 if exists('diffopt')
4658 let &diffopt = diffopt
4663 " Section: :Gmove, :Gremove
4665 function! s:Move(force, rename, destination) abort
4668 if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
4669 return 'echoerr ' . string('fugitive: mv not supported for this buffer')
4671 if a:destination =~# '^\.\.\=\%(/\|$\)'
4672 let destination = simplify(getcwd() . '/' . a:destination)
4673 elseif a:destination =~# '^\a\+:\|^/'
4674 let destination = a:destination
4675 elseif a:destination =~# '^:/:\='
4676 let destination = s:Tree(dir) . substitute(a:destination, '^:/:\=', '', '')
4677 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4678 let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
4679 elseif a:destination =~# '^:(literal)'
4680 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4682 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4684 let destination = s:Tree(dir) . '/' . a:destination
4686 let destination = s:Slash(destination)
4690 let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
4692 let v:errmsg = 'fugitive: '.message
4693 return 'echoerr v:errmsg'
4695 if isdirectory(destination)
4696 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4698 let reload = '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4699 if empty(s:DirCommitFile(@%)[1])
4700 if isdirectory(destination)
4701 return 'keepalt edit '.s:fnameescape(destination) . reload
4703 return 'keepalt saveas! '.s:fnameescape(destination) . reload
4706 return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
4710 function! fugitive#RenameComplete(A,L,P) abort
4711 if a:A =~# '^[.:]\=/'
4712 return fugitive#CompletePath(a:A)
4714 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4715 return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4719 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, args) abort
4720 return s:Move(a:bang, 0, a:arg)
4723 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, args) abort
4724 return s:Move(a:bang, 1, a:arg)
4727 function! s:Remove(after, force) abort
4730 if len(@%) && s:DirCommitFile(@%)[1] ==# ''
4732 elseif s:DirCommitFile(@%)[1] ==# '0'
4733 let cmd = ['rm','--cached']
4735 return 'echoerr ' . string('fugitive: rm not supported for this buffer')
4738 let cmd += ['--force']
4740 let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')], dir)
4742 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4743 return 'echoerr '.string(v:errmsg)
4745 return a:after . (a:force ? '!' : ''). '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4749 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, args) abort
4750 return s:Remove('edit', a:bang)
4753 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, args) abort
4754 return s:Remove('bdelete', a:bang)
4759 function! s:Keywordprg() abort
4760 let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4761 if has('gui_running') && !has('win32')
4762 return s:UserCommand() . ' --no-pager' . args . ' log -1'
4764 return s:UserCommand() . args . ' show'
4768 function! s:linechars(pattern) abort
4769 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4770 if exists('*synconcealed') && &conceallevel > 1
4771 for col in range(1, chars)
4772 let chars -= synconcealed(line('.'), col)[0]
4778 function! s:BlameBufnr(...) abort
4779 let state = s:TempState(bufname(a:0 ? a:1 : ''))
4780 if get(state, 'filetype', '') ==# 'fugitiveblame'
4781 return get(state, 'bufnr', -1)
4787 function! s:BlameCommitFileLnum(...) abort
4788 let line = a:0 ? a:1 : getline('.')
4789 let state = a:0 ? a:2 : s:TempState()
4790 let commit = matchstr(line, '^\^\=\zs\x\+')
4791 if commit =~# '^0\+$'
4793 elseif has_key(state, 'blame_reverse_end')
4794 let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4796 let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4797 let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4798 if empty(path) && lnum
4799 let path = get(state, 'blame_file', '')
4801 return [commit, path, lnum]
4804 function! s:BlameLeave() abort
4805 let bufwinnr = bufwinnr(s:BlameBufnr())
4807 let bufnr = bufnr('')
4808 exe bufwinnr . 'wincmd w'
4809 return bufnr . 'bdelete'
4814 function! s:BlameQuit() abort
4815 let cmd = s:BlameLeave()
4818 elseif len(s:DirCommitFile(@%)[1])
4819 return cmd . '|Gedit'
4825 function! fugitive#BlameComplete(A, L, P) abort
4826 return s:CompleteSub('blame', a:A, a:L, a:P)
4829 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4831 let flags = copy(a:args)
4837 if a:line1 > 0 && a:count > 0 && a:range != 1
4838 call extend(ranges, ['-L', a:line1 . ',' . a:count])
4840 while i < len(flags)
4841 let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4842 if len(match) && len(match[2])
4843 call insert(flags, match[1])
4844 let flags[i+1] = '-' . match[2]
4848 if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4850 elseif arg ==# '--contents' && i + 1 < len(flags)
4851 call extend(commits, remove(flags, i, i+1))
4853 elseif arg ==# '-L' && i + 1 < len(flags)
4854 call extend(ranges, remove(flags, i, i+1))
4856 elseif arg =~# '^--contents='
4857 call add(commits, remove(flags, i))
4859 elseif arg =~# '^-L.'
4860 call add(ranges, remove(flags, i))
4862 elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4866 echo s:ChompError(['blame', arg])[0]
4871 if i + 1 < len(flags)
4872 call extend(files, remove(flags, i + 1, -1))
4874 call remove(flags, i)
4876 elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4877 if index(flags, '--') >= 0
4878 call add(commits, remove(flags, i))
4881 if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4882 call add(commits, remove(flags, i))
4886 let dcf = s:DirCommitFile(fugitive#Find(arg))
4887 if len(dcf[1]) && empty(dcf[2])
4888 call add(commits, remove(flags, i))
4893 call add(files, remove(flags, i))
4898 let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4899 if empty(commits) && len(files) > 1
4900 call add(commits, remove(files, 1))
4904 let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4905 call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4906 if a:count > 0 && empty(ranges)
4907 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4909 call extend(cmd, ranges)
4912 elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4913 let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4914 elseif empty(files) && !s:HasOpt(flags, '--reverse')
4915 let cmd += ['--contents', '-']
4917 let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4918 let tempname = tempname()
4919 let error = tempname . '.err'
4920 let temp = tempname . (raw ? '' : '.fugitiveblame')
4922 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4924 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4926 let l:shell_error = v:shell_error
4930 let lines = readfile(error)
4932 let lines = readfile(temp)
4934 for i in range(len(lines))
4935 if lines[i] =~# '^error: \|^fatal: '
4943 if i != len(lines) - 1
4949 let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4950 if s:HasOpt(flags, '--reverse')
4951 let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4953 if (a:line1 == 0 || a:range == 1) && a:count > 0
4954 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4955 return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4957 let temp = s:Resolve(temp)
4958 let s:temp_files[s:cpath(temp)] = temp_state
4959 if len(ranges + commits + files) || raw
4960 let mods = s:Mods(a:mods)
4962 exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4963 elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4964 exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4966 return mods . 'edit ' . s:fnameescape(temp)
4970 if a:mods =~# '\<tab\>'
4973 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4974 for winnr in range(winnr('$'),1,-1)
4975 if getwinvar(winnr, '&scrollbind')
4976 call setwinvar(winnr, '&scrollbind', 0)
4978 if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4979 call setwinvar(winnr, '&cursorbind', 0)
4981 if s:BlameBufnr(winbufnr(winnr)) > 0
4982 execute winbufnr(winnr).'bdelete'
4985 let bufnr = bufnr('')
4986 let temp_state.bufnr = bufnr
4987 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4988 if exists('+cursorbind')
4989 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4992 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4995 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4997 setlocal scrollbind nowrap nofoldenable
4998 if exists('+cursorbind')
5001 let top = line('w0') + &scrolloff
5002 let current = line('.')
5003 exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
5004 let w:fugitive_leave = restore
5008 if exists('+cursorbind')
5011 setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
5012 if exists('+relativenumber')
5013 setlocal norelativenumber
5015 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
5016 call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
5017 call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
5018 call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
5025 return 'echoerr ' . string(v:exception)
5029 function! s:BlameCommit(cmd, ...) abort
5030 let line = a:0 ? a:1 : getline('.')
5031 let state = a:0 ? a:2 : s:TempState()
5032 let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
5033 let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
5034 let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
5035 if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
5036 let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
5037 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5039 if commit =~# '^0*$'
5040 return 'echoerr ' . string('fugitive: no commit')
5042 if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
5043 let path = commit . ':' . path
5044 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5046 let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
5047 if cmd =~# '^echoerr'
5051 if a:cmd ==# 'pedit' || empty(path)
5054 if search('^diff .* b/\M'.escape(path,'\').'$','W')
5056 let head = line('.')
5057 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
5058 let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
5059 let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
5060 if lnum >= top && lnum <= top + len
5061 let offset = lnum - top
5069 while offset > 0 && line('.') < line('$')
5071 if getline('.') =~# '^[ ' . sigil . ']'
5084 function! s:BlameJump(suffix, ...) abort
5085 let suffix = a:suffix
5086 let [commit, path, lnum] = s:BlameCommitFileLnum()
5088 return 'echoerr ' . string('fugitive: could not determine filename for blame')
5090 if commit =~# '^0*$'
5094 let offset = line('.') - line('w0')
5095 let flags = get(s:TempState(), 'blame_flags', [])
5097 if s:HasOpt(flags, '--reverse')
5098 call remove(flags, '--reverse')
5100 call add(flags, '--reverse')
5103 let blame_bufnr = s:BlameBufnr()
5105 let bufnr = bufnr('')
5106 let winnr = bufwinnr(blame_bufnr)
5108 exe winnr.'wincmd w'
5110 execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
5116 if exists(':Gblame')
5117 let my_bufnr = bufnr('')
5119 let blame_args = flags + [commit . suffix, '--', path]
5120 let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
5122 let blame_args = flags
5123 let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
5125 if bufnr('') == my_bufnr
5130 let delta = line('.') - line('w0') - offset
5132 execute 'normal! '.delta."\<C-E>"
5134 execute 'normal! '.(-delta)."\<C-Y>"
5138 echo ':Gblame' s:fnameescape(blame_args)
5143 let s:hash_colors = {}
5145 function! fugitive#BlameSyntax() abort
5146 let conceal = has('conceal') ? ' conceal' : ''
5147 let config = fugitive#Config()
5148 let flags = get(s:TempState(), 'blame_flags', [])
5149 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
5150 syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5151 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5152 if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5153 syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5155 syn match FugitiveblameBoundary "^\^"
5157 syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5158 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5159 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5160 exec 'syn match FugitiveblameLineNumber "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5161 exec 'syn match FugitiveblameOriginalFile "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-name', '-f') ? '' : conceal)
5162 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5163 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5164 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
5165 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5166 hi def link FugitiveblameBoundary Keyword
5167 hi def link FugitiveblameHash Identifier
5168 hi def link FugitiveblameBoundaryIgnore Ignore
5169 hi def link FugitiveblameUncommitted Ignore
5170 hi def link FugitiveblameScoreDebug Debug
5171 hi def link FugitiveblameTime PreProc
5172 hi def link FugitiveblameLineNumber Number
5173 hi def link FugitiveblameOriginalFile String
5174 hi def link FugitiveblameOriginalLineNumber Float
5175 hi def link FugitiveblameShort FugitiveblameDelimiter
5176 hi def link FugitiveblameDelimiter Delimiter
5177 hi def link FugitiveblameNotCommittedYet Comment
5178 if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5182 for lnum in range(1, line('$'))
5183 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5184 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5188 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5189 \ && empty(get(s:hash_colors, hash))
5190 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5191 let color = csapprox#per_component#Approximate(r, g, b)
5192 if color == 16 && &background ==# 'dark'
5195 let s:hash_colors[hash] = ' ctermfg='.color
5197 let s:hash_colors[hash] = ''
5199 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5201 call s:BlameRehighlight()
5204 function! s:BlameRehighlight() abort
5205 for [hash, cterm] in items(s:hash_colors)
5206 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5207 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5209 exe 'hi link FugitiveblameHash'.hash.' Identifier'
5214 function! s:BlameFileType() abort
5216 setlocal foldmethod=manual
5218 let &l:keywordprg = s:Keywordprg()
5220 let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5221 if exists('+concealcursor')
5222 setlocal concealcursor=nc conceallevel=2
5223 let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5228 call s:Map('n', '<F1>', ':help :Gblame<CR>', '<silent>')
5229 call s:Map('n', 'g?', ':help :Gblame<CR>', '<silent>')
5230 if mapcheck('q', 'n') =~# '^$\|bdelete'
5231 call s:Map('n', 'q', ':exe <SID>BlameQuit()<Bar>echohl WarningMsg<Bar>echo ":Gblame q is deprecated in favor of gq"<Bar>echohl NONE<CR>', '<silent>')
5233 call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>')
5234 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5235 call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5236 call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5237 call s:Map('n', 'P', ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5238 call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5239 call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5240 call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5241 call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5242 call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5245 augroup fugitive_blame
5247 autocmd FileType fugitiveblame call s:BlameFileType()
5248 autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5249 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5254 let s:redirects = {}
5256 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, args) abort
5260 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5263 return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5265 return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5268 let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5269 let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5275 let rev = s:DirRev(@%)[1]
5278 let expanded = s:Relative()
5280 let expanded = s:Expand(rev)
5282 let cdir = FugitiveVimPath(fugitive#CommonDir(dir))
5283 for subdir in ['tags/', 'heads/', 'remotes/']
5284 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5285 let expanded = '.git/refs/' . subdir . expanded
5288 let full = fugitive#Find(expanded, dir)
5290 if full =~? '^fugitive:'
5291 let [pathdir, commit, path] = s:DirCommitFile(full)
5292 if commit =~# '^:\=\d$'
5296 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5297 let branch = matchstr(expanded, '^[^:]*')
5301 let path = path[1:-1]
5302 elseif empty(s:Tree(dir))
5303 let path = '.git/' . full[strlen(dir)+1:-1]
5306 let path = fugitive#Path(full, '/')[1:-1]
5307 if path =~# '^\.git/'
5309 elseif isdirectory(full) || empty(path)
5315 if type ==# 'tree' && !empty(path)
5316 let path = s:sub(path, '/\=$', '/')
5318 if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5319 let body = readfile(dir . '/' . path[5:-1])[0]
5320 if body =~# '^\x\{40,\}$'
5324 elseif body =~# '^ref: refs/'
5325 let path = '.git/' . matchstr(body,'ref: \zs.*')
5330 if path =~# '^\.git/refs/remotes/.'
5332 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5333 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5335 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5336 let path = '.git/refs/heads/'.merge
5338 elseif path =~# '^\.git/refs/heads/.'
5339 let branch = path[16:-1]
5340 elseif !exists('branch')
5341 let branch = FugitiveHead()
5344 let r = fugitive#Config('branch.'.branch.'.remote')
5345 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5346 if r ==# '.' && !empty(m)
5347 let r2 = fugitive#Config('branch.'.m.'.remote')
5350 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5356 if r ==# '.' || r ==# remote
5358 if path =~# '^\.git/refs/heads/.'
5359 let path = '.git/refs/heads/'.merge
5364 let line1 = a:count > 0 ? a:line1 : 0
5365 let line2 = a:count > 0 ? a:count : 0
5366 if empty(commit) && path !~# '^\.git/'
5367 if a:count < 0 && !empty(merge)
5372 let owner = s:Owner(@%)
5373 let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5377 if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5378 let blame_list = tempname()
5379 call writefile([commit, ''], blame_list, 'b')
5380 let blame_in = tempname()
5381 silent exe '%write' blame_in
5382 let [blame, exec_error] = s:LinesError(['-c', 'blame.coloring=none', 'blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path])
5384 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5385 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5386 let line1 = +matchstr(blame[0], blame_regex)
5387 let line2 = +matchstr(blame[-1], blame_regex)
5389 call s:throw("Can't browse to uncommitted change")
5396 let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5399 while commit =~# '^ref: ' && i < 10
5400 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5408 let raw = fugitive#RemoteUrl(remote)
5413 if raw =~# '^https\=://' && s:executable('curl')
5414 if !has_key(s:redirects, raw)
5415 let s:redirects[raw] = matchstr(system('curl -I ' .
5416 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5417 \ 'Location: \zs\S\+\ze/info/refs?')
5419 if len(s:redirects[raw])
5420 let raw = s:redirects[raw]
5426 \ 'repo': fugitive#repo(dir),
5428 \ 'revision': 'No longer provided',
5436 for Handler in get(g:, 'fugitive_browse_handlers', [])
5437 let url = call(Handler, [copy(opts)])
5444 call s:throw("No Gbrowse handler installed for '".raw."'")
5447 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5452 return 'echomsg '.string(url)
5453 elseif exists(':Browse') == 2
5454 return 'echomsg '.string(url).'|Browse '.url
5456 if !exists('g:loaded_netrw')
5457 runtime! autoload/netrw.vim
5459 if exists('*netrw#BrowseX')
5460 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5462 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5466 return 'echoerr ' . string(v:exception)
5470 " Section: Go to file
5472 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5473 function! fugitive#MapCfile(...) abort
5474 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5475 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5476 if !exists('g:fugitive_no_maps')
5477 call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5478 call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5479 call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5480 call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5481 call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5485 function! s:ContainingCommit() abort
5486 let commit = s:Owner(@%)
5487 return empty(commit) ? 'HEAD' : commit
5490 function! s:SquashArgument(...) abort
5491 if &filetype == 'fugitive'
5492 let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5493 elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5494 let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5496 let commit = s:Owner(@%)
5498 return len(commit) && a:0 ? printf(a:1, commit) : commit
5501 function! s:RebaseArgument() abort
5502 return s:SquashArgument(' %s^')
5505 function! s:NavigateUp(count) abort
5506 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5510 let rev = matchstr(rev, '.*\ze/.\+', '')
5511 elseif rev =~# '.:.'
5512 let rev = matchstr(rev, '^.[^:]*:')
5525 function! s:MapMotion(lhs, rhs) abort
5526 call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5527 call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5528 call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5531 function! fugitive#MapJumps(...) abort
5533 if get(b:, 'fugitive_type', '') ==# 'blob'
5534 let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5535 call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5536 call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5537 call s:Map('n', 'o', ':<C-U>0,2' . blame_map, '<silent>')
5538 call s:Map('n', 'p', ':<C-U>0,3' . blame_map, '<silent>')
5539 call s:Map('n', 'gO', ':<C-U>0,4' . blame_map, '<silent>')
5540 call s:Map('n', 'O', ':<C-U>0,5' . blame_map, '<silent>')
5542 call s:Map('n', 'D', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<Bar>redraw<Bar>echohl WarningMsg<Bar> echo ':Gstatus D is deprecated in favor of dd'<Bar>echohl NONE<CR>", '<silent>')
5543 call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5544 call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5545 call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5546 call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5547 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5550 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5551 call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5552 call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5553 call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5554 call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5555 call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5557 if !exists('g:fugitive_no_maps')
5558 if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5559 nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5561 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5563 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5565 call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5566 call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5567 call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5568 call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5569 call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5570 call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5571 call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5572 call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5573 call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5574 call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5575 call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5576 call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5577 call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5578 call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5579 call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5580 call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5582 call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5583 call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5584 call s:Map('n', '-', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>NavigateUp(v:count1))<Bar> if getline(1) =~# '^tree \x\{40,\}$' && empty(getline(2))<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>", '<silent>')
5585 call s:Map('n', 'P', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5586 call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5587 call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5588 call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5589 call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5590 call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5591 call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5592 call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5594 nnoremap <buffer> c<Space> :Git commit<Space>
5595 nnoremap <buffer> c<CR> :Git commit<CR>
5596 nnoremap <buffer> cv<Space> :Git commit -v<Space>
5597 nnoremap <buffer> cv<CR> :Git commit -v<CR>
5598 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
5599 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
5600 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
5601 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
5602 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
5603 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
5604 nnoremap <buffer> <silent> cRa :<C-U>Gcommit --reset-author --amend<CR>
5605 nnoremap <buffer> <silent> cRe :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5606 nnoremap <buffer> <silent> cRw :<C-U>Gcommit --reset-author --amend --only<CR>
5607 nnoremap <buffer> cf :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5608 nnoremap <buffer> cF :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5609 nnoremap <buffer> cs :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5610 nnoremap <buffer> cS :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5611 nnoremap <buffer> cA :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5612 nnoremap <buffer> <silent> c? :<C-U>help fugitive_c<CR>
5614 nnoremap <buffer> cr<Space> :Git revert<Space>
5615 nnoremap <buffer> cr<CR> :Git revert<CR>
5616 nnoremap <buffer> <silent> crc :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5617 nnoremap <buffer> <silent> crn :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5618 nnoremap <buffer> <silent> cr? :help fugitive_cr<CR>
5620 nnoremap <buffer> cm<Space> :Git merge<Space>
5621 nnoremap <buffer> cm<CR> :Git merge<CR>
5622 nnoremap <buffer> <silent> cm? :help fugitive_cm<CR>
5624 nnoremap <buffer> cz<Space> :Git stash<Space>
5625 nnoremap <buffer> cz<CR> :Git stash<CR>
5626 nnoremap <buffer> <silent> cza :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5627 nnoremap <buffer> <silent> czA :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5628 nnoremap <buffer> <silent> czp :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5629 nnoremap <buffer> <silent> czP :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5630 nnoremap <buffer> <silent> czv :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5631 nnoremap <buffer> <silent> czw :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5632 nnoremap <buffer> <silent> czz :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5633 nnoremap <buffer> <silent> cz? :<C-U>help fugitive_cz<CR>
5635 nnoremap <buffer> co<Space> :Git checkout<Space>
5636 nnoremap <buffer> co<CR> :Git checkout<CR>
5637 nnoremap <buffer> coo :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5638 nnoremap <buffer> co? :<C-U>help fugitive_co<CR>
5640 nnoremap <buffer> cb<Space> :Git branch<Space>
5641 nnoremap <buffer> cb<CR> :Git branch<CR>
5642 nnoremap <buffer> cb? :<C-U>help fugitive_cb<CR>
5644 nnoremap <buffer> r<Space> :Git rebase<Space>
5645 nnoremap <buffer> r<CR> :Git rebase<CR>
5646 nnoremap <buffer> <silent> ri :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5647 nnoremap <buffer> <silent> rf :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5648 nnoremap <buffer> <silent> ru :<C-U>Grebase --interactive @{upstream}<CR>
5649 nnoremap <buffer> <silent> rp :<C-U>Grebase --interactive @{push}<CR>
5650 nnoremap <buffer> <silent> rw :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5651 nnoremap <buffer> <silent> rm :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5652 nnoremap <buffer> <silent> rd :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5653 nnoremap <buffer> <silent> rk :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5654 nnoremap <buffer> <silent> rx :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5655 nnoremap <buffer> <silent> rr :<C-U>Grebase --continue<CR>
5656 nnoremap <buffer> <silent> rs :<C-U>Grebase --skip<CR>
5657 nnoremap <buffer> <silent> re :<C-U>Grebase --edit-todo<CR>
5658 nnoremap <buffer> <silent> ra :<C-U>Grebase --abort<CR>
5659 nnoremap <buffer> <silent> r? :<C-U>help fugitive_r<CR>
5661 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5662 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5663 call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
5664 call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
5668 function! s:StatusCfile(...) abort
5670 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5671 let info = s:StageInfo()
5672 let line = getline('.')
5673 if len(info.sigil) && len(info.section) && len(info.paths)
5674 if info.section ==# 'Unstaged' && info.sigil !=# '-'
5675 return [lead . info.relative[0], info.offset, 'normal!zv']
5676 elseif info.section ==# 'Staged' && info.sigil ==# '-'
5677 return ['@:' . info.relative[0], info.offset, 'normal!zv']
5679 return [':0:' . info.relative[0], info.offset, 'normal!zv']
5681 elseif len(info.paths)
5682 return [lead . info.relative[0]]
5683 elseif len(info.commit)
5684 return [info.commit]
5685 elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5686 return [matchstr(line, ' \zs.*')]
5692 function! fugitive#StatusCfile() abort
5693 let file = s:Generate(s:StatusCfile()[0])
5694 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5697 function! s:MessageCfile(...) abort
5699 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5700 if getline('.') =~# '^.\=\trenamed:.* -> '
5701 return lead . matchstr(getline('.'),' -> \zs.*')
5702 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5703 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5704 elseif getline('.') =~# '^.\=\t.'
5705 return lead . matchstr(getline('.'),'\t\zs.*')
5706 elseif getline('.') =~# ': needs merge$'
5707 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5708 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5710 elseif getline('.') =~# '^\%(. \)\=On branch '
5711 return 'refs/heads/'.getline('.')[12:]
5712 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5713 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5719 function! fugitive#MessageCfile() abort
5720 let file = s:Generate(s:MessageCfile())
5721 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5724 function! s:cfile() abort
5726 let myhash = s:DirRev(@%)[1]
5729 let myhash = fugitive#RevParse(myhash)
5734 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5735 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5738 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5740 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5741 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5743 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5744 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5746 return [treebase . s:sub(getline('.'),'/$','')]
5753 if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5754 let ref = matchstr(getline('.'),'\x\{40,\}')
5755 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5759 if getline('.') =~# '^ref: '
5760 let ref = strpart(getline('.'),5)
5762 elseif getline('.') =~# '^commit \x\{40,\}\>'
5763 let ref = matchstr(getline('.'),'\x\{40,\}')
5766 elseif getline('.') =~# '^parent \x\{40,\}\>'
5767 let ref = matchstr(getline('.'),'\x\{40,\}')
5768 let line = line('.')
5770 while getline(line) =~# '^parent '
5776 elseif getline('.') =~# '^tree \x\{40,\}$'
5777 let ref = matchstr(getline('.'),'\x\{40,\}')
5778 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5779 let ref = myhash.':'
5783 elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5784 let ref = matchstr(getline('.'),'\x\{40,\}')
5785 let type = matchstr(getline(line('.')+1),'type \zs.*')
5787 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5788 let ref = s:DirRev(@%)[1]
5790 elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5791 let ref = matchstr(getline('.'),'\x\{40,\}')
5792 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5794 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5795 let ref = getline('.')[4:]
5797 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5798 let type = getline('.')[0]
5799 let lnum = line('.') - 1
5801 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5802 if getline(lnum) =~# '^[ '.type.']'
5807 let offset += matchstr(getline(lnum), type.'\zs\d\+')
5808 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5809 let dcmds = [offset, 'normal!zv']
5811 elseif getline('.') =~# '^rename from '
5812 let ref = 'a/'.getline('.')[12:]
5813 elseif getline('.') =~# '^rename to '
5814 let ref = 'b/'.getline('.')[10:]
5816 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5817 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5818 let offset = matchstr(getline('.'), '+\zs\d\+')
5820 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5821 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5822 let dcmd = 'Gdiffsplit! +'.offset
5824 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5825 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5826 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5827 let dcmd = 'Gdiffsplit!'
5829 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5830 let line = getline(line('.')-1)
5831 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5832 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5833 let dcmd = 'Gdiffsplit!'
5835 elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5836 let ref = getline('.')
5838 elseif expand('<cword>') =~# '^\x\{7,\}\>'
5839 return [expand('<cword>')]
5854 let prefixes.a = myhash.'^:'
5855 let prefixes.b = myhash.':'
5857 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5859 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5862 if ref ==# '/dev/null'
5864 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5868 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5870 return [ref] + dcmds
5878 function! s:GF(mode) abort
5880 let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5882 return 'echoerr ' . string(v:exception)
5885 return 'G' . a:mode .
5886 \ ' +' . escape(results[1], ' ') . ' ' .
5887 \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5888 elseif len(results) && len(results[0])
5889 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5895 function! fugitive#Cfile() abort
5897 let results = s:cfile()
5899 let cfile = expand('<cfile>')
5900 if &includeexpr =~# '\<v:fname\>'
5901 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5904 elseif len(results) > 1
5905 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5907 return pre . s:fnameescape(s:Generate(results[0]))
5910 " Section: Statusline
5912 function! fugitive#Statusline(...) abort
5913 let dir = s:Dir(bufnr(''))
5918 let commit = s:DirCommitFile(@%)[1]
5920 let status .= ':' . commit[0:6]
5922 let status .= '('.FugitiveHead(7, dir).')'
5923 return '[Git'.status.']'
5926 function! fugitive#statusline(...) abort
5927 return fugitive#Statusline()
5930 function! fugitive#head(...) abort
5935 return fugitive#Head(a:0 ? a:1 : 0)
5940 function! fugitive#Foldtext() abort
5941 if &foldmethod !=# 'syntax'
5945 let line_foldstart = getline(v:foldstart)
5946 if line_foldstart =~# '^diff '
5947 let [add, remove] = [-1, -1]
5949 for lnum in range(v:foldstart, v:foldend)
5950 let line = getline(lnum)
5951 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5952 let filename = line[6:-1]
5956 elseif line =~# '^-'
5958 elseif line =~# '^Binary '
5963 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5966 let filename = line_foldstart[5:-1]
5969 return 'Binary: '.filename
5971 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5973 elseif line_foldstart =~# '^# .*:$'
5974 let lines = getline(v:foldstart, v:foldend)
5975 call filter(lines, 'v:val =~# "^#\t"')
5976 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5977 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5978 return line_foldstart.' '.join(lines, ', ')
5983 function! fugitive#foldtext() abort
5984 return fugitive#Foldtext()
5987 augroup fugitive_folding
5989 autocmd User Fugitive
5990 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5991 \ set foldtext=fugitive#Foldtext() |
5995 " Section: Initialization
5997 function! fugitive#Init() abort
5998 if exists('#User#FugitiveBoot')
5999 exe s:DoAutocmd('User FugitiveBoot')
6002 if &tags !~# '\.git' && @% !~# '\.git' && !exists('s:tags_warning')
6003 let actualdir = fugitive#Find('.git/', dir)
6004 if filereadable(actualdir . 'tags')
6005 let s:tags_warning = 1
6007 echo "Fugitive .git/tags support removed in favor of `:set tags^=./.git/tags;`"
6011 exe s:DoAutocmd('User Fugitive')
6014 function! fugitive#is_git_dir(path) abort
6015 return FugitiveIsGitDir(a:path)
6018 function! fugitive#extract_git_dir(path) abort
6019 return FugitiveExtractGitDir(a:path)
6022 function! fugitive#detect(path) abort
6023 return FugitiveDetect(a:path)