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#RevParse(rev, ...) abort
191 let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev))[0:-2]
192 if !v:shell_error && hash =~# '^\x\{40\}$'
195 call s:throw('rev-parse '.a:rev.': '.hash)
198 function! fugitive#Config(name, ...) abort
199 let cmd = s:Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), 'config', '--get', a:name)
200 let out = matchstr(system(cmd), "[^\r\n]*")
201 return v:shell_error ? '' : out
204 function! s:Remote(dir) abort
205 let head = FugitiveHead(0, a:dir)
206 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
208 while remote ==# '.' && i > 0
209 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
210 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
213 return remote =~# '\.\=$' ? 'origin' : remote
216 function! fugitive#RemoteUrl(...) abort
217 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
218 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
219 if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
220 return fugitive#Config('remote.' . remote . '.url')
222 let cmd = s:Prepare(dir, 'remote', 'get-url', remote)
223 let out = substitute(system(cmd), "\n$", '', '')
224 return v:shell_error ? '' : out
227 function! s:recall() abort
228 let rev = s:sub(fugitive#buffer().rev(), '^/', '')
230 return matchstr(getline('.'),'^.\=\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
231 elseif fugitive#buffer().type('tree')
232 let file = matchstr(getline('.'), '\t\zs.*')
233 if empty(file) && line('.') > 2
234 let file = s:sub(getline('.'), '/$', '')
236 if !empty(file) && rev !~# ':$'
237 return rev . '/' . file
245 function! s:map(mode, lhs, rhs, ...) abort
246 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
249 let keys = get(g:, a:mode.'remap', {})
250 if type(keys) == type([])
254 if has_key(keys, head)
255 let head = keys[head]
261 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
262 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
264 if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
265 exe a:mode.'map <buffer>' flags head.tail a:rhs
267 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
268 \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
273 function! s:add_methods(namespace, method_names) abort
274 for name in a:method_names
275 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
280 function! s:command(definition) abort
281 let s:commands += [a:definition]
284 function! s:define_commands() abort
285 for command in s:commands
286 exe 'command! -buffer '.command
290 let s:abstract_prototype = {}
292 " Section: Repository
294 let s:repo_prototype = {}
297 function! fugitive#repo(...) abort
298 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
300 if has_key(s:repos, dir)
301 let repo = get(s:repos, dir)
303 let repo = {'git_dir': dir}
304 let s:repos[dir] = repo
306 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
308 call s:throw('not a git repository: '.expand('%:p'))
311 function! s:repo_dir(...) dict abort
312 return join([self.git_dir]+a:000,'/')
315 function! s:repo_tree(...) dict abort
316 let dir = s:Tree(self.git_dir)
318 call s:throw('no work tree')
320 return join([dir]+a:000,'/')
324 function! s:repo_bare() dict abort
325 if self.dir() =~# '/\.git$'
328 return s:Tree(self.git_dir) ==# ''
332 function! s:repo_translate(object, ...) dict abort
333 let rev = substitute(a:object, '[:/]\zs\.\%(/\+\|$\)', '', 'g')
334 let dir = self.git_dir
335 let tree = s:Tree(dir)
336 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
337 if rev =~# '^/\=\.git$' && empty(tree)
339 elseif rev =~# '^/\=\.git/'
340 let f = s:sub(rev, '^/=\.git', '')
341 let cdir = fugitive#CommonDir(dir)
342 if cdir !=# dir && (f =~# '^/\%(config\|info\|hooks\|objects\|refs\|worktrees\)' || !filereadable(f) && filereadable(cdir . f))
347 elseif rev ==# '^/\=\.$'
349 elseif rev =~# '^\.\=\%(/\|$\)'
350 let f = base . substitute(rev, '^\.', '', '')
351 elseif rev =~# '^:[0-3]:/\@!'
352 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
354 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
355 let f = fnamemodify($GIT_INDEX_FILE, ':p')
357 let f = dir . '/index'
359 elseif rev =~# '^:/\@!'
360 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
362 if rev =~# 'HEAD\|^refs/' && rev !~# ':'
363 let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
364 if filereadable(cdir . '/' . rev)
365 let f = simplify(cdir . '/' . rev)
369 let commit = substitute(matchstr(rev,'^[^:]\+\|^:.*'), '^@\%($|[^~]\)\@=', 'HEAD', '')
370 let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
371 if commit !~# '^[0-9a-f]\{40\}$'
372 let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit))[0:-2]
373 let commit = v:shell_error ? '' : commit
376 let f = 'fugitive://' . dir . '//' . commit . file
378 let f = base . '/' . rev
382 return a:0 && a:1 ? s:PlatformSlash(f) : f
385 function! s:Generate(rev, ...) abort
386 let repo = fugitive#repo(a:0 ? a:1 : b:git_dir)
387 if a:rev =~# '^\%(\a\+:\)\=/' && getftime(a:rev) >= 0 && getftime(repo.tree() . a:rev) < 0
388 return s:PlatformSlash(a:rev)
390 return repo.translate(a:rev, 1)
393 function! s:repo_head(...) dict abort
394 if !filereadable(self.dir('HEAD'))
397 let head = readfile(self.dir('HEAD'))[0]
400 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
401 elseif head =~# '^\x\{40\}$'
402 " truncate hash to a:1 characters if we're in detached head mode
403 let len = a:0 ? a:1 : 0
404 let branch = len < 0 ? head : len ? head[0:len-1] : ''
412 call s:add_methods('repo',['dir','tree','bare','translate','head'])
414 function! s:repo_git_command(...) dict abort
415 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
416 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
419 function! s:repo_git_chomp(...) dict abort
420 return s:sub(s:System(call('s:Prepare', [self.git_dir] + a:000)),'\n$','')
423 function! s:repo_git_chomp_in_tree(...) dict abort
424 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
427 execute cd s:fnameescape(self.tree())
428 return call(self.git_chomp, a:000, self)
430 execute cd s:fnameescape(dir)
434 function! s:repo_rev_parse(rev) dict abort
435 return fugitive#RevParse(a:rev, self.git_dir)
438 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
440 function! s:repo_superglob(base) dict abort
441 return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
444 call s:add_methods('repo',['superglob'])
446 function! s:repo_config(name) dict abort
447 return fugitive#Config(a:name, self.git_dir)
450 function! s:repo_user() dict abort
451 let username = self.config('user.name')
452 let useremail = self.config('user.email')
453 return username.' <'.useremail.'>'
456 call s:add_methods('repo',['config', 'user'])
460 function! s:DirCommitFile(path) abort
461 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
468 function! s:DirRev(url) abort
469 let [dir, commit, file] = s:DirCommitFile(a:url)
470 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
473 function! fugitive#Real(url) abort
474 let [dir, commit, file] = s:DirCommitFile(a:url)
476 let tree = s:Tree(dir)
477 return s:PlatformSlash((len(tree) ? tree : dir) . file)
479 let url = len(a:url) ? fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??')) : ''
480 if url =~# '^[\\/]\|^\a:[\\/]'
481 return s:PlatformSlash(url)
486 function! fugitive#Path(url, ...) abort
487 if !a:0 || empty(a:url)
488 return fugitive#Real(a:url)
490 let url = s:Slash(fnamemodify(a:url, ':p'))
491 if url =~# '/$' && s:Slash(a:url) !~# '/$'
494 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
495 let tree = s:Tree(dir)
496 let [argdir, commit, file] = s:DirCommitFile(a:url)
497 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
499 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
500 let file = '/.git'.url[strlen(dir) : -1]
501 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
502 let file = url[len(tree) : -1]
503 elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
506 if empty(file) && a:1 =~# '^\%([.:]\=/\)\=$'
507 return s:Slash(fugitive#Real(a:url))
509 return substitute(file, '^/', a:1, '')
512 function! s:RemoveDot(path, ...) abort
516 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
517 let cdir = fugitive#CommonDir(dir)
518 if len(filter(['', '/tags', '/heads', '/remotes'], 'getftime(cdir . "/refs" . v:val . a:path[1:-1]) >= 0')) ||
519 \ a:path =~# 'HEAD$' && filereadable(dir . a:path[1:-1]) ||
520 \ a:path =~# '^\./refs/' && filereadable(cdir . a:path[1:-1])
526 function! s:Expand(rev, ...) abort
527 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
528 if a:rev =~# '^:[0-3]$'
529 let file = a:rev . fugitive#Path(@%, a:rev, dir)
530 elseif a:rev =~# '^-'
531 let file = 'HEAD^{}' . a:rev[1:-1] . fugitive#Path(@%, ':', dir)
532 elseif a:rev =~# '^@{'
533 let file = 'HEAD'.a:rev. fugitive#Path(@%, ':', dir)
534 elseif a:rev =~# '^[~^]'
535 let commit = substitute(s:DirCommitFile(@%), '^\d\=$', 'HEAD')
536 let file = commit . a:rev . fugitive#Path(@%, ':', dir)
540 return s:sub(substitute(file,
541 \ '\([%#]\)$\|\\\([[:punct:]]\)','\=len(submatch(2)) ? submatch(2) : fugitive#Path(expand(submatch(1)), "./", dir)','g'),
545 function! s:ShellExpand(cmd, ...) abort
546 return substitute(a:cmd, '\\\@<![%#]:\@!', '\=s:RemoveDot(fugitive#Path(expand(submatch(0)), "./", a:0 ? a:1 : get(b:, "git_dir", "")))', 'g')
551 function! s:TreeInfo(dir, commit) abort
552 let git = s:Prepare(a:dir)
553 if a:commit =~# '^:\=[0-3]$'
554 let index = get(s:indexes, a:dir, [])
555 let newftime = getftime(a:dir . '/index')
556 if get(index, 0, -1) < newftime
557 let out = system(git . ' ls-files --stage')
558 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
562 for line in split(out, "\n")
563 let [info, filename] = split(line, "\t")
564 let [mode, sha, stage] = split(info, '\s\+')
565 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
566 while filename =~# '/'
567 let filename = substitute(filename, '/[^/]*$', '', '')
568 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
572 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
573 elseif a:commit =~# '^\x\{40\}$'
574 if !has_key(s:trees, a:dir)
575 let ftime = +system(git . ' log -1 --pretty=format:%ct ' . a:commit)
577 let s:trees[a:dir] = [{}, -1]
578 return s:trees[a:dir]
580 let s:trees[a:dir] = [{}, +ftime]
581 let out = system(git . ' ls-tree -rtl --full-name ' . a:commit)
583 return s:trees[a:dir]
585 for line in split(out, "\n")
586 let [info, filename] = split(line, "\t")
587 let [mode, type, sha, size] = split(info, '\s\+')
588 let s:trees[a:dir][0][filename] = [ftime, mode, type, sha, +size, filename]
591 return s:trees[a:dir]
596 function! s:PathInfo(url) abort
597 let [dir, commit, file] = s:DirCommitFile(a:url)
598 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
599 return [-1, '000000', '', '', -1]
601 let path = substitute(file[1:-1], '/*$', '', '')
602 let [tree, ftime] = s:TreeInfo(dir, commit)
603 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
604 if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
605 return [-1, '000000', '', '', -1]
611 function! fugitive#simplify(url) abort
612 let [dir, commit, file] = s:DirCommitFile(a:url)
616 if file =~# '/\.\.\%(/\|$\)'
617 let tree = s:Tree(dir)
619 let path = simplify(tree . file)
620 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
621 return s:PlatformSlash(path)
625 return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
628 function! fugitive#resolve(url) abort
629 let url = fugitive#simplify(a:url)
630 if url =~? '^fugitive:'
637 function! fugitive#getftime(url) abort
638 return s:PathInfo(a:url)[0]
641 function! fugitive#getfsize(url) abort
642 let entry = s:PathInfo(a:url)
643 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
644 let dir = s:DirCommitFile(a:url)[0]
645 let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
646 let entry[4] = v:shell_error ? -1 : size
651 function! fugitive#getftype(url) abort
652 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
655 function! fugitive#filereadable(url) abort
656 return s:PathInfo(a:url)[2] ==# 'blob'
659 function! fugitive#filewritable(url) abort
660 let [dir, commit, file] = s:DirCommitFile(a:url)
661 if commit !~# '^\d$' || !filewritable(dir . '/index')
664 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
667 function! fugitive#isdirectory(url) abort
668 return s:PathInfo(a:url)[2] ==# 'tree'
671 function! fugitive#getfperm(url) abort
672 let [dir, commit, file] = s:DirCommitFile(a:url)
673 let perm = getfperm(dir)
674 let fperm = s:PathInfo(a:url)[1]
675 if fperm ==# '040000'
679 let perm = tr(perm, 'x', '-')
682 let perm = tr(perm, 'rw', '--')
685 let perm = tr(perm, 'w', '-')
687 return perm ==# '---------' ? '' : perm
690 function! fugitive#setfperm(url, perm) abort
691 let [dir, commit, file] = s:DirCommitFile(a:url)
692 let entry = s:PathInfo(a:url)
693 let perm = fugitive#getfperm(a:url)
694 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
695 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
698 call system(s:Prepare(dir, 'update-index', '--index-info'),
699 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
700 return v:shell_error ? -1 : 0
703 function! s:TempCmd(out, cmd) abort
706 let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
707 let redir = ' > ' . a:out
709 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
710 return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
711 elseif &shell =~# 'fish'
712 return s:System(' begin;' . prefix . cmd . redir . ';end ')
714 return s:System(' (' . prefix . cmd . redir . ') ')
719 if !exists('s:blobdirs')
722 function! s:BlobTemp(url) abort
723 let [dir, commit, file] = s:DirCommitFile(a:url)
727 if !has_key(s:blobdirs, dir)
728 let s:blobdirs[dir] = s:tempname()
730 let tempfile = s:PlatformSlash(s:blobdirs[dir] . '/' . commit . file)
731 let tempparent = fnamemodify(tempfile, ':h')
732 if !isdirectory(tempparent)
733 call mkdir(tempparent, 'p')
735 if commit =~# '^\d$' || !filereadable(tempfile)
736 let rev = s:DirRev(a:url)[1]
737 let command = s:Prepare(dir, 'cat-file', 'blob', rev)
738 call s:TempCmd(tempfile, command)
740 call delete(tempfile)
747 function! fugitive#readfile(url, ...) abort
748 let entry = s:PathInfo(a:url)
749 if entry[2] !=# 'blob'
752 let temp = s:BlobTemp(a:url)
756 return call('readfile', [temp] + a:000)
759 function! fugitive#writefile(lines, url, ...) abort
760 let url = type(a:url) ==# type('') ? a:url : ''
761 let [dir, commit, file] = s:DirCommitFile(url)
762 let entry = s:PathInfo(url)
763 if commit =~# '^\d$' && entry[2] !=# 'tree'
764 let temp = s:tempname()
765 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
766 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
768 call call('writefile', [a:lines, temp] + a:000)
769 let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
770 let mode = len(entry[1]) ? entry[1] : '100644'
771 if !v:shell_error && hash =~# '^\x\{40\}$'
772 call system(s:Prepare(dir, 'update-index', '--index-info'),
773 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
779 return call('writefile', [a:lines, a:url] + a:000)
782 let s:globsubs = {'*': '[^/]*', '**': '.*', '**/': '\%(.*/\)\=', '?': '[^/]'}
783 function! fugitive#glob(url, ...) abort
784 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
785 let append = matchstr(glob, '/*$')
786 let glob = substitute(glob, '/*$', '', '')
787 let pattern = '^' . substitute(glob[1:-1], '\*\*/\=\|[.?*\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g') . '$'
789 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
790 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
793 let files = items(s:TreeInfo(dir, commit)[0])
795 call filter(files, 'v:val[1][2] ==# "tree"')
797 call map(files, 'v:val[0]')
798 call filter(files, 'v:val =~# pattern')
799 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
801 call map(files, 's:PlatformSlash(prepend . v:val . append)')
802 call extend(results, files)
807 return join(results, "\n")
811 function! fugitive#delete(url, ...) abort
812 let [dir, commit, file] = s:DirCommitFile(a:url)
813 if a:0 && len(a:1) || commit !~# '^\d$'
816 let entry = s:PathInfo(a:url)
817 if entry[2] !=# 'blob'
820 call system(s:Prepare(dir, 'update-index', '--index-info'),
821 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
822 return v:shell_error ? -1 : 0
825 let s:buffer_prototype = {}
827 function! fugitive#buffer(...) abort
828 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
829 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
830 if buffer.getvar('git_dir') !=# ''
833 call s:throw('not a git repository: '.bufname(buffer['#']))
836 function! s:buffer_getvar(var) dict abort
837 return getbufvar(self['#'],a:var)
840 function! s:buffer_getline(lnum) dict abort
841 return get(getbufline(self['#'], a:lnum), 0, '')
844 function! s:buffer_repo() dict abort
845 return fugitive#repo(self.getvar('git_dir'))
848 function! s:buffer_type(...) dict abort
849 if !empty(self.getvar('fugitive_type'))
850 let type = self.getvar('fugitive_type')
851 elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
853 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
855 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
857 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
859 elseif isdirectory(self.spec())
860 let type = 'directory'
861 elseif self.spec() == ''
867 return !empty(filter(copy(a:000),'v:val ==# type'))
875 function! s:buffer_spec() dict abort
876 let bufname = bufname(self['#'])
878 for i in split(bufname,'[^:]\zs\\')
879 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
881 return s:Slash(fnamemodify(retval,':p'))
886 function! s:buffer_spec() dict abort
887 let bufname = bufname(self['#'])
888 return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
893 function! s:buffer_name() dict abort
897 function! s:buffer_commit() dict abort
898 return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
901 function! s:buffer_relative(...) dict abort
902 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
904 let rev = s:sub(rev,'\w*','')
905 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
906 \ s:cpath(self.repo().dir() . '/')
907 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
908 elseif !self.repo().bare() &&
909 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
910 \ s:cpath(self.repo().tree() . '/')
911 let rev = self.spec()[strlen(self.repo().tree()) : -1]
913 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
916 function! s:Relative(...) abort
917 return fugitive#Path(@%, a:0 ? a:1 : './')
920 function! s:buffer_path(...) dict abort
922 return self.relative(a:1)
924 return self.relative()
927 function! s:buffer_rev() dict abort
928 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
930 return ':'.rev[0].':'.rev[2:-1]
932 return s:sub(rev,'/',':')
933 elseif self.spec() =~ '\.git/index$'
935 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
936 return self.spec()[strlen(self.repo().dir())+1 : -1]
938 return self.relative('/')
942 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative','rev'])
944 " Section: Completion
946 function! s:GlobComplete(lead, pattern) abort
948 let results = glob(a:lead . a:pattern, 0, 1)
950 let results = split(glob(a:lead . a:pattern), "\n")
952 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
953 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
957 function! fugitive#PathComplete(base, ...) abort
958 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
959 let tree = FugitiveTreeForGitDir(dir) . '/'
960 let strip = '^:\=/\%(\./\)\='
961 let base = substitute(a:base, strip, '', '')
962 if base =~# '^\.git/'
963 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
964 let matches = s:GlobComplete(dir . '/', pattern)
965 let cdir = fugitive#CommonDir(dir)
966 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
967 call extend(matches, s:GlobComplete(cdir . '/', pattern))
970 call map(matches, "'.git/' . v:val")
972 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
976 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
980 function! fugitive#Complete(base, ...) abort
981 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
982 let tree = s:Tree(dir) . '/'
983 if a:base =~# '^\.\=/' || a:base !~# ':'
985 if a:base =~# '^refs/'
986 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
987 elseif a:base !~# '^\.\=/'
988 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
989 let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
990 if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
991 let heads += ["stash"]
992 let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
994 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
997 call map(results, 's:fnameescape(v:val)')
999 let results += fugitive#PathComplete(a:base, dir)
1003 elseif a:base =~# '^:'
1004 let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1005 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1006 if a:base !~# '^:[0-3]\%(:\|$\)'
1007 call filter(entries,'v:val[1] == "0"')
1008 call map(entries,'v:val[2:-1]')
1010 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1011 return map(entries, 's:fnameescape(v:val)')
1014 let tree = matchstr(a:base,'.*[:/]')
1015 let entries = split(s:TreeChomp(['ls-tree',tree], dir),"\n")
1016 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1017 call map(entries,'tree.s:sub(v:val,".*\t","")')
1018 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1019 return map(entries, 's:fnameescape(v:val)')
1023 " Section: File access
1025 function! s:ReplaceCmd(cmd) abort
1026 let tmp = tempname()
1027 let err = s:TempCmd(tmp, a:cmd)
1029 call s:throw((len(err) ? err : filereadable(tmp) ? join(readfile(tmp), ' ') : 'unknown error running ' . a:cmd))
1031 let fn = expand('%:p')
1032 silent exe 'doau BufReadPre '.s:fnameescape(fn)
1033 silent exe 'keepalt file '.tmp
1035 silent noautocmd edit!
1038 silent exe 'keepalt file '.s:fnameescape(fn)
1039 catch /^Vim\%((\a\+)\)\=:E302:/
1042 if fnamemodify(bufname('$'), ':p') ==# tmp
1043 silent execute 'bwipeout '.bufnr('$')
1045 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1049 function! fugitive#BufReadStatus() abort
1050 let amatch = s:Slash(expand('%:p'))
1051 if !exists('b:fugitive_display_format')
1052 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1054 let b:fugitive_display_format = b:fugitive_display_format % 2
1055 let b:fugitive_type = 'index'
1057 let dir = fnamemodify(amatch, ':h')
1058 setlocal noro ma nomodeline
1060 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1062 let old_index = $GIT_INDEX_FILE
1064 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(amatch).' '
1067 if b:fugitive_display_format
1068 let cmd = ['ls-files', '--stage']
1069 elseif fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.'
1070 let cmd = ['status']
1073 \ '-c', 'status.displayCommentPrefix=true',
1074 \ '-c', 'color.status=false',
1075 \ '-c', 'status.short=false',
1078 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1080 let cmd_str = prefix . call('s:Prepare', [dir] + cmd)
1082 if exists('old_index')
1083 let $GIT_INDEX_FILE = amatch
1085 execute cd s:fnameescape(s:Tree(dir))
1086 call s:ReplaceCmd(cmd_str)
1088 if exists('old_index')
1089 let $GIT_INDEX_FILE = old_index
1091 execute cd s:fnameescape(cwd)
1093 if b:fugitive_display_format
1094 if &filetype !=# 'git'
1099 if &filetype !=# 'gitcommit'
1100 set filetype=gitcommit
1102 set foldtext=fugitive#Foldtext()
1104 setlocal readonly nomodifiable nomodified noswapfile
1105 if &bufhidden ==# ''
1106 setlocal bufhidden=delete
1108 call fugitive#MapJumps()
1111 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1112 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1113 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
1114 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
1115 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1116 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1117 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1118 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>
1119 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1120 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1121 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1122 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1123 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1124 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1125 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1126 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1127 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1128 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1129 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1130 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1131 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1132 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1133 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1134 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1135 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1136 nnoremap <buffer> <silent> r :<C-U>edit<CR>
1137 nnoremap <buffer> <silent> R :<C-U>edit<CR>
1138 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1139 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
1140 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1142 return 'echoerr v:errmsg'
1146 function! fugitive#FileReadCmd(...) abort
1147 let amatch = a:0 ? a:1 : expand('<amatch>')
1148 let [dir, rev] = s:DirRev(amatch)
1149 let line = a:0 > 1 ? a:2 : line("'[")
1151 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1154 let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev)
1156 let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1158 return line . 'read !' . escape(cmd, '!#%')
1161 function! fugitive#FileWriteCmd(...) abort
1162 let tmp = tempname()
1163 let amatch = a:0 ? a:1 : expand('<amatch>')
1164 let autype = a:0 > 1 ? 'Buf' : 'File'
1165 if exists('#' . autype . 'WritePre')
1166 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1169 let [dir, commit, file] = s:DirCommitFile(amatch)
1170 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1171 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1173 silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin').' > '.tmp
1174 let sha1 = readfile(tmp)[0]
1175 let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', file[1:-1])), '^\d\+')
1177 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1179 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1180 call writefile([info],tmp)
1182 let error = s:System('type '.s:gsub(tmp,'/','\\').'|'.s:Prepare(dir, 'update-index', '--index-info'))
1184 let error = s:System(s:Prepare(dir, 'update-index', '--index-info').' < '.tmp)
1186 if v:shell_error == 0
1188 if exists('#' . autype . 'WritePost')
1189 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1191 call fugitive#ReloadStatus()
1194 return 'echoerr '.string('fugitive: '.error)
1201 function! fugitive#BufReadCmd(...) abort
1202 let amatch = a:0 ? a:1 : expand('<amatch>')
1204 let [dir, rev] = s:DirRev(amatch)
1206 return 'echo "Invalid Fugitive URL"'
1209 let b:fugitive_type = 'stage'
1211 let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1212 if v:shell_error && rev =~# '^:0'
1213 let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1214 let b:fugitive_type = 'tree'
1217 unlet b:fugitive_type
1219 let &readonly = !filewritable(dir . '/index')
1220 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1222 setlocal readonly nomodifiable
1225 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1226 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1228 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1229 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1233 if b:fugitive_type !=# 'blob'
1237 setlocal noreadonly modifiable
1238 let pos = getpos('.')
1239 silent keepjumps %delete_
1243 if b:fugitive_type ==# 'tree'
1244 let b:fugitive_display_format = b:fugitive_display_format % 2
1245 if b:fugitive_display_format
1246 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1249 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev))[0:-2]
1251 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1253 elseif b:fugitive_type ==# 'tag'
1254 let b:fugitive_display_format = b:fugitive_display_format % 2
1255 if b:fugitive_display_format
1256 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1258 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1260 elseif b:fugitive_type ==# 'commit'
1261 let b:fugitive_display_format = b:fugitive_display_format % 2
1262 if b:fugitive_display_format
1263 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1265 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])
1266 keepjumps call search('^parent ')
1267 if getline('.') ==# 'parent '
1268 silent keepjumps delete_
1270 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1272 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1274 silent keepjumps delete_
1276 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1279 elseif b:fugitive_type ==# 'stage'
1280 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1281 elseif b:fugitive_type ==# 'blob'
1282 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1286 keepjumps call setpos('.',pos)
1287 setlocal nomodified noswapfile
1289 setlocal nomodifiable
1291 let &modifiable = b:fugitive_type !=# 'tree'
1293 let &readonly = !&modifiable || !filewritable(dir . '/index')
1294 if &bufhidden ==# ''
1295 setlocal bufhidden=delete
1297 if b:fugitive_type !=# 'blob'
1298 setlocal filetype=git foldmethod=syntax
1299 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1300 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1302 call fugitive#MapJumps()
1308 return 'echoerr v:errmsg'
1312 function! fugitive#BufWriteCmd(...) abort
1313 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1316 function! fugitive#SourceCmd(...) abort
1317 let amatch = a:0 ? a:1 : expand('<amatch>')
1318 let temp = s:BlobTemp(amatch)
1320 return 'noautocmd source ' . s:fnameescape(amatch)
1322 if !exists('g:virtual_scriptnames')
1323 let g:virtual_scriptnames = {}
1325 let g:virtual_scriptnames[temp] = amatch
1326 return 'source ' . s:fnameescape(temp)
1329 " Section: Temp files
1331 if !exists('s:temp_files')
1332 let s:temp_files = {}
1335 augroup fugitive_temp
1337 autocmd BufNewFile,BufReadPost *
1338 \ if has_key(s:temp_files,s:cpath(expand('<afile>:p'))) |
1339 \ let b:git_dir = s:temp_files[s:cpath(expand('<afile>:p'))].dir |
1340 \ call extend(b:, {'fugitive_type': 'temp'}, 'keep') |
1341 \ call FugitiveDetect(expand('<afile>:p')) |
1342 \ setlocal bufhidden=delete nobuflisted |
1343 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
1349 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1351 function! s:Git(bang, mods, args) abort
1353 return s:Edit('edit', 1, a:mods, a:args)
1355 let git = s:UserCommand()
1356 if has('gui_running') && !has('win32')
1357 let git .= ' --no-pager'
1359 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1360 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1363 let after = '|call fugitive#ReloadStatus()' . after
1365 let exec = escape(git . ' ' . s:ShellExpand(args), '!#%')
1366 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1372 execute 'lcd' fnameescape(tree)
1373 return 'exe ' . string('terminal ' . exec) . after
1375 let cmd = 'exe ' . string('!' . exec)
1376 if s:cpath(tree) !=# s:cpath(getcwd())
1377 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1378 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1384 let s:exec_paths = {}
1385 function! s:Subcommands() abort
1386 if !has_key(s:exec_paths, g:fugitive_git_executable)
1387 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1389 let exec_path = s:exec_paths[g:fugitive_git_executable]
1390 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1394 function! s:Aliases() abort
1395 if !has_key(s:aliases, b:git_dir)
1396 let s:aliases[b:git_dir] = {}
1397 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1398 for line in v:shell_error ? [] : lines
1399 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1402 return s:aliases[b:git_dir]
1405 function! s:GitComplete(A, L, P) abort
1406 let pre = strpart(a:L, 0, a:P)
1407 if pre !~# ' [[:alnum:]-]\+ '
1408 let cmds = s:Subcommands()
1409 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1410 elseif pre =~# ' -- '
1411 return fugitive#PathComplete(a:A)
1413 return fugitive#Complete(a:A, a:L, a:P)
1417 " Section: Gcd, Glcd
1419 function! s:DirComplete(A, L, P) abort
1420 let base = s:sub(a:A,'^/','')
1421 let matches = split(glob(s:Tree() . '/' . s:gsub(base,'/','*&').'*/'),"\n")
1422 call map(matches,'s:Slash(v:val[ strlen(s:Tree())+(a:A !~ "^/") : -1 ])')
1426 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>)")
1427 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>)")
1431 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1432 augroup fugitive_status
1435 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1436 autocmd BufDelete term://* call fugitive#ReloadStatus()
1440 function! s:Status(bang, count, mods) abort
1442 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1444 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1445 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1447 return 'echoerr v:errmsg'
1452 function! fugitive#ReloadStatus() abort
1453 if exists('s:reloading_status')
1457 let s:reloading_status = 1
1458 let mytab = tabpagenr()
1459 for tab in [mytab] + range(1,tabpagenr('$'))
1460 for winnr in range(1,tabpagewinnr(tab,'$'))
1461 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1462 execute 'tabnext '.tab
1464 execute winnr.'wincmd w'
1465 let restorewinnr = 1
1469 call fugitive#BufReadStatus()
1472 if exists('restorewinnr')
1475 execute 'tabnext '.mytab
1481 unlet! s:reloading_status
1485 function! fugitive#reload_status() abort
1486 return fugitive#ReloadStatus()
1489 function! s:stage_info(lnum) abort
1490 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1492 if has('multi_byte_encoding')
1493 let colon = '\%(:\|\%uff1a\)'
1497 while lnum && getline(lnum) !~# colon.'$'
1502 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1503 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1504 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1505 return [filename, 'untracked']
1506 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1507 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1508 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1509 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1511 return ['', 'unknown']
1515 function! s:StageNext(count) abort
1516 for i in range(a:count)
1517 call search('^.\=\t.*','W')
1522 function! s:StagePrevious(count) abort
1523 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1524 return 'CtrlP '.fnameescape(s:Tree())
1526 for i in range(a:count)
1527 call search('^.\=\t.*','Wbe')
1533 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1535 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1536 if f !=# '' | let jump = f | endif
1537 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1538 if f !=# '' | let jump = f | endif
1542 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1545 function! s:StageUndo() abort
1546 let [filename, section] = s:stage_info(line('.'))
1550 let hash = s:TreeChomp('hash-object', '-w', filename)
1552 if section ==# 'untracked'
1553 call s:TreeChomp('clean', '-f', '--', filename)
1554 elseif section ==# 'unmerged'
1555 call s:TreeChomp('rm', '--', filename)
1556 elseif section ==# 'unstaged'
1557 call s:TreeChomp('checkout', '--', filename)
1559 call s:TreeChomp('checkout', 'HEAD', '--', filename)
1561 call s:StageReloadSeek(filename, line('.'), line('.'))
1563 return 'checktime|redraw|echomsg ' .
1564 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1568 function! s:StageDiff(diff) abort
1569 let [filename, section] = s:stage_info(line('.'))
1570 if filename ==# '' && section ==# 'staged'
1571 return 'Git! diff --no-ext-diff --cached'
1572 elseif filename ==# ''
1573 return 'Git! diff --no-ext-diff'
1574 elseif filename =~# ' -> '
1575 let [old, new] = split(filename,' -> ')
1576 execute 'Gedit '.s:fnameescape(':0:'.new)
1577 return a:diff.' HEAD:'.s:fnameescape(old)
1578 elseif section ==# 'staged'
1579 execute 'Gedit '.s:fnameescape(':0:'.filename)
1582 execute 'Gedit '.s:fnameescape('/'.filename)
1587 function! s:StageDiffEdit() abort
1588 let [filename, section] = s:stage_info(line('.'))
1589 let arg = (filename ==# '' ? '.' : filename)
1590 if section ==# 'staged'
1591 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1592 elseif section ==# 'untracked'
1593 call s:TreeChomp('add','--intent-to-add',arg)
1597 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1598 call search(':$','W')
1601 call s:StageReloadSeek(arg,line('.'),line('.'))
1605 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1609 function! s:StageToggle(lnum1,lnum2) abort
1610 if a:lnum1 == 1 && a:lnum2 == 1
1611 return 'Gedit /.git|call search("^index$", "wc")'
1615 for lnum in range(a:lnum1,a:lnum2)
1616 let [filename, section] = s:stage_info(lnum)
1617 if getline('.') =~# ':$'
1618 if section ==# 'staged'
1619 call s:TreeChomp('reset','-q')
1622 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1623 call search(':$','W')
1626 elseif section ==# 'unstaged'
1627 call s:TreeChomp('add','-u')
1630 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1631 call search(':$','W')
1635 call s:TreeChomp('add','.')
1638 call search(':$','W')
1646 if section ==# 'staged'
1647 if filename =~ ' -> '
1648 let files_to_unstage = split(filename,' -> ')
1650 let files_to_unstage = [filename]
1652 let filename = files_to_unstage[-1]
1653 let cmd = ['reset','-q','--'] + files_to_unstage
1654 elseif getline(lnum) =~# '^.\=\tdeleted:'
1655 let cmd = ['rm','--',filename]
1656 elseif getline(lnum) =~# '^.\=\tmodified:'
1657 let cmd = ['add','--',filename]
1659 let cmd = ['add','-A','--',filename]
1661 if !exists('first_filename')
1662 let first_filename = filename
1664 let output .= call('s:TreeChomp', cmd)."\n"
1666 if exists('first_filename')
1667 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1669 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1671 return 'echoerr v:errmsg'
1676 function! s:StagePatch(lnum1,lnum2) abort
1680 for lnum in range(a:lnum1,a:lnum2)
1681 let [filename, section] = s:stage_info(lnum)
1682 if getline('.') =~# ':$' && section ==# 'staged'
1683 return 'Git reset --patch'
1684 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1685 return 'Git add --patch'
1686 elseif getline('.') =~# ':$' && section ==# 'untracked'
1687 return 'Git add -N .'
1688 elseif filename ==# ''
1691 if !exists('first_filename')
1692 let first_filename = filename
1695 if filename =~ ' -> '
1696 let reset += [split(filename,' -> ')[1]]
1697 elseif section ==# 'staged'
1698 let reset += [filename]
1699 elseif getline(lnum) !~# '^.\=\tdeleted:'
1700 let add += [filename]
1705 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1708 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1710 if exists('first_filename')
1714 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1717 return 'echoerr v:errmsg'
1724 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1726 function! s:Commit(mods, args, ...) abort
1727 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1728 let dir = a:0 ? a:1 : b:git_dir
1729 let tree = s:Tree(dir)
1730 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1732 let msgfile = dir . '/COMMIT_EDITMSG'
1733 let outfile = tempname()
1734 let errorfile = tempname()
1736 let guioptions = &guioptions
1738 if &guioptions =~# '!'
1739 setglobal guioptions-=!
1741 execute cd s:fnameescape(tree)
1744 let old_editor = $GIT_EDITOR
1745 let $GIT_EDITOR = 'false'
1747 let command = 'env GIT_EDITOR=false '
1749 let args = s:ShellExpand(a:args)
1750 let command .= s:UserCommand() . ' commit ' . args
1752 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1753 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1754 noautocmd execute '!'.command.' 2> '.errorfile
1756 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1758 let error = v:shell_error
1760 execute cd s:fnameescape(cwd)
1761 let &guioptions = guioptions
1763 if !has('gui_running')
1767 if filereadable(outfile)
1768 for line in readfile(outfile)
1774 let errors = readfile(errorfile)
1775 let error = get(errors,-2,get(errors,-1,'!'))
1776 if error =~# 'false''\=\.$'
1777 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1778 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1779 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1781 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1782 let args = '-F '.s:shellesc(msgfile).' '.args
1783 if args !~# '\%(^\| \)--cleanup\>'
1784 let args = '--cleanup=strip '.args
1786 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1787 execute mods 'keepalt edit' s:fnameescape(msgfile)
1788 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1789 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1790 elseif get(b:, 'fugitive_type', '') ==# 'index'
1791 execute mods 'keepalt edit' s:fnameescape(msgfile)
1792 execute (search('^#','n')+1).'wincmd+'
1793 setlocal nopreviewwindow
1795 execute mods 'keepalt split' s:fnameescape(msgfile)
1797 let b:fugitive_commit_arguments = args
1798 setlocal bufhidden=wipe filetype=gitcommit
1800 elseif error ==# '!'
1803 call s:throw(empty(error)?join(errors, ' '):error)
1807 return 'echoerr v:errmsg'
1809 if exists('old_editor')
1810 let $GIT_EDITOR = old_editor
1812 call delete(outfile)
1813 call delete(errorfile)
1814 call fugitive#ReloadStatus()
1818 function! s:CommitComplete(A,L,P) abort
1819 if a:A =~# '^--fixup=\|^--squash='
1820 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
1822 let pre = matchstr(a:A, '^--\w*=') . ':/^'
1823 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
1825 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1826 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']
1827 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1829 return fugitive#PathComplete(a:A)
1834 function! s:FinishCommit() abort
1835 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1837 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1838 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
1843 " Section: Gmerge, Gpull
1845 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1846 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1847 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
1848 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
1849 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1850 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1852 function! s:RevisionComplete(A, L, P) abort
1853 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1854 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1857 function! s:RemoteComplete(A, L, P) abort
1858 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1860 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
1861 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1862 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1864 let matches = split(s:TreeChomp('remote'), "\n")
1866 return join(matches, "\n")
1869 function! fugitive#Cwindow() abort
1870 if &buftype == 'quickfix'
1874 if &buftype == 'quickfix'
1880 let s:common_efm = ''
1882 \ . '%+Eusage:%.%#,'
1883 \ . '%+Eerror:%.%#,'
1884 \ . '%+Efatal:%.%#,'
1885 \ . '%-G%.%#%\e[K%.%#,'
1886 \ . '%-G%.%#%\r%.%\+'
1888 function! s:Merge(cmd, bang, args) abort
1889 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
1890 return 'echoerr "git rebase --interactive not supported"'
1892 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1894 let [mp, efm] = [&l:mp, &l:efm]
1895 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
1897 let &l:errorformat = ''
1898 \ . '%-Gerror:%.%#false''.,'
1899 \ . '%-G%.%# ''git commit'' %.%#,'
1900 \ . '%+Emerge:%.%#,'
1901 \ . s:common_efm . ','
1902 \ . '%+ECannot %.%#: You have unstaged changes.,'
1903 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1904 \ . '%+EThere is no tracking information for the current branch.,'
1905 \ . '%+EYou are not currently on a branch. Please specify which,'
1906 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1907 \ . 'CONFLICT (%m): Merge conflict in %f,'
1908 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1909 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1910 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1911 \ . '%+ECONFLICT %.%#,'
1912 \ . '%+EKONFLIKT %.%#,'
1913 \ . '%+ECONFLIT %.%#,'
1914 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1915 \ . "%+E\u51b2\u7a81 %.%#,"
1917 if a:cmd =~# '^merge' && empty(a:args) &&
1918 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
1919 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
1920 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1922 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
1923 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
1924 \ ' ' . a:args, ' *$', '')
1926 if !empty($GIT_EDITOR) || has('win32')
1927 let old_editor = $GIT_EDITOR
1928 let $GIT_EDITOR = 'false'
1930 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1932 execute cd fnameescape(s:Tree())
1933 silent noautocmd make!
1934 catch /^Vim\%((\a\+)\)\=:E211/
1935 let err = v:exception
1938 let [&l:mp, &l:efm] = [mp, efm]
1939 if exists('old_editor')
1940 let $GIT_EDITOR = old_editor
1942 execute cd fnameescape(cwd)
1944 call fugitive#ReloadStatus()
1945 if empty(filter(getqflist(),'v:val.valid'))
1946 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
1948 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
1951 let qflist = getqflist()
1956 let e.pattern = '^<<<<<<<'
1959 call fugitive#Cwindow()
1961 call setqflist(qflist, 'r')
1966 return exists('err') ? 'echoerr '.string(err) : ''
1969 " Section: Ggrep, Glog
1971 if !exists('g:fugitive_summary_format')
1972 let g:fugitive_summary_format = '%s'
1975 function! s:GrepComplete(A, L, P) abort
1976 if strpart(a:L, 0, a:P) =~# ' -- '
1977 return fugitive#PathComplete(a:A)
1979 return fugitive#Complete(a:A)
1983 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1984 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1985 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
1986 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
1988 function! s:Grep(cmd,bang,arg) abort
1989 let grepprg = &grepprg
1990 let grepformat = &grepformat
1991 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1994 execute cd s:fnameescape(s:Tree())
1995 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
1996 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
1997 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
1998 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2000 if bufname(entry.bufnr) =~ ':'
2001 let entry.filename = s:Generate(bufname(entry.bufnr))
2004 elseif a:arg =~# '\%(^\| \)--cached\>'
2005 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2010 if a:cmd =~# '^l' && exists('changed')
2011 call setloclist(0, list, 'r')
2012 elseif exists('changed')
2013 call setqflist(list, 'r')
2015 if !a:bang && !empty(list)
2016 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2018 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2021 let &grepprg = grepprg
2022 let &grepformat = grepformat
2023 execute cd s:fnameescape(dir)
2027 function! s:Log(cmd, bang, line1, line2, ...) abort
2028 let args = ' ' . join(a:000, ' ')
2029 let before = substitute(args, ' --\S\@!.*', '', '')
2030 let after = strpart(args, len(before))
2031 let path = s:Relative('/')
2032 if path =~# '^/\.git\%(/\|$\)' || len(after)
2035 let relative = s:Relative('')
2036 if before !~# '\s[^[:space:]-]'
2037 let commit = matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')
2039 let before .= ' ' . commit
2040 elseif relative =~# '^\.git/refs/\|^\.git/.*HEAD$'
2041 let before .= ' ' . relative[5:-1]
2044 if relative =~# '^\.git\%(/\|$\)'
2047 if len(relative) && a:line2 > 0
2048 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2049 elseif len(relative) && (empty(after) || a:line2 == 0)
2050 let after = (len(after) > 3 ? after : ' -- ') . relative
2052 let grepformat = &grepformat
2053 let grepprg = &grepprg
2054 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2057 execute cd s:fnameescape(s:Tree())
2058 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2059 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2060 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2061 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2063 let &grepformat = grepformat
2064 let &grepprg = grepprg
2065 execute cd s:fnameescape(dir)
2069 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
2071 function! s:UsableWin(nr) abort
2072 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2073 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2076 function! s:EditParse(args, dir) abort
2078 let args = copy(a:args)
2079 while !empty(args) && args[0] =~# '^+'
2080 call add(pre, escape(remove(args, 0), ' |"') . ' ')
2083 let file = join(args)
2084 elseif empty(expand('%'))
2086 elseif empty(s:DirCommitFile(@%)[1]) && fugitive#Path(@%, './', a:dir) !~# '^\./\.git\>'
2087 let file = fugitive#Path(@%, ':0:', a:dir)
2089 let file = fugitive#Path(@%, './', a:dir)
2091 return [s:Expand(file, a:dir), join(pre)]
2094 function! s:Edit(cmd, bang, mods, args, ...) abort
2096 let mods = a:mods ==# '<mods>' ? '' : a:mods
2097 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index' && a:cmd ==# 'edit'
2098 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2100 exe winnrs[0].'wincmd w'
2101 elseif winnr('$') == 1
2102 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2103 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2108 let mywinnr = winnr()
2109 for winnr in range(winnr('$'),1,-1)
2110 if winnr != mywinnr && getwinvar(winnr,'&diff')
2111 execute winnr.'wincmd w'
2123 let temp = s:tempname()
2124 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir }
2125 silent execute mods a:cmd temp
2126 if a:cmd =~# 'pedit'
2129 let echo = call('s:Read', [-1, 0, 1, 1, 1, mods, a:args] + a:000)
2131 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
2132 if getline(1) !~# '^diff '
2133 setlocal readonly nomodifiable
2135 if a:cmd =~# 'pedit'
2141 let [file, pre] = s:EditParse(a:000, dir)
2143 let file = s:Generate(file, dir)
2145 return 'echoerr v:errmsg'
2147 if file !~# '^fugitive:'
2148 let file = s:sub(file, '/$', '')
2150 return mods . ' ' . a:cmd . ' ' . pre . s:fnameescape(file)
2153 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2154 let mods = a:mods ==# '<mods>' ? '' : a:mods
2157 let delete = 'silent 1,' . line('$') . 'delete_|'
2158 let after = line('$')
2160 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2165 let args = s:ShellExpand(a:args)
2166 let git = s:UserCommand()
2167 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2170 execute cd s:fnameescape(s:Tree())
2171 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '#%!')
2173 execute cd s:fnameescape(cwd)
2175 execute delete . 'diffupdate'
2176 call fugitive#ReloadStatus()
2177 return 'redraw|echo '.string(':!'.git.' '.args)
2179 let [file, pre] = s:EditParse(a:000, b:git_dir)
2181 let file = s:Generate(file)
2183 return 'echoerr v:errmsg'
2185 return mods . ' ' . after . 'read ' . pre . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2188 function! s:EditRunComplete(A,L,P) abort
2190 return s:GitComplete(a:A, a:L, a:P)
2192 return fugitive#Complete(a:A, a:L, a:P)
2196 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2197 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2198 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2199 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>)")
2200 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>)")
2201 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>)")
2202 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>)")
2204 " Section: Gwrite, Gwq
2206 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2207 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2208 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2210 function! s:Write(force,...) abort
2211 if exists('b:fugitive_commit_arguments')
2212 return 'write|bdelete'
2213 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2215 elseif get(b:, 'fugitive_type', '') ==# 'index'
2217 elseif s:Relative('') ==# '' && getline(4) =~# '^+++ '
2218 let filename = getline(4)[6:-1]
2221 setlocal buftype=nowrite
2222 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2223 let err = s:TreeChomp('apply', '--cached', '--reverse', expand('%:p'))
2225 let err = s:TreeChomp('apply', '--cached', expand('%:p'))
2228 let v:errmsg = split(err,"\n")[0]
2229 return 'echoerr v:errmsg'
2233 return 'Gedit '.fnameescape(filename)
2236 let mytab = tabpagenr()
2237 let mybufnr = bufnr('')
2238 let path = a:0 ? s:Expand(join(a:000, ' ')) : s:Relative()
2240 return 'echoerr '.string('fugitive: cannot determine file path')
2242 if path =~# '^:\d\>'
2243 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:Generate(path))
2245 let always_permitted = ((s:Relative() ==# path || s:Relative('') ==# path) && s:DirCommitFile(@%)[1] =~# '^0\=$')
2246 if !always_permitted && !a:force && (len(s:TreeChomp('diff','--name-status','HEAD','--',path)) || len(s:TreeChomp('ls-files','--others','--',path)))
2247 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2248 return 'echoerr v:errmsg'
2250 let file = s:Generate(path)
2252 for nr in range(1,bufnr('$'))
2253 if fnamemodify(bufname(nr),':p') ==# file
2258 if treebufnr > 0 && treebufnr != bufnr('')
2259 let temp = tempname()
2260 silent execute '%write '.temp
2261 for tab in [mytab] + range(1,tabpagenr('$'))
2262 for winnr in range(1,tabpagewinnr(tab,'$'))
2263 if tabpagebuflist(tab)[winnr-1] == treebufnr
2264 execute 'tabnext '.tab
2266 execute winnr.'wincmd w'
2267 let restorewinnr = 1
2270 let lnum = line('.')
2271 let last = line('$')
2272 silent execute '$read '.temp
2273 silent execute '1,'.last.'delete_'
2278 if exists('restorewinnr')
2281 execute 'tabnext '.mytab
2287 call writefile(readfile(temp,'b'),file,'b')
2290 execute 'write! '.s:fnameescape(s:Generate(path))
2294 let error = s:TreeChomp('add', '--force', '--', path)
2296 let error = s:TreeChomp('add', '--', path)
2299 let v:errmsg = 'fugitive: '.error
2300 return 'echoerr v:errmsg'
2302 if s:Relative('') ==# path && s:DirCommitFile(@%)[1] =~# '^\d$'
2306 let one = s:Generate(':1:'.path)
2307 let two = s:Generate(':2:'.path)
2308 let three = s:Generate(':3:'.path)
2309 for nr in range(1,bufnr('$'))
2310 let name = fnamemodify(bufname(nr), ':p')
2311 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2312 execute nr.'bdelete'
2317 let zero = s:Generate(':0:'.path)
2318 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2319 for tab in range(1,tabpagenr('$'))
2320 for winnr in range(1,tabpagewinnr(tab,'$'))
2321 let bufnr = tabpagebuflist(tab)[winnr-1]
2322 let bufname = fnamemodify(bufname(bufnr), ':p')
2323 if bufname ==# zero && bufnr != mybufnr
2324 execute 'tabnext '.tab
2326 execute winnr.'wincmd w'
2327 let restorewinnr = 1
2330 let lnum = line('.')
2331 let last = line('$')
2332 silent execute '$read '.s:fnameescape(file)
2333 silent execute '1,'.last.'delete_'
2338 if exists('restorewinnr')
2341 execute 'tabnext '.mytab
2347 call fugitive#ReloadStatus()
2351 function! s:Wq(force,...) abort
2352 let bang = a:force ? '!' : ''
2353 if exists('b:fugitive_commit_arguments')
2356 let result = call(s:function('s:Write'),[a:force]+a:000)
2357 if result =~# '^\%(write\|wq\|echoerr\)'
2358 return s:sub(result,'^write','wq')
2360 return result.'|quit'.bang
2364 augroup fugitive_commit
2366 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2369 " Section: Gpush, Gfetch
2371 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2372 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2374 function! s:Dispatch(bang, args)
2375 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2377 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2379 let b:current_compiler = 'git'
2380 let &l:errorformat = s:common_efm
2381 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2382 execute cd fnameescape(s:Tree())
2383 if exists(':Make') == 2
2386 silent noautocmd make!
2388 return 'call fugitive#Cwindow()'
2392 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2393 if empty(cc) | unlet! b:current_compiler | endif
2394 execute cd fnameescape(cwd)
2400 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2401 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2402 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2404 augroup fugitive_diff
2406 autocmd BufWinLeave *
2407 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2408 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2410 autocmd BufWinEnter *
2411 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2412 \ call s:diffoff() |
2416 function! s:can_diffoff(buf) abort
2417 return getwinvar(bufwinnr(a:buf), '&diff') &&
2418 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2419 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2422 function! fugitive#CanDiffoff(buf) abort
2423 return s:can_diffoff(a:buf)
2426 function! s:diff_modifier(count) abort
2427 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2428 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2430 elseif &diffopt =~# 'vertical'
2431 return 'keepalt vert '
2432 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2435 return 'keepalt vert '
2439 function! s:diff_window_count() abort
2441 for nr in range(1,winnr('$'))
2442 let c += getwinvar(nr,'&diff')
2447 function! s:diff_restore() abort
2448 let restore = 'setlocal nodiff noscrollbind'
2449 \ . ' scrollopt=' . &l:scrollopt
2450 \ . (&l:wrap ? ' wrap' : ' nowrap')
2451 \ . ' foldlevel=999'
2452 \ . ' foldmethod=' . &l:foldmethod
2453 \ . ' foldcolumn=' . &l:foldcolumn
2454 \ . ' foldlevel=' . &l:foldlevel
2455 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2456 if has('cursorbind')
2457 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2462 function! s:diffthis() abort
2464 let w:fugitive_diff_restore = s:diff_restore()
2469 function! s:diffoff() abort
2470 if exists('w:fugitive_diff_restore')
2471 execute w:fugitive_diff_restore
2472 unlet w:fugitive_diff_restore
2478 function! s:diffoff_all(dir) abort
2479 let curwin = winnr()
2480 for nr in range(1,winnr('$'))
2481 if getwinvar(nr,'&diff')
2483 execute nr.'wincmd w'
2484 let restorewinnr = 1
2486 if exists('b:git_dir') && b:git_dir ==# a:dir
2491 execute curwin.'wincmd w'
2494 function! s:CompareAge(mine, theirs) abort
2495 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2496 let mine = substitute(a:mine, '^:', '', '')
2497 let theirs = substitute(a:theirs, '^:', '', '')
2498 let my_score = get(scores, ':'.mine, 0)
2499 let their_score = get(scores, ':'.theirs, 0)
2500 if my_score || their_score
2501 return my_score < their_score ? -1 : my_score != their_score
2502 elseif mine ==# theirs
2505 let base = s:TreeChomp('merge-base', mine, theirs)
2508 elseif base ==# theirs
2511 let my_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:mine)
2512 let their_time = +s:TreeChomp('log','--max-count=1','--pretty=format:%at',a:theirs)
2513 return my_time < their_time ? -1 : my_time != their_time
2516 function! s:Diff(vert,keepfocus,...) abort
2517 let args = copy(a:000)
2519 if get(args, 0) =~# '^+'
2520 let post = remove(args, 0)[1:-1]
2522 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2523 let commit = s:DirCommitFile(@%)[1]
2524 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2525 if exists(':DiffGitCached')
2526 return 'DiffGitCached'
2527 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', s:Relative('')))
2528 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2530 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2531 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2535 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2536 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2541 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2542 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2545 let arg = join(args, ' ')
2549 let file = s:Relative('/')
2551 let file = s:Relative(':0:')
2552 elseif arg =~# '^:/.'
2554 let file = fugitive#RevParse(arg).s:Relative(':')
2556 return 'echoerr v:errmsg'
2559 let file = s:Expand(arg)
2561 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2562 let file = file.s:Relative(':')
2565 let file = s:Relative(empty(commit) ? ':0:' : '/')
2568 let spec = s:Generate(file)
2569 let restore = s:diff_restore()
2570 if exists('+cursorbind')
2573 let w:fugitive_diff_restore = restore
2574 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2575 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2577 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2579 let &l:readonly = &l:readonly
2581 let w:fugitive_diff_restore = restore
2583 if getwinvar('#', '&diff')
2586 call feedkeys(winnr."\<C-W>w", 'n')
2591 return 'echoerr v:errmsg'
2595 " Section: Gmove, Gremove
2597 function! s:Move(force, rename, destination) abort
2598 if a:destination =~# '^[.:]\=/'
2599 let destination = substitute(a:destination[1:-1], '^[.:]\=/', '', '')
2601 let destination = fnamemodify(s:Relative(''), ':h') . '/' . a:destination
2603 let destination = a:destination
2608 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', s:Relative(''), destination])
2610 let v:errmsg = 'fugitive: '.message
2611 return 'echoerr v:errmsg'
2613 let destination = s:Tree() . '/' . destination
2614 if isdirectory(destination)
2615 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2617 call fugitive#ReloadStatus()
2618 if empty(s:DirCommitFile(@%)[1])
2619 if isdirectory(destination)
2620 return 'keepalt edit '.s:fnameescape(destination)
2622 return 'keepalt saveas! '.s:fnameescape(destination)
2625 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2629 function! s:RenameComplete(A,L,P) abort
2631 return fugitive#PathComplete(a:A)
2633 let pre = '/' . fnamemodify(s:Relative(''), ':h') . '/'
2634 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2638 function! s:Remove(after, force) abort
2639 if s:DirCommitFile(@%)[1] ==# ''
2641 elseif s:DirCommitFile(@%)[1] ==# '0'
2642 let cmd = ['rm','--cached']
2644 let v:errmsg = 'fugitive: rm not supported here'
2645 return 'echoerr v:errmsg'
2648 let cmd += ['--force']
2650 let message = call('s:TreeChomp', cmd+['--',s:Relative('')])
2652 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2653 return 'echoerr '.string(v:errmsg)
2655 call fugitive#ReloadStatus()
2656 return a:after . (a:force ? '!' : '')
2660 augroup fugitive_remove
2662 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2663 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2664 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2665 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2666 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2672 function! s:Keywordprg() abort
2673 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2674 if has('gui_running') && !has('win32')
2675 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2677 return s:UserCommand() . args . ' show'
2681 augroup fugitive_blame
2683 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2684 autocmd Syntax fugitiveblame call s:BlameSyntax()
2685 autocmd User Fugitive
2686 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2687 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2689 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2690 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2693 function! s:linechars(pattern) abort
2694 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2695 if exists('*synconcealed') && &conceallevel > 1
2696 for col in range(1, chars)
2697 let chars -= synconcealed(line('.'), col)[0]
2703 function! s:Blame(bang, line1, line2, count, mods, args) abort
2704 if exists('b:fugitive_blamed_bufnr')
2708 if empty(s:Relative(''))
2709 call s:throw('file or blob required')
2711 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2712 call s:throw('unsupported option')
2714 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2715 let cmd = ['--no-pager', 'blame', '--show-number']
2717 let cmd += ['-L', a:line1 . ',' . a:line1]
2720 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2721 let cmd += [s:DirCommitFile(@%)[1]]
2723 let cmd += ['--contents', '-']
2725 let cmd += ['--', s:Relative('')]
2726 let basecmd = escape(call('s:Prepare', [b:git_dir] + cmd), '!#%')
2728 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2730 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2732 execute cd s:fnameescape(tree)
2734 let error = s:tempname()
2735 let temp = error.'.fugitiveblame'
2737 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2739 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2742 execute cd s:fnameescape(cwd)
2746 call s:throw(join(readfile(error),"\n"))
2749 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
2750 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
2752 for winnr in range(winnr('$'),1,-1)
2753 call setwinvar(winnr, '&scrollbind', 0)
2754 if exists('+cursorbind')
2755 call setwinvar(winnr, '&cursorbind', 0)
2757 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2758 execute winbufnr(winnr).'bdelete'
2761 let bufnr = bufnr('')
2762 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2763 if exists('+cursorbind')
2764 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2767 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2770 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2772 setlocal scrollbind nowrap nofoldenable
2773 if exists('+cursorbind')
2776 let top = line('w0') + &scrolloff
2777 let current = line('.')
2778 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'args': cmd }
2779 exe 'keepalt leftabove vsplit '.temp
2780 let b:fugitive_blamed_bufnr = bufnr
2781 let b:fugitive_type = 'blame'
2782 let w:fugitive_leave = restore
2783 let b:fugitive_blame_arguments = join(a:args,' ')
2787 if exists('+cursorbind')
2790 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
2791 if exists('+concealcursor')
2792 setlocal concealcursor=nc conceallevel=2
2794 if exists('+relativenumber')
2795 setlocal norelativenumber
2797 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2798 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2799 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
2800 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2801 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>
2802 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2803 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
2804 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2805 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2806 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2807 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2808 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2809 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
2810 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2811 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2812 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2818 execute cd s:fnameescape(cwd)
2823 return 'echoerr v:errmsg'
2827 function! s:BlameCommit(cmd, ...) abort
2828 let line = a:0 ? a:1 : getline('.')
2829 if line =~# '^0\{4,40\} '
2830 return 'echoerr ' . string('Not Committed Yet')
2832 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
2833 if cmd =~# '^echoerr'
2836 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
2837 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2839 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
2842 if a:cmd ==# 'pedit'
2845 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2847 let head = line('.')
2848 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2849 let top = +matchstr(getline('.'),' +\zs\d\+')
2850 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2851 if lnum >= top && lnum <= top + len
2852 let offset = lnum - top
2860 while offset > 0 && line('.') < line('$')
2862 if getline('.') =~# '^[ +]'
2875 function! s:BlameJump(suffix) abort
2876 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2877 if commit =~# '^0\+$'
2880 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2881 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2883 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
2885 let args = b:fugitive_blame_arguments
2886 let offset = line('.') - line('w0')
2887 let bufnr = bufnr('%')
2888 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2890 exe winnr.'wincmd w'
2892 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
2897 if exists(':Gblame')
2898 execute 'Gblame '.args
2900 let delta = line('.') - line('w0') - offset
2902 execute 'normal! '.delta."\<C-E>"
2904 execute 'normal! '.(-delta)."\<C-Y>"
2911 let s:hash_colors = {}
2913 function! s:BlameSyntax() abort
2914 let b:current_syntax = 'fugitiveblame'
2915 let conceal = has('conceal') ? ' conceal' : ''
2916 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2917 syn match FugitiveblameBoundary "^\^"
2918 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2919 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2920 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2921 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2922 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2923 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2924 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2925 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2926 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2927 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2928 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2929 hi def link FugitiveblameBoundary Keyword
2930 hi def link FugitiveblameHash Identifier
2931 hi def link FugitiveblameUncommitted Ignore
2932 hi def link FugitiveblameTime PreProc
2933 hi def link FugitiveblameLineNumber Number
2934 hi def link FugitiveblameOriginalFile String
2935 hi def link FugitiveblameOriginalLineNumber Float
2936 hi def link FugitiveblameShort FugitiveblameDelimiter
2937 hi def link FugitiveblameDelimiter Delimiter
2938 hi def link FugitiveblameNotCommittedYet Comment
2940 for lnum in range(1, line('$'))
2941 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2942 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2946 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
2947 \ && empty(get(s:hash_colors, hash))
2948 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2949 let color = csapprox#per_component#Approximate(r, g, b)
2950 if color == 16 && &background ==# 'dark'
2953 let s:hash_colors[hash] = ' ctermfg='.color
2955 let s:hash_colors[hash] = ''
2957 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2959 call s:RehighlightBlame()
2962 function! s:RehighlightBlame() abort
2963 for [hash, cterm] in items(s:hash_colors)
2964 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
2965 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2967 exe 'hi link FugitiveblameHash'.hash.' Identifier'
2974 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2976 let s:redirects = {}
2978 function! s:Browse(bang,line1,count,...) abort
2980 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
2982 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
2983 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
2989 let rev = s:DirRev(@%)[1]
2992 let expanded = s:Relative('/')
2994 let expanded = s:Expand(rev)
2996 let cdir = fugitive#CommonDir(b:git_dir)
2997 for dir in ['tags/', 'heads/', 'remotes/']
2998 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
2999 let expanded = '/.git/refs/' . dir . expanded
3002 let full = s:Generate(expanded)
3004 if full =~? '^fugitive:'
3005 let [dir, commit, path] = s:DirCommitFile(full)
3006 if commit =~# '^:\=\d$'
3010 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3011 let branch = matchstr(expanded, '^[^:]*')
3015 let path = path[1:-1]
3016 elseif empty(s:Tree())
3017 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3020 let path = full[strlen(s:Tree())+1:-1]
3021 if path =~# '^\.git/'
3023 elseif isdirectory(full)
3029 if type ==# 'tree' && !empty(path)
3030 let path = s:sub(path, '/\=$', '/')
3032 if path =~# '^\.git/.*HEAD' && filereadable(b:git_dir . '/' . path[5:-1])
3033 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3034 if body =~# '^\x\{40\}$'
3038 elseif body =~# '^ref: refs/'
3039 let path = '.git/' . matchstr(body,'ref: \zs.*')
3044 if path =~# '^\.git/refs/remotes/.'
3046 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3047 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3049 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3050 let path = '.git/refs/heads/'.merge
3052 elseif path =~# '^\.git/refs/heads/.'
3053 let branch = path[16:-1]
3054 elseif !exists('branch')
3055 let branch = FugitiveHead()
3058 let r = fugitive#Config('branch.'.branch.'.remote')
3059 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3060 if r ==# '.' && !empty(m)
3061 let r2 = fugitive#Config('branch.'.m.'.remote')
3064 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3070 if r ==# '.' || r ==# remote
3072 if path =~# '^\.git/refs/heads/.'
3073 let path = '.git/refs/heads/'.merge
3078 let line1 = a:count > 0 ? a:line1 : 0
3079 let line2 = a:count > 0 ? a:count : 0
3080 if empty(commit) && path !~# '^\.git/'
3081 if a:line1 && !a:count && !empty(merge)
3086 let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3087 let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3088 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3089 let blame_list = s:tempname()
3090 call writefile([commit, ''], blame_list, 'b')
3091 let blame_in = s:tempname()
3092 silent exe '%write' blame_in
3093 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', '--', path), "\n")
3095 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3096 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3097 let line1 = +matchstr(blame[0], blame_regex)
3098 let line2 = +matchstr(blame[-1], blame_regex)
3100 call s:throw("Can't browse to uncommitted change")
3107 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3110 while commit =~# '^ref: ' && i < 10
3111 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3119 let raw = fugitive#RemoteUrl(remote)
3124 if raw =~# '^https\=://' && s:executable('curl')
3125 if !has_key(s:redirects, raw)
3126 let s:redirects[raw] = matchstr(system('curl -I ' .
3127 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3128 \ 'Location: \zs\S\+\ze/info/refs?')
3130 if len(s:redirects[raw])
3131 let raw = s:redirects[raw]
3137 \ 'repo': fugitive#repo(),
3139 \ 'revision': 'No longer provided',
3146 for Handler in get(g:, 'fugitive_browse_handlers', [])
3147 let url = call(Handler, [copy(opts)])
3154 call s:throw("No Gbrowse handler found for '".raw."'")
3157 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3162 return 'echomsg '.string(url)
3163 elseif exists(':Browse') == 2
3164 return 'echomsg '.string(url).'|Browse '.url
3166 if !exists('g:loaded_netrw')
3167 runtime! autoload/netrw.vim
3169 if exists('*netrw#BrowseX')
3170 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3172 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3176 return 'echoerr v:errmsg'
3180 " Section: Go to file
3182 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3183 function! fugitive#MapCfile(...) abort
3184 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3185 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3186 if !exists('g:fugitive_no_maps')
3187 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3188 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3189 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3190 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3191 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3195 function! s:ContainingCommit() abort
3196 let commit = s:DirCommitFile(@%)[1]
3197 if commit =~# '^\d\=$'
3204 function! s:NavigateUp(count) abort
3205 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3209 let rev = matchstr(rev, '.*\ze/.\+', '')
3210 elseif rev =~# '.:.'
3211 let rev = matchstr(rev, '^.[^:]*:')
3224 function! fugitive#MapJumps(...) abort
3225 if get(b:, 'fugitive_type', '') ==# 'blob'
3226 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3228 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3231 if get(b:, 'fugitive_type', '') ==# 'blob'
3232 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3233 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3234 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3235 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3237 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3238 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3239 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3240 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3242 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>
3243 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3244 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3245 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3246 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3247 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3248 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3249 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3250 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3251 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
3255 function! s:StatusCfile(...) abort
3257 if getline('.') =~# '^.\=\trenamed:.* -> '
3258 return '/'.matchstr(getline('.'),' -> \zs.*')
3259 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3260 return '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3261 elseif getline('.') =~# '^.\=\t.'
3262 return '/'.matchstr(getline('.'),'\t\zs.*')
3263 elseif getline('.') =~# ': needs merge$'
3264 return '/'.matchstr(getline('.'),'.*\ze: needs merge$')
3265 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3267 elseif getline('.') =~# '^\%(. \)\=On branch '
3268 return 'refs/heads/'.getline('.')[12:]
3269 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3270 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3276 function! fugitive#StatusCfile() abort
3277 let file = s:StatusCfile()
3278 return empty(file) ? "\<C-R>\<C-F>" : s:fnameescape(s:Generate(file))
3281 function! s:cfile() abort
3283 let myhash = s:DirRev(@%)[1]
3286 let myhash = fugitive#RevParse(myhash)
3291 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3292 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3295 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3297 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3298 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3300 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3301 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3303 return [treebase . s:sub(getline('.'),'/$','')]
3310 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3311 let ref = matchstr(getline('.'),'\x\{40\}')
3312 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3316 if getline('.') =~# '^ref: '
3317 let ref = strpart(getline('.'),5)
3319 elseif getline('.') =~# '^commit \x\{40\}\>'
3320 let ref = matchstr(getline('.'),'\x\{40\}')
3323 elseif getline('.') =~# '^parent \x\{40\}\>'
3324 let ref = matchstr(getline('.'),'\x\{40\}')
3325 let line = line('.')
3327 while getline(line) =~# '^parent '
3333 elseif getline('.') =~# '^tree \x\{40\}$'
3334 let ref = matchstr(getline('.'),'\x\{40\}')
3335 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3336 let ref = myhash.':'
3340 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3341 let ref = matchstr(getline('.'),'\x\{40\}')
3342 let type = matchstr(getline(line('.')+1),'type \zs.*')
3344 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3345 let ref = s:DirRev(@%)[1]
3347 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3348 let ref = matchstr(getline('.'),'\x\{40\}')
3349 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3351 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3352 let ref = getline('.')[4:]
3354 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3355 let type = getline('.')[0]
3356 let lnum = line('.') - 1
3358 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3359 if getline(lnum) =~# '^[ '.type.']'
3364 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3365 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3366 let dcmds = [offset, 'normal!zv']
3368 elseif getline('.') =~# '^rename from '
3369 let ref = 'a/'.getline('.')[12:]
3370 elseif getline('.') =~# '^rename to '
3371 let ref = 'b/'.getline('.')[10:]
3373 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3374 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3375 let offset = matchstr(getline('.'), '+\zs\d\+')
3377 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3378 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3379 let dcmd = 'Gdiff! +'.offset
3381 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3382 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3383 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3386 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3387 let line = getline(line('.')-1)
3388 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3389 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3392 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3393 let ref = getline('.')
3395 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3396 return [expand('<cword>')]
3411 let prefixes.a = myhash.'^:'
3412 let prefixes.b = myhash.':'
3414 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3416 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3419 if ref ==# '/dev/null'
3421 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3425 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3427 return [ref] + dcmds
3435 function! s:GF(mode) abort
3437 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3439 return 'echoerr v:errmsg'
3442 return 'G' . a:mode .
3443 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3444 \ s:fnameescape(results[0])
3446 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3452 function! fugitive#Cfile() abort
3454 let results = s:cfile()
3456 let cfile = expand('<cfile>')
3457 if &includeexpr =~# '\<v:fname\>'
3458 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3461 elseif len(results) > 1
3462 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3464 return pre . s:fnameescape(s:Generate(results[0]))
3467 " Section: Statusline
3469 function! fugitive#Statusline(...) abort
3470 if !exists('b:git_dir')
3474 let commit = s:DirCommitFile(@%)[1]
3476 let status .= ':' . commit[0:7]
3478 let status .= '('.FugitiveHead(7).')'
3479 return '[Git'.status.']'
3482 function! fugitive#statusline(...) abort
3483 return fugitive#Statusline()
3486 function! fugitive#head(...) abort
3487 if !exists('b:git_dir')
3491 return fugitive#repo().head(a:0 ? a:1 : 0)
3496 function! fugitive#Foldtext() abort
3497 if &foldmethod !=# 'syntax'
3501 let line_foldstart = getline(v:foldstart)
3502 if line_foldstart =~# '^diff '
3503 let [add, remove] = [-1, -1]
3505 for lnum in range(v:foldstart, v:foldend)
3506 let line = getline(lnum)
3507 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3508 let filename = line[6:-1]
3512 elseif line =~# '^-'
3514 elseif line =~# '^Binary '
3519 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3522 let filename = line_foldstart[5:-1]
3525 return 'Binary: '.filename
3527 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3529 elseif line_foldstart =~# '^# .*:$'
3530 let lines = getline(v:foldstart, v:foldend)
3531 call filter(lines, 'v:val =~# "^#\t"')
3532 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3533 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3534 return line_foldstart.' '.join(lines, ', ')
3539 function! fugitive#foldtext() abort
3540 return fugitive#Foldtext()
3543 augroup fugitive_folding
3545 autocmd User Fugitive
3546 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3547 \ set foldtext=fugitive#Foldtext() |
3551 " Section: Initialization
3553 function! fugitive#Init() abort
3554 if exists('#User#FugitiveBoot')
3556 let [save_mls, &modelines] = [&mls, 0]
3557 doautocmd User FugitiveBoot
3562 if !exists('g:fugitive_no_maps')
3563 call s:map('c', '<C-R><C-G>', 'fnameescape(<SID>recall())', '<expr>')
3564 call s:map('n', 'y<C-G>', ':call setreg(v:register, <SID>recall())<CR>', '<silent>')
3566 if expand('%:p') =~# ':[\/][\/]'
3567 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3569 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3570 if filereadable(b:git_dir.'/tags')
3571 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3573 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3574 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3578 let [save_mls, &modelines] = [&mls, 0]
3579 call s:define_commands()
3580 doautocmd User Fugitive
3586 function! fugitive#is_git_dir(path) abort
3587 return FugitiveIsGitDir(a:path)
3590 function! fugitive#extract_git_dir(path) abort
3591 return FugitiveExtractGitDir(a:path)
3594 function! fugitive#detect(path) abort
3595 return FugitiveDetect(a:path)