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