1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer: Tim Pope <http://tpo.pe/>
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
6 if exists('g:loaded_fugitive') || &cp
9 let g:loaded_fugitive = 1
11 if !exists('g:fugitive_git_executable')
12 let g:fugitive_git_executable = 'git'
17 function! s:function(name) abort
18 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
21 function! s:sub(str,pat,rep) abort
22 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
25 function! s:gsub(str,pat,rep) abort
26 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
29 function! s:winshell() abort
30 return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
33 function! s:shellesc(arg) abort
34 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
37 return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
39 return shellescape(a:arg)
43 function! s:fnameescape(file) abort
44 if exists('*fnameescape')
45 return fnameescape(a:file)
47 return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
51 function! s:throw(string) abort
52 let v:errmsg = 'fugitive: '.a:string
56 function! s:warn(str) abort
60 let v:warningmsg = a:str
63 function! s:shellslash(path) abort
65 return s:gsub(a:path,'\\','/')
71 let s:executables = {}
73 function! s:executable(binary) abort
74 if !has_key(s:executables, a:binary)
75 let s:executables[a:binary] = executable(a:binary)
77 return s:executables[a:binary]
80 let s:git_versions = {}
82 function! s:git_command() abort
83 return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
86 function! fugitive#git_version(...) abort
87 if !has_key(s:git_versions, g:fugitive_git_executable)
88 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\n")
90 return s:git_versions[g:fugitive_git_executable]
93 function! s:recall() abort
94 let rev = s:sub(s:buffer().rev(), '^/', '')
96 return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
97 elseif s:buffer().type('tree')
98 let file = matchstr(getline('.'), '\t\zs.*')
99 if empty(file) && line('.') > 2
100 let file = s:sub(getline('.'), '/$', '')
102 if !empty(file) && rev !~# ':$'
103 return rev . '/' . file
111 function! s:map(mode, lhs, rhs, ...) abort
112 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '^<Plug>' ? '' : '<script>')
115 let keys = get(g:, a:mode.'remap', {})
116 if type(keys) == type([])
120 if has_key(keys, head)
121 let head = keys[head]
127 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
128 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
130 if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
131 exe a:mode.'map <buffer>' flags head.tail a:rhs
135 function! s:add_methods(namespace, method_names) abort
136 for name in a:method_names
137 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
142 function! s:command(definition) abort
143 let s:commands += [a:definition]
146 function! s:define_commands() abort
147 for command in s:commands
148 exe 'command! -buffer '.command
152 let s:abstract_prototype = {}
154 " Section: Initialization
156 function! fugitive#is_git_dir(path) abort
157 let path = s:sub(a:path, '[\/]$', '') . '/'
158 return getfsize(path.'HEAD') > 10 && (
159 \ isdirectory(path.'objects') && isdirectory(path.'refs') ||
160 \ getftype(path.'commondir') ==# 'file')
163 function! FugitiveIsGitDir(path) abort
164 return fugitive#is_git_dir(a:path)
167 function! fugitive#extract_git_dir(path) abort
168 if s:shellslash(a:path) =~# '^fugitive://.*//'
169 return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
171 if isdirectory(a:path)
172 let path = fnamemodify(a:path, ':p:s?[\/]$??')
174 let path = fnamemodify(a:path, ':p:h:s?[\/]$??')
176 let root = s:shellslash(resolve(path))
178 while root !=# previous
179 if root =~# '\v^//%([^/]+/?)?$'
180 " This is for accessing network shares from Cygwin Vim. There won't be
181 " any git directory called //.git or //serverName/.git so let's avoid
182 " checking for them since such checks are extremely slow.
185 if index(split($GIT_CEILING_DIRECTORIES, ':'), root) >= 0
188 if root ==# $GIT_WORK_TREE && fugitive#is_git_dir($GIT_DIR)
189 return simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??'))
191 if fugitive#is_git_dir($GIT_DIR)
192 " Ensure that we've cached the worktree
193 call s:configured_tree(simplify(fnamemodify(expand($GIT_DIR), ':p:s?[\/]$??')))
194 if has_key(s:dir_for_worktree, root)
195 return s:dir_for_worktree[root]
198 let dir = s:sub(root, '[\/]$', '') . '/.git'
199 let type = getftype(dir)
200 if type ==# 'dir' && fugitive#is_git_dir(dir)
202 elseif type ==# 'link' && fugitive#is_git_dir(dir)
204 elseif type !=# '' && filereadable(dir)
205 let line = get(readfile(dir, '', 1), 0, '')
206 if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
207 return simplify(root.'/'.line[8:-1])
208 elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
211 elseif fugitive#is_git_dir(root)
215 let root = fnamemodify(root, ':h')
220 function! FugitiveExtractGitDir(path) abort
221 return fugitive#extract_git_dir(a:path)
224 function! fugitive#detect(path) abort
225 if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
228 if !exists('b:git_dir')
229 let dir = fugitive#extract_git_dir(a:path)
232 if empty(fugitive#buffer().path())
233 silent! exe haslocaldir() ? 'lcd .' : 'cd .'
237 if exists('b:git_dir')
238 if exists('#User#FugitiveBoot')
240 let [save_mls, &modelines] = [&mls, 0]
241 doautocmd User FugitiveBoot
246 if !exists('g:fugitive_no_maps')
247 call s:map('c', '<C-R><C-G>', 'fnameescape(<SID>recall())', '<expr>')
248 call s:map('n', 'y<C-G>', ':call setreg(v:register, <SID>recall())<CR>', '<silent>')
250 let buffer = fugitive#buffer()
251 if expand('%:p') =~# '://'
252 call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
254 if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
255 if filereadable(b:git_dir.'/tags')
256 call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
258 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
259 call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
263 let [save_mls, &modelines] = [&mls, 0]
264 call s:define_commands()
265 doautocmd User Fugitive
272 function! FugitiveDetect(path) abort
273 return fugitive#detect(a:path)
278 autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('%:p'))
279 autocmd FileType netrw call fugitive#detect(expand('%:p'))
280 autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTree.root.path.str())
281 autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
282 autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
283 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
286 " Section: Repository
288 let s:repo_prototype = {}
290 let s:worktree_for_dir = {}
291 let s:dir_for_worktree = {}
293 function! s:repo(...) abort
294 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
296 if has_key(s:repos, dir)
297 let repo = get(s:repos, dir)
299 let repo = {'git_dir': dir}
300 let s:repos[dir] = repo
302 return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
304 call s:throw('not a git repository: '.expand('%:p'))
307 function! fugitive#repo(...) abort
308 return call('s:repo', a:000)
311 function! s:repo_dir(...) dict abort
312 return join([self.git_dir]+a:000,'/')
315 function! s:configured_tree(git_dir) abort
316 if !has_key(s:worktree_for_dir, a:git_dir)
317 let s:worktree_for_dir[a:git_dir] = ''
318 let config_file = a:git_dir . '/config'
319 if filereadable(config_file)
320 let config = readfile(config_file,'',10)
321 call filter(config,'v:val =~# "^\\s*worktree *="')
323 let worktree = matchstr(config[0], '= *\zs.*')
325 elseif filereadable(a:git_dir . '/gitdir')
326 let worktree = fnamemodify(readfile(a:git_dir . '/gitdir')[0], ':h')
331 if exists('worktree')
332 let s:worktree_for_dir[a:git_dir] = worktree
333 let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
336 if s:worktree_for_dir[a:git_dir] =~# '^\.'
337 return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
339 return s:worktree_for_dir[a:git_dir]
343 function! s:repo_tree(...) dict abort
344 if self.dir() =~# '/\.git$'
345 let dir = self.dir()[0:-6]
350 let dir = s:configured_tree(self.git_dir)
353 call s:throw('no work tree')
355 return join([dir]+a:000,'/')
359 function! s:repo_bare() dict abort
360 if self.dir() =~# '/\.git$'
363 return s:configured_tree(self.git_dir) ==# ''
367 function! s:repo_translate(spec) dict abort
368 let refs = self.dir('refs/')
369 if filereadable(self.dir('commondir'))
370 let refs = simplify(self.dir(get(readfile(self.dir('commondir'), 1), 0, ''))) . '/refs/'
372 if a:spec ==# '.' || a:spec ==# '/.'
373 return self.bare() ? self.dir() : self.tree()
374 elseif a:spec =~# '^/\=\.git$' && self.bare()
376 elseif a:spec =~# '^/\=\.git/'
377 return self.dir(s:sub(a:spec, '^/=\.git/', ''))
378 elseif a:spec =~# '^/'
379 return self.tree().a:spec
380 elseif a:spec =~# '^:[0-3]:'
381 return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
382 elseif a:spec ==# ':'
383 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
384 return fnamemodify($GIT_INDEX_FILE,':p')
386 return self.dir('index')
388 elseif a:spec =~# '^:/'
389 let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
390 return 'fugitive://'.self.dir().'//'.ref
391 elseif a:spec =~# '^:'
392 return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
393 elseif a:spec ==# '@'
394 return self.dir('HEAD')
395 elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(refs . '../' . a:spec)
396 return simplify(refs . '../' . a:spec)
397 elseif filereadable(refs.a:spec)
399 elseif filereadable(refs.'tags/'.a:spec)
400 return refs.'tags/'.a:spec
401 elseif filereadable(refs.'heads/'.a:spec)
402 return refs.'heads/'.a:spec
403 elseif filereadable(refs.'remotes/'.a:spec)
404 return refs.'remotes/'.a:spec
405 elseif filereadable(refs.'remotes/'.a:spec.'/HEAD')
406 return refs.'remotes/'.a:spec.'/HEAD'
409 let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
410 let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
411 return 'fugitive://'.self.dir().'//'.ref.path
413 return self.tree(a:spec)
418 function! s:repo_head(...) dict abort
419 let head = s:repo().head_ref()
422 let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
423 elseif head =~# '^\x\{40\}$'
424 " truncate hash to a:1 characters if we're in detached head mode
425 let len = a:0 ? a:1 : 0
426 let branch = len ? head[0:len-1] : ''
434 call s:add_methods('repo',['dir','tree','bare','translate','head'])
436 function! s:repo_git_command(...) dict abort
437 let git = s:git_command() . ' --git-dir='.s:shellesc(self.git_dir)
438 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
441 function! s:repo_git_chomp(...) dict abort
442 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
443 let output = git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
444 return s:sub(system(output),'\n$','')
447 function! s:repo_git_chomp_in_tree(...) dict abort
448 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
451 execute cd s:fnameescape(s:repo().tree())
452 return call(s:repo().git_chomp, a:000, s:repo())
454 execute cd s:fnameescape(dir)
458 function! s:repo_rev_parse(rev) dict abort
459 let hash = self.git_chomp('rev-parse','--verify',a:rev)
460 if hash =~ '\<\x\{40\}$'
461 return matchstr(hash,'\<\x\{40\}$')
463 call s:throw('rev-parse '.a:rev.': '.hash)
466 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
468 function! s:repo_dirglob(base) dict abort
469 let base = s:sub(a:base,'^/','')
470 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
471 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
475 function! s:repo_superglob(base) dict abort
476 if a:base =~# '^/' || a:base !~# ':'
479 let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
480 let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
482 if filereadable(s:repo().dir('refs/stash'))
483 let heads += ["stash"]
484 let heads += sort(split(s:repo().git_chomp("stash","list","--pretty=format:%gd"),"\n"))
486 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
490 let base = s:sub(a:base,'^/','')
491 let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
492 call map(matches,'s:shellslash(v:val)')
493 call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
494 call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
495 let results += matches
499 elseif a:base =~# '^:'
500 let entries = split(self.git_chomp('ls-files','--stage'),"\n")
501 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
502 if a:base !~# '^:[0-3]\%(:\|$\)'
503 call filter(entries,'v:val[1] == "0"')
504 call map(entries,'v:val[2:-1]')
506 call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
510 let tree = matchstr(a:base,'.*[:/]')
511 let entries = split(self.git_chomp('ls-tree',tree),"\n")
512 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
513 call map(entries,'tree.s:sub(v:val,".*\t","")')
514 return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
518 call s:add_methods('repo',['dirglob','superglob'])
520 function! s:repo_config(conf) dict abort
521 return matchstr(s:repo().git_chomp('config',a:conf),"[^\r\n]*")
524 function! s:repo_user() dict abort
525 let username = s:repo().config('user.name')
526 let useremail = s:repo().config('user.email')
527 return username.' <'.useremail.'>'
530 function! s:repo_aliases() dict abort
531 if !has_key(self,'_aliases')
532 let self._aliases = {}
533 for line in split(self.git_chomp('config','-z','--get-regexp','^alias[.]'),"\1")
534 let self._aliases[matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
540 call s:add_methods('repo',['config', 'user', 'aliases'])
542 function! s:repo_keywordprg() dict abort
543 let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
544 if has('gui_running') && !has('win32')
545 return s:git_command() . ' --no-pager' . args . ' log -1'
547 return s:git_command() . args . ' show'
551 call s:add_methods('repo',['keywordprg'])
555 let s:buffer_prototype = {}
557 function! s:buffer(...) abort
558 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
559 call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
560 if buffer.getvar('git_dir') !=# ''
563 call s:throw('not a git repository: '.expand('%:p'))
566 function! fugitive#buffer(...) abort
567 return s:buffer(a:0 ? a:1 : '%')
570 function! s:buffer_getvar(var) dict abort
571 return getbufvar(self['#'],a:var)
574 function! s:buffer_setvar(var,value) dict abort
575 return setbufvar(self['#'],a:var,a:value)
578 function! s:buffer_getline(lnum) dict abort
579 return get(getbufline(self['#'], a:lnum), 0, '')
582 function! s:buffer_repo() dict abort
583 return s:repo(self.getvar('git_dir'))
586 function! s:buffer_type(...) dict abort
587 if self.getvar('fugitive_type') != ''
588 let type = self.getvar('fugitive_type')
589 elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
591 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
593 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
595 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
597 elseif isdirectory(self.spec())
598 let type = 'directory'
599 elseif self.spec() == ''
605 return !empty(filter(copy(a:000),'v:val ==# type'))
613 function! s:buffer_spec() dict abort
614 let bufname = bufname(self['#'])
616 for i in split(bufname,'[^:]\zs\\')
617 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
619 return s:shellslash(fnamemodify(retval,':p'))
624 function! s:buffer_spec() dict abort
625 let bufname = bufname(self['#'])
626 return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
631 function! s:buffer_name() dict abort
635 function! s:buffer_commit() dict abort
636 return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
639 function! s:cpath(path) abort
640 if exists('+fileignorecase') && &fileignorecase
641 return tolower(a:path)
647 function! s:buffer_path(...) dict abort
648 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
650 let rev = s:sub(rev,'\w*','')
651 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
652 \ s:cpath(self.repo().dir() . '/')
653 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
654 elseif !self.repo().bare() &&
655 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
656 \ s:cpath(self.repo().tree() . '/')
657 let rev = self.spec()[strlen(self.repo().tree()) : -1]
659 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
662 function! s:buffer_rev() dict abort
663 let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
665 return ':'.rev[0].':'.rev[2:-1]
667 return s:sub(rev,'/',':')
668 elseif self.spec() =~ '\.git/index$'
670 elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
671 return self.spec()[strlen(self.repo().dir())+1 : -1]
673 return self.path('/')
677 function! s:buffer_sha1() dict abort
678 if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
679 return self.repo().rev_parse(self.rev())
685 function! s:buffer_expand(rev) dict abort
686 if a:rev =~# '^:[0-3]$'
687 let file = a:rev.self.path(':')
688 elseif a:rev =~# '^[-:]/$'
689 let file = '/'.self.path()
690 elseif a:rev =~# '^-'
691 let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
692 elseif a:rev =~# '^@{'
693 let file = 'HEAD'.a:rev.self.path(':')
694 elseif a:rev =~# '^[~^]'
695 let commit = s:sub(self.commit(),'^\d=$','HEAD')
696 let file = commit.a:rev.self.path(':')
700 return s:sub(substitute(file,
701 \ '%$\|\\\([[:punct:]]\)','\=len(submatch(1)) ? submatch(1) : self.path()','g'),
705 function! s:buffer_containing_commit() dict abort
706 if self.commit() =~# '^\d$'
708 elseif self.commit() =~# '.'
715 function! s:buffer_up(...) dict abort
717 let c = a:0 ? a:1 : 1
723 elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
725 elseif rev =~# '^/\|:.*/'
726 let rev = s:sub(rev, '.*\zs/.*', '')
728 let rev = matchstr(rev, '^[^:]*:')
739 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
743 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
745 function! s:ExecuteInTree(cmd) abort
746 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
749 execute cd s:fnameescape(s:repo().tree())
752 execute cd s:fnameescape(dir)
756 function! s:Git(bang, args) abort
758 return s:Edit('edit', 1, a:args)
760 let git = s:git_command()
761 if has('gui_running') && !has('win32')
762 let git .= ' --no-pager'
764 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
765 if exists(':terminal') && has('nvim')
766 let dir = s:repo().tree()
772 execute 'lcd' fnameescape(dir)
773 execute 'terminal' git args
775 call s:ExecuteInTree('!'.git.' '.args)
777 call fugitive#reload_status()
780 return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
783 function! fugitive#git_commands() abort
784 if !exists('s:exec_path')
785 let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
787 return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
790 function! s:GitComplete(A, L, P) abort
791 if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
792 let cmds = fugitive#git_commands()
793 return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
795 return s:repo().superglob(a:A)
801 function! s:DirComplete(A,L,P) abort
802 let matches = s:repo().dirglob(a:A)
806 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
807 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>))")
811 call s:command("-bar Gstatus :execute s:Status()")
812 augroup fugitive_status
815 autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
816 autocmd BufDelete term://* call fugitive#reload_status()
820 function! s:Status() abort
824 setlocal foldmethod=syntax foldlevel=1
825 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
827 return 'echoerr v:errmsg'
832 function! fugitive#reload_status() abort
833 if exists('s:reloading_status')
837 let s:reloading_status = 1
838 let mytab = tabpagenr()
839 for tab in [mytab] + range(1,tabpagenr('$'))
840 for winnr in range(1,tabpagewinnr(tab,'$'))
841 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
842 execute 'tabnext '.tab
844 execute winnr.'wincmd w'
849 call s:BufReadIndex()
852 if exists('restorewinnr')
855 execute 'tabnext '.mytab
861 unlet! s:reloading_status
865 function! fugitive#ReloadStatus() abort
866 return fugitive#reload_status()
869 function! s:stage_info(lnum) abort
870 let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
872 if has('multi_byte_encoding')
873 let colon = '\%(:\|\%uff1a\)'
877 while lnum && getline(lnum) !~# colon.'$'
882 elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
883 return [matchstr(filename, colon.' *\zs.*'), 'staged']
884 elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) ==# '# Untracked files:'
885 return [filename, 'untracked']
886 elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
887 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
888 elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
889 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
891 return ['', 'unknown']
895 function! s:StageNext(count) abort
896 for i in range(a:count)
897 call search('^#\t.*','W')
902 function! s:StagePrevious(count) abort
903 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
904 return 'CtrlP '.fnameescape(s:repo().tree())
906 for i in range(a:count)
907 call search('^#\t.*','Wbe')
913 function! s:StageReloadSeek(target,lnum1,lnum2) abort
915 let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
916 if f !=# '' | let jump = f | endif
917 let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
918 if f !=# '' | let jump = f | endif
922 call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
925 function! s:StageUndo() abort
926 let [filename, section] = s:stage_info(line('.'))
931 let hash = repo.git_chomp('hash-object', '-w', filename)
933 if section ==# 'untracked'
934 call repo.git_chomp_in_tree('clean', '-f', '--', filename)
935 elseif section ==# 'unmerged'
936 call repo.git_chomp_in_tree('rm', '--', filename)
937 elseif section ==# 'unstaged'
938 call repo.git_chomp_in_tree('checkout', '--', filename)
940 call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
942 call s:StageReloadSeek(filename, line('.'), line('.'))
944 return 'checktime|redraw|echomsg ' .
945 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
949 function! s:StageDiff(diff) abort
950 let [filename, section] = s:stage_info(line('.'))
951 if filename ==# '' && section ==# 'staged'
952 return 'Git! diff --no-ext-diff --cached'
953 elseif filename ==# ''
954 return 'Git! diff --no-ext-diff'
955 elseif filename =~# ' -> '
956 let [old, new] = split(filename,' -> ')
957 execute 'Gedit '.s:fnameescape(':0:'.new)
958 return a:diff.' HEAD:'.s:fnameescape(old)
959 elseif section ==# 'staged'
960 execute 'Gedit '.s:fnameescape(':0:'.filename)
963 execute 'Gedit '.s:fnameescape('/'.filename)
968 function! s:StageDiffEdit() abort
969 let [filename, section] = s:stage_info(line('.'))
970 let arg = (filename ==# '' ? '.' : filename)
971 if section ==# 'staged'
972 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
973 elseif section ==# 'untracked'
975 call repo.git_chomp_in_tree('add','--intent-to-add',arg)
979 if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
980 call search('^# .*:$','W')
983 call s:StageReloadSeek(arg,line('.'),line('.'))
987 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
991 function! s:StageToggle(lnum1,lnum2) abort
992 if a:lnum1 == 1 && a:lnum2 == 1
993 return 'Gedit /.git|call search("^index$", "wc")'
997 for lnum in range(a:lnum1,a:lnum2)
998 let [filename, section] = s:stage_info(lnum)
1000 if getline('.') =~# '^# .*:$'
1001 if section ==# 'staged'
1002 call repo.git_chomp_in_tree('reset','-q')
1005 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
1006 call search('^# .*:$','W')
1009 elseif section ==# 'unstaged'
1010 call repo.git_chomp_in_tree('add','-u')
1013 if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
1014 call search('^# .*:$','W')
1018 call repo.git_chomp_in_tree('add','.')
1021 call search('^# .*:$','W')
1029 if section ==# 'staged'
1030 if filename =~ ' -> '
1031 let files_to_unstage = split(filename,' -> ')
1033 let files_to_unstage = [filename]
1035 let filename = files_to_unstage[-1]
1036 let cmd = ['reset','-q','--'] + files_to_unstage
1037 elseif getline(lnum) =~# '^#\tdeleted:'
1038 let cmd = ['rm','--',filename]
1039 elseif getline(lnum) =~# '^#\tmodified:'
1040 let cmd = ['add','--',filename]
1042 let cmd = ['add','-A','--',filename]
1044 if !exists('first_filename')
1045 let first_filename = filename
1047 let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
1049 if exists('first_filename')
1050 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1052 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1054 return 'echoerr v:errmsg'
1059 function! s:StagePatch(lnum1,lnum2) abort
1063 for lnum in range(a:lnum1,a:lnum2)
1064 let [filename, section] = s:stage_info(lnum)
1065 if getline('.') =~# '^# .*:$' && section ==# 'staged'
1066 return 'Git reset --patch'
1067 elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
1068 return 'Git add --patch'
1069 elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
1070 return 'Git add -N .'
1071 elseif filename ==# ''
1074 if !exists('first_filename')
1075 let first_filename = filename
1078 if filename =~ ' -> '
1079 let reset += [split(filename,' -> ')[1]]
1080 elseif section ==# 'staged'
1081 let reset += [filename]
1082 elseif getline(lnum) !~# '^#\tdeleted:'
1083 let add += [filename]
1088 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1091 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1093 if exists('first_filename')
1097 call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1100 return 'echoerr v:errmsg'
1107 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1109 function! s:Commit(mods, args, ...) abort
1110 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1111 let repo = a:0 ? a:1 : s:repo()
1112 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1114 let msgfile = repo.dir('COMMIT_EDITMSG')
1115 let outfile = tempname()
1116 let errorfile = tempname()
1119 execute cd s:fnameescape(repo.tree())
1122 let old_editor = $GIT_EDITOR
1123 let $GIT_EDITOR = 'false'
1125 let command = 'env GIT_EDITOR=false '
1127 let command .= repo.git_command('commit').' '.a:args
1129 noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1130 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1131 noautocmd execute '!'.command.' 2> '.errorfile
1133 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1135 let error = v:shell_error
1137 execute cd s:fnameescape(dir)
1139 if !has('gui_running')
1143 if filereadable(outfile)
1144 for line in readfile(outfile)
1150 let errors = readfile(errorfile)
1151 let error = get(errors,-2,get(errors,-1,'!'))
1152 if error =~# 'false''\=\.$'
1154 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1155 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1156 let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1157 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1158 let args = '-F '.s:shellesc(msgfile).' '.args
1159 if args !~# '\%(^\| \)--cleanup\>'
1160 let args = '--cleanup=strip '.args
1162 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1163 execute mods 'keepalt edit' s:fnameescape(msgfile)
1164 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
1165 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
1166 elseif s:buffer().type() ==# 'index'
1167 execute mods 'keepalt edit' s:fnameescape(msgfile)
1168 execute (search('^#','n')+1).'wincmd+'
1169 setlocal nopreviewwindow
1171 execute mods 'keepalt split' s:fnameescape(msgfile)
1173 let b:fugitive_commit_arguments = args
1174 setlocal bufhidden=wipe filetype=gitcommit
1176 elseif error ==# '!'
1179 call s:throw(empty(error)?join(errors, ' '):error)
1183 return 'echoerr v:errmsg'
1185 if exists('old_editor')
1186 let $GIT_EDITOR = old_editor
1188 call delete(outfile)
1189 call delete(errorfile)
1190 call fugitive#reload_status()
1194 function! s:CommitComplete(A,L,P) abort
1195 if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1196 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']
1197 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1199 return s:repo().superglob(a:A)
1203 function! s:FinishCommit() abort
1204 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1206 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1207 return s:Commit('', args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1212 " Section: Gmerge, Gpull
1214 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1215 \ "execute s:Merge('merge', <bang>0, <q-args>)")
1216 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1217 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1219 function! s:RevisionComplete(A, L, P) abort
1220 return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1221 \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1224 function! s:RemoteComplete(A, L, P) abort
1225 let remote = matchstr(a:L, ' \zs\S\+\ze ')
1227 let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1228 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1229 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1231 let matches = split(s:repo().git_chomp('remote'), "\n")
1233 return join(matches, "\n")
1236 function! fugitive#cwindow() abort
1237 if &buftype == 'quickfix'
1241 if &buftype == 'quickfix'
1247 let s:common_efm = ''
1249 \ . '%+Eusage:%.%#,'
1250 \ . '%+Eerror:%.%#,'
1251 \ . '%+Efatal:%.%#,'
1252 \ . '%-G%.%#%\e[K%.%#,'
1253 \ . '%-G%.%#%\r%.%\+'
1255 function! s:Merge(cmd, bang, args) abort
1256 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1258 let [mp, efm] = [&l:mp, &l:efm]
1259 let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1261 let &l:errorformat = ''
1262 \ . '%-Gerror:%.%#false''.,'
1263 \ . '%-G%.%# ''git commit'' %.%#,'
1264 \ . '%+Emerge:%.%#,'
1265 \ . s:common_efm . ','
1266 \ . '%+ECannot %.%#: You have unstaged changes.,'
1267 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1268 \ . '%+EThere is no tracking information for the current branch.,'
1269 \ . '%+EYou are not currently on a branch. Please specify which,'
1270 \ . 'CONFLICT (%m): %f deleted in %.%#,'
1271 \ . 'CONFLICT (%m): Merge conflict in %f,'
1272 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1273 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1274 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1275 \ . '%+ECONFLICT %.%#,'
1276 \ . '%+EKONFLIKT %.%#,'
1277 \ . '%+ECONFLIT %.%#,'
1278 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1279 \ . "%+E\u51b2\u7a81 %.%#,"
1281 if a:cmd =~# '^merge' && empty(a:args) &&
1282 \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1283 \ !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1284 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1286 let &l:makeprg = s:sub(s:git_command() . ' ' . a:cmd .
1287 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
1288 \ ' ' . a:args, ' *$', '')
1290 if !empty($GIT_EDITOR) || has('win32')
1291 let old_editor = $GIT_EDITOR
1292 let $GIT_EDITOR = 'false'
1294 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1296 execute cd fnameescape(s:repo().tree())
1297 silent noautocmd make!
1298 catch /^Vim\%((\a\+)\)\=:E211/
1299 let err = v:exception
1302 let [&l:mp, &l:efm] = [mp, efm]
1303 if exists('old_editor')
1304 let $GIT_EDITOR = old_editor
1306 execute cd fnameescape(cwd)
1308 call fugitive#reload_status()
1309 if empty(filter(getqflist(),'v:val.valid'))
1310 if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1312 return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1315 let qflist = getqflist()
1320 let e.pattern = '^<<<<<<<'
1323 call fugitive#cwindow()
1325 call setqflist(qflist, 'r')
1330 return exists('err') ? 'echoerr '.string(err) : ''
1333 " Section: Ggrep, Glog
1335 if !exists('g:fugitive_summary_format')
1336 let g:fugitive_summary_format = '%s'
1339 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1340 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1341 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1342 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1344 function! s:Grep(cmd,bang,arg) abort
1345 let grepprg = &grepprg
1346 let grepformat = &grepformat
1347 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1350 execute cd s:fnameescape(s:repo().tree())
1351 let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
1352 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
1353 exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1354 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1356 if bufname(entry.bufnr) =~ ':'
1357 let entry.filename = s:repo().translate(bufname(entry.bufnr))
1360 elseif a:arg =~# '\%(^\| \)--cached\>'
1361 let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1366 if a:cmd =~# '^l' && exists('changed')
1367 call setloclist(0, list, 'r')
1368 elseif exists('changed')
1369 call setqflist(list, 'r')
1371 if !a:bang && !empty(list)
1372 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1374 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1377 let &grepprg = grepprg
1378 let &grepformat = grepformat
1379 execute cd s:fnameescape(dir)
1383 function! s:Log(cmd, line1, line2, ...) abort
1384 let path = s:buffer().path('/')
1385 if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1388 let cmd = ['--no-pager', 'log', '--no-color']
1389 let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1390 if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1391 if s:buffer().commit() =~# '\x\{40\}'
1392 let cmd += [s:buffer().commit()]
1393 elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1394 let cmd += [s:buffer().path()[5:-1]]
1397 let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1400 let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1402 let cmd += ['--', path[1:-1]]
1405 let grepformat = &grepformat
1406 let grepprg = &grepprg
1407 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1410 execute cd s:fnameescape(s:repo().tree())
1411 let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1412 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1415 let &grepformat = grepformat
1416 let &grepprg = grepprg
1417 execute cd s:fnameescape(dir)
1421 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1423 function! s:UsableWin(nr) abort
1424 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
1425 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
1428 function! s:Edit(cmd,bang,...) abort
1429 let buffer = s:buffer()
1431 if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1432 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
1434 exe winnrs[0].'wincmd w'
1435 elseif winnr('$') == 1
1436 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
1437 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
1442 let mywinnr = winnr()
1443 for winnr in range(winnr('$'),1,-1)
1444 if winnr != mywinnr && getwinvar(winnr,'&diff')
1445 execute winnr.'wincmd w'
1458 let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1459 let args = join(arglist, ' ')
1461 let git = buffer.repo().git_command()
1462 let last = line('$')
1463 silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1465 silent execute '1,'.last.'delete_'
1467 call fugitive#reload_status()
1469 return 'redraw|echo '.string(':!'.git.' '.args)
1471 let temp = resolve(tempname())
1473 let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
1475 let s:temp_files[s:cpath(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1476 silent execute a:cmd.' '.temp
1477 if a:cmd =~# 'pedit'
1480 let echo = s:Edit('read',1,args)
1482 setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1483 if getline(1) !~# '^diff '
1484 setlocal readonly nomodifiable
1486 if a:cmd =~# 'pedit'
1497 let file = buffer.expand(join(a:000, ' '))
1498 elseif expand('%') ==# ''
1500 elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1501 let file = buffer.path(':')
1503 let file = buffer.path('/')
1506 let file = buffer.repo().translate(file)
1508 return 'echoerr v:errmsg'
1510 if file !~# '^fugitive:'
1511 let file = s:sub(file, '/$', '')
1514 return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1516 return a:cmd.' '.s:fnameescape(file)
1520 function! s:EditComplete(A,L,P) abort
1521 return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1524 function! s:EditRunComplete(A,L,P) abort
1526 return s:GitComplete(a:A,a:L,a:P)
1528 return s:repo().superglob(a:A)
1532 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge :execute s:Edit('edit<bang>',0,<f-args>)")
1533 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit :execute s:Edit('edit<bang>',0,<f-args>)")
1534 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit :execute s:Edit('pedit',<bang>0,<f-args>)")
1535 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit :execute s:Edit('split',<bang>0,<f-args>)")
1536 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit :execute s:Edit('vsplit',<bang>0,<f-args>)")
1537 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1538 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread :execute s:Edit((<count> == -1 ? '' : <count>).'read',<bang>0,<f-args>)")
1540 " Section: Gwrite, Gwq
1542 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1543 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1544 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1546 function! s:Write(force,...) abort
1547 if exists('b:fugitive_commit_arguments')
1548 return 'write|bdelete'
1549 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1551 elseif s:buffer().type() == 'index'
1553 elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1554 let filename = getline(4)[6:-1]
1557 setlocal buftype=nowrite
1558 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1559 let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1561 let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1564 let v:errmsg = split(err,"\n")[0]
1565 return 'echoerr v:errmsg'
1569 return 'Gedit '.fnameescape(filename)
1572 let mytab = tabpagenr()
1573 let mybufnr = bufnr('')
1574 let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1576 return 'echoerr '.string('fugitive: cannot determine file path')
1578 if path =~# '^:\d\>'
1579 return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1581 let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1582 if !always_permitted && !a:force && s:repo().git_chomp_in_tree('diff','--name-status','HEAD','--',path) . s:repo().git_chomp_in_tree('ls-files','--others','--',path) !=# ''
1583 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1584 return 'echoerr v:errmsg'
1586 let file = s:repo().translate(path)
1588 for nr in range(1,bufnr('$'))
1589 if fnamemodify(bufname(nr),':p') ==# file
1594 if treebufnr > 0 && treebufnr != bufnr('')
1595 let temp = tempname()
1596 silent execute '%write '.temp
1597 for tab in [mytab] + range(1,tabpagenr('$'))
1598 for winnr in range(1,tabpagewinnr(tab,'$'))
1599 if tabpagebuflist(tab)[winnr-1] == treebufnr
1600 execute 'tabnext '.tab
1602 execute winnr.'wincmd w'
1603 let restorewinnr = 1
1606 let lnum = line('.')
1607 let last = line('$')
1608 silent execute '$read '.temp
1609 silent execute '1,'.last.'delete_'
1614 if exists('restorewinnr')
1617 execute 'tabnext '.mytab
1623 call writefile(readfile(temp,'b'),file,'b')
1626 execute 'write! '.s:fnameescape(s:repo().translate(path))
1630 let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1632 let error = s:repo().git_chomp_in_tree('add', '--', path)
1635 let v:errmsg = 'fugitive: '.error
1636 return 'echoerr v:errmsg'
1638 if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1642 let one = s:repo().translate(':1:'.path)
1643 let two = s:repo().translate(':2:'.path)
1644 let three = s:repo().translate(':3:'.path)
1645 for nr in range(1,bufnr('$'))
1646 let name = fnamemodify(bufname(nr), ':p')
1647 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1648 execute nr.'bdelete'
1653 let zero = s:repo().translate(':0:'.path)
1654 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
1655 for tab in range(1,tabpagenr('$'))
1656 for winnr in range(1,tabpagewinnr(tab,'$'))
1657 let bufnr = tabpagebuflist(tab)[winnr-1]
1658 let bufname = fnamemodify(bufname(bufnr), ':p')
1659 if bufname ==# zero && bufnr != mybufnr
1660 execute 'tabnext '.tab
1662 execute winnr.'wincmd w'
1663 let restorewinnr = 1
1666 let lnum = line('.')
1667 let last = line('$')
1668 silent execute '$read '.s:fnameescape(file)
1669 silent execute '1,'.last.'delete_'
1674 if exists('restorewinnr')
1677 execute 'tabnext '.mytab
1683 call fugitive#reload_status()
1687 function! s:Wq(force,...) abort
1688 let bang = a:force ? '!' : ''
1689 if exists('b:fugitive_commit_arguments')
1692 let result = call(s:function('s:Write'),[a:force]+a:000)
1693 if result =~# '^\%(write\|wq\|echoerr\)'
1694 return s:sub(result,'^write','wq')
1696 return result.'|quit'.bang
1700 augroup fugitive_commit
1702 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1705 " Section: Gpush, Gfetch
1707 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
1708 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1710 function! s:Dispatch(bang, args)
1711 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1713 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1715 let b:current_compiler = 'git'
1716 let &l:errorformat = s:common_efm
1717 let &l:makeprg = substitute(s:git_command() . ' ' . a:args, '\s\+$', '', '')
1718 execute cd fnameescape(s:repo().tree())
1719 if exists(':Make') == 2
1722 silent noautocmd make!
1724 return 'call fugitive#cwindow()'
1728 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1729 if empty(cc) | unlet! b:current_compiler | endif
1730 execute cd fnameescape(cwd)
1736 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
1737 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
1738 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
1740 augroup fugitive_diff
1742 autocmd BufWinLeave *
1743 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1744 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1746 autocmd BufWinEnter *
1747 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1748 \ call s:diffoff() |
1752 function! s:can_diffoff(buf) abort
1753 return getwinvar(bufwinnr(a:buf), '&diff') &&
1754 \ !empty(getbufvar(a:buf, 'git_dir')) &&
1755 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1758 function! fugitive#can_diffoff(buf) abort
1759 return s:can_diffoff(a:buf)
1762 function! s:diff_modifier(count) abort
1763 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1764 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1766 elseif &diffopt =~# 'vertical'
1767 return 'keepalt vert '
1768 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1771 return 'keepalt vert '
1775 function! s:diff_window_count() abort
1777 for nr in range(1,winnr('$'))
1778 let c += getwinvar(nr,'&diff')
1783 function! s:diff_restore() abort
1784 let restore = 'setlocal nodiff noscrollbind'
1785 \ . ' scrollopt=' . &l:scrollopt
1786 \ . (&l:wrap ? ' wrap' : ' nowrap')
1787 \ . ' foldlevel=999'
1788 \ . ' foldmethod=' . &l:foldmethod
1789 \ . ' foldcolumn=' . &l:foldcolumn
1790 \ . ' foldlevel=' . &l:foldlevel
1791 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1792 if has('cursorbind')
1793 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1798 function! s:diffthis() abort
1800 let w:fugitive_diff_restore = s:diff_restore()
1805 function! s:diffoff() abort
1806 if exists('w:fugitive_diff_restore')
1807 execute w:fugitive_diff_restore
1808 unlet w:fugitive_diff_restore
1814 function! s:diffoff_all(dir) abort
1815 let curwin = winnr()
1816 for nr in range(1,winnr('$'))
1817 if getwinvar(nr,'&diff')
1819 execute nr.'wincmd w'
1820 let restorewinnr = 1
1822 if exists('b:git_dir') && b:git_dir ==# a:dir
1827 execute curwin.'wincmd w'
1830 function! s:buffer_compare_age(commit) dict abort
1831 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1832 let my_score = get(scores,':'.self.commit(),0)
1833 let their_score = get(scores,':'.a:commit,0)
1834 if my_score || their_score
1835 return my_score < their_score ? -1 : my_score != their_score
1836 elseif self.commit() ==# a:commit
1839 let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1840 if base ==# self.commit()
1842 elseif base ==# a:commit
1845 let my_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1846 let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1847 return my_time < their_time ? -1 : my_time != their_time
1850 call s:add_methods('buffer',['compare_age'])
1852 function! s:Diff(vert,keepfocus,...) abort
1853 let args = copy(a:000)
1855 if get(args, 0) =~# '^+'
1856 let post = remove(args, 0)[1:-1]
1858 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1859 if exists(':DiffGitCached')
1860 return 'DiffGitCached'
1861 elseif (empty(args) || args[0] == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1862 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1864 execute 'leftabove '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':2')))
1865 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1869 execute 'rightbelow '.vert.'split' s:fnameescape(fugitive#repo().translate(s:buffer().expand(':3')))
1870 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1875 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
1876 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
1879 let arg = join(args, ' ')
1883 let file = s:buffer().path('/')
1885 let file = s:buffer().path(':0:')
1886 elseif arg =~# '^:/.'
1888 let file = s:repo().rev_parse(arg).s:buffer().path(':')
1890 return 'echoerr v:errmsg'
1893 let file = s:buffer().expand(arg)
1895 if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1896 let file = file.s:buffer().path(':')
1899 let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1902 let spec = s:repo().translate(file)
1903 let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1904 let restore = s:diff_restore()
1905 if exists('+cursorbind')
1908 let w:fugitive_diff_restore = restore
1909 if s:buffer().compare_age(commit) < 0
1910 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1912 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1914 let &l:readonly = &l:readonly
1916 let w:fugitive_diff_restore = restore
1918 if getwinvar('#', '&diff')
1921 call feedkeys(winnr."\<C-W>w", 'n')
1926 return 'echoerr v:errmsg'
1930 " Section: Gmove, Gremove
1932 function! s:Move(force, rename, destination) abort
1933 if a:destination =~# '^/'
1934 let destination = a:destination[1:-1]
1936 let destination = fnamemodify(s:buffer().path(), ':h') . '/' . a:destination
1938 let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1939 if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1940 let destination = destination[strlen(s:repo().tree('')):-1]
1943 if isdirectory(s:buffer().spec())
1944 " Work around Vim parser idiosyncrasy
1945 let discarded = s:buffer().setvar('&swapfile',0)
1947 let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1949 let v:errmsg = 'fugitive: '.message
1950 return 'echoerr v:errmsg'
1952 let destination = s:repo().tree(destination)
1953 if isdirectory(destination)
1954 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1956 call fugitive#reload_status()
1957 if empty(s:buffer().commit())
1958 if isdirectory(destination)
1959 return 'keepalt edit '.s:fnameescape(destination)
1961 return 'keepalt saveas! '.s:fnameescape(destination)
1964 return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1968 function! s:MoveComplete(A,L,P) abort
1970 return s:repo().superglob(a:A)
1972 let matches = split(glob(a:A.'*'),"\n")
1973 call map(matches,'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1978 function! s:RenameComplete(A,L,P) abort
1980 return s:repo().superglob(a:A)
1982 let pre = '/'. fnamemodify(s:buffer().path(), ':h') . '/'
1983 return map(s:repo().superglob(pre.a:A), 'strpart(v:val, len(pre))')
1987 function! s:Remove(after, force) abort
1988 if s:buffer().commit() ==# ''
1990 elseif s:buffer().commit() ==# '0'
1991 let cmd = ['rm','--cached']
1993 let v:errmsg = 'fugitive: rm not supported here'
1994 return 'echoerr v:errmsg'
1997 let cmd += ['--force']
1999 let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
2001 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2002 return 'echoerr '.string(v:errmsg)
2004 call fugitive#reload_status()
2005 return a:after . (a:force ? '!' : '')
2009 augroup fugitive_remove
2011 autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
2012 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2013 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2014 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2015 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2021 augroup fugitive_blame
2023 autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
2024 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
2025 autocmd Syntax fugitiveblame call s:BlameSyntax()
2026 autocmd User Fugitive if s:buffer().type('file', 'blob') | exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,[<f-args>])" | endif
2027 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2030 function! s:linechars(pattern) abort
2031 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2032 if exists('*synconcealed') && &conceallevel > 1
2033 for col in range(1, chars)
2034 let chars -= synconcealed(line('.'), col)[0]
2040 function! s:Blame(bang,line1,line2,count,args) abort
2041 if exists('b:fugitive_blamed_bufnr')
2045 if s:buffer().path() == ''
2046 call s:throw('file or blob required')
2048 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2049 call s:throw('unsupported option')
2051 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2052 let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
2053 if s:buffer().commit() =~# '\D\|..'
2054 let cmd += [s:buffer().commit()]
2056 let cmd += ['--contents', '-']
2058 let cmd += ['--', s:buffer().path()]
2059 let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!%#')
2061 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2064 execute cd s:fnameescape(s:repo().tree())
2067 execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
2069 let error = resolve(tempname())
2070 let temp = error.'.fugitiveblame'
2072 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2074 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2077 execute cd s:fnameescape(dir)
2081 call s:throw(join(readfile(error),"\n"))
2083 for winnr in range(winnr('$'),1,-1)
2084 call setwinvar(winnr, '&scrollbind', 0)
2085 if exists('+cursorbind')
2086 call setwinvar(winnr, '&cursorbind', 0)
2088 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2089 execute winbufnr(winnr).'bdelete'
2092 let bufnr = bufnr('')
2093 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
2094 if exists('+cursorbind')
2095 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
2098 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
2101 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
2103 setlocal scrollbind nowrap nofoldenable
2104 if exists('+cursorbind')
2107 let top = line('w0') + &scrolloff
2108 let current = line('.')
2110 let temp = fnamemodify(fnamemodify(temp, ':h'), ':p').fnamemodify(temp, ':t')
2112 let s:temp_files[s:cpath(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
2113 exe 'keepalt leftabove vsplit '.temp
2114 let b:fugitive_blamed_bufnr = bufnr
2115 let w:fugitive_leave = restore
2116 let b:fugitive_blame_arguments = join(a:args,' ')
2120 if exists('+cursorbind')
2123 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
2124 if exists('+concealcursor')
2125 setlocal concealcursor=nc conceallevel=2
2127 if exists('+relativenumber')
2128 setlocal norelativenumber
2130 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
2131 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
2132 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
2133 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
2134 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>
2135 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2136 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
2137 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
2138 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
2139 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
2140 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
2141 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
2142 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
2143 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
2144 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
2150 execute cd s:fnameescape(dir)
2155 return 'echoerr v:errmsg'
2159 function! s:BlameCommit(cmd) abort
2160 let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
2161 if cmd =~# '^echoerr'
2164 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2165 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2167 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2170 if search('^diff .* b/\M'.escape(path,'\').'$','W')
2172 let head = line('.')
2173 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2174 let top = +matchstr(getline('.'),' +\zs\d\+')
2175 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2176 if lnum >= top && lnum <= top + len
2177 let offset = lnum - top
2185 while offset > 0 && line('.') < line('$')
2187 if getline('.') =~# '^[ +]'
2200 function! s:BlameJump(suffix) abort
2201 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2202 if commit =~# '^0\+$'
2205 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2206 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2208 let path = s:buffer(b:fugitive_blamed_bufnr).path()
2210 let args = b:fugitive_blame_arguments
2211 let offset = line('.') - line('w0')
2212 let bufnr = bufnr('%')
2213 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2215 exe winnr.'wincmd w'
2217 execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2222 if exists(':Gblame')
2223 execute 'Gblame '.args
2225 let delta = line('.') - line('w0') - offset
2227 execute 'normal! '.delta."\<C-E>"
2229 execute 'normal! '.(-delta)."\<C-Y>"
2236 let s:hash_colors = {}
2238 function! s:BlameSyntax() abort
2239 let b:current_syntax = 'fugitiveblame'
2240 let conceal = has('conceal') ? ' conceal' : ''
2241 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2242 syn match FugitiveblameBoundary "^\^"
2243 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2244 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2245 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2246 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2247 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2248 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2249 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2250 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2251 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2252 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
2253 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2254 hi def link FugitiveblameBoundary Keyword
2255 hi def link FugitiveblameHash Identifier
2256 hi def link FugitiveblameUncommitted Ignore
2257 hi def link FugitiveblameTime PreProc
2258 hi def link FugitiveblameLineNumber Number
2259 hi def link FugitiveblameOriginalFile String
2260 hi def link FugitiveblameOriginalLineNumber Float
2261 hi def link FugitiveblameShort FugitiveblameDelimiter
2262 hi def link FugitiveblameDelimiter Delimiter
2263 hi def link FugitiveblameNotCommittedYet Comment
2265 for lnum in range(1, line('$'))
2266 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2267 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2271 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
2272 \ && empty(get(s:hash_colors, hash))
2273 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2274 let color = csapprox#per_component#Approximate(r, g, b)
2275 if color == 16 && &background ==# 'dark'
2278 let s:hash_colors[hash] = ' ctermfg='.color
2280 let s:hash_colors[hash] = ''
2282 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2284 call s:RehighlightBlame()
2287 function! s:RehighlightBlame() abort
2288 for [hash, cterm] in items(s:hash_colors)
2289 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
2290 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2292 exe 'hi link FugitiveblameHash'.hash.' Identifier'
2299 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2301 let s:redirects = {}
2303 function! s:Browse(bang,line1,count,...) abort
2305 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
2307 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
2308 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
2314 let expanded = s:buffer().rev()
2316 let expanded = s:buffer().path('/')
2318 let expanded = s:buffer().expand(rev)
2320 let full = s:repo().translate(expanded)
2322 if full =~# '^fugitive://'
2323 let commit = matchstr(full,'://.*//\zs\w\w\+')
2324 let path = matchstr(full,'://.*//\w\+\zs/.*')
2326 let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2327 let branch = matchstr(expanded, '^[^:]*')
2331 let path = path[1:-1]
2332 elseif s:repo().bare()
2333 let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2336 let path = full[strlen(s:repo().tree())+1:-1]
2337 if path =~# '^\.git/'
2339 elseif isdirectory(full)
2345 if type ==# 'tree' && !empty(path)
2346 let path = s:sub(path, '/\=$', '/')
2348 if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2349 let body = readfile(s:repo().dir(path[5:-1]))[0]
2350 if body =~# '^\x\{40\}$'
2354 elseif body =~# '^ref: refs/'
2355 let path = '.git/' . matchstr(body,'ref: \zs.*')
2360 if path =~# '^\.git/refs/remotes/.'
2362 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
2363 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
2365 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
2366 let path = '.git/refs/heads/'.merge
2368 elseif path =~# '^\.git/refs/heads/.'
2369 let branch = path[16:-1]
2370 elseif !exists('branch')
2371 let branch = s:repo().head()
2374 let r = s:repo().git_chomp('config','branch.'.branch.'.remote')
2375 let m = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1]
2376 if r ==# '.' && !empty(m)
2377 let r2 = s:repo().git_chomp('config','branch.'.m.'.remote')
2380 let m = s:repo().git_chomp('config','branch.'.m.'.merge')[11:-1]
2386 if r ==# '.' || r ==# remote
2388 if path =~# '^\.git/refs/heads/.'
2389 let path = '.git/refs/heads/'.merge
2394 if empty(commit) && path !~# '^\.git/'
2395 if a:line1 && !a:count && !empty(merge)
2398 let commit = s:repo().rev_parse('HEAD')
2404 let remote_for_url = 'origin'
2406 let remote_for_url = remote
2408 if fugitive#git_version() =~# '^[01]\.\|^2\.[0-6]\.'
2409 let raw = s:repo().git_chomp('config','remote.'.remote_for_url.'.url')
2411 let raw = s:repo().git_chomp('remote','get-url',remote_for_url)
2417 if raw =~# '^https\=://' && s:executable('curl')
2418 if !has_key(s:redirects, raw)
2419 let s:redirects[raw] = matchstr(system('curl -I ' .
2420 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
2421 \ 'Location: \zs\S\+\ze/info/refs?')
2423 if len(s:redirects[raw])
2424 let raw = s:redirects[raw]
2428 for Handler in g:fugitive_browse_handlers
2429 let url = call(Handler, [{
2432 \ 'revision': 'No longer provided',
2436 \ 'line1': a:count > 0 ? a:line1 : 0,
2437 \ 'line2': a:count > 0 ? a:count : 0}])
2443 if empty(url) && raw ==# '.'
2444 call s:throw("Instaweb failed to start")
2446 call s:throw("'".remote."' is not a supported remote")
2449 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
2454 return 'echomsg '.string(url)
2455 elseif exists(':Browse') == 2
2456 return 'echomsg '.string(url).'|Browse '.url
2458 if !exists('g:loaded_netrw')
2459 runtime! autoload/netrw.vim
2461 if exists('*netrw#BrowseX')
2462 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
2464 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2468 return 'echoerr v:errmsg'
2472 function! s:github_url(opts, ...) abort
2473 if a:0 || type(a:opts) != type({})
2476 let domain_pattern = 'github\.com'
2477 let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2478 for domain in domains
2479 let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2481 let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2485 call s:warn('Install rhubarb.vim for GitHub support')
2486 return 'https://github.com/tpope/vim-rhubarb'
2489 function! s:instaweb_url(opts) abort
2490 if a:opts.remote !=# '.'
2493 let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2494 if output =~# 'http://'
2495 let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2499 if a:opts.path =~# '^\.git/refs/.'
2500 return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2501 elseif a:opts.path =~# '^\.git\>'
2505 if a:opts.commit =~# '^\x\{40\}$'
2506 if a:opts.type ==# 'commit'
2507 let url .= ';a=commit'
2509 let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2511 if a:opts.type ==# 'blob' && empty(a:opts.commit)
2512 let url .= ';h='.a:opts.repo.git_chomp('hash-object', '-w', a:opts.path)
2515 let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2517 call s:throw('fugitive: cannot browse uncommitted file')
2520 let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2522 if a:opts.path !=# ''
2523 let url .= ';f=' . a:opts.path
2525 if get(a:opts, 'line1')
2526 let url .= '#l' . a:opts.line1
2531 if !exists('g:fugitive_browse_handlers')
2532 let g:fugitive_browse_handlers = []
2535 call extend(g:fugitive_browse_handlers,
2536 \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2538 " Section: File access
2540 function! s:ReplaceCmd(cmd,...) abort
2541 let fn = expand('%:p')
2542 let tmp = tempname()
2547 let old_index = $GIT_INDEX_FILE
2548 let $GIT_INDEX_FILE = a:1
2550 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2553 let redir = ' > '.tmp
2554 if &shellpipe =~ '2>&1'
2555 let redir .= ' 2>&1'
2558 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
2559 call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').redir.'"')
2560 elseif &shell =~# 'fish'
2561 call system(' begin;'.prefix.a:cmd.redir.';end ')
2563 call system(' ('.prefix.a:cmd.redir.') ')
2566 if exists('old_index')
2567 let $GIT_INDEX_FILE = old_index
2570 silent exe 'doau BufReadPre '.s:fnameescape(fn)
2571 silent exe 'keepalt file '.tmp
2573 silent noautocmd edit!
2576 silent exe 'keepalt file '.s:fnameescape(fn)
2577 catch /^Vim\%((\a\+)\)\=:E302/
2580 if fnamemodify(bufname('$'), ':p') ==# tmp
2581 silent execute 'bwipeout '.bufnr('$')
2583 silent exe 'doau BufReadPost '.s:fnameescape(fn)
2587 function! s:BufReadIndex() abort
2588 if !exists('b:fugitive_display_format')
2589 let b:fugitive_display_format = filereadable(expand('%').'.lock')
2591 let b:fugitive_display_format = b:fugitive_display_format % 2
2592 let b:fugitive_type = 'index'
2594 let b:git_dir = s:repo().dir()
2595 setlocal noro ma nomodeline
2596 if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2599 let index = expand('%:p')
2601 if b:fugitive_display_format
2602 call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2605 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2607 if fugitive#git_version() =~# '^0\|^1\.[1-7]\.'
2608 let cmd = s:repo().git_command('status')
2610 let cmd = s:repo().git_command(
2611 \ '-c', 'status.displayCommentPrefix=true',
2612 \ '-c', 'color.status=false',
2613 \ '-c', 'status.short=false',
2617 execute cd s:fnameescape(s:repo().tree())
2618 call s:ReplaceCmd(cmd, index)
2620 execute cd s:fnameescape(dir)
2623 set foldtext=fugitive#foldtext()
2625 setlocal ro noma nomod noswapfile
2626 if &bufhidden ==# ''
2627 setlocal bufhidden=delete
2632 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2633 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2634 nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2635 xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2636 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2637 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2638 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
2639 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<CR>
2640 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2641 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2642 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
2643 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
2644 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
2645 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
2646 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2647 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2648 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2649 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2650 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2651 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2652 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2653 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2654 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2655 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2656 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2657 nnoremap <buffer> <silent> r :<C-U>edit<CR>
2658 nnoremap <buffer> <silent> R :<C-U>edit<CR>
2659 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2660 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
2661 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2663 return 'echoerr v:errmsg'
2667 function! s:FileRead() abort
2669 let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2670 let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2671 let hash = repo.rev_parse(path)
2675 let type = repo.git_chomp('cat-file','-t',hash)
2677 " TODO: use count, if possible
2678 return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2680 return 'echoerr v:errmsg'
2684 function! s:BufReadIndexFile() abort
2686 let b:fugitive_type = 'blob'
2687 let b:git_dir = s:repo().dir()
2689 call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2691 if &bufhidden ==# ''
2692 setlocal bufhidden=delete
2697 catch /^fugitive: rev-parse/
2698 silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2701 return 'echoerr v:errmsg'
2705 function! s:BufWriteIndexFile() abort
2706 let tmp = tempname()
2708 let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2709 let stage = matchstr(expand('<amatch>'),'//\zs\d')
2710 silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2711 let sha1 = readfile(tmp)[0]
2712 let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2714 let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2716 let info = old_mode.' '.sha1.' '.stage."\t".path
2717 call writefile([info],tmp)
2719 let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2721 let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2723 if v:shell_error == 0
2725 if exists('#BufWritePost')
2726 execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2728 call fugitive#reload_status()
2731 return 'echoerr '.string('fugitive: '.error)
2738 function! s:BufReadObject() abort
2741 let b:git_dir = s:repo().dir()
2742 let hash = s:buffer().sha1()
2743 if !exists("b:fugitive_type")
2744 let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2746 if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2747 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
2749 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2750 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2753 if b:fugitive_type !=# 'blob'
2757 let pos = getpos('.')
2758 silent keepjumps %delete_
2762 if b:fugitive_type ==# 'tree'
2763 let b:fugitive_display_format = b:fugitive_display_format % 2
2764 if b:fugitive_display_format
2765 call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2767 call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2769 elseif b:fugitive_type ==# 'tag'
2770 let b:fugitive_display_format = b:fugitive_display_format % 2
2771 if b:fugitive_display_format
2772 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2774 call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2776 elseif b:fugitive_type ==# 'commit'
2777 let b:fugitive_display_format = b:fugitive_display_format % 2
2778 if b:fugitive_display_format
2779 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2781 call s:ReplaceCmd(s:repo().git_command('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',hash))
2782 keepjumps call search('^parent ')
2783 if getline('.') ==# 'parent '
2784 silent keepjumps delete_
2786 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
2788 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2790 silent keepjumps delete_
2792 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
2795 elseif b:fugitive_type ==# 'blob'
2796 call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2800 keepjumps call setpos('.',pos)
2801 setlocal ro noma nomod noswapfile
2802 if &bufhidden ==# ''
2803 setlocal bufhidden=delete
2805 if b:fugitive_type !=# 'blob'
2806 setlocal filetype=git foldmethod=syntax
2807 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2808 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2815 catch /^fugitive: rev-parse/
2818 return 'echoerr v:errmsg'
2822 augroup fugitive_files
2824 autocmd BufReadCmd index{,.lock}
2825 \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2826 \ exe s:BufReadIndex() |
2827 \ elseif filereadable(expand('<amatch>')) |
2831 autocmd FileReadCmd fugitive://**//[0-3]/** exe s:FileRead()
2832 autocmd BufReadCmd fugitive://**//[0-3]/** exe s:BufReadIndexFile()
2833 autocmd BufWriteCmd fugitive://**//[0-3]/** exe s:BufWriteIndexFile()
2834 autocmd BufReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2835 autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2836 autocmd FileType git
2837 \ if exists('b:git_dir') |
2838 \ call s:JumpInit() |
2840 autocmd FileType git,gitcommit,gitrebase
2841 \ if exists('b:git_dir') |
2846 " Section: Temp files
2848 if !exists('s:temp_files')
2849 let s:temp_files = {}
2852 augroup fugitive_temp
2854 autocmd BufNewFile,BufReadPost *
2855 \ if has_key(s:temp_files,s:cpath(expand('<afile>:p'))) |
2856 \ let b:git_dir = s:temp_files[s:cpath(expand('<afile>:p'))].dir |
2857 \ let b:git_type = 'temp' |
2858 \ let b:git_args = s:temp_files[s:cpath(expand('<afile>:p'))].args |
2859 \ call fugitive#detect(expand('<afile>:p')) |
2860 \ setlocal bufhidden=delete nobuflisted |
2861 \ nnoremap <buffer> <silent> q :<C-U>bdelete<CR>|
2865 " Section: Go to file
2867 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
2868 function! s:GFInit(...) abort
2869 cnoremap <buffer> <expr> <Plug><cfile> fugitive#cfile()
2870 if !exists('g:fugitive_no_maps')
2871 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>')
2872 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>')
2873 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>')
2874 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>')
2875 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>')
2879 function! s:JumpInit(...) abort
2880 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
2882 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
2883 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
2884 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
2885 nnoremap <buffer> <silent> - :<C-U>exe <SID>Edit('edit',0,<SID>buffer().up(v:count1))<Bar> if fugitive#buffer().type('tree')<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>
2886 nnoremap <buffer> <silent> P :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2887 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2888 nnoremap <buffer> <silent> C :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2889 nnoremap <buffer> <silent> cc :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2890 nnoremap <buffer> <silent> co :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2891 nnoremap <buffer> <silent> cS :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2892 nnoremap <buffer> <silent> cO :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2893 nnoremap <buffer> <silent> cP :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2894 nnoremap <buffer> . : <C-R>=fnameescape(<SID>recall())<CR><Home>
2898 function! s:cfile() abort
2900 let buffer = s:buffer()
2901 let myhash = buffer.sha1()
2902 if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2903 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2906 if buffer.type('tree')
2907 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2908 if showtree && line('.') > 2
2909 return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$','')]
2910 elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2911 return [buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
2914 elseif buffer.type('blob')
2915 let ref = expand("<cfile>")
2917 let sha1 = buffer.repo().rev_parse(ref)
2929 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2930 let ref = matchstr(getline('.'),'\x\{40\}')
2931 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2934 elseif getline('.') =~# '^#\trenamed:.* -> '
2935 let file = '/'.matchstr(getline('.'),' -> \zs.*')
2937 elseif getline('.') =~# '^#\t\(\k\| \)\+\p\?: *.'
2938 let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2940 elseif getline('.') =~# '^#\t.'
2941 let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2943 elseif getline('.') =~# ': needs merge$'
2944 let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2945 return [file, 'Gdiff!']
2947 elseif getline('.') ==# '# Not currently on any branch.'
2949 elseif getline('.') =~# '^# On branch '
2950 let file = 'refs/heads/'.getline('.')[12:]
2952 elseif getline('.') =~# "^# Your branch .*'"
2953 let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2957 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2959 if getline('.') =~# '^ref: '
2960 let ref = strpart(getline('.'),5)
2962 elseif getline('.') =~# '^commit \x\{40\}\>'
2963 let ref = matchstr(getline('.'),'\x\{40\}')
2966 elseif getline('.') =~# '^parent \x\{40\}\>'
2967 let ref = matchstr(getline('.'),'\x\{40\}')
2968 let line = line('.')
2970 while getline(line) =~# '^parent '
2976 elseif getline('.') =~ '^tree \x\{40\}$'
2977 let ref = matchstr(getline('.'),'\x\{40\}')
2978 if s:repo().rev_parse(myhash.':') == ref
2979 let ref = myhash.':'
2983 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2984 let ref = matchstr(getline('.'),'\x\{40\}')
2985 let type = matchstr(getline(line('.')+1),'type \zs.*')
2987 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2988 let ref = buffer.rev()
2990 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2991 let ref = matchstr(getline('.'),'\x\{40\}')
2992 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2994 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
2995 let ref = getline('.')[4:]
2997 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2998 let type = getline('.')[0]
2999 let lnum = line('.') - 1
3001 while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
3002 if getline(lnum) =~# '^[ '.type.']'
3007 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3008 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3009 let dcmds = [offset, 'normal!zv']
3011 elseif getline('.') =~# '^rename from '
3012 let ref = 'a/'.getline('.')[12:]
3013 elseif getline('.') =~# '^rename to '
3014 let ref = 'b/'.getline('.')[10:]
3016 elseif getline('.') =~# '^@@ -\d\+,\d\+ +\d\+,'
3017 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3018 let offset = matchstr(getline('.'), '+\zs\d\+')
3020 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3021 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3022 let dcmd = 'Gdiff! +'.offset
3024 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3025 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3026 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3029 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3030 let line = getline(line('.')-1)
3031 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3032 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3035 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3036 let ref = getline('.')
3038 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3039 return [expand('<cword>')]
3054 let prefixes.a = myhash.'^:'
3055 let prefixes.b = myhash.':'
3057 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3059 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3062 if ref ==# '/dev/null'
3064 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3068 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3070 return [ref] + dcmds
3078 function! s:GF(mode) abort
3080 let results = s:cfile()
3082 return 'echoerr v:errmsg'
3085 return s:Edit(a:mode, 0, results[0]).join(map(results[1:-1], '"|".v:val'), '')
3091 function! fugitive#cfile() abort
3093 let results = s:cfile()
3095 let cfile = expand('<cfile>')
3096 if &includeexpr =~# '\<v:fname\>'
3097 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3100 elseif len(results) > 1
3101 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3103 return pre . s:fnameescape(fugitive#repo().translate(results[0]))
3106 function! fugitive#Cfile() abort
3107 return fugitive#cfile()
3110 " Section: Statusline
3112 function! s:repo_head_ref() dict abort
3113 if !filereadable(self.dir('HEAD'))
3116 return readfile(self.dir('HEAD'))[0]
3119 call s:add_methods('repo',['head_ref'])
3121 function! fugitive#statusline(...) abort
3122 if !exists('b:git_dir')
3126 if s:buffer().commit() != ''
3127 let status .= ':' . s:buffer().commit()[0:7]
3129 let status .= '('.fugitive#head(7).')'
3130 if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
3131 return ',GIT'.status
3133 return '[Git'.status.']'
3137 function! fugitive#Statusline(...) abort
3138 return fugitive#statusline()
3141 function! FugitiveStatusline(...) abort
3142 return fugitive#statusline()
3145 function! fugitive#head(...) abort
3146 if !exists('b:git_dir')
3150 return s:repo().head(a:0 ? a:1 : 0)
3153 function! FugitiveHead(...) abort
3154 return fugitive#head(a:0 ? a:1 : 0)
3157 augroup fugitive_statusline
3159 autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
3164 function! fugitive#foldtext() abort
3165 if &foldmethod !=# 'syntax'
3169 let line_foldstart = getline(v:foldstart)
3170 if line_foldstart =~# '^diff '
3171 let [add, remove] = [-1, -1]
3173 for lnum in range(v:foldstart, v:foldend)
3174 let line = getline(lnum)
3175 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3176 let filename = line[6:-1]
3180 elseif line =~# '^-'
3182 elseif line =~# '^Binary '
3187 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3190 let filename = line_foldstart[5:-1]
3193 return 'Binary: '.filename
3195 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3197 elseif line_foldstart =~# '^# .*:$'
3198 let lines = getline(v:foldstart, v:foldend)
3199 call filter(lines, 'v:val =~# "^#\t"')
3200 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3201 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3202 return line_foldstart.' '.join(lines, ', ')
3207 function! fugitive#Foldtext() abort
3208 return fugitive#foldtext()
3211 augroup fugitive_foldtext
3213 autocmd User Fugitive
3214 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3215 \ set foldtext=fugitive#Foldtext() |