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 let s:nowait = v:version >= 704 ? '<nowait>' : ''
143 function! s:Map(mode, lhs, rhs, ...) abort
144 for mode in split(a:mode, '\zs')
145 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
148 let keys = get(g:, mode.'remap', {})
149 if type(keys) == type([])
153 if has_key(keys, head)
154 let head = keys[head]
160 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
161 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
163 if flags !~# '<unique>' || empty(mapcheck(head.tail, mode))
164 exe mode.'map <buffer>' s:nowait flags head.tail a:rhs
166 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
167 \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
175 function! s:QuickfixGet(nr, ...) abort
177 return call('getqflist', a:000)
179 return call('getloclist', [a:nr] + a:000)
183 function! s:QuickfixSet(nr, ...) abort
185 return call('setqflist', a:000)
187 return call('setloclist', [a:nr] + a:000)
191 function! s:QuickfixCreate(nr, opts) abort
192 if has('patch-7.4.2200')
193 call s:QuickfixSet(a:nr, [], ' ', a:opts)
195 call s:QuickfixSet(a:nr, [], ' ')
199 function! s:QuickfixStream(nr, title, cmd, first, callback, ...) abort
200 call s:QuickfixCreate(a:nr, {'title': a:title})
202 exe a:nr < 0 ? 'copen' : 'lopen'
208 let lines = split(s:SystemError(s:shellesc(a:cmd))[0], "\n")
210 call extend(buffer, call(a:callback, a:000 + [line]))
212 call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
216 call s:QuickfixSet(a:nr, extend(buffer, call(a:callback, a:000 + [0])), 'a')
218 if a:first && len(s:QuickfixGet(a:nr))
220 return a:nr < 0 ? 'cfirst' : 'lfirst'
228 function! s:UserCommandList(...) abort
229 let git = split(get(g:, 'fugitive_git_command', g:fugitive_git_executable), '\s\+')
230 let dir = a:0 ? s:Dir(a:1) : ''
232 let tree = s:Tree(dir)
234 call add(git, '--git-dir=' . FugitiveGitPath(dir))
235 elseif len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
236 if fugitive#GitVersion(1, 8, 5)
237 call extend(git, ['-C', FugitiveGitPath(tree)])
239 throw 'fugitive: Git 1.8.5 or higher required to change directory'
246 function! s:UserCommand(...) abort
247 return s:shellesc(call('s:UserCommandList', a:0 ? [a:1] : []) + (a:0 ? a:2 : []))
250 let s:git_versions = {}
251 function! fugitive#GitVersion(...) abort
252 if !has_key(s:git_versions, g:fugitive_git_executable)
253 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), '\d[^[:space:]]\+')
256 return s:git_versions[g:fugitive_git_executable]
258 let components = split(s:git_versions[g:fugitive_git_executable], '\D\+')
262 for i in range(len(a:000))
263 if a:000[i] > +get(components, i)
265 elseif a:000[i] < +get(components, i)
269 return a:000[i] ==# get(components, i)
272 let s:commondirs = {}
273 function! fugitive#CommonDir(dir) abort
277 if !has_key(s:commondirs, a:dir)
278 if getfsize(a:dir . '/HEAD') < 10
279 let s:commondirs[a:dir] = ''
280 elseif filereadable(a:dir . '/commondir')
281 let cdir = get(readfile(a:dir . '/commondir', 1), 0, '')
282 if cdir =~# '^/\|^\a:/'
283 let s:commondirs[a:dir] = s:Slash(FugitiveVimPath(cdir))
285 let s:commondirs[a:dir] = simplify(a:dir . '/' . cdir)
288 let s:commondirs[a:dir] = a:dir
291 return s:commondirs[a:dir]
294 function! s:Dir(...) abort
295 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
298 function! s:Tree(...) abort
299 return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
302 function! s:HasOpt(args, ...) abort
303 let args = a:args[0 : index(a:args, '--')]
304 let opts = copy(a:000)
305 if type(opts[0]) == type([])
306 if empty(args) || index(opts[0], args[0]) == -1
312 if index(args, opt) != -1
318 function! s:PreparePathArgs(cmd, dir, literal) abort
319 let literal_supported = fugitive#GitVersion(1, 9)
320 if a:literal && literal_supported
321 call insert(a:cmd, '--literal-pathspecs')
323 let split = index(a:cmd, '--')
324 for i in range(split < 0 ? len(a:cmd) : split)
325 if type(a:cmd[i]) == type(0)
326 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
332 for i in range(split + 1, len(a:cmd) - 1)
333 if type(a:cmd[i]) == type(0)
334 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
336 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
337 elseif !literal_supported
338 let a:cmd[i] = substitute(a:cmd[i], '^:\%(/\|([^)]*)\)\=:\=', './', '')
344 let s:prepare_env = {
345 \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
346 \ 'core.editor': 'GIT_EDITOR',
347 \ 'core.askpass': 'GIT_ASKPASS',
349 function! fugitive#PrepareDirEnvArgv(...) abort
350 if a:0 && type(a:1) ==# type([])
351 let cmd = a:000[1:-1] + a:1
353 let cmd = copy(a:000)
358 if cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
359 let dir = remove(cmd, 0)
360 elseif cmd[i] =~# '^--git-dir='
361 let dir = remove(cmd, 0)[10:-1]
362 elseif type(cmd[i]) ==# type(0)
363 let dir = s:Dir(remove(cmd, i))
364 elseif cmd[i] ==# '-c' && len(cmd) > i + 1
365 let key = matchstr(cmd[i+1], '^[^=]*')
366 if has_key(s:prepare_env, tolower(key)) || key !~# '\.'
367 let var = get(s:prepare_env, tolower(key), key)
368 let val = matchstr(cmd[i+1], '=\zs.*')
371 if fugitive#GitVersion(1, 8) && cmd[i+1] =~# '\.'
374 call remove(cmd, i, i + 1)
376 elseif cmd[i] =~# '^--.*pathspecs$'
377 let explicit_pathspec_option = 1
378 if fugitive#GitVersion(1, 9)
383 elseif cmd[i] !~# '^-'
392 call s:PreparePathArgs(cmd, dir, !exists('explicit_pathspec_option'))
393 return [dir, env, cmd]
396 function! s:BuildShell(dir, env, args) abort
397 let cmd = copy(a:args)
398 let tree = s:Tree(a:dir)
400 for [var, val] in items(a:env)
402 let pre .= 'set ' . var . '=' . s:shellesc(val) . '& '
404 let pre = (len(pre) ? pre : 'env ') . var . '=' . s:shellesc(val) . ' '
407 if empty(tree) || index(cmd, '--') == len(cmd) - 1
408 call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
409 elseif fugitive#GitVersion(1, 8, 5)
410 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
412 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? '& ' : '; ') . pre
414 return pre . g:fugitive_git_executable . ' ' . join(map(cmd, 's:shellesc(v:val)'))
417 function! fugitive#Prepare(...) abort
418 let [dir, env, argv] = call('fugitive#PrepareDirEnvArgv', a:000)
419 return s:BuildShell(dir, env, argv)
422 function! s:SystemError(cmd, ...) abort
424 if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
425 let shellredir = &shellredir
429 set shellredir=>%s\ 2>&1
432 let out = call('system', [type(a:cmd) ==# type([]) ? fugitive#Prepare(a:cmd) : a:cmd] + a:000)
433 return [out, v:shell_error]
434 catch /^Vim\%((\a\+)\)\=:E484:/
435 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
436 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
437 call map(opts, 'v:val."=".eval("&".v:val)')
438 call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
440 if exists('shellredir')
441 let &shellredir = shellredir
446 function! s:ChompError(...) abort
447 let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
448 return [s:sub(out, '\n$', ''), exec_error]
451 function! s:ChompDefault(default, ...) abort
452 let [out, exec_error] = call('s:ChompError', a:000)
453 return exec_error ? a:default : out
456 function! s:LinesError(...) abort
457 let [out, exec_error] = call('s:ChompError', a:000)
458 return [len(out) && !exec_error ? split(out, "\n", 1) : [], exec_error]
461 function! s:NullError(...) abort
462 let [out, exec_error] = s:SystemError(call('fugitive#Prepare', a:000))
463 return [exec_error ? [] : split(out, "\1"), exec_error ? substitute(out, "\n$", "", "") : '', exec_error]
466 function! s:TreeChomp(...) abort
467 let cmd = call('fugitive#Prepare', a:000)
468 let [out, exec_error] = s:SystemError(cmd)
469 let out = s:sub(out, '\n$', '')
473 throw 'fugitive: error running `' . cmd . '`: ' . out
476 function! s:EchoExec(...) abort
477 echo call('s:ChompError', a:000)[0]
478 call fugitive#ReloadStatus(-1, 1)
482 function! fugitive#Head(...) abort
483 let dir = a:0 > 1 ? a:2 : s:Dir()
484 if empty(dir) || !filereadable(fugitive#Find('.git/HEAD', dir))
487 let head = readfile(fugitive#Find('.git/HEAD', dir))[0]
489 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
490 elseif head =~# '^\x\{40,\}$'
491 let len = a:0 ? a:1 : 0
492 return len < 0 ? head : len ? head[0:len-1] : ''
498 function! fugitive#RevParse(rev, ...) abort
499 let [hash, exec_error] = s:ChompError([a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
500 if !exec_error && hash =~# '^\x\{40,\}$'
503 throw 'fugitive: rev-parse '.a:rev.': '.hash
506 function! s:ConfigTimestamps(dir, dict) abort
507 let files = ['/etc/gitconfig', '~/.gitconfig',
508 \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
510 call add(files, fugitive#Find('.git/config', a:dir))
512 call extend(files, get(a:dict, 'include.path', []))
513 return join(map(files, 'getftime(expand(v:val))'), ',')
517 function! fugitive#Config(...) abort
520 if a:0 >= 2 && type(a:2) == type({})
521 let name = substitute(a:1, '^[^.]\+\|[^.]\+$', '\L&', 'g')
522 return len(a:1) ? get(get(a:2, name, []), 0, '') : a:2
526 elseif a:0 == 1 && type(a:1) == type({})
528 elseif a:0 == 1 && a:1 =~# '^[[:alnum:]-]\+\.'
533 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
534 let key = len(dir) ? dir : '_'
535 if has_key(s:config, key) && s:config[key][0] ==# s:ConfigTimestamps(dir, s:config[key][1])
536 let dict = s:config[key][1]
539 let [lines, message, exec_error] = s:NullError([dir, 'config', '--list', '-z'])
544 let key = matchstr(line, "^[^\n]*")
545 if !has_key(dict, key)
548 call add(dict[key], strpart(line, len(key) + 1))
550 let s:config[dir] = [s:ConfigTimestamps(dir, dict), dict]
553 return len(name) ? get(get(dict, name, []), 0, '') : dict
556 function! s:Remote(dir) abort
557 let head = FugitiveHead(0, a:dir)
558 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
560 while remote ==# '.' && i > 0
561 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
562 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
565 return remote =~# '^\.\=$' ? 'origin' : remote
568 function! fugitive#RemoteUrl(...) abort
569 let dir = a:0 > 1 ? a:2 : s:Dir()
570 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
571 if !fugitive#GitVersion(2, 7)
572 return fugitive#Config('remote.' . remote . '.url')
574 return s:ChompDefault('', [dir, 'remote', 'get-url', remote, '--'])
577 " Section: Repository Object
579 function! s:add_methods(namespace, method_names) abort
580 for name in a:method_names
581 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
585 function! s:Command(command, line1, line2, range, bang, mods, arg, args) abort
587 if a:command =~# '^\l[[:alnum:]-]\+$'
588 return fugitive#Command(a:line1, a:line2, a:range, a:bang, s:Mods(a:mods), a:command . ' ' . a:arg)
590 return s:{a:command}Command(a:line1, a:line2, a:range, a:line2, a:bang, s:Mods(a:mods), '', a:arg, a:args)
592 return 'echoerr ' . string(v:exception)
597 function! s:command(definition, ...) abort
598 let def = a:definition
599 if !has('patch-7.4.542')
600 let def = substitute(def, '-addr=\S\+ ', '', '')
602 if !has('patch-8.1.560')
603 let def = substitute(def, '-addr=other ', '', '')
606 call add(s:commands, def . ' execute s:Command(' . string(a:1) . ", <line1>, <count>, +'<range>', <bang>0, '<mods>', <q-args>, [<f-args>])")
608 call add(s:commands, def)
612 function! s:define_commands() abort
613 for command in s:commands
614 exe 'command! -buffer '.command
618 let s:repo_prototype = {}
621 function! fugitive#repo(...) abort
622 let dir = a:0 ? s:Dir(a:1) : (len(s:Dir()) ? s:Dir() : FugitiveExtractGitDir(expand('%:p')))
624 if has_key(s:repos, dir)
625 let repo = get(s:repos, dir)
627 let repo = {'git_dir': dir}
628 let s:repos[dir] = repo
630 return extend(repo, s:repo_prototype, 'keep')
632 call s:throw('not a Git repository')
635 function! s:repo_dir(...) dict abort
636 return join([self.git_dir]+a:000,'/')
639 function! s:repo_tree(...) dict abort
640 let dir = s:Tree(self.git_dir)
642 call s:throw('no work tree')
644 return join([dir]+a:000,'/')
648 function! s:repo_bare() dict abort
649 if self.dir() =~# '/\.git$'
652 return s:Tree(self.git_dir) ==# ''
656 function! s:repo_find(object) dict abort
657 return fugitive#Find(a:object, self.git_dir)
660 function! s:repo_translate(rev) dict abort
661 return s:Slash(fugitive#Find(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
664 function! s:repo_head(...) dict abort
665 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
668 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
670 function! s:repo_prepare(...) dict abort
671 return call('fugitive#Prepare', [self.git_dir] + a:000)
674 function! s:repo_git_command(...) dict abort
675 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
676 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
679 function! s:repo_git_chomp(...) dict abort
680 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
681 let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
682 return s:sub(system(output), '\n$', '')
685 function! s:repo_git_chomp_in_tree(...) dict abort
686 let cdback = s:Cd(self.tree())
688 return call(self.git_chomp, a:000, self)
694 function! s:repo_rev_parse(rev) dict abort
695 return fugitive#RevParse(a:rev, self.git_dir)
698 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
700 function! s:repo_superglob(base) dict abort
701 return map(fugitive#CompleteObject(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
704 call s:add_methods('repo',['superglob'])
706 function! s:repo_config(name) dict abort
707 return fugitive#Config(a:name, self.git_dir)
710 function! s:repo_user() dict abort
711 let username = self.config('user.name')
712 let useremail = self.config('user.email')
713 return username.' <'.useremail.'>'
716 call s:add_methods('repo',['config', 'user'])
720 function! s:DirCommitFile(path) abort
721 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40,\}\|[0-3]\)\(/.*\)\=$')
728 function! s:DirRev(url) abort
729 let [dir, commit, file] = s:DirCommitFile(a:url)
730 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
733 function! s:Owner(path, ...) abort
734 let dir = a:0 ? a:1 : s:Dir()
738 let actualdir = fugitive#Find('.git/', dir)
739 let [pdir, commit, file] = s:DirCommitFile(a:path)
740 if s:cpath(dir, pdir)
741 if commit =~# '^\x\{40,\}$'
743 elseif commit ==# '2'
746 if filereadable(actualdir . 'MERGE_HEAD')
747 let merge_head = 'MERGE_HEAD'
748 elseif filereadable(actualdir . 'REBASE_HEAD')
749 let merge_head = 'REBASE_HEAD'
754 return merge_head . '^{}'
755 elseif commit ==# '1'
756 return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
759 let path = fnamemodify(a:path, ':p')
760 if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
761 return strpart(path, len(actualdir))
763 let refs = fugitive#Find('.git/refs', dir)
764 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
765 return strpart(path, len(refs) - 4)
770 function! fugitive#Real(url) abort
774 let [dir, commit, file] = s:DirCommitFile(a:url)
776 let tree = s:Tree(dir)
777 return FugitiveVimPath((len(tree) ? tree : dir) . file)
779 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
780 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
781 let url = {pre}Real(a:url)
783 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
785 return FugitiveVimPath(empty(url) ? a:url : url)
788 function! fugitive#Path(url, ...) abort
792 let dir = a:0 > 1 ? a:2 : s:Dir()
793 let tree = s:Tree(dir)
795 return fugitive#Real(a:url)
797 let path = s:Slash(fugitive#Real(a:url))
800 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
801 if s:cpath(cwd . '/', path[0 : len(cwd)])
802 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
805 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
807 let cwd = fnamemodify(cwd, ':h')
810 return a:1[0:-2] . path
813 let temp_state = s:TempState(url)
814 if has_key(temp_state, 'bufnr')
815 let url = bufname(temp_state.bufnr)
817 let url = s:Slash(fnamemodify(url, ':p'))
818 if url =~# '/$' && s:Slash(a:url) !~# '/$'
821 let [argdir, commit, file] = s:DirCommitFile(a:url)
822 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
824 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
825 let file = '/.git'.url[strlen(dir) : -1]
826 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
827 let file = url[len(tree) : -1]
828 elseif s:cpath(url) ==# s:cpath(tree)
831 if empty(file) && a:1 =~# '^$\|^[.:]/$'
832 return FugitiveGitPath(fugitive#Real(a:url))
834 return substitute(file, '^/', a:1, '')
837 function! s:Relative(...) abort
838 return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
841 function! fugitive#Find(object, ...) abort
842 if type(a:object) == type(0)
843 let name = bufname(a:object)
844 return FugitiveVimPath(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
845 elseif a:object =~# '^[~$]'
846 let prefix = matchstr(a:object, '^[~$]\i*')
847 let owner = expand(prefix)
848 return FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
849 elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
850 return FugitiveVimPath(a:object)
851 elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
852 return FugitiveVimPath(simplify(getcwd() . '/' . a:object))
854 let dir = a:0 ? a:1 : s:Dir()
856 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
857 let dir = FugitiveExtractGitDir(file)
859 return fnamemodify(FugitiveVimPath(len(file) ? file : a:object), ':p')
862 let rev = s:Slash(a:object)
863 let tree = s:Tree(dir)
864 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
866 let f = len(tree) ? tree . '/.git' : dir
867 elseif rev =~# '^\.git/'
868 let f = substitute(rev, '^\.git', '', '')
869 let cdir = fugitive#CommonDir(dir)
870 if f =~# '^/\.\./\.\.\%(/\|$\)'
871 let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
872 elseif f =~# '^/\.\.\%(/\|$\)'
873 let f = base . f[3:-1]
874 elseif cdir !=# dir && (
875 \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
876 \ f !~# '^/\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
877 \ getftime(FugitiveVimPath(dir . f)) < 0 && getftime(FugitiveVimPath(cdir . f)) >= 0)
878 let f = simplify(cdir . f)
880 let f = simplify(dir . f)
884 elseif rev =~# '^\.\%(/\|$\)'
885 let f = base . rev[1:-1]
886 elseif rev =~# '^::\%(/\|\a\+\:\)'
888 elseif rev =~# '^::\.\.\=\%(/\|$\)'
889 let f = simplify(getcwd() . '/' . rev[2:-1])
891 let f = base . '/' . rev[2:-1]
892 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
893 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
894 if s:cpath(base . '/', (f . '/')[0 : len(base)])
895 let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
897 let altdir = FugitiveExtractGitDir(f)
898 if len(altdir) && !s:cpath(dir, altdir)
899 return fugitive#Find(a:object, altdir)
902 elseif rev =~# '^:[0-3]:'
903 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
905 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
906 let f = fnamemodify($GIT_INDEX_FILE, ':p')
908 let f = fugitive#Find('.git/index', dir)
910 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
911 let f = matchstr(rev, ')\zs.*')
912 if f=~# '^\.\.\=\%(/\|$\)'
913 let f = simplify(getcwd() . '/' . f)
914 elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
915 let f = base . '/' . f
917 elseif rev =~# '^:/\@!'
918 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
921 let commit = substitute(matchstr(rev, '^[^:.-][^:]*\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
922 let file = substitute(matchstr(rev, '^[^:.-][^:]*\zs:.*'), '^:', '/', '')
923 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
924 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
925 if s:cpath(base . '/', (file . '/')[0 : len(base)])
926 let file = '/' . strpart(file, len(base) + 1)
928 let altdir = FugitiveExtractGitDir(file)
929 if len(altdir) && !s:cpath(dir, altdir)
930 return fugitive#Find(a:object, altdir)
935 let commits = split(commit, '\.\.\.-\@!', 1)
937 call map(commits, 'empty(v:val) || v:val ==# "@" ? "HEAD" : v:val')
938 let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
940 if commit !~# '^[0-9a-f]\{40,\}$'
941 let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit, '--']), '\<[0-9a-f]\{40,\}\>')
944 let f = 'fugitive://' . dir . '//' . commit . file
946 let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
950 return FugitiveVimPath(f)
953 function! s:Generate(rev, ...) abort
954 return fugitive#Find(a:rev, a:0 ? a:1 : s:Dir())
957 function! s:DotRelative(path, ...) abort
958 let cwd = a:0 ? a:1 : getcwd()
959 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
960 if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
961 return '.' . strpart(path, len(cwd))
966 function! fugitive#Object(...) abort
967 let dir = a:0 > 1 ? a:2 : s:Dir()
968 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
969 if s:cpath(dir) !=# s:cpath(fdir)
972 let tree = s:Tree(dir)
973 if empty(rev) && empty(tree)
975 let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
976 if rev =~# '^\./.git\%(/\|$\)'
977 return fnamemodify(a:0 ? a:1 : @%, ':p' . (rev =~# '/$' ? '' : ':s?/$??'))
980 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
983 return tree . rev[1:-1]
987 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
988 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
989 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
991 function! s:BufName(var) abort
993 return bufname(get(s:TempState(), 'bufnr', ''))
994 elseif a:var =~# '^#\d*$'
995 let nr = get(s:TempState(bufname(+a:var[1:-1])), 'bufnr', '')
996 return bufname(nr ? nr : +a:var[1:-1])
1002 function! s:ExpandVarLegacy(str) abort
1003 if get(g:, 'fugitive_legacy_quoting', 1)
1004 return substitute(a:str, '\\\ze[%#!]', '', 'g')
1010 function! s:ExpandVar(other, var, flags, esc, ...) abort
1011 let cwd = a:0 ? a:1 : getcwd()
1013 return a:other[1:-1]
1014 elseif a:other =~# '^'''
1015 return s:ExpandVarLegacy(substitute(a:other[1:-2], "''", "'", "g"))
1016 elseif a:other =~# '^"'
1017 return s:ExpandVarLegacy(substitute(a:other[1:-2], '""', '"', "g"))
1018 elseif a:other =~# '^!'
1019 let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
1020 let owner = s:Owner(buffer)
1021 return len(owner) ? owner : '@'
1024 let file = s:DotRelative(fugitive#Real(s:BufName(a:var)), cwd)
1026 let flag = matchstr(flags, s:flag)
1027 let flags = strpart(flags, len(flag))
1029 let file = s:DotRelative(file, cwd)
1031 let file = fnamemodify(file, flag)
1034 let file = s:Slash(file)
1035 return (len(a:esc) ? shellescape(file) : file)
1038 function! s:Expand(rev, ...) abort
1039 if a:rev =~# '^:[0-3]$'
1040 let file = len(expand('%')) ? a:rev . ':%' : '%'
1041 elseif a:rev ==# '>'
1043 elseif a:rev =~# '^>[~^]'
1044 let file = len(expand('%')) ? '!' . a:rev[1:-1] . ':%' : '%'
1045 elseif a:rev =~# '^>[> ]\@!'
1046 let file = len(expand('%')) ? a:rev[1:-1] . ':%' : '%'
1050 return substitute(file,
1051 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1052 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd())', 'g')
1055 function! fugitive#Expand(object) abort
1056 return substitute(a:object,
1057 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1058 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
1061 function! s:ExpandSplit(string, ...) abort
1063 let string = a:string
1064 let handle_bar = a:0 && a:1
1065 let dquote = handle_bar ? '"\%([^"]\|""\|\\"\)*"\|' : ''
1066 let cwd = a:0 > 1 ? a:2 : getcwd()
1067 while string =~# '\S'
1068 if handle_bar && string =~# '^\s*|'
1069 return [list, substitute(string, '^\s*', '', '')]
1071 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^[:space:] ' . (handle_bar ? '|' : '') . ']\)\+')
1072 let string = strpart(string, len(arg))
1073 let arg = substitute(arg, '^\s\+', '', '')
1074 if !exists('seen_separator')
1075 let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
1076 \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
1078 let arg = substitute(arg,
1079 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
1080 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
1083 let seen_separator = 1
1086 return handle_bar ? [list, ''] : list
1089 function! s:SplitExpand(string, ...) abort
1090 return s:ExpandSplit(a:string, 0, a:0 ? a:1 : getcwd())
1093 function! s:SplitExpandChain(string, ...) abort
1094 return s:ExpandSplit(a:string, 1, a:0 ? a:1 : getcwd())
1099 function! s:TreeInfo(dir, commit) abort
1100 if a:commit =~# '^:\=[0-3]$'
1101 let index = get(s:indexes, a:dir, [])
1102 let newftime = getftime(fugitive#Find('.git/index', a:dir))
1103 if get(index, 0, -1) < newftime
1104 let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
1105 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
1110 let [info, filename] = split(line, "\t")
1111 let [mode, sha, stage] = split(info, '\s\+')
1112 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
1113 while filename =~# '/'
1114 let filename = substitute(filename, '/[^/]*$', '', '')
1115 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
1119 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
1120 elseif a:commit =~# '^\x\{40,\}$'
1121 if !has_key(s:trees, a:dir)
1122 let s:trees[a:dir] = {}
1124 if !has_key(s:trees[a:dir], a:commit)
1125 let [ftime, exec_error] = s:ChompError([a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
1127 let s:trees[a:dir][a:commit] = [{}, -1]
1128 return s:trees[a:dir][a:commit]
1130 let s:trees[a:dir][a:commit] = [{}, +ftime]
1131 let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
1133 return s:trees[a:dir][a:commit]
1136 let [info, filename] = split(line, "\t")
1137 let [mode, type, sha, size] = split(info, '\s\+')
1138 let s:trees[a:dir][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
1141 return s:trees[a:dir][a:commit]
1146 function! s:PathInfo(url) abort
1147 let [dir, commit, file] = s:DirCommitFile(a:url)
1148 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
1149 return [-1, '000000', '', '', -1]
1151 let path = substitute(file[1:-1], '/*$', '', '')
1152 let [tree, ftime] = s:TreeInfo(dir, commit)
1153 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
1154 if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
1155 return [-1, '000000', '', '', -1]
1161 function! fugitive#simplify(url) abort
1162 let [dir, commit, file] = s:DirCommitFile(a:url)
1166 if file =~# '/\.\.\%(/\|$\)'
1167 let tree = s:Tree(dir)
1169 let path = simplify(tree . file)
1170 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
1171 return FugitiveVimPath(path)
1175 return FugitiveVimPath('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
1178 function! fugitive#resolve(url) abort
1179 let url = fugitive#simplify(a:url)
1180 if url =~? '^fugitive:'
1187 function! fugitive#getftime(url) abort
1188 return s:PathInfo(a:url)[0]
1191 function! fugitive#getfsize(url) abort
1192 let entry = s:PathInfo(a:url)
1193 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
1194 let dir = s:DirCommitFile(a:url)[0]
1195 let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
1200 function! fugitive#getftype(url) abort
1201 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
1204 function! fugitive#filereadable(url) abort
1205 return s:PathInfo(a:url)[2] ==# 'blob'
1208 function! fugitive#filewritable(url) abort
1209 let [dir, commit, file] = s:DirCommitFile(a:url)
1210 if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
1213 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
1216 function! fugitive#isdirectory(url) abort
1217 return s:PathInfo(a:url)[2] ==# 'tree'
1220 function! fugitive#getfperm(url) abort
1221 let [dir, commit, file] = s:DirCommitFile(a:url)
1222 let perm = getfperm(dir)
1223 let fperm = s:PathInfo(a:url)[1]
1224 if fperm ==# '040000'
1225 let fperm = '000755'
1228 let perm = tr(perm, 'x', '-')
1230 if fperm !~# '[45]$'
1231 let perm = tr(perm, 'rw', '--')
1233 if commit !~# '^\d$'
1234 let perm = tr(perm, 'w', '-')
1236 return perm ==# '---------' ? '' : perm
1239 function! fugitive#setfperm(url, perm) abort
1240 let [dir, commit, file] = s:DirCommitFile(a:url)
1241 let entry = s:PathInfo(a:url)
1242 let perm = fugitive#getfperm(a:url)
1243 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
1244 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
1247 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1248 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])[1]
1249 return exec_error ? -1 : 0
1252 function! s:TempCmd(out, cmd) abort
1255 let cmd = (type(a:cmd) == type([]) ? fugitive#Prepare(a:cmd) : a:cmd)
1256 let redir = ' > ' . a:out
1258 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
1259 return s:SystemError('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
1260 elseif &shell =~# 'fish'
1261 return s:SystemError(' begin;' . prefix . cmd . redir . ';end ')
1263 return s:SystemError(' (' . prefix . cmd . redir . ') ')
1268 if !exists('s:blobdirs')
1271 function! s:BlobTemp(url) abort
1272 let [dir, commit, file] = s:DirCommitFile(a:url)
1276 if !has_key(s:blobdirs, dir)
1277 let s:blobdirs[dir] = tempname()
1279 let tempfile = s:blobdirs[dir] . '/' . commit . file
1280 let tempparent = fnamemodify(tempfile, ':h')
1281 if !isdirectory(tempparent)
1282 call mkdir(tempparent, 'p')
1284 if commit =~# '^\d$' || !filereadable(tempfile)
1285 let rev = s:DirRev(a:url)[1]
1286 let exec_error = s:TempCmd(tempfile, [dir, 'cat-file', 'blob', rev])[1]
1288 call delete(tempfile)
1292 return s:Resolve(tempfile)
1295 function! fugitive#readfile(url, ...) abort
1296 let entry = s:PathInfo(a:url)
1297 if entry[2] !=# 'blob'
1300 let temp = s:BlobTemp(a:url)
1304 return call('readfile', [temp] + a:000)
1307 function! fugitive#writefile(lines, url, ...) abort
1308 let url = type(a:url) ==# type('') ? a:url : ''
1309 let [dir, commit, file] = s:DirCommitFile(url)
1310 let entry = s:PathInfo(url)
1311 if commit =~# '^\d$' && entry[2] !=# 'tree'
1312 let temp = tempname()
1313 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1314 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1316 call call('writefile', [a:lines, temp] + a:000)
1317 let [hash, exec_error] = s:ChompError([dir, 'hash-object', '-w', temp])
1318 let mode = len(entry[1]) ? entry[1] : '100644'
1319 if !exec_error && hash =~# '^\x\{40,\}$'
1320 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1321 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])[1]
1327 return call('writefile', [a:lines, a:url] + a:000)
1331 \ '/**/': '/\%([^./][^/]*/\)*',
1332 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1333 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1335 \ '/*': '/[^/.][^/]*',
1338 function! fugitive#glob(url, ...) abort
1339 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1340 let append = matchstr(glob, '/*$')
1341 let glob = substitute(glob, '/*$', '', '')
1342 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1344 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1345 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
1348 let files = items(s:TreeInfo(dir, commit)[0])
1350 call filter(files, 'v:val[1][2] ==# "tree"')
1352 call map(files, 'v:val[0]')
1353 call filter(files, 'v:val =~# pattern')
1354 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1356 call map(files, 'FugitiveVimPath(prepend . v:val . append)')
1357 call extend(results, files)
1362 return join(results, "\n")
1366 function! fugitive#delete(url, ...) abort
1367 let [dir, commit, file] = s:DirCommitFile(a:url)
1368 if a:0 && len(a:1) || commit !~# '^\d$'
1371 let entry = s:PathInfo(a:url)
1372 if entry[2] !=# 'blob'
1375 let exec_error = s:SystemError([dir, 'update-index', '--index-info'],
1376 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])[1]
1377 return exec_error ? -1 : 0
1380 " Section: Buffer Object
1382 let s:buffer_prototype = {}
1384 function! fugitive#buffer(...) abort
1385 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1386 call extend(buffer, s:buffer_prototype, 'keep')
1390 function! s:buffer_repo() dict abort
1391 return fugitive#repo(self['#'])
1394 function! s:buffer_type(...) dict abort
1395 return 'see b:fugitive_type'
1398 call s:add_methods('buffer', ['repo', 'type'])
1400 " Section: Completion
1402 function! s:FilterEscape(items, ...) abort
1403 let items = copy(a:items)
1404 if a:0 && type(a:1) == type('')
1405 call filter(items, 'strpart(v:val, 0, strlen(a:1)) ==# a:1')
1407 return map(items, 's:fnameescape(v:val)')
1410 function! s:GlobComplete(lead, pattern) abort
1413 elseif v:version >= 704
1414 let results = glob(a:lead . a:pattern, 0, 1)
1416 let results = split(glob(a:lead . a:pattern), "\n")
1418 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1419 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1423 function! fugitive#CompletePath(base, ...) abort
1424 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1425 let tree = s:Tree(dir) . '/'
1426 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1427 let base = substitute(a:base, strip, '', '')
1428 if base =~# '^\.git/'
1429 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1430 let matches = s:GlobComplete(dir . '/', pattern)
1431 let cdir = fugitive#CommonDir(dir)
1432 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1433 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1435 call s:Uniq(matches)
1436 call map(matches, "'.git/' . v:val")
1437 elseif base =~# '^\~/'
1438 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1439 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1440 let matches = s:GlobComplete('', base . '*')
1441 elseif len(tree) > 1
1442 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1446 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1450 function! fugitive#PathComplete(...) abort
1451 return call('fugitive#CompletePath', a:000)
1454 function! fugitive#CompleteObject(base, ...) abort
1455 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
1457 let tree = s:Tree(dir) . '/'
1459 if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1460 let subdir = strpart(cwd, len(tree)) . '/'
1463 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1465 if a:base =~# '^refs/'
1466 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1467 elseif a:base !~# '^\.\=/\|^:('
1468 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1469 let heads += sort(s:LinesError(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir)[0])
1470 if filereadable(fugitive#Find('.git/refs/stash', dir))
1471 let heads += ["stash"]
1472 let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
1474 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1475 let results += heads
1477 call map(results, 's:fnameescape(v:val)')
1479 let results += a:0 == 1 ? fugitive#CompletePath(a:base, dir) : fugitive#CompletePath(a:base)
1483 elseif a:base =~# '^:'
1484 let entries = s:LinesError(['ls-files','--stage'], dir)[0]
1485 if a:base =~# ':\./'
1486 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1488 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1489 if a:base !~# '^:[0-3]\%(:\|$\)'
1490 call filter(entries,'v:val[1] == "0"')
1491 call map(entries,'v:val[2:-1]')
1495 let tree = matchstr(a:base, '.*[:/]')
1496 let entries = s:LinesError(['ls-tree', substitute(tree, ':\zs\./', '\=subdir', '')], dir)[0]
1497 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1498 call map(entries,'tree.s:sub(v:val,".*\t","")')
1501 return s:FilterEscape(entries, a:base)
1504 function! s:CompleteSub(subcommand, A, L, P, ...) abort
1505 let pre = strpart(a:L, 0, a:P)
1507 return fugitive#CompletePath(a:A)
1508 elseif a:A =~# '^-' || a:A is# 0
1509 return s:FilterEscape(split(s:ChompDefault('', a:subcommand, '--git-completion-helper'), ' '), a:A)
1511 return fugitive#CompleteObject(a:A, s:Dir())
1512 elseif type(a:1) == type(function('tr'))
1513 return call(a:1, [a:A, a:L, a:P])
1515 return s:FilterEscape(a:1, a:A)
1519 function! s:CompleteRevision(A, L, P) abort
1520 return s:FilterEscape(['HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'ORIG_HEAD'] +
1521 \ s:LinesError('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')[0], a:A)
1524 function! s:CompleteRemote(A, L, P) abort
1525 let remote = matchstr(a:L, '\u\w*[! ] *\zs\S\+\ze ')
1527 let matches = s:LinesError('ls-remote', remote)[0]
1528 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1529 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1531 let matches = s:LinesError('remote')[0]
1533 return s:FilterEscape(matches, a:A)
1536 " Section: Buffer auto-commands
1538 function! s:ReplaceCmd(cmd) abort
1539 let temp = tempname()
1540 let [err, exec_error] = s:TempCmd(temp, a:cmd)
1542 call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1544 let temp = s:Resolve(temp)
1545 let fn = expand('%:p')
1546 silent exe 'keepalt file '.temp
1547 let modelines = &modelines
1550 silent keepjumps noautocmd edit!
1552 let &modelines = modelines
1554 silent exe 'keepalt file '.s:fnameescape(fn)
1555 catch /^Vim\%((\a\+)\)\=:E302:/
1558 if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1559 silent execute 'bwipeout '.bufnr('$')
1564 function! s:QueryLog(refspec) abort
1565 let lines = s:LinesError(['log', '-n', '256', '--format=%h%x09%s', a:refspec, '--'])[0]
1566 call map(lines, 'split(v:val, "\t")')
1567 call map(lines, '{"type": "Log", "commit": v:val[0], "subject": v:val[-1]}')
1571 function! s:FormatLog(dict) abort
1572 return a:dict.commit . ' ' . a:dict.subject
1575 function! s:FormatRebase(dict) abort
1576 return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
1579 function! s:FormatFile(dict) abort
1580 return a:dict.status . ' ' . a:dict.filename
1583 function! s:Format(val) abort
1584 if type(a:val) == type({})
1585 return s:Format{a:val.type}(a:val)
1586 elseif type(a:val) == type([])
1587 return map(copy(a:val), 's:Format(v:val)')
1593 function! s:AddHeader(key, value) abort
1598 while !empty(getline(before))
1601 call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
1602 if before == 1 && line('$') == 2
1607 function! s:AddSection(label, lines, ...) abort
1608 let note = a:0 ? a:1 : ''
1609 if empty(a:lines) && empty(note)
1612 call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
1615 function! fugitive#BufReadStatus() abort
1616 let amatch = s:Slash(expand('%:p'))
1617 let b:fugitive_type = 'index'
1618 unlet! b:fugitive_reltime
1620 silent doautocmd BufReadPre
1621 let cmd = [fnamemodify(amatch, ':h')]
1622 setlocal noro ma nomodeline buftype=nowrite
1623 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : fugitive#Find('.git/index'), ':p')) !=# s:cpath(amatch)
1624 let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1626 let cmd += ['status', '--porcelain', '-bz']
1627 let [output, message, exec_error] = s:NullError(cmd)
1629 throw 'fugitive: ' . message
1632 let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
1634 if head =~# '\.\.\.'
1635 let [head, pull] = split(head, '\.\.\.')
1637 elseif head ==# 'HEAD' || empty(head)
1638 let head = FugitiveHead(11)
1644 let b:fugitive_status = {'Staged': {}, 'Unstaged': {}}
1645 let [staged, unstaged, untracked] = [[], [], []]
1647 while i < len(output)
1648 let line = output[i]
1649 let file = line[3:-1]
1655 if line[0:1] =~# '[RC]'
1656 let files = output[i] . ' -> ' . file
1659 if line[0] !~# '[ ?!#]'
1660 call add(staged, {'type': 'File', 'status': line[0], 'filename': files})
1662 if line[0:1] ==# '??'
1663 call add(untracked, {'type': 'File', 'status': line[1], 'filename': files})
1664 elseif line[1] !~# '[ !#]'
1665 call add(unstaged, {'type': 'File', 'status': line[1], 'filename': files})
1670 let b:fugitive_status['Staged'][dict.filename] = dict.status
1672 for dict in unstaged
1673 let b:fugitive_status['Unstaged'][dict.filename] = dict.status
1676 let config = fugitive#Config()
1678 let pull_type = 'Pull'
1680 let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
1682 let rebase = fugitive#Config('pull.rebase', config)
1684 if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
1685 let pull_type = 'Rebase'
1686 elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
1687 let pull_type = 'Merge'
1691 let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
1692 if empty(push_remote)
1693 let push_remote = fugitive#Config('remote.pushDefault', config)
1695 let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
1701 let unpulled = s:QueryLog(head . '..' . pull)
1706 let unpushed = s:QueryLog(push . '..' . head)
1711 if isdirectory(fugitive#Find('.git/rebase-merge/'))
1712 let rebasing_dir = fugitive#Find('.git/rebase-merge/')
1713 elseif isdirectory(fugitive#Find('.git/rebase-apply/'))
1714 let rebasing_dir = fugitive#Find('.git/rebase-apply/')
1718 let rebasing_head = 'detached HEAD'
1719 if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
1720 let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
1722 let lines = readfile(rebasing_dir . 'git-rebase-todo')
1724 let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
1730 if getfsize(rebasing_dir . 'done') > 0
1731 let done = readfile(rebasing_dir . 'done')
1732 call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
1733 let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
1734 let lines = done + lines
1738 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
1739 if len(match) && match[1] !~# 'exec\|merge\|label'
1740 call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
1745 let diff = {'Staged': [], 'Unstaged': []}
1747 let diff['Staged'] =
1748 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix', '--cached'])[0]
1751 let diff['Unstaged'] =
1752 \ s:LinesError(['diff', '--color=never', '--no-ext-diff', '--no-prefix'])[0]
1754 let b:fugitive_diff = diff
1755 let expanded = get(b:, 'fugitive_expanded', {'Staged': {}, 'Unstaged': {}})
1756 let b:fugitive_expanded = {'Staged': {}, 'Unstaged': {}}
1758 silent keepjumps %delete_
1760 call s:AddHeader('Head', head)
1761 call s:AddHeader(pull_type, pull)
1763 call s:AddHeader('Push', push)
1765 call s:AddSection('Rebasing ' . rebasing_head, rebasing)
1766 call s:AddSection('Untracked', untracked)
1767 call s:AddSection('Unstaged', unstaged)
1768 let unstaged_end = len(unstaged) ? line('$') : 0
1769 call s:AddSection('Staged', staged)
1770 let staged_end = len(staged) ? line('$') : 0
1771 call s:AddSection('Unpushed to ' . push, unpushed)
1772 call s:AddSection('Unpulled from ' . pull, unpulled)
1774 setlocal nomodified readonly noswapfile
1775 silent doautocmd BufReadPost
1776 setlocal nomodifiable
1777 if &bufhidden ==# ''
1778 setlocal bufhidden=delete
1780 let b:dispatch = ':Gfetch --all'
1781 call fugitive#MapJumps()
1782 call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1783 call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
1784 call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
1785 call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
1786 call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
1787 call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
1788 call s:Map('n', 'U', ":exe <SID>EchoExec('reset', '-q')<CR>", '<silent>')
1789 call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
1790 call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
1791 call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
1792 call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
1793 call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
1794 call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
1795 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>')
1796 call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
1797 call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
1798 call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
1799 call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
1800 call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
1801 call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1802 call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1803 call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
1804 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>')
1805 call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
1806 call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1807 call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
1808 call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
1809 call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
1810 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
1811 call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
1812 call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1813 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>')
1814 call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1815 call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
1816 call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
1817 if empty(mapcheck('q', 'n'))
1818 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>
1820 call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
1821 call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
1822 call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1823 call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent>')
1824 call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
1825 call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1826 call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
1827 call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
1828 call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
1829 call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
1830 setlocal filetype=fugitive
1832 for [lnum, section] in [[staged_end, 'Staged'], [unstaged_end, 'Unstaged']]
1833 while len(getline(lnum))
1834 let filename = matchstr(getline(lnum), '^[A-Z?] \zs.*')
1835 if has_key(expanded[section], filename)
1836 call s:StageInline('show', lnum)
1842 let b:fugitive_reltime = reltime()
1845 return 'echoerr ' . string(v:exception)
1849 function! fugitive#FileReadCmd(...) abort
1850 let amatch = a:0 ? a:1 : expand('<amatch>')
1851 let [dir, rev] = s:DirRev(amatch)
1852 let line = a:0 > 1 ? a:2 : line("'[")
1854 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1856 if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
1857 let cmd = fugitive#Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1859 let cmd = fugitive#Prepare(dir, 'cat-file', '-p', rev)
1861 return line . 'read !' . escape(cmd, '!#%')
1864 function! fugitive#FileWriteCmd(...) abort
1865 let tmp = tempname()
1866 let amatch = a:0 ? a:1 : expand('<amatch>')
1867 let autype = a:0 > 1 ? 'Buf' : 'File'
1868 if exists('#' . autype . 'WritePre')
1869 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1872 let [dir, commit, file] = s:DirCommitFile(amatch)
1873 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1874 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1876 silent execute "'[,']write !".fugitive#Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1877 let sha1 = readfile(tmp)[0]
1878 let old_mode = matchstr(s:SystemError([dir, 'ls-files', '--stage', '.' . file])[0], '^\d\+')
1880 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1882 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1883 let [error, exec_error] = s:SystemError([dir, 'update-index', '--index-info'], info . "\n")
1886 if exists('#' . autype . 'WritePost')
1887 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1891 return 'echoerr '.string('fugitive: '.error)
1898 let s:nomodeline = (v:version >= 704 ? '<nomodeline>' : '')
1900 function! fugitive#BufReadCmd(...) abort
1901 let amatch = a:0 ? a:1 : expand('<amatch>')
1903 let [dir, rev] = s:DirRev(amatch)
1905 return 'echo "Invalid Fugitive URL"'
1908 let b:fugitive_type = 'stage'
1910 let [b:fugitive_type, exec_error] = s:ChompError([dir, 'cat-file', '-t', rev])
1911 if exec_error && rev =~# '^:0'
1912 let sha = s:ChompDefault('', dir, 'write-tree', '--prefix=' . rev[3:-1])
1913 let exec_error = empty(sha)
1914 let b:fugitive_type = exec_error ? '' : 'tree'
1917 let error = b:fugitive_type
1918 unlet b:fugitive_type
1920 if empty(&bufhidden)
1921 setlocal bufhidden=delete
1924 let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
1925 return 'silent doautocmd BufNewFile'
1927 setlocal readonly nomodifiable
1928 return 'silent doautocmd BufNewFile|echo ' . string(error)
1930 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1931 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1933 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1934 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1938 if b:fugitive_type !=# 'blob'
1942 setlocal noreadonly modifiable
1943 let pos = getpos('.')
1944 silent keepjumps %delete_
1948 silent doautocmd BufReadPre
1949 if b:fugitive_type ==# 'tree'
1950 let b:fugitive_display_format = b:fugitive_display_format % 2
1951 if b:fugitive_display_format
1952 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1955 let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
1957 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1959 elseif b:fugitive_type ==# 'tag'
1960 let b:fugitive_display_format = b:fugitive_display_format % 2
1961 if b:fugitive_display_format
1962 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1964 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1966 elseif b:fugitive_type ==# 'commit'
1967 let b:fugitive_display_format = b:fugitive_display_format % 2
1968 if b:fugitive_display_format
1969 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1971 call s:ReplaceCmd([dir, 'show', '--no-color', '--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])
1972 keepjumps call search('^parent ')
1973 if getline('.') ==# 'parent '
1974 silent keepjumps delete_
1976 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1978 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1980 silent keepjumps delete_
1982 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
1985 elseif b:fugitive_type ==# 'stage'
1986 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1987 elseif b:fugitive_type ==# 'blob'
1988 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1991 keepjumps call setpos('.',pos)
1992 setlocal nomodified noswapfile
1993 let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
1994 let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
1995 if empty(&bufhidden)
1996 setlocal bufhidden=delete
1998 let &l:modifiable = modifiable
1999 if b:fugitive_type !=# 'blob'
2000 setlocal filetype=git foldmethod=syntax
2001 call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2002 call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
2004 call fugitive#MapJumps()
2008 return 'silent doautocmd' . s:nomodeline .
2009 \ ' BufReadPost' . (modifiable ? '' : '|setl nomodifiable')
2011 return 'echoerr ' . string(v:exception)
2015 function! fugitive#BufWriteCmd(...) abort
2016 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
2019 function! fugitive#SourceCmd(...) abort
2020 let amatch = a:0 ? a:1 : expand('<amatch>')
2021 let temp = s:BlobTemp(amatch)
2023 return 'noautocmd source ' . s:fnameescape(amatch)
2025 if !exists('g:virtual_scriptnames')
2026 let g:virtual_scriptnames = {}
2028 let g:virtual_scriptnames[temp] = amatch
2029 return 'source ' . s:fnameescape(temp)
2032 " Section: Temp files
2034 if !exists('s:temp_files')
2035 let s:temp_files = {}
2038 function! s:TempState(...) abort
2039 return get(s:temp_files, s:cpath(fnamemodify(a:0 ? a:1 : @%, ':p')), {})
2042 function! s:TempReadPre(file) abort
2043 if has_key(s:temp_files, s:cpath(a:file))
2044 let dict = s:temp_files[s:cpath(a:file)]
2046 setlocal bufhidden=delete nobuflisted
2047 setlocal buftype=nowrite
2048 if has_key(dict, 'modifiable')
2049 let &l:modifiable = dict.modifiable
2052 let b:git_dir = dict.dir
2053 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
2058 function! s:TempReadPost(file) abort
2059 if has_key(s:temp_files, s:cpath(a:file))
2060 let dict = s:temp_files[s:cpath(a:file)]
2061 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
2062 let &l:filetype = dict.filetype
2064 setlocal foldmarker=<<<<<<<,>>>>>>>
2065 if empty(mapcheck('q', 'n'))
2066 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>
2069 call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
2075 augroup fugitive_temp
2077 autocmd BufReadPre * exe s:TempReadPre( expand('<amatch>:p'))
2078 autocmd BufReadPost * exe s:TempReadPost(expand('<amatch>:p'))
2083 function! fugitive#Command(line1, line2, range, bang, mods, arg) abort
2085 let [args, after] = s:SplitExpandChain(a:arg, s:Tree(dir))
2087 let cmd = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
2088 return (empty(cmd) ? 'exe' : cmd) . after
2090 let alias = get(s:Aliases(dir), args[0], '!')
2091 if get(args, 1, '') !=# '--help' && alias !~# '^!\|[\"'']' && !filereadable(s:ExecPath() . '/git-' . args[0])
2092 \ && !(has('win32') && filereadable(s:ExecPath() . '/git-' . args[0] . '.exe'))
2093 call remove(args, 0)
2094 call extend(args, split(alias, '\s\+'), 'keep')
2096 let name = substitute(args[0], '\%(^\|-\)\(\l\)', '\u\1', 'g')
2097 if exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
2100 return 'exe ' . string(s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, args[1:-1])) . after
2102 return 'echoerr ' . string(v:exception)
2105 if a:bang || args[0] =~# '^-P$\|^--no-pager$\|diff\%(tool\)\@!\|log\|^show$' ||
2106 \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
2107 \ (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
2108 return s:OpenExec((a:line2 > 0 ? a:line2 : '') . (a:line2 ? 'split' : 'edit'), a:mods, args, dir) . after
2110 if s:HasOpt(args, ['add', 'checkout', 'commit', 'stage', 'stash', 'reset'], '-p', '--patch') ||
2111 \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive') ||
2112 \ index(['--paginate', '-p'], args[0]) >= 0
2113 let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
2115 if &autowrite || &autowriteall | silent! wall | endif
2116 return mods . (a:line2 ? 'split' : 'edit') . ' term://' . s:fnameescape(s:UserCommand(dir, args)) . '|startinsert' . after
2117 elseif has('terminal')
2118 if &autowrite || &autowriteall | silent! wall | endif
2119 return 'exe ' . string(mods . 'terminal ' . (a:line2 ? '' : '++curwin ') . join(map(s:UserCommandList(dir) + args, 's:fnameescape(v:val)'))) . after
2122 if has('gui_running') && !has('win32')
2123 call insert(args, '--no-pager')
2126 if has('nvim') && executable('env')
2127 let pre .= 'env GIT_TERMINAL_PROMPT=0 '
2129 return 'exe ' . string('!' . escape(pre . s:UserCommand(dir, args), '!#%')) . after
2132 let s:exec_paths = {}
2133 function! s:ExecPath() abort
2134 if !has_key(s:exec_paths, g:fugitive_git_executable)
2135 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
2137 return s:exec_paths[g:fugitive_git_executable]
2140 function! s:Subcommands() abort
2141 let exec_path = s:ExecPath()
2142 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
2146 function! s:Aliases(dir) abort
2147 if !has_key(s:aliases, a:dir)
2148 let s:aliases[a:dir] = {}
2149 let lines = s:NullError([a:dir, 'config', '-z', '--get-regexp', '^alias[.]'])[0]
2151 let s:aliases[a:dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
2154 return s:aliases[a:dir]
2157 function! fugitive#Complete(lead, ...) abort
2158 let dir = a:0 == 1 ? a:1 : a:0 == 3 ? a:3 : s:Dir()
2159 let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
2160 let subcmd = matchstr(pre, '\u\w*[! ] *\zs[[:alnum:]-]\+\ze ')
2162 let results = sort(s:Subcommands() + keys(s:Aliases(dir)))
2163 elseif pre =~# ' -- '
2164 return fugitive#CompletePath(a:lead, dir)
2165 elseif a:lead =~# '^-'
2166 let results = split(s:ChompDefault('', dir, subcmd, '--git-completion-helper'), ' ')
2168 return fugitive#CompleteObject(a:lead, dir)
2170 return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
2173 " Section: :Gcd, :Glcd
2175 function! s:DirComplete(A, L, P) abort
2176 return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
2179 function! s:DirArg(path) abort
2180 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
2181 if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
2184 return FugitiveVimPath((empty(s:Tree()) ? s:Dir() : s:Tree()) . '/' . path)
2188 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe s:DirCheck()|exe 'cd<bang>' s:fnameescape(s:DirArg(<q-args>))")
2189 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe s:DirCheck()|exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
2193 call s:command("-bar -bang -range=-1 -addr=other Gstatus", "Status")
2195 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
2196 let dir = a:0 ? a:1 : s:Dir()
2199 let mods = s:Mods(a:mods, &splitbelow ? 'botright' : 'topleft')
2200 let file = fugitive#Find(':', dir)
2201 let arg = ' +setl\ foldmethod=syntax\ foldlevel=1\|let\ w:fugitive_status=FugitiveGitDir() ' .
2202 \ s:fnameescape(file)
2203 for winnr in range(1, winnr('$'))
2204 if s:cpath(file, fnamemodify(bufname(winbufnr(winnr)), ':p'))
2206 call s:ReloadStatus()
2208 call s:ExpireStatus(dir)
2209 exe winnr . 'wincmd w'
2211 let w:fugitive_status = dir
2217 return mods . 'edit' . (a:bang ? '!' : '') . arg
2219 return mods . 'pedit' . arg . '|wincmd P'
2221 return mods . (a:count > 0 ? a:count : '') . 'split' . arg
2224 return 'echoerr ' . string(v:exception)
2229 function! s:StageJump(offset, section, ...) abort
2230 let line = search('^\%(' . a:section . '\)', 'nw')
2232 let line = search('^\%(' . a:1 . '\)', 'nw')
2237 for i in range(a:offset)
2238 call search(s:file_commit_pattern . '\|^$', 'W')
2239 if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
2240 call search(s:file_commit_pattern . '\|^$', 'W')
2242 if empty(getline('.'))
2246 call s:StageReveal()
2248 call s:StageReveal()
2255 function! s:StageSeek(info, fallback) abort
2257 if empty(info.section)
2260 let line = search('^' . info.section, 'wn')
2262 for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
2263 let line = search('^' . section, 'wn')
2265 return line + (info.index > 0 ? 1 : 0)
2271 while len(getline(line))
2272 let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
2274 \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
2275 \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
2276 \ filename ==# info.filename)
2280 if getline(line+1) !~# '^@'
2281 exe s:StageInline('show', line)
2283 if getline(line+1) !~# '^@'
2286 let type = info.sigil ==# '-' ? '-' : '+'
2288 while offset < info.offset
2290 if getline(line) =~# '^@'
2291 let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
2292 elseif getline(line) =~# '^[ ' . type . ']'
2294 elseif getline(line) !~# '^[ @\+-]'
2301 let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
2302 if len(commit) && commit ==# info.commit
2308 let i += getline(line) !~# '^[ @\+-]'
2311 return exists('backup') ? backup : line - 1
2314 function! s:ReloadStatus(...) abort
2315 call s:ExpireStatus(-1)
2316 if get(b:, 'fugitive_type', '') !=# 'index'
2319 let original_lnum = a:0 ? a:1 : line('.')
2320 let info = s:StageInfo(original_lnum)
2321 call fugitive#BufReadStatus()
2322 exe s:StageSeek(info, original_lnum)
2327 let s:last_time = reltime()
2328 if !exists('s:last_times')
2329 let s:last_times = {}
2332 function! s:ExpireStatus(bufnr) abort
2334 let s:last_time = reltime()
2337 let dir = s:Dir(a:bufnr)
2339 let s:last_times[s:cpath(dir)] = reltime()
2344 function! FugitiveReloadCheck() abort
2345 let t = b:fugitive_reltime
2346 return [t, reltimestr(reltime(s:last_time, t)),
2347 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t))]
2350 function! s:ReloadWinStatus(...) abort
2351 if get(b:, 'fugitive_type', '') !=# 'index' || &modified
2354 if !exists('b:fugitive_reltime')
2355 exe s:ReloadStatus()
2358 let t = b:fugitive_reltime
2359 if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
2360 \ reltimestr(reltime(get(s:last_times, s:cpath(s:Dir()), t), t)) =~# '-\|\d\{10\}\.'
2361 exe s:ReloadStatus()
2365 function! s:ReloadTabStatus(...) abort
2366 let mytab = tabpagenr()
2367 let tab = a:0 ? a:1 : mytab
2368 for winnr in range(1, tabpagewinnr(tab, '$'))
2369 if getbufvar(tabpagebuflist(tab)[winnr-1], 'fugitive_type') ==# 'index'
2370 execute 'tabnext '.tab
2372 execute winnr.'wincmd w'
2373 let restorewinnr = 1
2376 call s:ReloadWinStatus()
2378 if exists('restorewinnr')
2382 execute 'tabnext '.mytab
2386 unlet! t:fugitive_reload_status
2389 function! fugitive#ReloadStatus(...) abort
2390 call s:ExpireStatus(a:0 ? a:1 : -2)
2391 if a:0 > 1 ? a:2 : s:CanAutoReloadStatus()
2393 let t:fugitive_reload_status = t
2394 for tabnr in exists('*settabvar') ? range(1, tabpagenr('$')) : []
2395 call settabvar(tabnr, 'fugitive_reload_status', t)
2397 call s:ReloadTabStatus()
2399 call s:ReloadWinStatus()
2403 function! s:CanAutoReloadStatus() abort
2404 return get(g:, 'fugitive_autoreload_status', !has('win32'))
2407 augroup fugitive_status
2409 autocmd BufWritePost * call fugitive#ReloadStatus(-1, 0)
2410 autocmd ShellCmdPost * nested call fugitive#ReloadStatus()
2411 autocmd BufDelete term://* nested call fugitive#ReloadStatus()
2413 autocmd FocusGained * call fugitive#ReloadStatus(-2, 0)
2415 autocmd BufEnter index,index.lock
2416 \ call s:ReloadWinStatus()
2418 \ if exists('t:fugitive_reload_status') |
2419 \ call s:ReloadTabStatus() |
2423 function! s:StageInfo(...) abort
2424 let lnum = a:0 ? a:1 : line('.')
2425 let sigil = matchstr(getline(lnum), '^[ @\+-]')
2428 let type = sigil ==# '-' ? '-' : '+'
2429 while lnum > 0 && getline(lnum) !~# '^@'
2430 if getline(lnum) =~# '^[ '.type.']'
2435 let offset += matchstr(getline(lnum), type.'\zs\d\+')
2436 while getline(lnum) =~# '^[ @\+-]'
2440 let slnum = lnum + 1
2443 while len(getline(slnum - 1)) && empty(section)
2445 let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
2446 if empty(section) && getline(slnum) !~# '^[ @\+-]'
2450 let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
2451 return {'section': section,
2452 \ 'heading': getline(slnum),
2456 \ 'relative': reverse(split(text, ' -> ')),
2457 \ 'paths': map(reverse(split(text, ' -> ')), 's:Tree() . "/" . v:val'),
2458 \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
2459 \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
2463 function! s:Selection(arg1, ...) abort
2465 let arg1 = line('.')
2467 elseif a:arg1 ==# 'v'
2468 let arg1 = line("'<")
2469 let arg2 = line("'>")
2472 let arg2 = a:0 ? a:1 : 0
2476 let last = first - arg2 + 1
2482 while getline(first) =~# '^$\|^[A-Z][a-z]'
2485 if first > last || &filetype !=# 'fugitive'
2489 while getline(flnum) =~# '^[ @\+-]'
2492 let slnum = flnum + 1
2495 while len(getline(slnum - 1)) && empty(section)
2497 let heading = matchstr(getline(slnum), '^\u\l\+.* (\d\+)$')
2498 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
2504 \ 'heading': heading,
2505 \ 'section': matchstr(heading, '^\u\l\+\ze.* (\d\+)$'),
2513 let line = getline(flnum)
2514 let lnum = first - (arg1 == flnum ? 0 : 1)
2515 let root = s:Tree() . '/'
2517 if line =~# '^\u\l\+\ze.* (\d\+)$'
2518 let template.heading = getline(lnum)
2519 let template.section = matchstr(template.heading, '^\u\l\+\ze.* (\d\+)$')
2520 let template.index = 0
2521 elseif line =~# '^[ @\+-]'
2522 let template.index -= 1
2523 if !results[-1].patch
2524 let results[-1].patch = lnum
2526 let results[-1].lnum = lnum
2527 elseif line =~# '^[A-Z?] '
2528 let filename = matchstr(line, '^[A-Z?] \zs.*')
2529 call add(results, extend(deepcopy(template), {
2531 \ 'filename': filename,
2532 \ 'relative': reverse(split(filename, ' -> ')),
2533 \ 'paths': map(reverse(split(filename, ' -> ')), 'root . v:val'),
2534 \ 'status': matchstr(line, '^[A-Z?]'),
2536 elseif line =~# '^\x\x\x\+ '
2537 call add(results, extend({
2539 \ 'commit': matchstr(line, '^\x\x\x\+'),
2540 \ }, template, 'keep'))
2541 elseif line =~# '^\l\+ \x\x\x\+ '
2542 call add(results, extend({
2544 \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
2545 \ 'status': matchstr(line, '^\l\+'),
2546 \ }, template, 'keep'))
2549 let template.index += 1
2550 let line = getline(lnum)
2552 if len(results) && results[0].patch && arg2 == 0
2553 while getline(results[0].patch) =~# '^[ \+-]'
2554 let results[0].patch -= 1
2556 while getline(results[0].lnum + 1) =~# '^[ \+-]'
2557 let results[0].lnum += 1
2563 function! s:StageArgs(visual) abort
2566 for record in s:Selection(a:visual ? 'v' : 'n')
2567 if len(record.commit)
2568 call add(commits, record.commit)
2570 call extend(paths, record.paths)
2572 if s:cpath(s:Tree(), getcwd())
2573 call map(paths, 'fugitive#Path(v:val, "./")')
2575 return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
2578 function! s:Do(action, visual) abort
2579 let line = getline('.')
2581 if !a:0 && !v:count && line =~# '^[A-Z][a-z]'
2582 let header = matchstr(line, '^\S\+\ze:')
2583 if len(header) && exists('*s:Do' . a:action . header . 'Header')
2584 let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
2586 let section = matchstr(line, '^\S\+')
2587 if exists('*s:Do' . a:action . section . 'Heading')
2588 let reload = s:Do{a:action}{section}Heading(line) > 0
2591 return reload ? s:ReloadStatus() : ''
2593 let selection = s:Selection(a:visual ? 'v' : 'n')
2597 call filter(selection, 'v:val.section ==# selection[0].section')
2601 for record in selection
2602 if exists('*s:Do' . a:action . record.section)
2603 let status = s:Do{a:action}{record.section}(record)
2610 let reload = reload || (status > 0)
2613 execute record.lnum + 1
2617 return 'echoerr ' . string(v:exception)
2620 execute s:ReloadStatus()
2622 if exists('success')
2623 call s:StageReveal()
2629 function! s:StageReveal(...) abort
2630 let begin = a:0 ? a:1 : line('.')
2631 if getline(begin) =~# '^@'
2633 while getline(end) =~# '^[ \+-]'
2636 elseif getline(begin) =~# '^commit '
2638 while end < line('$') && getline(end + 1) !~# '^commit '
2641 elseif getline(begin) =~# s:section_pattern
2643 while len(getline(end + 1))
2648 while line('.') > line('w0') + &scrolloff && end > line('w$')
2649 execute "normal! \<C-E>"
2654 let s:file_pattern = '^[A-Z?] .\|^diff --'
2655 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
2656 let s:item_pattern = s:file_commit_pattern . '\|^@@'
2658 function! s:NextHunk(count) abort
2659 if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
2660 exe s:StageInline('show')
2662 for i in range(a:count)
2663 if &filetype ==# 'fugitive'
2664 call search(s:file_pattern . '\|^@', 'W')
2665 if getline('.') =~# s:file_pattern
2666 exe s:StageInline('show')
2667 if getline(line('.') + 1) =~# '^@'
2672 call search('^@@', 'W')
2675 call s:StageReveal()
2679 function! s:PreviousHunk(count) abort
2680 for i in range(a:count)
2681 if &filetype ==# 'fugitive'
2682 let lnum = search(s:file_pattern . '\|^@','Wbn')
2683 call s:StageInline('show', lnum)
2684 call search('^? .\|^@','Wb')
2686 call search('^@@', 'Wb')
2689 call s:StageReveal()
2693 function! s:NextFile(count) abort
2694 for i in range(a:count)
2695 exe s:StageInline('hide')
2696 if !search(s:file_pattern, 'W')
2700 exe s:StageInline('hide')
2704 function! s:PreviousFile(count) abort
2705 exe s:StageInline('hide')
2706 for i in range(a:count)
2707 if !search(s:file_pattern, 'Wb')
2710 exe s:StageInline('hide')
2715 function! s:NextItem(count) abort
2716 for i in range(a:count)
2717 if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
2718 call search('^commit ', 'W')
2721 call s:StageReveal()
2725 function! s:PreviousItem(count) abort
2726 for i in range(a:count)
2727 if !search(s:item_pattern, 'Wbe') && getline('.') !~# s:item_pattern
2728 call search('^commit ', 'Wbe')
2731 call s:StageReveal()
2735 let s:section_pattern = '^[A-Z][a-z][^:]*$'
2736 let s:section_commit_pattern = s:section_pattern . '\|^commit '
2738 function! s:NextSection(count) abort
2739 let orig = line('.')
2740 if getline('.') !~# '^commit '
2743 for i in range(a:count)
2744 if !search(s:section_commit_pattern, 'W')
2748 if getline('.') =~# s:section_commit_pattern
2749 call s:StageReveal()
2750 return getline('.') =~# s:section_pattern ? '+' : ':'
2756 function! s:PreviousSection(count) abort
2757 let orig = line('.')
2758 if getline('.') !~# '^commit '
2761 for i in range(a:count)
2762 if !search(s:section_commit_pattern . '\|\%^', 'bW')
2766 if getline('.') =~# s:section_commit_pattern || line('.') == 1
2767 call s:StageReveal()
2768 return getline('.') =~# s:section_pattern ? '+' : ':'
2774 function! s:NextSectionEnd(count) abort
2776 if empty(getline('.'))
2779 for i in range(a:count)
2780 if !search(s:section_commit_pattern, 'W')
2784 return search('^.', 'Wb')
2787 function! s:PreviousSectionEnd(count) abort
2789 for i in range(a:count)
2790 if search(s:section_commit_pattern, 'Wb') <= 1
2800 return search('^.', 'Wb')
2803 function! s:PatchSearchExpr(reverse) abort
2804 let line = getline('.')
2805 if col('.') ==# 1 && line =~# '^[+-]'
2806 if line =~# '^[+-]\{3\} '
2807 let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
2809 let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
2812 return '?' . escape(pattern, '/') . "\<CR>"
2814 return '/' . escape(pattern, '/?') . "\<CR>"
2817 return a:reverse ? '#' : '*'
2820 function! s:StageInline(mode, ...) abort
2821 if &filetype !=# 'fugitive'
2824 let lnum1 = a:0 ? a:1 : line('.')
2825 let lnum = lnum1 + 1
2826 if a:0 > 1 && a:2 == 0
2827 let info = s:StageInfo(lnum - 1)
2828 if empty(info.paths) && len(info.section)
2829 while len(getline(lnum))
2838 while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
2841 let info = s:StageInfo(lnum)
2842 if !has_key(b:fugitive_diff, info.section)
2845 if getline(lnum + 1) =~# '^[ @\+-]'
2846 let lnum2 = lnum + 1
2847 while getline(lnum2 + 1) =~# '^[ @\+-]'
2850 if a:mode !=# 'show'
2851 setlocal modifiable noreadonly
2852 exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
2853 call remove(b:fugitive_expanded[info.section], info.filename)
2854 setlocal nomodifiable readonly nomodified
2858 if !has_key(b:fugitive_diff, info.section) || info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
2865 for line in b:fugitive_diff[info.section]
2866 if mode ==# 'await' && line[0] ==# '@'
2867 let mode = 'capture'
2869 if mode !=# 'head' && line !~# '^[ @\+-]'
2875 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '--- ' . info.relative[-1]
2877 elseif mode ==# 'head' && substitute(line, "\t$", '', '') ==# '+++ ' . info.relative[0]
2879 elseif mode ==# 'capture'
2880 call add(diff, line)
2881 elseif line[0] ==# '@'
2887 setlocal modifiable noreadonly
2888 silent call append(lnum, diff)
2889 let b:fugitive_expanded[info.section][info.filename] = [start, len(diff)]
2890 setlocal nomodifiable readonly nomodified
2896 function! s:NextExpandedHunk(count) abort
2897 for i in range(a:count)
2898 call s:StageInline('show', line('.'), 1)
2899 call search(s:file_pattern . '\|^@','W')
2904 function! s:StageDiff(diff) abort
2905 let lnum = line('.')
2906 let info = s:StageInfo(lnum)
2907 let prefix = info.offset > 0 ? '+' . info.offset : ''
2908 if empty(info.paths) && info.section ==# 'Staged'
2909 return 'Git! diff --no-ext-diff --cached'
2910 elseif empty(info.paths)
2911 return 'Git! diff --no-ext-diff'
2912 elseif len(info.paths) > 1
2913 execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
2914 return a:diff . '! HEAD:'.s:fnameescape(info.paths[1])
2915 elseif info.section ==# 'Staged' && info.sigil ==# '-'
2916 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2917 return a:diff . '! :0:%'
2918 elseif info.section ==# 'Staged'
2919 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2920 return a:diff . '! @:%'
2921 elseif info.sigil ==# '-'
2922 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
2923 return a:diff . '! :(top)%'
2925 execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
2930 function! s:StageDiffEdit() abort
2931 let info = s:StageInfo(line('.'))
2932 let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
2933 if info.section ==# 'Staged'
2934 return 'Git! diff --no-ext-diff --cached '.s:fnameescape(arg)
2935 elseif info.status ==# '?'
2936 call s:TreeChomp('add', '--intent-to-add', '--', arg)
2937 return s:ReloadStatus()
2939 return 'Git! diff --no-ext-diff '.s:fnameescape(arg)
2943 function! s:StageApply(info, reverse, extra) abort
2944 if a:info.status ==# 'R'
2945 call s:throw('fugitive: patching renamed file not yet supported')
2947 let cmd = ['apply', '-p0', '--recount'] + a:extra
2949 let start = info.patch
2951 let lines = getline(start, end)
2952 if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
2955 while getline(end) =~# '^[-+ ]'
2957 if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2958 call add(lines, ' ' . getline(end)[1:-1])
2961 while start > 0 && getline(start) !~# '^@'
2963 if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
2964 call insert(lines, ' ' . getline(start)[1:-1])
2965 elseif getline(start) =~# '^@'
2966 call insert(lines, getline(start))
2970 throw 'fugitive: cold not find hunk'
2971 elseif getline(start) !~# '^@@ '
2972 throw 'fugitive: cannot apply conflict hunk'
2974 let i = b:fugitive_expanded[info.section][info.filename][0]
2976 while get(b:fugitive_diff[info.section], i, '@') !~# '^@'
2977 call add(head, b:fugitive_diff[info.section][i])
2980 call extend(lines, head, 'keep')
2981 let temp = tempname()
2982 call writefile(lines, temp)
2984 call add(cmd, '--reverse')
2986 call extend(cmd, ['--', temp])
2987 let [output, exec_error] = s:ChompError(cmd)
2991 call s:throw(output)
2994 function! s:StageDelete(lnum1, lnum2, count) abort
2998 for info in s:Selection(a:lnum1, a:lnum2)
2999 if empty(info.paths)
3002 let hash = s:TreeChomp('hash-object', '-w', '--', info.paths[0])
3007 call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
3008 elseif info.status ==# '?'
3009 call s:TreeChomp('clean', '-f', '--', info.paths[0])
3011 call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
3013 call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
3014 elseif info.status =~# '[ADU]' &&
3015 \ get(b:fugitive_status[info.section ==# 'Staged' ? 'Unstaged' : 'Staged'], info.filename, '') =~# '[AU]'
3016 call s:TreeChomp('checkout', info.section ==# 'Staged' ? '--ours' : '--theirs', '--', info.paths[0])
3017 elseif info.status ==# 'U'
3018 call s:TreeChomp('rm', '--', info.paths[0])
3019 elseif info.status ==# 'A'
3020 call s:TreeChomp('rm', '-f', '--', info.paths[0])
3021 elseif info.section ==# 'Unstaged'
3022 call s:TreeChomp('checkout', '--', info.paths[0])
3024 call s:TreeChomp('checkout', 'HEAD^{}', '--', info.paths[0])
3026 call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|Gread ' . hash[0:6])
3029 let err = '|echoerr ' . string(v:exception)
3034 exe s:ReloadStatus()
3035 call s:StageReveal()
3036 return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
3039 function! s:StageIgnore(lnum1, lnum2, count) abort
3041 for info in s:Selection(a:lnum1, a:lnum2)
3042 call extend(paths, info.relative)
3044 call map(paths, '"/" . v:val')
3045 exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
3046 let last = line('$')
3047 if last == 1 && empty(getline(1))
3048 call setline(last, paths)
3050 call append(last, paths)
3056 function! s:DoToggleHeadHeader(value) abort
3057 exe 'edit' s:fnameescape(s:Dir())
3058 call search('\C^index$', 'wc')
3061 function! s:DoStageUnpushedHeading(heading) abort
3062 let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
3066 let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
3067 call feedkeys(':Gpush ' . remote . ' ' . 'HEAD:' . branch)
3070 function! s:DoToggleUnpushedHeading(heading) abort
3071 return s:DoStageUnpushedHeading(a:heading)
3074 function! s:DoStageUnpushed(record) abort
3075 let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
3079 let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
3080 call feedkeys(':Gpush ' . remote . ' ' . a:record.commit . ':' . branch)
3083 function! s:DoToggleUnpushed(record) abort
3084 return s:DoStageUnpushed(a:record)
3087 function! s:DoUnstageUnpulledHeading(heading) abort
3088 call feedkeys(':Grebase')
3091 function! s:DoToggleUnpulledHeading(heading) abort
3092 call s:DoUnstageUnpulledHeading(a:heading)
3095 function! s:DoUnstageUnpulled(record) abort
3096 call feedkeys(':Grebase ' . a:record.commit)
3099 function! s:DoToggleUnpulled(record) abort
3100 call s:DoUnstageUnpulled(a:record)
3103 function! s:DoUnstageUnpushed(record) abort
3104 call feedkeys(':Grebase --autosquash ' . a:record.commit . '^')
3107 function! s:DoToggleStagedHeading(...) abort
3108 call s:TreeChomp('reset', '-q')
3112 function! s:DoUnstageStagedHeading(heading) abort
3113 return s:DoToggleStagedHeading(a:heading)
3116 function! s:DoToggleUnstagedHeading(...) abort
3117 call s:TreeChomp('add', '-u')
3121 function! s:DoStageUnstagedHeading(heading) abort
3122 return s:DoToggleUnstagedHeading(a:heading)
3125 function! s:DoToggleUntrackedHeading(...) abort
3126 call s:TreeChomp('add', '.')
3130 function! s:DoStageUntrackedHeading(heading) abort
3131 return s:DoToggleUntrackedHeading(a:heading)
3134 function! s:DoToggleStaged(record) abort
3136 return s:StageApply(a:record, 1, ['--cached'])
3138 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3143 function! s:DoUnstageStaged(record) abort
3144 return s:DoToggleStaged(a:record)
3147 function! s:DoToggleUnstaged(record) abort
3148 if a:record.patch && a:record.status !=# 'A'
3149 return s:StageApply(a:record, 0, ['--cached'])
3151 call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
3156 function! s:DoStageUnstaged(record) abort
3157 return s:DoToggleUnstaged(a:record)
3160 function! s:DoUnstageUnstaged(record) abort
3161 if a:record.status ==# 'A'
3162 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
3169 function! s:DoToggleUntracked(record) abort
3170 call s:TreeChomp(['add', '--'] + a:record.paths)
3174 function! s:DoStageUntracked(record) abort
3175 return s:DoToggleUntracked(a:record)
3178 function! s:StagePatch(lnum1,lnum2) abort
3183 for lnum in range(a:lnum1,a:lnum2)
3184 let info = s:StageInfo(lnum)
3185 if empty(info.paths) && info.section ==# 'Staged'
3186 return 'Git reset --patch'
3187 elseif empty(info.paths) && info.section ==# 'Unstaged'
3188 return 'Git add --patch'
3189 elseif empty(info.paths) && info.section ==# 'Untracked'
3190 return 'Git add --interactive'
3191 elseif empty(info.paths)
3195 if info.section ==# 'Staged'
3196 let reset += info.relative
3197 elseif info.section ==# 'Untracked'
3198 let intend += info.paths
3199 elseif info.status !~# '^D'
3200 let add += info.relative
3205 call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
3208 execute "Git add --patch -- ".join(map(add,'s:fnameescape(v:val)'))
3211 execute "Git reset --patch -- ".join(map(reset,'s:fnameescape(v:val)'))
3214 return 'echoerr ' . string(v:exception)
3216 return s:ReloadStatus()
3219 " Section: :Gcommit, :Grevert
3221 function! s:CommitInteractive(line1, line2, range, bang, mods, args, patch) abort
3222 let status = s:StatusCommand(a:line1, a:line2, a:range, a:line2, a:bang, a:mods, '', '', [])
3223 let status = len(status) ? status . '|' : ''
3225 return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
3227 return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
3231 function! s:CommitSubcommand(line1, line2, range, bang, mods, args, ...) abort
3232 let mods = substitute(s:Mods(a:mods), '\C\<tab\>', '-tab', 'g')
3233 let dir = a:0 ? a:1 : s:Dir()
3234 let tree = s:Tree(dir)
3235 let msgfile = fugitive#Find('.git/COMMIT_EDITMSG', dir)
3236 let outfile = tempname()
3239 let command = 'set GIT_EDITOR=false& '
3241 let command = 'env GIT_EDITOR=false '
3245 while get(argv, i, '--') !=# '--'
3246 if argv[i] =~# '^-[apzsneiovq].'
3247 call insert(argv, argv[i][0:1])
3248 let argv[i+1] = '-' . argv[i+1][2:-1]
3253 let command .= s:UserCommand(dir, ['commit'] + argv)
3254 if (&autowrite || &autowriteall) && !a:0
3257 if s:HasOpt(argv, '-i', '--interactive')
3258 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 0)
3259 elseif s:HasOpt(argv, '-p', '--patch')
3260 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, argv, 1)
3262 let [error_string, exec_error] = s:TempCmd(outfile, command)
3263 let errors = split(error_string, "\n")
3265 if !has('gui_running')
3269 echo join(errors, "\n")
3270 if filereadable(outfile)
3271 echo join(readfile(outfile), "\n")
3273 call fugitive#ReloadStatus(dir, 1)
3276 let error = get(errors,-2,get(errors,-1,'!'))
3277 if error =~# 'false''\=\.$'
3279 while get(argv, i, '--') !=# '--'
3280 if argv[i] =~# '^\%(-[eips]\|-[CcFm].\+\|--edit\|--interactive\|--patch\|--signoff\|--reedit-message=.*\|--reuse-message=.*\|--file=.*\|--message=.*\)$'
3281 call remove(argv, i)
3282 elseif argv[i] =~# '^\%(-[CcFm]\|--reedit-message\|--reuse-message\|--file\|--message\)$'
3283 call remove(argv, i, i + 1)
3285 if argv[i] =~# '^--cleanup\>'
3291 call insert(argv, '--no-signoff', i)
3292 call insert(argv, '--no-interactive', i)
3293 call insert(argv, '--no-edit', i)
3294 if !exists('cleanup')
3295 call insert(argv, '--cleanup=strip')
3297 call extend(argv, ['-F', msgfile], 'keep')
3298 if (bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&modified) || a:line2 == 0
3299 execute mods . 'keepalt edit' s:fnameescape(msgfile)
3300 elseif s:HasOpt(argv, '-v') || mods =~# '\<tab\>'
3301 execute mods . 'keepalt -tabedit' s:fnameescape(msgfile)
3303 execute mods . 'keepalt split' s:fnameescape(msgfile)
3305 let b:fugitive_commit_arguments = argv
3306 setlocal bufhidden=wipe filetype=gitcommit
3308 elseif empty(errors)
3309 let out = readfile(outfile)
3310 echo get(out, -1, '') =~# 'stash\|\d' ? get(out, -2, '') : get(out, -1, '')
3313 echo join(errors, "\n")
3318 return 'echoerr ' . string(v:exception)
3320 call delete(outfile)
3324 function! s:RevertSubcommand(line1, line2, range, bang, mods, args) abort
3326 let no_commit = s:HasOpt(a:args, '-n', '--no-commit', '--no-edit', '--abort', '--continue', '--quit')
3327 let cmd = s:UserCommand(dir, ['revert'] + (no_commit ? [] : ['-n']) + a:args)
3328 let [out, exec_error] = s:SystemError(cmd)
3329 call fugitive#ReloadStatus(-1, 1)
3330 if no_commit || exec_error
3331 return 'echo ' . string(substitute(out, "\n$", '', ''))
3333 return s:CommitSubcommand(a:line1, a:line2, a:range, a:bang, a:mods, [], dir)
3336 function! s:CommitComplete(A, L, P) abort
3337 if a:A =~# '^--fixup=\|^--squash='
3338 let commits = s:LinesError(['log', '--pretty=format:%s', '@{upstream}..'])[0]
3339 let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
3341 call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
3342 call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
3345 return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
3348 return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'))
3353 function! s:RevertComplete(A, L, P) abort
3354 return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'))
3357 function! s:FinishCommit() abort
3358 let buf = +expand('<abuf>')
3359 let args = getbufvar(buf, 'fugitive_commit_arguments')
3361 call setbufvar(buf, 'fugitive_commit_arguments', [])
3362 if getbufvar(buf, 'fugitive_commit_rebase')
3363 call setbufvar(buf, 'fugitive_commit_rebase', 0)
3364 let s:rebase_continue = s:Dir(buf)
3366 return s:CommitSubcommand(-1, -1, 0, 0, '', args, s:Dir(buf))
3371 call s:command("-nargs=? -range=-1 -complete=customlist,s:CommitComplete Gcommit", "commit")
3372 call s:command("-nargs=? -range=-1 -complete=customlist,s:RevertComplete Grevert", "revert")
3374 " Section: :Gmerge, :Grebase, :Gpull
3376 function! s:MergeComplete(A, L, P) abort
3377 return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'))
3380 function! s:RebaseComplete(A, L, P) abort
3381 return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'))
3384 function! s:PullComplete(A, L, P) abort
3385 return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'))
3388 function! s:RebaseSequenceAborter() abort
3389 if !exists('s:rebase_sequence_aborter')
3390 let temp = tempname() . '.sh'
3393 \ 'echo exec false | cat - "$1" > "$1.fugitive"',
3394 \ 'mv "$1.fugitive" "$1"'],
3396 let s:rebase_sequence_aborter = temp
3398 return s:rebase_sequence_aborter
3401 function! fugitive#Cwindow() abort
3402 if &buftype == 'quickfix'
3406 if &buftype == 'quickfix'
3412 let s:common_efm = ''
3414 \ . '%+Eusage:%.%#,'
3415 \ . '%+Eerror:%.%#,'
3416 \ . '%+Efatal:%.%#,'
3417 \ . '%-G%.%#%\e[K%.%#,'
3418 \ . '%-G%.%#%\r%.%\+'
3420 let s:rebase_abbrevs = {
3434 function! s:RebaseEdit(cmd, dir) abort
3435 let rebase_todo = s:fnameescape(fugitive#Find('.git/rebase-merge/git-rebase-todo', a:dir))
3437 if filereadable(rebase_todo)
3438 let new = readfile(rebase_todo)
3442 for i in range(len(new))
3443 if new[i] =~# '^\l\+\s\+[0-9a-f]\{5,\}\>'
3444 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3446 let sha_length = len(s:TreeChomp(a:dir, 'rev-parse', '--short', sha))
3448 let shortened_sha = strpart(sha, 0, sha_length)
3449 let shas[shortened_sha] = sha
3450 let new[i] = substitute(new[i], sha, shortened_sha, '')
3453 call writefile(new, rebase_todo)
3455 return a:cmd . ' +setlocal\ bufhidden=wipe\|' . escape('let b:fugitive_rebase_shas = ' . string(shas), ' ') . ' ' . rebase_todo
3458 function! s:MergeRebase(cmd, bang, mods, args, ...) abort
3459 let dir = a:0 ? a:1 : s:Dir()
3461 let mods = s:Mods(a:mods)
3462 if a:cmd =~# '^rebase' && s:HasOpt(args, '-i', '--interactive')
3463 let cmd = fugitive#Prepare(dir, '-c', 'sequence.editor=sh ' . s:RebaseSequenceAborter(), 'rebase') . ' ' . s:shellesc(args)
3464 let out = system(cmd)[0:-2]
3465 for file in ['end', 'msgnum']
3466 let file = fugitive#Find('.git/rebase-merge/' . file, dir)
3467 if !filereadable(file)
3468 return 'echoerr ' . string("fugitive: " . out)
3470 call writefile([readfile(file)[0] - 1], file)
3472 call writefile([], fugitive#Find('.git/rebase-merge/done', dir))
3476 return s:RebaseEdit(mods . 'split', dir)
3477 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--edit-todo') && filereadable(fugitive#Find('.git/rebase-merge/git-rebase-todo', dir))
3478 return s:RebaseEdit(mods . 'split', dir)
3479 elseif a:cmd =~# '^rebase' && s:HasOpt(args, '--continue') && !a:0
3480 let rdir = fugitive#Find('.git/rebase-merge', dir)
3481 let exec_error = s:ChompError([dir, 'diff-index', '--cached', '--quiet', 'HEAD', '--'])[1]
3482 if exec_error && isdirectory(rdir)
3483 if getfsize(rdir . '/amend') <= 0
3484 return 'exe ' . string(mods . 'Gcommit -n -F ' . s:fnameescape(rdir .'/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3485 elseif readfile(rdir . '/amend')[0] ==# fugitive#Head(-1, dir)
3486 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(rdir . '/message') . ' -e') . '|let b:fugitive_commit_rebase = 1'
3490 let had_merge_msg = filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3493 let argv += s:AskPassArgs(dir) + ['pull', '--progress']
3495 call add(argv, a:cmd)
3497 if !s:HasOpt(args, '--no-edit', '--abort', '-m') && a:cmd !=# 'rebase'
3498 call add(argv, '--edit')
3500 if a:cmd ==# 'rebase' && s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '--interactive', '-i')
3501 call add(argv, '--interactive')
3503 call extend(argv, args)
3505 let [mp, efm] = [&l:mp, &l:efm]
3507 let cdback = s:Cd(s:Tree(dir))
3508 let &l:errorformat = ''
3509 \ . '%-Gerror:%.%#false''.,'
3510 \ . '%-G%.%# ''git commit'' %.%#,'
3511 \ . '%+Emerge:%.%#,'
3512 \ . s:common_efm . ','
3513 \ . '%+ECannot %.%#: You have unstaged changes.,'
3514 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
3515 \ . '%+EThere is no tracking information for the current branch.,'
3516 \ . '%+EYou are not currently on a branch. Please specify which,'
3517 \ . '%+I %#git rebase --continue,'
3518 \ . 'CONFLICT (%m): %f deleted in %.%#,'
3519 \ . 'CONFLICT (%m): Merge conflict in %f,'
3520 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
3521 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
3522 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
3523 \ . '%+ECONFLICT %.%#,'
3524 \ . '%+EKONFLIKT %.%#,'
3525 \ . '%+ECONFLIT %.%#,'
3526 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
3527 \ . "%+E\u51b2\u7a81 %.%#,"
3529 if a:cmd =~# '^merge' && empty(args) &&
3530 \ (had_merge_msg || isdirectory(fugitive#Find('.git/rebase-apply', dir)) ||
3531 \ !empty(s:TreeChomp(dir, 'diff-files', '--diff-filter=U')))
3532 let cmd = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
3534 let cmd = s:UserCommand(dir, argv)
3536 if !empty($GIT_SEQUENCE_EDITOR) || has('win32')
3537 let old_sequence_editor = $GIT_SEQUENCE_EDITOR
3538 let $GIT_SEQUENCE_EDITOR = 'true'
3540 let cmd = 'env GIT_SEQUENCE_EDITOR=true ' . cmd
3542 if !empty($GIT_EDITOR) || has('win32')
3543 let old_editor = $GIT_EDITOR
3544 let $GIT_EDITOR = 'false'
3546 let cmd = 'env GIT_EDITOR=false ' . substitute(cmd, '^env ', '', '')
3548 if !has('patch-8.1.0334') && has('terminal') && &autowrite
3549 let autowrite_was_set = 1
3553 let &l:makeprg = cmd
3554 silent noautocmd make!
3555 catch /^Vim\%((\a\+)\)\=:E211/
3556 let err = v:exception
3558 if exists('autowrite_was_set')
3562 let [&l:mp, &l:efm] = [mp, efm]
3563 if exists('old_editor')
3564 let $GIT_EDITOR = old_editor
3566 if exists('old_sequence_editor')
3567 let $GIT_SEQUENCE_EDITOR = old_sequence_editor
3571 call fugitive#ReloadStatus(dir, 1)
3572 if empty(filter(getqflist(),'v:val.valid && v:val.type !=# "I"'))
3573 if a:cmd =~# '^rebase' &&
3574 \ filereadable(fugitive#Find('.git/rebase-merge/amend', dir)) &&
3575 \ filereadable(fugitive#Find('.git/rebase-merge/done', dir)) &&
3576 \ get(readfile(fugitive#Find('.git/rebase-merge/done', dir)), -1, '') =~# '^[^e]'
3578 return 'exe ' . string(mods . 'Gcommit --amend -n -F ' . s:fnameescape(fugitive#Find('.git/rebase-merge/message', dir)) . ' -e') . '|let b:fugitive_commit_rebase = 1'
3579 elseif !had_merge_msg && filereadable(fugitive#Find('.git/MERGE_MSG', dir))
3581 return mods . 'Gcommit --no-status -n -t '.s:fnameescape(fugitive#Find('.git/MERGE_MSG', dir))
3584 let qflist = getqflist()
3589 let e.pattern = '^<<<<<<<'
3592 call fugitive#Cwindow()
3594 call setqflist(qflist, 'r')
3600 return exists('err') ? 'echoerr '.string(err) : 'exe'
3603 function! s:RebaseClean(file) abort
3604 if !filereadable(a:file)
3607 let old = readfile(a:file)
3609 for i in range(len(new))
3610 let new[i] = substitute(new[i], '^\l\>', '\=get(s:rebase_abbrevs,submatch(0),submatch(0))', '')
3612 let sha = matchstr(new[i], '\C\<[a-f0-9]\{5,\}\>')
3613 let rebase_shas = getbufvar(a:file, 'fugitive_rebase_shas')
3614 if len(sha) && type(rebase_shas) == type({}) && has_key(rebase_shas, sha)
3615 let new[i] = substitute(new[i], '\C\<' . sha . '\>', rebase_shas[sha], '')
3619 call writefile(new, a:file)
3624 function! s:MergeSubcommand(line1, line2, range, bang, mods, args) abort
3625 return s:MergeRebase('merge', a:bang, a:mods, a:args)
3628 function! s:RebaseSubcommand(line1, line2, range, bang, mods, args) abort
3629 return s:MergeRebase('rebase', a:bang, a:mods, a:args)
3632 function! s:PullSubcommand(line1, line2, range, bang, mods, args) abort
3633 return s:MergeRebase('pull', a:bang, a:mods, a:args)
3636 augroup fugitive_merge
3638 autocmd VimLeavePre,BufDelete git-rebase-todo
3639 \ if getbufvar(+expand('<abuf>'), '&bufhidden') ==# 'wipe' |
3640 \ call s:RebaseClean(expand('<afile>')) |
3641 \ if getfsize(FugitiveFind('.git/rebase-merge/done', +expand('<abuf>'))) == 0 |
3642 \ let s:rebase_continue = FugitiveGitDir(+expand('<abuf>')) |
3645 autocmd BufEnter * nested
3646 \ if exists('s:rebase_continue') |
3647 \ exe s:MergeRebase('rebase', 0, '', [getfsize(fugitive#Find('.git/rebase-merge/git-rebase-todo', s:rebase_continue)) > 0 ? '--continue' : '--abort'], remove(s:, 'rebase_continue')) |
3651 call s:command("-nargs=? -bang -complete=customlist,s:MergeComplete Gmerge", "merge")
3652 call s:command("-nargs=? -bang -complete=customlist,s:RebaseComplete Grebase", "rebase")
3653 call s:command("-nargs=? -bang -complete=customlist,s:PullComplete Gpull", "pull")
3655 " Section: :Ggrep, :Glog
3657 if !exists('g:fugitive_summary_format')
3658 let g:fugitive_summary_format = '%s'
3661 function! s:GrepComplete(A, L, P) abort
3662 return s:CompleteSub('grep', a:A, a:L, a:P)
3665 function! s:LogComplete(A, L, P) abort
3666 return s:CompleteSub('log', a:A, a:L, a:P)
3669 function! s:GrepParseLine(prefix, name_only, dir, line) abort
3670 let entry = {'valid': 1}
3671 let match = matchlist(a:line, '^\(.\{-\}\):\(\d\+\):\(\d\+:\)\=\(.*\)$')
3673 let entry.module = match[1]
3674 let entry.lnum = +match[2]
3675 let entry.col = +match[3]
3676 let entry.text = match[4]
3677 elseif a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
3678 return {'text': a:line}
3680 let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
3681 if len(entry.module)
3682 let entry.text = 'Binary file'
3686 if empty(entry.module) && a:name_only
3687 let entry.module = a:line
3689 if empty(entry.module)
3690 return {'text': a:line}
3692 if entry.module !~# ':'
3693 let entry.filename = a:prefix . entry.module
3695 let entry.filename = fugitive#Find(entry.module, a:dir)
3700 function! s:GrepSubcommand(line1, line2, range, bang, mods, args) abort
3703 let listnr = a:line1 == 0 ? a:line1 : a:line2
3704 let cmd = ['--no-pager', 'grep', '-n', '--no-color', '--full-name']
3705 if fugitive#GitVersion(2, 19)
3706 call add(cmd, '--column')
3708 let tree = s:Tree(dir)
3709 if type(a:args) == type([])
3710 let [args, after] = [a:args, '']
3712 let [args, after] = s:SplitExpandChain(a:args, tree)
3714 let prefix = FugitiveVimPath(s:HasOpt(args, '--cached') || empty(tree) ? 'fugitive://' . dir . '//0/' : tree . '/')
3715 let name_only = s:HasOpt(args, '-l', '--files-with-matches', '--name-only', '-L', '--files-without-match')
3716 let title = [listnr < 0 ? ':Ggrep' : ':Glgrep'] + args
3718 exe listnr 'wincmd w'
3723 call s:QuickfixCreate(listnr, {'title': (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)})
3724 let tempfile = tempname()
3725 if v:version >= 704 | exe 'silent doautocmd <nomodeline> QuickFixCmdPre ' (listnr < 0 ? 'Ggrep' : 'Glgrep') | endif
3726 exe '!' . escape(s:UserCommand(dir, cmd + args), '%#!')
3727 \ printf(&shellpipe . (&shellpipe =~# '%s' ? '' : ' %s'), s:shellesc(tempfile))
3728 let list = map(readfile(tempfile), 's:GrepParseLine(prefix, name_only, dir, v:val)')
3729 call s:QuickfixSet(listnr, list, 'a')
3730 if v:version >= 704 | exe 'silent doautocmd <nomodeline> QuickFixCmdPost ' (listnr < 0 ? 'Ggrep' : 'Glgrep') | endif
3731 if !has('gui_running')
3734 if !a:bang && !empty(list)
3735 return (listnr < 0 ? 'c' : 'l').'first' . after
3741 function! s:LogFlushQueue(state) abort
3742 let queue = remove(a:state, 'queue')
3743 if a:state.child_found
3744 call remove(queue, 0)
3746 if len(queue) && queue[-1] ==# {'text': ''}
3747 call remove(queue, -1)
3752 function! s:LogParse(state, dir, line) abort
3753 if a:state.context ==# 'hunk' && a:line =~# '^[-+ ]'
3756 let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
3758 let a:state.context = 'commit'
3759 let a:state.base = 'fugitive://' . a:dir . '//' . list[2]
3760 let a:state.base_module = len(list[1]) ? list[1] : list[2]
3761 let a:state.message = list[3]
3762 if has_key(a:state, 'diffing')
3763 call remove(a:state, 'diffing')
3765 let queue = s:LogFlushQueue(a:state)
3766 let a:state.queue = [{
3768 \ 'filename': a:state.base . a:state.target,
3769 \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
3770 \ 'text': a:state.message}]
3771 let a:state.child_found = 0
3773 elseif type(a:line) == type(0)
3774 return s:LogFlushQueue(a:state)
3775 elseif a:line =~# '^diff'
3776 let a:state.context = 'diffhead'
3777 elseif a:line =~# '^[+-]\{3\} \w/' && a:state.context ==# 'diffhead'
3778 let a:state.diffing = a:line[5:-1]
3779 elseif a:line =~# '^@@[^@]*+\d' && has_key(a:state, 'diffing') && has_key(a:state, 'base')
3780 let a:state.context = 'hunk'
3781 if empty(a:state.target) || a:state.target ==# a:state.diffing
3782 let a:state.child_found = 1
3783 call add(a:state.queue, {
3785 \ 'filename': a:state.base . a:state.diffing,
3786 \ 'module': a:state.base_module . substitute(a:state.diffing, '^/', ':', ''),
3787 \ 'lnum': +matchstr(a:line, '+\zs\d\+'),
3788 \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
3790 elseif a:state.follow &&
3791 \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
3792 let rename = matchstr(a:line, '^ rename \zs.* => .*\ze (\d\+%)$')
3794 let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
3795 if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
3796 let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
3799 if !get(a:state, 'ignore_summary')
3800 call add(a:state.queue, {'text': a:line})
3802 elseif a:state.context ==# 'commit' || a:state.context ==# 'init'
3803 call add(a:state.queue, {'text': a:line})
3808 function! s:Log(type, bang, line1, count, args, legacy) abort
3811 let listnr = a:type =~# '^l' ? 0 : -1
3812 let [args, after] = s:SplitExpandChain(a:args, s:Tree(dir))
3813 let split = index(args, '--')
3815 let paths = args[split : -1]
3816 let args = args[0 : split - 1]
3823 if a:line1 == 0 && a:count
3824 let path = fugitive#Path(bufname(a:count), '/', dir)
3826 let path = fugitive#Path(@%, '/', dir)
3832 let state = {'context': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
3833 if path =~# '^/\.git\%(/\|$\)\|^$'
3836 let range = "0," . (a:count ? a:count : bufnr(''))
3837 let extra = ['.' . path]
3838 if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
3839 let state.follow = 1
3840 if !s:HasOpt(args, '--follow')
3841 call insert(args, '--follow')
3843 if !s:HasOpt(args, '--summary')
3844 call insert(args, '--summary')
3845 let state.ignore_summary = 1
3849 if !s:HasOpt(args, '--merges', '--no-merges')
3850 call insert(args, '--no-merges')
3852 call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
3854 if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
3855 let owner = s:Owner(@%, dir)
3857 call add(args, owner)
3863 if s:HasOpt(args, '-g', '--walk-reflogs')
3864 let format = "%gd\t%H %gs"
3866 let format = "%h\t%H " . g:fugitive_summary_format
3868 let cmd = ['--no-pager']
3869 if fugitive#GitVersion(1, 9)
3870 call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'])
3872 call extend(cmd, ['log', '-U0', '--no-patch'])
3875 \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
3876 \ args + paths + extra)
3877 let state.target = path
3878 let title = (listnr < 0 ? ':Gclog ' : ':Gllog ') . s:fnameescape(args + paths)
3879 if empty(paths + extra) && a:legacy && len(s:Relative('/'))
3880 let after = '|echohl WarningMsg|echo ' . string('Use :0Glog or :0Gclog for old behavior of targeting current file') . '|echohl NONE' . after
3882 return s:QuickfixStream(listnr, title, s:UserCommandList(dir) + cmd, !a:bang, s:function('s:LogParse'), state, dir) . after
3885 call s:command("-bang -nargs=? -range=-1 -addr=windows -complete=customlist,s:GrepComplete Ggrep", "grep")
3886 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Gcgrep :execute s:GrepSubcommand(-1, -1, 0, <bang>0, '<mods>', <q-args>)")
3887 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:GrepSubcommand(0, 0, 0, <bang>0, '<mods>', <q-args>)")
3888 call s:command("-bang -nargs=? -range=-1 -addr=other -complete=customlist,s:LogComplete Glog :exe s:Log('c',<bang>0,<line1>,<count>,<q-args>, 1)")
3889 call s:command("-bang -nargs=? -range=-1 -addr=other -complete=customlist,s:LogComplete Gclog :exe s:Log('c',<bang>0,<line1>,<count>,<q-args>, 0)")
3890 call s:command("-bang -nargs=? -range=-1 -addr=other -complete=customlist,s:LogComplete Gllog :exe s:Log('l',<bang>0,<line1>,<count>,<q-args>, 0)")
3892 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
3894 function! s:UsableWin(nr) abort
3895 return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
3896 \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
3897 \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
3898 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
3901 function! s:OpenParse(args) abort
3903 let args = copy(a:args)
3904 while !empty(args) && args[0] =~# '^+'
3905 call add(pre, ' ' . escape(remove(args, 0), ' |"'))
3908 let file = join(args)
3909 elseif empty(expand('%'))
3911 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
3916 return [s:Expand(file), join(pre)]
3919 function! s:DiffClose() abort
3920 let mywinnr = winnr()
3921 for winnr in [winnr('#')] + range(winnr('$'),1,-1)
3922 if winnr != mywinnr && getwinvar(winnr,'&diff')
3923 execute winnr.'wincmd w'
3933 function! s:BlurStatus() abort
3934 if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
3935 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
3937 exe winnrs[0].'wincmd w'
3947 function! s:OpenExec(cmd, mods, args, ...) abort
3948 let dir = a:0 ? s:Dir(a:1) : s:Dir()
3949 let temp = tempname()
3950 let columns = get(g:, 'fugitive_columns', 80)
3954 let env = 'set COLUMNS=' . columns . '& '
3956 let env = 'env COLUMNS=' . columns . ' '
3958 silent! execute '!' . escape(env . s:UserCommand(dir, ['--no-pager'] + a:args), '!#%') .
3959 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
3961 let temp = s:Resolve(temp)
3962 let first = join(readfile(temp, '', 2), "\n")
3963 if first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3964 let filetype = 'man'
3966 let filetype = 'git'
3968 let s:temp_files[s:cpath(temp)] = { 'dir': dir, 'filetype': filetype, 'modifiable': first =~# '^diff ' }
3972 silent execute s:Mods(a:mods) . a:cmd temp
3973 call fugitive#ReloadStatus(dir, 1)
3974 return 'echo ' . string(':!' . s:UserCommand(dir, a:args))
3977 function! s:Open(cmd, bang, mods, arg, args) abort
3979 return s:OpenExec(a:cmd, a:mods, s:SplitExpand(a:arg, s:Tree()))
3982 let mods = s:Mods(a:mods)
3984 let [file, pre] = s:OpenParse(a:args)
3985 let file = s:Generate(file)
3987 return 'echoerr ' . string(v:exception)
3989 if file !~# '^\a\a\+:'
3990 let file = s:sub(file, '/$', '')
3995 return mods . a:cmd . pre . ' ' . s:fnameescape(file)
3998 function! s:ReadCommand(line1, line2, range, count, bang, mods, reg, arg, args) abort
3999 let mods = s:Mods(a:mods)
4002 let delete = 'silent 1,' . line('$') . 'delete_|'
4003 let after = line('$')
4005 let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
4011 let args = s:SplitExpand(a:arg, s:Tree(dir))
4012 silent execute mods . after . 'read!' escape(s:UserCommand(dir, ['--no-pager'] + args), '!#%')
4013 execute delete . 'diffupdate'
4014 call fugitive#ReloadStatus()
4015 return 'redraw|echo '.string(':!'.s:UserCommand(dir, args))
4018 let [file, pre] = s:OpenParse(a:args)
4019 let file = s:Generate(file)
4021 return 'echoerr ' . string(v:exception)
4023 if file =~# '^fugitive:' && after is# 0
4024 return 'exe ' .string(mods . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
4027 exe after . 'foldopen!'
4029 return mods . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
4032 function! s:ReadComplete(A,L,P) abort
4034 return fugitive#Complete(a:A, a:L, a:P)
4036 return fugitive#CompleteObject(a:A, a:L, a:P)
4040 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#CompleteObject Ge execute s:Open('edit<bang>', 0, '<mods>', <q-args>, [<f-args>])")
4041 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#CompleteObject Gedit execute s:Open('edit<bang>', 0, '<mods>', <q-args>, [<f-args>])")
4042 call s:command("-bar -bang -nargs=* -complete=customlist,s:ReadComplete Gpedit execute s:Open('pedit', <bang>0, '<mods>', <q-args>, [<f-args>])")
4043 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:ReadComplete Gsplit execute s:Open((<count> > 0 ? <count> : '').(<count> ? 'split' : 'edit'), <bang>0, '<mods>', <q-args>, [<f-args>])")
4044 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:ReadComplete Gvsplit execute s:Open((<count> > 0 ? <count> : '').(<count> ? 'vsplit' : 'edit!'), <bang>0, '<mods>', <q-args>, [<f-args>])")
4045 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:ReadComplete -addr=tabs Gtabedit execute s:Open((<count> >= 0 ? <count> : '').'tabedit', <bang>0, '<mods>', <q-args>, [<f-args>])")
4046 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:ReadComplete Gr", "Read")
4047 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:ReadComplete Gread", "Read")
4049 " Section: :Gwrite, :Gwq
4051 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#CompleteObject Gwrite", "Write")
4052 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#CompleteObject Gw", "Write")
4053 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#CompleteObject Gwq", "Wq")
4055 function! s:WriteCommand(line1, line2, range, count, bang, mods, reg, arg, args) abort
4056 if exists('b:fugitive_commit_arguments')
4057 return 'write|bdelete'
4058 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
4060 elseif get(b:, 'fugitive_type', '') ==# 'index'
4062 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
4063 let filename = getline(4)[6:-1]
4066 setlocal buftype=nowrite
4067 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
4068 let [message, exec_error] = s:ChompError(['apply', '--cached', '--reverse', '--', expand('%:p')])
4070 let [message, exec_error] = s:ChompError(['apply', '--cached', '--', expand('%:p')])
4080 return 'Gedit '.fnameescape(filename)
4083 let mytab = tabpagenr()
4084 let mybufnr = bufnr('')
4086 let file = len(a:args) ? s:Generate(s:Expand(join(a:args, ' '))) : fugitive#Real(@%)
4088 return 'echoerr ' . string(v:exception)
4091 return 'echoerr '.string('fugitive: cannot determine file path')
4093 if file =~# '^fugitive:'
4094 return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
4097 let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
4098 if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
4099 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
4100 return 'echoerr v:errmsg'
4103 for nr in range(1,bufnr('$'))
4104 if fnamemodify(bufname(nr),':p') ==# file
4109 if treebufnr > 0 && treebufnr != bufnr('')
4110 let temp = tempname()
4111 silent execute 'keepalt %write '.temp
4112 for tab in [mytab] + range(1,tabpagenr('$'))
4113 for winnr in range(1,tabpagewinnr(tab,'$'))
4114 if tabpagebuflist(tab)[winnr-1] == treebufnr
4115 execute 'tabnext '.tab
4117 execute winnr.'wincmd w'
4118 let restorewinnr = 1
4121 let lnum = line('.')
4122 let last = line('$')
4123 silent execute '$read '.temp
4124 silent execute '1,'.last.'delete_'
4130 if exists('restorewinnr')
4133 execute 'tabnext '.mytab
4140 call writefile(readfile(temp,'b'),file,'b')
4143 execute 'write! '.s:fnameescape(file)
4147 let [error, exec_error] = s:ChompError(['add', '--force', '--', file])
4149 let [error, exec_error] = s:ChompError(['add', '--', file])
4152 let v:errmsg = 'fugitive: '.error
4153 return 'echoerr v:errmsg'
4155 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
4159 let one = s:Generate(':1:'.file)
4160 let two = s:Generate(':2:'.file)
4161 let three = s:Generate(':3:'.file)
4162 for nr in range(1,bufnr('$'))
4163 let name = fnamemodify(bufname(nr), ':p')
4164 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
4165 execute nr.'bdelete'
4170 let zero = s:Generate(':0:'.file)
4171 silent execute 'doautocmd' s:nomodeline 'BufWritePost' s:fnameescape(zero)
4172 for tab in range(1,tabpagenr('$'))
4173 for winnr in range(1,tabpagewinnr(tab,'$'))
4174 let bufnr = tabpagebuflist(tab)[winnr-1]
4175 let bufname = fnamemodify(bufname(bufnr), ':p')
4176 if bufname ==# zero && bufnr != mybufnr
4177 execute 'tabnext '.tab
4179 execute winnr.'wincmd w'
4180 let restorewinnr = 1
4183 let lnum = line('.')
4184 let last = line('$')
4185 silent execute '$read '.s:fnameescape(file)
4186 silent execute '1,'.last.'delete_'
4191 if exists('restorewinnr')
4194 execute 'tabnext '.mytab
4200 call fugitive#ReloadStatus()
4204 function! s:WqCommand(...) abort
4205 let bang = a:5 ? '!' : ''
4206 if exists('b:fugitive_commit_arguments')
4209 let result = call(s:function('s:WriteCommand'),a:000)
4210 if result =~# '^\%(write\|wq\|echoerr\)'
4211 return s:sub(result,'^write','wq')
4213 return result.'|quit'.bang
4217 augroup fugitive_commit
4219 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute substitute(s:FinishCommit(), '\C^echoerr \(''[^'']*''\)*', 'redraw|echohl ErrorMsg|echo \1|echohl NONE', '')
4222 " Section: :Gpush, :Gfetch
4224 function! s:PushComplete(A, L, P) abort
4225 return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompleteRemote'))
4228 function! s:FetchComplete(A, L, P) abort
4229 return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'))
4232 function! s:AskPassArgs(dir) abort
4233 if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) && fugitive#GitVersion(1, 8) &&
4234 \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#Config('core.askPass', a:dir))
4235 if s:executable(s:ExecPath() . '/git-gui--askpass')
4236 return ['-c', 'core.askPass=' . s:ExecPath() . '/git-gui--askpass']
4237 elseif s:executable('ssh-askpass')
4238 return ['-c', 'core.askPass=ssh-askpass']
4244 function! s:Dispatch(bang, cmd, args) abort
4246 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
4248 let b:current_compiler = 'git'
4249 let &l:errorformat = s:common_efm
4250 let &l:makeprg = s:UserCommand(dir, s:AskPassArgs(dir) + [a:cmd] + a:args)
4251 if exists(':Make') == 2
4255 if !has('patch-8.1.0334') && has('terminal') && &autowrite
4256 let autowrite_was_set = 1
4260 silent noautocmd make!
4262 return 'call fugitive#Cwindow()|call fugitive#ReloadStatus()'
4265 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
4266 if empty(cc) | unlet! b:current_compiler | endif
4267 if exists('autowrite_was_set')
4273 function! s:PushSubcommand(line1, line2, range, bang, mods, args) abort
4274 return s:Dispatch(a:bang ? '!' : '', 'push', a:args)
4277 function! s:FetchSubcommand(line1, line2, range, bang, mods, args) abort
4278 return s:Dispatch(a:bang ? '!' : '', 'fetch', a:args)
4281 call s:command("-nargs=? -bang -complete=customlist,s:PushComplete Gpush", "push")
4282 call s:command("-nargs=? -bang -complete=customlist,s:FetchComplete Gfetch", "fetch")
4286 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gdiffsplit :execute s:Diff(1, <bang>0, '<mods>', <f-args>)")
4287 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Gvdiffsplit :execute s:Diff(0, <bang>0, 'vertical <mods>', <f-args>)")
4288 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#CompleteObject Ghdiffsplit :execute s:Diff(0, <bang>0, '<mods>', <f-args>)")
4290 augroup fugitive_diff
4292 autocmd BufWinLeave *
4293 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
4294 \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
4296 autocmd BufWinEnter *
4297 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
4298 \ call s:diffoff() |
4302 function! s:can_diffoff(buf) abort
4303 return getwinvar(bufwinnr(a:buf), '&diff') &&
4304 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
4307 function! fugitive#CanDiffoff(buf) abort
4308 return s:can_diffoff(bufnr(a:buf))
4311 function! s:diff_modifier(count) abort
4312 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
4313 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
4315 elseif &diffopt =~# 'vertical'
4317 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
4324 function! s:diff_window_count() abort
4326 for nr in range(1,winnr('$'))
4327 let c += getwinvar(nr,'&diff')
4332 function! s:diff_restore() abort
4333 let restore = 'setlocal nodiff noscrollbind'
4334 \ . ' scrollopt=' . &l:scrollopt
4335 \ . (&l:wrap ? ' wrap' : ' nowrap')
4336 \ . ' foldlevel=999'
4337 \ . ' foldmethod=' . &l:foldmethod
4338 \ . ' foldcolumn=' . &l:foldcolumn
4339 \ . ' foldlevel=' . &l:foldlevel
4340 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
4341 if has('cursorbind')
4342 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
4347 function! s:diffthis() abort
4349 let w:fugitive_diff_restore = s:diff_restore()
4354 function! s:diffoff() abort
4355 if exists('w:fugitive_diff_restore')
4356 execute w:fugitive_diff_restore
4357 unlet w:fugitive_diff_restore
4363 function! s:diffoff_all(dir) abort
4364 let curwin = winnr()
4365 for nr in range(1,winnr('$'))
4366 if getwinvar(nr,'&diff')
4368 execute nr.'wincmd w'
4369 let restorewinnr = 1
4371 if s:Dir() ==# a:dir
4376 execute curwin.'wincmd w'
4379 function! s:CompareAge(mine, theirs) abort
4380 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
4381 let mine = substitute(a:mine, '^:', '', '')
4382 let theirs = substitute(a:theirs, '^:', '', '')
4383 let my_score = get(scores, ':'.mine, 0)
4384 let their_score = get(scores, ':'.theirs, 0)
4385 if my_score || their_score
4386 return my_score < their_score ? -1 : my_score != their_score
4387 elseif mine ==# theirs
4390 let base = s:TreeChomp('merge-base', mine, theirs)
4393 elseif base ==# theirs
4396 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
4397 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
4398 return my_time < their_time ? -1 : my_time != their_time
4401 function! s:IsConflicted() abort
4402 return len(@%) && !empty(s:ChompDefault('', 'ls-files', '--unmerged', '--', expand('%:p')))
4405 function! s:Diff(autodir, keepfocus, mods, ...) abort
4406 let args = copy(a:000)
4408 if get(args, 0) =~# '^+'
4409 let post = remove(args, 0)[1:-1]
4411 if exists(':DiffGitCached') && empty(args)
4412 return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
4414 let commit = s:DirCommitFile(@%)[1]
4415 if a:mods =~# '\<tab\>'
4416 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4417 let pre = 'tab split'
4419 let mods = 'keepalt ' . a:mods
4422 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
4423 if (empty(args) || args[0] ==# ':') && a:keepfocus
4425 if empty(commit) && s:IsConflicted()
4426 let parents = [s:Relative(':2:'), s:Relative(':3:')]
4427 elseif empty(commit)
4428 let parents = [s:Relative(':0:')]
4429 elseif commit =~# '^\d\=$'
4430 let parents = [s:Relative('HEAD:')]
4431 elseif commit =~# '^\x\x\+$'
4432 let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
4433 call map(parents, 's:Relative(v:val . ":")')
4437 if exists('parents') && len(parents) > 1
4439 let mods = (a:autodir ? s:diff_modifier(len(parents) + 1) : '') . s:Mods(mods, 'leftabove')
4441 execute mods 'split' s:fnameescape(s:Generate(parents[0]))
4442 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4446 call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
4447 let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
4448 for i in range(len(parents)-1, 1, -1)
4449 execute mods 'split' s:fnameescape(s:Generate(parents[i]))
4450 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
4454 call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
4462 let arg = join(args, ' ')
4467 let file = s:Relative()
4470 let file = s:Relative(':0:')
4471 elseif arg =~# '^:\d$'
4473 let file = s:Relative(arg . ':')
4476 let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
4478 return 'echoerr ' . string(v:exception)
4481 elseif exists('parents') && len(parents)
4482 let file = parents[-1]
4484 let file = s:Relative()
4485 elseif s:IsConflicted()
4486 let file = s:Relative(':1:')
4487 let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
4490 let file = s:Relative(':0:')
4492 let spec = s:Generate(file)
4493 if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
4494 let spec = FugitiveVimPath(spec . s:Relative('/'))
4497 let restore = s:diff_restore()
4498 let w:fugitive_diff_restore = restore
4499 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
4500 let mods = s:Mods(mods, 'rightbelow')
4502 let mods = s:Mods(mods, 'leftabove')
4504 let mods = (a:autodir ? s:diff_modifier(2) : '') . mods
4505 if &diffopt =~# 'vertical'
4506 let diffopt = &diffopt
4507 set diffopt-=vertical
4509 execute mods 'diffsplit' s:fnameescape(spec)
4510 let &l:readonly = &l:readonly
4512 let w:fugitive_diff_restore = restore
4514 if getwinvar('#', '&diff')
4521 return 'echoerr ' . string(v:exception)
4523 if exists('diffopt')
4524 let &diffopt = diffopt
4529 " Section: :Gmove, :Gremove
4531 function! s:Move(force, rename, destination) abort
4532 if a:destination =~# '^\.\.\=\%(/\|$\)'
4533 let destination = simplify(getcwd() . '/' . a:destination)
4534 elseif a:destination =~# '^\a\+:\|^/'
4535 let destination = a:destination
4536 elseif a:destination =~# '^:/:\='
4537 let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
4538 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
4539 let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
4540 elseif a:destination =~# '^:(literal)'
4541 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
4543 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
4545 let destination = s:Tree() . '/' . a:destination
4547 let destination = s:Slash(destination)
4551 let [message, exec_error] = s:ChompError(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
4553 let v:errmsg = 'fugitive: '.message
4554 return 'echoerr v:errmsg'
4556 if isdirectory(destination)
4557 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
4559 call fugitive#ReloadStatus()
4560 if empty(s:DirCommitFile(@%)[1])
4561 if isdirectory(destination)
4562 return 'keepalt edit '.s:fnameescape(destination)
4564 return 'keepalt saveas! '.s:fnameescape(destination)
4567 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
4571 function! s:RenameComplete(A,L,P) abort
4572 if a:A =~# '^[.:]\=/'
4573 return fugitive#CompletePath(a:A)
4575 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
4576 return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
4580 function! s:Remove(after, force) abort
4581 if s:DirCommitFile(@%)[1] ==# ''
4583 elseif s:DirCommitFile(@%)[1] ==# '0'
4584 let cmd = ['rm','--cached']
4586 let v:errmsg = 'fugitive: rm not supported here'
4587 return 'echoerr v:errmsg'
4590 let cmd += ['--force']
4592 let [message, exec_error] = s:ChompError(cmd + ['--', expand('%:p')])
4594 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
4595 return 'echoerr '.string(v:errmsg)
4597 call fugitive#ReloadStatus()
4598 return a:after . (a:force ? '!' : '')
4602 augroup fugitive_remove
4604 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
4605 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#CompletePath Gmove :execute s:Move(<bang>0,0,<q-args>)" |
4606 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
4607 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
4608 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
4614 function! s:Keywordprg() abort
4615 let args = ' --git-dir='.escape(s:Dir(),"\\\"' ")
4616 if has('gui_running') && !has('win32')
4617 return s:UserCommand() . ' --no-pager' . args . ' log -1'
4619 return s:UserCommand() . args . ' show'
4623 function! s:linechars(pattern) abort
4624 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
4625 if exists('*synconcealed') && &conceallevel > 1
4626 for col in range(1, chars)
4627 let chars -= synconcealed(line('.'), col)[0]
4633 function! s:BlameBufnr(...) abort
4634 let state = s:TempState(bufname(a:0 ? a:1 : ''))
4635 if get(state, 'filetype', '') ==# 'fugitiveblame'
4636 return get(state, 'bufnr', -1)
4642 function! s:BlameCommitFileLnum(...) abort
4643 let line = a:0 ? a:1 : getline('.')
4644 let state = a:0 ? a:2 : s:TempState()
4645 let commit = matchstr(line, '^\^\=\zs\x\+')
4646 if commit =~# '^0\+$'
4648 elseif line !~# '^\^' && has_key(state, 'blame_reverse_end')
4649 let commit = get(s:LinesError('rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end)[0], 0, '')
4651 let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
4652 let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s\+\%(\%( \d\+ \)\@<!([^()]*\w \d\+)\|\d\+ \)')
4653 if empty(path) && lnum
4654 let path = get(state, 'blame_file', '')
4656 return [commit, path, lnum]
4659 function! s:BlameLeave() abort
4660 let bufwinnr = bufwinnr(s:BlameBufnr())
4662 let bufnr = bufnr('')
4663 exe bufwinnr . 'wincmd w'
4664 return bufnr . 'bdelete'
4669 function! s:BlameQuit() abort
4670 let cmd = s:BlameLeave()
4673 elseif len(s:DirCommitFile(@%)[1])
4674 return cmd . '|Gedit'
4680 function! s:BlameComplete(A, L, P) abort
4681 return s:CompleteSub('blame', a:A, a:L, a:P)
4684 function! s:BlameSubcommand(line1, count, range, bang, mods, args) abort
4686 let flags = copy(a:args)
4692 if a:line1 > 0 && a:count > 0 && a:range != 1
4693 call extend(ranges, ['-L', a:line1 . ',' . a:count])
4695 while i < len(flags)
4696 let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
4697 if len(match) && len(match[2])
4698 call insert(flags, match[1])
4699 let flags[i+1] = '-' . match[2]
4703 if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
4705 elseif arg ==# '--contents' && i + 1 < len(flags)
4706 call extend(commits, remove(flags, i, i+1))
4708 elseif arg ==# '-L' && i + 1 < len(flags)
4709 call extend(ranges, remove(flags, i, i+1))
4711 elseif arg =~# '^--contents='
4712 call add(commits, remove(flags, i))
4714 elseif arg =~# '^-L.'
4715 call add(ranges, remove(flags, i))
4717 elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
4721 echo s:ChompError(['blame', arg])[0]
4726 if i + 1 < len(flags)
4727 call extend(files, remove(flags, i + 1, -1))
4729 call remove(flags, i)
4731 elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
4732 if index(flags, '--') >= 0
4733 call add(commits, remove(flags, i))
4736 if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
4737 call add(commits, remove(flags, i))
4741 let dcf = s:DirCommitFile(fugitive#Find(arg))
4742 if len(dcf[1]) && empty(dcf[2])
4743 call add(commits, remove(flags, i))
4748 call add(files, remove(flags, i))
4753 let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./'))), '^\.\%(/\|$\)', '', '')
4754 if empty(commits) && len(files) > 1
4755 call add(commits, remove(files, 1))
4759 let cmd = ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', 'blame', '--show-number']
4760 call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
4761 if a:count > 0 && empty(ranges)
4762 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
4764 call extend(cmd, ranges)
4767 elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
4768 let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
4769 elseif empty(files) && !s:HasOpt(flags, '--reverse')
4770 let cmd += ['--contents', '-']
4772 let basecmd = escape(fugitive#Prepare(cmd) . ' -- ' . s:shellesc(len(files) ? files : file), '!#%')
4773 let tempname = tempname()
4774 let error = tempname . '.err'
4775 let temp = tempname . (raw ? '' : '.fugitiveblame')
4777 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
4779 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
4784 let lines = readfile(error)
4786 let lines = readfile(temp)
4788 for i in range(len(lines))
4789 if lines[i] =~# '^error: \|^fatal: '
4797 if i != len(lines) - 1
4803 let temp_state = {'dir': s:Dir(), 'filetype': (raw ? '' : 'fugitiveblame'), 'blame_flags': flags, 'blame_file': file, 'modifiable': 0}
4804 if s:HasOpt(flags, '--reverse')
4805 let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
4807 if (a:line1 == 0 || a:range == 1) && a:count > 0
4808 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit'], a:count - (a:line1 ? a:line1 : 1), 'split')
4809 return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
4811 let temp = s:Resolve(temp)
4812 let s:temp_files[s:cpath(temp)] = temp_state
4813 if len(ranges + commits + files) || raw
4814 let mods = s:Mods(a:mods)
4816 exe 'silent keepalt' mods 'split' s:fnameescape(temp)
4817 elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
4818 exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
4820 return mods . 'edit ' . s:fnameescape(temp)
4824 if a:mods =~# '\<tab\>'
4827 let mods = substitute(a:mods, '\<tab\>', '', 'g')
4828 for winnr in range(winnr('$'),1,-1)
4829 if getwinvar(winnr, '&scrollbind')
4830 call setwinvar(winnr, '&scrollbind', 0)
4832 if exists('+cursorbind') && getwinvar(winnr, '&cursorbind')
4833 call setwinvar(winnr, '&cursorbind', 0)
4835 if s:BlameBufnr(winbufnr(winnr)) > 0
4836 execute winbufnr(winnr).'bdelete'
4839 let bufnr = bufnr('')
4840 let temp_state.bufnr = bufnr
4841 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
4842 if exists('+cursorbind')
4843 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
4846 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
4849 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
4851 setlocal scrollbind nowrap nofoldenable
4852 if exists('+cursorbind')
4855 let top = line('w0') + &scrolloff
4856 let current = line('.')
4857 exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
4858 let w:fugitive_leave = restore
4862 if exists('+cursorbind')
4865 setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
4866 if exists('+relativenumber')
4867 setlocal norelativenumber
4869 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
4870 call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>')
4871 call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>')
4872 call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>')
4879 return 'echoerr ' . string(v:exception)
4883 function! s:BlameCommit(cmd, ...) abort
4884 let line = a:0 ? a:1 : getline('.')
4885 let state = a:0 ? a:2 : s:TempState()
4886 let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
4887 let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
4888 let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
4889 if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
4890 let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
4891 return s:Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4893 if commit =~# '^0*$'
4894 return 'echoerr ' . string('fugitive: no commit')
4896 if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
4897 let path = commit . ':' . path
4898 return s:Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
4900 let cmd = s:Open(mods . a:cmd, 0, '', commit, [commit])
4901 if cmd =~# '^echoerr'
4905 if a:cmd ==# 'pedit' || empty(path)
4908 if search('^diff .* b/\M'.escape(path,'\').'$','W')
4910 let head = line('.')
4911 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
4912 let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
4913 let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
4914 if lnum >= top && lnum <= top + len
4915 let offset = lnum - top
4923 while offset > 0 && line('.') < line('$')
4925 if getline('.') =~# '^[ ' . sigil . ']'
4938 function! s:BlameJump(suffix, ...) abort
4939 let suffix = a:suffix
4940 let [commit, path, lnum] = s:BlameCommitFileLnum()
4942 return 'echoerr ' . string('fugitive: could not determine filename for blame')
4944 if commit =~# '^0*$'
4948 let offset = line('.') - line('w0')
4949 let flags = get(s:TempState(), 'blame_flags', [])
4951 if s:HasOpt(flags, '--reverse')
4952 call remove(flags, '--reverse')
4954 call add(flags, '--reverse')
4957 let blame_bufnr = s:BlameBufnr()
4959 let bufnr = bufnr('')
4960 let winnr = bufwinnr(blame_bufnr)
4962 exe winnr.'wincmd w'
4964 execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
4970 if exists(':Gblame')
4971 let my_bufnr = bufnr('')
4973 let blame_args = flags + [commit . suffix, '--', path]
4974 let result = s:BlameSubcommand(0, 0, 0, 0, '', blame_args)
4976 let blame_args = flags
4977 let result = s:BlameSubcommand(-1, -1, 0, 0, '', blame_args)
4979 if bufnr('') == my_bufnr
4984 let delta = line('.') - line('w0') - offset
4986 execute 'normal! '.delta."\<C-E>"
4988 execute 'normal! '.(-delta)."\<C-Y>"
4992 echo ':Gblame' s:fnameescape(blame_args)
4997 let s:hash_colors = {}
4999 function! fugitive#BlameSyntax() abort
5000 let conceal = has('conceal') ? ' conceal' : ''
5001 let config = fugitive#Config()
5002 let flags = get(s:TempState(), 'blame_flags', [])
5003 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
5004 syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5005 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5006 if get(get(config, 'blame.blankboundary', ['x']), 0, 'x') =~# '^$\|^true$' || s:HasOpt(flags, '-b')
5007 syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
5009 syn match FugitiveblameBoundary "^\^"
5011 syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
5012 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
5013 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
5014 exec 'syn match FugitiveblameLineNumber "\s*\d\+)\@=" contained containedin=FugitiveblameAnnotation' conceal
5015 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)
5016 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5017 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
5018 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
5019 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
5020 hi def link FugitiveblameBoundary Keyword
5021 hi def link FugitiveblameHash Identifier
5022 hi def link FugitiveblameBoundaryIgnore Ignore
5023 hi def link FugitiveblameUncommitted Ignore
5024 hi def link FugitiveblameScoreDebug Debug
5025 hi def link FugitiveblameTime PreProc
5026 hi def link FugitiveblameLineNumber Number
5027 hi def link FugitiveblameOriginalFile String
5028 hi def link FugitiveblameOriginalLineNumber Float
5029 hi def link FugitiveblameShort FugitiveblameDelimiter
5030 hi def link FugitiveblameDelimiter Delimiter
5031 hi def link FugitiveblameNotCommittedYet Comment
5032 if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
5036 for lnum in range(1, line('$'))
5037 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
5038 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
5042 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
5043 \ && empty(get(s:hash_colors, hash))
5044 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
5045 let color = csapprox#per_component#Approximate(r, g, b)
5046 if color == 16 && &background ==# 'dark'
5049 let s:hash_colors[hash] = ' ctermfg='.color
5051 let s:hash_colors[hash] = ''
5053 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
5055 call s:BlameRehighlight()
5058 function! s:BlameRehighlight() abort
5059 for [hash, cterm] in items(s:hash_colors)
5060 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
5061 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
5063 exe 'hi link FugitiveblameHash'.hash.' Identifier'
5068 function! s:BlameFileType() abort
5070 setlocal foldmethod=manual
5072 let &l:keywordprg = s:Keywordprg()
5074 let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
5075 if exists('+concealcursor')
5076 setlocal concealcursor=nc conceallevel=2
5077 let b:undo_ftplugin .= ' concealcursor< conceallevel<'
5082 call s:Map('n', '<F1>', ':help fugitive-:Gblame<CR>', '<silent>')
5083 call s:Map('n', 'g?', ':help fugitive-:Gblame<CR>', '<silent>')
5084 if mapcheck('q', 'n') =~# '^$\|bdelete'
5085 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>')
5087 call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>')
5088 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5089 call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5090 call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>')
5091 call s:Map('n', 'P', ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>')
5092 call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>')
5093 call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>')
5094 call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>')
5095 call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>')
5096 call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>')
5099 augroup fugitive_blame
5101 autocmd FileType fugitiveblame call s:BlameFileType()
5102 autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
5103 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
5106 call s:command('-buffer -bang -range=-1 -nargs=? -complete=customlist,s:BlameComplete Gblame', 'blame')
5110 call s:command("-bar -bang -range=-1 -nargs=* -complete=customlist,fugitive#CompleteObject Gbrowse", "Browse")
5112 let s:redirects = {}
5114 function! s:BrowseCommand(line1, line2, range, count, bang, mods, reg, arg, args) abort
5118 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
5121 return 'echoerr ' . string('fugitive: ''-'' no longer required to get persistent URL if range given')
5123 return 'echoerr ' . string('fugitive: use :0Gbrowse instead of :Gbrowse -')
5126 let remote = matchstr(join(a:args, ' '),'@\zs\%('.validremote.'\)$')
5127 let rev = substitute(join(a:args, ' '),'@\%('.validremote.'\)$','','')
5133 let rev = s:DirRev(@%)[1]
5136 let expanded = s:Relative()
5138 let expanded = s:Expand(rev)
5140 let cdir = FugitiveVimPath(fugitive#CommonDir(s:Dir()))
5141 for subdir in ['tags/', 'heads/', 'remotes/']
5142 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . subdir . expanded)
5143 let expanded = '.git/refs/' . subdir . expanded
5146 let full = s:Generate(expanded)
5148 if full =~? '^fugitive:'
5149 let [pathdir, commit, path] = s:DirCommitFile(full)
5150 if commit =~# '^:\=\d$'
5154 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
5155 let branch = matchstr(expanded, '^[^:]*')
5159 let path = path[1:-1]
5160 elseif empty(s:Tree(dir))
5161 let path = '.git/' . full[strlen(dir)+1:-1]
5164 let path = fugitive#Path(full, '/')[1:-1]
5165 if path =~# '^\.git/'
5167 elseif isdirectory(full) || empty(path)
5173 if type ==# 'tree' && !empty(path)
5174 let path = s:sub(path, '/\=$', '/')
5176 if path =~# '^\.git/.*HEAD$' && filereadable(dir . '/' . path[5:-1])
5177 let body = readfile(dir . '/' . path[5:-1])[0]
5178 if body =~# '^\x\{40,\}$'
5182 elseif body =~# '^ref: refs/'
5183 let path = '.git/' . matchstr(body,'ref: \zs.*')
5188 if path =~# '^\.git/refs/remotes/.'
5190 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
5191 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5193 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
5194 let path = '.git/refs/heads/'.merge
5196 elseif path =~# '^\.git/refs/heads/.'
5197 let branch = path[16:-1]
5198 elseif !exists('branch')
5199 let branch = FugitiveHead()
5202 let r = fugitive#Config('branch.'.branch.'.remote')
5203 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
5204 if r ==# '.' && !empty(m)
5205 let r2 = fugitive#Config('branch.'.m.'.remote')
5208 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
5214 if r ==# '.' || r ==# remote
5216 if path =~# '^\.git/refs/heads/.'
5217 let path = '.git/refs/heads/'.merge
5222 let line1 = a:count > 0 ? a:line1 : 0
5223 let line2 = a:count > 0 ? a:count : 0
5224 if empty(commit) && path !~# '^\.git/'
5225 if a:count < 0 && !empty(merge)
5230 let owner = s:Owner(@%)
5231 let [commit, exec_error] = s:ChompError(['merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--'])
5235 if a:count > 0 && empty(a:args) && commit =~# '^\x\{40,\}$'
5236 let blame_list = tempname()
5237 call writefile([commit, ''], blame_list, 'b')
5238 let blame_in = tempname()
5239 silent exe '%write' blame_in
5240 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])
5242 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
5243 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
5244 let line1 = +matchstr(blame[0], blame_regex)
5245 let line2 = +matchstr(blame[-1], blame_regex)
5247 call s:throw("Can't browse to uncommitted change")
5254 let commit = readfile(fugitive#Find('.git/HEAD', dir), '', 1)[0]
5257 while commit =~# '^ref: ' && i < 10
5258 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
5266 let raw = fugitive#RemoteUrl(remote)
5271 if raw =~# '^https\=://' && s:executable('curl')
5272 if !has_key(s:redirects, raw)
5273 let s:redirects[raw] = matchstr(system('curl -I ' .
5274 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
5275 \ 'Location: \zs\S\+\ze/info/refs?')
5277 if len(s:redirects[raw])
5278 let raw = s:redirects[raw]
5284 \ 'repo': fugitive#repo(dir),
5286 \ 'revision': 'No longer provided',
5294 for Handler in get(g:, 'fugitive_browse_handlers', [])
5295 let url = call(Handler, [copy(opts)])
5302 call s:throw("No Gbrowse handler installed for '".raw."'")
5305 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
5310 return 'echomsg '.string(url)
5311 elseif exists(':Browse') == 2
5312 return 'echomsg '.string(url).'|Browse '.url
5314 if !exists('g:loaded_netrw')
5315 runtime! autoload/netrw.vim
5317 if exists('*netrw#BrowseX')
5318 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
5320 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
5324 return 'echoerr ' . string(v:exception)
5328 " Section: Go to file
5330 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
5331 function! fugitive#MapCfile(...) abort
5332 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
5333 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
5334 if !exists('g:fugitive_no_maps')
5335 call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
5336 call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5337 call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
5338 call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
5339 call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
5343 function! s:ContainingCommit() abort
5344 let commit = s:Owner(@%)
5345 return empty(commit) ? 'HEAD' : commit
5348 function! s:SquashArgument(...) abort
5349 if &filetype == 'fugitive'
5350 let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze ')
5351 elseif has_key(s:temp_files, s:cpath(expand('%:p')))
5352 let commit = matchstr(getline('.'), '\<\x\{4,\}\>')
5354 let commit = s:Owner(@%)
5356 return len(commit) && a:0 ? printf(a:1, commit) : commit
5359 function! s:RebaseArgument() abort
5360 return s:SquashArgument(' %s^')
5363 function! s:NavigateUp(count) abort
5364 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
5368 let rev = matchstr(rev, '.*\ze/.\+', '')
5369 elseif rev =~# '.:.'
5370 let rev = matchstr(rev, '^.[^:]*:')
5383 function! s:MapMotion(lhs, rhs) abort
5384 call s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5385 call s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>")
5386 call s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")
5389 function! fugitive#MapJumps(...) abort
5391 if get(b:, 'fugitive_type', '') ==# 'blob'
5392 let blame_map = 'Gblame<C-R>=v:count ? " --reverse" : ""<CR><CR>'
5393 call s:Map('n', '<2-LeftMouse>', ':<C-U>0,1' . blame_map, '<silent>')
5394 call s:Map('n', '<CR>', ':<C-U>0,1' . blame_map, '<silent>')
5395 call s:Map('n', 'o', ':<C-U>0,2' . blame_map, '<silent>')
5396 call s:Map('n', 'p', ':<C-U>0,3' . blame_map, '<silent>')
5397 call s:Map('n', 'gO', ':<C-U>0,4' . blame_map, '<silent>')
5398 call s:Map('n', 'O', ':<C-U>0,5' . blame_map, '<silent>')
5400 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>')
5401 call s:Map('n', 'dd', ":<C-U>call <SID>DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
5402 call s:Map('n', 'dh', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5403 call s:Map('n', 'ds', ":<C-U>call <SID>DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
5404 call s:Map('n', 'dv', ":<C-U>call <SID>DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
5405 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
5408 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5409 call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
5410 call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
5411 call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
5412 call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
5413 call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
5415 if !exists('g:fugitive_no_maps')
5416 if exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
5417 nnoremap <buffer> <silent> <C-P> :<C-U>execute line('.') == 1 ? 'CtrlP ' . fnameescape(<SID>Tree()) : <SID>PreviousItem(v:count1)<CR>
5419 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>PreviousItem(v:count1)<CR>
5421 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>NextItem(v:count1)<CR>
5423 call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
5424 call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
5425 call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
5426 call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
5427 call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
5428 call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
5429 call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
5430 call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
5431 call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
5432 call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
5433 call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
5434 call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
5435 call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
5436 call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
5437 call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
5438 call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
5440 call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent>')
5441 call s:Map('n', 'dq', ":<C-U>call <SID>DiffClose()<CR>", '<silent>')
5442 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>')
5443 call s:Map('n', 'P', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5444 call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
5445 call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5446 call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent>')
5447 call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5448 call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
5449 call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5450 call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
5452 nnoremap <buffer> c<Space> :Git commit<Space>
5453 nnoremap <buffer> c<CR> :Git commit<CR>
5454 nnoremap <buffer> cv<Space> :Git commit -v<Space>
5455 nnoremap <buffer> cv<CR> :Git commit -v<CR>
5456 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
5457 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
5458 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
5459 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
5460 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
5461 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
5462 nnoremap <buffer> <silent> cRa :<C-U>Gcommit --reset-author --amend<CR>
5463 nnoremap <buffer> <silent> cRe :<C-U>Gcommit --reset-author --amend --no-edit<CR>
5464 nnoremap <buffer> <silent> cRw :<C-U>Gcommit --reset-author --amend --only<CR>
5465 nnoremap <buffer> cf :<C-U>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5466 nnoremap <buffer> cF :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --fixup=<C-R>=<SID>SquashArgument()<CR>
5467 nnoremap <buffer> cs :<C-U>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5468 nnoremap <buffer> cS :<C-U><Bar>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Gcommit --squash=<C-R>=<SID>SquashArgument()<CR>
5469 nnoremap <buffer> cA :<C-U>Gcommit --edit --squash=<C-R>=<SID>SquashArgument()<CR>
5470 nnoremap <buffer> <silent> c? :<C-U>help fugitive_c<CR>
5472 nnoremap <buffer> cr<Space> :Git revert<Space>
5473 nnoremap <buffer> cr<CR> :Git revert<CR>
5474 nnoremap <buffer> <silent> crc :<C-U>Grevert <C-R>=<SID>SquashArgument()<CR><CR>
5475 nnoremap <buffer> <silent> crn :<C-U>Grevert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>
5476 nnoremap <buffer> <silent> cr? :help fugitive_cr<CR>
5478 nnoremap <buffer> cm<Space> :Git merge<Space>
5479 nnoremap <buffer> cm<CR> :Git merge<CR>
5480 nnoremap <buffer> <silent> cm? :help fugitive_cm<CR>
5482 nnoremap <buffer> cz<Space> :Git stash<Space>
5483 nnoremap <buffer> cz<CR> :Git stash<CR>
5484 nnoremap <buffer> <silent> cza :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5485 nnoremap <buffer> <silent> czA :<C-U>exe <SID>EchoExec(['stash', 'apply', '--quiet', 'stash@{' . v:count . '}'])<CR>
5486 nnoremap <buffer> <silent> czp :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', '--index', 'stash@{' . v:count . '}'])<CR>
5487 nnoremap <buffer> <silent> czP :<C-U>exe <SID>EchoExec(['stash', 'pop', '--quiet', 'stash@{' . v:count . '}'])<CR>
5488 nnoremap <buffer> <silent> czv :<C-U>exe 'Gedit' fugitive#RevParse('stash@{' . v:count . '}')<CR>
5489 nnoremap <buffer> <silent> czw :<C-U>exe <SID>EchoExec(['stash', '--keep-index'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5490 nnoremap <buffer> <silent> czz :<C-U>exe <SID>EchoExec(['stash'] + (v:count > 1 ? ['--all'] : v:count ? ['--include-untracked'] : []))<CR>
5491 nnoremap <buffer> <silent> cz? :<C-U>help fugitive_cz<CR>
5493 nnoremap <buffer> co<Space> :Git checkout<Space>
5494 nnoremap <buffer> co<CR> :Git checkout<CR>
5495 nnoremap <buffer> coo :exe <SID>EchoExec(['checkout'] + split(<SID>SquashArgument()) + ['--'])<CR>
5496 nnoremap <buffer> co? :<C-U>help fugitive_co<CR>
5498 nnoremap <buffer> cb<Space> :Git branch<Space>
5499 nnoremap <buffer> cb<CR> :Git branch<CR>
5500 nnoremap <buffer> cb? :<C-U>help fugitive_cb<CR>
5502 nnoremap <buffer> r<Space> :Git rebase<Space>
5503 nnoremap <buffer> r<CR> :Git rebase<CR>
5504 nnoremap <buffer> <silent> ri :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>
5505 nnoremap <buffer> <silent> rf :<C-U>Grebase --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>
5506 nnoremap <buffer> <silent> ru :<C-U>Grebase --interactive @{upstream}<CR>
5507 nnoremap <buffer> <silent> rp :<C-U>Grebase --interactive @{push}<CR>
5508 nnoremap <buffer> <silent> rw :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>
5509 nnoremap <buffer> <silent> rm :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>
5510 nnoremap <buffer> <silent> rd :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5511 nnoremap <buffer> <silent> rk :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5512 nnoremap <buffer> <silent> rx :<C-U>Grebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>
5513 nnoremap <buffer> <silent> rr :<C-U>Grebase --continue<CR>
5514 nnoremap <buffer> <silent> rs :<C-U>Grebase --skip<CR>
5515 nnoremap <buffer> <silent> re :<C-U>Grebase --edit-todo<CR>
5516 nnoremap <buffer> <silent> ra :<C-U>Grebase --abort<CR>
5517 nnoremap <buffer> <silent> r? :<C-U>help fugitive_r<CR>
5519 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5520 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
5521 call s:Map('n', 'g?', ":<C-U>help fugitive-mappings<CR>", '<silent>')
5522 call s:Map('n', '<F1>', ":<C-U>help fugitive-mappings<CR>", '<silent>')
5526 function! s:StatusCfile(...) abort
5528 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5529 let info = s:StageInfo()
5530 let line = getline('.')
5531 if len(info.sigil) && len(info.section) && len(info.paths)
5532 if info.section ==# 'Unstaged' && info.sigil !=# '-'
5533 return [lead . info.relative[0], info.offset, 'normal!zv']
5534 elseif info.section ==# 'Staged' && info.sigil ==# '-'
5535 return ['@:' . info.relative[0], info.offset, 'normal!zv']
5537 return [':0:' . info.relative[0], info.offset, 'normal!zv']
5539 elseif len(info.paths)
5540 return [lead . info.relative[0]]
5541 elseif len(info.commit)
5542 return [info.commit]
5543 elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
5544 return [matchstr(line, ' \zs.*')]
5550 function! fugitive#StatusCfile() abort
5551 let file = s:Generate(s:StatusCfile()[0])
5552 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5555 function! s:MessageCfile(...) abort
5557 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
5558 if getline('.') =~# '^.\=\trenamed:.* -> '
5559 return lead . matchstr(getline('.'),' -> \zs.*')
5560 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
5561 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
5562 elseif getline('.') =~# '^.\=\t.'
5563 return lead . matchstr(getline('.'),'\t\zs.*')
5564 elseif getline('.') =~# ': needs merge$'
5565 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
5566 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
5568 elseif getline('.') =~# '^\%(. \)\=On branch '
5569 return 'refs/heads/'.getline('.')[12:]
5570 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
5571 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
5577 function! fugitive#MessageCfile() abort
5578 let file = s:Generate(s:MessageCfile())
5579 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
5582 function! s:cfile() abort
5584 let myhash = s:DirRev(@%)[1]
5587 let myhash = fugitive#RevParse(myhash)
5592 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
5593 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
5596 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
5598 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
5599 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
5601 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
5602 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
5604 return [treebase . s:sub(getline('.'),'/$','')]
5611 if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
5612 let ref = matchstr(getline('.'),'\x\{40,\}')
5613 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
5617 if getline('.') =~# '^ref: '
5618 let ref = strpart(getline('.'),5)
5620 elseif getline('.') =~# '^commit \x\{40,\}\>'
5621 let ref = matchstr(getline('.'),'\x\{40,\}')
5624 elseif getline('.') =~# '^parent \x\{40,\}\>'
5625 let ref = matchstr(getline('.'),'\x\{40,\}')
5626 let line = line('.')
5628 while getline(line) =~# '^parent '
5634 elseif getline('.') =~# '^tree \x\{40,\}$'
5635 let ref = matchstr(getline('.'),'\x\{40,\}')
5636 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
5637 let ref = myhash.':'
5641 elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
5642 let ref = matchstr(getline('.'),'\x\{40,\}')
5643 let type = matchstr(getline(line('.')+1),'type \zs.*')
5645 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
5646 let ref = s:DirRev(@%)[1]
5648 elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
5649 let ref = matchstr(getline('.'),'\x\{40,\}')
5650 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
5652 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
5653 let ref = getline('.')[4:]
5655 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
5656 let type = getline('.')[0]
5657 let lnum = line('.') - 1
5659 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5660 if getline(lnum) =~# '^[ '.type.']'
5665 let offset += matchstr(getline(lnum), type.'\zs\d\+')
5666 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
5667 let dcmds = [offset, 'normal!zv']
5669 elseif getline('.') =~# '^rename from '
5670 let ref = 'a/'.getline('.')[12:]
5671 elseif getline('.') =~# '^rename to '
5672 let ref = 'b/'.getline('.')[10:]
5674 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
5675 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
5676 let offset = matchstr(getline('.'), '+\zs\d\+')
5678 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5679 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5680 let dcmd = 'Gdiffsplit! +'.offset
5682 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5683 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5684 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5685 let dcmd = 'Gdiffsplit!'
5687 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
5688 let line = getline(line('.')-1)
5689 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
5690 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
5691 let dcmd = 'Gdiffsplit!'
5693 elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
5694 let ref = getline('.')
5696 elseif expand('<cword>') =~# '^\x\{7,\}\>'
5697 return [expand('<cword>')]
5712 let prefixes.a = myhash.'^:'
5713 let prefixes.b = myhash.':'
5715 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5717 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
5720 if ref ==# '/dev/null'
5722 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
5726 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
5728 return [ref] + dcmds
5736 function! s:GF(mode) abort
5738 let results = &filetype ==# 'fugitive' ? s:StatusCfile() : &filetype ==# 'gitcommit' ? [s:MessageCfile()] : s:cfile()
5740 return 'echoerr ' . string(v:exception)
5743 return 'G' . a:mode .
5744 \ ' +' . escape(results[1], ' ') . ' ' .
5745 \ s:fnameescape(results[0]) . join(map(results[2:-1], '"|" . v:val'), '')
5746 elseif len(results) && len(results[0])
5747 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
5753 function! fugitive#Cfile() abort
5755 let results = s:cfile()
5757 let cfile = expand('<cfile>')
5758 if &includeexpr =~# '\<v:fname\>'
5759 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
5762 elseif len(results) > 1
5763 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
5765 return pre . s:fnameescape(s:Generate(results[0]))
5768 " Section: Statusline
5770 function! fugitive#Statusline(...) abort
5775 let commit = s:DirCommitFile(@%)[1]
5777 let status .= ':' . commit[0:6]
5779 let status .= '('.FugitiveHead(7).')'
5780 return '[Git'.status.']'
5783 function! fugitive#statusline(...) abort
5784 return fugitive#Statusline()
5787 function! fugitive#head(...) abort
5792 return fugitive#Head(a:0 ? a:1 : 0)
5797 function! fugitive#Foldtext() abort
5798 if &foldmethod !=# 'syntax'
5802 let line_foldstart = getline(v:foldstart)
5803 if line_foldstart =~# '^diff '
5804 let [add, remove] = [-1, -1]
5806 for lnum in range(v:foldstart, v:foldend)
5807 let line = getline(lnum)
5808 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
5809 let filename = line[6:-1]
5813 elseif line =~# '^-'
5815 elseif line =~# '^Binary '
5820 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
5823 let filename = line_foldstart[5:-1]
5826 return 'Binary: '.filename
5828 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
5830 elseif line_foldstart =~# '^# .*:$'
5831 let lines = getline(v:foldstart, v:foldend)
5832 call filter(lines, 'v:val =~# "^#\t"')
5833 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
5834 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
5835 return line_foldstart.' '.join(lines, ', ')
5840 function! fugitive#foldtext() abort
5841 return fugitive#Foldtext()
5844 augroup fugitive_folding
5846 autocmd User Fugitive
5847 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
5848 \ set foldtext=fugitive#Foldtext() |
5852 " Section: Initialization
5854 function! fugitive#Init() abort
5855 if exists('#User#FugitiveBoot')
5857 let [save_mls, &modelines] = [&mls, 0]
5858 doautocmd User FugitiveBoot
5863 if !exists('g:fugitive_no_maps')
5864 call s:Map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
5865 call s:Map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
5867 if expand('%:p') =~# ':[\/][\/]'
5868 let &l:path = s:sub(&path, '^\.%(,|$)', '')
5871 if stridx(&tags, escape(dir, ', ')) == -1
5872 let actualdir = fugitive#Find('.git/', dir)
5873 if filereadable(actualdir . 'tags')
5874 let &l:tags = escape(actualdir . 'tags', ', ').','.&tags
5876 if &filetype !=# '' && filereadable(actualdir . &filetype . '.tags')
5877 let &l:tags = escape(actualdir . &filetype . '.tags', ', ').','.&tags
5881 let [save_mls, &modelines] = [&mls, 0]
5882 call s:define_commands()
5883 doautocmd User Fugitive
5889 function! fugitive#is_git_dir(path) abort
5890 return FugitiveIsGitDir(a:path)
5893 function! fugitive#extract_git_dir(path) abort
5894 return FugitiveExtractGitDir(a:path)
5897 function! fugitive#detect(path) abort
5898 return FugitiveDetect(a:path)