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