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 let args = ['--git-dir=' . a:dir] + (a:000)
135 return g:fugitive_git_executable . ' ' . join(map(args, 's:shellesc(v:val)'))
138 let s:git_versions = {}
139 function! fugitive#GitVersion(...) abort
140 if !has_key(s:git_versions, g:fugitive_git_executable)
141 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
143 return s:git_versions[g:fugitive_git_executable]
146 let s:commondirs = {}
147 function! fugitive#CommonDir(dir) abort
151 if !has_key(s:commondirs, a:dir)
152 if getfsize(a:dir . '/HEAD') < 10
153 let s:commondirs[a:dir] = ''
154 elseif filereadable(a:dir . '/commondir')
155 let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
156 if dir =~# '^/\|^\a:/'
157 let s:commondirs[a:dir] = dir
159 let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
162 let s:commondirs[a:dir] = a:dir
165 return s:commondirs[a:dir]
168 function! s:Tree(...) abort
169 return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
172 function! s:TreeChomp(...) abort
173 let args = copy(type(a:1) == type([]) ? a:1 : a:000)
174 let dir = a:0 > 1 && type(a:1) == type([]) ? a:2 : b:git_dir
175 let tree = s:Tree(dir)
178 let args = ['--git-dir=' . dir] + args
179 elseif s:cpath(tree) !=# s:cpath(getcwd())
180 if fugitive#GitVersion() =~# '^[01]\.'
181 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
183 let args = ['-C', tree] + args
186 return s:sub(s:System(pre . g:fugitive_git_executable . ' ' .
187 \ join(map(args, 's:shellesc(v:val)'))), '\n$', '')
190 function! fugitive#Prepare(cmd, ...) abort
191 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
192 let tree = s:Tree(dir)
193 let args = type(a:cmd) == type([]) ? join(map(copy(a:cmd), 's:shellesc(v:val)')) : a:cmd
196 let args = s:shellesc('--git-dir=' . dir) . ' ' . args
197 elseif fugitive#GitVersion() =~# '^[01]\.'
198 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
200 let args = '-C ' . s:shellesc(tree) . ' ' . args
202 return pre . g:fugitive_git_executable . ' ' . args
205 function! fugitive#RevParse(rev, ...) abort
206 let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev))[0:-2]
207 if !v:shell_error && hash =~# '^\x\{40\}$'
210 call s:throw('rev-parse '.a:rev.': '.hash)
213 function! fugitive#Config(name, ...) abort
214 let cmd = s:Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), 'config', '--get', a:name)
215 let out = matchstr(system(cmd), "[^\r\n]*")
216 return v:shell_error ? '' : out
219 function! s:Remote(dir) abort
220 let head = FugitiveHead(0, a:dir)
221 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
223 while remote ==# '.' && i > 0
224 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
225 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
228 return remote =~# '\.\=$' ? 'origin' : remote
231 function! fugitive#RemoteUrl(...) abort
232 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
233 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
234 if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
235 return fugitive#Config('remote.' . remote . '.url')
237 let cmd = s:Prepare(dir, 'remote', 'get-url', remote)
238 let out = substitute(system(cmd), "\n$", '', '')
239 return v:shell_error ? '' : out
242 function! s:recall() abort
243 let rev = s:sub(fugitive#buffer().rev(), '^/', '')
245 return matchstr(getline('.'),'^.\=\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
246 elseif fugitive#buffer().type('tree')
247 let file = matchstr(getline('.'), '\t\zs.*')
248 if empty(file) && line('.') > 2
249 let file = s:sub(getline('.'), '/$', '')
251 if !empty(file) && rev !~# ':$'
252 return rev . '/' . file
260 function! s:map(mode, lhs, rhs, ...) abort
261 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
264 let keys = get(g:, a:mode.'remap', {})
265 if type(keys) == type([])
269 if has_key(keys, head)
270 let head = keys[head]
276 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
277 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
279 if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
280 exe a:mode.'map <buffer>' flags head.tail a:rhs
282 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
283 \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
288 function! s:add_methods(namespace, method_names) abort
289 for name in a:method_names
290 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
295 function! s:command(definition) abort
296 let s:commands += [a:definition]
299 function! s:define_commands() abort
300 for command in s:commands
301 exe 'command! -buffer '.command
305 let s:abstract_prototype = {}
307 " Section: Repository
309 let s:repo_prototype = {}
312 function! fugitive#repo(...) abort
313 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
315 if has_key(s:repos, dir)
316 let repo = get(s:repos, dir)
318 let repo = {'git_dir': dir}
319 let s:repos[dir] = repo
321 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
323 call s:throw('not a git repository: '.expand('%:p'))
326 function! s:repo_dir(...) dict abort
327 return join([self.git_dir]+a:000,'/')
330 function! s:repo_tree(...) dict abort
331 let dir = s:Tree(self.git_dir)
333 call s:throw('no work tree')
335 return join([dir]+a:000,'/')
339 function! s:repo_bare() dict abort
340 if self.dir() =~# '/\.git$'
343 return s:Tree(self.git_dir) ==# ''
347 function! s:repo_translate(object, ...) dict abort
348 let rev = substitute(a:object, '[:/]\zs\.\%(/\+\|$\)', '', 'g')
349 let dir = self.git_dir
350 let tree = s:Tree(dir)
351 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
352 if rev =~# '^/\=\.git$' && empty(tree)
354 elseif rev =~# '^/\=\.git/'
355 let f = s:sub(rev, '^/=\.git', '')
356 let cdir = fugitive#CommonDir(dir)
357 if cdir !=# dir && (f =~# '^/\%(config\|info\|hooks\|objects\|refs\|worktrees\)' || !filereadable(f) && filereadable(cdir . f))
362 elseif rev =~# '^/\.$\|^:/$'
364 elseif rev =~# '^\.\=\%(/\|$\)'
365 let f = base . substitute(rev, '^\.', '', '')
366 elseif rev =~# '^:[0-3]:/\@!'
367 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
369 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
370 let f = fnamemodify($GIT_INDEX_FILE, ':p')
372 let f = dir . '/index'
374 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
375 let f = base . '/' . matchstr(rev, ')\zs.*')
376 elseif rev =~# '^:/\@!'
377 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
379 if rev =~# 'HEAD\|^refs/' && rev !~# ':'
380 let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
381 if filereadable(cdir . '/' . rev)
382 let f = simplify(cdir . '/' . rev)
386 let commit = substitute(matchstr(rev,'^[^:]\+\|^:.*'), '^@\%($|[^~]\)\@=', 'HEAD', '')
387 let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
388 if commit !~# '^[0-9a-f]\{40\}$'
389 let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit))[0:-2]
390 let commit = v:shell_error ? '' : commit
393 let f = 'fugitive://' . dir . '//' . commit . file
395 let f = base . '/' . rev
399 return a:0 && a:1 ? s:PlatformSlash(f) : f
402 function! s:Generate(rev, ...) abort
403 let repo = fugitive#repo(a:0 ? a:1 : b:git_dir)
404 if a:rev =~# '^\%(\a\+:\)\=/' && getftime(a:rev) >= 0 && getftime(repo.tree() . a:rev) < 0
405 return s:PlatformSlash(a:rev)
407 return repo.translate(a:rev, 1)
410 function! s:repo_head(...) dict abort
411 if !filereadable(self.dir('HEAD'))
414 let head = readfile(self.dir('HEAD'))[0]
417 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
418 elseif head =~# '^\x\{40\}$'
419 " truncate hash to a:1 characters if we're in detached head mode
420 let len = a:0 ? a:1 : 0
421 let branch = len < 0 ? head : len ? head[0:len-1] : ''
429 call s:add_methods('repo',['dir','tree','bare','translate','head'])
431 function! s:repo_git_command(...) dict abort
432 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
433 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
436 function! s:repo_git_chomp(...) dict abort
437 return s:sub(s:System(call('s:Prepare', [self.git_dir] + a:000)),'\n$','')
440 function! s:repo_git_chomp_in_tree(...) dict abort
441 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
444 execute cd s:fnameescape(self.tree())
445 return call(self.git_chomp, a:000, self)
447 execute cd s:fnameescape(dir)
451 function! s:repo_rev_parse(rev) dict abort
452 return fugitive#RevParse(a:rev, self.git_dir)
455 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
457 function! s:repo_superglob(base) dict abort
458 return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
461 call s:add_methods('repo',['superglob'])
463 function! s:repo_config(name) dict abort
464 return fugitive#Config(a:name, self.git_dir)
467 function! s:repo_user() dict abort
468 let username = self.config('user.name')
469 let useremail = self.config('user.email')
470 return username.' <'.useremail.'>'
473 call s:add_methods('repo',['config', 'user'])
477 function! s:DirCommitFile(path) abort
478 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
485 function! s:DirRev(url) abort
486 let [dir, commit, file] = s:DirCommitFile(a:url)
487 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
490 function! fugitive#Real(url) abort
491 let [dir, commit, file] = s:DirCommitFile(a:url)
493 let tree = s:Tree(dir)
494 return s:PlatformSlash((len(tree) ? tree : dir) . file)
496 let url = len(a:url) ? fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??')) : ''
497 if url =~# '^[\\/]\|^\a:[\\/]'
498 return s:PlatformSlash(url)
503 function! fugitive#Path(url, ...) abort
504 if !a:0 || empty(a:url)
505 return fugitive#Real(a:url)
507 let url = s:Slash(fnamemodify(a:url, ':p'))
508 if url =~# '/$' && s:Slash(a:url) !~# '/$'
511 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
512 let tree = s:Tree(dir)
513 let [argdir, commit, file] = s:DirCommitFile(a:url)
514 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
516 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
517 let file = '/.git'.url[strlen(dir) : -1]
518 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
519 let file = url[len(tree) : -1]
520 elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
523 if empty(file) && a:1 =~# '^$\|^[.:]/$'
524 return s:Slash(fugitive#Real(a:url))
526 return substitute(file, '^/', a:1, '')
529 function! s:RemoveDot(path, ...) abort
533 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
534 let cdir = fugitive#CommonDir(dir)
535 if len(filter(['', '/tags', '/heads', '/remotes'], 'getftime(cdir . "/refs" . v:val . a:path[1:-1]) >= 0')) ||
536 \ a:path =~# 'HEAD$' && filereadable(dir . a:path[1:-1]) ||
537 \ a:path =~# '^\./refs/' && filereadable(cdir . a:path[1:-1])
543 function! s:Expand(rev) abort
544 if a:rev =~# '^:[0-3]$'
545 let file = a:rev . s:Relative(':')
546 elseif a:rev =~# '^-'
547 let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
548 elseif a:rev =~# '^@{'
549 let file = 'HEAD'.a:rev. s:Relative(':')
550 elseif a:rev =~# '^[~^]/\@!'
551 let commit = substitute(s:DirCommitFile(@%), '^\d\=$', 'HEAD', '')
552 let file = commit . a:rev . s:Relative(':')
556 return s:sub(substitute(file,
557 \ '\([%#]\)$\|\\\([[:punct:]]\)','\=len(submatch(2)) ? submatch(2) : fugitive#Path(expand(submatch(1)), "./", dir)','g'),
561 function! s:ShellExpand(cmd) abort
562 return substitute(a:cmd, '\\\@<![%#]:\@!', '\=s:RemoveDot(fugitive#Path(expand(submatch(0)), "./"))', 'g')
567 function! s:TreeInfo(dir, commit) abort
568 let git = s:Prepare(a:dir)
569 if a:commit =~# '^:\=[0-3]$'
570 let index = get(s:indexes, a:dir, [])
571 let newftime = getftime(a:dir . '/index')
572 if get(index, 0, -1) < newftime
573 let out = system(git . ' ls-files --stage')
574 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
578 for line in split(out, "\n")
579 let [info, filename] = split(line, "\t")
580 let [mode, sha, stage] = split(info, '\s\+')
581 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
582 while filename =~# '/'
583 let filename = substitute(filename, '/[^/]*$', '', '')
584 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
588 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
589 elseif a:commit =~# '^\x\{40\}$'
590 if !has_key(s:trees, a:dir)
591 let ftime = +system(git . ' log -1 --pretty=format:%ct ' . a:commit)
593 let s:trees[a:dir] = [{}, -1]
594 return s:trees[a:dir]
596 let s:trees[a:dir] = [{}, +ftime]
597 let out = system(git . ' ls-tree -rtl --full-name ' . a:commit)
599 return s:trees[a:dir]
601 for line in split(out, "\n")
602 let [info, filename] = split(line, "\t")
603 let [mode, type, sha, size] = split(info, '\s\+')
604 let s:trees[a:dir][0][filename] = [ftime, mode, type, sha, +size, filename]
607 return s:trees[a:dir]
612 function! s:PathInfo(url) abort
613 let [dir, commit, file] = s:DirCommitFile(a:url)
614 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
615 return [-1, '000000', '', '', -1]
617 let path = substitute(file[1:-1], '/*$', '', '')
618 let [tree, ftime] = s:TreeInfo(dir, commit)
619 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
620 if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
621 return [-1, '000000', '', '', -1]
627 function! fugitive#simplify(url) abort
628 let [dir, commit, file] = s:DirCommitFile(a:url)
632 if file =~# '/\.\.\%(/\|$\)'
633 let tree = s:Tree(dir)
635 let path = simplify(tree . file)
636 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
637 return s:PlatformSlash(path)
641 return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
644 function! fugitive#resolve(url) abort
645 let url = fugitive#simplify(a:url)
646 if url =~? '^fugitive:'
653 function! fugitive#getftime(url) abort
654 return s:PathInfo(a:url)[0]
657 function! fugitive#getfsize(url) abort
658 let entry = s:PathInfo(a:url)
659 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
660 let dir = s:DirCommitFile(a:url)[0]
661 let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
662 let entry[4] = v:shell_error ? -1 : size
667 function! fugitive#getftype(url) abort
668 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
671 function! fugitive#filereadable(url) abort
672 return s:PathInfo(a:url)[2] ==# 'blob'
675 function! fugitive#filewritable(url) abort
676 let [dir, commit, file] = s:DirCommitFile(a:url)
677 if commit !~# '^\d$' || !filewritable(dir . '/index')
680 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
683 function! fugitive#isdirectory(url) abort
684 return s:PathInfo(a:url)[2] ==# 'tree'
687 function! fugitive#getfperm(url) abort
688 let [dir, commit, file] = s:DirCommitFile(a:url)
689 let perm = getfperm(dir)
690 let fperm = s:PathInfo(a:url)[1]
691 if fperm ==# '040000'
695 let perm = tr(perm, 'x', '-')
698 let perm = tr(perm, 'rw', '--')
701 let perm = tr(perm, 'w', '-')
703 return perm ==# '---------' ? '' : perm
706 function! fugitive#setfperm(url, perm) abort
707 let [dir, commit, file] = s:DirCommitFile(a:url)
708 let entry = s:PathInfo(a:url)
709 let perm = fugitive#getfperm(a:url)
710 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
711 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
714 call system(s:Prepare(dir, 'update-index', '--index-info'),
715 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
716 return v:shell_error ? -1 : 0
719 function! s:TempCmd(out, cmd) abort
722 let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
723 let redir = ' > ' . a:out
725 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
726 return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
727 elseif &shell =~# 'fish'
728 return s:System(' begin;' . prefix . cmd . redir . ';end ')
730 return s:System(' (' . prefix . cmd . redir . ') ')
735 if !exists('s:blobdirs')
738 function! s:BlobTemp(url) abort
739 let [dir, commit, file] = s:DirCommitFile(a:url)
743 if !has_key(s:blobdirs, dir)
744 let s:blobdirs[dir] = s:tempname()
746 let tempfile = s:PlatformSlash(s:blobdirs[dir] . '/' . commit . file)
747 let tempparent = fnamemodify(tempfile, ':h')
748 if !isdirectory(tempparent)
749 call mkdir(tempparent, 'p')
751 if commit =~# '^\d$' || !filereadable(tempfile)
752 let rev = s:DirRev(a:url)[1]
753 let command = s:Prepare(dir, 'cat-file', 'blob', rev)
754 call s:TempCmd(tempfile, command)
756 call delete(tempfile)
763 function! fugitive#readfile(url, ...) abort
764 let entry = s:PathInfo(a:url)
765 if entry[2] !=# 'blob'
768 let temp = s:BlobTemp(a:url)
772 return call('readfile', [temp] + a:000)
775 function! fugitive#writefile(lines, url, ...) abort
776 let url = type(a:url) ==# type('') ? a:url : ''
777 let [dir, commit, file] = s:DirCommitFile(url)
778 let entry = s:PathInfo(url)
779 if commit =~# '^\d$' && entry[2] !=# 'tree'
780 let temp = s:tempname()
781 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
782 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
784 call call('writefile', [a:lines, temp] + a:000)
785 let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
786 let mode = len(entry[1]) ? entry[1] : '100644'
787 if !v:shell_error && hash =~# '^\x\{40\}$'
788 call system(s:Prepare(dir, 'update-index', '--index-info'),
789 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
795 return call('writefile', [a:lines, a:url] + a:000)
798 let s:globsubs = {'*': '[^/]*', '**': '.*', '**/': '\%(.*/\)\=', '?': '[^/]'}
799 function! fugitive#glob(url, ...) abort
800 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
801 let append = matchstr(glob, '/*$')
802 let glob = substitute(glob, '/*$', '', '')
803 let pattern = '^' . substitute(glob[1:-1], '\*\*/\=\|[.?*\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g') . '$'
805 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
806 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
809 let files = items(s:TreeInfo(dir, commit)[0])
811 call filter(files, 'v:val[1][2] ==# "tree"')
813 call map(files, 'v:val[0]')
814 call filter(files, 'v:val =~# pattern')
815 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
817 call map(files, 's:PlatformSlash(prepend . v:val . append)')
818 call extend(results, files)
823 return join(results, "\n")
827 function! fugitive#delete(url, ...) abort
828 let [dir, commit, file] = s:DirCommitFile(a:url)
829 if a:0 && len(a:1) || commit !~# '^\d$'
832 let entry = s:PathInfo(a:url)
833 if entry[2] !=# 'blob'
836 call system(s:Prepare(dir, 'update-index', '--index-info'),
837 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
838 return v:shell_error ? -1 : 0
841 let s:buffer_prototype = {}
843 function! fugitive#buffer(...) abort
844 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
845 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
846 if buffer.getvar('git_dir') !=# ''
849 call s:throw('not a git repository: '.bufname(buffer['#']))
852 function! s:buffer_getvar(var) dict abort
853 return getbufvar(self['#'],a:var)
856 function! s:buffer_getline(lnum) dict abort
857 return get(getbufline(self['#'], a:lnum), 0, '')
860 function! s:buffer_repo() dict abort
861 return fugitive#repo(self.getvar('git_dir'))
864 function! s:buffer_type(...) dict abort
865 if !empty(self.getvar('fugitive_type'))
866 let type = self.getvar('fugitive_type')
867 elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
869 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
871 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
873 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
875 elseif isdirectory(self.spec())
876 let type = 'directory'
877 elseif self.spec() == ''
883 return !empty(filter(copy(a:000),'v:val ==# type'))
891 function! s:buffer_spec() dict abort
892 let bufname = bufname(self['#'])
894 for i in split(bufname,'[^:]\zs\\')
895 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
897 return s:Slash(fnamemodify(retval,':p'))
902 function! s:buffer_spec() dict abort
903 let bufname = bufname(self['#'])
904 return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
909 function! s:buffer_name() dict abort
913 function! s:buffer_commit() dict abort
914 return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
917 function! s:buffer_relative(...) dict abort
918 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
920 let rev = s:sub(rev,'\w*','')
921 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
922 \ s:cpath(self.repo().dir() . '/')
923 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
924 elseif !self.repo().bare() &&
925 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
926 \ s:cpath(self.repo().tree() . '/')
927 let rev = self.spec()[strlen(self.repo().tree()) : -1]
929 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
932 function! s:Relative(...) abort
933 return fugitive#Path(@%, a:0 ? a:1 : './')
936 function! s:buffer_path(...) dict abort
938 return self.relative(a:1)
940 return self.relative()
943 function! s:buffer_rev() dict abort
944 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
946 return ':'.rev[0].':'.rev[2:-1]
948 return s:sub(rev,'/',':')
949 elseif self.spec() =~ '\.git/index$'
951 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
952 return self.spec()[strlen(self.repo().dir())+1 : -1]
954 return self.relative('/')
958 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative','rev'])
960 " Section: Completion
962 function! s:GlobComplete(lead, pattern) abort
964 let results = glob(a:lead . a:pattern, 0, 1)
966 let results = split(glob(a:lead . a:pattern), "\n")
968 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
969 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
973 function! fugitive#PathComplete(base, ...) abort
974 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
975 let tree = FugitiveTreeForGitDir(dir) . '/'
976 let strip = '^\%(:\=/\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)\%(\./\)\='
977 let base = substitute(a:base, strip, '', '')
978 if base =~# '^\.git/'
979 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
980 let matches = s:GlobComplete(dir . '/', pattern)
981 let cdir = fugitive#CommonDir(dir)
982 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
983 call extend(matches, s:GlobComplete(cdir . '/', pattern))
986 call map(matches, "'.git/' . v:val")
988 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
992 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
996 function! fugitive#Complete(base, ...) abort
997 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
998 let tree = s:Tree(dir) . '/'
999 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1001 if a:base =~# '^refs/'
1002 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1003 elseif a:base !~# '^\.\=/\|^:('
1004 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1005 let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1006 if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1007 let heads += ["stash"]
1008 let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1010 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1011 let results += heads
1013 call map(results, 's:fnameescape(v:val)')
1015 let results += fugitive#PathComplete(a:base, dir)
1019 elseif a:base =~# '^:'
1020 let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1021 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1022 if a:base !~# '^:[0-3]\%(:\|$\)'
1023 call filter(entries,'v:val[1] == "0"')
1024 call map(entries,'v:val[2:-1]')
1026 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1027 return map(entries, 's:fnameescape(v:val)')
1030 let tree = matchstr(a:base,'.*[:/]')
1031 let entries = split(s:TreeChomp(['ls-tree',tree], dir),"\n")
1032 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1033 call map(entries,'tree.s:sub(v:val,".*\t","")')
1034 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1035 return map(entries, 's:fnameescape(v:val)')
1039 " Section: File access
1041 function! s:ReplaceCmd(cmd) abort
1042 let tmp = tempname()
1043 let err = s:TempCmd(tmp, a:cmd)
1045 call s:throw((len(err) ? err : filereadable(tmp) ? join(readfile(tmp), ' ') : 'unknown error running ' . a:cmd))
1047 let fn = expand('%:p')
1048 silent exe 'doau BufReadPre '.s:fnameescape(fn)
1049 silent exe 'keepalt file '.tmp
1051 silent noautocmd edit!
1054 silent exe 'keepalt file '.s:fnameescape(fn)
1055 catch /^Vim\%((\a\+)\)\=:E302:/
1058 if fnamemodify(bufname('$'), ':p') ==# tmp
1059 silent execute 'bwipeout '.bufnr('$')
1061 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1065 function! fugitive#BufReadStatus() abort
1066 let amatch = s:Slash(expand('%:p'))
1067 if !exists('b:fugitive_display_format')
1068 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1070 let b:fugitive_display_format = b:fugitive_display_format % 2
1071 let b:fugitive_type = 'index'
1073 let dir = fnamemodify(amatch, ':h')
1074 setlocal noro ma nomodeline
1076 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1078 let old_index = $GIT_INDEX_FILE
1080 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(amatch).' '
1083 if b:fugitive_display_format
1084 let cmd = ['ls-files', '--stage']
1085 elseif fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.'
1086 let cmd = ['status']
1089 \ '-c', 'status.displayCommentPrefix=true',
1090 \ '-c', 'color.status=false',
1091 \ '-c', 'status.short=false',
1094 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1096 let cmd_str = prefix . call('s:Prepare', [dir] + cmd)
1098 if exists('old_index')
1099 let $GIT_INDEX_FILE = amatch
1101 execute cd s:fnameescape(s:Tree(dir))
1102 call s:ReplaceCmd(cmd_str)
1104 if exists('old_index')
1105 let $GIT_INDEX_FILE = old_index
1107 execute cd s:fnameescape(cwd)
1109 if b:fugitive_display_format
1110 if &filetype !=# 'git'
1115 if &filetype !=# 'gitcommit'
1116 set filetype=gitcommit
1118 set foldtext=fugitive#Foldtext()
1120 setlocal readonly nomodifiable nomodified noswapfile
1121 if &bufhidden ==# ''
1122 setlocal bufhidden=delete
1124 call fugitive#MapJumps()
1127 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1128 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1129 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
1130 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
1131 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1132 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1133 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1134 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>
1135 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1136 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1137 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1138 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1139 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1140 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1141 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1142 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1143 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1144 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1145 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1146 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1147 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1148 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1149 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1150 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1151 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1152 nnoremap <buffer> <silent> r :<C-U>edit<CR>
1153 nnoremap <buffer> <silent> R :<C-U>edit<CR>
1154 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1155 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
1156 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1158 return 'echoerr v:errmsg'
1162 function! fugitive#FileReadCmd(...) abort
1163 let amatch = a:0 ? a:1 : expand('<amatch>')
1164 let [dir, rev] = s:DirRev(amatch)
1165 let line = a:0 > 1 ? a:2 : line("'[")
1167 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1170 let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev)
1172 let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1174 return line . 'read !' . escape(cmd, '!#%')
1177 function! fugitive#FileWriteCmd(...) abort
1178 let tmp = tempname()
1179 let amatch = a:0 ? a:1 : expand('<amatch>')
1180 let autype = a:0 > 1 ? 'Buf' : 'File'
1181 if exists('#' . autype . 'WritePre')
1182 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1185 let [dir, commit, file] = s:DirCommitFile(amatch)
1186 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1187 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1189 silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin').' > '.tmp
1190 let sha1 = readfile(tmp)[0]
1191 let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', file[1:-1])), '^\d\+')
1193 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1195 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1196 call writefile([info],tmp)
1198 let error = s:System('type '.s:gsub(tmp,'/','\\').'|'.s:Prepare(dir, 'update-index', '--index-info'))
1200 let error = s:System(s:Prepare(dir, 'update-index', '--index-info').' < '.tmp)
1202 if v:shell_error == 0
1204 if exists('#' . autype . 'WritePost')
1205 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1207 call fugitive#ReloadStatus()
1210 return 'echoerr '.string('fugitive: '.error)
1217 function! fugitive#BufReadCmd(...) abort
1218 let amatch = a:0 ? a:1 : expand('<amatch>')
1220 let [dir, rev] = s:DirRev(amatch)
1222 return 'echo "Invalid Fugitive URL"'
1225 let b:fugitive_type = 'stage'
1227 let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1228 if v:shell_error && rev =~# '^:0'
1229 let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1230 let b:fugitive_type = 'tree'
1233 unlet b:fugitive_type
1235 let &readonly = !filewritable(dir . '/index')
1236 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1238 setlocal readonly nomodifiable
1241 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1242 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1244 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1245 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1249 if b:fugitive_type !=# 'blob'
1253 setlocal noreadonly modifiable
1254 let pos = getpos('.')
1255 silent keepjumps %delete_
1259 if b:fugitive_type ==# 'tree'
1260 let b:fugitive_display_format = b:fugitive_display_format % 2
1261 if b:fugitive_display_format
1262 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1265 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev))[0:-2]
1267 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1269 elseif b:fugitive_type ==# 'tag'
1270 let b:fugitive_display_format = b:fugitive_display_format % 2
1271 if b:fugitive_display_format
1272 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1274 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1276 elseif b:fugitive_type ==# 'commit'
1277 let b:fugitive_display_format = b:fugitive_display_format % 2
1278 if b:fugitive_display_format
1279 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1281 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])
1282 keepjumps call search('^parent ')
1283 if getline('.') ==# 'parent '
1284 silent keepjumps delete_
1286 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1288 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1290 silent keepjumps delete_
1292 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1295 elseif b:fugitive_type ==# 'stage'
1296 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1297 elseif b:fugitive_type ==# 'blob'
1298 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1302 keepjumps call setpos('.',pos)
1303 setlocal nomodified noswapfile
1305 setlocal nomodifiable
1307 let &modifiable = b:fugitive_type !=# 'tree'
1309 let &readonly = !&modifiable || !filewritable(dir . '/index')
1310 if &bufhidden ==# ''
1311 setlocal bufhidden=delete
1313 if b:fugitive_type !=# 'blob'
1314 setlocal filetype=git foldmethod=syntax
1315 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1316 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1318 call fugitive#MapJumps()
1324 return 'echoerr v:errmsg'
1328 function! fugitive#BufWriteCmd(...) abort
1329 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1332 function! fugitive#SourceCmd(...) abort
1333 let amatch = a:0 ? a:1 : expand('<amatch>')
1334 let temp = s:BlobTemp(amatch)
1336 return 'noautocmd source ' . s:fnameescape(amatch)
1338 if !exists('g:virtual_scriptnames')
1339 let g:virtual_scriptnames = {}
1341 let g:virtual_scriptnames[temp] = amatch
1342 return 'source ' . s:fnameescape(temp)
1345 " Section: Temp files
1347 if !exists('s:temp_files')
1348 let s:temp_files = {}
1351 function! s:SetupTemp(file) abort
1352 if has_key(s:temp_files, s:cpath(a:file))
1353 let dict = s:temp_files[s:cpath(a:file)]
1354 let b:git_dir = dict.dir
1355 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1356 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1357 let &l:filetype = dict.filetype
1359 setlocal foldmarker=<<<<<<<,>>>>>>>
1360 setlocal bufhidden=delete nobuflisted
1361 setlocal buftype=nowrite
1362 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1363 if getline(1) !~# '^diff '
1364 setlocal nomodifiable
1366 call FugitiveDetect(a:file)
1371 augroup fugitive_temp
1373 autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1378 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1380 function! s:Git(bang, mods, args) abort
1382 return s:Edit('edit', 1, a:mods, a:args)
1384 let git = s:UserCommand()
1385 if has('gui_running') && !has('win32')
1386 let git .= ' --no-pager'
1388 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1389 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1392 let after = '|call fugitive#ReloadStatus()' . after
1394 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1400 execute 'lcd' fnameescape(tree)
1401 let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1402 return 'exe ' . string('terminal ' . exec) . after
1404 let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1405 if s:cpath(tree) !=# s:cpath(getcwd())
1406 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1407 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1413 let s:exec_paths = {}
1414 function! s:Subcommands() abort
1415 if !has_key(s:exec_paths, g:fugitive_git_executable)
1416 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1418 let exec_path = s:exec_paths[g:fugitive_git_executable]
1419 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1423 function! s:Aliases() abort
1424 if !has_key(s:aliases, b:git_dir)
1425 let s:aliases[b:git_dir] = {}
1426 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1427 for line in v:shell_error ? [] : lines
1428 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1431 return s:aliases[b:git_dir]
1434 function! s:GitComplete(A, L, P) abort
1435 let pre = strpart(a:L, 0, a:P)
1436 if pre !~# ' [[:alnum:]-]\+ '
1437 let cmds = s:Subcommands()
1438 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1439 elseif pre =~# ' -- '
1440 return fugitive#PathComplete(a:A)
1442 return fugitive#Complete(a:A, a:L, a:P)
1446 " Section: Gcd, Glcd
1448 function! s:DirComplete(A, L, P) abort
1449 let base = s:sub(a:A,'^/','')
1450 let matches = split(glob(s:Tree() . '/' . s:gsub(base,'/','*&').'*/'),"\n")
1451 call map(matches,'s:Slash(v:val[ strlen(s:Tree())+(a:A !~ "^/") : -1 ])')
1455 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape((empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . <q-args>)")
1456 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape((empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . <q-args>)")
1460 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1461 augroup fugitive_status
1464 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1465 autocmd BufDelete term://* call fugitive#ReloadStatus()
1469 function! s:Status(bang, count, mods) abort
1471 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1473 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1474 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1476 return 'echoerr v:errmsg'
1481 function! fugitive#ReloadStatus() abort
1482 if exists('s:reloading_status')
1486 let s:reloading_status = 1
1487 let mytab = tabpagenr()
1488 for tab in [mytab] + range(1,tabpagenr('$'))
1489 for winnr in range(1,tabpagewinnr(tab,'$'))
1490 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1491 execute 'tabnext '.tab
1493 execute winnr.'wincmd w'
1494 let restorewinnr = 1
1498 call fugitive#BufReadStatus()
1501 if exists('restorewinnr')
1504 execute 'tabnext '.mytab
1510 unlet! s:reloading_status
1514 function! fugitive#reload_status() abort
1515 return fugitive#ReloadStatus()
1518 function! s:stage_info(lnum) abort
1519 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1521 if has('multi_byte_encoding')
1522 let colon = '\%(:\|\%uff1a\)'
1526 while lnum && getline(lnum) !~# colon.'$'
1531 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1532 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1533 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1534 return [filename, 'untracked']
1535 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1536 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1537 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1538 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1540 return ['', 'unknown']
1544 function! s:StageNext(count) abort
1545 for i in range(a:count)
1546 call search('^.\=\t.*','W')
1551 function! s:StagePrevious(count) abort
1552 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1553 return 'CtrlP '.fnameescape(s:Tree())
1555 for i in range(a:count)
1556 call search('^.\=\t.*','Wbe')
1562 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1564 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1565 if f !=# '' | let jump = f | endif
1566 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1567 if f !=# '' | let jump = f | endif
1571 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1574 function! s:StageUndo() abort
1575 let [filename, section] = s:stage_info(line('.'))
1579 let hash = s:TreeChomp('hash-object', '-w', filename)
1581 if section ==# 'untracked'
1582 call s:TreeChomp('clean', '-f', '--', filename)
1583 elseif section ==# 'unmerged'
1584 call s:TreeChomp('rm', '--', filename)
1585 elseif section ==# 'unstaged'
1586 call s:TreeChomp('checkout', '--', filename)
1588 call s:TreeChomp('checkout', 'HEAD', '--', filename)
1590 call s:StageReloadSeek(filename, line('.'), line('.'))
1592 return 'checktime|redraw|echomsg ' .
1593 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1597 function! s:StageDiff(diff) abort
1598 let [filename, section] = s:stage_info(line('.'))
1599 if filename ==# '' && section ==# 'staged'
1600 return 'Git! diff --no-ext-diff --cached'
1601 elseif filename ==# ''
1602 return 'Git! diff --no-ext-diff'
1603 elseif filename =~# ' -> '
1604 let [old, new] = split(filename,' -> ')
1605 execute 'Gedit '.s:fnameescape(':0:'.new)
1606 return a:diff.' HEAD:'.s:fnameescape(old)
1607 elseif section ==# 'staged'
1608 execute 'Gedit '.s:fnameescape(':0:'.filename)
1611 execute 'Gedit '.s:fnameescape('/'.filename)
1616 function! s:StageDiffEdit() abort
1617 let [filename, section] = s:stage_info(line('.'))
1618 let arg = (filename ==# '' ? '.' : filename)
1619 if section ==# 'staged'
1620 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1621 elseif section ==# 'untracked'
1622 call s:TreeChomp('add','--intent-to-add',arg)
1626 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1627 call search(':$','W')
1630 call s:StageReloadSeek(arg,line('.'),line('.'))
1634 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1638 function! s:StageToggle(lnum1,lnum2) abort
1639 if a:lnum1 == 1 && a:lnum2 == 1
1640 return 'Gedit /.git|call search("^index$", "wc")'
1644 for lnum in range(a:lnum1,a:lnum2)
1645 let [filename, section] = s:stage_info(lnum)
1646 if getline('.') =~# ':$'
1647 if section ==# 'staged'
1648 call s:TreeChomp('reset','-q')
1651 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1652 call search(':$','W')
1655 elseif section ==# 'unstaged'
1656 call s:TreeChomp('add','-u')
1659 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1660 call search(':$','W')
1664 call s:TreeChomp('add','.')
1667 call search(':$','W')
1675 if section ==# 'staged'
1676 if filename =~ ' -> '
1677 let files_to_unstage = split(filename,' -> ')
1679 let files_to_unstage = [filename]
1681 let filename = files_to_unstage[-1]
1682 let cmd = ['reset','-q','--'] + files_to_unstage
1683 elseif getline(lnum) =~# '^.\=\tdeleted:'
1684 let cmd = ['rm','--',filename]
1685 elseif getline(lnum) =~# '^.\=\tmodified:'
1686 let cmd = ['add','--',filename]
1688 let cmd = ['add','-A','--',filename]
1690 if !exists('first_filename')
1691 let first_filename = filename
1693 let output .= call('s:TreeChomp', cmd)."\n"
1695 if exists('first_filename')
1696 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1698 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1700 return 'echoerr v:errmsg'
1705 function! s:StagePatch(lnum1,lnum2) abort
1709 for lnum in range(a:lnum1,a:lnum2)
1710 let [filename, section] = s:stage_info(lnum)
1711 if getline('.') =~# ':$' && section ==# 'staged'
1712 return 'Git reset --patch'
1713 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1714 return 'Git add --patch'
1715 elseif getline('.') =~# ':$' && section ==# 'untracked'
1716 return 'Git add -N .'
1717 elseif filename ==# ''
1720 if !exists('first_filename')
1721 let first_filename = filename
1724 if filename =~ ' -> '
1725 let reset += [split(filename,' -> ')[1]]
1726 elseif section ==# 'staged'
1727 let reset += [filename]
1728 elseif getline(lnum) !~# '^.\=\tdeleted:'
1729 let add += [filename]
1734 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1737 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1739 if exists('first_filename')
1743 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1746 return 'echoerr v:errmsg'
1753 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1755 function! s:Commit(mods, args, ...) abort
1756 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1757 let dir = a:0 ? a:1 : b:git_dir
1758 let tree = s:Tree(dir)
1759 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1761 let msgfile = dir . '/COMMIT_EDITMSG'
1762 let outfile = tempname()
1763 let errorfile = tempname()
1765 let guioptions = &guioptions
1767 if &guioptions =~# '!'
1768 setglobal guioptions-=!
1770 execute cd s:fnameescape(tree)
1773 let old_editor = $GIT_EDITOR
1774 let $GIT_EDITOR = 'false'
1776 let command = 'env GIT_EDITOR=false '
1778 let args = s:ShellExpand(a:args)
1779 let command .= s:UserCommand() . ' commit ' . args
1781 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1782 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1783 noautocmd execute '!'.command.' 2> '.errorfile
1785 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1787 let error = v:shell_error
1789 execute cd s:fnameescape(cwd)
1790 let &guioptions = guioptions
1792 if !has('gui_running')
1796 if filereadable(outfile)
1797 for line in readfile(outfile)
1803 let errors = readfile(errorfile)
1804 let error = get(errors,-2,get(errors,-1,'!'))
1805 if error =~# 'false''\=\.$'
1806 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1807 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1808 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1810 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1811 let args = '-F '.s:shellesc(msgfile).' '.args
1812 if args !~# '\%(^\| \)--cleanup\>'
1813 let args = '--cleanup=strip '.args
1815 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1816 execute mods 'keepalt edit' s:fnameescape(msgfile)
1817 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1818 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1819 elseif get(b:, 'fugitive_type', '') ==# 'index'
1820 execute mods 'keepalt edit' s:fnameescape(msgfile)
1821 execute (search('^#','n')+1).'wincmd+'
1822 setlocal nopreviewwindow
1824 execute mods 'keepalt split' s:fnameescape(msgfile)
1826 let b:fugitive_commit_arguments = args
1827 setlocal bufhidden=wipe filetype=gitcommit
1829 elseif error ==# '!'
1832 call s:throw(empty(error)?join(errors, ' '):error)
1836 return 'echoerr v:errmsg'
1838 if exists('old_editor')
1839 let $GIT_EDITOR = old_editor
1841 call delete(outfile)
1842 call delete(errorfile)
1843 call fugitive#ReloadStatus()
1847 function! s:CommitComplete(A,L,P) abort
1848 if a:A =~# '^--fixup=\|^--squash='
1849 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
1851 let pre = matchstr(a:A, '^--\w*=') . ':/^'
1852 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
1854 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1855 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']
1856 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1858 return fugitive#PathComplete(a:A)
1863 function! s:FinishCommit() abort
1864 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1866 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1867 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
1872 " Section: Gmerge, Gpull
1874 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1875 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1876 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
1877 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
1878 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1879 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1881 function! s:RevisionComplete(A, L, P) abort
1882 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1883 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1886 function! s:RemoteComplete(A, L, P) abort
1887 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1889 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
1890 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1891 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1893 let matches = split(s:TreeChomp('remote'), "\n")
1895 return join(matches, "\n")
1898 function! fugitive#Cwindow() abort
1899 if &buftype == 'quickfix'
1903 if &buftype == 'quickfix'
1909 let s:common_efm = ''
1911 \ . '%+Eusage:%.%#,'
1912 \ . '%+Eerror:%.%#,'
1913 \ . '%+Efatal:%.%#,'
1914 \ . '%-G%.%#%\e[K%.%#,'
1915 \ . '%-G%.%#%\r%.%\+'
1917 function! s:Merge(cmd, bang, args) abort
1918 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
1919 return 'echoerr "git rebase --interactive not supported"'
1921 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1923 let [mp, efm] = [&l:mp, &l:efm]
1924 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
1926 let &l:errorformat = ''
1927 \ . '%-Gerror:%.%#false''.,'
1928 \ . '%-G%.%# ''git commit'' %.%#,'
1929 \ . '%+Emerge:%.%#,'
1930 \ . s:common_efm . ','
1931 \ . '%+ECannot %.%#: You have unstaged changes.,'
1932 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1933 \ . '%+EThere is no tracking information for the current branch.,'
1934 \ . '%+EYou are not currently on a branch. Please specify which,'
1935 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1936 \ . 'CONFLICT (%m): Merge conflict in %f,'
1937 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1938 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1939 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1940 \ . '%+ECONFLICT %.%#,'
1941 \ . '%+EKONFLIKT %.%#,'
1942 \ . '%+ECONFLIT %.%#,'
1943 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1944 \ . "%+E\u51b2\u7a81 %.%#,"
1946 if a:cmd =~# '^merge' && empty(a:args) &&
1947 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
1948 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
1949 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1951 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
1952 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
1953 \ ' ' . a:args, ' *$', '')
1955 if !empty($GIT_EDITOR) || has('win32')
1956 let old_editor = $GIT_EDITOR
1957 let $GIT_EDITOR = 'false'
1959 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1961 execute cd fnameescape(s:Tree())
1962 silent noautocmd make!
1963 catch /^Vim\%((\a\+)\)\=:E211/
1964 let err = v:exception
1967 let [&l:mp, &l:efm] = [mp, efm]
1968 if exists('old_editor')
1969 let $GIT_EDITOR = old_editor
1971 execute cd fnameescape(cwd)
1973 call fugitive#ReloadStatus()
1974 if empty(filter(getqflist(),'v:val.valid'))
1975 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
1977 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
1980 let qflist = getqflist()
1985 let e.pattern = '^<<<<<<<'
1988 call fugitive#Cwindow()
1990 call setqflist(qflist, 'r')
1995 return exists('err') ? 'echoerr '.string(err) : ''
1998 " Section: Ggrep, Glog
2000 if !exists('g:fugitive_summary_format')
2001 let g:fugitive_summary_format = '%s'
2004 function! s:GrepComplete(A, L, P) abort
2005 if strpart(a:L, 0, a:P) =~# ' -- '
2006 return fugitive#PathComplete(a:A)
2008 return fugitive#Complete(a:A)
2012 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2013 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2014 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2015 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2017 function! s:Grep(cmd,bang,arg) abort
2018 let grepprg = &grepprg
2019 let grepformat = &grepformat
2020 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2023 execute cd s:fnameescape(s:Tree())
2024 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2025 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2026 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2027 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2029 if bufname(entry.bufnr) =~ ':'
2030 let entry.filename = s:Generate(bufname(entry.bufnr))
2033 elseif a:arg =~# '\%(^\| \)--cached\>'
2034 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2039 if a:cmd =~# '^l' && exists('changed')
2040 call setloclist(0, list, 'r')
2041 elseif exists('changed')
2042 call setqflist(list, 'r')
2044 if !a:bang && !empty(list)
2045 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2047 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2050 let &grepprg = grepprg
2051 let &grepformat = grepformat
2052 execute cd s:fnameescape(dir)
2056 function! s:Log(cmd, bang, line1, line2, ...) abort
2057 let args = ' ' . join(a:000, ' ')
2058 let before = substitute(args, ' --\S\@!.*', '', '')
2059 let after = strpart(args, len(before))
2060 let path = s:Relative('/')
2061 if path =~# '^/\.git\%(/\|$\)' || len(after)
2064 let relative = s:Relative('')
2065 if before !~# '\s[^[:space:]-]'
2066 let commit = matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')
2068 let before .= ' ' . commit
2069 elseif relative =~# '^\.git/refs/\|^\.git/.*HEAD$'
2070 let before .= ' ' . relative[5:-1]
2073 if relative =~# '^\.git\%(/\|$\)'
2076 if len(relative) && a:line2 > 0
2077 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2078 elseif len(relative) && (empty(after) || a:line2 == 0)
2079 let after = (len(after) > 3 ? after : ' -- ') . relative
2081 let grepformat = &grepformat
2082 let grepprg = &grepprg
2083 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2086 execute cd s:fnameescape(s:Tree())
2087 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2088 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2089 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2090 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2092 let &grepformat = grepformat
2093 let &grepprg = grepprg
2094 execute cd s:fnameescape(dir)
2098 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
2100 function! s:UsableWin(nr) abort
2101 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2102 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2105 function! s:EditParse(args) abort
2107 let args = copy(a:args)
2108 while !empty(args) && args[0] =~# '^+'
2109 call add(pre, escape(remove(args, 0), ' |"') . ' ')
2112 let file = join(args)
2113 elseif empty(expand('%'))
2115 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2116 let file = s:Relative(':0:')
2118 let file = s:Relative('./')
2120 return [s:Expand(file), join(pre)]
2123 function! s:BlurStatus() abort
2124 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2125 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2127 exe winnrs[0].'wincmd w'
2128 elseif winnr('$') == 1
2129 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2130 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2135 let mywinnr = winnr()
2136 for winnr in range(winnr('$'),1,-1)
2137 if winnr != mywinnr && getwinvar(winnr,'&diff')
2138 execute winnr.'wincmd w'
2150 function! s:Edit(cmd, bang, mods, args, ...) abort
2151 let mods = a:mods ==# '<mods>' ? '' : a:mods
2154 let temp = s:tempname()
2155 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2158 execute cd s:fnameescape(s:Tree())
2159 let git = s:UserCommand()
2160 let args = s:ShellExpand(a:args)
2161 silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2162 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2164 execute cd s:fnameescape(cwd)
2166 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2170 silent execute mods a:cmd temp
2171 call fugitive#ReloadStatus()
2172 return 'redraw|echo ' . string(':!' . git . ' ' . args)
2175 let [file, pre] = s:EditParse(a:000)
2177 let file = s:Generate(file)
2179 return 'echoerr v:errmsg'
2181 if file !~# '^fugitive:'
2182 let file = s:sub(file, '/$', '')
2187 return mods . ' ' . a:cmd . ' ' . pre . s:fnameescape(file)
2190 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2191 let mods = a:mods ==# '<mods>' ? '' : a:mods
2194 let delete = 'silent 1,' . line('$') . 'delete_|'
2195 let after = line('$')
2197 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2202 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2205 execute cd s:fnameescape(s:Tree())
2206 let git = s:UserCommand()
2207 let args = s:ShellExpand(a:args)
2208 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2210 execute cd s:fnameescape(cwd)
2212 execute delete . 'diffupdate'
2213 call fugitive#ReloadStatus()
2214 return 'redraw|echo '.string(':!'.git.' '.args)
2216 let [file, pre] = s:EditParse(a:000)
2218 let file = s:Generate(file)
2220 return 'echoerr v:errmsg'
2222 return mods . ' ' . after . 'read ' . pre . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2225 function! s:EditRunComplete(A,L,P) abort
2227 return s:GitComplete(a:A, a:L, a:P)
2229 return fugitive#Complete(a:A, a:L, a:P)
2233 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2234 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2235 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2236 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>)")
2237 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>)")
2238 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>)")
2239 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>)")
2241 " Section: Gwrite, Gwq
2243 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2244 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2245 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2247 function! s:Write(force,...) abort
2248 if exists('b:fugitive_commit_arguments')
2249 return 'write|bdelete'
2250 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2252 elseif get(b:, 'fugitive_type', '') ==# 'index'
2254 elseif s:Relative('') ==# '' && getline(4) =~# '^+++ '
2255 let filename = getline(4)[6:-1]
2258 setlocal buftype=nowrite
2259 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2260 let err = s:TreeChomp('apply', '--cached', '--reverse', expand('%:p'))
2262 let err = s:TreeChomp('apply', '--cached', expand('%:p'))
2265 let v:errmsg = split(err,"\n")[0]
2266 return 'echoerr v:errmsg'
2270 return 'Gedit '.fnameescape(filename)
2273 let mytab = tabpagenr()
2274 let mybufnr = bufnr('')
2275 let path = a:0 ? s:Expand(join(a:000, ' ')) : s:Relative()
2277 return 'echoerr '.string('fugitive: cannot determine file path')
2279 if path =~# '^:\d\>'
2280 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:Generate(path))
2282 let always_permitted = ((s:Relative() ==# path || s:Relative('') ==# path) && s:DirCommitFile(@%)[1] =~# '^0\=$')
2283 if !always_permitted && !a:force && (len(s:TreeChomp('diff','--name-status','HEAD','--',path)) || len(s:TreeChomp('ls-files','--others','--',path)))
2284 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2285 return 'echoerr v:errmsg'
2287 let file = s:Generate(path)
2289 for nr in range(1,bufnr('$'))
2290 if fnamemodify(bufname(nr),':p') ==# file
2295 if treebufnr > 0 && treebufnr != bufnr('')
2296 let temp = tempname()
2297 silent execute '%write '.temp
2298 for tab in [mytab] + range(1,tabpagenr('$'))
2299 for winnr in range(1,tabpagewinnr(tab,'$'))
2300 if tabpagebuflist(tab)[winnr-1] == treebufnr
2301 execute 'tabnext '.tab
2303 execute winnr.'wincmd w'
2304 let restorewinnr = 1
2307 let lnum = line('.')
2308 let last = line('$')
2309 silent execute '$read '.temp
2310 silent execute '1,'.last.'delete_'
2315 if exists('restorewinnr')
2318 execute 'tabnext '.mytab
2324 call writefile(readfile(temp,'b'),file,'b')
2327 execute 'write! '.s:fnameescape(s:Generate(path))
2331 let error = s:TreeChomp('add', '--force', '--', path)
2333 let error = s:TreeChomp('add', '--', path)
2336 let v:errmsg = 'fugitive: '.error
2337 return 'echoerr v:errmsg'
2339 if s:Relative('') ==# path && s:DirCommitFile(@%)[1] =~# '^\d$'
2343 let one = s:Generate(':1:'.path)
2344 let two = s:Generate(':2:'.path)
2345 let three = s:Generate(':3:'.path)
2346 for nr in range(1,bufnr('$'))
2347 let name = fnamemodify(bufname(nr), ':p')
2348 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2349 execute nr.'bdelete'
2354 let zero = s:Generate(':0:'.path)
2355 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2356 for tab in range(1,tabpagenr('$'))
2357 for winnr in range(1,tabpagewinnr(tab,'$'))
2358 let bufnr = tabpagebuflist(tab)[winnr-1]
2359 let bufname = fnamemodify(bufname(bufnr), ':p')
2360 if bufname ==# zero && bufnr != mybufnr
2361 execute 'tabnext '.tab
2363 execute winnr.'wincmd w'
2364 let restorewinnr = 1
2367 let lnum = line('.')
2368 let last = line('$')
2369 silent execute '$read '.s:fnameescape(file)
2370 silent execute '1,'.last.'delete_'
2375 if exists('restorewinnr')
2378 execute 'tabnext '.mytab
2384 call fugitive#ReloadStatus()
2388 function! s:Wq(force,...) abort
2389 let bang = a:force ? '!' : ''
2390 if exists('b:fugitive_commit_arguments')
2393 let result = call(s:function('s:Write'),[a:force]+a:000)
2394 if result =~# '^\%(write\|wq\|echoerr\)'
2395 return s:sub(result,'^write','wq')
2397 return result.'|quit'.bang
2401 augroup fugitive_commit
2403 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2406 " Section: Gpush, Gfetch
2408 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2409 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2411 function! s:Dispatch(bang, args)
2412 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2414 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2416 let b:current_compiler = 'git'
2417 let &l:errorformat = s:common_efm
2418 execute cd fnameescape(s:Tree())
2419 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2420 if exists(':Make') == 2
2423 silent noautocmd make!
2425 return 'call fugitive#Cwindow()'
2429 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2430 if empty(cc) | unlet! b:current_compiler | endif
2431 execute cd fnameescape(cwd)
2437 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2438 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2439 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2441 augroup fugitive_diff
2443 autocmd BufWinLeave *
2444 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2445 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2447 autocmd BufWinEnter *
2448 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2449 \ call s:diffoff() |
2453 function! s:can_diffoff(buf) abort
2454 return getwinvar(bufwinnr(a:buf), '&diff') &&
2455 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2456 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2459 function! fugitive#CanDiffoff(buf) abort
2460 return s:can_diffoff(a:buf)
2463 function! s:diff_modifier(count) abort
2464 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2465 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2467 elseif &diffopt =~# 'vertical'
2468 return 'keepalt vert '
2469 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2472 return 'keepalt vert '
2476 function! s:diff_window_count() abort
2478 for nr in range(1,winnr('$'))
2479 let c += getwinvar(nr,'&diff')
2484 function! s:diff_restore() abort
2485 let restore = 'setlocal nodiff noscrollbind'
2486 \ . ' scrollopt=' . &l:scrollopt
2487 \ . (&l:wrap ? ' wrap' : ' nowrap')
2488 \ . ' foldlevel=999'
2489 \ . ' foldmethod=' . &l:foldmethod
2490 \ . ' foldcolumn=' . &l:foldcolumn
2491 \ . ' foldlevel=' . &l:foldlevel
2492 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2493 if has('cursorbind')
2494 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2499 function! s:diffthis() abort
2501 let w:fugitive_diff_restore = s:diff_restore()
2506 function! s:diffoff() abort
2507 if exists('w:fugitive_diff_restore')
2508 execute w:fugitive_diff_restore
2509 unlet w:fugitive_diff_restore
2515 function! s:diffoff_all(dir) abort
2516 let curwin = winnr()
2517 for nr in range(1,winnr('$'))
2518 if getwinvar(nr,'&diff')
2520 execute nr.'wincmd w'
2521 let restorewinnr = 1
2523 if exists('b:git_dir') && b:git_dir ==# a:dir
2528 execute curwin.'wincmd w'
2531 function! s:CompareAge(mine, theirs) abort
2532 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2533 let mine = substitute(a:mine, '^:', '', '')
2534 let theirs = substitute(a:theirs, '^:', '', '')
2535 let my_score = get(scores, ':'.mine, 0)
2536 let their_score = get(scores, ':'.theirs, 0)
2537 if my_score || their_score
2538 return my_score < their_score ? -1 : my_score != their_score
2539 elseif mine ==# theirs
2542 let base = s:TreeChomp('merge-base', mine, theirs)
2545 elseif base ==# theirs
2548 let my_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:mine)
2549 let their_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:theirs)
2550 return my_time < their_time ? -1 : my_time != their_time
2553 function! s:Diff(vert,keepfocus,...) abort
2554 let args = copy(a:000)
2556 if get(args, 0) =~# '^+'
2557 let post = remove(args, 0)[1:-1]
2559 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2560 let commit = s:DirCommitFile(@%)[1]
2561 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2562 if exists(':DiffGitCached')
2563 return 'DiffGitCached'
2564 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', s:Relative('')))
2565 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2567 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2568 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2572 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2573 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2578 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2579 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2582 let arg = join(args, ' ')
2586 let file = s:Relative('/')
2588 let file = s:Relative(':0:')
2589 elseif arg =~# '^:/.'
2591 let file = fugitive#RevParse(arg).s:Relative(':')
2593 return 'echoerr v:errmsg'
2596 let file = s:Expand(arg)
2598 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2599 let file = file.s:Relative(':')
2602 let file = s:Relative(empty(commit) ? ':0:' : '/')
2605 let spec = s:Generate(file)
2606 let restore = s:diff_restore()
2607 if exists('+cursorbind')
2610 let w:fugitive_diff_restore = restore
2611 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2612 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2614 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2616 let &l:readonly = &l:readonly
2618 let w:fugitive_diff_restore = restore
2620 if getwinvar('#', '&diff')
2623 call feedkeys(winnr."\<C-W>w", 'n')
2628 return 'echoerr v:errmsg'
2632 " Section: Gmove, Gremove
2634 function! s:Move(force, rename, destination) abort
2635 if a:destination =~# '^[.:]\=/'
2636 let destination = substitute(a:destination[1:-1], '^[.:]\=/', '', '')
2637 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
2638 let destination = matchstr(a:destination, ')\zs.*')
2640 let destination = fnamemodify(s:Relative(''), ':h') . '/' . a:destination
2642 let destination = a:destination
2647 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', s:Relative(''), destination])
2649 let v:errmsg = 'fugitive: '.message
2650 return 'echoerr v:errmsg'
2652 let destination = s:Tree() . '/' . destination
2653 if isdirectory(destination)
2654 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2656 call fugitive#ReloadStatus()
2657 if empty(s:DirCommitFile(@%)[1])
2658 if isdirectory(destination)
2659 return 'keepalt edit '.s:fnameescape(destination)
2661 return 'keepalt saveas! '.s:fnameescape(destination)
2664 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2668 function! s:RenameComplete(A,L,P) abort
2670 return fugitive#PathComplete(a:A)
2672 let pre = '/' . fnamemodify(s:Relative(''), ':h') . '/'
2673 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2677 function! s:Remove(after, force) abort
2678 if s:DirCommitFile(@%)[1] ==# ''
2680 elseif s:DirCommitFile(@%)[1] ==# '0'
2681 let cmd = ['rm','--cached']
2683 let v:errmsg = 'fugitive: rm not supported here'
2684 return 'echoerr v:errmsg'
2687 let cmd += ['--force']
2689 let message = call('s:TreeChomp', cmd+['--',s:Relative('')])
2691 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2692 return 'echoerr '.string(v:errmsg)
2694 call fugitive#ReloadStatus()
2695 return a:after . (a:force ? '!' : '')
2699 augroup fugitive_remove
2701 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2702 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2703 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2704 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2705 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2711 function! s:Keywordprg() abort
2712 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2713 if has('gui_running') && !has('win32')
2714 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2716 return s:UserCommand() . args . ' show'
2720 augroup fugitive_blame
2722 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2723 autocmd Syntax fugitiveblame call s:BlameSyntax()
2724 autocmd User Fugitive
2725 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2726 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2728 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2729 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2732 function! s:linechars(pattern) abort
2733 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2734 if exists('*synconcealed') && &conceallevel > 1
2735 for col in range(1, chars)
2736 let chars -= synconcealed(line('.'), col)[0]
2742 function! s:Blame(bang, line1, line2, count, mods, args) abort
2743 if exists('b:fugitive_blamed_bufnr')
2747 if empty(s:Relative(''))
2748 call s:throw('file or blob required')
2750 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2751 call s:throw('unsupported option')
2753 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2754 let cmd = ['--no-pager', 'blame', '--show-number']
2756 let cmd += ['-L', a:line1 . ',' . a:line1]
2759 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2760 let cmd += [s:DirCommitFile(@%)[1]]
2762 let cmd += ['--contents', '-']
2764 let cmd += ['--', s:Relative('')]
2765 let basecmd = escape(call('s:Prepare', [b:git_dir] + cmd), '!#%')
2767 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2769 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2771 execute cd s:fnameescape(tree)
2773 let error = s:tempname()
2774 let temp = error.'.fugitiveblame'
2776 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2778 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2781 execute cd s:fnameescape(cwd)
2785 call s:throw(join(readfile(error),"\n"))
2788 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
2789 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
2791 for winnr in range(winnr('$'),1,-1)
2792 call setwinvar(winnr, '&scrollbind', 0)
2793 if exists('+cursorbind')
2794 call setwinvar(winnr, '&cursorbind', 0)
2796 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2797 execute winbufnr(winnr).'bdelete'
2800 let bufnr = bufnr('')
2801 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2802 if exists('+cursorbind')
2803 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2806 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2809 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2811 setlocal scrollbind nowrap nofoldenable
2812 if exists('+cursorbind')
2815 let top = line('w0') + &scrolloff
2816 let current = line('.')
2817 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
2818 exe 'keepalt leftabove vsplit '.temp
2819 let b:fugitive_blamed_bufnr = bufnr
2820 let b:fugitive_type = 'blame'
2821 let w:fugitive_leave = restore
2822 let b:fugitive_blame_arguments = join(a:args,' ')
2826 if exists('+cursorbind')
2829 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
2830 if exists('+concealcursor')
2831 setlocal concealcursor=nc conceallevel=2
2833 if exists('+relativenumber')
2834 setlocal norelativenumber
2836 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2837 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2838 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
2839 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2840 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>
2841 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2842 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
2843 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2844 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2845 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2846 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2847 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2848 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
2849 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2850 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2851 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2857 execute cd s:fnameescape(cwd)
2862 return 'echoerr v:errmsg'
2866 function! s:BlameCommit(cmd, ...) abort
2867 let line = a:0 ? a:1 : getline('.')
2868 if line =~# '^0\{4,40\} '
2869 return 'echoerr ' . string('Not Committed Yet')
2871 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
2872 if cmd =~# '^echoerr'
2875 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
2876 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2878 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
2881 if a:cmd ==# 'pedit'
2884 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2886 let head = line('.')
2887 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2888 let top = +matchstr(getline('.'),' +\zs\d\+')
2889 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2890 if lnum >= top && lnum <= top + len
2891 let offset = lnum - top
2899 while offset > 0 && line('.') < line('$')
2901 if getline('.') =~# '^[ +]'
2914 function! s:BlameJump(suffix) abort
2915 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2916 if commit =~# '^0\+$'
2919 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2920 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2922 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
2924 let args = b:fugitive_blame_arguments
2925 let offset = line('.') - line('w0')
2926 let bufnr = bufnr('%')
2927 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2929 exe winnr.'wincmd w'
2931 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
2936 if exists(':Gblame')
2937 execute 'Gblame '.args
2939 let delta = line('.') - line('w0') - offset
2941 execute 'normal! '.delta."\<C-E>"
2943 execute 'normal! '.(-delta)."\<C-Y>"
2950 let s:hash_colors = {}
2952 function! s:BlameSyntax() abort
2953 let b:current_syntax = 'fugitiveblame'
2954 let conceal = has('conceal') ? ' conceal' : ''
2955 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2956 syn match FugitiveblameBoundary "^\^"
2957 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2958 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2959 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2960 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2961 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2962 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2963 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2964 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2965 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2966 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2967 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2968 hi def link FugitiveblameBoundary Keyword
2969 hi def link FugitiveblameHash Identifier
2970 hi def link FugitiveblameUncommitted Ignore
2971 hi def link FugitiveblameTime PreProc
2972 hi def link FugitiveblameLineNumber Number
2973 hi def link FugitiveblameOriginalFile String
2974 hi def link FugitiveblameOriginalLineNumber Float
2975 hi def link FugitiveblameShort FugitiveblameDelimiter
2976 hi def link FugitiveblameDelimiter Delimiter
2977 hi def link FugitiveblameNotCommittedYet Comment
2979 for lnum in range(1, line('$'))
2980 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2981 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2985 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
2986 \ && empty(get(s:hash_colors, hash))
2987 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2988 let color = csapprox#per_component#Approximate(r, g, b)
2989 if color == 16 && &background ==# 'dark'
2992 let s:hash_colors[hash] = ' ctermfg='.color
2994 let s:hash_colors[hash] = ''
2996 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2998 call s:RehighlightBlame()
3001 function! s:RehighlightBlame() abort
3002 for [hash, cterm] in items(s:hash_colors)
3003 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3004 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3006 exe 'hi link FugitiveblameHash'.hash.' Identifier'
3013 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3015 let s:redirects = {}
3017 function! s:Browse(bang,line1,count,...) abort
3019 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3021 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3022 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3028 let rev = s:DirRev(@%)[1]
3031 let expanded = s:Relative('/')
3033 let expanded = s:Expand(rev)
3035 let cdir = fugitive#CommonDir(b:git_dir)
3036 for dir in ['tags/', 'heads/', 'remotes/']
3037 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3038 let expanded = '/.git/refs/' . dir . expanded
3041 let full = s:Generate(expanded)
3043 if full =~? '^fugitive:'
3044 let [dir, commit, path] = s:DirCommitFile(full)
3045 if commit =~# '^:\=\d$'
3049 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3050 let branch = matchstr(expanded, '^[^:]*')
3054 let path = path[1:-1]
3055 elseif empty(s:Tree())
3056 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3059 let path = full[strlen(s:Tree())+1:-1]
3060 if path =~# '^\.git/'
3062 elseif isdirectory(full)
3068 if type ==# 'tree' && !empty(path)
3069 let path = s:sub(path, '/\=$', '/')
3071 if path =~# '^\.git/.*HEAD' && filereadable(b:git_dir . '/' . path[5:-1])
3072 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3073 if body =~# '^\x\{40\}$'
3077 elseif body =~# '^ref: refs/'
3078 let path = '.git/' . matchstr(body,'ref: \zs.*')
3083 if path =~# '^\.git/refs/remotes/.'
3085 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3086 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3088 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3089 let path = '.git/refs/heads/'.merge
3091 elseif path =~# '^\.git/refs/heads/.'
3092 let branch = path[16:-1]
3093 elseif !exists('branch')
3094 let branch = FugitiveHead()
3097 let r = fugitive#Config('branch.'.branch.'.remote')
3098 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3099 if r ==# '.' && !empty(m)
3100 let r2 = fugitive#Config('branch.'.m.'.remote')
3103 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3109 if r ==# '.' || r ==# remote
3111 if path =~# '^\.git/refs/heads/.'
3112 let path = '.git/refs/heads/'.merge
3117 let line1 = a:count > 0 ? a:line1 : 0
3118 let line2 = a:count > 0 ? a:count : 0
3119 if empty(commit) && path !~# '^\.git/'
3120 if a:line1 && !a:count && !empty(merge)
3125 let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3126 let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3127 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3128 let blame_list = s:tempname()
3129 call writefile([commit, ''], blame_list, 'b')
3130 let blame_in = s:tempname()
3131 silent exe '%write' blame_in
3132 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', '--', path), "\n")
3134 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3135 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3136 let line1 = +matchstr(blame[0], blame_regex)
3137 let line2 = +matchstr(blame[-1], blame_regex)
3139 call s:throw("Can't browse to uncommitted change")
3146 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3149 while commit =~# '^ref: ' && i < 10
3150 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3158 let raw = fugitive#RemoteUrl(remote)
3163 if raw =~# '^https\=://' && s:executable('curl')
3164 if !has_key(s:redirects, raw)
3165 let s:redirects[raw] = matchstr(system('curl -I ' .
3166 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3167 \ 'Location: \zs\S\+\ze/info/refs?')
3169 if len(s:redirects[raw])
3170 let raw = s:redirects[raw]
3176 \ 'repo': fugitive#repo(),
3178 \ 'revision': 'No longer provided',
3185 for Handler in get(g:, 'fugitive_browse_handlers', [])
3186 let url = call(Handler, [copy(opts)])
3193 call s:throw("No Gbrowse handler found for '".raw."'")
3196 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3201 return 'echomsg '.string(url)
3202 elseif exists(':Browse') == 2
3203 return 'echomsg '.string(url).'|Browse '.url
3205 if !exists('g:loaded_netrw')
3206 runtime! autoload/netrw.vim
3208 if exists('*netrw#BrowseX')
3209 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3211 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3215 return 'echoerr v:errmsg'
3219 " Section: Go to file
3221 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3222 function! fugitive#MapCfile(...) abort
3223 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3224 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3225 if !exists('g:fugitive_no_maps')
3226 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3227 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3228 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3229 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3230 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3234 function! s:ContainingCommit() abort
3235 let commit = s:DirCommitFile(@%)[1]
3236 if commit =~# '^\d\=$'
3243 function! s:NavigateUp(count) abort
3244 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3248 let rev = matchstr(rev, '.*\ze/.\+', '')
3249 elseif rev =~# '.:.'
3250 let rev = matchstr(rev, '^.[^:]*:')
3263 function! fugitive#MapJumps(...) abort
3264 if get(b:, 'fugitive_type', '') ==# 'blob'
3265 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3267 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3270 if get(b:, 'fugitive_type', '') ==# 'blob'
3271 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3272 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3273 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3274 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3276 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3277 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3278 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3279 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3281 nnoremap <buffer> <silent> - :<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>
3282 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3283 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3284 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3285 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3286 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3287 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3288 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3289 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3290 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
3294 function! s:StatusCfile(...) abort
3296 if getline('.') =~# '^.\=\trenamed:.* -> '
3297 return '/'.matchstr(getline('.'),' -> \zs.*')
3298 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3299 return '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3300 elseif getline('.') =~# '^.\=\t.'
3301 return '/'.matchstr(getline('.'),'\t\zs.*')
3302 elseif getline('.') =~# ': needs merge$'
3303 return '/'.matchstr(getline('.'),'.*\ze: needs merge$')
3304 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3306 elseif getline('.') =~# '^\%(. \)\=On branch '
3307 return 'refs/heads/'.getline('.')[12:]
3308 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3309 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3315 function! fugitive#StatusCfile() abort
3316 let file = s:StatusCfile()
3317 return empty(file) ? "\<C-R>\<C-F>" : s:fnameescape(s:Generate(file))
3320 function! s:cfile() abort
3322 let myhash = s:DirRev(@%)[1]
3325 let myhash = fugitive#RevParse(myhash)
3330 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3331 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3334 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3336 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3337 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3339 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3340 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3342 return [treebase . s:sub(getline('.'),'/$','')]
3349 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3350 let ref = matchstr(getline('.'),'\x\{40\}')
3351 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3355 if getline('.') =~# '^ref: '
3356 let ref = strpart(getline('.'),5)
3358 elseif getline('.') =~# '^commit \x\{40\}\>'
3359 let ref = matchstr(getline('.'),'\x\{40\}')
3362 elseif getline('.') =~# '^parent \x\{40\}\>'
3363 let ref = matchstr(getline('.'),'\x\{40\}')
3364 let line = line('.')
3366 while getline(line) =~# '^parent '
3372 elseif getline('.') =~# '^tree \x\{40\}$'
3373 let ref = matchstr(getline('.'),'\x\{40\}')
3374 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3375 let ref = myhash.':'
3379 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3380 let ref = matchstr(getline('.'),'\x\{40\}')
3381 let type = matchstr(getline(line('.')+1),'type \zs.*')
3383 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3384 let ref = s:DirRev(@%)[1]
3386 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3387 let ref = matchstr(getline('.'),'\x\{40\}')
3388 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3390 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3391 let ref = getline('.')[4:]
3393 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3394 let type = getline('.')[0]
3395 let lnum = line('.') - 1
3397 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3398 if getline(lnum) =~# '^[ '.type.']'
3403 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3404 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3405 let dcmds = [offset, 'normal!zv']
3407 elseif getline('.') =~# '^rename from '
3408 let ref = 'a/'.getline('.')[12:]
3409 elseif getline('.') =~# '^rename to '
3410 let ref = 'b/'.getline('.')[10:]
3412 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3413 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3414 let offset = matchstr(getline('.'), '+\zs\d\+')
3416 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3417 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3418 let dcmd = 'Gdiff! +'.offset
3420 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3421 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3422 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3425 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3426 let line = getline(line('.')-1)
3427 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3428 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3431 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3432 let ref = getline('.')
3434 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3435 return [expand('<cword>')]
3450 let prefixes.a = myhash.'^:'
3451 let prefixes.b = myhash.':'
3453 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3455 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3458 if ref ==# '/dev/null'
3460 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3464 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3466 return [ref] + dcmds
3474 function! s:GF(mode) abort
3476 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3478 return 'echoerr v:errmsg'
3481 return 'G' . a:mode .
3482 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3483 \ s:fnameescape(results[0])
3485 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3491 function! fugitive#Cfile() abort
3493 let results = s:cfile()
3495 let cfile = expand('<cfile>')
3496 if &includeexpr =~# '\<v:fname\>'
3497 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3500 elseif len(results) > 1
3501 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3503 return pre . s:fnameescape(s:Generate(results[0]))
3506 " Section: Statusline
3508 function! fugitive#Statusline(...) abort
3509 if !exists('b:git_dir')
3513 let commit = s:DirCommitFile(@%)[1]
3515 let status .= ':' . commit[0:7]
3517 let status .= '('.FugitiveHead(7).')'
3518 return '[Git'.status.']'
3521 function! fugitive#statusline(...) abort
3522 return fugitive#Statusline()
3525 function! fugitive#head(...) abort
3526 if !exists('b:git_dir')
3530 return fugitive#repo().head(a:0 ? a:1 : 0)
3535 function! fugitive#Foldtext() abort
3536 if &foldmethod !=# 'syntax'
3540 let line_foldstart = getline(v:foldstart)
3541 if line_foldstart =~# '^diff '
3542 let [add, remove] = [-1, -1]
3544 for lnum in range(v:foldstart, v:foldend)
3545 let line = getline(lnum)
3546 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3547 let filename = line[6:-1]
3551 elseif line =~# '^-'
3553 elseif line =~# '^Binary '
3558 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3561 let filename = line_foldstart[5:-1]
3564 return 'Binary: '.filename
3566 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3568 elseif line_foldstart =~# '^# .*:$'
3569 let lines = getline(v:foldstart, v:foldend)
3570 call filter(lines, 'v:val =~# "^#\t"')
3571 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3572 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3573 return line_foldstart.' '.join(lines, ', ')
3578 function! fugitive#foldtext() abort
3579 return fugitive#Foldtext()
3582 augroup fugitive_folding
3584 autocmd User Fugitive
3585 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3586 \ set foldtext=fugitive#Foldtext() |
3590 " Section: Initialization
3592 function! fugitive#Init() abort
3593 if exists('#User#FugitiveBoot')
3595 let [save_mls, &modelines] = [&mls, 0]
3596 doautocmd User FugitiveBoot
3601 if !exists('g:fugitive_no_maps')
3602 call s:map('c', '<C-R><C-G>', 'fnameescape(<SID>recall())', '<expr>')
3603 call s:map('n', 'y<C-G>', ':call setreg(v:register, <SID>recall())<CR>', '<silent>')
3605 if expand('%:p') =~# ':[\/][\/]'
3606 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3608 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3609 if filereadable(b:git_dir.'/tags')
3610 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3612 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3613 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3617 let [save_mls, &modelines] = [&mls, 0]
3618 call s:define_commands()
3619 doautocmd User Fugitive
3625 function! fugitive#is_git_dir(path) abort
3626 return FugitiveIsGitDir(a:path)
3629 function! fugitive#extract_git_dir(path) abort
3630 return FugitiveExtractGitDir(a:path)
3633 function! fugitive#detect(path) abort
3634 return FugitiveDetect(a:path)