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'
15 function! s:function(name) abort
16 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
19 function! s:sub(str,pat,rep) abort
20 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
23 function! s:gsub(str,pat,rep) abort
24 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
27 function! s:Uniq(list) abort
31 let str = string(a:list[i])
33 call remove(a:list, i)
42 function! s:winshell() abort
43 return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
46 function! s:shellesc(arg) abort
47 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
50 return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
52 return shellescape(a:arg)
56 function! s:fnameescape(file) abort
57 if exists('*fnameescape')
58 return fnameescape(a:file)
60 return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
64 function! s:tempname() abort
65 let temp = resolve(tempname())
67 let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
72 function! s:throw(string) abort
73 let v:errmsg = 'fugitive: '.a:string
77 function! s:warn(str) abort
81 let v:warningmsg = a:str
84 function! s:Slash(path) abort
86 return tr(a:path, '\', '/')
92 function! s:PlatformSlash(path) abort
93 if exists('+shellslash') && !&shellslash
94 return tr(a:path, '/', '\')
100 function! s:cpath(path, ...) abort
101 if exists('+fileignorecase') && &fileignorecase
102 let path = s:PlatformSlash(tolower(a:path))
104 let path = s:PlatformSlash(a:path)
106 return a:0 ? path ==# s:cpath(a:1) : path
109 let s:executables = {}
111 function! s:executable(binary) abort
112 if !has_key(s:executables, a:binary)
113 let s:executables[a:binary] = executable(a:binary)
115 return s:executables[a:binary]
118 function! s:UserCommand() abort
119 return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
122 function! s:System(cmd) abort
125 catch /^Vim\%((\a\+)\)\=:E484:/
126 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
127 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
128 call map(opts, 'v:val."=".eval("&".v:val)')
129 call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
133 function! s:Prepare(dir, ...) abort
134 if type(a:dir) == type([])
135 let args = ['--git-dir=' . (a:0 ? a:1 : get(b:, 'git_dir', ''))] + a:dir
137 let args = ['--git-dir=' . a:dir] + (a:000)
139 return g:fugitive_git_executable . ' ' . join(map(args, 's:shellesc(v:val)'))
142 let s:git_versions = {}
143 function! fugitive#GitVersion(...) abort
144 if !has_key(s:git_versions, g:fugitive_git_executable)
145 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
147 return s:git_versions[g:fugitive_git_executable]
150 let s:commondirs = {}
151 function! fugitive#CommonDir(dir) abort
155 if !has_key(s:commondirs, a:dir)
156 if getfsize(a:dir . '/HEAD') < 10
157 let s:commondirs[a:dir] = ''
158 elseif filereadable(a:dir . '/commondir')
159 let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
160 if dir =~# '^/\|^\a:/'
161 let s:commondirs[a:dir] = dir
163 let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
166 let s:commondirs[a:dir] = a:dir
169 return s:commondirs[a:dir]
172 function! s:Tree(...) abort
173 return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
176 function! s:TreeChomp(...) abort
177 let args = copy(type(a:1) == type([]) ? a:1 : a:000)
178 let dir = a:0 > 1 && type(a:1) == type([]) ? a:2 : b:git_dir
179 let tree = s:Tree(dir)
182 let args = ['--git-dir=' . dir] + args
183 elseif s:cpath(tree) !=# s:cpath(getcwd())
184 if fugitive#GitVersion() =~# '^[01]\.'
185 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
187 let args = ['-C', tree] + args
190 return s:sub(s:System(pre . g:fugitive_git_executable . ' ' .
191 \ join(map(args, 's:shellesc(v:val)'))), '\n$', '')
194 function! fugitive#Prepare(cmd, ...) abort
195 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
196 let tree = s:Tree(dir)
197 let args = type(a:cmd) == type([]) ? join(map(copy(a:cmd), 's:shellesc(v:val)')) : a:cmd
200 let args = s:shellesc('--git-dir=' . dir) . ' ' . args
201 elseif fugitive#GitVersion() =~# '^[01]\.'
202 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
204 let args = '-C ' . s:shellesc(tree) . ' ' . args
206 return pre . g:fugitive_git_executable . ' ' . args
209 function! fugitive#Head(...) abort
210 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
211 if empty(dir) || !filereadable(dir . '/HEAD')
214 let head = readfile(dir . '/HEAD')[0]
216 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
217 elseif head =~# '^\x\{40\}$'
218 let len = a:0 ? a:1 : 0
219 return len < 0 ? head : len ? head[0:len-1] : ''
225 function! fugitive#RevParse(rev, ...) abort
226 let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev))[0:-2]
227 if !v:shell_error && hash =~# '^\x\{40\}$'
230 call s:throw('rev-parse '.a:rev.': '.hash)
233 function! fugitive#Config(name, ...) abort
234 let cmd = s:Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), 'config', '--get', a:name)
235 let out = matchstr(system(cmd), "[^\r\n]*")
236 return v:shell_error ? '' : out
239 function! s:Remote(dir) abort
240 let head = FugitiveHead(0, a:dir)
241 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
243 while remote ==# '.' && i > 0
244 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
245 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
248 return remote =~# '^\.\=$' ? 'origin' : remote
251 function! fugitive#RemoteUrl(...) abort
252 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
253 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
254 if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
255 return fugitive#Config('remote.' . remote . '.url')
257 let cmd = s:Prepare(dir, 'remote', 'get-url', remote)
258 let out = substitute(system(cmd), "\n$", '', '')
259 return v:shell_error ? '' : out
262 function! s:map(mode, lhs, rhs, ...) abort
263 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
266 let keys = get(g:, a:mode.'remap', {})
267 if type(keys) == type([])
271 if has_key(keys, head)
272 let head = keys[head]
278 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
279 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
281 if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
282 exe a:mode.'map <buffer>' flags head.tail a:rhs
284 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
285 \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
290 function! s:add_methods(namespace, method_names) abort
291 for name in a:method_names
292 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
297 function! s:command(definition) abort
298 let s:commands += [a:definition]
301 function! s:define_commands() abort
302 for command in s:commands
303 exe 'command! -buffer '.command
307 let s:abstract_prototype = {}
309 " Section: Repository
311 let s:repo_prototype = {}
314 function! fugitive#repo(...) abort
315 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
317 if has_key(s:repos, dir)
318 let repo = get(s:repos, dir)
320 let repo = {'git_dir': dir}
321 let s:repos[dir] = repo
323 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
325 call s:throw('not a git repository: '.expand('%:p'))
328 function! s:repo_dir(...) dict abort
329 return join([self.git_dir]+a:000,'/')
332 function! s:repo_tree(...) dict abort
333 let dir = s:Tree(self.git_dir)
335 call s:throw('no work tree')
337 return join([dir]+a:000,'/')
341 function! s:repo_bare() dict abort
342 if self.dir() =~# '/\.git$'
345 return s:Tree(self.git_dir) ==# ''
349 function! s:repo_route(object) dict abort
350 return fugitive#Route(a:object, self.git_dir)
353 function! s:repo_translate(rev) dict abort
354 return s:Slash(s:Generate(a:rev, self.git_dir))
357 function! s:repo_head(...) dict abort
358 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
361 call s:add_methods('repo',['dir','tree','bare','route','translate','head'])
363 function! s:repo_git_command(...) dict abort
364 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
365 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
368 function! s:repo_git_chomp(...) dict abort
369 return s:sub(s:System(s:Prepare(a:000, self.git_dir)), '\n$', '')
372 function! s:repo_git_chomp_in_tree(...) dict abort
373 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
376 execute cd s:fnameescape(self.tree())
377 return call(self.git_chomp, a:000, self)
379 execute cd s:fnameescape(dir)
383 function! s:repo_rev_parse(rev) dict abort
384 return fugitive#RevParse(a:rev, self.git_dir)
387 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
389 function! s:repo_superglob(base) dict abort
390 return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
393 call s:add_methods('repo',['superglob'])
395 function! s:repo_config(name) dict abort
396 return fugitive#Config(a:name, self.git_dir)
399 function! s:repo_user() dict abort
400 let username = self.config('user.name')
401 let useremail = self.config('user.email')
402 return username.' <'.useremail.'>'
405 call s:add_methods('repo',['config', 'user'])
409 function! s:DirCommitFile(path) abort
410 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
417 function! s:DirRev(url) abort
418 let [dir, commit, file] = s:DirCommitFile(a:url)
419 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
422 function! s:Owner(path, ...) abort
423 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
427 let [pdir, commit, file] = s:DirCommitFile(a:path)
428 if s:cpath(dir, pdir) && commit =~# '^\x\{40\}$'
431 let path = fnamemodify(a:path, ':p')
432 if s:cpath(dir . '/', path[0 : len(dir)]) && a:path =~# 'HEAD$'
433 return strpart(path, len(dir) + 1)
435 let refs = fugitive#CommonDir(dir) . '/refs'
436 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
437 return strpart(path, len(refs) - 4)
442 function! fugitive#Real(url) abort
446 let [dir, commit, file] = s:DirCommitFile(a:url)
448 let tree = s:Tree(dir)
449 return s:PlatformSlash((len(tree) ? tree : dir) . file)
451 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
452 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
453 let url = {pre}Real(a:url)
455 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
457 return s:PlatformSlash(empty(url) ? a:url : url)
460 function! fugitive#Path(url, ...) abort
461 if !a:0 || empty(a:url)
462 return fugitive#Real(a:url)
464 let url = s:Slash(fnamemodify(a:url, ':p'))
465 if url =~# '/$' && s:Slash(a:url) !~# '/$'
468 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
469 let tree = s:Tree(dir)
470 let [argdir, commit, file] = s:DirCommitFile(a:url)
471 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
473 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
474 let file = '/.git'.url[strlen(dir) : -1]
475 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
476 let file = url[len(tree) : -1]
477 elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
480 if empty(file) && a:1 =~# '^$\|^[.:]/$'
481 return s:Slash(fugitive#Real(a:url))
483 return substitute(file, '^/', a:1, '')
486 function! fugitive#Route(object, ...) abort
487 if a:object =~# '^[~$]'
488 let prefix = matchstr(a:object, '^[~$]\i*')
489 let owner = expand(prefix)
490 return s:PlatformSlash((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
491 elseif s:Slash(a:object) =~# '^\%(\a\a\+:\)\=\%(a:\)\=/'
492 return s:PlatformSlash(a:object)
494 let rev = substitute(a:object, '[:/]\zs\.\%(/\+\|$\)', '', 'g')
495 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
496 let tree = s:Tree(dir)
497 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
498 if rev =~# '^\%(\./\)\=\.git$' && empty(tree)
500 elseif rev =~# '^\%(\./\)\=\.git/'
501 let f = substitute(rev, '\C^\%(\./\)\=\.git', '', '')
502 let cdir = fugitive#CommonDir(dir)
503 if cdir !=# dir && (f =~# '^/\%(config\|info\|hooks\|objects\|refs\|worktrees\)' || !filereadable(f) && filereadable(cdir . f))
508 elseif rev =~# '^$\|^:/$'
510 elseif rev =~# '^\.\%(/\|$\)'
511 let f = base . rev[1:-1]
512 elseif rev =~# '^:[0-3]:/\@!'
513 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
515 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
516 let f = fnamemodify($GIT_INDEX_FILE, ':p')
518 let f = dir . '/index'
520 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
521 let f = base . '/' . matchstr(rev, ')\zs.*')
522 elseif rev =~# '^:/\@!'
523 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
525 if rev =~# 'HEAD$\|^refs/' && rev !~# ':'
526 let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
527 if filereadable(cdir . '/' . rev)
528 let f = simplify(cdir . '/' . rev)
532 let commit = substitute(matchstr(rev,'^[^:]\+\|^:.*'), '^@\%($|[^~]\)\@=', 'HEAD', '')
533 let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
534 if commit !~# '^[0-9a-f]\{40\}$'
535 let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit))[0:-2]
536 let commit = v:shell_error ? '' : commit
539 let f = 'fugitive://' . dir . '//' . commit . file
541 let f = base . '/' . rev
545 return s:PlatformSlash(f)
548 function! s:Generate(rev, ...) abort
549 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
550 let tree = s:Tree(dir)
552 if a:rev =~# '^/' && len(tree) && getftime(tree . a:rev) >= 0 && getftime(a:rev) < 0 || a:rev =~# '^/\.git\%(/\|$\)'
553 let object = '.' . object
555 return fugitive#Route(object, dir)
558 function! s:RemoveDot(path, ...) abort
562 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
563 let cdir = fugitive#CommonDir(dir)
564 if len(filter(['', '/tags', '/heads', '/remotes'], 'getftime(cdir . "/refs" . v:val . a:path[1:-1]) >= 0')) ||
565 \ a:path =~# 'HEAD$' && filereadable(dir . a:path[1:-1]) ||
566 \ a:path =~# '^\./refs/' && filereadable(cdir . a:path[1:-1])
572 function! fugitive#Object(...) abort
573 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
574 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
575 if s:cpath(dir) !=# s:cpath(fdir)
578 let tree = s:Tree(dir)
579 if empty(rev) && empty(tree)
581 let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
582 let cdir = fugitive#CommonDir(dir)
583 if rev =~# '^\./\.git/refs/\%(tags\|heads\|remotes\)/.\|^\./\.git/\w*HEAD$'
585 elseif s:cpath(cdir . '/refs/', rev[0 : len(cdir)])
586 let rev = strpart(rev, len(cdir)+1)
587 elseif rev =~# '^\./.git\%(/\|$\)'
588 return fnamemodify(a:0 ? a:1 : @%, ':p')
591 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
594 return tree . rev[1:-1]
598 function! s:Expand(rev) abort
599 if a:rev =~# '^:[0-3]$'
600 let file = a:rev . s:Relative(':')
601 elseif a:rev =~# '^-'
602 let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
603 elseif a:rev =~# '^@{'
604 let file = 'HEAD' . a:rev. s:Relative(':')
605 elseif a:rev =~# '^[~^]/\@!'
606 let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
607 let file = commit . a:rev . s:Relative(':')
611 return s:sub(substitute(file,
612 \ '\([%#]\)$\|\\\([[:punct:]]\)','\=len(submatch(2)) ? submatch(2) : fugitive#Path(expand(submatch(1)), "./", dir)','g'),
616 function! s:ShellExpand(cmd) abort
617 return substitute(a:cmd, '\\\@<![%#]:\@!', '\=s:RemoveDot(fugitive#Path(expand(submatch(0)), "./"))', 'g')
622 function! s:TreeInfo(dir, commit) abort
623 let git = s:Prepare(a:dir)
624 if a:commit =~# '^:\=[0-3]$'
625 let index = get(s:indexes, a:dir, [])
626 let newftime = getftime(a:dir . '/index')
627 if get(index, 0, -1) < newftime
628 let out = system(git . ' ls-files --stage')
629 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
633 for line in split(out, "\n")
634 let [info, filename] = split(line, "\t")
635 let [mode, sha, stage] = split(info, '\s\+')
636 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
637 while filename =~# '/'
638 let filename = substitute(filename, '/[^/]*$', '', '')
639 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
643 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
644 elseif a:commit =~# '^\x\{40\}$'
645 if !has_key(s:trees, a:dir)
646 let s:trees[a:dir] = {}
648 if !has_key(s:trees[a:dir], a:commit)
649 let ftime = +system(git . ' log -1 --pretty=format:%ct ' . a:commit)
651 let s:trees[a:dir][a:commit] = [{}, -1]
652 return s:trees[a:dir][a:commit]
654 let s:trees[a:dir][a:commit] = [{}, +ftime]
655 let out = system(git . ' ls-tree -rtl --full-name ' . a:commit)
657 return s:trees[a:dir][a:commit]
659 for line in split(out, "\n")
660 let [info, filename] = split(line, "\t")
661 let [mode, type, sha, size] = split(info, '\s\+')
662 let s:trees[a:dir][a:commit][0][filename] = [ftime, mode, type, sha, +size, filename]
665 return s:trees[a:dir][a:commit]
670 function! s:PathInfo(url) abort
671 let [dir, commit, file] = s:DirCommitFile(a:url)
672 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
673 return [-1, '000000', '', '', -1]
675 let path = substitute(file[1:-1], '/*$', '', '')
676 let [tree, ftime] = s:TreeInfo(dir, commit)
677 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
678 if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
679 return [-1, '000000', '', '', -1]
685 function! fugitive#simplify(url) abort
686 let [dir, commit, file] = s:DirCommitFile(a:url)
690 if file =~# '/\.\.\%(/\|$\)'
691 let tree = s:Tree(dir)
693 let path = simplify(tree . file)
694 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
695 return s:PlatformSlash(path)
699 return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
702 function! fugitive#resolve(url) abort
703 let url = fugitive#simplify(a:url)
704 if url =~? '^fugitive:'
711 function! fugitive#getftime(url) abort
712 return s:PathInfo(a:url)[0]
715 function! fugitive#getfsize(url) abort
716 let entry = s:PathInfo(a:url)
717 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
718 let dir = s:DirCommitFile(a:url)[0]
719 let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
720 let entry[4] = v:shell_error ? -1 : size
725 function! fugitive#getftype(url) abort
726 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
729 function! fugitive#filereadable(url) abort
730 return s:PathInfo(a:url)[2] ==# 'blob'
733 function! fugitive#filewritable(url) abort
734 let [dir, commit, file] = s:DirCommitFile(a:url)
735 if commit !~# '^\d$' || !filewritable(dir . '/index')
738 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
741 function! fugitive#isdirectory(url) abort
742 return s:PathInfo(a:url)[2] ==# 'tree'
745 function! fugitive#getfperm(url) abort
746 let [dir, commit, file] = s:DirCommitFile(a:url)
747 let perm = getfperm(dir)
748 let fperm = s:PathInfo(a:url)[1]
749 if fperm ==# '040000'
753 let perm = tr(perm, 'x', '-')
756 let perm = tr(perm, 'rw', '--')
759 let perm = tr(perm, 'w', '-')
761 return perm ==# '---------' ? '' : perm
764 function! fugitive#setfperm(url, perm) abort
765 let [dir, commit, file] = s:DirCommitFile(a:url)
766 let entry = s:PathInfo(a:url)
767 let perm = fugitive#getfperm(a:url)
768 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
769 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
772 call system(s:Prepare(dir, 'update-index', '--index-info'),
773 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
774 return v:shell_error ? -1 : 0
777 function! s:TempCmd(out, cmd) abort
780 let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
781 let redir = ' > ' . a:out
783 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
784 return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
785 elseif &shell =~# 'fish'
786 return s:System(' begin;' . prefix . cmd . redir . ';end ')
788 return s:System(' (' . prefix . cmd . redir . ') ')
793 if !exists('s:blobdirs')
796 function! s:BlobTemp(url) abort
797 let [dir, commit, file] = s:DirCommitFile(a:url)
801 if !has_key(s:blobdirs, dir)
802 let s:blobdirs[dir] = s:tempname()
804 let tempfile = s:PlatformSlash(s:blobdirs[dir] . '/' . commit . file)
805 let tempparent = fnamemodify(tempfile, ':h')
806 if !isdirectory(tempparent)
807 call mkdir(tempparent, 'p')
809 if commit =~# '^\d$' || !filereadable(tempfile)
810 let rev = s:DirRev(a:url)[1]
811 let command = s:Prepare(dir, 'cat-file', 'blob', rev)
812 call s:TempCmd(tempfile, command)
814 call delete(tempfile)
821 function! fugitive#readfile(url, ...) abort
822 let entry = s:PathInfo(a:url)
823 if entry[2] !=# 'blob'
826 let temp = s:BlobTemp(a:url)
830 return call('readfile', [temp] + a:000)
833 function! fugitive#writefile(lines, url, ...) abort
834 let url = type(a:url) ==# type('') ? a:url : ''
835 let [dir, commit, file] = s:DirCommitFile(url)
836 let entry = s:PathInfo(url)
837 if commit =~# '^\d$' && entry[2] !=# 'tree'
838 let temp = s:tempname()
839 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
840 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
842 call call('writefile', [a:lines, temp] + a:000)
843 let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
844 let mode = len(entry[1]) ? entry[1] : '100644'
845 if !v:shell_error && hash =~# '^\x\{40\}$'
846 call system(s:Prepare(dir, 'update-index', '--index-info'),
847 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
853 return call('writefile', [a:lines, a:url] + a:000)
857 \ '/**/': '/\%([^./][^/]*/\)*',
858 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
859 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
861 \ '/*': '/[^/.][^/]*',
864 function! fugitive#glob(url, ...) abort
865 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
866 let append = matchstr(glob, '/*$')
867 let glob = substitute(glob, '/*$', '', '')
868 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
870 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
871 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
874 let files = items(s:TreeInfo(dir, commit)[0])
876 call filter(files, 'v:val[1][2] ==# "tree"')
878 call map(files, 'v:val[0]')
879 call filter(files, 'v:val =~# pattern')
880 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
882 call map(files, 's:PlatformSlash(prepend . v:val . append)')
883 call extend(results, files)
888 return join(results, "\n")
892 function! fugitive#delete(url, ...) abort
893 let [dir, commit, file] = s:DirCommitFile(a:url)
894 if a:0 && len(a:1) || commit !~# '^\d$'
897 let entry = s:PathInfo(a:url)
898 if entry[2] !=# 'blob'
901 call system(s:Prepare(dir, 'update-index', '--index-info'),
902 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
903 return v:shell_error ? -1 : 0
906 let s:buffer_prototype = {}
908 function! fugitive#buffer(...) abort
909 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
910 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
911 if buffer.getvar('git_dir') !=# ''
914 call s:throw('not a git repository: '.bufname(buffer['#']))
917 function! s:buffer_getvar(var) dict abort
918 return getbufvar(self['#'],a:var)
921 function! s:buffer_getline(lnum) dict abort
922 return get(getbufline(self['#'], a:lnum), 0, '')
925 function! s:buffer_repo() dict abort
926 return fugitive#repo(self.getvar('git_dir'))
929 function! s:buffer_type(...) dict abort
930 if !empty(self.getvar('fugitive_type'))
931 let type = self.getvar('fugitive_type')
932 elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
934 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
936 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
938 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
940 elseif isdirectory(self.spec())
941 let type = 'directory'
942 elseif self.spec() == ''
948 return !empty(filter(copy(a:000),'v:val ==# type'))
956 function! s:buffer_spec() dict abort
957 let bufname = bufname(self['#'])
959 for i in split(bufname,'[^:]\zs\\')
960 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
962 return s:Slash(fnamemodify(retval,':p'))
967 function! s:buffer_spec() dict abort
968 let bufname = bufname(self['#'])
969 return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
974 function! s:buffer_name() dict abort
978 function! s:buffer_commit() dict abort
979 return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
982 function! s:buffer_relative(...) dict abort
983 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
985 let rev = s:sub(rev,'\w*','')
986 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
987 \ s:cpath(self.repo().dir() . '/')
988 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
989 elseif !self.repo().bare() &&
990 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
991 \ s:cpath(self.repo().tree() . '/')
992 let rev = self.spec()[strlen(self.repo().tree()) : -1]
994 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
997 function! s:Relative(...) abort
998 return fugitive#Path(@%, a:0 ? a:1 : './')
1001 function! s:buffer_path(...) dict abort
1003 return self.relative(a:1)
1005 return self.relative()
1008 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative'])
1010 " Section: Completion
1012 function! s:GlobComplete(lead, pattern) abort
1014 let results = glob(a:lead . a:pattern, 0, 1)
1016 let results = split(glob(a:lead . a:pattern), "\n")
1018 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1019 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1023 function! fugitive#PathComplete(base, ...) abort
1024 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1025 let tree = FugitiveTreeForGitDir(dir) . '/'
1026 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)\%(\./\)\='
1027 let base = substitute((a:base =~# '^/' ? '.' : '') . a:base, strip, '', '')
1028 if base =~# '^\.git/'
1029 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1030 let matches = s:GlobComplete(dir . '/', pattern)
1031 let cdir = fugitive#CommonDir(dir)
1032 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1033 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1035 call s:Uniq(matches)
1036 call map(matches, "'.git/' . v:val")
1037 elseif base =~# '^\~/'
1038 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1039 elseif len(tree) > 1
1040 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1044 if empty(matches) && a:base =~# '^/\|^\a\+:'
1045 let matches = s:GlobComplete('', a:base . '*')
1047 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1051 function! fugitive#Complete(base, ...) abort
1052 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1053 let tree = s:Tree(dir) . '/'
1054 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1056 if a:base =~# '^refs/'
1057 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1058 elseif a:base !~# '^\.\=/\|^:('
1059 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1060 let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1061 if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1062 let heads += ["stash"]
1063 let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1065 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1066 let results += heads
1068 call map(results, 's:fnameescape(v:val)')
1070 let results += fugitive#PathComplete(a:base, dir)
1074 elseif a:base =~# '^:'
1075 let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1076 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1077 if a:base !~# '^:[0-3]\%(:\|$\)'
1078 call filter(entries,'v:val[1] == "0"')
1079 call map(entries,'v:val[2:-1]')
1081 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1082 return map(entries, 's:fnameescape(v:val)')
1085 let tree = matchstr(a:base,'.*[:/]')
1086 let entries = split(s:TreeChomp(['ls-tree',tree], dir),"\n")
1087 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1088 call map(entries,'tree.s:sub(v:val,".*\t","")')
1089 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1090 return map(entries, 's:fnameescape(v:val)')
1094 " Section: File access
1096 function! s:ReplaceCmd(cmd) abort
1097 let tmp = tempname()
1098 let err = s:TempCmd(tmp, a:cmd)
1100 call s:throw((len(err) ? err : filereadable(tmp) ? join(readfile(tmp), ' ') : 'unknown error running ' . a:cmd))
1102 let fn = expand('%:p')
1103 silent exe 'doau BufReadPre '.s:fnameescape(fn)
1104 silent exe 'keepalt file '.tmp
1106 silent noautocmd edit!
1109 silent exe 'keepalt file '.s:fnameescape(fn)
1110 catch /^Vim\%((\a\+)\)\=:E302:/
1113 if fnamemodify(bufname('$'), ':p') ==# tmp
1114 silent execute 'bwipeout '.bufnr('$')
1116 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1120 function! fugitive#BufReadStatus() abort
1121 let amatch = s:Slash(expand('%:p'))
1122 if !exists('b:fugitive_display_format')
1123 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1125 let b:fugitive_display_format = b:fugitive_display_format % 2
1126 let b:fugitive_type = 'index'
1128 let dir = fnamemodify(amatch, ':h')
1129 setlocal noro ma nomodeline
1131 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1133 let old_index = $GIT_INDEX_FILE
1135 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(amatch).' '
1138 if b:fugitive_display_format
1139 let cmd = ['ls-files', '--stage']
1140 elseif fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.'
1141 let cmd = ['status']
1144 \ '-c', 'status.displayCommentPrefix=true',
1145 \ '-c', 'color.status=false',
1146 \ '-c', 'status.short=false',
1149 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1151 let cmd_str = prefix . s:Prepare(cmd, dir)
1153 if exists('old_index')
1154 let $GIT_INDEX_FILE = amatch
1156 execute cd s:fnameescape(s:Tree(dir))
1157 call s:ReplaceCmd(cmd_str)
1159 if exists('old_index')
1160 let $GIT_INDEX_FILE = old_index
1162 execute cd s:fnameescape(cwd)
1164 if b:fugitive_display_format
1165 if &filetype !=# 'git'
1170 if &filetype !=# 'gitcommit'
1171 set filetype=gitcommit
1173 set foldtext=fugitive#Foldtext()
1175 setlocal readonly nomodifiable nomodified noswapfile
1176 if &bufhidden ==# ''
1177 setlocal bufhidden=delete
1179 call fugitive#MapJumps()
1180 let nowait = v:version >= 704 ? '<nowait>' : ''
1183 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1184 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1185 exe "nnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
1186 exe "xnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
1187 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1188 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1189 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1190 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<Bar>echohl NONE<CR>
1191 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1192 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1193 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1194 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1195 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1196 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1197 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1198 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1199 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1200 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1201 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1202 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1203 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1204 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1205 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1206 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1207 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1208 nnoremap <buffer> <silent> r :<C-U>edit<CR>
1209 nnoremap <buffer> <silent> R :<C-U>edit<CR>
1210 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1211 nnoremap <buffer> . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
1212 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
1213 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1215 return 'echoerr v:errmsg'
1219 function! fugitive#FileReadCmd(...) abort
1220 let amatch = a:0 ? a:1 : expand('<amatch>')
1221 let [dir, rev] = s:DirRev(amatch)
1222 let line = a:0 > 1 ? a:2 : line("'[")
1224 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1227 let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev)
1229 let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1231 return line . 'read !' . escape(cmd, '!#%')
1234 function! fugitive#FileWriteCmd(...) abort
1235 let tmp = tempname()
1236 let amatch = a:0 ? a:1 : expand('<amatch>')
1237 let autype = a:0 > 1 ? 'Buf' : 'File'
1238 if exists('#' . autype . 'WritePre')
1239 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1242 let [dir, commit, file] = s:DirCommitFile(amatch)
1243 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1244 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1246 silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin').' > '.tmp
1247 let sha1 = readfile(tmp)[0]
1248 let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', file[1:-1])), '^\d\+')
1250 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1252 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1253 call writefile([info],tmp)
1255 let error = s:System('type '.s:gsub(tmp,'/','\\').'|'.s:Prepare(dir, 'update-index', '--index-info'))
1257 let error = s:System(s:Prepare(dir, 'update-index', '--index-info').' < '.tmp)
1259 if v:shell_error == 0
1261 if exists('#' . autype . 'WritePost')
1262 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1264 call fugitive#ReloadStatus()
1267 return 'echoerr '.string('fugitive: '.error)
1274 function! fugitive#BufReadCmd(...) abort
1275 let amatch = a:0 ? a:1 : expand('<amatch>')
1277 let [dir, rev] = s:DirRev(amatch)
1279 return 'echo "Invalid Fugitive URL"'
1282 let b:fugitive_type = 'stage'
1284 let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1285 if v:shell_error && rev =~# '^:0'
1286 let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1287 let b:fugitive_type = 'tree'
1290 unlet b:fugitive_type
1292 let &readonly = !filewritable(dir . '/index')
1293 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1295 setlocal readonly nomodifiable
1298 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1299 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1301 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1302 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1306 if b:fugitive_type !=# 'blob'
1310 setlocal noreadonly modifiable
1311 let pos = getpos('.')
1312 silent keepjumps %delete_
1316 if b:fugitive_type ==# 'tree'
1317 let b:fugitive_display_format = b:fugitive_display_format % 2
1318 if b:fugitive_display_format
1319 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1322 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev))[0:-2]
1324 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1326 elseif b:fugitive_type ==# 'tag'
1327 let b:fugitive_display_format = b:fugitive_display_format % 2
1328 if b:fugitive_display_format
1329 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1331 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1333 elseif b:fugitive_type ==# 'commit'
1334 let b:fugitive_display_format = b:fugitive_display_format % 2
1335 if b:fugitive_display_format
1336 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1338 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])
1339 keepjumps call search('^parent ')
1340 if getline('.') ==# 'parent '
1341 silent keepjumps delete_
1343 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1345 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1347 silent keepjumps delete_
1349 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1352 elseif b:fugitive_type ==# 'stage'
1353 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1354 elseif b:fugitive_type ==# 'blob'
1355 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1359 keepjumps call setpos('.',pos)
1360 setlocal nomodified noswapfile
1362 setlocal nomodifiable
1364 let &modifiable = b:fugitive_type !=# 'tree'
1366 let &readonly = !&modifiable || !filewritable(dir . '/index')
1367 if &bufhidden ==# ''
1368 setlocal bufhidden=delete
1370 if b:fugitive_type !=# 'blob'
1371 setlocal filetype=git foldmethod=syntax
1372 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1373 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1375 call fugitive#MapJumps()
1381 return 'echoerr v:errmsg'
1385 function! fugitive#BufWriteCmd(...) abort
1386 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1389 function! fugitive#SourceCmd(...) abort
1390 let amatch = a:0 ? a:1 : expand('<amatch>')
1391 let temp = s:BlobTemp(amatch)
1393 return 'noautocmd source ' . s:fnameescape(amatch)
1395 if !exists('g:virtual_scriptnames')
1396 let g:virtual_scriptnames = {}
1398 let g:virtual_scriptnames[temp] = amatch
1399 return 'source ' . s:fnameescape(temp)
1402 " Section: Temp files
1404 if !exists('s:temp_files')
1405 let s:temp_files = {}
1408 function! s:SetupTemp(file) abort
1409 if has_key(s:temp_files, s:cpath(a:file))
1410 let dict = s:temp_files[s:cpath(a:file)]
1411 let b:git_dir = dict.dir
1412 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1413 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1414 let &l:filetype = dict.filetype
1416 setlocal foldmarker=<<<<<<<,>>>>>>>
1417 setlocal bufhidden=delete nobuflisted
1418 setlocal buftype=nowrite
1419 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1420 if getline(1) !~# '^diff '
1421 setlocal nomodifiable
1423 call FugitiveDetect(a:file)
1428 augroup fugitive_temp
1430 autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1435 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1437 function! s:Git(bang, mods, args) abort
1439 return s:Edit('edit', 1, a:mods, a:args)
1441 let git = s:UserCommand()
1442 if has('gui_running') && !has('win32')
1443 let git .= ' --no-pager'
1445 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1446 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1449 let after = '|call fugitive#ReloadStatus()' . after
1451 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1457 execute 'lcd' fnameescape(tree)
1458 let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1459 return 'exe ' . string('terminal ' . exec) . after
1461 let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1462 if s:cpath(tree) !=# s:cpath(getcwd())
1463 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1464 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1470 let s:exec_paths = {}
1471 function! s:Subcommands() abort
1472 if !has_key(s:exec_paths, g:fugitive_git_executable)
1473 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1475 let exec_path = s:exec_paths[g:fugitive_git_executable]
1476 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1480 function! s:Aliases() abort
1481 if !has_key(s:aliases, b:git_dir)
1482 let s:aliases[b:git_dir] = {}
1483 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1484 for line in v:shell_error ? [] : lines
1485 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1488 return s:aliases[b:git_dir]
1491 function! s:GitComplete(A, L, P) abort
1492 let pre = strpart(a:L, 0, a:P)
1493 if pre !~# ' [[:alnum:]-]\+ '
1494 let cmds = s:Subcommands()
1495 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1496 elseif pre =~# ' -- '
1497 return fugitive#PathComplete(a:A)
1499 return fugitive#Complete(a:A, a:L, a:P)
1503 " Section: Gcd, Glcd
1505 function! s:DirComplete(A, L, P) abort
1506 return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1509 function! s:DirArg(path) abort
1510 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1511 if path =~# '^/\|^\a\+:'
1514 return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1518 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1519 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1523 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1524 augroup fugitive_status
1527 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1528 autocmd BufDelete term://* call fugitive#ReloadStatus()
1532 function! s:Status(bang, count, mods) abort
1534 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1536 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1537 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1539 return 'echoerr v:errmsg'
1544 function! fugitive#ReloadStatus() abort
1545 if exists('s:reloading_status')
1549 let s:reloading_status = 1
1550 let mytab = tabpagenr()
1551 for tab in [mytab] + range(1,tabpagenr('$'))
1552 for winnr in range(1,tabpagewinnr(tab,'$'))
1553 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1554 execute 'tabnext '.tab
1556 execute winnr.'wincmd w'
1557 let restorewinnr = 1
1561 call fugitive#BufReadStatus()
1564 if exists('restorewinnr')
1567 execute 'tabnext '.mytab
1573 unlet! s:reloading_status
1577 function! fugitive#reload_status() abort
1578 return fugitive#ReloadStatus()
1581 function! s:stage_info(lnum) abort
1582 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1584 if has('multi_byte_encoding')
1585 let colon = '\%(:\|\%uff1a\)'
1589 while lnum && getline(lnum) !~# colon.'$'
1594 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1595 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1596 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1597 return [filename, 'untracked']
1598 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1599 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1600 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1601 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1603 return ['', 'unknown']
1607 function! s:StageNext(count) abort
1608 for i in range(a:count)
1609 call search('^.\=\t.*','W')
1614 function! s:StagePrevious(count) abort
1615 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1616 return 'CtrlP '.fnameescape(s:Tree())
1618 for i in range(a:count)
1619 call search('^.\=\t.*','Wbe')
1625 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1627 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1628 if f !=# '' | let jump = f | endif
1629 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1630 if f !=# '' | let jump = f | endif
1634 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1637 function! s:StageUndo() abort
1638 let [filename, section] = s:stage_info(line('.'))
1642 let hash = s:TreeChomp('hash-object', '-w', filename)
1644 if section ==# 'untracked'
1645 call s:TreeChomp('clean', '-f', '--', filename)
1646 elseif section ==# 'unmerged'
1647 call s:TreeChomp('rm', '--', filename)
1648 elseif section ==# 'unstaged'
1649 call s:TreeChomp('checkout', '--', filename)
1651 call s:TreeChomp('checkout', 'HEAD', '--', filename)
1653 call s:StageReloadSeek(filename, line('.'), line('.'))
1655 return 'checktime|redraw|echomsg ' .
1656 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1660 function! s:StageDiff(diff) abort
1661 let [filename, section] = s:stage_info(line('.'))
1662 if filename ==# '' && section ==# 'staged'
1663 return 'Git! diff --no-ext-diff --cached'
1664 elseif filename ==# ''
1665 return 'Git! diff --no-ext-diff'
1666 elseif filename =~# ' -> '
1667 let [old, new] = split(filename,' -> ')
1668 execute 'Gedit '.s:fnameescape(':0:'.new)
1669 return a:diff.' HEAD:'.s:fnameescape(old)
1670 elseif section ==# 'staged'
1671 execute 'Gedit '.s:fnameescape(':0:'.filename)
1674 execute 'Gedit '.s:fnameescape('/'.filename)
1679 function! s:StageDiffEdit() abort
1680 let [filename, section] = s:stage_info(line('.'))
1681 let arg = (filename ==# '' ? '.' : filename)
1682 if section ==# 'staged'
1683 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1684 elseif section ==# 'untracked'
1685 call s:TreeChomp('add','--intent-to-add',arg)
1689 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1690 call search(':$','W')
1693 call s:StageReloadSeek(arg,line('.'),line('.'))
1697 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1701 function! s:StageToggle(lnum1,lnum2) abort
1702 if a:lnum1 == 1 && a:lnum2 == 1
1703 return 'Gedit /.git|call search("^index$", "wc")'
1707 for lnum in range(a:lnum1,a:lnum2)
1708 let [filename, section] = s:stage_info(lnum)
1709 if getline('.') =~# ':$'
1710 if section ==# 'staged'
1711 call s:TreeChomp('reset','-q')
1714 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1715 call search(':$','W')
1718 elseif section ==# 'unstaged'
1719 call s:TreeChomp('add','-u')
1722 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1723 call search(':$','W')
1727 call s:TreeChomp('add','.')
1730 call search(':$','W')
1738 if section ==# 'staged'
1739 if filename =~ ' -> '
1740 let files_to_unstage = split(filename,' -> ')
1742 let files_to_unstage = [filename]
1744 let filename = files_to_unstage[-1]
1745 let cmd = ['reset','-q','--'] + files_to_unstage
1746 elseif getline(lnum) =~# '^.\=\tdeleted:'
1747 let cmd = ['rm','--',filename]
1748 elseif getline(lnum) =~# '^.\=\tmodified:'
1749 let cmd = ['add','--',filename]
1751 let cmd = ['add','-A','--',filename]
1753 if !exists('first_filename')
1754 let first_filename = filename
1756 let output .= call('s:TreeChomp', cmd)."\n"
1758 if exists('first_filename')
1759 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1761 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1763 return 'echoerr v:errmsg'
1768 function! s:StagePatch(lnum1,lnum2) abort
1772 for lnum in range(a:lnum1,a:lnum2)
1773 let [filename, section] = s:stage_info(lnum)
1774 if getline('.') =~# ':$' && section ==# 'staged'
1775 return 'Git reset --patch'
1776 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1777 return 'Git add --patch'
1778 elseif getline('.') =~# ':$' && section ==# 'untracked'
1779 return 'Git add -N .'
1780 elseif filename ==# ''
1783 if !exists('first_filename')
1784 let first_filename = filename
1787 if filename =~ ' -> '
1788 let reset += [split(filename,' -> ')[1]]
1789 elseif section ==# 'staged'
1790 let reset += [filename]
1791 elseif getline(lnum) !~# '^.\=\tdeleted:'
1792 let add += [filename]
1797 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1800 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1802 if exists('first_filename')
1806 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1809 return 'echoerr v:errmsg'
1816 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1818 function! s:Commit(mods, args, ...) abort
1819 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1820 let dir = a:0 ? a:1 : b:git_dir
1821 let tree = s:Tree(dir)
1822 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1824 let msgfile = dir . '/COMMIT_EDITMSG'
1825 let outfile = tempname()
1826 let errorfile = tempname()
1828 let guioptions = &guioptions
1830 if &guioptions =~# '!'
1831 setglobal guioptions-=!
1833 execute cd s:fnameescape(tree)
1836 let old_editor = $GIT_EDITOR
1837 let $GIT_EDITOR = 'false'
1839 let command = 'env GIT_EDITOR=false '
1841 let args = s:ShellExpand(a:args)
1842 let command .= s:UserCommand() . ' commit ' . args
1844 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1845 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1846 noautocmd execute '!'.command.' 2> '.errorfile
1848 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1850 let error = v:shell_error
1852 execute cd s:fnameescape(cwd)
1853 let &guioptions = guioptions
1855 if !has('gui_running')
1859 if filereadable(outfile)
1860 for line in readfile(outfile)
1866 let errors = readfile(errorfile)
1867 let error = get(errors,-2,get(errors,-1,'!'))
1868 if error =~# 'false''\=\.$'
1869 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1870 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1871 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1873 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1874 let args = '-F '.s:shellesc(msgfile).' '.args
1875 if args !~# '\%(^\| \)--cleanup\>'
1876 let args = '--cleanup=strip '.args
1878 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1879 execute mods 'keepalt edit' s:fnameescape(msgfile)
1880 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1881 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1882 elseif get(b:, 'fugitive_type', '') ==# 'index'
1883 execute mods 'keepalt edit' s:fnameescape(msgfile)
1884 execute (search('^#','n')+1).'wincmd+'
1885 setlocal nopreviewwindow
1887 execute mods 'keepalt split' s:fnameescape(msgfile)
1889 let b:fugitive_commit_arguments = args
1890 setlocal bufhidden=wipe filetype=gitcommit
1892 elseif error ==# '!'
1895 call s:throw(empty(error)?join(errors, ' '):error)
1899 return 'echoerr v:errmsg'
1901 if exists('old_editor')
1902 let $GIT_EDITOR = old_editor
1904 call delete(outfile)
1905 call delete(errorfile)
1906 call fugitive#ReloadStatus()
1910 function! s:CommitComplete(A,L,P) abort
1911 if a:A =~# '^--fixup=\|^--squash='
1912 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
1914 let pre = matchstr(a:A, '^--\w*=') . ':/^'
1915 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
1917 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1918 let args = ['-C', '-F', '-a', '-c', '-e', '-i', '-m', '-n', '-o', '-q', '-s', '-t', '-u', '-v', '--all', '--allow-empty', '--amend', '--author=', '--cleanup=', '--dry-run', '--edit', '--file=', '--fixup=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--squash=', '--template=', '--untracked-files', '--verbose']
1919 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1921 return fugitive#PathComplete(a:A)
1926 function! s:FinishCommit() abort
1927 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1929 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1930 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
1935 " Section: Gmerge, Gpull
1937 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1938 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1939 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
1940 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
1941 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1942 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1944 function! s:RevisionComplete(A, L, P) abort
1945 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1946 \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
1949 function! s:RemoteComplete(A, L, P) abort
1950 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1952 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
1953 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1954 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1956 let matches = split(s:TreeChomp('remote'), "\n")
1958 return join(matches, "\n")
1961 function! fugitive#Cwindow() abort
1962 if &buftype == 'quickfix'
1966 if &buftype == 'quickfix'
1972 let s:common_efm = ''
1974 \ . '%+Eusage:%.%#,'
1975 \ . '%+Eerror:%.%#,'
1976 \ . '%+Efatal:%.%#,'
1977 \ . '%-G%.%#%\e[K%.%#,'
1978 \ . '%-G%.%#%\r%.%\+'
1980 function! s:Merge(cmd, bang, args) abort
1981 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
1982 return 'echoerr "git rebase --interactive not supported"'
1984 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1986 let [mp, efm] = [&l:mp, &l:efm]
1987 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
1989 let &l:errorformat = ''
1990 \ . '%-Gerror:%.%#false''.,'
1991 \ . '%-G%.%# ''git commit'' %.%#,'
1992 \ . '%+Emerge:%.%#,'
1993 \ . s:common_efm . ','
1994 \ . '%+ECannot %.%#: You have unstaged changes.,'
1995 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1996 \ . '%+EThere is no tracking information for the current branch.,'
1997 \ . '%+EYou are not currently on a branch. Please specify which,'
1998 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1999 \ . 'CONFLICT (%m): Merge conflict in %f,'
2000 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2001 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2002 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2003 \ . '%+ECONFLICT %.%#,'
2004 \ . '%+EKONFLIKT %.%#,'
2005 \ . '%+ECONFLIT %.%#,'
2006 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2007 \ . "%+E\u51b2\u7a81 %.%#,"
2009 if a:cmd =~# '^merge' && empty(a:args) &&
2010 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2011 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2012 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2014 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2015 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2016 \ ' ' . a:args, ' *$', '')
2018 if !empty($GIT_EDITOR) || has('win32')
2019 let old_editor = $GIT_EDITOR
2020 let $GIT_EDITOR = 'false'
2022 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2024 execute cd fnameescape(s:Tree())
2025 silent noautocmd make!
2026 catch /^Vim\%((\a\+)\)\=:E211/
2027 let err = v:exception
2030 let [&l:mp, &l:efm] = [mp, efm]
2031 if exists('old_editor')
2032 let $GIT_EDITOR = old_editor
2034 execute cd fnameescape(cwd)
2036 call fugitive#ReloadStatus()
2037 if empty(filter(getqflist(),'v:val.valid'))
2038 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2040 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2043 let qflist = getqflist()
2048 let e.pattern = '^<<<<<<<'
2051 call fugitive#Cwindow()
2053 call setqflist(qflist, 'r')
2058 return exists('err') ? 'echoerr '.string(err) : ''
2061 " Section: Ggrep, Glog
2063 if !exists('g:fugitive_summary_format')
2064 let g:fugitive_summary_format = '%s'
2067 function! s:GrepComplete(A, L, P) abort
2068 if strpart(a:L, 0, a:P) =~# ' -- '
2069 return fugitive#PathComplete(a:A)
2071 return fugitive#Complete(a:A)
2075 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2076 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2077 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2078 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2080 function! s:Grep(cmd,bang,arg) abort
2081 let grepprg = &grepprg
2082 let grepformat = &grepformat
2083 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2086 execute cd s:fnameescape(s:Tree())
2087 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2088 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2089 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2090 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2092 if bufname(entry.bufnr) =~ ':'
2093 let entry.filename = s:Generate(bufname(entry.bufnr))
2096 elseif a:arg =~# '\%(^\| \)--cached\>'
2097 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2102 if a:cmd =~# '^l' && exists('changed')
2103 call setloclist(0, list, 'r')
2104 elseif exists('changed')
2105 call setqflist(list, 'r')
2107 if !a:bang && !empty(list)
2108 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2110 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2113 let &grepprg = grepprg
2114 let &grepformat = grepformat
2115 execute cd s:fnameescape(dir)
2119 function! s:Log(cmd, bang, line1, line2, ...) abort
2120 let args = ' ' . join(a:000, ' ')
2121 let before = substitute(args, ' --\S\@!.*', '', '')
2122 let after = strpart(args, len(before))
2123 let path = s:Relative('/')
2124 if path =~# '^/\.git\%(/\|$\)' || len(after)
2127 let relative = s:Relative('')
2128 if before !~# '\s[^[:space:]-]'
2129 let owner = s:Owner(@%)
2131 let before .= ' ' . s:shellesc(owner)
2134 if relative =~# '^\.git\%(/\|$\)'
2137 if len(relative) && a:line2 > 0
2138 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2139 elseif len(relative) && (empty(after) || a:line2 == 0)
2140 let after = (len(after) > 3 ? after : ' -- ') . relative
2142 let grepformat = &grepformat
2143 let grepprg = &grepprg
2144 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2147 execute cd s:fnameescape(s:Tree())
2148 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2149 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2150 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2151 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2153 let &grepformat = grepformat
2154 let &grepprg = grepprg
2155 execute cd s:fnameescape(dir)
2159 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
2161 function! s:UsableWin(nr) abort
2162 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2163 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2166 function! s:EditParse(args) abort
2168 let args = copy(a:args)
2169 while !empty(args) && args[0] =~# '^+'
2170 call add(pre, escape(remove(args, 0), ' |"') . ' ')
2173 let file = join(args)
2174 elseif empty(expand('%'))
2176 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2177 let file = s:Relative(':0:')
2179 let file = s:Relative('./')
2181 return [s:Expand(file), join(pre)]
2184 function! s:BlurStatus() abort
2185 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2186 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2188 exe winnrs[0].'wincmd w'
2189 elseif winnr('$') == 1
2190 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2191 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2196 let mywinnr = winnr()
2197 for winnr in range(winnr('$'),1,-1)
2198 if winnr != mywinnr && getwinvar(winnr,'&diff')
2199 execute winnr.'wincmd w'
2211 function! s:Edit(cmd, bang, mods, args, ...) abort
2212 let mods = a:mods ==# '<mods>' ? '' : a:mods
2215 let temp = s:tempname()
2216 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2219 execute cd s:fnameescape(s:Tree())
2220 let git = s:UserCommand()
2221 let args = s:ShellExpand(a:args)
2222 silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2223 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2225 execute cd s:fnameescape(cwd)
2227 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2231 silent execute mods a:cmd temp
2232 call fugitive#ReloadStatus()
2233 return 'redraw|echo ' . string(':!' . git . ' ' . args)
2236 let [file, pre] = s:EditParse(a:000)
2238 let file = s:Generate(file)
2240 return 'echoerr v:errmsg'
2242 if file !~# '^\a\a\+:'
2243 let file = s:sub(file, '/$', '')
2248 return mods . ' ' . a:cmd . ' ' . pre . s:fnameescape(file)
2251 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2252 let mods = a:mods ==# '<mods>' ? '' : a:mods
2255 let delete = 'silent 1,' . line('$') . 'delete_|'
2256 let after = line('$')
2258 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2263 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2266 execute cd s:fnameescape(s:Tree())
2267 let git = s:UserCommand()
2268 let args = s:ShellExpand(a:args)
2269 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2271 execute cd s:fnameescape(cwd)
2273 execute delete . 'diffupdate'
2274 call fugitive#ReloadStatus()
2275 return 'redraw|echo '.string(':!'.git.' '.args)
2277 let [file, pre] = s:EditParse(a:000)
2279 let file = s:Generate(file)
2281 return 'echoerr v:errmsg'
2283 return mods . ' ' . after . 'read ' . pre . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2286 function! s:EditRunComplete(A,L,P) abort
2288 return s:GitComplete(a:A, a:L, a:P)
2290 return fugitive#Complete(a:A, a:L, a:P)
2294 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2295 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2296 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2297 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete Gsplit execute s:Edit((<count> ? <count> : '').'split', <bang>0, '<mods>', <q-args>, <f-args>)")
2298 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete Gvsplit execute s:Edit((<count> ? <count> : '').'vsplit', <bang>0, '<mods>', <q-args>, <f-args>)")
2299 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete" . (has('patch-7.4.542') ? ' -addr=tabs' : '') . " Gtabedit execute s:Edit((<count> ? <count> : '').'tabedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2300 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread execute s:Read(<count>, <line1>, <line2>, +'<range>', <bang>0, '<mods>', <q-args>, <f-args>)")
2302 " Section: Gwrite, Gwq
2304 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2305 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2306 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2308 function! s:Write(force,...) abort
2309 if exists('b:fugitive_commit_arguments')
2310 return 'write|bdelete'
2311 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2313 elseif get(b:, 'fugitive_type', '') ==# 'index'
2315 elseif s:Relative('') ==# '' && getline(4) =~# '^+++ '
2316 let filename = getline(4)[6:-1]
2319 setlocal buftype=nowrite
2320 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2321 let err = s:TreeChomp('apply', '--cached', '--reverse', expand('%:p'))
2323 let err = s:TreeChomp('apply', '--cached', expand('%:p'))
2326 let v:errmsg = split(err,"\n")[0]
2327 return 'echoerr v:errmsg'
2331 return 'Gedit '.fnameescape(filename)
2334 let mytab = tabpagenr()
2335 let mybufnr = bufnr('')
2336 let path = a:0 ? s:Expand(join(a:000, ' ')) : s:Relative()
2338 return 'echoerr '.string('fugitive: cannot determine file path')
2340 if path =~# '^:\d\>'
2341 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:Generate(path))
2343 let always_permitted = ((s:Relative() ==# path || s:Relative('') ==# path) && s:DirCommitFile(@%)[1] =~# '^0\=$')
2344 if !always_permitted && !a:force && (len(s:TreeChomp('diff','--name-status','HEAD','--',path)) || len(s:TreeChomp('ls-files','--others','--',path)))
2345 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2346 return 'echoerr v:errmsg'
2348 let file = s:Generate(path)
2350 for nr in range(1,bufnr('$'))
2351 if fnamemodify(bufname(nr),':p') ==# file
2356 if treebufnr > 0 && treebufnr != bufnr('')
2357 let temp = tempname()
2358 silent execute '%write '.temp
2359 for tab in [mytab] + range(1,tabpagenr('$'))
2360 for winnr in range(1,tabpagewinnr(tab,'$'))
2361 if tabpagebuflist(tab)[winnr-1] == treebufnr
2362 execute 'tabnext '.tab
2364 execute winnr.'wincmd w'
2365 let restorewinnr = 1
2368 let lnum = line('.')
2369 let last = line('$')
2370 silent execute '$read '.temp
2371 silent execute '1,'.last.'delete_'
2376 if exists('restorewinnr')
2379 execute 'tabnext '.mytab
2385 call writefile(readfile(temp,'b'),file,'b')
2388 execute 'write! '.s:fnameescape(s:Generate(path))
2392 let error = s:TreeChomp('add', '--force', '--', path)
2394 let error = s:TreeChomp('add', '--', path)
2397 let v:errmsg = 'fugitive: '.error
2398 return 'echoerr v:errmsg'
2400 if s:Relative('') ==# path && s:DirCommitFile(@%)[1] =~# '^\d$'
2404 let one = s:Generate(':1:'.path)
2405 let two = s:Generate(':2:'.path)
2406 let three = s:Generate(':3:'.path)
2407 for nr in range(1,bufnr('$'))
2408 let name = fnamemodify(bufname(nr), ':p')
2409 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2410 execute nr.'bdelete'
2415 let zero = s:Generate(':0:'.path)
2416 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2417 for tab in range(1,tabpagenr('$'))
2418 for winnr in range(1,tabpagewinnr(tab,'$'))
2419 let bufnr = tabpagebuflist(tab)[winnr-1]
2420 let bufname = fnamemodify(bufname(bufnr), ':p')
2421 if bufname ==# zero && bufnr != mybufnr
2422 execute 'tabnext '.tab
2424 execute winnr.'wincmd w'
2425 let restorewinnr = 1
2428 let lnum = line('.')
2429 let last = line('$')
2430 silent execute '$read '.s:fnameescape(file)
2431 silent execute '1,'.last.'delete_'
2436 if exists('restorewinnr')
2439 execute 'tabnext '.mytab
2445 call fugitive#ReloadStatus()
2449 function! s:Wq(force,...) abort
2450 let bang = a:force ? '!' : ''
2451 if exists('b:fugitive_commit_arguments')
2454 let result = call(s:function('s:Write'),[a:force]+a:000)
2455 if result =~# '^\%(write\|wq\|echoerr\)'
2456 return s:sub(result,'^write','wq')
2458 return result.'|quit'.bang
2462 augroup fugitive_commit
2464 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2467 " Section: Gpush, Gfetch
2469 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2470 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2472 function! s:Dispatch(bang, args)
2473 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2475 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2477 let b:current_compiler = 'git'
2478 let &l:errorformat = s:common_efm
2479 execute cd fnameescape(s:Tree())
2480 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2481 if exists(':Make') == 2
2484 silent noautocmd make!
2486 return 'call fugitive#Cwindow()'
2490 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2491 if empty(cc) | unlet! b:current_compiler | endif
2492 execute cd fnameescape(cwd)
2498 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2499 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2500 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2502 augroup fugitive_diff
2504 autocmd BufWinLeave *
2505 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2506 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2508 autocmd BufWinEnter *
2509 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2510 \ call s:diffoff() |
2514 function! s:can_diffoff(buf) abort
2515 return getwinvar(bufwinnr(a:buf), '&diff') &&
2516 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2517 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2520 function! fugitive#CanDiffoff(buf) abort
2521 return s:can_diffoff(a:buf)
2524 function! s:diff_modifier(count) abort
2525 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2526 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2528 elseif &diffopt =~# 'vertical'
2529 return 'keepalt vert '
2530 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2533 return 'keepalt vert '
2537 function! s:diff_window_count() abort
2539 for nr in range(1,winnr('$'))
2540 let c += getwinvar(nr,'&diff')
2545 function! s:diff_restore() abort
2546 let restore = 'setlocal nodiff noscrollbind'
2547 \ . ' scrollopt=' . &l:scrollopt
2548 \ . (&l:wrap ? ' wrap' : ' nowrap')
2549 \ . ' foldlevel=999'
2550 \ . ' foldmethod=' . &l:foldmethod
2551 \ . ' foldcolumn=' . &l:foldcolumn
2552 \ . ' foldlevel=' . &l:foldlevel
2553 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2554 if has('cursorbind')
2555 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2560 function! s:diffthis() abort
2562 let w:fugitive_diff_restore = s:diff_restore()
2567 function! s:diffoff() abort
2568 if exists('w:fugitive_diff_restore')
2569 execute w:fugitive_diff_restore
2570 unlet w:fugitive_diff_restore
2576 function! s:diffoff_all(dir) abort
2577 let curwin = winnr()
2578 for nr in range(1,winnr('$'))
2579 if getwinvar(nr,'&diff')
2581 execute nr.'wincmd w'
2582 let restorewinnr = 1
2584 if exists('b:git_dir') && b:git_dir ==# a:dir
2589 execute curwin.'wincmd w'
2592 function! s:CompareAge(mine, theirs) abort
2593 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2594 let mine = substitute(a:mine, '^:', '', '')
2595 let theirs = substitute(a:theirs, '^:', '', '')
2596 let my_score = get(scores, ':'.mine, 0)
2597 let their_score = get(scores, ':'.theirs, 0)
2598 if my_score || their_score
2599 return my_score < their_score ? -1 : my_score != their_score
2600 elseif mine ==# theirs
2603 let base = s:TreeChomp('merge-base', mine, theirs)
2606 elseif base ==# theirs
2609 let my_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:mine)
2610 let their_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:theirs)
2611 return my_time < their_time ? -1 : my_time != their_time
2614 function! s:Diff(vert,keepfocus,...) abort
2615 let args = copy(a:000)
2617 if get(args, 0) =~# '^+'
2618 let post = remove(args, 0)[1:-1]
2620 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2621 let commit = s:DirCommitFile(@%)[1]
2622 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2623 if exists(':DiffGitCached')
2624 return 'DiffGitCached'
2625 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', s:Relative('')))
2627 return 'echoerr ' . string("fugitive: error determining merge status of " . s:Relative(''))
2629 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2631 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2632 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2636 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2637 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2642 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2643 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2646 let arg = join(args, ' ')
2650 let file = s:Relative('/')
2652 let file = s:Relative(':0:')
2653 elseif arg =~# '^:/.'
2655 let file = fugitive#RevParse(arg).s:Relative(':')
2657 return 'echoerr v:errmsg'
2660 let file = s:Expand(arg)
2662 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2663 let file = file.s:Relative(':')
2666 let file = s:Relative(empty(commit) ? ':0:' : '/')
2669 let spec = s:Generate(file)
2670 let restore = s:diff_restore()
2671 if exists('+cursorbind')
2674 let w:fugitive_diff_restore = restore
2675 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2676 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2678 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2680 let &l:readonly = &l:readonly
2682 let w:fugitive_diff_restore = restore
2684 if getwinvar('#', '&diff')
2687 call feedkeys(winnr."\<C-W>w", 'n')
2692 return 'echoerr v:errmsg'
2696 " Section: Gmove, Gremove
2698 function! s:Move(force, rename, destination) abort
2699 if a:destination =~# '^\./.'
2700 let destination = a:destination[2:-1]
2701 elseif a:destination =~# '^:/:\='
2702 let destination = substitute(a:destination, '^:/:\=', '', '')
2703 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
2704 let destination = matchstr(a:destination, ')\zs.*')
2705 elseif a:rename && a:destination !~# '^\a\+:\|^/'
2706 let destination = fnamemodify(s:Relative(''), ':h') . '/' . a:destination
2708 let destination = a:destination
2713 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', s:Relative(''), destination])
2715 let v:errmsg = 'fugitive: '.message
2716 return 'echoerr v:errmsg'
2718 let destination = s:Tree() . '/' . destination
2719 if isdirectory(destination)
2720 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2722 call fugitive#ReloadStatus()
2723 if empty(s:DirCommitFile(@%)[1])
2724 if isdirectory(destination)
2725 return 'keepalt edit '.s:fnameescape(destination)
2727 return 'keepalt saveas! '.s:fnameescape(destination)
2730 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2734 function! s:RenameComplete(A,L,P) abort
2735 if a:A =~# '^[.:]\=/'
2736 return fugitive#PathComplete(a:A)
2738 let pre = '/' . fnamemodify(s:Relative(''), ':h') . '/'
2739 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2743 function! s:Remove(after, force) abort
2744 if s:DirCommitFile(@%)[1] ==# ''
2746 elseif s:DirCommitFile(@%)[1] ==# '0'
2747 let cmd = ['rm','--cached']
2749 let v:errmsg = 'fugitive: rm not supported here'
2750 return 'echoerr v:errmsg'
2753 let cmd += ['--force']
2755 let message = call('s:TreeChomp', cmd+['--',s:Relative('')])
2757 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2758 return 'echoerr '.string(v:errmsg)
2760 call fugitive#ReloadStatus()
2761 return a:after . (a:force ? '!' : '')
2765 augroup fugitive_remove
2767 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2768 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2769 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2770 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2771 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2777 function! s:Keywordprg() abort
2778 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2779 if has('gui_running') && !has('win32')
2780 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2782 return s:UserCommand() . args . ' show'
2786 augroup fugitive_blame
2788 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2789 autocmd Syntax fugitiveblame call s:BlameSyntax()
2790 autocmd User Fugitive
2791 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2792 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2794 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2795 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2798 function! s:linechars(pattern) abort
2799 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2800 if exists('*synconcealed') && &conceallevel > 1
2801 for col in range(1, chars)
2802 let chars -= synconcealed(line('.'), col)[0]
2808 function! s:Blame(bang, line1, line2, count, mods, args) abort
2809 if exists('b:fugitive_blamed_bufnr')
2813 if empty(s:Relative(''))
2814 call s:throw('file or blob required')
2816 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2817 call s:throw('unsupported option')
2819 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2820 let cmd = ['--no-pager', 'blame', '--show-number']
2822 let cmd += ['-L', a:line1 . ',' . a:line1]
2825 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2826 let cmd += [s:DirCommitFile(@%)[1]]
2828 let cmd += ['--contents', '-']
2830 let cmd += ['--', s:Relative('')]
2831 let basecmd = escape(s:Prepare(cmd), '!#%')
2833 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2835 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2837 execute cd s:fnameescape(tree)
2839 let error = s:tempname()
2840 let temp = error.'.fugitiveblame'
2842 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2844 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2847 execute cd s:fnameescape(cwd)
2851 call s:throw(join(readfile(error),"\n"))
2854 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
2855 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
2857 for winnr in range(winnr('$'),1,-1)
2858 call setwinvar(winnr, '&scrollbind', 0)
2859 if exists('+cursorbind')
2860 call setwinvar(winnr, '&cursorbind', 0)
2862 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2863 execute winbufnr(winnr).'bdelete'
2866 let bufnr = bufnr('')
2867 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2868 if exists('+cursorbind')
2869 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2872 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2875 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2877 setlocal scrollbind nowrap nofoldenable
2878 if exists('+cursorbind')
2881 let top = line('w0') + &scrolloff
2882 let current = line('.')
2883 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
2884 exe 'keepalt leftabove vsplit '.temp
2885 let b:fugitive_blamed_bufnr = bufnr
2886 let b:fugitive_type = 'blame'
2887 let w:fugitive_leave = restore
2888 let b:fugitive_blame_arguments = join(a:args,' ')
2892 if exists('+cursorbind')
2895 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
2896 if exists('+concealcursor')
2897 setlocal concealcursor=nc conceallevel=2
2899 if exists('+relativenumber')
2900 setlocal norelativenumber
2902 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2903 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2904 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
2905 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2906 nnoremap <buffer> <silent> gq :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
2907 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2908 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
2909 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2910 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2911 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2912 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2913 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2914 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
2915 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2916 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2917 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2923 execute cd s:fnameescape(cwd)
2928 return 'echoerr v:errmsg'
2932 function! s:BlameCommit(cmd, ...) abort
2933 let line = a:0 ? a:1 : getline('.')
2934 if line =~# '^0\{4,40\} '
2935 return 'echoerr ' . string('Not Committed Yet')
2937 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
2938 if cmd =~# '^echoerr'
2941 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
2942 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2944 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
2947 if a:cmd ==# 'pedit'
2950 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2952 let head = line('.')
2953 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2954 let top = +matchstr(getline('.'),' +\zs\d\+')
2955 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2956 if lnum >= top && lnum <= top + len
2957 let offset = lnum - top
2965 while offset > 0 && line('.') < line('$')
2967 if getline('.') =~# '^[ +]'
2980 function! s:BlameJump(suffix) abort
2981 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2982 if commit =~# '^0\+$'
2985 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2986 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2988 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
2990 let args = b:fugitive_blame_arguments
2991 let offset = line('.') - line('w0')
2992 let bufnr = bufnr('%')
2993 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2995 exe winnr.'wincmd w'
2997 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3002 if exists(':Gblame')
3003 execute 'Gblame '.args
3005 let delta = line('.') - line('w0') - offset
3007 execute 'normal! '.delta."\<C-E>"
3009 execute 'normal! '.(-delta)."\<C-Y>"
3016 let s:hash_colors = {}
3018 function! s:BlameSyntax() abort
3019 let b:current_syntax = 'fugitiveblame'
3020 let conceal = has('conceal') ? ' conceal' : ''
3021 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3022 syn match FugitiveblameBoundary "^\^"
3023 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3024 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3025 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3026 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3027 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3028 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3029 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3030 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3031 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3032 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
3033 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3034 hi def link FugitiveblameBoundary Keyword
3035 hi def link FugitiveblameHash Identifier
3036 hi def link FugitiveblameUncommitted Ignore
3037 hi def link FugitiveblameTime PreProc
3038 hi def link FugitiveblameLineNumber Number
3039 hi def link FugitiveblameOriginalFile String
3040 hi def link FugitiveblameOriginalLineNumber Float
3041 hi def link FugitiveblameShort FugitiveblameDelimiter
3042 hi def link FugitiveblameDelimiter Delimiter
3043 hi def link FugitiveblameNotCommittedYet Comment
3045 for lnum in range(1, line('$'))
3046 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3047 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3051 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3052 \ && empty(get(s:hash_colors, hash))
3053 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3054 let color = csapprox#per_component#Approximate(r, g, b)
3055 if color == 16 && &background ==# 'dark'
3058 let s:hash_colors[hash] = ' ctermfg='.color
3060 let s:hash_colors[hash] = ''
3062 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3064 call s:RehighlightBlame()
3067 function! s:RehighlightBlame() abort
3068 for [hash, cterm] in items(s:hash_colors)
3069 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3070 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3072 exe 'hi link FugitiveblameHash'.hash.' Identifier'
3079 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3081 let s:redirects = {}
3083 function! s:Browse(bang,line1,count,...) abort
3085 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3087 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3088 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3094 let rev = s:DirRev(@%)[1]
3097 let expanded = s:Relative('/')
3099 let expanded = s:Expand(rev)
3101 let cdir = fugitive#CommonDir(b:git_dir)
3102 for dir in ['tags/', 'heads/', 'remotes/']
3103 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3104 let expanded = '/.git/refs/' . dir . expanded
3107 let full = s:Generate(expanded)
3109 if full =~? '^fugitive:'
3110 let [dir, commit, path] = s:DirCommitFile(full)
3111 if commit =~# '^:\=\d$'
3115 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3116 let branch = matchstr(expanded, '^[^:]*')
3120 let path = path[1:-1]
3121 elseif empty(s:Tree())
3122 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3125 let path = full[strlen(s:Tree())+1:-1]
3126 if path =~# '^\.git/'
3128 elseif isdirectory(full)
3134 if type ==# 'tree' && !empty(path)
3135 let path = s:sub(path, '/\=$', '/')
3137 if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3138 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3139 if body =~# '^\x\{40\}$'
3143 elseif body =~# '^ref: refs/'
3144 let path = '.git/' . matchstr(body,'ref: \zs.*')
3149 if path =~# '^\.git/refs/remotes/.'
3151 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3152 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3154 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3155 let path = '.git/refs/heads/'.merge
3157 elseif path =~# '^\.git/refs/heads/.'
3158 let branch = path[16:-1]
3159 elseif !exists('branch')
3160 let branch = FugitiveHead()
3163 let r = fugitive#Config('branch.'.branch.'.remote')
3164 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3165 if r ==# '.' && !empty(m)
3166 let r2 = fugitive#Config('branch.'.m.'.remote')
3169 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3175 if r ==# '.' || r ==# remote
3177 if path =~# '^\.git/refs/heads/.'
3178 let path = '.git/refs/heads/'.merge
3183 let line1 = a:count > 0 ? a:line1 : 0
3184 let line2 = a:count > 0 ? a:count : 0
3185 if empty(commit) && path !~# '^\.git/'
3186 if a:line1 && !a:count && !empty(merge)
3191 let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3192 let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3193 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3194 let blame_list = s:tempname()
3195 call writefile([commit, ''], blame_list, 'b')
3196 let blame_in = s:tempname()
3197 silent exe '%write' blame_in
3198 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', '--', path), "\n")
3200 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3201 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3202 let line1 = +matchstr(blame[0], blame_regex)
3203 let line2 = +matchstr(blame[-1], blame_regex)
3205 call s:throw("Can't browse to uncommitted change")
3212 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3215 while commit =~# '^ref: ' && i < 10
3216 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3224 let raw = fugitive#RemoteUrl(remote)
3229 if raw =~# '^https\=://' && s:executable('curl')
3230 if !has_key(s:redirects, raw)
3231 let s:redirects[raw] = matchstr(system('curl -I ' .
3232 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3233 \ 'Location: \zs\S\+\ze/info/refs?')
3235 if len(s:redirects[raw])
3236 let raw = s:redirects[raw]
3242 \ 'repo': fugitive#repo(),
3244 \ 'revision': 'No longer provided',
3251 for Handler in get(g:, 'fugitive_browse_handlers', [])
3252 let url = call(Handler, [copy(opts)])
3259 call s:throw("No Gbrowse handler found for '".raw."'")
3262 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3267 return 'echomsg '.string(url)
3268 elseif exists(':Browse') == 2
3269 return 'echomsg '.string(url).'|Browse '.url
3271 if !exists('g:loaded_netrw')
3272 runtime! autoload/netrw.vim
3274 if exists('*netrw#BrowseX')
3275 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3277 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3281 return 'echoerr v:errmsg'
3285 " Section: Go to file
3287 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3288 function! fugitive#MapCfile(...) abort
3289 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3290 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3291 if !exists('g:fugitive_no_maps')
3292 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3293 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3294 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3295 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3296 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3300 function! s:ContainingCommit() abort
3301 let commit = s:Owner(@%)
3302 return empty(commit) ? 'HEAD' : commit
3305 function! s:NavigateUp(count) abort
3306 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3310 let rev = matchstr(rev, '.*\ze/.\+', '')
3311 elseif rev =~# '.:.'
3312 let rev = matchstr(rev, '^.[^:]*:')
3325 function! fugitive#MapJumps(...) abort
3326 if get(b:, 'fugitive_type', '') ==# 'blob'
3327 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3329 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3332 let nowait = v:version >= 704 ? '<nowait>' : ''
3333 if get(b:, 'fugitive_type', '') ==# 'blob'
3334 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3335 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3336 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3337 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3339 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3340 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3341 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3342 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3344 exe "nnoremap <buffer> <silent>" nowait "- :<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>"
3345 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3346 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3347 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3348 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3349 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3350 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3351 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3352 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3353 nmap <buffer> . <SID>: <Plug><cfile><Home>
3357 function! s:StatusCfile(...) abort
3359 let tree = FugitiveTreeForGitDir(b:git_dir)
3360 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3361 if getline('.') =~# '^.\=\trenamed:.* -> '
3362 return lead . matchstr(getline('.'),' -> \zs.*')
3363 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3364 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3365 elseif getline('.') =~# '^.\=\t.'
3366 return lead . matchstr(getline('.'),'\t\zs.*')
3367 elseif getline('.') =~# ': needs merge$'
3368 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3369 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3371 elseif getline('.') =~# '^\%(. \)\=On branch '
3372 return 'refs/heads/'.getline('.')[12:]
3373 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3374 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3380 function! fugitive#StatusCfile() abort
3381 let file = s:Generate(s:StatusCfile())
3382 return empty(file) ? "\<C-R>\<C-F>" : s:fnameescape(file)
3385 function! s:cfile() abort
3387 let myhash = s:DirRev(@%)[1]
3390 let myhash = fugitive#RevParse(myhash)
3395 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3396 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3399 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3401 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3402 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3404 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3405 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3407 return [treebase . s:sub(getline('.'),'/$','')]
3414 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3415 let ref = matchstr(getline('.'),'\x\{40\}')
3416 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3420 if getline('.') =~# '^ref: '
3421 let ref = strpart(getline('.'),5)
3423 elseif getline('.') =~# '^commit \x\{40\}\>'
3424 let ref = matchstr(getline('.'),'\x\{40\}')
3427 elseif getline('.') =~# '^parent \x\{40\}\>'
3428 let ref = matchstr(getline('.'),'\x\{40\}')
3429 let line = line('.')
3431 while getline(line) =~# '^parent '
3437 elseif getline('.') =~# '^tree \x\{40\}$'
3438 let ref = matchstr(getline('.'),'\x\{40\}')
3439 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3440 let ref = myhash.':'
3444 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3445 let ref = matchstr(getline('.'),'\x\{40\}')
3446 let type = matchstr(getline(line('.')+1),'type \zs.*')
3448 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3449 let ref = s:DirRev(@%)[1]
3451 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3452 let ref = matchstr(getline('.'),'\x\{40\}')
3453 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3455 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3456 let ref = getline('.')[4:]
3458 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3459 let type = getline('.')[0]
3460 let lnum = line('.') - 1
3462 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3463 if getline(lnum) =~# '^[ '.type.']'
3468 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3469 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3470 let dcmds = [offset, 'normal!zv']
3472 elseif getline('.') =~# '^rename from '
3473 let ref = 'a/'.getline('.')[12:]
3474 elseif getline('.') =~# '^rename to '
3475 let ref = 'b/'.getline('.')[10:]
3477 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3478 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3479 let offset = matchstr(getline('.'), '+\zs\d\+')
3481 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3482 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3483 let dcmd = 'Gdiff! +'.offset
3485 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3486 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3487 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3490 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3491 let line = getline(line('.')-1)
3492 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3493 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3496 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3497 let ref = getline('.')
3499 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3500 return [expand('<cword>')]
3515 let prefixes.a = myhash.'^:'
3516 let prefixes.b = myhash.':'
3518 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3520 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3523 if ref ==# '/dev/null'
3525 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3529 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3531 return [ref] + dcmds
3539 function! s:GF(mode) abort
3541 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3543 return 'echoerr v:errmsg'
3546 return 'G' . a:mode .
3547 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3548 \ s:fnameescape(results[0])
3550 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3556 function! fugitive#Cfile() abort
3558 let results = s:cfile()
3560 let cfile = expand('<cfile>')
3561 if &includeexpr =~# '\<v:fname\>'
3562 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3565 elseif len(results) > 1
3566 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3568 return pre . s:fnameescape(s:Generate(results[0]))
3571 " Section: Statusline
3573 function! fugitive#Statusline(...) abort
3574 if !exists('b:git_dir')
3578 let commit = s:DirCommitFile(@%)[1]
3580 let status .= ':' . commit[0:7]
3582 let status .= '('.FugitiveHead(7).')'
3583 return '[Git'.status.']'
3586 function! fugitive#statusline(...) abort
3587 return fugitive#Statusline()
3590 function! fugitive#head(...) abort
3591 if !exists('b:git_dir')
3595 return fugitive#Head(a:0 ? a:1 : 0)
3600 function! fugitive#Foldtext() abort
3601 if &foldmethod !=# 'syntax'
3605 let line_foldstart = getline(v:foldstart)
3606 if line_foldstart =~# '^diff '
3607 let [add, remove] = [-1, -1]
3609 for lnum in range(v:foldstart, v:foldend)
3610 let line = getline(lnum)
3611 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3612 let filename = line[6:-1]
3616 elseif line =~# '^-'
3618 elseif line =~# '^Binary '
3623 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3626 let filename = line_foldstart[5:-1]
3629 return 'Binary: '.filename
3631 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3633 elseif line_foldstart =~# '^# .*:$'
3634 let lines = getline(v:foldstart, v:foldend)
3635 call filter(lines, 'v:val =~# "^#\t"')
3636 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3637 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3638 return line_foldstart.' '.join(lines, ', ')
3643 function! fugitive#foldtext() abort
3644 return fugitive#Foldtext()
3647 augroup fugitive_folding
3649 autocmd User Fugitive
3650 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3651 \ set foldtext=fugitive#Foldtext() |
3655 " Section: Initialization
3657 function! fugitive#Init() abort
3658 if exists('#User#FugitiveBoot')
3660 let [save_mls, &modelines] = [&mls, 0]
3661 doautocmd User FugitiveBoot
3666 if !exists('g:fugitive_no_maps')
3667 call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3668 call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3670 if expand('%:p') =~# ':[\/][\/]'
3671 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3673 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3674 if filereadable(b:git_dir.'/tags')
3675 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3677 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3678 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3682 let [save_mls, &modelines] = [&mls, 0]
3683 call s:define_commands()
3684 doautocmd User Fugitive
3690 function! fugitive#is_git_dir(path) abort
3691 return FugitiveIsGitDir(a:path)
3694 function! fugitive#extract_git_dir(path) abort
3695 return FugitiveExtractGitDir(a:path)
3698 function! fugitive#detect(path) abort
3699 return FugitiveDetect(a:path)