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>'), '<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 !~# '^-'
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 function! s:Command(command, line1, line2, range, bang, mods, arg, args) abort
597 if a:command =~# '^\l[[:alnum:]-]\+$'
598 return fugitive#Command(a:line1, a:line2, a:range, a:bang, s:Mods(a:mods), a:command . ' ' . a:arg)
600 return s:{a:command}Command(a:line1, a:line2, a:range, a:line2, a:bang, s:Mods(a:mods), '', a:arg, a:args)
602 return 'echoerr ' . string(v:exception)
607 function! s:command(definition, ...) abort
608 let def = a:definition
609 if !has('patch-7.4.542')
610 let def = substitute(def, '-addr=\S\+ ', '', '')
612 if !has('patch-8.1.560')
613 let def = substitute(def, '-addr=other ', '', '')
616 call add(s:commands, def . ' execute s:Command(' . string(a:1) . ", <line1>, <count>, +'<range>', <bang>0, '<mods>', <q-args>, [<f-args>])")
618 call add(s:commands, def)
622 function! s:define_commands() abort
623 for command in s:commands
624 exe 'command! -buffer '.command
628 let s:repo_prototype = {}
631 function! fugitive#repo(...) abort
632 let dir = a:0 ? s:Dir(a:1) : (len(s:Dir()) ? s:Dir() : FugitiveExtractGitDir(expand('%:p')))
634 if has_key(s:repos, dir)
635 let repo = get(s:repos, dir)
637 let repo = {'git_dir': dir}
638 let s:repos[dir] = repo
640 return extend(repo, s:repo_prototype, 'keep')
642 call s:throw('not a Git repository')
645 function! s:repo_dir(...) dict abort
646 return join([self.git_dir]+a:000,'/')
649 function! s:repo_tree(...) dict abort
650 let dir = s:Tree(self.git_dir)
652 call s:throw('no work tree')
654 return join([dir]+a:000,'/')
658 function! s:repo_bare() dict abort
659 if self.dir() =~# '/\.git$'
662 return s:Tree(self.git_dir) ==# ''
666 function! s:repo_find(object) dict abort
667 return fugitive#Find(a:object, self.git_dir)
670 function! s:repo_translate(rev) dict abort
671 return s:Slash(fugitive#Find(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
674 function! s:repo_head(...) dict abort
675 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
678 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
680 function! s:repo_prepare(...) dict abort
681 return call('fugitive#Prepare', [self.git_dir] + a:000)
684 function! s:repo_git_command(...) dict abort
685 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
686 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
689 function! s:repo_git_chomp(...) dict abort
690 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
691 let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
692 return s:sub(system(output), '\n$', '')
695 function! s:repo_git_chomp_in_tree(...) dict abort
696 let cdback = s:Cd(self.tree())
698 return call(self.git_chomp, a:000, self)
704 function! s:repo_rev_parse(rev) dict abort
705 return fugitive#RevParse(a:rev, self.git_dir)
708 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
710 function! s:repo_superglob(base) dict abort
711 return map(fugitive#CompleteObject(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
714 call s:add_methods('repo',['superglob'])
716 function! s:repo_config(name) dict abort
717 return fugitive#Config(a:name, self.git_dir)
720 function! s:repo_user() dict abort
721 let username = self.config('user.name')
722 let useremail = self.config('user.email')
723 return username.' <'.useremail.'>'
726 call s:add_methods('repo',['config', 'user'])
730 function! s:DirCommitFile(path) abort
731 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40,\}\|[0-3]\)\(/.*\)\=$')
738 function! s:DirRev(url) abort
739 let [dir, commit, file] = s:DirCommitFile(a:url)
740 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
743 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
744 function! s:MergeHead(...) abort
745 let dir = fugitive#Find('.git/', a:0 ? a:1 : s:Dir())
746 for head in s:merge_heads
747 if filereadable(dir . head)
754 function! s:Owner(path, ...) abort
755 let dir = a:0 ? a:1 : s:Dir()
759 let actualdir = fugitive#Find('.git/', dir)
760 let [pdir, commit, file] = s:DirCommitFile(a:path)
761 if s:cpath(dir, pdir)
762 if commit =~# '^\x\{40,\}$'
764 elseif commit ==# '2'
766 elseif commit ==# '0'
769 let merge_head = s:MergeHead()
774 return merge_head . '^{}'
775 elseif commit ==# '1'
776 return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
779 let path = fnamemodify(a:path, ':p')
780 if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
781 return strpart(path, len(actualdir))
783 let refs = fugitive#Find('.git/refs', dir)
784 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
785 return strpart(path, len(refs) - 4)
790 function! fugitive#Real(url) abort
794 let [dir, commit, file] = s:DirCommitFile(a:url)
796 let tree = s:Tree(dir)
797 return FugitiveVimPath((len(tree) ? tree : dir) . file)
799 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
800 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
801 let url = {pre}Real(a:url)
803 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
805 return FugitiveVimPath(empty(url) ? a:url : url)
808 function! fugitive#Path(url, ...) abort
812 let dir = a:0 > 1 ? a:2 : s:Dir()
813 let tree = s:Tree(dir)
815 return fugitive#Real(a:url)
817 let path = s:Slash(fugitive#Real(a:url))
820 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
821 if s:cpath(cwd . '/', path[0 : len(cwd)])
822 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
825 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
827 let cwd = fnamemodify(cwd, ':h')
830 return a:1[0:-2] . path
833 let temp_state = s:TempState(url)
834 if has_key(temp_state, 'bufnr')
835 let url = bufname(temp_state.bufnr)
837 let url = s:Slash(fnamemodify(url, ':p'))
838 if url =~# '/$' && s:Slash(a:url) !~# '/$'
841 let [argdir, commit, file] = s:DirCommitFile(a:url)
842 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
844 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
845 let file = '/.git'.url[strlen(dir) : -1]
846 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
847 let file = url[len(tree) : -1]
848 elseif s:cpath(url) ==# s:cpath(tree)
851 if empty(file) && a:1 =~# '^$\|^[.:]/$'
852 return FugitiveGitPath(fugitive#Real(a:url))
854 return substitute(file, '^/', a:1, '')
857 function! s:Relative(...) abort
858 return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
861 function! fugitive#Find(object, ...) abort
862 if type(a:object) == type(0)
863 let name = bufname(a:object)
864 return FugitiveVimPath(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
865 elseif a:object =~# '^[~$]'
866 let prefix = matchstr(a:object, '^[~$]\i*')
867 let owner = expand(prefix)
868 return FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
869 elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
870 return FugitiveVimPath(a:object)
871 elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
872 return FugitiveVimPath(simplify(getcwd() . '/' . a:object))
874 let dir = a:0 ? a:1 : s:Dir()
876 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
877 let dir = FugitiveExtractGitDir(file)
879 return fnamemodify(FugitiveVimPath(len(file) ? file : a:object), ':p')
882 let rev = s:Slash(a:object)
883 let tree = s:Tree(dir)
884 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
886 let f = len(tree) ? tree . '/.git' : dir
887 elseif rev =~# '^\.git/'
888 let f = substitute(rev, '^\.git', '', '')
889 let cdir = fugitive#CommonDir(dir)
890 if f =~# '^/\.\./\.\.\%(/\|$\)'
891 let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
892 elseif f =~# '^/\.\.\%(/\|$\)'
893 let f = base . f[3:-1]
894 elseif cdir !=# dir && (
895 \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
896 \ f !~# '^/\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
897 \ getftime(FugitiveVimPath(dir . f)) < 0 && getftime(FugitiveVimPath(cdir . f)) >= 0)
898 let f = simplify(cdir . f)
900 let f = simplify(dir . f)
904 elseif rev =~# '^\.\%(/\|$\)'
905 let f = base . rev[1:-1]
906 elseif rev =~# '^::\%(/\|\a\+\:\)'
908 elseif rev =~# '^::\.\.\=\%(/\|$\)'
909 let f = simplify(getcwd() . '/' . rev[2:-1])
911 let f = base . '/' . rev[2:-1]
912 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
913 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
914 if s:cpath(base . '/', (f . '/')[0 : len(base)])
915 let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
917 let altdir = FugitiveExtractGitDir(f)
918 if len(altdir) && !s:cpath(dir, altdir)
919 return fugitive#Find(a:object, altdir)
922 elseif rev =~# '^:[0-3]:'
923 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
925 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
926 let f = fnamemodify($GIT_INDEX_FILE, ':p')
928 let f = fugitive#Find('.git/index', dir)
930 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
931 let f = matchstr(rev, ')\zs.*')
932 if f=~# '^\.\.\=\%(/\|$\)'
933 let f = simplify(getcwd() . '/' . f)
934 elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
935 let f = base . '/' . f
937 elseif rev =~# '^:/\@!'
938 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
941 let commit = substitute(matchstr(rev, '^[^:.-][^:]*\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
942 let file = substitute(matchstr(rev, '^[^:.-][^:]*\zs:.*'), '^:', '/', '')
943 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
944 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
945 if s:cpath(base . '/', (file . '/')[0 : len(base)])
946 let file = '/' . strpart(file, len(base) + 1)
948 let altdir = FugitiveExtractGitDir(file)
949 if len(altdir) && !s:cpath(dir, altdir)
950 return fugitive#Find(a:object, altdir)
955 let commits = split(commit, '\.\.\.-\@!', 1)
957 call map(commits, 'empty(v:val) || v:val ==# "@" ? "HEAD" : v:val')
958 let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
960 if commit !~# '^[0-9a-f]\{40,\}$'
961 let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit, '--']), '\<[0-9a-f]\{40,\}\>')
964 let f = 'fugitive://' . dir . '//' . commit . file
966 let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
970 return FugitiveVimPath(f)
973 function! s:Generate(rev, ...) abort
974 return fugitive#Find(a:rev, a:0 ? a:1 : s:Dir())
977 function! s:DotRelative(path, ...) abort
978 let cwd = a:0 ? a:1 : getcwd()
979 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
980 if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
981 return '.' . strpart(path, len(cwd))
986 function! fugitive#Object(...) abort
987 let dir = a:0 > 1 ? a:2 : s:Dir()
988 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
989 if s:cpath(dir) !=# s:cpath(fdir)
992 let tree = s:Tree(dir)
993 let full = a:0 ? a:1 : @%
994 let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
995 if empty(rev) && empty(tree)
996 return FugitiveGitPath(full)
998 let rev = fugitive#Path(full, './', dir)
999 if rev =~# '^\./.git\%(/\|$\)'
1000 return FugitiveGitPath(full)
1003 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
1006 return FugitiveGitPath(tree . rev[1:-1])
1010 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
1011 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
1012 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
1014 function! s:BufName(var) abort
1016 return bufname(get(s:TempState(), 'bufnr', ''))
1017 elseif a:var =~# '^#\d*$'
1018 let nr = get(s:TempState(bufname(+a:var[1:-1])), 'bufnr', '')
1019 return bufname(nr ? nr : +a:var[1:-1])
1021 return expand(a:var)
1025 function! s:ExpandVarLegacy(str) abort
1026 if get(g:, 'fugitive_legacy_quoting', 1)
1027 return substitute(a:str, '\\\ze[%#!]', '', 'g')
1033 function! s:ExpandVar(other, var, flags, esc, ...) abort
1034 let cwd = a:0 ? a:1 : getcwd()
1036 return a:other[1:-1]
1037 elseif a:other =~# '^'''
1038 return s:ExpandVarLegacy(substitute(a:other[1:-2], "''", "'", "g"))
1039 elseif a:other =~# '^"'
1040 return s:ExpandVarLegacy(substitute(a:other[1:-2], '""', '"', "g"))
1041 elseif a:other =~# '^!'
1042 let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
1043 let owner = s:Owner(buffer)
1044 return len(owner) ? owner : '@'
1047 let file = s:DotRelative(fugitive#Real(s:BufName(a:var)), cwd)
1049 let flag = matchstr(flags, s:flag)
1050 let flags = strpart(flags, len(flag))
1052 let file = s:DotRelative(file, cwd)
1054 let file = fnamemodify(file, flag)
1057 let file = s:Slash(file)
1058 return (len(a:esc) ? shellescape(file) : file)
1061 function! s:Expand(rev, ...) abort
1062 if a:rev =~# '^:[0-3]$'
1063 let file = len(expand('%')) ? a:rev . ':%' : '%'
1064 elseif a:rev ==# '>'
1066 elseif a:rev =~# '^>[~^]'
1067 let file = len(expand('%')) ? '!' . a:rev[1:-1] . ':%' : '%'
1068 elseif a:rev =~# '^>[> ]\@!'
1069 let file = len(expand('%')) ? a:rev[1:-1] . ':%' : '%'
1073 return substitute(file,
1074 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1075 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd())', 'g')
1078 function! fugitive#Expand(object) abort
1079 return substitute(a:object,
1080 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1081 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
1084 function! s:ExpandSplit(string, ...) abort
1086 let string = a:string
1087 let handle_bar = a:0 && a:1
1088 let dquote = handle_bar ? '"\%([^"]\|""\|\\"\)*"\|' : ''
1089 let cwd = a:0 > 1 ? a:2 : getcwd()
1090 while string =~# '\S'
1091 if handle_bar && string =~# '^\s*|'
1092 return [list, substitute(string, '^\s*', '', '')]
1094 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] ' . (handle_bar ? '|' : '') . ']\)\+')
1095 let string = strpart(string, len(arg))
1096 let arg = substitute(arg, '^\s\+', '', '')
1097 if !exists('seen_separator')
1098 let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
1099 \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
1101 let arg = substitute(arg,
1102 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1103 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
1106 let seen_separator = 1
1109 return handle_bar ? [list, ''] : list
1112 function! s:SplitExpand(string, ...) abort
1113 return s:ExpandSplit(a:string, 0, a:0 ? a:1 : getcwd())
1116 function! s:SplitExpandChain(string, ...) abort
1117 return s:ExpandSplit(a:string, 1, a:0 ? a:1 : getcwd())
1122 function! s:TreeInfo(dir, commit) abort
1123 if a:commit =~# '^:\=[0-3]$'
1124 let index = get(s:indexes, a:dir, [])
1125 let newftime = getftime(fugitive#Find('.git/index', a:dir))
1126 if get(index, 0, -1) < newftime
1127 let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
1128 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
1133 let [info, filename] = split(line, "\t")
1134 let [mode, sha, stage] = split(info, '\s\+')
1135 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
1136 while filename =~# '/'
1137 let filename = substitute(filename, '/[^/]*$', '', '')
1138 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
1142 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
1143 elseif a:commit =~# '^\x\{40,\}$'
1144 if !has_key(s:trees, a:dir)
1145 let s:trees[a:dir] = {}
1147 if !has_key(s:trees[a:dir], a:commit)
1148 let [ftime, exec_error] = s:ChompError([a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
1150 let s:trees[a:dir][a:commit] = [{}, -1]
1151 return s:trees[a:dir][a:commit]
1153 let s:trees[a:dir][a:commit] = [{}, +ftime]
1154 let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
1156 return s:trees[a:dir][a:commit]
1159 let [info, filename] = split(line, "\t")
1160 let [mode, type, sha, size] = split(info, '\s\+')
1161 let s:trees[a:dir][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
1164 return s:trees[a:dir][a:commit]
1169 function! s:PathInfo(url) abort
1170 let [dir, commit, file] = s:DirCommitFile(a:url)
1171 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
1172 return [-1, '000000', '', '', -1]
1174 let path = substitute(file[1:-1], '/*$', '', '')
1175 let [tree, ftime] = s:TreeInfo(dir, commit)
1176 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
1177 if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
1178 return [-1, '000000', '', '', -1]
1184 function! fugitive#simplify(url) abort
1185 let [dir, commit, file] = s:DirCommitFile(a:url)
1189 if file =~# '/\.\.\%(/\|$\)'
1190 let tree = s:Tree(dir)
1192 let path = simplify(tree . file)
1193 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
1194 return FugitiveVimPath(path)
1198 return FugitiveVimPath('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
1201 function! fugitive#resolve(url) abort
1202 let url = fugitive#simplify(a:url)
1203 if url =~? '^fugitive:'
1210 function! fugitive#getftime(url) abort
1211 return s:PathInfo(a:url)[0]
1214 function! fugitive#getfsize(url) abort
1215 let entry = s:PathInfo(a:url)
1216 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
1217 let dir = s:DirCommitFile(a:url)[0]
1218 let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
1223 function! fugitive#getftype(url) abort
1224 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
1227 function! fugitive#filereadable(url) abort
1228 return s:PathInfo(a:url)[2] ==# 'blob'
1231 function! fugitive#filewritable(url) abort
1232 let [dir, commit, file] = s:DirCommitFile(a:url)
1233 if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
1236 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
1239 function! fugitive#isdirectory(url) abort
1240 return s:PathInfo(a:url)[2] ==# 'tree'
1243 function! fugitive#getfperm(url) abort
1244 let [dir, commit, file] = s:DirCommitFile(a:url)
1245 let perm = getfperm(dir)
1246 let fperm = s:PathInfo(a:url)[1]
1247 if fperm ==# '040000'
1248 let fperm = '000755'
1251 let perm = tr(perm, 'x', '-')
1253 if fperm !~# '[45]$'
1254 let perm = tr(perm, 'rw', '--')
1256 if commit !~# '^\d$'
1257 let perm = tr(perm, 'w', '-')
1259 return perm ==# '---------' ? '' : perm
1262 function! fugitive#setfperm(url, perm) abort
1263 let [dir, commit, file] = s:DirCommitFile(a:url)
1264 let entry = s:PathInfo(a:url)
1265 let perm = fugitive#getfperm(a:url)
1266 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
1267 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
1270 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1271 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])[1]
1272 return exec_error ? -1 : 0
1275 function! s:TempCmd(out, cmd) abort
1278 let cmd = (type(a:cmd) == type([]) ? fugitive#Prepare(a:cmd) : a:cmd)
1279 let redir = ' > ' . a:out
1281 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
1282 return s:SystemError('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
1283 elseif &shell =~# 'fish'
1284 return s:SystemError(' begin;' . prefix . cmd . redir . ';end ')
1286 return s:SystemError(' (' . prefix . cmd . redir . ') ')
1291 if !exists('s:blobdirs')
1294 function! s:BlobTemp(url) abort
1295 let [dir, commit, file] = s:DirCommitFile(a:url)
1299 if !has_key(s:blobdirs, dir)
1300 let s:blobdirs[dir] = tempname()
1302 let tempfile = s:blobdirs[dir] . '/' . commit . file
1303 let tempparent = fnamemodify(tempfile, ':h')
1304 if !isdirectory(tempparent)
1305 call mkdir(tempparent, 'p')
1307 if commit =~# '^\d$' || !filereadable(tempfile)
1308 let rev = s:DirRev(a:url)[1]
1309 let exec_error = s:TempCmd(tempfile, [dir, 'cat-file', 'blob', rev])[1]
1311 call delete(tempfile)
1315 return s:Resolve(tempfile)
1318 function! fugitive#readfile(url, ...) abort
1319 let entry = s:PathInfo(a:url)
1320 if entry[2] !=# 'blob'
1323 let temp = s:BlobTemp(a:url)
1327 return call('readfile', [temp] + a:000)
1330 function! fugitive#writefile(lines, url, ...) abort
1331 let url = type(a:url) ==# type('') ? a:url : ''
1332 let [dir, commit, file] = s:DirCommitFile(url)
1333 let entry = s:PathInfo(url)
1334 if commit =~# '^\d$' && entry[2] !=# 'tree'
1335 let temp = tempname()
1336 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1337 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1339 call call('writefile', [a:lines, temp] + a:000)
1340 let [hash, exec_error] = s:ChompError([dir, 'hash-object', '-w', temp])
1341 let mode = len(entry[1]) ? entry[1] : '100644'
1342 if !exec_error && hash =~# '^\x\{40,\}$'
1343 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1344 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])[1]
1350 return call('writefile', [a:lines, a:url] + a:000)
1354 \ '/**/': '/\%([^./][^/]*/\)*',
1355 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1356 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1358 \ '/*': '/[^/.][^/]*',
1361 function! fugitive#glob(url, ...) abort
1362 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1363 let append = matchstr(glob, '/*$')
1364 let glob = substitute(glob, '/*$', '', '')
1365 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1367 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1368 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
1371 let files = items(s:TreeInfo(dir, commit)[0])
1373 call filter(files, 'v:val[1][2] ==# "tree"')
1375 call map(files, 'v:val[0]')
1376 call filter(files, 'v:val =~# pattern')
1377 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1379 call map(files, 'FugitiveVimPath(prepend . v:val . append)')
1380 call extend(results, files)
1385 return join(results, "\n")
1389 function! fugitive#delete(url, ...) abort
1390 let [dir, commit, file] = s:DirCommitFile(a:url)
1391 if a:0 && len(a:1) || commit !~# '^\d$'
1394 let entry = s:PathInfo(a:url)
1395 if entry[2] !=# 'blob'
1398 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1399 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])[1]
1400 return exec_error ? -1 : 0
1403 " Section: Buffer Object
1405 let s:buffer_prototype = {}
1407 function! fugitive#buffer(...) abort
1408 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1409 call extend(buffer, s:buffer_prototype, 'keep')
1413 function! s:buffer_repo() dict abort
1414 return fugitive#repo(self['#'])
1417 function! s:buffer_type(...) dict abort
1418 return 'see b:fugitive_type'
1421 call s:add_methods('buffer', ['repo', 'type'])
1423 " Section: Completion
1425 function! s:FilterEscape(items, ...) abort
1426 let items = copy(a:items)
1427 if a:0 && type(a:1) == type('')
1428 call filter(items, 'strpart(v:val, 0, strlen(a:1)) ==# a:1')
1430 return map(items, 's:fnameescape(v:val)')
1433 function! s:GlobComplete(lead, pattern) abort
1436 elseif v:version >= 704
1437 let results = glob(a:lead . a:pattern, 0, 1)
1439 let results = split(glob(a:lead . a:pattern), "\n")
1441 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1442 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1446 function! fugitive#CompletePath(base, ...) abort
1447 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1448 let tree = s:Tree(dir) . '/'
1449 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1450 let base = substitute(a:base, strip, '', '')
1451 if base =~# '^\.git/'
1452 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1453 let matches = s:GlobComplete(dir . '/', pattern)
1454 let cdir = fugitive#CommonDir(dir)
1455 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1456 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1458 call s:Uniq(matches)
1459 call map(matches, "'.git/' . v:val")
1460 elseif base =~# '^\~/'
1461 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1462 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1463 let matches = s:GlobComplete('', base . '*')
1464 elseif len(tree) > 1
1465 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1469 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1473 function! fugitive#PathComplete(...) abort
1474 return call('fugitive#CompletePath', a:000)
1477 function! s:CompleteHeads(dir) abort
1478 let dir = fugitive#Find('.git/', a:dir)
1479 return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
1480 \ sort(s:LinesError('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')[0])
1483 function! fugitive#CompleteObject(base, ...) abort
1484 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1486 let tree = s:Tree(dir) . '/'
1488 if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1489 let subdir = strpart(cwd, len(tree)) . '/'
1492 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1494 if a:base =~# '^refs/'
1495 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1496 elseif a:base !~# '^\.\=/\|^:('
1497 let heads = s:CompleteHeads(dir)
1498 if filereadable(fugitive#Find('.git/refs/stash', dir))
1499 let heads += ["stash"]
1500 let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
1502 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1503 let results += heads
1505 call map(results, 's:fnameescape(v:val)')
1507 let results += a:0 == 1 ? fugitive#CompletePath(a:base, dir) : fugitive#CompletePath(a:base)
1511 elseif a:base =~# '^:'
1512 let entries = s:LinesError(['ls-files','--stage'], dir)[0]
1513 if a:base =~# ':\./'
1514 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1516 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1517 if a:base !~# '^:[0-3]\%(:\|$\)'
1518 call filter(entries,'v:val[1] == "0"')
1519 call map(entries,'v:val[2:-1]')
1523 let tree = matchstr(a:base, '.*[:/]')
1524 let entries = s:LinesError(['ls-tree', substitute(tree, ':\zs\./', '\=subdir', '')], dir)[0]
1525 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1526 call map(entries,'tree.s:sub(v:val,".*\t","")')
1529 return s:FilterEscape(entries, a:base)
1532 function! s:CompleteSub(subcommand, A, L, P, ...) abort
1533 let pre = strpart(a:L, 0, a:P)
1535 return fugitive#CompletePath(a:A)
1536 elseif a:A =~# '^-' || a:A is# 0
1537 return s:FilterEscape(split(s:ChompDefault('', a:subcommand, '--git-completion-helper'), ' '), a:A)
1539 return fugitive#CompleteObject(a:A, s:Dir())
1540 elseif type(a:1) == type(function('tr'))
1541 return call(a:1, [a:A, a:L, a:P])
1543 return s:FilterEscape(a:1, a:A)
1547 function! s:CompleteRevision(A, L, P, ...) abort
1548 return s:FilterEscape(s:CompleteHeads(s:Dir()), a:A)
1551 function! s:CompleteRemote(A, L, P) abort
1552 let remote = matchstr(a:L, '\u\w*[! ] *\zs\S\+\ze ')
1554 let matches = s:LinesError('ls-remote', remote)[0]
1555 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1556 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1558 let matches = s:LinesError('remote')[0]
1560 return s:FilterEscape(matches, a:A)
1563 " Section: Buffer auto-commands
1565 function! s:ReplaceCmd(cmd) abort
1566 let temp = tempname()
1567 let [err, exec_error] = s:TempCmd(temp, a:cmd)
1569 call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1571 let temp = s:Resolve(temp)
1572 let fn = expand('%:p')
1573 silent exe 'keepalt file '.temp
1574 let modelines = &modelines
1577 silent keepjumps noautocmd edit!
1579 let &modelines = modelines
1581 silent exe 'keepalt file '.s:fnameescape(fn)
1582 catch /^Vim\%((\a\+)\)\=:E302:/
1585 if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1586 silent execute 'bwipeout '.bufnr('$')
1591 function! s:QueryLog(refspec) abort
1592 let lines = s:LinesError(['log', '-n', '256', '--format=%h%x09%s', a:refspec, '--'])[0]
1593 call map(lines, 'split(v:val, "\t")')
1594 call map(lines, '{"type": "Log", "commit": v:val[0], "subject": v:val[-1]}')
1598 function! s:FormatLog(dict) abort
1599 return a:dict.commit . ' ' . a:dict.subject
1602 function! s:FormatRebase(dict) abort
1603 return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
1606 function! s:FormatFile(dict) abort
1607 return a:dict.status . ' ' . a:dict.filename
1610 function! s:Format(val) abort
1611 if type(a:val) == type({})
1612 return s:Format{a:val.type}(a:val)
1613 elseif type(a:val) == type([])
1614 return map(copy(a:val), 's:Format(v:val)')
1620 function! s:AddHeader(key, value) abort
1625 while !empty(getline(before))
1628 call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
1629 if before == 1 && line('$') == 2
1630 silent keepjumps 2delete _
1634 function! s:AddSection(label, lines, ...) abort
1635 let note = a:0 ? a:1 : ''
1636 if empty(a:lines) && empty(note)
1639 call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
1642 function! fugitive#BufReadStatus() abort
1643 let amatch = s:Slash(expand('%:p'))
1644 let b:fugitive_type = 'index'
1645 unlet! b:fugitive_reltime
1647 silent doautocmd BufReadPre
1648 let cmd = [fnamemodify(amatch, ':h')]
1649 setlocal noro ma nomodeline buftype=nowrite
1650 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : fugitive#Find('.git/index'), ':p')) !=# s:cpath(amatch)
1651 let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1653 let cmd += ['status', '--porcelain', '-bz']
1654 let [output, message, exec_error] = s:NullError(cmd)
1656 throw 'fugitive: ' . message
1659 let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
1661 if head =~# '\.\.\.'
1662 let [head, pull] = split(head, '\.\.\.')
1664 elseif head ==# 'HEAD' || empty(head)
1665 let head = FugitiveHead(11)
1671 let b:fugitive_files = {'Staged': {}, 'Unstaged': {}}
1672 let [staged, unstaged, untracked] = [[], [], []]
1674 while i < len(output)
1675 let line = output[i]
1676 let file = line[3:-1]
1682 if line[0:1] =~# '[RC]'
1683 let files = output[i] . ' -> ' . file
1686 if line[0] !~# '[ ?!#]'
1687 call add(staged, {'type': 'File', 'status': line[0], 'filename': files})
1689 if line[0:1] ==# '??'
1690 call add(untracked, {'type': 'File', 'status': line[1], 'filename': files})
1691 elseif line[1] !~# '[ !#]'
1692 call add(unstaged, {'type': 'File', 'status': line[1], 'filename': files})
1697 let b:fugitive_files['Staged'][dict.filename] = dict
1699 for dict in unstaged
1700 let b:fugitive_files['Unstaged'][dict.filename] = dict
1703 let config = fugitive#Config()
1705 let pull_type = 'Pull'
1707 let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
1709 let rebase = fugitive#Config('pull.rebase', config)
1711 if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
1712 let pull_type = 'Rebase'
1713 elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
1714 let pull_type = 'Merge'
1718 let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
1719 if empty(push_remote)
1720 let push_remote = fugitive#Config('remote.pushDefault', config)
1722 let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
1728 let unpulled = s:QueryLog(head . '..' . pull)
1733 let unpushed = s:QueryLog(push . '..' . head)
1738 if isdirectory(fugitive#Find('.git/rebase-merge/'))
1739 let rebasing_dir = fugitive#Find('.git/rebase-merge/')
1740 elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
1741 let rebasing_dir = fugitive#Find('.git/rebase-apply/')
1745 let rebasing_head = 'detached HEAD'
1746 if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
1747 let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
1749 let lines = readfile(rebasing_dir . 'git-rebase-todo')
1751 let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
1757 if getfsize(rebasing_dir . 'done') > 0
1758 let done = readfile(rebasing_dir . 'done')
1759 call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
1760 let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
1761 let lines = done + lines
1765 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
1766 if len(match) && match[1] !~# 'exec\|merge\|label'
1767 call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
1772 let diff = {'Staged': [], 'Unstaged': []}
1774 let diff['Staged'] =
1775 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'])[0]
1778 let diff['Unstaged'] =
1779 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix'])[0]
1781 let b:fugitive_diff = diff
1782 let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
1783 let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
1785 silent keepjumps %delete_
1787 call s:AddHeader('Head', head)
1788 call s:AddHeader(pull_type, pull)
1790 call s:AddHeader('Push', push)
1792 call s:AddSection('Rebasing ' . rebasing_head, rebasing)
1793 call s:AddSection('Untracked', untracked)
1794 call s:AddSection('Unstaged', unstaged)
1795 let unstaged_end = len(unstaged) ? line('$') : 0
1796 call s:AddSection('Staged', staged)
1797 let staged_end = len(staged) ? line('$') : 0
1798 call s:AddSection('Unpushed to ' . push, unpushed)
1799 call s:AddSection('Unpulled from ' . pull, unpulled)
1801 setlocal nomodified readonly noswapfile
1802 silent doautocmd BufReadPost
1803 setlocal nomodifiable
1804 if &bufhidden ==# ''
1805 setlocal bufhidden=delete
1807 let b:dispatch = ':Gfetch --all'
1808 call fugitive#MapJumps()
1809 call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1810 call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
1811 call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
1812 call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
1813 call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
1814 call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
1815 call s:Map('n', 'U', ":exe <SID>EchoExec('reset', '-q')<CR>", '<silent>')
1816 call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
1817 call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
1818 call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
1819 call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
1820 call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
1821 call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
1822 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>')
1823 call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1824 call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
1825 call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
1826 call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
1827 call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
1828 call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1829 call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1830 call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1831 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>')
1832 call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
1833 call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1834 call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1835 call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
1836 call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
1837 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
1838 call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
1839 call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1840 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>')
1841 call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1842 call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
1843 call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1844 if empty(mapcheck('q', 'n'))
1845 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>
1847 call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
1848 call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
1849 call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1850 call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1851 call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
1852 call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1853 call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
1854 call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1855 call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
1856 call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
1857 setlocal filetype=fugitive
1859 for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
1860 while len(getline(lnum))
1861 let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
1862 if has_key(expanded[section], filename)
1863 call s:StageInline('show', lnum)
1869 let b:fugitive_reltime = reltime()
1872 return 'echoerr ' . string(v:exception)
1876 function! fugitive#FileReadCmd(...) abort
1877 let amatch = a:0 ? a:1 : expand('<amatch>')
1878 let [dir, rev] = s:DirRev(amatch)
1879 let line = a:0 > 1 ? a:2 : line("'[")
1881 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1883 if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
1884 let cmd = fugitive#Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1886 let cmd = fugitive#Prepare(dir, 'cat-file', '-p', rev)
1888 return line . 'read !' . escape(cmd, '!#%')
1891 function! fugitive#FileWriteCmd(...) abort
1892 let tmp = tempname()
1893 let amatch = a:0 ? a:1 : expand('<amatch>')
1894 let autype = a:0 > 1 ? 'Buf' : 'File'
1895 if exists('#' . autype . 'WritePre')
1896 execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
1899 let [dir, commit, file] = s:DirCommitFile(amatch)
1900 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1901 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1903 silent execute "'[,']write !".fugitive#Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1904 let sha1 = readfile(tmp)[0]
1905 let old_mode = matchstr(s:SystemError([dir, 'ls-files', '--stage', '.' . file])[0], '^\d\+')
1907 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1909 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1910 let [error, exec_error] = s:SystemError([dir, 'update-index', '--index-info'], info . "\n")
1913 if exists('#' . autype . 'WritePost')
1914 execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
1918 return 'echoerr '.string('fugitive: '.error)
1925 function! fugitive#BufReadCmd(...) abort
1926 let amatch = a:0 ? a:1 : expand('<amatch>')
1928 let [dir, rev] = s:DirRev(amatch)
1930 return 'echo "Invalid Fugitive URL"'
1933 let b:fugitive_type = 'stage'
1935 let [b:fugitive_type, exec_error] = s:ChompError([dir, 'cat-file', '-t', rev])
1936 if exec_error && rev =~# '^:0'
1937 let sha = s:ChompDefault('', dir, 'write-tree', '--prefix=' . rev[3:-1])
1938 let exec_error = empty(sha)
1939 let b:fugitive_type = exec_error ? '' : 'tree'
1942 let error = b:fugitive_type
1943 unlet b:fugitive_type
1945 if empty(&bufhidden)
1946 setlocal bufhidden=delete
1949 let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
1950 return 'silent doautocmd BufNewFile'
1952 setlocal readonly nomodifiable
1953 return 'silent doautocmd BufNewFile|echo ' . string(error)
1955 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1956 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1958 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1959 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1963 if b:fugitive_type !=# 'blob'
1967 setlocal noreadonly modifiable
1968 let pos = getpos('.')
1969 silent keepjumps %delete_
1973 silent doautocmd BufReadPre
1974 if b:fugitive_type ==# 'tree'
1975 let b:fugitive_display_format = b:fugitive_display_format % 2
1976 if b:fugitive_display_format
1977 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1980 let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
1982 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1984 elseif b:fugitive_type ==# 'tag'
1985 let b:fugitive_display_format = b:fugitive_display_format % 2
1986 if b:fugitive_display_format
1987 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1989 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1991 elseif b:fugitive_type ==# 'commit'
1992 let b:fugitive_display_format = b:fugitive_display_format % 2
1993 if b:fugitive_display_format
1994 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1996 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])
1997 keepjumps call search('^parent ')
1998 if getline('.') ==# 'parent '
1999 silent keepjumps delete_
2001 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
2003 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2005 silent keepjumps delete_
2007 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
2010 elseif b:fugitive_type ==# 'stage'
2011 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
2012 elseif b:fugitive_type ==# 'blob'
2013 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
2016 keepjumps call setpos('.',pos)
2017 setlocal nomodified noswapfile
2018 let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
2019 let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
2020 if empty(&bufhidden)
2021 setlocal bufhidden=delete
2023 let &l:modifiable = modifiable
2024 if b:fugitive_type !=# 'blob'
2025 setlocal filetype=git foldmethod=syntax
2026 call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2027 call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2029 call fugitive#MapJumps()
2033 return 'silent ' . s:DoAutocmd('BufReadPost') .
2034 \ (modifiable ? '' : '|setl nomodifiable')
2036 return 'echoerr ' . string(v:exception)
2040 function! fugitive#BufWriteCmd(...) abort
2041 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
2044 function! fugitive#SourceCmd(...) abort
2045 let amatch = a:0 ? a:1 : expand('<amatch>')
2046 let temp = s:BlobTemp(amatch)
2048 return 'noautocmd source ' . s:fnameescape(amatch)
2050 if !exists('g:virtual_scriptnames')
2051 let g:virtual_scriptnames = {}
2053 let g:virtual_scriptnames[temp] = amatch
2054 return 'source ' . s:fnameescape(temp)
2057 " Section: Temp files
2059 if !exists('s:temp_files')
2060 let s:temp_files = {}
2063 function! s:TempState(...) abort
2064 return get(s:temp_files, s:cpath(fnamemodify(a:0 ? a:1 : @%, ':p')), {})
2067 function! s:TempReadPre(file) abort
2068 if has_key(s:temp_files, s:cpath(a:file))
2069 let dict = s:temp_files[s:cpath(a:file)]
2071 setlocal bufhidden=delete nobuflisted
2072 setlocal buftype=nowrite
2073 if has_key(dict, 'modifiable')
2074 let &l:modifiable = dict.modifiable
2077 let b:git_dir = dict.dir
2078 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
2083 function! s:TempReadPost(file) abort
2084 if has_key(s:temp_files, s:cpath(a:file))
2085 let dict = s:temp_files[s:cpath(a:file)]
2086 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
2087 let &l:filetype = dict.filetype
2089 setlocal foldmarker=<<<<<<<,>>>>>>>
2090 if empty(mapcheck('q', 'n'))
2091 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>
2094 call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
2100 augroup fugitive_temp
2102 autocmd BufReadPre * exe s:TempReadPre( expand('<amatch>:p'))
2103 autocmd BufReadPost * exe s:TempReadPost(expand('<amatch>:p'))
2108 function! fugitive#Command(line1, line2, range, bang, mods, arg) abort
2110 let [args, after] = s:SplitExpandChain(a:arg, s:Tree(dir))
2112 let cmd = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
2113 return (empty(cmd) ? 'exe' : cmd) . after
2115 let alias = get(s:Aliases(dir), args[0], '!')
2116 if get(args, 1, '') !=# '--help' && alias !~# '^!\|[\"'']' && !filereadable(s:ExecPath() . '/git-' . args[0])
2117 \ && !(has('win32') && filereadable(s:ExecPath() . '/git-' . args[0] . '.exe'))
2118 call remove(args, 0)
2119 call extend(args, split(alias, '\s\+'), 'keep')
2121 let name = substitute(args[0], '\%(^\|-\)\(\l\)', '\u\1', 'g')
2122 if exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
2125 return 'exe ' . string(s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, args[1:-1])) . after
2127 return 'echoerr ' . string(v:exception)
2130 if a:bang || args[0] =~# '^-P$\|^--no-pager$\|diff\%(tool\)\@!\|log\|^show$' ||
2131 \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
2132 \ (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
2133 return s:OpenExec((a:line2 > 0 ? a:line2 : '') . (a:line2 ? 'split' : 'edit'), a:mods, args, dir) . after
2135 if s:HasOpt(args, ['add', 'checkout', 'commit', 'stage', 'stash', 'reset'], '-p', '--patch') ||
2136 \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive') ||
2137 \ index(['--paginate', '-p'], args[0]) >= 0
2138 let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
2139 let assign = len(dir) ? '|let b:git_dir = ' . string(dir) : ''
2141 if &autowrite || &autowriteall | silent! wall | endif
2142 return mods . (a:line2 ? 'split' : 'edit') . ' term://' . s:fnameescape(s:UserCommand(dir, args)) . assign . '|startinsert' . after
2143 elseif has('terminal')
2144 if &autowrite || &autowriteall | silent! wall | endif
2145 return 'exe ' . string(mods . 'terminal ' . (a:line2 ? '' : '++curwin ') . join(map(s:UserCommandList(dir) + args, 's:fnameescape(v:val)'))) . assign . after
2148 if has('gui_running') && !has('win32')
2149 call insert(args, '--no-pager')
2152 if has('nvim') && executable('env')
2153 let pre .= 'env GIT_TERMINAL_PROMPT=0 '
2155 return 'exe ' . string('noautocmd !' . escape(pre . s:UserCommand(dir, args), '!#%')) .
2156 \ '|call fugitive#ReloadStatus(' . string(dir) . ', 1)' .
2160 let s:exec_paths = {}
2161 function! s:ExecPath() abort
2162 if !has_key(s:exec_paths, g:fugitive_git_executable)
2163 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
2165 return s:exec_paths[g:fugitive_git_executable]
2168 function! s:Subcommands() abort
2169 let exec_path = s:ExecPath()
2170 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
2174 function! s:Aliases(dir) abort
2175 if !has_key(s:aliases, a:dir)
2176 let s:aliases[a:dir] = {}
2177 let lines = s:NullError([a:dir, 'config', '-z', '--get-regexp', '^alias[.]'])[0]
2179 let s:aliases[a:dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
2182 return s:aliases[a:dir]
2185 function! fugitive#Complete(lead, ...) abort
2186 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
2187 let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
2188 let subcmd = matchstr(pre, '\u\w*[! ] *\zs[[:alnum:]-]\+\ze ')
2190 let results = sort(s:Subcommands() + keys(s:Aliases(dir)))
2191 elseif pre =~# ' -- '
2192 return fugitive#CompletePath(a:lead, dir)
2193 elseif a:lead =~# '^-'
2194 let results = split(s:ChompDefault('', dir, subcmd, '--git-completion-helper'), ' ')
2196 return fugitive#CompleteObject(a:lead, dir)
2198 return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
2201 " Section: :Gcd, :Glcd
2203 function! fugitive#CdComplete(A, L, P) abort
2204 return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
2207 function! fugitive#Cd(path, ...) abort
2208 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
2209 if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
2212 let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
2214 return (a:0 && a:1 ? 'lcd ' : 'cd ') . s:fnameescape(FugitiveVimPath(path))
2219 call s:command("-bar -bang -range=-1 -addr=other Gstatus", "Status")
2221 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
2222 let dir = a:0 ? a:1 : s:Dir()
2225 let mods = s:Mods(a:mods, &splitbelow ? 'botright' : 'topleft')
2226 let file = fugitive#Find(':', dir)
2227 let arg = ' +setl\ foldmethod=syntax\ foldlevel=1\|let\ w:fugitive_status=FugitiveGitDir() ' .
2228 \ s:fnameescape(file)
2229 for winnr in range(1, winnr('$'))
2230 if s:cpath(file, fnamemodify(bufname(winbufnr(winnr)), ':p'))
2232 call s:ReloadStatus()
2234 call s:ExpireStatus(dir)
2235 exe winnr . 'wincmd w'
2237 let w:fugitive_status = dir
2243 return mods . 'edit' . (a:bang ? '!' : '') . arg
2245 return mods . 'pedit' . arg . '|wincmd P'
2247 return mods . (a:count > 0 ? a:count : '') . 'split' . arg
2250 return 'echoerr ' . string(v:exception)
2255 function! s:StageJump(offset, section, ...) abort
2256 let line = search('^\%(' . a:section . '\)', 'nw')
2258 let line = search('^\%(' . a:1 . '\)', 'nw')
2263 for i in range(a:offset)
2264 call search(s:file_commit_pattern . '\|^$', 'W')
2265 if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
2266 call search(s:file_commit_pattern . '\|^$', 'W')
2268 if empty(getline('.'))
2272 call s:StageReveal()
2274 call s:StageReveal()
2281 function! s:StageSeek(info, fallback) abort
2283 if empty(info.section)
2286 let line = search('^' . info.section, 'wn')
2288 for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
2289 let line = search('^' . section, 'wn')
2291 return line + (info.index > 0 ? 1 : 0)
2297 while len(getline(line))
2298 let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
2300 \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
2301 \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
2302 \ filename ==# info.filename)
2306 if getline(line+1) !~# '^@'
2307 exe s:StageInline('show', line)
2309 if getline(line+1) !~# '^@'
2312 let type = info.sigil ==# '-' ? '-' : '+'
2314 while offset < info.offset
2316 if getline(line) =~# '^@'
2317 let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
2318 elseif getline(line) =~# '^[ ' . type . ']'
2320 elseif getline(line) !~# '^[ @\+-]'
2327 let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
2328 if len(commit) && commit ==# info.commit
2334 let i += getline(line) !~# '^[ @\+-]'
2337 return exists('backup') ? backup : line - 1
2340 function! s:DoAutocmdChanged(dir) abort
2341 let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
2342 if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
2346 let g:fugitive_event = dir
2347 exe s:DoAutocmd('User FugitiveChanged')
2349 unlet! g:fugitive_event
2350 " Force statusline reload with the buffer's Git dir
2356 function! s:ReloadStatusBuffer(...) abort
2357 if get(b:, 'fugitive_type', '') !=# 'index'
2360 let original_lnum = a:0 ? a:1 : line('.')
2361 let info = s:StageInfo(original_lnum)
2362 call fugitive#BufReadStatus()
2363 exe s:StageSeek(info, original_lnum)
2368 function! s:ReloadStatus(...) abort
2369 call s:ExpireStatus(-1)
2370 call s:ReloadStatusBuffer(a:0 ? a:1 : line('.'))
2371 exe s:DoAutocmdChanged(-1)
2375 let s:last_time = reltime()
2376 if !exists('s:last_times')
2377 let s:last_times = {}
2380 function! s:ExpireStatus(bufnr) abort
2382 let s:last_time = reltime()
2385 let dir = s:Dir(a:bufnr)
2387 let s:last_times[s:cpath(dir)] = reltime()
2392 function! FugitiveReloadCheck() abort
2393 let t = b:fugitive_reltime
2394 return [t, reltimestr(reltime(s:last_time, t)),
2395 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t))]
2398 function! s:ReloadWinStatus(...) abort
2399 if get(b:, 'fugitive_type', '') !=# 'index' || &modified
2402 if !exists('b:fugitive_reltime')
2403 exe s:ReloadStatusBuffer()
2406 let t = b:fugitive_reltime
2407 if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
2408 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t)) =~# '-\|\d\{10\}\.'
2409 exe s:ReloadStatusBuffer()
2413 function! s:ReloadTabStatus(...) abort
2414 let mytab = tabpagenr()
2415 let tab = a:0 ? a:1 : mytab
2416 for winnr in range(1, tabpagewinnr(tab, '$'))
2417 if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
2418 execute 'tabnext '.tab
2420 execute winnr.'wincmd w'
2421 let restorewinnr = 1
2424 call s:ReloadWinStatus()
2426 if exists('restorewinnr')
2430 execute 'tabnext '.mytab
2434 unlet! t:fugitive_reload_status
2437 function! fugitive#ReloadStatus(...) abort
2438 call s:ExpireStatus(a:0 ? a:1 : -1)
2439 if a:0 > 1 ? a:2 : 1
2441 let t:fugitive_reload_status = t
2442 for tabnr in exists('*settabvar') ? range(1, tabpagenr('$')) : []
2443 call settabvar(tabnr, 'fugitive_reload_status', t)
2445 call s:ReloadTabStatus()
2446 exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
2448 call s:ReloadWinStatus()
2453 function! fugitive#EfmDir(...) abort
2454 let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
2455 let dir = substitute(dir, '%%', '%', 'g')
2456 let dir = substitute(dir, '\\\ze[\,]', '', 'g')
2460 augroup fugitive_status
2462 autocmd BufWritePost * call fugitive#ReloadStatus(-1, 0)
2463 autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#ReloadStatus(-2, 0)
2464 autocmd BufDelete * nested
2465 \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
2466 \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
2467 \ call fugitive#ReloadStatus(+expand('<abuf>'), 1) |
2469 \ call fugitive#ReloadStatus(-2, 0) |
2472 autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
2473 \ call fugitive#ReloadStatus(fugitive#EfmDir(), 1)
2475 autocmd FocusGained * call fugitive#ReloadStatus(-2, 0)
2477 autocmd BufEnter index,index.lock
2478 \ call s:ReloadWinStatus()
2480 \ if exists('t:fugitive_reload_status') |
2481 \ call s:ReloadTabStatus() |
2485 function! s:StageInfo(...) abort
2486 let lnum = a:0 ? a:1 : line('.')
2487 let sigil = matchstr(getline(lnum), '^[ @\+-]')
2490 let type = sigil ==# '-' ? '-' : '+'
2491 while lnum > 0 && getline(lnum) !~# '^@'
2492 if getline(lnum) =~# '^[ '.type.']'
2497 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2498 while getline(lnum) =~# '^[ @\+-]'
2502 let slnum = lnum + 1
2505 while len(getline(slnum - 1)) && empty(section)
2507 let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2508 if empty(section) && getline(slnum) !~# '^[ @\+-]'
2512 let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2513 return {'section': section,
2514 \ 'heading': getline(slnum),
2518 \ 'relative': reverse(split(text, ' -> ')),
2519 \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2520 \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2521 \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2525 function! s:Selection(arg1, ...) abort
2527 let arg1 = line('.')
2529 elseif a:arg1 ==# 'v'
2530 let arg1 = line("'<")
2531 let arg2 = line("'>")
2534 let arg2 = a:0 ? a:1 : 0
2538 let last = first - arg2 - 1
2544 while getline(first) =~# '^$\|^[A-Z][a-z]'
2547 if first > last || &filetype !=# 'fugitive'
2551 while getline(flnum) =~# '^[ @\+-]'
2554 let slnum = flnum + 1
2557 while len(getline(slnum - 1)) && empty(section)
2559 let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2560 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2566 \ 'heading': heading,
2567 \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2575 let line = getline(flnum)
2576 let lnum = first - (arg1 == flnum ? 0 : 1)
2577 let root = s:Tree() . '/'
2579 if line =~# '^\u\l\+\ze.* (\d\+)$'
2580 let template.heading = getline(lnum)
2581 let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2582 let template.index = 0
2583 elseif line =~# '^[ @\+-]'
2584 let template.index -= 1
2585 if !results[-1].patch
2586 let results[-1].patch = lnum
2588 let results[-1].lnum = lnum
2589 elseif line =~# '^[A-Z?] '
2590 let filename = matchstr(line, '^[A-Z?] \zs.*')
2591 call add(results, extend(deepcopy(template), {
2593 \ 'filename': filename,
2594 \ 'relative': reverse(split(filename, ' -> ')),
2595 \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2596 \ 'status': matchstr(line, '^[A-Z?]'),
2598 elseif line =~# '^\x\x\x\+ '
2599 call add(results, extend({
2601 \ 'commit': matchstr(line, '^\x\x\x\+'),
2602 \ }, template, 'keep'))
2603 elseif line =~# '^\l\+ \x\x\x\+ '
2604 call add(results, extend({
2606 \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2607 \ 'status': matchstr(line, '^\l\+'),
2608 \ }, template, 'keep'))
2611 let template.index += 1
2612 let line = getline(lnum)
2614 if len(results) && results[0].patch && arg2 == 0
2615 while getline(results[0].patch) =~# '^[ \+-]'
2616 let results[0].patch -= 1
2618 while getline(results[0].lnum + 1) =~# '^[ \+-]'
2619 let results[0].lnum += 1
2625 function! s:StageArgs(visual) abort
2628 for record in s:Selection(a:visual ? 'v' : 'n')
2629 if len(record.commit)
2630 call add(commits, record.commit)
2632 call extend(paths, record.paths)
2634 if s:cpath(s:Tree(), getcwd())
2635 call map(paths, 'fugitive#Path(v:val, "./")')
2637 return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2640 function! s:Do(action, visual) abort
2641 let line = getline('.')
2643 if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
2644 let header = matchstr(line, '^\S\+\ze:')
2645 if len(header) && exists('*s:Do' . a:action . header . 'Header')
2646 let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2648 let section = matchstr(line, '^\S\+')
2649 if exists('*s:Do' . a:action . section . 'Heading')
2650 let reload = s:Do{a:action}{section}Heading(line) > 0
2653 return reload ? s:ReloadStatus() : ''
2655 let selection = s:Selection(a:visual ? 'v' : 'n')
2659 call filter(selection, 'v:val.section ==# selection[0].section')
2663 for record in selection
2664 if exists('*s:Do' . a:action . record.section)
2665 let status = s:Do{a:action}{record.section}(record)
2672 let reload = reload || (status > 0)
2675 execute record.lnum + 1
2679 return 'echoerr ' . string(v:exception)
2682 execute s:ReloadStatus()
2684 if exists('success')
2685 call s:StageReveal()
2691 function! s:StageReveal() abort
2693 let begin = line('.')
2694 if getline(begin) =~# '^@'
2696 while getline(end) =~# '^[ \+-]'
2699 elseif getline(begin) =~# '^commit '
2701 while end < line('$') && getline(end + 1) !~# '^commit '
2704 elseif getline(begin) =~# s:section_pattern
2706 while len(getline(end + 1))
2711 while line('.') > line('w0') + &scrolloff && end > line('w$')
2712 execute "normal! \<C-E>"
2717 let s:file_pattern = '^[A-Z?] .\|^diff --'
2718 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2719 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2721 function! s:NextHunk(count) abort
2722 if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2723 exe s:StageInline('show')
2725 for i in range(a:count)
2726 if &filetype ==# 'fugitive'
2727 call search(s:file_pattern . '\|^@', 'W')
2728 if getline('.') =~# s:file_pattern
2729 exe s:StageInline('show')
2730 if getline(line('.') + 1) =~# '^@'
2735 call search('^@@', 'W')
2738 call s:StageReveal()
2742 function! s:PreviousHunk(count) abort
2743 for i in range(a:count)
2744 if &filetype ==# 'fugitive'
2745 let lnum = search(s:file_pattern . '\|^@','Wbn')
2746 call s:StageInline('show', lnum)
2747 call search('^? .\|^@','Wb')
2749 call search('^@@', 'Wb')
2752 call s:StageReveal()
2756 function! s:NextFile(count) abort
2757 for i in range(a:count)
2758 exe s:StageInline('hide')
2759 if !search(s:file_pattern, 'W')
2763 exe s:StageInline('hide')
2767 function! s:PreviousFile(count) abort
2768 exe s:StageInline('hide')
2769 for i in range(a:count)
2770 if !search(s:file_pattern, 'Wb')
2773 exe s:StageInline('hide')
2778 function! s:NextItem(count) abort
2779 for i in range(a:count)
2780 if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2781 call search('^commit ', 'W')
2784 call s:StageReveal()
2788 function! s:PreviousItem(count) abort
2789 for i in range(a:count)
2790 if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2791 call search('^commit ', 'Wbe')
2794 call s:StageReveal()
2798 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2799 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2801 function! s:NextSection(count) abort
2802 let orig = line('.')
2803 if getline('.') !~# '^commit '
2806 for i in range(a:count)
2807 if !search(s:section_commit_pattern, 'W')
2811 if getline('.') =~# s:section_commit_pattern
2812 call s:StageReveal()
2813 return getline('.') =~# s:section_pattern ? '+' : ':'
2819 function! s:PreviousSection(count) abort
2820 let orig = line('.')
2821 if getline('.') !~# '^commit '
2824 for i in range(a:count)
2825 if !search(s:section_commit_pattern . '\|\%^', 'bW')
2829 if getline('.') =~# s:section_commit_pattern || line('.') == 1
2830 call s:StageReveal()
2831 return getline('.') =~# s:section_pattern ? '+' : ':'
2837 function! s:NextSectionEnd(count) abort
2839 if empty(getline('.'))
2842 for i in range(a:count)
2843 if !search(s:section_commit_pattern, 'W')
2847 return search('^.', 'Wb')
2850 function! s:PreviousSectionEnd(count) abort
2852 for i in range(a:count)
2853 if search(s:section_commit_pattern, 'Wb') <= 1
2863 return search('^.', 'Wb')
2866 function! s:PatchSearchExpr(reverse) abort
2867 let line = getline('.')
2868 if col('.') ==# 1 && line =~# '^[+-]'
2869 if line =~# '^[+-]\{3\} '
2870 let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2872 let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2875 return '?' . escape(pattern, '/') . "\<CR>"
2877 return '/' . escape(pattern, '/?') . "\<CR>"
2880 return a:reverse ? '#' : '*'
2883 function! s:StageInline(mode, ...) abort
2884 if &filetype !=# 'fugitive'
2887 let lnum1 = a:0 ? a:1 : line('.')
2888 let lnum = lnum1 + 1
2889 if a:0 > 1 && a:2 == 0
2890 let info = s:StageInfo(lnum - 1)
2891 if empty(info.paths) && len(info.section)
2892 while len(getline(lnum))
2901 while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2904 let info = s:StageInfo(lnum)
2905 if !has_key(b:fugitive_diff, info.section)
2908 if getline(lnum + 1) =~# '^[ @\+-]'
2909 let lnum2 = lnum + 1
2910 while getline(lnum2 + 1) =~# '^[ @\+-]'
2913 if a:mode !=# 'show'
2914 setlocal modifiable noreadonly
2915 exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2916 call remove(b:fugitive_expanded[info.section], info.filename)
2917 setlocal nomodifiable readonly nomodified
2921 if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2928 for line in b:fugitive_diff[info.section]
2929 if mode ==# 'await' && line[0] ==# '@'
2930 let mode = 'capture'
2932 if mode !=# 'head' && line !~# '^[ @\+-]'
2938 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2940 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2942 elseif mode ==# 'capture'
2943 call add(diff, line)
2944 elseif line[0] ==# '@'
2950 setlocal modifiable noreadonly
2951 silent call append(lnum, diff)
2952 let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2953 setlocal nomodifiable readonly nomodified
2959 function! s:NextExpandedHunk(count) abort
2960 for i in range(a:count)
2961 call s:StageInline('show', line('.'), 1)
2962 call search(s:file_pattern . '\|^@','W')
2967 function! s:StageDiff(diff) abort
2968 let lnum = line('.')
2969 let info = s:StageInfo(lnum)
2970 let prefix = info.offset > 0 ? '+' . info.offset : ''
2971 if empty(info.paths) && info.section ==# 'Staged'
2972 return 'Git! diff --no-ext-diff --cached'
2973 elseif empty(info.paths)
2974 return 'Git! diff --no-ext-diff'
2975 elseif len(info.paths) > 1
2976 execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2977 return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2978 elseif info.section ==# 'Staged' && info.sigil ==# '-'
2979 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2980 return a:diff . '! :0:%'
2981 elseif info.section ==# 'Staged'
2982 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2983 return a:diff . '! @:%'
2984 elseif info.sigil ==# '-'
2985 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2986 return a:diff . '! :(top)%'
2988 execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
2993 function! s:StageDiffEdit() abort
2994 let info = s:StageInfo(line('.'))
2995 let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
2996 if info.section ==# 'Staged'
2997 return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
2998 elseif info.status ==# '?'
2999 call s:TreeChomp('add', '--intent-to-add', '--', arg)
3000 return s:ReloadStatus()
3002 return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
3006 function! s:StageApply(info, reverse, extra) abort
3007 if a:info.status ==# 'R'
3008 call s:throw('fugitive: patching renamed file not yet supported')
3010 let cmd = ['apply', '-p0', '--recount'] + a:extra
3012 let start = info.patch
3014 let lines = getline(start, end)
3015 if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
3018 while getline(end) =~# '^[-+ ]'
3020 if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3021 call add(lines, ' ' . getline(end)[1:-1])
3024 while start > 0 && getline(start) !~# '^@'
3026 if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
3027 call insert(lines, ' ' . getline(start)[1:-1])
3028 elseif getline(start) =~# '^@'
3029 call insert(lines, getline(start))
3033 throw 'fugitive: cold not find hunk'
3034 elseif getline(start) !~# '^@@ '
3035 throw 'fugitive: cannot apply conflict hunk'
3037 let i = b:fugitive_expanded[info.section][info.filename][0]
3039 while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
3040 call add(head, b:fugitive_diff[info.section][i])
3043 call extend(lines, head, 'keep')
3044 let temp = tempname()
3045 call writefile(lines, temp)
3047 call add(cmd, '--reverse')
3049 call extend(cmd, ['--', temp])
3050 let [output, exec_error] = s:ChompError(cmd)
3054 call s:throw(output)
3057 function! s:StageDelete(lnum1, lnum2, count) abort
3061 for info in s:Selection(a:lnum1, a:lnum2)
3062 if empty(info.paths)
3065 if info.status ==# 'D'
3066 let undo = 'Gremove'
3067 elseif info.paths[0] =~# '/$'
3068 let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
3071 let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
3074 call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3075 elseif info.status ==# '?'
3076 call s:TreeChomp('clean', '-f', '--', info.paths[0])
3078 call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3080 call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3081 elseif info.status =~# '[ADU]' &&
3082 \ get(b:fugitive_files[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, {'status': ''}).status =~# '[AU]'
3083 call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3084 elseif info.status ==# 'U'
3085 call s:TreeChomp('rm', '--', info.paths[0])
3086 elseif info.status ==# 'A'
3087 call s:TreeChomp('rm', '-f', '--', info.paths[0])
3088 elseif info.section ==# 'Unstaged'
3089 call s:TreeChomp('checkout', '--', info.paths[0])
3091 call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3093 call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
3096 let err .= '|echoerr ' . string(v:exception)
3101 exe s:ReloadStatus()
3102 call s:StageReveal()
3104 return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3106 return 'checktime|redraw' . err
3110 function! s:StageIgnore(lnum1, lnum2, count) abort
3112 for info in s:Selection(a:lnum1, a:lnum2)
3113 call extend(paths, info.relative)
3115 call map(paths, '"/" . v:val')
3116 exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3117 let last = line('$')
3118 if last == 1 && empty(getline(1))
3119 call setline(last, paths)
3121 call append(last, paths)
3127 function! s:DoToggleHeadHeader(value) abort
3128 exe 'edit' s:fnameescape(s:Dir())
3129 call search('\C^index$', 'wc')
3132 function! s:DoStageUnpushedHeading(heading) abort
3133 let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3137 let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3138 call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3141 function! s:DoToggleUnpushedHeading(heading) abort
3142 return s:DoStageUnpushedHeading(a:heading)
3145 function! s:DoStageUnpushed(record) abort
3146 let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3150 let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3151 call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3154 function! s:DoToggleUnpushed(record) abort
3155 return s:DoStageUnpushed(a:record)
3158 function! s:DoUnstageUnpulledHeading(heading) abort
3159 call feedkeys(':Grebase')
3162 function! s:DoToggleUnpulledHeading(heading) abort
3163 call s:DoUnstageUnpulledHeading(a:heading)
3166 function! s:DoUnstageUnpulled(record) abort
3167 call feedkeys(':Grebase ' . a:record.commit)
3170 function! s:DoToggleUnpulled(record) abort
3171 call s:DoUnstageUnpulled(a:record)
3174 function! s:DoUnstageUnpushed(record) abort
3175 call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3178 function! s:DoToggleStagedHeading(...) abort
3179 call s:TreeChomp('reset', '-q')
3183 function! s:DoUnstageStagedHeading(heading) abort
3184 return s:DoToggleStagedHeading(a:heading)
3187 function! s:DoToggleUnstagedHeading(...) abort
3188 call s:TreeChomp('add', '-u')
3192 function! s:DoStageUnstagedHeading(heading) abort
3193 return s:DoToggleUnstagedHeading(a:heading)
3196 function! s:DoToggleUntrackedHeading(...) abort
3197 call s:TreeChomp('add', '.')
3201 function! s:DoStageUntrackedHeading(heading) abort
3202 return s:DoToggleUntrackedHeading(a:heading)
3205 function! s:DoToggleStaged(record) abort
3207 return s:StageApply(a:record, 1, ['--cached'])
3209 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3214 function! s:DoUnstageStaged(record) abort
3215 return s:DoToggleStaged(a:record)
3218 function! s:DoToggleUnstaged(record) abort
3219 if a:record.patch && a:record.status !=# 'A'
3220 return s:StageApply(a:record, 0, ['--cached'])
3222 call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3227 function! s:DoStageUnstaged(record) abort
3228 return s:DoToggleUnstaged(a:record)
3231 function! s:DoUnstageUnstaged(record) abort
3232 if a:record.status ==# 'A'
3233 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3240 function! s:DoToggleUntracked(record) abort
3241 call s:TreeChomp(['add', '--'] + a:record.paths)
3245 function! s:DoStageUntracked(record) abort
3246 return s:DoToggleUntracked(a:record)
3249 function! s:StagePatch(lnum1,lnum2) abort
3254 for lnum in range(a:lnum1,a:lnum2)
3255 let info = s:StageInfo(lnum)
3256 if empty(info.paths) && info.section ==# 'Staged'
3257 return 'Git reset --patch'
3258 elseif empty(info.paths) && info.section ==# 'Unstaged'
3259 return 'Git add --patch'
3260 elseif empty(info.paths) && info.section ==# 'Untracked'
3261 return 'Git add --interactive'
3262 elseif empty(info.paths)
3266 if info.section ==# 'Staged'
3267 let reset += info.relative
3268 elseif info.section ==# 'Untracked'
3269 let intend += info.paths
3270 elseif info.status !~# '^D'
3271 let add += info.relative
3276 call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3279 execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3282 execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3285 return 'echoerr ' . string(v:exception)
3287 return s:ReloadStatus()
3290 " Section: :Gcommit, :Grevert
3292 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3293 let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3294 let status = len(status) ? status . '|' : ''
3296 return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3298 return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3302 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3303 let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3304 let dir = a:0 ? a:1 : s:Dir()
3305 let tree = s:Tree(dir)
3306 let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3307 let outfile = tempname()
3310 let command = 'set GIT_EDITOR=false& '
3312 let command = 'env GIT_EDITOR=false '
3316 while get(argv, i, '--') !=# '--'
3317 if argv[i] =~# '^-[apzsneiovq].'
3318 call insert(argv, argv[i][0:1])
3319 let argv[i+1] = '-' . argv[i+1][2:-1]
3324 let command .= s:UserCommand(dir, ['commit'] + argv)
3325 if (&autowrite || &autowriteall) && !a:0
3328 if s:HasOpt(argv, '-i', '--interactive')
3329 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3330 elseif s:HasOpt(argv, '-p', '--patch')
3331 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3333 let [error_string, exec_error] = s:TempCmd(outfile, command)
3334 let errors = split(error_string, "\n")
3336 if !has('gui_running')
3340 echo join(errors, "\n")
3341 if filereadable(outfile)
3342 echo join(readfile(outfile), "\n")
3344 call fugitive#ReloadStatus(dir, 1)
3347 let error = get(errors,-2,get(errors,-1,'!'))
3348 if error =~# 'false''\=\.$'
3350 while get(argv, i, '--') !=# '--'
3351 if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3352 call remove(argv, i)
3353 elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3354 call remove(argv, i, i + 1)
3356 if argv[i] =~# '^--cleanup\>'
3362 call insert(argv, '--no-signoff', i)
3363 call insert(argv, '--no-interactive', i)
3364 call insert(argv, '--no-edit', i)
3365 if !exists('cleanup')
3366 call insert(argv, '--cleanup=strip')
3368 call extend(argv, ['-F', msgfile], 'keep')
3369 if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3370 execute mods . 'keepalt edit' s:fnameescape(msgfile)
3371 elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3372 execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3374 execute mods . 'keepalt split' s:fnameescape(msgfile)
3376 let b:fugitive_commit_arguments = argv
3377 setlocal bufhidden=wipe filetype=gitcommit
3379 elseif empty(errors)
3380 let out = readfile(outfile)
3381 echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3384 echo join(errors, "\n")
3389 return 'echoerr ' . string(v:exception)
3391 call delete(outfile)
3395 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3397 let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3398 let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3399 let [out, exec_error] = s:SystemError(cmd)
3400 call fugitive#ReloadStatus(dir, 1)
3401 if no_commit || exec_error
3402 return 'echo ' . string(substitute(out, "\n$", '', ''))
3404 return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3407 function! fugitive#CommitComplete(A, L, P) abort
3408 if a:A =~# '^--fixup=\|^--squash='
3409 let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3410 let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3412 call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3413 call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3416 return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3419 return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3424 function! fugitive#RevertComplete(A, L, P) abort
3425 return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3428 function! s:FinishCommit() abort
3429 let buf = +expand('<abuf>')
3430 let args = getbufvar(buf, 'fugitive_commit_arguments')
3432 call setbufvar(buf, 'fugitive_commit_arguments', [])
3433 if getbufvar(buf, 'fugitive_commit_rebase')
3434 call setbufvar(buf, 'fugitive_commit_rebase', 0)
3435 let s:rebase_continue = s:Dir(buf)
3437 return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3442 call s:command("-nargs=? -range=-1 -complete=customlist,fugitive#CommitComplete Gcommit", "commit")
3443 call s:command("-nargs=? -range=-1 -complete=customlist,fugitive#RevertComplete Grevert", "revert")
3445 " Section: :Gmerge, :Grebase, :Gpull
3447 function! fugitive#MergeComplete(A, L, P) abort
3448 return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3451 function! fugitive#RebaseComplete(A, L, P) abort
3452 return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3455 function! fugitive#PullComplete(A, L, P) abort
3456 return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3459 function! s:RebaseSequenceAborter() abort
3460 if !exists('s:rebase_sequence_aborter')
3461 let temp = tempname() . '.sh'
3464 \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3465 \ 'mv "$1.fugitive" "$1"'],
3467 let s:rebase_sequence_aborter = temp
3469 return s:rebase_sequence_aborter
3472 function! fugitive#Cwindow() abort
3473 if &buftype == 'quickfix'
3477 if &buftype == 'quickfix'
3483 let s:common_efm = ''
3485 \ . '%+Eusage:%.%#,'
3486 \ . '%+Eerror:%.%#,'
3487 \ . '%+Efatal:%.%#,'
3488 \ . '%-G%.%#%\e[K%.%#,'
3489 \ . '%-G%.%#%\r%.%\+'
3491 let s:rebase_abbrevs = {
3505 function! s:RebaseEdit(cmd, dir) abort
3506 let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3508 if filereadable(rebase_todo)
3509 let new = readfile(rebase_todo)
3513 for i in range(len(new))
3514 if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3515 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3517 let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3519 let shortened_sha = strpart(sha, 0, sha_length)
3520 let shas[shortened_sha] = sha
3521 let new[i] = substitute(new[i], sha, shortened_sha, '')
3524 call writefile(new, rebase_todo)
3526 return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3529 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3530 let dir = a:0 ? a:1 : s:Dir()
3532 let mods = s:Mods(a:mods)
3533 if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3534 let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3535 let out = system(cmd)[0:-2]
3536 for file in ['end', 'msgnum']
3537 let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3538 if !filereadable(file)
3539 return 'echoerr ' . string("fugitive: " . out)
3541 call writefile([readfile(file)[0] - 1], file)
3543 call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3547 return s:RebaseEdit(mods . 'split', dir)
3548 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3549 return s:RebaseEdit(mods . 'split', dir)
3550 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3551 let rdir = fugitive#Find('.git/rebase-merge', dir)
3552 let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3553 if exec_error && isdirectory(rdir)
3554 if getfsize(rdir . '/amend') <= 0
3555 return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3556 elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3557 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3561 let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3564 let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3566 call add(argv, a:cmd)
3568 if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3569 call add(argv, '--edit')
3571 if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3572 call add(argv, '--interactive')
3574 call extend(argv, args)
3576 let [mp, efm] = [&l:mp, &l:efm]
3578 let cdback = s:Cd(s:Tree(dir))
3579 let &l:errorformat = ''
3580 \ . '%-Gerror:%.%#false''.,'
3581 \ . '%-G%.%# ''git commit'' %.%#,'
3582 \ . '%+Emerge:%.%#,'
3583 \ . s:common_efm . ','
3584 \ . '%+ECannot %.%#: You have unstaged changes.,'
3585 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3586 \ . '%+EThere is no tracking information for the current branch.,'
3587 \ . '%+EYou are not currently on a branch. Please specify which,'
3588 \ . '%+I %#git rebase --continue,'
3589 \ . 'CONFLICT (%m): %f deleted in %.%#,'
3590 \ . 'CONFLICT (%m): Merge conflict in %f,'
3591 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3592 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3593 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3594 \ . '%+ECONFLICT %.%#,'
3595 \ . '%+EKONFLIKT %.%#,'
3596 \ . '%+ECONFLIT %.%#,'
3597 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3598 \ . "%+E\u51b2\u7a81 %.%#,"
3600 if a:cmd =~# '^merge' && empty(args) &&
3601 \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3602 \ !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3603 let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3605 let cmd = s:UserCommand(dir, argv)
3607 if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3608 let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3609 let $GIT_SEQUENCE_EDITOR = 'true'
3611 let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3613 if !empty($GIT_EDITOR) || has('win32')
3614 let old_editor = $GIT_EDITOR
3615 let $GIT_EDITOR = 'false'
3617 let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3619 if !has('patch-8.1.0334') && has('terminal') && &autowrite
3620 let autowrite_was_set = 1
3624 let &l:makeprg = cmd
3625 silent noautocmd make!
3626 catch /^Vim\%((\a\+)\)\=:E211/
3627 let err = v:exception
3629 if exists('autowrite_was_set')
3633 let [&l:mp, &l:efm] = [mp, efm]
3634 if exists('old_editor')
3635 let $GIT_EDITOR = old_editor
3637 if exists('old_sequence_editor')
3638 let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3642 call fugitive#ReloadStatus(dir, 1)
3643 if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3644 if a:cmd =~# '^rebase' &&
3645 \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3646 \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3647 \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3649 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3650 elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3652 return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3655 let qflist = getqflist()
3660 let e.pattern = '^<<<<<<<'
3663 call fugitive#Cwindow()
3665 call setqflist(qflist, 'r')
3671 return exists('err') ? 'echoerr '.string(err) : 'exe'
3674 function! s:RebaseClean(file) abort
3675 if !filereadable(a:file)
3678 let old = readfile(a:file)
3680 for i in range(len(new))
3681 let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3683 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3684 let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3685 if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3686 let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3690 call writefile(new, a:file)
3695 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3696 return s:MergeRebase('merge', a:bang, a:mods, a:args)
3699 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3700 return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3703 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3704 return s:MergeRebase('pull', a:bang, a:mods, a:args)
3707 augroup fugitive_merge
3709 autocmd VimLeavePre,BufDelete git-rebase-todo
3710 \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3711 \ call s:RebaseClean(expand('<afile>')) |
3712 \ if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3713 \ let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3716 autocmd BufEnter * nested
3717 \ if exists('s:rebase_continue') |
3718 \ exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3722 call s:command("-nargs=? -bang -complete=customlist,fugitive#MergeComplete Gmerge", "merge")
3723 call s:command("-nargs=? -bang -complete=customlist,fugitive#RebaseComplete Grebase", "rebase")
3724 call s:command("-nargs=? -bang -complete=customlist,fugitive#PullComplete Gpull", "pull")
3726 " Section: :Ggrep, :Glog
3728 if !exists('g:fugitive_summary_format')
3729 let g:fugitive_summary_format = '%s'
3732 function! fugitive#GrepComplete(A, L, P) abort
3733 return s:CompleteSub('grep', a:A, a:L, a:P)
3736 function! fugitive#LogComplete(A, L, P) abort
3737 return s:CompleteSub('log', a:A, a:L, a:P)
3740 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3741 let entry = {'valid': 1}
3742 let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3744 let entry.module = match[1]
3745 let entry.lnum = +match[2]
3746 let entry.col = +match[3]
3747 let entry.text = match[4]
3748 elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3749 return {'text': a:line}
3751 let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3752 if len(entry.module)
3753 let entry.text = 'Binary file'
3757 if empty(entry.module) && a:name_only
3758 let entry.module = a:line
3760 if empty(entry.module)
3761 return {'text': a:line}
3763 if entry.module !~# ':'
3764 let entry.filename = a:prefix . entry.module
3766 let entry.filename = fugitive#Find(entry.module, a:dir)
3771 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3774 let listnr = a:line1 == 0 ? a:line1 : a:line2
3775 let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3776 if fugitive#GitVersion(2, 19)
3777 call add(cmd, '--column')
3779 let tree = s:Tree(dir)
3780 if type(a:args) == type([])
3781 let [args, after] = [a:args, '']
3783 let [args, after] = s:SplitExpandChain(a:args, tree)
3785 let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3786 let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3787 let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3789 exe listnr 'wincmd w'
3794 call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3795 let tempfile = tempname()
3796 let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
3797 silent exe s:DoAutocmd('QuickFixCmdPre ' . event)
3798 exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3799 \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3800 let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3801 call s:QuickfixSet(listnr, list, 'a')
3802 silent exe s:DoAutocmd('QuickFixCmdPost ' . event)
3803 if !has('gui_running')
3806 if !a:bang && !empty(list)
3807 return (listnr < 0 ? 'c' : 'l').'first' . after
3813 function! s:LogFlushQueue(state) abort
3814 let queue = remove(a:state, 'queue')
3815 if a:state.child_found
3816 call remove(queue, 0)
3818 if len(queue) && queue[-1] ==# {'text': ''}
3819 call remove(queue, -1)
3824 function! s:LogParse(state, dir, line) abort
3825 if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3828 let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3830 let a:state.context = 'commit'
3831 let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3832 let a:state.base_module = len(list[1]) ? list[1] : list[2]
3833 let a:state.message = list[3]
3834 if has_key(a:state, 'diffing')
3835 call remove(a:state, 'diffing')
3837 let queue = s:LogFlushQueue(a:state)
3838 let a:state.queue = [{
3840 \ 'filename': a:state.base . a:state.target,
3841 \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3842 \ 'text': a:state.message}]
3843 let a:state.child_found = 0
3845 elseif type(a:line) == type(0)
3846 return s:LogFlushQueue(a:state)
3847 elseif a:line =~# '^diff'
3848 let a:state.context = 'diffhead'
3849 elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3850 let a:state.diffing = a:line[5:-1]
3851 elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3852 let a:state.context = 'hunk'
3853 if empty(a:state.target) || a:state.target ==# a:state.diffing
3854 let a:state.child_found = 1
3855 call add(a:state.queue, {
3857 \ 'filename': a:state.base . a:state.diffing,
3858 \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3859 \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3860 \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3862 elseif a:state.follow &&
3863 \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3864 let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3866 let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3867 if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3868 let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3871 if !get(a:state, 'ignore_summary')
3872 call add(a:state.queue, {'text': a:line})
3874 elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3875 call add(a:state.queue, {'text': a:line})
3880 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
3883 let listnr = a:type =~# '^l' ? 0 : -1
3884 let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3885 let split = index(args, '--')
3887 let paths = args[split : -1]
3888 let args = args[0 : split - 1]
3895 if a:line1 == 0 && a:count
3896 let path = fugitive#Path(bufname(a:count), '/', dir)
3898 let path = fugitive#Path(@%, '/', dir)
3904 let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3905 if path =~# '^/\.git\%(/\|$\)\|^$'
3908 let range = "0," . (a:count ? a:count : bufnr(''))
3909 let extra = ['.' . path]
3910 if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3911 let state.follow = 1
3912 if !s:HasOpt(args, '--follow')
3913 call insert(args, '--follow')
3915 if !s:HasOpt(args, '--summary')
3916 call insert(args, '--summary')
3917 let state.ignore_summary = 1
3921 if !s:HasOpt(args, '--merges', '--no-merges')
3922 call insert(args, '--no-merges')
3924 call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3926 if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3927 let owner = s:Owner(@%, dir)
3929 call add(args, owner)
3935 if s:HasOpt(args, '-g', '--walk-reflogs')
3936 let format = "%gd\t%H %gs"
3938 let format = "%h\t%H " . g:fugitive_summary_format
3940 let cmd = ['--no-pager']
3941 if fugitive#GitVersion(1, 9)
3942 call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3944 call extend(cmd, ['log', '-U0', '--no-patch'])
3947 \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3948 \ args + paths + extra)
3949 let state.target = path
3950 let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3951 if empty(paths + extra) && empty(a:type) && len(s:Relative('/'))
3952 let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3954 return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3957 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3959 function! s:UsableWin(nr) abort
3960 return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3961 \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3962 \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3963 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3966 function! s:OpenParse(args, wants_cmd) abort
3969 let args = copy(a:args)
3971 if args[0] =~# '^++'
3972 call add(opts, ' ' . escape(remove(args, 0), ' |"'))
3973 elseif a:wants_cmd && args[0] =~# '^+'
3974 call add(cmds, remove(args, 0)[1:-1])
3980 let file = join(args)
3981 elseif empty(expand('%'))
3983 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
3989 let efile = s:Expand(file)
3990 let url = fugitive#Find(efile, dir)
3992 if a:wants_cmd && file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
3993 let line = line('.')
3994 if expand('%:p') !=# url
3995 let diffcmd = 'diff'
3996 let from = s:DirRev(@%)[1]
3997 let to = s:DirRev(url)[1]
3998 if empty(from) && empty(to)
3999 let diffcmd = 'diff-files'
4000 let args = ['--', expand('%:p'), url]
4002 let args = [from, '--', url]
4004 let args = [to, '--', expand('%:p')]
4007 let args = [from, to]
4009 let [res, exec_error] = s:LinesError([dir, diffcmd, '-U0'] + args)
4011 call filter(res, 'v:val =~# "^@@ "')
4012 call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
4013 call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
4014 if exists('reverse')
4015 call map(res, 'v:val[2:3] + v:val[0:1]')
4017 call filter(res, 'v:val[0] < '.line('.'))
4018 let hunk = get(res, -1, [0,0,0,0])
4019 if hunk[0] + hunk[1] > line('.')
4020 let line = hunk[2] + max([1 - hunk[3], 0])
4022 let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
4026 call insert(cmds, line)
4029 let pre = join(opts, '')
4031 let pre .= ' +' . escape(join(map(cmds, '"exe ".string(v:val)'), '|'), ' |"')
4033 let pre .= ' +' . escape(cmds[0], ' |"')
4038 function! s:DiffClose() abort
4039 let mywinnr = winnr()
4040 for winnr in [winnr('#')] + range(winnr('$'),1,-1)
4041 if winnr != mywinnr && getwinvar(winnr,'&diff')
4042 execute winnr.'wincmd w'
4052 function! s:BlurStatus() abort
4053 if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
4054 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
4056 exe winnrs[0].'wincmd w'
4066 function! s:OpenExec(cmd, mods, args, ...) abort
4067 let dir = a:0 ? s:Dir(a:1) : s:Dir()
4068 let temp = tempname()
4069 let columns = get(g:, 'fugitive_columns', 80)
4073 let env = 'set COLUMNS=' . columns . '& '
4075 let env = 'env COLUMNS=' . columns . ' '
4077 silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
4078 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
4080 let temp = s:Resolve(temp)
4081 let first = join(readfile(temp, '', 2), "\n")
4082 if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
4083 let filetype = 'man'
4085 let filetype = 'git'
4087 let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
4091 silent execute s:Mods(a:mods) . a:cmd temp
4092 call fugitive#ReloadStatus(dir, 1)
4093 return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
4096 function! fugitive#Open(cmd, bang, mods, arg, args) abort
4098 return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
4101 let mods = s:Mods(a:mods)
4103 let [file, pre] = s:OpenParse(a:args, 1)
4105 return 'echoerr ' . string(v:exception)
4107 if file !~# '^\a\a\+:'
4108 let file = s:sub(file, '/$', '')
4113 return mods . a:cmd . pre . ' ' . s:fnameescape(file)
4116 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, args) abort
4117 let mods = s:Mods(a:mods)
4120 let delete = 'silent 1,' . line('$') . 'delete_|'
4121 let after = line('$')
4123 let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4129 let args = s:SplitExpand(a:arg, s:Tree(dir))
4130 silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4131 execute delete . 'diffupdate'
4132 call fugitive#ReloadStatus(dir, 1)
4133 return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4136 let [file, pre] = s:OpenParse(a:args, 0)
4138 return 'echoerr ' . string(v:exception)
4140 if file =~# '^fugitive:' && after is# 0
4141 return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4144 exe after . 'foldopen!'
4146 return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4149 function! fugitive#ReadComplete(A, L, P) abort
4151 return fugitive#Complete(a:A, a:L, a:P)
4153 return fugitive#CompleteObject(a:A, a:L, a:P)
4157 " Section: :Gwrite, :Gwq
4159 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, args) abort
4160 if exists('b:fugitive_commit_arguments')
4161 return 'write|bdelete'
4162 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4164 elseif get(b:, 'fugitive_type', '') ==# 'index'
4166 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4167 let filename = getline(4)[6:-1]
4170 setlocal buftype=nowrite
4171 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4172 let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4174 let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4184 return 'Gedit '.fnameescape(filename)
4187 let mytab = tabpagenr()
4188 let mybufnr = bufnr('')
4190 let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4192 return 'echoerr ' . string(v:exception)
4195 return 'echoerr '.string('fugitive: cannot determine file path')
4197 if file =~# '^fugitive:'
4198 return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4201 let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4202 if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4203 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4204 return 'echoerr v:errmsg'
4207 for nr in range(1,bufnr('$'))
4208 if fnamemodify(bufname(nr),':p') ==# file
4213 if treebufnr > 0 && treebufnr != bufnr('')
4214 let temp = tempname()
4215 silent execute 'keepalt %write '.temp
4216 for tab in [mytab] + range(1,tabpagenr('$'))
4217 for winnr in range(1,tabpagewinnr(tab,'$'))
4218 if tabpagebuflist(tab)[winnr-1] == treebufnr
4219 execute 'tabnext '.tab
4221 execute winnr.'wincmd w'
4222 let restorewinnr = 1
4225 let lnum = line('.')
4226 let last = line('$')
4227 silent execute '$read '.temp
4228 silent execute '1,'.last.'delete_'
4234 if exists('restorewinnr')
4237 execute 'tabnext '.mytab
4244 call writefile(readfile(temp,'b'),file,'b')
4247 execute 'write! '.s:fnameescape(file)
4251 let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4253 let [error, exec_error] = s:ChompError(['add', '--', file])
4256 let v:errmsg = 'fugitive: '.error
4257 return 'echoerr v:errmsg'
4259 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4263 let one = s:Generate(':1:'.file)
4264 let two = s:Generate(':2:'.file)
4265 let three = s:Generate(':3:'.file)
4266 for nr in range(1,bufnr('$'))
4267 let name = fnamemodify(bufname(nr), ':p')
4268 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4269 execute nr.'bdelete'
4274 let zero = s:Generate(':0:'.file)
4275 silent exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
4276 for tab in range(1,tabpagenr('$'))
4277 for winnr in range(1,tabpagewinnr(tab,'$'))
4278 let bufnr = tabpagebuflist(tab)[winnr-1]
4279 let bufname = fnamemodify(bufname(bufnr), ':p')
4280 if bufname ==# zero && bufnr != mybufnr
4281 execute 'tabnext '.tab
4283 execute winnr.'wincmd w'
4284 let restorewinnr = 1
4287 let lnum = line('.')
4288 let last = line('$')
4289 silent execute '$read '.s:fnameescape(file)
4290 silent execute '1,'.last.'delete_'
4295 if exists('restorewinnr')
4298 execute 'tabnext '.mytab
4304 call fugitive#ReloadStatus(-1, 1)
4308 function! fugitive#WqCommand(...) abort
4309 let bang = a:4 ? '!' : ''
4310 if exists('b:fugitive_commit_arguments')
4313 let result = call('fugitive#WriteCommand', a:000)
4314 if result =~# '^\%(write\|wq\|echoerr\)'
4315 return s:sub(result,'^write','wq')
4317 return result.'|quit'.bang
4321 augroup fugitive_commit
4323 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4326 " Section: :Gpush, :Gfetch
4328 function! fugitive#PushComplete(A, L, P) abort
4329 return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4332 function! fugitive#FetchComplete(A, L, P) abort
4333 return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4336 function! s:AskPassArgs(dir) abort
4337 if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4338 \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4339 if s:executable(s:ExecPath() . '/git-gui--askpass')
4340 return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4341 elseif s:executable('ssh-askpass')
4342 return ['-c', 'core.askPass=ssh-askpass']
4348 function! s:Dispatch(bang, cmd, args) abort
4350 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4352 let b:current_compiler = 'git'
4353 let &l:errorformat = s:common_efm .
4354 \ ',%\&git_dir=' . escape(substitute(dir, '%', '%%', 'g'), '\,')
4355 let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4356 if exists(':Make') == 2
4360 if !has('patch-8.1.0334') && has('terminal') && &autowrite
4361 let autowrite_was_set = 1
4365 silent noautocmd make!
4367 return 'call fugitive#Cwindow()|silent ' . s:DoAutocmd('ShellCmdPost')
4370 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4371 if empty(cc) | unlet! b:current_compiler | endif
4372 if exists('autowrite_was_set')
4378 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4379 return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4382 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4383 return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4386 call s:command("-nargs=? -bang -complete=customlist,fugitive#PushComplete Gpush", "push")
4387 call s:command("-nargs=? -bang -complete=customlist,fugitive#FetchComplete Gfetch", "fetch")
4391 augroup fugitive_diff
4393 autocmd BufWinLeave *
4394 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4395 \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4397 autocmd BufWinEnter *
4398 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4399 \ call s:diffoff() |
4403 function! s:can_diffoff(buf) abort
4404 return getwinvar(bufwinnr(a:buf), '&diff') &&
4405 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4408 function! fugitive#CanDiffoff(buf) abort
4409 return s:can_diffoff(bufnr(a:buf))
4412 function! s:diff_modifier(count) abort
4413 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4414 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4416 elseif &diffopt =~# 'vertical'
4418 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4425 function! s:diff_window_count() abort
4427 for nr in range(1,winnr('$'))
4428 let c += getwinvar(nr,'&diff')
4433 function! s:diff_restore() abort
4434 let restore = 'setlocal nodiff noscrollbind'
4435 \ . ' scrollopt=' . &l:scrollopt
4436 \ . (&l:wrap ? ' wrap' : ' nowrap')
4437 \ . ' foldlevel=999'
4438 \ . ' foldmethod=' . &l:foldmethod
4439 \ . ' foldcolumn=' . &l:foldcolumn
4440 \ . ' foldlevel=' . &l:foldlevel
4441 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4442 if has('cursorbind')
4443 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4448 function! s:diffthis() abort
4450 let w:fugitive_diff_restore = s:diff_restore()
4455 function! s:diffoff() abort
4456 if exists('w:fugitive_diff_restore')
4457 execute w:fugitive_diff_restore
4458 unlet w:fugitive_diff_restore
4464 function! s:diffoff_all(dir) abort
4465 let curwin = winnr()
4466 for nr in range(1,winnr('$'))
4467 if getwinvar(nr,'&diff')
4469 execute nr.'wincmd w'
4470 let restorewinnr = 1
4472 if s:Dir() ==# a:dir
4477 execute curwin.'wincmd w'
4480 function! s:CompareAge(mine, theirs) abort
4481 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4482 let mine = substitute(a:mine, '^:', '', '')
4483 let theirs = substitute(a:theirs, '^:', '', '')
4484 let my_score = get(scores, ':'.mine, 0)
4485 let their_score = get(scores, ':'.theirs, 0)
4486 if my_score || their_score
4487 return my_score < their_score ? -1 : my_score != their_score
4488 elseif mine ==# theirs
4491 let base = s:TreeChomp('merge-base', mine, theirs)
4494 elseif base ==# theirs
4497 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4498 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4499 return my_time < their_time ? -1 : my_time != their_time
4502 function! s:IsConflicted() abort
4503 return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4506 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, args) abort
4507 let args = copy(a:args)
4509 if get(args, 0) =~# '^+'
4510 let post = remove(args, 0)[1:-1]
4512 if exists(':DiffGitCached') && empty(args)
4513 return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4515 let commit = s:DirCommitFile(@%)[1]
4516 if a:mods =~# '\<tab\>'
4517 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4518 let pre = 'tab split'
4520 let mods = 'keepalt ' . a:mods
4523 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4524 if (empty(args) || args[0] ==# ':') && a:keepfocus
4526 if empty(commit) && s:IsConflicted()
4527 let parents = [s:Relative(':2:'), s:Relative(':3:')]
4528 elseif empty(commit)
4529 let parents = [s:Relative(':0:')]
4530 elseif commit =~# '^\d\=$'
4531 let parents = [s:Relative('HEAD:')]
4532 elseif commit =~# '^\x\x\+$'
4533 let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4534 call map(parents, 's:Relative(v:val . ":")')
4538 if exists('parents') && len(parents) > 1
4540 let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4542 execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4543 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4547 call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4548 let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4549 for i in range(len(parents)-1, 1, -1)
4550 execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4551 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4555 call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4563 let arg = join(args, ' ')
4568 let file = s:Relative()
4571 let file = s:Relative(':0:')
4572 elseif arg =~# '^:\d$'
4574 let file = s:Relative(arg . ':')
4577 let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4579 return 'echoerr ' . string(v:exception)
4582 elseif exists('parents') && len(parents)
4583 let file = parents[-1]
4585 let file = s:Relative()
4586 elseif s:IsConflicted()
4587 let file = s:Relative(':1:')
4588 let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4591 let file = s:Relative(':0:')
4593 let spec = s:Generate(file)
4594 if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4595 let spec = FugitiveVimPath(spec . s:Relative('/'))
4598 let restore = s:diff_restore()
4599 let w:fugitive_diff_restore = restore
4600 if len(spec) && s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4601 let mods = s:Mods(mods, 'rightbelow')
4603 let mods = s:Mods(mods, 'leftabove')
4605 let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4606 if &diffopt =~# 'vertical'
4607 let diffopt = &diffopt
4608 set diffopt-=vertical
4610 execute mods 'diffsplit' s:fnameescape(spec)
4611 let &l:readonly = &l:readonly
4613 let w:fugitive_diff_restore = restore
4615 if getwinvar('#', '&diff')
4622 return 'echoerr ' . string(v:exception)
4624 if exists('diffopt')
4625 let &diffopt = diffopt
4630 " Section: :Gmove, :Gremove
4632 function! s:Move(force, rename, destination) abort
4635 if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
4636 return 'echoerr ' . string('fugitive: mv not supported for this buffer')
4638 if a:destination =~# '^\.\.\=\%(/\|$\)'
4639 let destination = simplify(getcwd() . '/' . a:destination)
4640 elseif a:destination =~# '^\a\+:\|^/'
4641 let destination = a:destination
4642 elseif a:destination =~# '^:/:\='
4643 let destination = s:Tree(dir) . substitute(a:destination, '^:/:\=', '', '')
4644 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4645 let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
4646 elseif a:destination =~# '^:(literal)'
4647 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4649 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4651 let destination = s:Tree(dir) . '/' . a:destination
4653 let destination = s:Slash(destination)
4657 let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
4659 let v:errmsg = 'fugitive: '.message
4660 return 'echoerr v:errmsg'
4662 if isdirectory(destination)
4663 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4665 let reload = '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4666 if empty(s:DirCommitFile(@%)[1])
4667 if isdirectory(destination)
4668 return 'keepalt edit '.s:fnameescape(destination) . reload
4670 return 'keepalt saveas! '.s:fnameescape(destination) . reload
4673 return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
4677 function! fugitive#RenameComplete(A,L,P) abort
4678 if a:A =~# '^[.:]\=/'
4679 return fugitive#CompletePath(a:A)
4681 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4682 return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4686 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, args) abort
4687 return s:Move(a:bang, 0, a:arg)
4690 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, args) abort
4691 return s:Move(a:bang, 1, a:arg)
4694 function! s:Remove(after, force) abort
4697 if len(@%) && s:DirCommitFile(@%)[1] ==# ''
4699 elseif s:DirCommitFile(@%)[1] ==# '0'
4700 let cmd = ['rm','--cached']
4702 return 'echoerr ' . string('fugitive: rm not supported for this buffer')
4705 let cmd += ['--force']
4707 let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')], dir)
4709 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4710 return 'echoerr '.string(v:errmsg)
4712 return a:after . (a:force ? '!' : ''). '|call fugitive#ReloadStatus(' . string(dir) . ', 1)'
4716 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, args) abort
4717 return s:Remove('edit', a:bang)
4720 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, args) abort
4721 return s:Remove('bdelete', a:bang)
4726 function! s:Keywordprg() abort
4727 let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4728 if has('gui_running') && !has('win32')
4729 return s:UserCommand() . ' --no-pager' . args . ' log -1'
4731 return s:UserCommand() . args . ' show'
4735 function! s:linechars(pattern) abort
4736 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4737 if exists('*synconcealed') && &conceallevel > 1
4738 for col in range(1, chars)
4739 let chars -= synconcealed(line('.'), col)[0]
4745 function! s:BlameBufnr(...) abort
4746 let state = s:TempState(bufname(a:0 ? a:1 : ''))
4747 if get(state, 'filetype', '') ==# 'fugitiveblame'
4748 return get(state, 'bufnr', -1)
4754 function! s:BlameCommitFileLnum(...) abort
4755 let line = a:0 ? a:1 : getline('.')
4756 let state = a:0 ? a:2 : s:TempState()
4757 let commit = matchstr(line, '^\^\=\zs\x\+')
4758 if commit =~# '^0\+$'
4760 elseif line !~# '^\^' && has_key(state, 'blame_reverse_end')
4761 let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4763 let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4764 let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4765 if empty(path) && lnum
4766 let path = get(state, 'blame_file', '')
4768 return [commit, path, lnum]
4771 function! s:BlameLeave() abort
4772 let bufwinnr = bufwinnr(s:BlameBufnr())
4774 let bufnr = bufnr('')
4775 exe bufwinnr . 'wincmd w'
4776 return bufnr . 'bdelete'
4781 function! s:BlameQuit() abort
4782 let cmd = s:BlameLeave()
4785 elseif len(s:DirCommitFile(@%)[1])
4786 return cmd . '|Gedit'
4792 function! fugitive#BlameComplete(A, L, P) abort
4793 return s:CompleteSub('blame', a:A, a:L, a:P)
4796 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4798 let flags = copy(a:args)
4804 if a:line1 > 0 && a:count > 0 && a:range != 1
4805 call extend(ranges, ['-L', a:line1 . ',' . a:count])
4807 while i < len(flags)
4808 let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4809 if len(match) && len(match[2])
4810 call insert(flags, match[1])
4811 let flags[i+1] = '-' . match[2]
4815 if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4817 elseif arg ==# '--contents' && i + 1 < len(flags)
4818 call extend(commits, remove(flags, i, i+1))
4820 elseif arg ==# '-L' && i + 1 < len(flags)
4821 call extend(ranges, remove(flags, i, i+1))
4823 elseif arg =~# '^--contents='
4824 call add(commits, remove(flags, i))
4826 elseif arg =~# '^-L.'
4827 call add(ranges, remove(flags, i))
4829 elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4833 echo s:ChompError(['blame', arg])[0]
4838 if i + 1 < len(flags)
4839 call extend(files, remove(flags, i + 1, -1))
4841 call remove(flags, i)
4843 elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4844 if index(flags, '--') >= 0
4845 call add(commits, remove(flags, i))
4848 if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4849 call add(commits, remove(flags, i))
4853 let dcf = s:DirCommitFile(fugitive#Find(arg))
4854 if len(dcf[1]) && empty(dcf[2])
4855 call add(commits, remove(flags, i))
4860 call add(files, remove(flags, i))
4865 let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4866 if empty(commits) && len(files) > 1
4867 call add(commits, remove(files, 1))
4871 let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4872 call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4873 if a:count > 0 && empty(ranges)
4874 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4876 call extend(cmd, ranges)
4879 elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4880 let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4881 elseif empty(files) && !s:HasOpt(flags, '--reverse')
4882 let cmd += ['--contents', '-']
4884 let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4885 let tempname = tempname()
4886 let error = tempname . '.err'
4887 let temp = tempname . (raw ? '' : '.fugitiveblame')
4889 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4891 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4893 let l:shell_error = v:shell_error
4897 let lines = readfile(error)
4899 let lines = readfile(temp)
4901 for i in range(len(lines))
4902 if lines[i] =~# '^error: \|^fatal: '
4910 if i != len(lines) - 1
4916 let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4917 if s:HasOpt(flags, '--reverse')
4918 let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4920 if (a:line1 == 0 || a:range == 1) && a:count > 0
4921 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4922 return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4924 let temp = s:Resolve(temp)
4925 let s:temp_files[s:cpath(temp)] = temp_state
4926 if len(ranges + commits + files) || raw
4927 let mods = s:Mods(a:mods)
4929 exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4930 elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4931 exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4933 return mods . 'edit ' . s:fnameescape(temp)
4937 if a:mods =~# '\<tab\>'
4940 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4941 for winnr in range(winnr('$'),1,-1)
4942 if getwinvar(winnr, '&scrollbind')
4943 call setwinvar(winnr, '&scrollbind', 0)
4945 if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4946 call setwinvar(winnr, '&cursorbind', 0)
4948 if s:BlameBufnr(winbufnr(winnr)) > 0
4949 execute winbufnr(winnr).'bdelete'
4952 let bufnr = bufnr('')
4953 let temp_state.bufnr = bufnr
4954 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4955 if exists('+cursorbind')
4956 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4959 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4962 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4964 setlocal scrollbind nowrap nofoldenable
4965 if exists('+cursorbind')
4968 let top = line('w0') + &scrolloff
4969 let current = line('.')
4970 exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
4971 let w:fugitive_leave = restore
4975 if exists('+cursorbind')
4978 setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
4979 if exists('+relativenumber')
4980 setlocal norelativenumber
4982 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
4983 call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
4984 call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
4985 call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
4992 return 'echoerr ' . string(v:exception)
4996 function! s:BlameCommit(cmd, ...) abort
4997 let line = a:0 ? a:1 : getline('.')
4998 let state = a:0 ? a:2 : s:TempState()
4999 let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
5000 let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
5001 let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
5002 if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
5003 let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
5004 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5006 if commit =~# '^0*$'
5007 return 'echoerr ' . string('fugitive: no commit')
5009 if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
5010 let path = commit . ':' . path
5011 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
5013 let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
5014 if cmd =~# '^echoerr'
5018 if a:cmd ==# 'pedit' || empty(path)
5021 if search('^diff .* b/\M'.escape(path,'\').'$','W')
5023 let head = line('.')
5024 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
5025 let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
5026 let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
5027 if lnum >= top && lnum <= top + len
5028 let offset = lnum - top
5036 while offset > 0 && line('.') < line('$')
5038 if getline('.') =~# '^[ ' . sigil . ']'
5051 function! s:BlameJump(suffix, ...) abort
5052 let suffix = a:suffix
5053 let [commit, path, lnum] = s:BlameCommitFileLnum()
5055 return 'echoerr ' . string('fugitive: could not determine filename for blame')
5057 if commit =~# '^0*$'
5061 let offset = line('.') - line('w0')
5062 let flags = get(s:TempState(), 'blame_flags', [])
5064 if s:HasOpt(flags, '--reverse')
5065 call remove(flags, '--reverse')
5067 call add(flags, '--reverse')
5070 let blame_bufnr = s:BlameBufnr()
5072 let bufnr = bufnr('')
5073 let winnr = bufwinnr(blame_bufnr)
5075 exe winnr.'wincmd w'
5077 execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
5083 if exists(':Gblame')
5084 let my_bufnr = bufnr('')
5086 let blame_args = flags + [commit . suffix, '--', path]
5087 let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
5089 let blame_args = flags
5090 let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
5092 if bufnr('') == my_bufnr
5097 let delta = line('.') - line('w0') - offset
5099 execute 'normal! '.delta."\<C-E>"
5101 execute 'normal! '.(-delta)."\<C-Y>"
5105 echo ':Gblame' s:fnameescape(blame_args)
5110 let s:hash_colors = {}
5112 function! fugitive#BlameSyntax() abort
5113 let conceal = has('conceal') ? ' conceal' : ''
5114 let config = fugitive#Config()
5115 let flags = get(s:TempState(), 'blame_flags', [])
5116 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
5117 syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5118 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5119 if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5120 syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5122 syn match FugitiveblameBoundary "^\^"
5124 syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5125 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5126 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5127 exec 'syn match FugitiveblameLineNumber "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5128 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)
5129 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5130 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5131 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
5132 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5133 hi def link FugitiveblameBoundary Keyword
5134 hi def link FugitiveblameHash Identifier
5135 hi def link FugitiveblameBoundaryIgnore Ignore
5136 hi def link FugitiveblameUncommitted Ignore
5137 hi def link FugitiveblameScoreDebug Debug
5138 hi def link FugitiveblameTime PreProc
5139 hi def link FugitiveblameLineNumber Number
5140 hi def link FugitiveblameOriginalFile String
5141 hi def link FugitiveblameOriginalLineNumber Float
5142 hi def link FugitiveblameShort FugitiveblameDelimiter
5143 hi def link FugitiveblameDelimiter Delimiter
5144 hi def link FugitiveblameNotCommittedYet Comment
5145 if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5149 for lnum in range(1, line('$'))
5150 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5151 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5155 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5156 \ && empty(get(s:hash_colors, hash))
5157 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5158 let color = csapprox#per_component#Approximate(r, g, b)
5159 if color == 16 && &background ==# 'dark'
5162 let s:hash_colors[hash] = ' ctermfg='.color
5164 let s:hash_colors[hash] = ''
5166 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5168 call s:BlameRehighlight()
5171 function! s:BlameRehighlight() abort
5172 for [hash, cterm] in items(s:hash_colors)
5173 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5174 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5176 exe 'hi link FugitiveblameHash'.hash.' Identifier'
5181 function! s:BlameFileType() abort
5183 setlocal foldmethod=manual
5185 let &l:keywordprg = s:Keywordprg()
5187 let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5188 if exists('+concealcursor')
5189 setlocal concealcursor=nc conceallevel=2
5190 let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5195 call s:Map('n', '<F1>', ':help :Gblame<CR>', '<silent>')
5196 call s:Map('n', 'g?', ':help :Gblame<CR>', '<silent>')
5197 if mapcheck('q', 'n') =~# '^$\|bdelete'
5198 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>')
5200 call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>')
5201 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5202 call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5203 call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5204 call s:Map('n', 'P', ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5205 call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5206 call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5207 call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5208 call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5209 call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5212 augroup fugitive_blame
5214 autocmd FileType fugitiveblame call s:BlameFileType()
5215 autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5216 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5219 call s:command('-buffer -bang -range=-1 -nargs=? -complete=customlist,fugitive#BlameComplete Gblame', 'blame')
5223 let s:redirects = {}
5225 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, args) abort
5229 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5232 return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5234 return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5237 let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5238 let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5244 let rev = s:DirRev(@%)[1]
5247 let expanded = s:Relative()
5249 let expanded = s:Expand(rev)
5251 let cdir = FugitiveVimPath(fugitive#CommonDir(dir))
5252 for subdir in ['tags/', 'heads/', 'remotes/']
5253 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5254 let expanded = '.git/refs/' . subdir . expanded
5257 let full = fugitive#Find(expanded, dir)
5259 if full =~? '^fugitive:'
5260 let [pathdir, commit, path] = s:DirCommitFile(full)
5261 if commit =~# '^:\=\d$'
5265 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5266 let branch = matchstr(expanded, '^[^:]*')
5270 let path = path[1:-1]
5271 elseif empty(s:Tree(dir))
5272 let path = '.git/' . full[strlen(dir)+1:-1]
5275 let path = fugitive#Path(full, '/')[1:-1]
5276 if path =~# '^\.git/'
5278 elseif isdirectory(full) || empty(path)
5284 if type ==# 'tree' && !empty(path)
5285 let path = s:sub(path, '/\=$', '/')
5287 if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5288 let body = readfile(dir . '/' . path[5:-1])[0]
5289 if body =~# '^\x\{40,\}$'
5293 elseif body =~# '^ref: refs/'
5294 let path = '.git/' . matchstr(body,'ref: \zs.*')
5299 if path =~# '^\.git/refs/remotes/.'
5301 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5302 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5304 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5305 let path = '.git/refs/heads/'.merge
5307 elseif path =~# '^\.git/refs/heads/.'
5308 let branch = path[16:-1]
5309 elseif !exists('branch')
5310 let branch = FugitiveHead()
5313 let r = fugitive#Config('branch.'.branch.'.remote')
5314 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5315 if r ==# '.' && !empty(m)
5316 let r2 = fugitive#Config('branch.'.m.'.remote')
5319 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5325 if r ==# '.' || r ==# remote
5327 if path =~# '^\.git/refs/heads/.'
5328 let path = '.git/refs/heads/'.merge
5333 let line1 = a:count > 0 ? a:line1 : 0
5334 let line2 = a:count > 0 ? a:count : 0
5335 if empty(commit) && path !~# '^\.git/'
5336 if a:count < 0 && !empty(merge)
5341 let owner = s:Owner(@%)
5342 let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5346 if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5347 let blame_list = tempname()
5348 call writefile([commit, ''], blame_list, 'b')
5349 let blame_in = tempname()
5350 silent exe '%write' blame_in
5351 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])
5353 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5354 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5355 let line1 = +matchstr(blame[0], blame_regex)
5356 let line2 = +matchstr(blame[-1], blame_regex)
5358 call s:throw("Can't browse to uncommitted change")
5365 let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5368 while commit =~# '^ref: ' && i < 10
5369 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5377 let raw = fugitive#RemoteUrl(remote)
5382 if raw =~# '^https\=://' && s:executable('curl')
5383 if !has_key(s:redirects, raw)
5384 let s:redirects[raw] = matchstr(system('curl -I ' .
5385 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5386 \ 'Location: \zs\S\+\ze/info/refs?')
5388 if len(s:redirects[raw])
5389 let raw = s:redirects[raw]
5395 \ 'repo': fugitive#repo(dir),
5397 \ 'revision': 'No longer provided',
5405 for Handler in get(g:, 'fugitive_browse_handlers', [])
5406 let url = call(Handler, [copy(opts)])
5413 call s:throw("No Gbrowse handler installed for '".raw."'")
5416 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5421 return 'echomsg '.string(url)
5422 elseif exists(':Browse') == 2
5423 return 'echomsg '.string(url).'|Browse '.url
5425 if !exists('g:loaded_netrw')
5426 runtime! autoload/netrw.vim
5428 if exists('*netrw#BrowseX')
5429 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5431 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5435 return 'echoerr ' . string(v:exception)
5439 " Section: Go to file
5441 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5442 function! fugitive#MapCfile(...) abort
5443 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5444 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5445 if !exists('g:fugitive_no_maps')
5446 call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5447 call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5448 call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5449 call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5450 call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5454 function! s:ContainingCommit() abort
5455 let commit = s:Owner(@%)
5456 return empty(commit) ? 'HEAD' : commit
5459 function! s:SquashArgument(...) abort
5460 if &filetype == 'fugitive'
5461 let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5462 elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5463 let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5465 let commit = s:Owner(@%)
5467 return len(commit) && a:0 ? printf(a:1, commit) : commit
5470 function! s:RebaseArgument() abort
5471 return s:SquashArgument(' %s^')
5474 function! s:NavigateUp(count) abort
5475 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5479 let rev = matchstr(rev, '.*\ze/.\+', '')
5480 elseif rev =~# '.:.'
5481 let rev = matchstr(rev, '^.[^:]*:')
5494 function! s:MapMotion(lhs, rhs) abort
5495 call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5496 call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5497 call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5500 function! fugitive#MapJumps(...) abort
5502 if get(b:, 'fugitive_type', '') ==# 'blob'
5503 let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5504 call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5505 call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5506 call s:Map('n', 'o', ':<C-U>0,2' . blame_map, '<silent>')
5507 call s:Map('n', 'p', ':<C-U>0,3' . blame_map, '<silent>')
5508 call s:Map('n', 'gO', ':<C-U>0,4' . blame_map, '<silent>')
5509 call s:Map('n', 'O', ':<C-U>0,5' . blame_map, '<silent>')
5511 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>')
5512 call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5513 call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5514 call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5515 call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5516 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5519 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5520 call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5521 call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5522 call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5523 call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5524 call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5526 if !exists('g:fugitive_no_maps')
5527 if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5528 nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5530 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5532 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5534 call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5535 call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5536 call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5537 call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5538 call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5539 call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5540 call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5541 call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5542 call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5543 call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5544 call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5545 call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5546 call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5547 call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5548 call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5549 call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5551 call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5552 call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5553 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>')
5554 call s:Map('n', 'P', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5555 call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5556 call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5557 call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5558 call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5559 call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5560 call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5561 call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5563 nnoremap <buffer> c<Space> :Git commit<Space>
5564 nnoremap <buffer> c<CR> :Git commit<CR>
5565 nnoremap <buffer> cv<Space> :Git commit -v<Space>
5566 nnoremap <buffer> cv<CR> :Git commit -v<CR>
5567 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
5568 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
5569 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
5570 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
5571 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
5572 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
5573 nnoremap <buffer> <silent> cRa :<C-U>Gcommit --reset-author --amend<CR>
5574 nnoremap <buffer> <silent> cRe :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5575 nnoremap <buffer> <silent> cRw :<C-U>Gcommit --reset-author --amend --only<CR>
5576 nnoremap <buffer> cf :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5577 nnoremap <buffer> cF :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5578 nnoremap <buffer> cs :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5579 nnoremap <buffer> cS :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5580 nnoremap <buffer> cA :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5581 nnoremap <buffer> <silent> c? :<C-U>help fugitive_c<CR>
5583 nnoremap <buffer> cr<Space> :Git revert<Space>
5584 nnoremap <buffer> cr<CR> :Git revert<CR>
5585 nnoremap <buffer> <silent> crc :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5586 nnoremap <buffer> <silent> crn :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5587 nnoremap <buffer> <silent> cr? :help fugitive_cr<CR>
5589 nnoremap <buffer> cm<Space> :Git merge<Space>
5590 nnoremap <buffer> cm<CR> :Git merge<CR>
5591 nnoremap <buffer> <silent> cm? :help fugitive_cm<CR>
5593 nnoremap <buffer> cz<Space> :Git stash<Space>
5594 nnoremap <buffer> cz<CR> :Git stash<CR>
5595 nnoremap <buffer> <silent> cza :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5596 nnoremap <buffer> <silent> czA :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5597 nnoremap <buffer> <silent> czp :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5598 nnoremap <buffer> <silent> czP :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5599 nnoremap <buffer> <silent> czv :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5600 nnoremap <buffer> <silent> czw :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5601 nnoremap <buffer> <silent> czz :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5602 nnoremap <buffer> <silent> cz? :<C-U>help fugitive_cz<CR>
5604 nnoremap <buffer> co<Space> :Git checkout<Space>
5605 nnoremap <buffer> co<CR> :Git checkout<CR>
5606 nnoremap <buffer> coo :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5607 nnoremap <buffer> co? :<C-U>help fugitive_co<CR>
5609 nnoremap <buffer> cb<Space> :Git branch<Space>
5610 nnoremap <buffer> cb<CR> :Git branch<CR>
5611 nnoremap <buffer> cb? :<C-U>help fugitive_cb<CR>
5613 nnoremap <buffer> r<Space> :Git rebase<Space>
5614 nnoremap <buffer> r<CR> :Git rebase<CR>
5615 nnoremap <buffer> <silent> ri :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5616 nnoremap <buffer> <silent> rf :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5617 nnoremap <buffer> <silent> ru :<C-U>Grebase --interactive @{upstream}<CR>
5618 nnoremap <buffer> <silent> rp :<C-U>Grebase --interactive @{push}<CR>
5619 nnoremap <buffer> <silent> rw :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5620 nnoremap <buffer> <silent> rm :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5621 nnoremap <buffer> <silent> rd :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5622 nnoremap <buffer> <silent> rk :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5623 nnoremap <buffer> <silent> rx :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5624 nnoremap <buffer> <silent> rr :<C-U>Grebase --continue<CR>
5625 nnoremap <buffer> <silent> rs :<C-U>Grebase --skip<CR>
5626 nnoremap <buffer> <silent> re :<C-U>Grebase --edit-todo<CR>
5627 nnoremap <buffer> <silent> ra :<C-U>Grebase --abort<CR>
5628 nnoremap <buffer> <silent> r? :<C-U>help fugitive_r<CR>
5630 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5631 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5632 call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
5633 call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
5637 function! s:StatusCfile(...) abort
5639 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5640 let info = s:StageInfo()
5641 let line = getline('.')
5642 if len(info.sigil) && len(info.section) && len(info.paths)
5643 if info.section ==# 'Unstaged' && info.sigil !=# '-'
5644 return [lead . info.relative[0], info.offset, 'normal!zv']
5645 elseif info.section ==# 'Staged' && info.sigil ==# '-'
5646 return ['@:' . info.relative[0], info.offset, 'normal!zv']
5648 return [':0:' . info.relative[0], info.offset, 'normal!zv']
5650 elseif len(info.paths)
5651 return [lead . info.relative[0]]
5652 elseif len(info.commit)
5653 return [info.commit]
5654 elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5655 return [matchstr(line, ' \zs.*')]
5661 function! fugitive#StatusCfile() abort
5662 let file = s:Generate(s:StatusCfile()[0])
5663 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5666 function! s:MessageCfile(...) abort
5668 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5669 if getline('.') =~# '^.\=\trenamed:.* -> '
5670 return lead . matchstr(getline('.'),' -> \zs.*')
5671 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5672 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5673 elseif getline('.') =~# '^.\=\t.'
5674 return lead . matchstr(getline('.'),'\t\zs.*')
5675 elseif getline('.') =~# ': needs merge$'
5676 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5677 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5679 elseif getline('.') =~# '^\%(. \)\=On branch '
5680 return 'refs/heads/'.getline('.')[12:]
5681 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5682 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5688 function! fugitive#MessageCfile() abort
5689 let file = s:Generate(s:MessageCfile())
5690 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5693 function! s:cfile() abort
5695 let myhash = s:DirRev(@%)[1]
5698 let myhash = fugitive#RevParse(myhash)
5703 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5704 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5707 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5709 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5710 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5712 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5713 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5715 return [treebase . s:sub(getline('.'),'/$','')]
5722 if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5723 let ref = matchstr(getline('.'),'\x\{40,\}')
5724 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5728 if getline('.') =~# '^ref: '
5729 let ref = strpart(getline('.'),5)
5731 elseif getline('.') =~# '^commit \x\{40,\}\>'
5732 let ref = matchstr(getline('.'),'\x\{40,\}')
5735 elseif getline('.') =~# '^parent \x\{40,\}\>'
5736 let ref = matchstr(getline('.'),'\x\{40,\}')
5737 let line = line('.')
5739 while getline(line) =~# '^parent '
5745 elseif getline('.') =~# '^tree \x\{40,\}$'
5746 let ref = matchstr(getline('.'),'\x\{40,\}')
5747 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5748 let ref = myhash.':'
5752 elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5753 let ref = matchstr(getline('.'),'\x\{40,\}')
5754 let type = matchstr(getline(line('.')+1),'type \zs.*')
5756 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5757 let ref = s:DirRev(@%)[1]
5759 elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5760 let ref = matchstr(getline('.'),'\x\{40,\}')
5761 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5763 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5764 let ref = getline('.')[4:]
5766 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5767 let type = getline('.')[0]
5768 let lnum = line('.') - 1
5770 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5771 if getline(lnum) =~# '^[ '.type.']'
5776 let offset += matchstr(getline(lnum), type.'\zs\d\+')
5777 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5778 let dcmds = [offset, 'normal!zv']
5780 elseif getline('.') =~# '^rename from '
5781 let ref = 'a/'.getline('.')[12:]
5782 elseif getline('.') =~# '^rename to '
5783 let ref = 'b/'.getline('.')[10:]
5785 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5786 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5787 let offset = matchstr(getline('.'), '+\zs\d\+')
5789 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5790 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5791 let dcmd = 'Gdiffsplit! +'.offset
5793 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5794 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5795 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5796 let dcmd = 'Gdiffsplit!'
5798 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5799 let line = getline(line('.')-1)
5800 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5801 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5802 let dcmd = 'Gdiffsplit!'
5804 elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5805 let ref = getline('.')
5807 elseif expand('<cword>') =~# '^\x\{7,\}\>'
5808 return [expand('<cword>')]
5823 let prefixes.a = myhash.'^:'
5824 let prefixes.b = myhash.':'
5826 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5828 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5831 if ref ==# '/dev/null'
5833 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5837 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5839 return [ref] + dcmds
5847 function! s:GF(mode) abort
5849 let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5851 return 'echoerr ' . string(v:exception)
5854 return 'G' . a:mode .
5855 \ ' +' . escape(results[1], ' ') . ' ' .
5856 \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5857 elseif len(results) && len(results[0])
5858 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5864 function! fugitive#Cfile() abort
5866 let results = s:cfile()
5868 let cfile = expand('<cfile>')
5869 if &includeexpr =~# '\<v:fname\>'
5870 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5873 elseif len(results) > 1
5874 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5876 return pre . s:fnameescape(s:Generate(results[0]))
5879 " Section: Statusline
5881 function! fugitive#Statusline(...) abort
5882 let dir = s:Dir(bufnr(''))
5887 let commit = s:DirCommitFile(@%)[1]
5889 let status .= ':' . commit[0:6]
5891 let status .= '('.FugitiveHead(7, dir).')'
5892 return '[Git'.status.']'
5895 function! fugitive#statusline(...) abort
5896 return fugitive#Statusline()
5899 function! fugitive#head(...) abort
5904 return fugitive#Head(a:0 ? a:1 : 0)
5909 function! fugitive#Foldtext() abort
5910 if &foldmethod !=# 'syntax'
5914 let line_foldstart = getline(v:foldstart)
5915 if line_foldstart =~# '^diff '
5916 let [add, remove] = [-1, -1]
5918 for lnum in range(v:foldstart, v:foldend)
5919 let line = getline(lnum)
5920 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5921 let filename = line[6:-1]
5925 elseif line =~# '^-'
5927 elseif line =~# '^Binary '
5932 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5935 let filename = line_foldstart[5:-1]
5938 return 'Binary: '.filename
5940 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5942 elseif line_foldstart =~# '^# .*:$'
5943 let lines = getline(v:foldstart, v:foldend)
5944 call filter(lines, 'v:val =~# "^#\t"')
5945 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5946 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5947 return line_foldstart.' '.join(lines, ', ')
5952 function! fugitive#foldtext() abort
5953 return fugitive#Foldtext()
5956 augroup fugitive_folding
5958 autocmd User Fugitive
5959 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5960 \ set foldtext=fugitive#Foldtext() |
5964 " Section: Initialization
5966 function! fugitive#Init() abort
5967 if exists('#User#FugitiveBoot')
5968 exe s:DoAutocmd('User FugitiveBoot')
5971 if &tags !~# '\.git' && @% !~# '\.git' && !exists('s:tags_warning')
5972 let actualdir = fugitive#Find('.git/', dir)
5973 if filereadable(actualdir . 'tags')
5974 let s:tags_warning = 1
5976 echo "Fugitive .git/tags support removed in favor of `:set tags^=./.git/tags;`"
5980 call s:define_commands()
5981 exe s:DoAutocmd('User Fugitive')
5984 function! fugitive#is_git_dir(path) abort
5985 return FugitiveIsGitDir(a:path)
5988 function! fugitive#extract_git_dir(path) abort
5989 return FugitiveExtractGitDir(a:path)
5992 function! fugitive#detect(path) abort
5993 return FugitiveDetect(a:path)