Open :Gcommit tab before current
[vim-fugitive.git] / plugin / fugitive.vim
blob2adb3ac4d2bd2aec4b20fbadfca7883ea14cb69a
1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer:   Tim Pope <http://tpo.pe/>
3 " Version:      2.2
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
6 if exists('g:loaded_fugitive') || &cp
7   finish
8 endif
9 let g:loaded_fugitive = 1
11 if !exists('g:fugitive_git_executable')
12   let g:fugitive_git_executable = 'git'
13 endif
15 " Section: Utility
17 function! s:function(name) abort
18   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
19 endfunction
21 function! s:sub(str,pat,rep) abort
22   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
23 endfunction
25 function! s:gsub(str,pat,rep) abort
26   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
27 endfunction
29 function! s:winshell() abort
30   return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
31 endfunction
33 function! s:shellesc(arg) abort
34   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
35     return a:arg
36   elseif s:winshell()
37     return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
38   else
39     return shellescape(a:arg)
40   endif
41 endfunction
43 function! s:fnameescape(file) abort
44   if exists('*fnameescape')
45     return fnameescape(a:file)
46   else
47     return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
48   endif
49 endfunction
51 function! s:throw(string) abort
52   let v:errmsg = 'fugitive: '.a:string
53   throw v:errmsg
54 endfunction
56 function! s:warn(str) abort
57   echohl WarningMsg
58   echomsg a:str
59   echohl None
60   let v:warningmsg = a:str
61 endfunction
63 function! s:shellslash(path) abort
64   if s:winshell()
65     return s:gsub(a:path,'\\','/')
66   else
67     return a:path
68   endif
69 endfunction
71 let s:git_versions = {}
73 function! fugitive#git_version(...) abort
74   if !has_key(s:git_versions, g:fugitive_git_executable)
75     let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\n")
76   endif
77   return s:git_versions[g:fugitive_git_executable]
78 endfunction
80 function! s:recall() abort
81   let rev = s:sub(s:buffer().rev(), '^/', '')
82   if rev ==# ':'
83     return matchstr(getline('.'),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$\|^\d\{6} \x\{40\} \d\t\zs.*')
84   elseif s:buffer().type('tree')
85     let file = matchstr(getline('.'), '\t\zs.*')
86     if empty(file) && line('.') > 2
87       let file = s:sub(getline('.'), '/$', '')
88     endif
89     if !empty(file) && rev !~# ':$'
90       return rev . '/' . file
91     else
92       return rev . file
93     endif
94   endif
95   return rev
96 endfunction
98 function! s:add_methods(namespace, method_names) abort
99   for name in a:method_names
100     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
101   endfor
102 endfunction
104 let s:commands = []
105 function! s:command(definition) abort
106   let s:commands += [a:definition]
107 endfunction
109 function! s:define_commands() abort
110   for command in s:commands
111     exe 'command! -buffer '.command
112   endfor
113 endfunction
115 augroup fugitive_utility
116   autocmd!
117   autocmd User Fugitive call s:define_commands()
118 augroup END
120 let s:abstract_prototype = {}
122 " Section: Initialization
124 function! fugitive#is_git_dir(path) abort
125   let path = s:sub(a:path, '[\/]$', '') . '/'
126   return isdirectory(path.'objects') && isdirectory(path.'refs') && getfsize(path.'HEAD') > 10
127 endfunction
129 function! fugitive#extract_git_dir(path) abort
130   if s:shellslash(a:path) =~# '^fugitive://.*//'
131     return matchstr(s:shellslash(a:path), '\C^fugitive://\zs.\{-\}\ze//')
132   endif
133   let root = s:shellslash(simplify(fnamemodify(a:path, ':p:s?[\/]$??')))
134   let previous = ""
135   while root !=# previous
136     if root =~# '\v^//%([^/]+/?)?$'
137       " This is for accessing network shares from Cygwin Vim. There won't be
138       " any git directory called //.git or //serverName/.git so let's avoid
139       " checking for them since such checks are extremely slow.
140       break
141     endif
142     if index(split($GIT_CEILING_DIRECTORIES, ':'), root) >= 0
143       break
144     endif
145     if root ==# $GIT_WORK_TREE && fugitive#is_git_dir($GIT_DIR)
146       return $GIT_DIR
147     endif
148     if fugitive#is_git_dir($GIT_DIR)
149       " Ensure that we've cached the worktree
150       call s:configured_tree($GIT_DIR)
151       if has_key(s:dir_for_worktree, root)
152         return s:dir_for_worktree[root]
153       endif
154     endif
155     let dir = s:sub(root, '[\/]$', '') . '/.git'
156     let type = getftype(dir)
157     if type ==# 'dir' && fugitive#is_git_dir(dir)
158       return dir
159     elseif type ==# 'link' && fugitive#is_git_dir(dir)
160       return resolve(dir)
161     elseif type !=# '' && filereadable(dir)
162       let line = get(readfile(dir, '', 1), 0, '')
163       if line =~# '^gitdir: \.' && fugitive#is_git_dir(root.'/'.line[8:-1])
164         return simplify(root.'/'.line[8:-1])
165       elseif line =~# '^gitdir: ' && fugitive#is_git_dir(line[8:-1])
166         return line[8:-1]
167       endif
168     elseif fugitive#is_git_dir(root)
169       return root
170     endif
171     let previous = root
172     let root = fnamemodify(root, ':h')
173   endwhile
174   return ''
175 endfunction
177 function! fugitive#detect(path) abort
178   if exists('b:git_dir') && (b:git_dir ==# '' || b:git_dir =~# '/$')
179     unlet b:git_dir
180   endif
181   if !exists('b:git_dir')
182     let dir = fugitive#extract_git_dir(a:path)
183     if dir !=# ''
184       let b:git_dir = dir
185     endif
186   endif
187   if exists('b:git_dir')
188     if exists('#User#FugitiveBoot')
189       try
190         let [save_mls, &modelines] = [&mls, 0]
191         doautocmd User FugitiveBoot
192       finally
193         let &mls = save_mls
194       endtry
195     endif
196     if !exists('g:fugitive_no_maps')
197       cnoremap <buffer> <expr> <C-R><C-G> fnameescape(<SID>recall())
198       nnoremap <buffer> <silent> y<C-G> :call setreg(v:register, <SID>recall())<CR>
199     endif
200     let buffer = fugitive#buffer()
201     if expand('%:p') =~# '//'
202       call buffer.setvar('&path', s:sub(buffer.getvar('&path'), '^\.%(,|$)', ''))
203     endif
204     if stridx(buffer.getvar('&tags'), escape(b:git_dir, ', ')) == -1
205       if filereadable(b:git_dir.'/tags')
206         call buffer.setvar('&tags', escape(b:git_dir.'/tags', ', ').','.buffer.getvar('&tags'))
207       endif
208       if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
209         call buffer.setvar('&tags', escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.buffer.getvar('&tags'))
210       endif
211     endif
212     try
213       let [save_mls, &modelines] = [&mls, 0]
214       doautocmd User Fugitive
215     finally
216       let &mls = save_mls
217     endtry
218   endif
219 endfunction
221 augroup fugitive
222   autocmd!
223   autocmd BufNewFile,BufReadPost * call fugitive#detect(expand('<amatch>:p'))
224   autocmd FileType           netrw call fugitive#detect(expand('%:p'))
225   autocmd User NERDTreeInit,NERDTreeNewRoot call fugitive#detect(b:NERDTreeRoot.path.str())
226   autocmd VimEnter * if expand('<amatch>')==''|call fugitive#detect(getcwd())|endif
227   autocmd CmdWinEnter * call fugitive#detect(expand('#:p'))
228   autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
229 augroup END
231 " Section: Repository
233 let s:repo_prototype = {}
234 let s:repos = {}
235 let s:worktree_for_dir = {}
236 let s:dir_for_worktree = {}
238 function! s:repo(...) abort
239   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : fugitive#extract_git_dir(expand('%:p')))
240   if dir !=# ''
241     if has_key(s:repos, dir)
242       let repo = get(s:repos, dir)
243     else
244       let repo = {'git_dir': dir}
245       let s:repos[dir] = repo
246     endif
247     return extend(extend(repo, s:repo_prototype, 'keep'), s:abstract_prototype, 'keep')
248   endif
249   call s:throw('not a git repository: '.expand('%:p'))
250 endfunction
252 function! fugitive#repo(...) abort
253   return call('s:repo', a:000)
254 endfunction
256 function! s:repo_dir(...) dict abort
257   return join([self.git_dir]+a:000,'/')
258 endfunction
260 function! s:configured_tree(git_dir) abort
261   if !has_key(s:worktree_for_dir, a:git_dir)
262     let s:worktree_for_dir[a:git_dir] = ''
263     let config_file = a:git_dir . '/config'
264     if filereadable(config_file)
265       let config = readfile(config_file,'',10)
266       call filter(config,'v:val =~# "^\\s*worktree *="')
267       if len(config) == 1
268         let s:worktree_for_dir[a:git_dir] = matchstr(config[0], '= *\zs.*')
269         let s:dir_for_worktree[s:worktree_for_dir[a:git_dir]] = a:git_dir
270       endif
271     endif
272   endif
273   if s:worktree_for_dir[a:git_dir] =~# '^\.'
274     return simplify(a:git_dir . '/' . s:worktree_for_dir[a:git_dir])
275   else
276     return s:worktree_for_dir[a:git_dir]
277   endif
278 endfunction
280 function! s:repo_tree(...) dict abort
281   if self.dir() =~# '/\.git$'
282     let dir = self.dir()[0:-6]
283   else
284     let dir = s:configured_tree(self.git_dir)
285   endif
286   if dir ==# ''
287     call s:throw('no work tree')
288   else
289     return join([dir]+a:000,'/')
290   endif
291 endfunction
293 function! s:repo_bare() dict abort
294   if self.dir() =~# '/\.git$'
295     return 0
296   else
297     return s:configured_tree(self.git_dir) ==# ''
298   endif
299 endfunction
301 function! s:repo_translate(spec) dict abort
302   if a:spec ==# '.' || a:spec ==# '/.'
303     return self.bare() ? self.dir() : self.tree()
304   elseif a:spec =~# '^/\=\.git$' && self.bare()
305     return self.dir()
306   elseif a:spec =~# '^/\=\.git/'
307     return self.dir(s:sub(a:spec, '^/=\.git/', ''))
308   elseif a:spec =~# '^/'
309     return self.tree().a:spec
310   elseif a:spec =~# '^:[0-3]:'
311     return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
312   elseif a:spec ==# ':'
313     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(self.dir())] ==# self.dir('') && filereadable($GIT_INDEX_FILE)
314       return fnamemodify($GIT_INDEX_FILE,':p')
315     else
316       return self.dir('index')
317     endif
318   elseif a:spec =~# '^:/'
319     let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
320     return 'fugitive://'.self.dir().'//'.ref
321   elseif a:spec =~# '^:'
322     return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
323   elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
324     return self.dir(a:spec)
325   elseif filereadable(self.dir('refs/'.a:spec))
326     return self.dir('refs/'.a:spec)
327   elseif filereadable(self.dir('refs/tags/'.a:spec))
328     return self.dir('refs/tags/'.a:spec)
329   elseif filereadable(self.dir('refs/heads/'.a:spec))
330     return self.dir('refs/heads/'.a:spec)
331   elseif filereadable(self.dir('refs/remotes/'.a:spec))
332     return self.dir('refs/remotes/'.a:spec)
333   elseif filereadable(self.dir('refs/remotes/'.a:spec.'/HEAD'))
334     return self.dir('refs/remotes/'.a:spec,'/HEAD')
335   else
336     try
337       let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
338       let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
339       return 'fugitive://'.self.dir().'//'.ref.path
340     catch /^fugitive:/
341       return self.tree(a:spec)
342     endtry
343   endif
344 endfunction
346 function! s:repo_head(...) dict abort
347     let head = s:repo().head_ref()
349     if head =~# '^ref: '
350       let branch = s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','')
351     elseif head =~# '^\x\{40\}$'
352       " truncate hash to a:1 characters if we're in detached head mode
353       let len = a:0 ? a:1 : 0
354       let branch = len ? head[0:len-1] : ''
355     else
356       return ''
357     endif
359     return branch
360 endfunction
362 call s:add_methods('repo',['dir','tree','bare','translate','head'])
364 function! s:repo_git_command(...) dict abort
365   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
366   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
367 endfunction
369 function! s:repo_git_chomp(...) dict abort
370   return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
371 endfunction
373 function! s:repo_git_chomp_in_tree(...) dict abort
374   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
375   let dir = getcwd()
376   try
377     execute cd.'`=s:repo().tree()`'
378     return call(s:repo().git_chomp, a:000, s:repo())
379   finally
380     execute cd.'`=dir`'
381   endtry
382 endfunction
384 function! s:repo_rev_parse(rev) dict abort
385   let hash = self.git_chomp('rev-parse','--verify',a:rev)
386   if hash =~ '\<\x\{40\}$'
387     return matchstr(hash,'\<\x\{40\}$')
388   endif
389   call s:throw('rev-parse '.a:rev.': '.hash)
390 endfunction
392 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
394 function! s:repo_dirglob(base) dict abort
395   let base = s:sub(a:base,'^/','')
396   let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
397   call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
398   return matches
399 endfunction
401 function! s:repo_superglob(base) dict abort
402   if a:base =~# '^/' || a:base !~# ':'
403     let results = []
404     if a:base !~# '^/'
405       let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
406       let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
407       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
408       let results += heads
409     endif
410     if !self.bare()
411       let base = s:sub(a:base,'^/','')
412       let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
413       call map(matches,'s:shellslash(v:val)')
414       call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
415       call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
416       let results += matches
417     endif
418     return results
420   elseif a:base =~# '^:'
421     let entries = split(self.git_chomp('ls-files','--stage'),"\n")
422     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
423     if a:base !~# '^:[0-3]\%(:\|$\)'
424       call filter(entries,'v:val[1] == "0"')
425       call map(entries,'v:val[2:-1]')
426     endif
427     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
428     return entries
430   else
431     let tree = matchstr(a:base,'.*[:/]')
432     let entries = split(self.git_chomp('ls-tree',tree),"\n")
433     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
434     call map(entries,'tree.s:sub(v:val,".*\t","")')
435     return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
436   endif
437 endfunction
439 call s:add_methods('repo',['dirglob','superglob'])
441 function! s:repo_config(conf) dict abort
442   return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
443 endfun
445 function! s:repo_user() dict abort
446   let username = s:repo().config('user.name')
447   let useremail = s:repo().config('user.email')
448   return username.' <'.useremail.'>'
449 endfun
451 function! s:repo_aliases() dict abort
452   if !has_key(self,'_aliases')
453     let self._aliases = {}
454     for line in split(self.git_chomp('config','--get-regexp','^alias[.]'),"\n")
455       let self._aliases[matchstr(line,'\.\zs\S\+')] = matchstr(line,' \zs.*')
456     endfor
457   endif
458   return self._aliases
459 endfunction
461 call s:add_methods('repo',['config', 'user', 'aliases'])
463 function! s:repo_keywordprg() dict abort
464   let args = ' --git-dir='.escape(self.dir(),"\\\"' ")
465   if has('gui_running') && !has('win32')
466     return g:fugitive_git_executable . ' --no-pager' . args . ' log -1'
467   else
468     return g:fugitive_git_executable . args . ' show'
469   endif
470 endfunction
472 call s:add_methods('repo',['keywordprg'])
474 " Section: Buffer
476 let s:buffer_prototype = {}
478 function! s:buffer(...) abort
479   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
480   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
481   if buffer.getvar('git_dir') !=# ''
482     return buffer
483   endif
484   call s:throw('not a git repository: '.expand('%:p'))
485 endfunction
487 function! fugitive#buffer(...) abort
488   return s:buffer(a:0 ? a:1 : '%')
489 endfunction
491 function! s:buffer_getvar(var) dict abort
492   return getbufvar(self['#'],a:var)
493 endfunction
495 function! s:buffer_setvar(var,value) dict abort
496   return setbufvar(self['#'],a:var,a:value)
497 endfunction
499 function! s:buffer_getline(lnum) dict abort
500   return get(getbufline(self['#'], a:lnum), 0, '')
501 endfunction
503 function! s:buffer_repo() dict abort
504   return s:repo(self.getvar('git_dir'))
505 endfunction
507 function! s:buffer_type(...) dict abort
508   if self.getvar('fugitive_type') != ''
509     let type = self.getvar('fugitive_type')
510   elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
511     let type = 'head'
512   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
513     let type = 'tree'
514   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
515     let type = 'tree'
516   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
517     let type = 'index'
518   elseif isdirectory(self.spec())
519     let type = 'directory'
520   elseif self.spec() == ''
521     let type = 'null'
522   else
523     let type = 'file'
524   endif
525   if a:0
526     return !empty(filter(copy(a:000),'v:val ==# type'))
527   else
528     return type
529   endif
530 endfunction
532 if has('win32')
534   function! s:buffer_spec() dict abort
535     let bufname = bufname(self['#'])
536     let retval = ''
537     for i in split(bufname,'[^:]\zs\\')
538       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
539     endfor
540     return s:shellslash(fnamemodify(retval,':p'))
541   endfunction
543 else
545   function! s:buffer_spec() dict abort
546     let bufname = bufname(self['#'])
547     return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
548   endfunction
550 endif
552 function! s:buffer_name() dict abort
553   return self.spec()
554 endfunction
556 function! s:buffer_commit() dict abort
557   return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
558 endfunction
560 function! s:buffer_path(...) dict abort
561   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
562   if rev != ''
563     let rev = s:sub(rev,'\w*','')
564   elseif self.spec()[0 : len(self.repo().dir())] ==# self.repo().dir() . '/'
565     let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
566   elseif !self.repo().bare() && self.spec()[0 : len(self.repo().tree())] ==# self.repo().tree() . '/'
567     let rev = self.spec()[strlen(self.repo().tree()) : -1]
568   endif
569   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
570 endfunction
572 function! s:buffer_rev() dict abort
573   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
574   if rev =~ '^\x/'
575     return ':'.rev[0].':'.rev[2:-1]
576   elseif rev =~ '.'
577     return s:sub(rev,'/',':')
578   elseif self.spec() =~ '\.git/index$'
579     return ':'
580   elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
581     return self.spec()[strlen(self.repo().dir())+1 : -1]
582   else
583     return self.path('/')
584   endif
585 endfunction
587 function! s:buffer_sha1() dict abort
588   if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
589     return self.repo().rev_parse(self.rev())
590   else
591     return ''
592   endif
593 endfunction
595 function! s:buffer_expand(rev) dict abort
596   if a:rev =~# '^:[0-3]$'
597     let file = a:rev.self.path(':')
598   elseif a:rev =~# '^[-:]/$'
599     let file = '/'.self.path()
600   elseif a:rev =~# '^-'
601     let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
602   elseif a:rev =~# '^@{'
603     let file = 'HEAD'.a:rev.self.path(':')
604   elseif a:rev =~# '^[~^]'
605     let commit = s:sub(self.commit(),'^\d=$','HEAD')
606     let file = commit.a:rev.self.path(':')
607   else
608     let file = a:rev
609   endif
610   return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
611 endfunction
613 function! s:buffer_containing_commit() dict abort
614   if self.commit() =~# '^\d$'
615     return ':'
616   elseif self.commit() =~# '.'
617     return self.commit()
618   else
619     return 'HEAD'
620   endif
621 endfunction
623 function! s:buffer_up(...) dict abort
624   let rev = self.rev()
625   let c = a:0 ? a:1 : 1
626   while c
627     if rev =~# '^[/:]$'
628       let rev = 'HEAD'
629     elseif rev =~# '^:'
630       let rev = ':'
631     elseif rev =~# '^refs/[^^~:]*$\|^[^^~:]*HEAD$'
632       let rev .= '^{}'
633     elseif rev =~# '^/\|:.*/'
634       let rev = s:sub(rev, '.*\zs/.*', '')
635     elseif rev =~# ':.'
636       let rev = matchstr(rev, '^[^:]*:')
637     elseif rev =~# ':$'
638       let rev = rev[0:-2]
639     else
640       return rev.'~'.c
641     endif
642     let c -= 1
643   endwhile
644   return rev
645 endfunction
647 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit','up'])
649 " Section: Git
651 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
653 function! s:ExecuteInTree(cmd) abort
654   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
655   let dir = getcwd()
656   try
657     execute cd.'`=s:repo().tree()`'
658     execute a:cmd
659   finally
660     execute cd.'`=dir`'
661   endtry
662 endfunction
664 function! s:Git(bang, args) abort
665   if a:bang
666     return s:Edit('edit', 1, a:args)
667   endif
668   let git = g:fugitive_git_executable
669   if has('gui_running') && !has('win32')
670     let git .= ' --no-pager'
671   endif
672   let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
673   if exists(':terminal')
674     let dir = s:repo().tree()
675     tabnew
676     execute 'lcd' fnameescape(dir)
677     execute 'terminal' git args
678   else
679     call s:ExecuteInTree('!'.git.' '.args)
680     if has('win32')
681       call fugitive#reload_status()
682     endif
683   endif
684   return matchstr(a:args, '\v\C\\@<!%(\\\\)*\|\zs.*')
685 endfunction
687 function! fugitive#git_commands() abort
688   if !exists('s:exec_path')
689     let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
690   endif
691   return map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
692 endfunction
694 function! s:GitComplete(A, L, P) abort
695   if strpart(a:L, 0, a:P) !~# ' [[:alnum:]-]\+ '
696     let cmds = fugitive#git_commands()
697     return filter(sort(cmds+keys(s:repo().aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
698   else
699     return s:repo().superglob(a:A)
700   endif
701 endfunction
703 " Section: Gcd, Glcd
705 function! s:DirComplete(A,L,P) abort
706   let matches = s:repo().dirglob(a:A)
707   return matches
708 endfunction
710 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd  :cd<bang>  `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
711 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :lcd<bang> `=s:repo().bare() ? s:repo().dir(<q-args>) : s:repo().tree(<q-args>)`")
713 " Section: Gstatus
715 call s:command("-bar Gstatus :execute s:Status()")
716 augroup fugitive_status
717   autocmd!
718   if !has('win32')
719     autocmd FocusGained,ShellCmdPost * call fugitive#reload_status()
720     autocmd BufDelete term://* call fugitive#reload_status()
721   endif
722 augroup END
724 function! s:Status() abort
725   try
726     Gpedit :
727     wincmd P
728     setlocal foldmethod=syntax foldlevel=1
729     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
730   catch /^fugitive:/
731     return 'echoerr v:errmsg'
732   endtry
733   return ''
734 endfunction
736 function! fugitive#reload_status() abort
737   if exists('s:reloading_status')
738     return
739   endif
740   try
741     let s:reloading_status = 1
742     let mytab = tabpagenr()
743     for tab in [mytab] + range(1,tabpagenr('$'))
744       for winnr in range(1,tabpagewinnr(tab,'$'))
745         if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
746           execute 'tabnext '.tab
747           if winnr != winnr()
748             execute winnr.'wincmd w'
749             let restorewinnr = 1
750           endif
751           try
752             if !&modified
753               call s:BufReadIndex()
754             endif
755           finally
756             if exists('restorewinnr')
757               wincmd p
758             endif
759             execute 'tabnext '.mytab
760           endtry
761         endif
762       endfor
763     endfor
764   finally
765     unlet! s:reloading_status
766   endtry
767 endfunction
769 function! s:stage_info(lnum) abort
770   let filename = matchstr(getline(a:lnum),'^#\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
771   let lnum = a:lnum
772   if has('multi_byte_encoding')
773     let colon = '\%(:\|\%uff1a\)'
774   else
775     let colon = ':'
776   endif
777   while lnum && getline(lnum) !~# colon.'$'
778     let lnum -= 1
779   endwhile
780   if !lnum
781     return ['', '']
782   elseif (getline(lnum+1) =~# '^# .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) ==# '# Changes to be committed:'
783     return [matchstr(filename, colon.' *\zs.*'), 'staged']
784   elseif (getline(lnum+1) =~# '^# .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.'  ') || getline(lnum) ==# '# Untracked files:'
785     return [filename, 'untracked']
786   elseif getline(lnum+2) =~# '^# .*\<git checkout ' || getline(lnum) ==# '# Changes not staged for commit:'
787     return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
788   elseif getline(lnum+2) =~# '^# .*\<git \%(add\|rm\)' || getline(lnum) ==# '# Unmerged paths:'
789     return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
790   else
791     return ['', 'unknown']
792   endif
793 endfunction
795 function! s:StageNext(count) abort
796   for i in range(a:count)
797     call search('^#\t.*','W')
798   endfor
799   return '.'
800 endfunction
802 function! s:StagePrevious(count) abort
803   if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
804     return 'CtrlP '.fnameescape(s:repo().tree())
805   else
806     for i in range(a:count)
807       call search('^#\t.*','Wbe')
808     endfor
809     return '.'
810   endif
811 endfunction
813 function! s:StageReloadSeek(target,lnum1,lnum2) abort
814   let jump = a:target
815   let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
816   if f !=# '' | let jump = f | endif
817   let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
818   if f !=# '' | let jump = f | endif
819   silent! edit!
820   1
821   redraw
822   call search('^#\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
823 endfunction
825 function! s:StageUndo() abort
826   let [filename, section] = s:stage_info(line('.'))
827   if empty(filename)
828     return ''
829   endif
830   let repo = s:repo()
831   let hash = repo.git_chomp('hash-object', '-w', filename)
832   if !empty(hash)
833     if section ==# 'untracked'
834       call delete(s:repo().tree(filename))
835     elseif section ==# 'unstaged'
836       call repo.git_chomp_in_tree('checkout', '--', filename)
837     else
838       call repo.git_chomp_in_tree('checkout', 'HEAD', '--', filename)
839     endif
840     call s:StageReloadSeek(filename, line('.'), line('.'))
841     let @" = hash
842     return 'checktime|redraw|echomsg ' .
843           \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
844   endif
845 endfunction
847 function! s:StageDiff(diff) abort
848   let [filename, section] = s:stage_info(line('.'))
849   if filename ==# '' && section ==# 'staged'
850     return 'Git! diff --no-ext-diff --cached'
851   elseif filename ==# ''
852     return 'Git! diff --no-ext-diff'
853   elseif filename =~# ' -> '
854     let [old, new] = split(filename,' -> ')
855     execute 'Gedit '.s:fnameescape(':0:'.new)
856     return a:diff.' HEAD:'.s:fnameescape(old)
857   elseif section ==# 'staged'
858     execute 'Gedit '.s:fnameescape(':0:'.filename)
859     return a:diff.' -'
860   else
861     execute 'Gedit '.s:fnameescape('/'.filename)
862     return a:diff
863   endif
864 endfunction
866 function! s:StageDiffEdit() abort
867   let [filename, section] = s:stage_info(line('.'))
868   let arg = (filename ==# '' ? '.' : filename)
869   if section ==# 'staged'
870     return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
871   elseif section ==# 'untracked'
872     let repo = s:repo()
873     call repo.git_chomp_in_tree('add','--intent-to-add',arg)
874     if arg ==# '.'
875       silent! edit!
876       1
877       if !search('^# .*:\n#.*\n# .*"git checkout \|^# Changes not staged for commit:$','W')
878         call search('^# .*:$','W')
879       endif
880     else
881       call s:StageReloadSeek(arg,line('.'),line('.'))
882     endif
883     return ''
884   else
885     return 'Git! diff --no-ext-diff '.s:shellesc(arg)
886   endif
887 endfunction
889 function! s:StageToggle(lnum1,lnum2) abort
890   if a:lnum1 == 1 && a:lnum2 == 1
891     return 'Gedit /.git|call search("^index$", "wc")'
892   endif
893   try
894     let output = ''
895     for lnum in range(a:lnum1,a:lnum2)
896       let [filename, section] = s:stage_info(lnum)
897       let repo = s:repo()
898       if getline('.') =~# '^# .*:$'
899         if section ==# 'staged'
900           call repo.git_chomp_in_tree('reset','-q')
901           silent! edit!
902           1
903           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
904             call search('^# .*:$','W')
905           endif
906           return ''
907         elseif section ==# 'unstaged'
908           call repo.git_chomp_in_tree('add','-u')
909           silent! edit!
910           1
911           if !search('^# .*:\n# .*"git add .*\n#\n\|^# Untracked files:$','W')
912             call search('^# .*:$','W')
913           endif
914           return ''
915         else
916           call repo.git_chomp_in_tree('add','.')
917           silent! edit!
918           1
919           call search('^# .*:$','W')
920           return ''
921         endif
922       endif
923       if filename ==# ''
924         continue
925       endif
926       execute lnum
927       if filename =~ ' -> '
928         let cmd = ['mv','--'] + reverse(split(filename,' -> '))
929         let filename = cmd[-1]
930       elseif section ==# 'staged'
931         let cmd = ['reset','-q','--',filename]
932       elseif getline(lnum) =~# '^#\tdeleted:'
933         let cmd = ['rm','--',filename]
934       elseif getline(lnum) =~# '^#\tmodified:'
935         let cmd = ['add','--',filename]
936       else
937         let cmd = ['add','-A','--',filename]
938       endif
939       if !exists('first_filename')
940         let first_filename = filename
941       endif
942       let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
943     endfor
944     if exists('first_filename')
945       call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
946     endif
947     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
948   catch /^fugitive:/
949     return 'echoerr v:errmsg'
950   endtry
951   return 'checktime'
952 endfunction
954 function! s:StagePatch(lnum1,lnum2) abort
955   let add = []
956   let reset = []
958   for lnum in range(a:lnum1,a:lnum2)
959     let [filename, section] = s:stage_info(lnum)
960     if getline('.') =~# '^# .*:$' && section ==# 'staged'
961       return 'Git reset --patch'
962     elseif getline('.') =~# '^# .*:$' && section ==# 'unstaged'
963       return 'Git add --patch'
964     elseif getline('.') =~# '^# .*:$' && section ==# 'untracked'
965       return 'Git add -N .'
966     elseif filename ==# ''
967       continue
968     endif
969     if !exists('first_filename')
970       let first_filename = filename
971     endif
972     execute lnum
973     if filename =~ ' -> '
974       let reset += [split(filename,' -> ')[1]]
975     elseif section ==# 'staged'
976       let reset += [filename]
977     elseif getline(lnum) !~# '^#\tdeleted:'
978       let add += [filename]
979     endif
980   endfor
981   try
982     if !empty(add)
983       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
984     endif
985     if !empty(reset)
986       execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
987     endif
988     if exists('first_filename')
989       silent! edit!
990       1
991       redraw
992       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
993     endif
994   catch /^fugitive:/
995     return 'echoerr v:errmsg'
996   endtry
997   return 'checktime'
998 endfunction
1000 " Section: Gcommit
1002 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
1004 function! s:Commit(args, ...) abort
1005   let repo = a:0 ? a:1 : s:repo()
1006   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1007   let dir = getcwd()
1008   let msgfile = repo.dir('COMMIT_EDITMSG')
1009   let outfile = tempname()
1010   let errorfile = tempname()
1011   try
1012     try
1013       execute cd.s:fnameescape(repo.tree())
1014       if s:winshell()
1015         let command = ''
1016         let old_editor = $GIT_EDITOR
1017         let $GIT_EDITOR = 'false'
1018       else
1019         let command = 'env GIT_EDITOR=false '
1020       endif
1021       let command .= repo.git_command('commit').' '.a:args
1022       if &shell =~# 'csh'
1023         noautocmd silent execute '!('.command.' > '.outfile.') >& '.errorfile
1024       elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1025         noautocmd execute '!'.command.' 2> '.errorfile
1026       else
1027         noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1028       endif
1029     finally
1030       execute cd.'`=dir`'
1031     endtry
1032     if !has('gui_running')
1033       redraw!
1034     endif
1035     if !v:shell_error
1036       if filereadable(outfile)
1037         for line in readfile(outfile)
1038           echo line
1039         endfor
1040       endif
1041       return ''
1042     else
1043       let errors = readfile(errorfile)
1044       let error = get(errors,-2,get(errors,-1,'!'))
1045       if error =~# 'false''\=\.$'
1046         let args = a:args
1047         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1048         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1049         let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
1050         let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
1051         let args = '-F '.s:shellesc(msgfile).' '.args
1052         if args !~# '\%(^\| \)--cleanup\>'
1053           let args = '--cleanup=strip '.args
1054         endif
1055         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
1056           execute 'keepalt edit '.s:fnameescape(msgfile)
1057         elseif a:args =~# '\%(^\| \)-\%(-verbose\|\w*v\)\>'
1058           execute 'keepalt '.(tabpagenr()-1).'tabedit '.s:fnameescape(msgfile)
1059         elseif s:buffer().type() ==# 'index'
1060           execute 'keepalt edit '.s:fnameescape(msgfile)
1061           execute (search('^#','n')+1).'wincmd+'
1062           setlocal nopreviewwindow
1063         else
1064           execute 'keepalt split '.s:fnameescape(msgfile)
1065         endif
1066         let b:fugitive_commit_arguments = args
1067         setlocal bufhidden=wipe filetype=gitcommit
1068         return '1'
1069       elseif error ==# '!'
1070         return s:Status()
1071       else
1072         call s:throw(error)
1073       endif
1074     endif
1075   catch /^fugitive:/
1076     return 'echoerr v:errmsg'
1077   finally
1078     if exists('old_editor')
1079       let $GIT_EDITOR = old_editor
1080     endif
1081     call delete(outfile)
1082     call delete(errorfile)
1083     call fugitive#reload_status()
1084   endtry
1085 endfunction
1087 function! s:CommitComplete(A,L,P) abort
1088   if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
1089     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=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--template=', '--untracked-files', '--verbose']
1090     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
1091   else
1092     return s:repo().superglob(a:A)
1093   endif
1094 endfunction
1096 function! s:FinishCommit() abort
1097   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
1098   if !empty(args)
1099     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
1100     return s:Commit(args, s:repo(getbufvar(+expand('<abuf>'),'git_dir')))
1101   endif
1102   return ''
1103 endfunction
1105 " Section: Gmerge, Gpull
1107 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
1108       \ "execute s:Merge('merge', <bang>0, <q-args>)")
1109 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
1110       \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
1112 function! s:RevisionComplete(A, L, P) abort
1113   return s:repo().git_chomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
1114         \ . "\nHEAD\nFETCH_HEAD\nORIG_HEAD"
1115 endfunction
1117 function! s:RemoteComplete(A, L, P) abort
1118   let remote = matchstr(a:L, ' \zs\S\+\ze ')
1119   if !empty(remote)
1120     let matches = split(s:repo().git_chomp('ls-remote', remote), "\n")
1121     call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
1122     call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
1123   else
1124     let matches = split(s:repo().git_chomp('remote'), "\n")
1125   endif
1126   return join(matches, "\n")
1127 endfunction
1129 function! fugitive#cwindow() abort
1130   if &buftype == 'quickfix'
1131     cwindow
1132   else
1133     botright cwindow
1134     if &buftype == 'quickfix'
1135       wincmd p
1136     endif
1137   endif
1138 endfunction
1140 let s:common_efm = ''
1141       \ . '%+Egit:%.%#,'
1142       \ . '%+Eusage:%.%#,'
1143       \ . '%+Eerror:%.%#,'
1144       \ . '%+Efatal:%.%#,'
1145       \ . '%-G%.%#%\e[K%.%#,'
1146       \ . '%-G%.%#%\r%.%\+'
1148 function! s:Merge(cmd, bang, args) abort
1149   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1150   let cwd = getcwd()
1151   let [mp, efm] = [&l:mp, &l:efm]
1152   let had_merge_msg = filereadable(s:repo().dir('MERGE_MSG'))
1153   try
1154     let &l:errorformat = ''
1155           \ . '%-Gerror:%.%#false''.,'
1156           \ . '%-G%.%# ''git commit'' %.%#,'
1157           \ . '%+Emerge:%.%#,'
1158           \ . s:common_efm . ','
1159           \ . '%+ECannot %.%#: You have unstaged changes.,'
1160           \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
1161           \ . '%+EThere is no tracking information for the current branch.,'
1162           \ . '%+EYou are not currently on a branch. Please specify which,'
1163           \ . 'CONFLICT (%m): %f deleted in %.%#,'
1164           \ . 'CONFLICT (%m): Merge conflict in %f,'
1165           \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
1166           \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
1167           \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
1168           \ . '%+ECONFLICT %.%#,'
1169           \ . '%+EKONFLIKT %.%#,'
1170           \ . '%+ECONFLIT %.%#,'
1171           \ . "%+EXUNG \u0110\u1ed8T %.%#,"
1172           \ . "%+E\u51b2\u7a81 %.%#,"
1173           \ . 'U%\t%f'
1174     if a:cmd =~# '^merge' && empty(a:args) &&
1175           \ (had_merge_msg || isdirectory(s:repo().dir('rebase-apply')) ||
1176           \  !empty(s:repo().git_chomp('diff-files', '--diff-filter=U')))
1177       let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
1178     else
1179       let &l:makeprg = s:sub(g:fugitive_git_executable . ' ' . a:cmd .
1180             \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' ? '' : ' --edit') .
1181             \ ' ' . a:args, ' *$', '')
1182     endif
1183     if !empty($GIT_EDITOR) || has('win32')
1184       let old_editor = $GIT_EDITOR
1185       let $GIT_EDITOR = 'false'
1186     else
1187       let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
1188     endif
1189     execute cd fnameescape(s:repo().tree())
1190     silent noautocmd make!
1191   catch /^Vim\%((\a\+)\)\=:E211/
1192     let err = v:exception
1193   finally
1194     redraw!
1195     let [&l:mp, &l:efm] = [mp, efm]
1196     if exists('old_editor')
1197       let $GIT_EDITOR = old_editor
1198     endif
1199     execute cd fnameescape(cwd)
1200   endtry
1201   call fugitive#reload_status()
1202   if empty(filter(getqflist(),'v:val.valid'))
1203     if !had_merge_msg && filereadable(s:repo().dir('MERGE_MSG'))
1204       cclose
1205       return 'Gcommit --no-status -n -t '.s:shellesc(s:repo().dir('MERGE_MSG'))
1206     endif
1207   endif
1208   let qflist = getqflist()
1209   let found = 0
1210   for e in qflist
1211     if !empty(e.bufnr)
1212       let found = 1
1213       let e.pattern = '^<<<<<<<'
1214     endif
1215   endfor
1216   call fugitive#cwindow()
1217   if found
1218     call setqflist(qflist, 'r')
1219     if !a:bang
1220       return 'cfirst'
1221     endif
1222   endif
1223   return exists('err') ? 'echoerr '.string(err) : ''
1224 endfunction
1226 " Section: Ggrep, Glog
1228 if !exists('g:fugitive_summary_format')
1229   let g:fugitive_summary_format = '%s'
1230 endif
1232 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
1233 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
1234 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Glog :call s:Log('grep<bang>',<line1>,<count>,<f-args>)")
1235 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditComplete Gllog :call s:Log('lgrep<bang>',<line1>,<count>,<f-args>)")
1237 function! s:Grep(cmd,bang,arg) abort
1238   let grepprg = &grepprg
1239   let grepformat = &grepformat
1240   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1241   let dir = getcwd()
1242   try
1243     execute cd.'`=s:repo().tree()`'
1244     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n', '--no-color')
1245     let &grepformat = '%f:%l:%m'
1246     exe a:cmd.'! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
1247     let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
1248     for entry in list
1249       if bufname(entry.bufnr) =~ ':'
1250         let entry.filename = s:repo().translate(bufname(entry.bufnr))
1251         unlet! entry.bufnr
1252         let changed = 1
1253       elseif a:arg =~# '\%(^\| \)--cached\>'
1254         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
1255         unlet! entry.bufnr
1256         let changed = 1
1257       endif
1258     endfor
1259     if a:cmd =~# '^l' && exists('changed')
1260       call setloclist(0, list, 'r')
1261     elseif exists('changed')
1262       call setqflist(list, 'r')
1263     endif
1264     if !a:bang && !empty(list)
1265       return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
1266     else
1267       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
1268     endif
1269   finally
1270     let &grepprg = grepprg
1271     let &grepformat = grepformat
1272     execute cd.'`=dir`'
1273   endtry
1274 endfunction
1276 function! s:Log(cmd, line1, line2, ...) abort
1277   let path = s:buffer().path('/')
1278   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
1279     let path = ''
1280   endif
1281   let cmd = ['--no-pager', 'log', '--no-color']
1282   let cmd += ['--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format]
1283   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
1284     if s:buffer().commit() =~# '\x\{40\}'
1285       let cmd += [s:buffer().commit()]
1286     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
1287       let cmd += [s:buffer().path()[5:-1]]
1288     endif
1289   end
1290   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
1291   if path =~# '/.'
1292     if a:line2
1293       let cmd += ['-L', a:line1 . ',' . a:line2 . ':' . path[1:-1]]
1294     else
1295       let cmd += ['--', path[1:-1]]
1296     endif
1297   endif
1298   let grepformat = &grepformat
1299   let grepprg = &grepprg
1300   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1301   let dir = getcwd()
1302   try
1303     execute cd.'`=s:repo().tree()`'
1304     let &grepprg = escape(call(s:repo().git_command,cmd,s:repo()),'%#')
1305     let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
1306     exe a:cmd
1307   finally
1308     let &grepformat = grepformat
1309     let &grepprg = grepprg
1310     execute cd.'`=dir`'
1311   endtry
1312 endfunction
1314 " Section: Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread
1316 function! s:Edit(cmd,bang,...) abort
1317   let buffer = s:buffer()
1318   if a:cmd !~# 'read'
1319     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
1320       wincmd p
1321       if &diff
1322         let mywinnr = winnr()
1323         for winnr in range(winnr('$'),1,-1)
1324           if winnr != mywinnr && getwinvar(winnr,'&diff')
1325             execute winnr.'wincmd w'
1326             close
1327             if winnr('$') > 1
1328               wincmd p
1329             endif
1330           endif
1331         endfor
1332       endif
1333     endif
1334   endif
1336   if a:bang
1337     let arglist = map(copy(a:000), 's:gsub(v:val, ''\\@<!%(\\\\)*\zs[%#]'', ''\=s:buffer().expand(submatch(0))'')')
1338     let args = join(arglist, ' ')
1339     if a:cmd =~# 'read'
1340       let git = buffer.repo().git_command()
1341       let last = line('$')
1342       silent call s:ExecuteInTree((a:cmd ==# 'read' ? '$read' : a:cmd).'!'.git.' --no-pager '.args)
1343       if a:cmd ==# 'read'
1344         silent execute '1,'.last.'delete_'
1345       endif
1346       call fugitive#reload_status()
1347       diffupdate
1348       return 'redraw|echo '.string(':!'.git.' '.args)
1349     else
1350       let temp = resolve(tempname())
1351       let s:temp_files[tolower(temp)] = { 'dir': buffer.repo().dir(), 'args': arglist }
1352       silent execute a:cmd.' '.temp
1353       if a:cmd =~# 'pedit'
1354         wincmd P
1355       endif
1356       let echo = s:Edit('read',1,args)
1357       silent write!
1358       setlocal buftype=nowrite nomodified filetype=git foldmarker=<<<<<<<,>>>>>>>
1359       if getline(1) !~# '^diff '
1360         setlocal readonly nomodifiable
1361       endif
1362       if a:cmd =~# 'pedit'
1363         wincmd p
1364       endif
1365       return echo
1366     endif
1367     return ''
1368   endif
1370   if a:0 && a:1 == ''
1371     return ''
1372   elseif a:0
1373     let file = buffer.expand(join(a:000, ' '))
1374   elseif expand('%') ==# ''
1375     let file = ':'
1376   elseif buffer.commit() ==# '' && buffer.path('/') !~# '^/.git\>'
1377     let file = buffer.path(':')
1378   else
1379     let file = buffer.path('/')
1380   endif
1381   try
1382     let file = buffer.repo().translate(file)
1383   catch /^fugitive:/
1384     return 'echoerr v:errmsg'
1385   endtry
1386   if a:cmd ==# 'read'
1387     return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
1388   else
1389     return a:cmd.' '.s:fnameescape(file)
1390   endif
1391 endfunction
1393 function! s:EditComplete(A,L,P) abort
1394   return map(s:repo().superglob(a:A), 'fnameescape(v:val)')
1395 endfunction
1397 function! s:EditRunComplete(A,L,P) abort
1398   if a:L =~# '^\w\+!'
1399     return s:GitComplete(a:A,a:L,a:P)
1400   else
1401     return s:repo().superglob(a:A)
1402   endif
1403 endfunction
1405 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',0,<f-args>)")
1406 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',0,<f-args>)")
1407 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit   :execute s:Edit('pedit',<bang>0,<f-args>)")
1408 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gsplit   :execute s:Edit('split',<bang>0,<f-args>)")
1409 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gvsplit  :execute s:Edit('vsplit',<bang>0,<f-args>)")
1410 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gtabedit :execute s:Edit('tabedit',<bang>0,<f-args>)")
1411 call s:command("-bar -bang -nargs=* -count -complete=customlist,s:EditRunComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read',<bang>0,<f-args>)")
1413 " Section: Gwrite, Gwq
1415 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
1416 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
1417 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
1419 function! s:Write(force,...) abort
1420   if exists('b:fugitive_commit_arguments')
1421     return 'write|bdelete'
1422   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
1423     return 'wq'
1424   elseif s:buffer().type() == 'index'
1425     return 'Gcommit'
1426   elseif s:buffer().path() ==# '' && getline(4) =~# '^+++ '
1427     let filename = getline(4)[6:-1]
1428     setlocal buftype=
1429     silent write
1430     setlocal buftype=nowrite
1431     if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# s:repo().rev_parse(':0:'.filename)[0:6]
1432       let err = s:repo().git_chomp('apply','--cached','--reverse',s:buffer().spec())
1433     else
1434       let err = s:repo().git_chomp('apply','--cached',s:buffer().spec())
1435     endif
1436     if err !=# ''
1437       let v:errmsg = split(err,"\n")[0]
1438       return 'echoerr v:errmsg'
1439     elseif a:force
1440       return 'bdelete'
1441     else
1442       return 'Gedit '.fnameescape(filename)
1443     endif
1444   endif
1445   let mytab = tabpagenr()
1446   let mybufnr = bufnr('')
1447   let path = a:0 ? join(a:000, ' ') : s:buffer().path()
1448   if path =~# '^:\d\>'
1449     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
1450   endif
1451   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
1452   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) !=# ''
1453     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
1454     return 'echoerr v:errmsg'
1455   endif
1456   let file = s:repo().translate(path)
1457   let treebufnr = 0
1458   for nr in range(1,bufnr('$'))
1459     if fnamemodify(bufname(nr),':p') ==# file
1460       let treebufnr = nr
1461     endif
1462   endfor
1464   if treebufnr > 0 && treebufnr != bufnr('')
1465     let temp = tempname()
1466     silent execute '%write '.temp
1467     for tab in [mytab] + range(1,tabpagenr('$'))
1468       for winnr in range(1,tabpagewinnr(tab,'$'))
1469         if tabpagebuflist(tab)[winnr-1] == treebufnr
1470           execute 'tabnext '.tab
1471           if winnr != winnr()
1472             execute winnr.'wincmd w'
1473             let restorewinnr = 1
1474           endif
1475           try
1476             let lnum = line('.')
1477             let last = line('$')
1478             silent execute '$read '.temp
1479             silent execute '1,'.last.'delete_'
1480             silent write!
1481             silent execute lnum
1482             let did = 1
1483           finally
1484             if exists('restorewinnr')
1485               wincmd p
1486             endif
1487             execute 'tabnext '.mytab
1488           endtry
1489         endif
1490       endfor
1491     endfor
1492     if !exists('did')
1493       call writefile(readfile(temp,'b'),file,'b')
1494     endif
1495   else
1496     execute 'write! '.s:fnameescape(s:repo().translate(path))
1497   endif
1499   if a:force
1500     let error = s:repo().git_chomp_in_tree('add', '--force', '--', path)
1501   else
1502     let error = s:repo().git_chomp_in_tree('add', '--', path)
1503   endif
1504   if v:shell_error
1505     let v:errmsg = 'fugitive: '.error
1506     return 'echoerr v:errmsg'
1507   endif
1508   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1509     set nomodified
1510   endif
1512   let one = s:repo().translate(':1:'.path)
1513   let two = s:repo().translate(':2:'.path)
1514   let three = s:repo().translate(':3:'.path)
1515   for nr in range(1,bufnr('$'))
1516     let name = fnamemodify(bufname(nr), ':p')
1517     if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
1518       execute nr.'bdelete'
1519     endif
1520   endfor
1522   unlet! restorewinnr
1523   let zero = s:repo().translate(':0:'.path)
1524   for tab in range(1,tabpagenr('$'))
1525     for winnr in range(1,tabpagewinnr(tab,'$'))
1526       let bufnr = tabpagebuflist(tab)[winnr-1]
1527       let bufname = fnamemodify(bufname(bufnr), ':p')
1528       if bufname ==# zero && bufnr != mybufnr
1529         execute 'tabnext '.tab
1530         if winnr != winnr()
1531           execute winnr.'wincmd w'
1532           let restorewinnr = 1
1533         endif
1534         try
1535           let lnum = line('.')
1536           let last = line('$')
1537           silent execute '$read '.s:fnameescape(file)
1538           silent execute '1,'.last.'delete_'
1539           silent execute lnum
1540           set nomodified
1541           diffupdate
1542         finally
1543           if exists('restorewinnr')
1544             wincmd p
1545           endif
1546           execute 'tabnext '.mytab
1547         endtry
1548         break
1549       endif
1550     endfor
1551   endfor
1552   call fugitive#reload_status()
1553   return 'checktime'
1554 endfunction
1556 function! s:Wq(force,...) abort
1557   let bang = a:force ? '!' : ''
1558   if exists('b:fugitive_commit_arguments')
1559     return 'wq'.bang
1560   endif
1561   let result = call(s:function('s:Write'),[a:force]+a:000)
1562   if result =~# '^\%(write\|wq\|echoerr\)'
1563     return s:sub(result,'^write','wq')
1564   else
1565     return result.'|quit'.bang
1566   endif
1567 endfunction
1569 augroup fugitive_commit
1570   autocmd!
1571   autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
1572 augroup END
1574 " Section: Gpush, Gfetch
1576 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush  execute s:Dispatch('<bang>', 'push '.<q-args>)")
1577 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
1579 function! s:Dispatch(bang, args)
1580   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1581   let cwd = getcwd()
1582   let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
1583   try
1584     let b:current_compiler = 'git'
1585     let &l:errorformat = s:common_efm
1586     let &l:makeprg = g:fugitive_git_executable . ' ' . a:args
1587     execute cd fnameescape(s:repo().tree())
1588     if exists(':Make') == 2
1589       noautocmd Make
1590     else
1591       silent noautocmd make!
1592       redraw!
1593       return 'call fugitive#cwindow()'
1594     endif
1595     return ''
1596   finally
1597     let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
1598     if empty(cc) | unlet! b:current_compiler | endif
1599     execute cd fnameescape(cwd)
1600   endtry
1601 endfunction
1603 " Section: Gdiff
1605 call s:command("-bang -bar -nargs=* -complete=customlist,s:EditComplete Gdiff :execute s:Diff('',<f-args>)")
1606 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gvdiff :execute s:Diff('keepalt vert ',<f-args>)")
1607 call s:command("-bar -nargs=* -complete=customlist,s:EditComplete Gsdiff :execute s:Diff('keepalt ',<f-args>)")
1609 augroup fugitive_diff
1610   autocmd!
1611   autocmd BufWinLeave *
1612         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
1613         \   call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
1614         \ endif
1615   autocmd BufWinEnter *
1616         \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
1617         \   call s:diffoff() |
1618         \ endif
1619 augroup END
1621 function! s:can_diffoff(buf) abort
1622   return getwinvar(bufwinnr(a:buf), '&diff') &&
1623         \ !empty(getbufvar(a:buf, 'git_dir')) &&
1624         \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
1625 endfunction
1627 function! fugitive#can_diffoff(buf) abort
1628   return s:can_diffoff(a:buf)
1629 endfunction
1631 function! s:diff_modifier(count) abort
1632   let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
1633   if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
1634     return 'keepalt '
1635   elseif &diffopt =~# 'vertical'
1636     return 'keepalt vert '
1637   elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
1638     return 'keepalt '
1639   else
1640     return 'keepalt vert '
1641   endif
1642 endfunction
1644 function! s:diff_window_count() abort
1645   let c = 0
1646   for nr in range(1,winnr('$'))
1647     let c += getwinvar(nr,'&diff')
1648   endfor
1649   return c
1650 endfunction
1652 function! s:diff_restore() abort
1653   let restore = 'setlocal nodiff noscrollbind'
1654         \ . ' scrollopt=' . &l:scrollopt
1655         \ . (&l:wrap ? ' wrap' : ' nowrap')
1656         \ . ' foldlevel=999'
1657         \ . ' foldmethod=' . &l:foldmethod
1658         \ . ' foldcolumn=' . &l:foldcolumn
1659         \ . ' foldlevel=' . &l:foldlevel
1660         \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
1661   if has('cursorbind')
1662     let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
1663   endif
1664   return restore
1665 endfunction
1667 function! s:diffthis() abort
1668   if !&diff
1669     let w:fugitive_diff_restore = s:diff_restore()
1670     diffthis
1671   endif
1672 endfunction
1674 function! s:diffoff() abort
1675   if exists('w:fugitive_diff_restore')
1676     execute w:fugitive_diff_restore
1677     unlet w:fugitive_diff_restore
1678   else
1679     diffoff
1680   endif
1681 endfunction
1683 function! s:diffoff_all(dir) abort
1684   let curwin = winnr()
1685   for nr in range(1,winnr('$'))
1686     if getwinvar(nr,'&diff')
1687       if nr != winnr()
1688         execute nr.'wincmd w'
1689         let restorewinnr = 1
1690       endif
1691       if exists('b:git_dir') && b:git_dir ==# a:dir
1692         call s:diffoff()
1693       endif
1694     endif
1695   endfor
1696   execute curwin.'wincmd w'
1697 endfunction
1699 function! s:buffer_compare_age(commit) dict abort
1700   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1701   let my_score    = get(scores,':'.self.commit(),0)
1702   let their_score = get(scores,':'.a:commit,0)
1703   if my_score || their_score
1704     return my_score < their_score ? -1 : my_score != their_score
1705   elseif self.commit() ==# a:commit
1706     return 0
1707   endif
1708   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1709   if base ==# self.commit()
1710     return -1
1711   elseif base ==# a:commit
1712     return 1
1713   endif
1714   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1715   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1716   return my_time < their_time ? -1 : my_time != their_time
1717 endfunction
1719 call s:add_methods('buffer',['compare_age'])
1721 function! s:Diff(vert,...) abort
1722   let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
1723   if exists(':DiffGitCached')
1724     return 'DiffGitCached'
1725   elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1726     let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
1727     let nr = bufnr('')
1728     execute 'leftabove '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1729     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1730     call s:diffthis()
1731     wincmd p
1732     execute 'rightbelow '.vert.'split `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1733     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1734     call s:diffthis()
1735     wincmd p
1736     call s:diffthis()
1737     return ''
1738   elseif a:0
1739     let arg = join(a:000, ' ')
1740     if arg ==# ''
1741       return ''
1742     elseif arg ==# '/'
1743       let file = s:buffer().path('/')
1744     elseif arg ==# ':'
1745       let file = s:buffer().path(':0:')
1746     elseif arg =~# '^:/.'
1747       try
1748         let file = s:repo().rev_parse(arg).s:buffer().path(':')
1749       catch /^fugitive:/
1750         return 'echoerr v:errmsg'
1751       endtry
1752     else
1753       let file = s:buffer().expand(arg)
1754     endif
1755     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1756       let file = file.s:buffer().path(':')
1757     endif
1758   else
1759     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1760   endif
1761   try
1762     let spec = s:repo().translate(file)
1763     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1764     let restore = s:diff_restore()
1765     if exists('+cursorbind')
1766       setlocal cursorbind
1767     endif
1768     let w:fugitive_diff_restore = restore
1769     if s:buffer().compare_age(commit) < 0
1770       execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
1771     else
1772       execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
1773     endif
1774     let w:fugitive_diff_restore = restore
1775     let winnr = winnr()
1776     if getwinvar('#', '&diff')
1777       wincmd p
1778       call feedkeys(winnr."\<C-W>w", 'n')
1779     endif
1780     return ''
1781   catch /^fugitive:/
1782     return 'echoerr v:errmsg'
1783   endtry
1784 endfunction
1786 " Section: Gmove, Gremove
1788 function! s:Move(force,destination) abort
1789   if a:destination =~# '^/'
1790     let destination = a:destination[1:-1]
1791   else
1792     let destination = s:shellslash(fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p'))
1793     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1794       let destination = destination[strlen(s:repo().tree('')):-1]
1795     endif
1796   endif
1797   if isdirectory(s:buffer().spec())
1798     " Work around Vim parser idiosyncrasy
1799     let discarded = s:buffer().setvar('&swapfile',0)
1800   endif
1801   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1802   if v:shell_error
1803     let v:errmsg = 'fugitive: '.message
1804     return 'echoerr v:errmsg'
1805   endif
1806   let destination = s:repo().tree(destination)
1807   if isdirectory(destination)
1808     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1809   endif
1810   call fugitive#reload_status()
1811   if s:buffer().commit() == ''
1812     if isdirectory(destination)
1813       return 'keepalt edit '.s:fnameescape(destination)
1814     else
1815       return 'keepalt saveas! '.s:fnameescape(destination)
1816     endif
1817   else
1818     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination))
1819   endif
1820 endfunction
1822 function! s:MoveComplete(A,L,P) abort
1823   if a:A =~ '^/'
1824     return s:repo().superglob(a:A)
1825   else
1826     let matches = split(glob(a:A.'*'),"\n")
1827     call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1828     return matches
1829   endif
1830 endfunction
1832 function! s:Remove(force) abort
1833   if s:buffer().commit() ==# ''
1834     let cmd = ['rm']
1835   elseif s:buffer().commit() ==# '0'
1836     let cmd = ['rm','--cached']
1837   else
1838     let v:errmsg = 'fugitive: rm not supported here'
1839     return 'echoerr v:errmsg'
1840   endif
1841   if a:force
1842     let cmd += ['--force']
1843   endif
1844   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1845   if v:shell_error
1846     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1847     return 'echoerr '.string(v:errmsg)
1848   else
1849     call fugitive#reload_status()
1850     return 'edit'.(a:force ? '!' : '')
1851   endif
1852 endfunction
1854 augroup fugitive_remove
1855   autocmd!
1856   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1857         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1858         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1859         \ endif
1860 augroup END
1862 " Section: Gblame
1864 augroup fugitive_blame
1865   autocmd!
1866   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1867   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1868   autocmd Syntax fugitiveblame call s:BlameSyntax()
1869   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
1870   autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
1871 augroup END
1873 function! s:linechars(pattern) abort
1874   let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
1875   if exists('*synconcealed') && &conceallevel > 1
1876     for col in range(1, chars)
1877       let chars -= synconcealed(line('.'), col)[0]
1878     endfor
1879   endif
1880   return chars
1881 endfunction
1883 function! s:Blame(bang,line1,line2,count,args) abort
1884   if exists('b:fugitive_blamed_bufnr')
1885     return 'bdelete'
1886   endif
1887   try
1888     if s:buffer().path() == ''
1889       call s:throw('file or blob required')
1890     endif
1891     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
1892       call s:throw('unsupported option')
1893     endif
1894     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1895     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1896     if s:buffer().commit() =~# '\D\|..'
1897       let cmd += [s:buffer().commit()]
1898     else
1899       let cmd += ['--contents', '-']
1900     endif
1901     let cmd += ['--', s:buffer().path()]
1902     let basecmd = escape(call(s:repo().git_command,cmd,s:repo()),'!')
1903     try
1904       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1905       if !s:repo().bare()
1906         let dir = getcwd()
1907         execute cd.'`=s:repo().tree()`'
1908       endif
1909       if a:count
1910         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1911       else
1912         let error = resolve(tempname())
1913         let temp = error.'.fugitiveblame'
1914         if &shell =~# 'csh'
1915           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1916         else
1917           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1918         endif
1919         if exists('l:dir')
1920           execute cd.'`=dir`'
1921           unlet dir
1922         endif
1923         if v:shell_error
1924           call s:throw(join(readfile(error),"\n"))
1925         endif
1926         for winnr in range(winnr('$'),1,-1)
1927           call setwinvar(winnr, '&scrollbind', 0)
1928           if exists('+cursorbind')
1929             call setwinvar(winnr, '&cursorbind', 0)
1930           endif
1931           if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
1932             execute winbufnr(winnr).'bdelete'
1933           endif
1934         endfor
1935         let bufnr = bufnr('')
1936         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1937         if exists('+cursorbind')
1938           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
1939         endif
1940         if &l:wrap
1941           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1942         endif
1943         if &l:foldenable
1944           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1945         endif
1946         setlocal scrollbind nowrap nofoldenable
1947         if exists('+cursorbind')
1948           setlocal cursorbind
1949         endif
1950         let top = line('w0') + &scrolloff
1951         let current = line('.')
1952         let s:temp_files[tolower(temp)] = { 'dir': s:repo().dir(), 'args': cmd }
1953         exe 'keepalt leftabove vsplit '.temp
1954         let b:fugitive_blamed_bufnr = bufnr
1955         let w:fugitive_leave = restore
1956         let b:fugitive_blame_arguments = join(a:args,' ')
1957         execute top
1958         normal! zt
1959         execute current
1960         if exists('+cursorbind')
1961           setlocal cursorbind
1962         endif
1963         setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame
1964         if exists('+concealcursor')
1965           setlocal concealcursor=nc conceallevel=2
1966         endif
1967         if exists('+relativenumber')
1968           setlocal norelativenumber
1969         endif
1970         execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
1971         nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
1972         nnoremap <buffer> <silent> g?   :help fugitive-:Gblame<CR>
1973         nnoremap <buffer> <silent> q    :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
1974         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>
1975         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1976         nnoremap <buffer> <silent> -    :<C-U>exe <SID>BlameJump('')<CR>
1977         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1978         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1979         nnoremap <buffer> <silent> i    :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
1980         nnoremap <buffer> <silent> o    :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
1981         nnoremap <buffer> <silent> O    :<C-U>exe <SID>BlameCommit("tabedit")<CR>
1982         nnoremap <buffer> <silent> A    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
1983         nnoremap <buffer> <silent> C    :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
1984         nnoremap <buffer> <silent> D    :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
1985         redraw
1986         syncbind
1987       endif
1988     finally
1989       if exists('l:dir')
1990         execute cd.'`=dir`'
1991       endif
1992     endtry
1993     return ''
1994   catch /^fugitive:/
1995     return 'echoerr v:errmsg'
1996   endtry
1997 endfunction
1999 function! s:BlameCommit(cmd) abort
2000   let cmd = s:Edit(a:cmd, 0, matchstr(getline('.'),'\x\+'))
2001   if cmd =~# '^echoerr'
2002     return cmd
2003   endif
2004   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2005   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2006   if path ==# ''
2007     let path = s:buffer(b:fugitive_blamed_bufnr).path()
2008   endif
2009   execute cmd
2010   if search('^diff .* b/\M'.escape(path,'\').'$','W')
2011     call search('^+++')
2012     let head = line('.')
2013     while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
2014       let top = +matchstr(getline('.'),' +\zs\d\+')
2015       let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
2016       if lnum >= top && lnum <= top + len
2017         let offset = lnum - top
2018         if &scrolloff
2019           +
2020           normal! zt
2021         else
2022           normal! zt
2023           +
2024         endif
2025         while offset > 0 && line('.') < line('$')
2026           +
2027           if getline('.') =~# '^[ +]'
2028             let offset -= 1
2029           endif
2030         endwhile
2031         return 'normal! zv'
2032       endif
2033     endwhile
2034     execute head
2035     normal! zt
2036   endif
2037   return ''
2038 endfunction
2040 function! s:BlameJump(suffix) abort
2041   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
2042   if commit =~# '^0\+$'
2043     let commit = ':0'
2044   endif
2045   let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
2046   let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
2047   if path ==# ''
2048     let path = s:buffer(b:fugitive_blamed_bufnr).path()
2049   endif
2050   let args = b:fugitive_blame_arguments
2051   let offset = line('.') - line('w0')
2052   let bufnr = bufnr('%')
2053   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
2054   if winnr > 0
2055     exe winnr.'wincmd w'
2056   endif
2057   execute s:Edit('edit', 0, commit.a:suffix.':'.path)
2058   execute lnum
2059   if winnr > 0
2060     exe bufnr.'bdelete'
2061   endif
2062   execute 'Gblame '.args
2063   execute lnum
2064   let delta = line('.') - line('w0') - offset
2065   if delta > 0
2066     execute 'normal! '.delta."\<C-E>"
2067   elseif delta < 0
2068     execute 'normal! '.(-delta)."\<C-Y>"
2069   endif
2070   syncbind
2071   return ''
2072 endfunction
2074 let s:hash_colors = {}
2076 function! s:BlameSyntax() abort
2077   let b:current_syntax = 'fugitiveblame'
2078   let conceal = has('conceal') ? ' conceal' : ''
2079   let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
2080   syn match FugitiveblameBoundary "^\^"
2081   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
2082   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2083   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
2084   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
2085   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
2086   exec 'syn match FugitiveblameLineNumber         " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
2087   exec 'syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
2088   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
2089   exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
2090   syn match FugitiveblameShort              " \d\+)" contained contains=FugitiveblameLineNumber
2091   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
2092   hi def link FugitiveblameBoundary           Keyword
2093   hi def link FugitiveblameHash               Identifier
2094   hi def link FugitiveblameUncommitted        Ignore
2095   hi def link FugitiveblameTime               PreProc
2096   hi def link FugitiveblameLineNumber         Number
2097   hi def link FugitiveblameOriginalFile       String
2098   hi def link FugitiveblameOriginalLineNumber Float
2099   hi def link FugitiveblameShort              FugitiveblameDelimiter
2100   hi def link FugitiveblameDelimiter          Delimiter
2101   hi def link FugitiveblameNotCommittedYet    Comment
2102   let seen = {}
2103   for lnum in range(1, line('$'))
2104     let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
2105     if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
2106       continue
2107     endif
2108     let seen[hash] = 1
2109     if &t_Co > 16 && exists('g:CSApprox_loaded')
2110           \ && empty(get(s:hash_colors, hash))
2111       let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
2112       let color = csapprox#per_component#Approximate(r, g, b)
2113       if color == 16 && &background ==# 'dark'
2114         let color = 8
2115       endif
2116       let s:hash_colors[hash] = ' ctermfg='.color
2117     else
2118       let s:hash_colors[hash] = ''
2119     endif
2120     exe 'syn match FugitiveblameHash'.hash.'       "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
2121   endfor
2122   call s:RehighlightBlame()
2123 endfunction
2125 function! s:RehighlightBlame() abort
2126   for [hash, cterm] in items(s:hash_colors)
2127     if !empty(cterm) || has('gui_running')
2128       exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
2129     else
2130       exe 'hi link FugitiveblameHash'.hash.' Identifier'
2131     endif
2132   endfor
2133 endfunction
2135 " Section: Gbrowse
2137 call s:command("-bar -bang -range -nargs=* -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
2139 function! s:Browse(bang,line1,count,...) abort
2140   try
2141     let rev = a:0 ? substitute(join(a:000, ' '),'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
2142     if rev ==# ''
2143       let expanded = s:buffer().rev()
2144     elseif rev ==# ':'
2145       let expanded = s:buffer().path('/')
2146     else
2147       let expanded = s:buffer().expand(rev)
2148     endif
2149     let full = s:repo().translate(expanded)
2150     let commit = ''
2151     if full =~# '^fugitive://'
2152       let commit = matchstr(full,'://.*//\zs\w\+')
2153       let path = matchstr(full,'://.*//\w\+\zs/.*')
2154       if commit =~ '..'
2155         let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
2156       else
2157         let type = 'blob'
2158       endif
2159       let path = path[1:-1]
2160     elseif s:repo().bare()
2161       let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
2162       let type = ''
2163     else
2164       let path = full[strlen(s:repo().tree())+1:-1]
2165       if path =~# '^\.git/'
2166         let type = ''
2167       elseif isdirectory(full)
2168         let type = 'tree'
2169       else
2170         let type = 'blob'
2171       endif
2172     endif
2173     if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
2174       let body = readfile(s:repo().dir(path[5:-1]))[0]
2175       if body =~# '^\x\{40\}$'
2176         let commit = body
2177         let type = 'commit'
2178         let path = ''
2179       elseif body =~# '^ref: refs/'
2180         let path = '.git/' . matchstr(body,'ref: \zs.*')
2181       endif
2182     endif
2184     if a:0 && join(a:000, ' ') =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
2185       let remote = matchstr(join(a:000, ' '),'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
2186     elseif path =~# '^\.git/refs/remotes/.'
2187       let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
2188     else
2189       let remote = 'origin'
2190       let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
2191       if branch ==# '' && path =~# '^\.git/refs/\w\+/'
2192         let branch = s:sub(path,'^\.git/refs/\w+/','')
2193       endif
2194       if filereadable(s:repo().dir('refs/remotes/'.branch))
2195         let remote = matchstr(branch,'[^/]\+')
2196         let rev = rev[strlen(remote)+1:-1]
2197       else
2198         if branch ==# ''
2199           let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
2200         endif
2201         if branch != ''
2202           let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
2203           if remote =~# '^\.\=$'
2204             let remote = 'origin'
2205           elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
2206             let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
2207           endif
2208         endif
2209       endif
2210     endif
2212     let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
2213     if raw ==# ''
2214       let raw = remote
2215     endif
2217     for Handler in g:fugitive_browse_handlers
2218       let url = call(Handler, [{
2219             \ 'repo': s:repo(),
2220             \ 'remote': raw,
2221             \ 'revision': rev,
2222             \ 'commit': commit,
2223             \ 'path': path,
2224             \ 'type': type,
2225             \ 'line1': a:count > 0 ? a:line1 : 0,
2226             \ 'line2': a:count > 0 ? a:count : 0}])
2227       if !empty(url)
2228         break
2229       endif
2230     endfor
2232     if empty(url)
2233       call s:throw("Instaweb failed to start and '".remote."' is not a supported remote")
2234     endif
2236     if a:bang
2237       if has('clipboard')
2238         let @* = url
2239       endif
2240       return 'echomsg '.string(url)
2241     elseif exists(':Browse') == 2
2242       return 'echomsg '.string(url).'|Browse '.url
2243     else
2244       if !exists('g:loaded_netrw')
2245         runtime! autoload/netrw.vim
2246       endif
2247       if exists('*netrw#BrowseX')
2248         return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
2249       else
2250         return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
2251       endif
2252     endif
2253   catch /^fugitive:/
2254     return 'echoerr v:errmsg'
2255   endtry
2256 endfunction
2258 function! s:github_url(opts, ...) abort
2259   if a:0 || type(a:opts) != type({})
2260     return ''
2261   endif
2262   let domain_pattern = 'github\.com'
2263   let domains = exists('g:fugitive_github_domains') ? g:fugitive_github_domains : []
2264   for domain in domains
2265     let domain_pattern .= '\|' . escape(split(domain, '://')[-1], '.')
2266   endfor
2267   let repo = matchstr(get(a:opts, 'remote'), '^\%(https\=://\|git://\|git@\)\=\zs\('.domain_pattern.'\)[/:].\{-\}\ze\%(\.git\)\=$')
2268   if repo ==# ''
2269     return ''
2270   endif
2271   let path = a:opts.path
2272   if index(domains, 'http://' . matchstr(repo, '^[^:/]*')) >= 0
2273     let root = 'http://' . s:sub(repo,':','/')
2274   else
2275     let root = 'https://' . s:sub(repo,':','/')
2276   endif
2277   if path =~# '^\.git/refs/heads/'
2278     let branch = a:opts.repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
2279     if branch ==# ''
2280       return root . '/commits/' . path[16:-1]
2281     else
2282       return root . '/commits/' . branch
2283     endif
2284   elseif path =~# '^\.git/refs/.'
2285     return root . '/commits/' . matchstr(path,'[^/]\+$')
2286   elseif path =~# '.git/\%(config$\|hooks\>\)'
2287     return root . '/admin'
2288   elseif path =~# '^\.git\>'
2289     return root
2290   endif
2291   if a:opts.revision =~# '^[[:alnum:]._-]\+:'
2292     let commit = matchstr(a:opts.revision,'^[^:]*')
2293   elseif a:opts.commit =~# '^\d\=$'
2294     let local = matchstr(a:opts.repo.head_ref(),'\<refs/heads/\zs.*')
2295     let commit = a:opts.repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
2296     if commit ==# ''
2297       let commit = local
2298     endif
2299   else
2300     let commit = a:opts.commit
2301   endif
2302   if a:opts.type == 'tree'
2303     let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
2304   elseif a:opts.type == 'blob'
2305     let url = root . '/blob/' . commit . '/' . path
2306     if get(a:opts, 'line2') && a:opts.line1 == a:opts.line2
2307       let url .= '#L' . a:opts.line1
2308     elseif get(a:opts, 'line2')
2309       let url .= '#L' . a:opts.line1 . '-' . a:opts.line2
2310     endif
2311   elseif a:opts.type == 'tag'
2312     let commit = matchstr(getline(3),'^tag \zs.*')
2313     let url = root . '/tree/' . commit
2314   else
2315     let url = root . '/commit/' . commit
2316   endif
2317   return url
2318 endfunction
2320 function! s:instaweb_url(opts) abort
2321   let output = a:opts.repo.git_chomp('instaweb','-b','unknown')
2322   if output =~# 'http://'
2323     let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:opts.repo.dir(),':t')
2324   else
2325     return ''
2326   endif
2327   if a:opts.path =~# '^\.git/refs/.'
2328     return root . ';a=shortlog;h=' . matchstr(a:opts.path,'^\.git/\zs.*')
2329   elseif a:opts.path =~# '^\.git\>'
2330     return root
2331   endif
2332   let url = root
2333   if a:opts.commit =~# '^\x\{40\}$'
2334     if a:opts.type ==# 'commit'
2335       let url .= ';a=commit'
2336     endif
2337     let url .= ';h=' . a:opts.repo.rev_parse(a:opts.commit . (a:opts.path == '' ? '' : ':' . a:opts.path))
2338   else
2339     if a:opts.type ==# 'blob'
2340       let tmp = tempname()
2341       silent execute 'write !'.a:opts.repo.git_command('hash-object','-w','--stdin').' > '.tmp
2342       let url .= ';h=' . readfile(tmp)[0]
2343     else
2344       try
2345         let url .= ';h=' . a:opts.repo.rev_parse((a:opts.commit == '' ? 'HEAD' : ':' . a:opts.commit) . ':' . a:opts.path)
2346       catch /^fugitive:/
2347         call s:throw('fugitive: cannot browse uncommitted file')
2348       endtry
2349     endif
2350     let root .= ';hb=' . matchstr(a:opts.repo.head_ref(),'[^ ]\+$')
2351   endif
2352   if a:opts.path !=# ''
2353     let url .= ';f=' . a:opts.path
2354   endif
2355   if get(a:opts, 'line1')
2356     let url .= '#l' . a:opts.line1
2357   endif
2358   return url
2359 endfunction
2361 if !exists('g:fugitive_browse_handlers')
2362   let g:fugitive_browse_handlers = []
2363 endif
2365 call extend(g:fugitive_browse_handlers,
2366       \ [s:function('s:github_url'), s:function('s:instaweb_url')])
2368 " Section: File access
2370 function! s:ReplaceCmd(cmd,...) abort
2371   let fn = expand('%:p')
2372   let tmp = tempname()
2373   let prefix = ''
2374   try
2375     if a:0 && a:1 != ''
2376       if s:winshell()
2377         let old_index = $GIT_INDEX_FILE
2378         let $GIT_INDEX_FILE = a:1
2379       else
2380         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
2381       endif
2382     endif
2383     if s:winshell()
2384       let cmd_escape_char = &shellxquote == '(' ?  '^' : '^^^'
2385       call system('cmd /c "'.prefix.s:gsub(a:cmd,'[<>]', cmd_escape_char.'&').' > '.tmp.'"')
2386     else
2387       call system(' ('.prefix.a:cmd.' > '.tmp.') ')
2388     endif
2389   finally
2390     if exists('old_index')
2391       let $GIT_INDEX_FILE = old_index
2392     endif
2393   endtry
2394   silent exe 'keepalt file '.tmp
2395   try
2396     silent edit!
2397   finally
2398     try
2399       silent exe 'keepalt file '.s:fnameescape(fn)
2400     catch /^Vim\%((\a\+)\)\=:E302/
2401     endtry
2402     call delete(tmp)
2403     if fnamemodify(bufname('$'), ':p') ==# tmp
2404       silent execute 'bwipeout '.bufnr('$')
2405     endif
2406     silent exe 'doau BufReadPost '.s:fnameescape(fn)
2407   endtry
2408 endfunction
2410 function! s:BufReadIndex() abort
2411   if !exists('b:fugitive_display_format')
2412     let b:fugitive_display_format = filereadable(expand('%').'.lock')
2413   endif
2414   let b:fugitive_display_format = b:fugitive_display_format % 2
2415   let b:fugitive_type = 'index'
2416   try
2417     let b:git_dir = s:repo().dir()
2418     setlocal noro ma nomodeline
2419     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
2420       let index = ''
2421     else
2422       let index = expand('%:p')
2423     endif
2424     if b:fugitive_display_format
2425       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
2426       set ft=git nospell
2427     else
2428       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
2429       let dir = getcwd()
2430       if fugitive#git_version() =~# '^0\|^1\.[1-3]\.'
2431         let cmd = s:repo().git_command('status')
2432       elseif fugitive#git_version() =~# '^1\.[4-7]\.'
2433         let cmd = s:repo().git_command('status', '-u')
2434       else
2435         let cmd = s:repo().git_command(
2436               \ '-c', 'status.displayCommentPrefix=true',
2437               \ '-c', 'color.status=false',
2438               \ '-c', 'status.short=false',
2439               \ '-c', 'status.showUntrackedFiles=all',
2440               \ 'status')
2441       endif
2442       try
2443         execute cd.'`=s:repo().tree()`'
2444         call s:ReplaceCmd(cmd, index)
2445       finally
2446         execute cd.'`=dir`'
2447       endtry
2448       set ft=gitcommit
2449       set foldtext=fugitive#foldtext()
2450     endif
2451     setlocal ro noma nomod noswapfile
2452     if &bufhidden ==# ''
2453       setlocal bufhidden=delete
2454     endif
2455     call s:JumpInit()
2456     nunmap   <buffer>          P
2457     nunmap   <buffer>          ~
2458     nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
2459     nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
2460     nnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
2461     xnoremap <buffer> <silent> - :<C-U>silent execute <SID>StageToggle(line("'<"),line("'>"))<CR>
2462     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
2463     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
2464     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
2465     nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>
2466     nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
2467     nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
2468     nnoremap <buffer> <silent> cva :<C-U>Gcommit --amend --verbose<CR>
2469     nnoremap <buffer> <silent> cvc :<C-U>Gcommit --verbose<CR>
2470     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2471     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
2472     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2473     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
2474     nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
2475     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
2476     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
2477     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
2478     nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
2479     nnoremap <buffer> <silent> r :<C-U>edit<CR>
2480     nnoremap <buffer> <silent> R :<C-U>edit<CR>
2481     nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
2482     nnoremap <buffer> <silent> g?   :help fugitive-:Gstatus<CR>
2483     nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
2484   catch /^fugitive:/
2485     return 'echoerr v:errmsg'
2486   endtry
2487 endfunction
2489 function! s:FileRead() abort
2490   try
2491     let repo = s:repo(fugitive#extract_git_dir(expand('<amatch>')))
2492     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
2493     let hash = repo.rev_parse(path)
2494     if path =~ '^:'
2495       let type = 'blob'
2496     else
2497       let type = repo.git_chomp('cat-file','-t',hash)
2498     endif
2499     " TODO: use count, if possible
2500     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
2501   catch /^fugitive:/
2502     return 'echoerr v:errmsg'
2503   endtry
2504 endfunction
2506 function! s:BufReadIndexFile() abort
2507   try
2508     let b:fugitive_type = 'blob'
2509     let b:git_dir = s:repo().dir()
2510     try
2511       call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
2512     finally
2513       if &bufhidden ==# ''
2514         setlocal bufhidden=delete
2515       endif
2516       setlocal noswapfile
2517     endtry
2518     return ''
2519   catch /^fugitive: rev-parse/
2520     silent exe 'doau BufNewFile '.s:fnameescape(expand('%:p'))
2521     return ''
2522   catch /^fugitive:/
2523     return 'echoerr v:errmsg'
2524   endtry
2525 endfunction
2527 function! s:BufWriteIndexFile() abort
2528   let tmp = tempname()
2529   try
2530     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
2531     let stage = matchstr(expand('<amatch>'),'//\zs\d')
2532     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
2533     let sha1 = readfile(tmp)[0]
2534     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
2535     if old_mode == ''
2536       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
2537     endif
2538     let info = old_mode.' '.sha1.' '.stage."\t".path
2539     call writefile([info],tmp)
2540     if s:winshell()
2541       let error = system('type '.s:gsub(tmp,'/','\\').'|'.s:repo().git_command('update-index','--index-info'))
2542     else
2543       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
2544     endif
2545     if v:shell_error == 0
2546       setlocal nomodified
2547       if exists('#BufWritePost')
2548         execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
2549       endif
2550       call fugitive#reload_status()
2551       return ''
2552     else
2553       return 'echoerr '.string('fugitive: '.error)
2554     endif
2555   finally
2556     call delete(tmp)
2557   endtry
2558 endfunction
2560 function! s:BufReadObject() abort
2561   try
2562     setlocal noro ma
2563     let b:git_dir = s:repo().dir()
2564     let hash = s:buffer().sha1()
2565     if !exists("b:fugitive_type")
2566       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
2567     endif
2568     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
2569       return "echoerr 'fugitive: unrecognized git type'"
2570     endif
2571     let firstline = getline('.')
2572     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
2573       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
2574     endif
2576     if b:fugitive_type !=# 'blob'
2577       setlocal nomodeline
2578     endif
2580     let pos = getpos('.')
2581     silent keepjumps %delete_
2582     setlocal endofline
2584     try
2585       if b:fugitive_type ==# 'tree'
2586         let b:fugitive_display_format = b:fugitive_display_format % 2
2587         if b:fugitive_display_format
2588           call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
2589         else
2590           call s:ReplaceCmd(s:repo().git_command('show','--no-color',hash))
2591         endif
2592       elseif b:fugitive_type ==# 'tag'
2593         let b:fugitive_display_format = b:fugitive_display_format % 2
2594         if b:fugitive_display_format
2595           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2596         else
2597           call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
2598         endif
2599       elseif b:fugitive_type ==# 'commit'
2600         let b:fugitive_display_format = b:fugitive_display_format % 2
2601         if b:fugitive_display_format
2602           call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2603         else
2604           call s:ReplaceCmd(s:repo().git_command('show','--no-color','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
2605           keepjumps call search('^parent ')
2606           if getline('.') ==# 'parent '
2607             silent keepjumps delete_
2608           else
2609             silent keepjumps s/\%(^parent\)\@<! /\rparent /ge
2610           endif
2611           keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
2612           if lnum
2613             silent keepjumps delete_
2614           end
2615           keepjumps 1
2616         endif
2617       elseif b:fugitive_type ==# 'blob'
2618         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
2619         setlocal nomodeline
2620       endif
2621     finally
2622       keepjumps call setpos('.',pos)
2623       setlocal ro noma nomod noswapfile
2624       if &bufhidden ==# ''
2625         setlocal bufhidden=delete
2626       endif
2627       if b:fugitive_type !=# 'blob'
2628         setlocal filetype=git foldmethod=syntax
2629         nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
2630         nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
2631       else
2632         call s:JumpInit()
2633       endif
2634     endtry
2636     return ''
2637   catch /^fugitive:/
2638     return 'echoerr v:errmsg'
2639   endtry
2640 endfunction
2642 augroup fugitive_files
2643   autocmd!
2644   autocmd BufReadCmd  index{,.lock}
2645         \ if fugitive#is_git_dir(expand('<amatch>:p:h')) |
2646         \   exe s:BufReadIndex() |
2647         \ elseif filereadable(expand('<amatch>')) |
2648         \   read <amatch> |
2649         \   1delete |
2650         \ endif
2651   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
2652   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
2653   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
2654   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
2655   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
2656   autocmd FileType git
2657         \ if exists('b:git_dir') |
2658         \  call s:JumpInit() |
2659         \ endif
2660 augroup END
2662 " Section: Temp files
2664 if !exists('s:temp_files')
2665   let s:temp_files = {}
2666 endif
2668 augroup fugitive_temp
2669   autocmd!
2670   autocmd BufNewFile,BufReadPost *
2671         \ if has_key(s:temp_files,tolower(expand('<afile>:p'))) |
2672         \   let b:git_dir = s:temp_files[tolower(expand('<afile>:p'))].dir |
2673         \   let b:git_type = 'temp' |
2674         \   let b:git_args = s:temp_files[tolower(expand('<afile>:p'))].args |
2675         \   call fugitive#detect(expand('<afile>:p')) |
2676         \   setlocal bufhidden=delete nobuflisted |
2677         \   nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>|
2678         \ endif
2679 augroup END
2681 " Section: Go to file
2683 function! s:JumpInit() abort
2684   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
2685   if !&modifiable
2686     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
2687     nnoremap <buffer> <silent> S     :<C-U>exe <SID>GF("vsplit")<CR>
2688     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
2689     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>
2690     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
2691     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
2692     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2693     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',0,<SID>buffer().containing_commit())<CR>
2694     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',0,<SID>buffer().containing_commit())<CR>
2695     nnoremap <buffer> <silent> cS    :<C-U>exe <SID>Edit('vsplit',0,<SID>buffer().containing_commit())<CR>
2696     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',0,<SID>buffer().containing_commit())<CR>
2697     nnoremap <buffer> <silent> cP    :<C-U>exe <SID>Edit('pedit',0,<SID>buffer().containing_commit())<CR>
2698     nnoremap <buffer>          .     : <C-R>=fnameescape(<SID>recall())<CR><Home>
2699   endif
2700 endfunction
2702 function! s:GF(mode) abort
2703   try
2704     let buffer = s:buffer()
2705     let myhash = buffer.sha1()
2706     if myhash ==# '' && getline(1) =~# '^\%(commit\|tag\) \w'
2707       let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
2708     endif
2710     if buffer.type('tree')
2711       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2712       if showtree && line('.') == 1
2713         return ""
2714       elseif showtree && line('.') > 2
2715         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
2716       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
2717         return s:Edit(a:mode,0,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
2718       endif
2720     elseif buffer.type('blob')
2721       let ref = expand("<cfile>")
2722       try
2723         let sha1 = buffer.repo().rev_parse(ref)
2724       catch /^fugitive:/
2725       endtry
2726       if exists('sha1')
2727         return s:Edit(a:mode,0,ref)
2728       endif
2730     else
2732       " Index
2733       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
2734         let ref = matchstr(getline('.'),'\x\{40\}')
2735         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
2736         return s:Edit(a:mode,0,file)
2738       elseif getline('.') =~# '^#\trenamed:.* -> '
2739         let file = '/'.matchstr(getline('.'),' -> \zs.*')
2740         return s:Edit(a:mode,0,file)
2741       elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
2742         let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
2743         return s:Edit(a:mode,0,file)
2744       elseif getline('.') =~# '^#\t.'
2745         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
2746         return s:Edit(a:mode,0,file)
2747       elseif getline('.') =~# ': needs merge$'
2748         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
2749         return s:Edit(a:mode,0,file).'|Gdiff'
2751       elseif getline('.') ==# '# Not currently on any branch.'
2752         return s:Edit(a:mode,0,'HEAD')
2753       elseif getline('.') =~# '^# On branch '
2754         let file = 'refs/heads/'.getline('.')[12:]
2755         return s:Edit(a:mode,0,file)
2756       elseif getline('.') =~# "^# Your branch .*'"
2757         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
2758         return s:Edit(a:mode,0,file)
2759       endif
2761       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
2763       if getline('.') =~# '^ref: '
2764         let ref = strpart(getline('.'),5)
2766       elseif getline('.') =~# '^commit \x\{40\}\>'
2767         let ref = matchstr(getline('.'),'\x\{40\}')
2768         return s:Edit(a:mode,0,ref)
2770       elseif getline('.') =~# '^parent \x\{40\}\>'
2771         let ref = matchstr(getline('.'),'\x\{40\}')
2772         let line = line('.')
2773         let parent = 0
2774         while getline(line) =~# '^parent '
2775           let parent += 1
2776           let line -= 1
2777         endwhile
2778         return s:Edit(a:mode,0,ref)
2780       elseif getline('.') =~ '^tree \x\{40\}$'
2781         let ref = matchstr(getline('.'),'\x\{40\}')
2782         if s:repo().rev_parse(myhash.':') == ref
2783           let ref = myhash.':'
2784         endif
2785         return s:Edit(a:mode,0,ref)
2787       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
2788         let ref = matchstr(getline('.'),'\x\{40\}')
2789         let type = matchstr(getline(line('.')+1),'type \zs.*')
2791       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
2792         return ''
2794       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
2795         let ref = matchstr(getline('.'),'\x\{40\}')
2796         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
2798       elseif getline('.') =~# '^[+-]\{3\} [ab/]'
2799         let ref = getline('.')[4:]
2801       elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+,\d\+ +\d\+,','bnW')
2802         let type = getline('.')[0]
2803         let lnum = line('.') - 1
2804         let offset = -1
2805         while getline(lnum) !~# '^@@ -\d\+,\d\+ +\d\+,'
2806           if getline(lnum) =~# '^[ '.type.']'
2807             let offset += 1
2808           endif
2809           let lnum -= 1
2810         endwhile
2811         let offset += matchstr(getline(lnum), type.'\zs\d\+')
2812         let ref = getline(search('^'.type.'\{3\} [ab]/','bnW'))[4:-1]
2813         let dcmd = '+'.offset.'|normal! zv'
2814         let dref = ''
2816       elseif getline('.') =~# '^rename from '
2817         let ref = 'a/'.getline('.')[12:]
2818       elseif getline('.') =~# '^rename to '
2819         let ref = 'b/'.getline('.')[10:]
2821       elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2822         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2823         let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2824         let dcmd = 'Gdiff'
2826       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
2827         let line = getline(line('.')-1)
2828         let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
2829         let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
2830         let dcmd = 'Gdiff!'
2832       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
2833         let ref = getline('.')
2835       elseif expand('<cword>') =~# '^\x\{7,40\}\>'
2836         return s:Edit(a:mode,0,expand('<cword>'))
2838       else
2839         let ref = ''
2840       endif
2842       if myhash ==# ''
2843         let ref = s:sub(ref,'^a/','HEAD:')
2844         let ref = s:sub(ref,'^b/',':0:')
2845         if exists('dref')
2846           let dref = s:sub(dref,'^a/','HEAD:')
2847         endif
2848       else
2849         let ref = s:sub(ref,'^a/',myhash.'^:')
2850         let ref = s:sub(ref,'^b/',myhash.':')
2851         if exists('dref')
2852           let dref = s:sub(dref,'^a/',myhash.'^:')
2853         endif
2854       endif
2856       if ref ==# '/dev/null'
2857         " Empty blob
2858         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
2859       endif
2861       if exists('dref')
2862         return s:Edit(a:mode,0,ref) . '|'.dcmd.' '.s:fnameescape(dref)
2863       elseif ref != ""
2864         return s:Edit(a:mode,0,ref)
2865       endif
2867     endif
2868     return ''
2869   catch /^fugitive:/
2870     return 'echoerr v:errmsg'
2871   endtry
2872 endfunction
2874 " Section: Statusline
2876 function! s:repo_head_ref() dict abort
2877   if !filereadable(self.dir('HEAD'))
2878     return ''
2879   endif
2880   return readfile(self.dir('HEAD'))[0]
2881 endfunction
2883 call s:add_methods('repo',['head_ref'])
2885 function! fugitive#statusline(...) abort
2886   if !exists('b:git_dir')
2887     return ''
2888   endif
2889   let status = ''
2890   if s:buffer().commit() != ''
2891     let status .= ':' . s:buffer().commit()[0:7]
2892   endif
2893   let status .= '('.fugitive#head(7).')'
2894   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2895     return ',GIT'.status
2896   else
2897     return '[Git'.status.']'
2898   endif
2899 endfunction
2901 function! fugitive#head(...) abort
2902   if !exists('b:git_dir')
2903     return ''
2904   endif
2906   return s:repo().head(a:0 ? a:1 : 0)
2907 endfunction
2909 augroup fugitive_statusline
2910   autocmd!
2911   autocmd User Flags call Hoist('buffer', function('fugitive#statusline'))
2912 augroup END
2914 " Section: Folding
2916 function! fugitive#foldtext() abort
2917   if &foldmethod !=# 'syntax'
2918     return foldtext()
2919   elseif getline(v:foldstart) =~# '^diff '
2920     let [add, remove] = [-1, -1]
2921     let filename = ''
2922     for lnum in range(v:foldstart, v:foldend)
2923       if filename ==# '' && getline(lnum) =~# '^[+-]\{3\} [abciow12]/'
2924         let filename = getline(lnum)[6:-1]
2925       endif
2926       if getline(lnum) =~# '^+'
2927         let add += 1
2928       elseif getline(lnum) =~# '^-'
2929         let remove += 1
2930       elseif getline(lnum) =~# '^Binary '
2931         let binary = 1
2932       endif
2933     endfor
2934     if filename ==# ''
2935       let filename = matchstr(getline(v:foldstart), '^diff .\{-\} a/\zs.*\ze b/')
2936     endif
2937     if filename ==# ''
2938       let filename = getline(v:foldstart)[5:-1]
2939     endif
2940     if exists('binary')
2941       return 'Binary: '.filename
2942     else
2943       return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
2944     endif
2945   elseif getline(v:foldstart) =~# '^# .*:$'
2946     let lines = getline(v:foldstart, v:foldend)
2947     call filter(lines, 'v:val =~# "^#\t"')
2948     cal map(lines,'s:sub(v:val, "^#\t%(modified: +|renamed: +)=", "")')
2949     cal map(lines,'s:sub(v:val, "^([[:alpha:] ]+): +(.*)", "\\2 (\\1)")')
2950     return getline(v:foldstart).' '.join(lines, ', ')
2951   endif
2952   return foldtext()
2953 endfunction
2955 augroup fugitive_foldtext
2956   autocmd!
2957   autocmd User Fugitive
2958         \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
2959         \    set foldtext=fugitive#foldtext() |
2960         \ endif
2961 augroup END