Vim 7.4 won't let us quit windows from a script.
[profile.git] / .vim / plugin / fugitive.vim
1 " fugitive.vim - A Git wrapper so awesome, it should be illegal
2 " Maintainer:   Tim Pope <vimNOSPAM@tpope.org>
3 " Version:      1.2
4 " GetLatestVimScripts: 2975 1 :AutoInstall: fugitive.vim
5
6 if v:version < 700
7     finish
8 endif
9 if exists('g:loaded_fugitive') || &cp
10   finish
11 endif
12 let g:loaded_fugitive = 1
13
14 if !exists('g:fugitive_git_executable')
15   let g:fugitive_git_executable = 'git'
16 endif
17
18 " Utility {{{1
19
20 function! s:function(name) abort
21   return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
22 endfunction
23
24 function! s:sub(str,pat,rep) abort
25   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
26 endfunction
27
28 function! s:gsub(str,pat,rep) abort
29   return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
30 endfunction
31
32 function! s:shellesc(arg) abort
33   if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
34     return a:arg
35   elseif &shell =~# 'cmd' && a:arg !~# '"'
36     return '"'.a:arg.'"'
37   else
38     return shellescape(a:arg)
39   endif
40 endfunction
41
42 function! s:fnameescape(file) abort
43   if exists('*fnameescape')
44     return fnameescape(a:file)
45   else
46     return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
47   endif
48 endfunction
49
50 function! s:throw(string) abort
51   let v:errmsg = 'fugitive: '.a:string
52   throw v:errmsg
53 endfunction
54
55 function! s:warn(str)
56   echohl WarningMsg
57   echomsg a:str
58   echohl None
59   let v:warningmsg = a:str
60 endfunction
61
62 function! s:shellslash(path)
63   if exists('+shellslash') && !&shellslash
64     return s:gsub(a:path,'\\','/')
65   else
66     return a:path
67   endif
68 endfunction
69
70 function! s:add_methods(namespace, method_names) abort
71   for name in a:method_names
72     let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
73   endfor
74 endfunction
75
76 let s:commands = []
77 function! s:command(definition) abort
78   let s:commands += [a:definition]
79 endfunction
80
81 function! s:define_commands()
82   for command in s:commands
83     exe 'command! -buffer '.command
84   endfor
85 endfunction
86
87 function! s:compatibility_check()
88   if exists('b:git_dir') && exists('*GitBranchInfoCheckGitDir') && !exists('g:fugitive_did_compatibility_warning')
89     let g:fugitive_did_compatibility_warning = 1
90     call s:warn("See http://github.com/tpope/vim-fugitive/issues#issue/1 for why you should remove git-branch-info.vim")
91   endif
92 endfunction
93
94 augroup fugitive_utility
95   autocmd!
96   autocmd User Fugitive call s:define_commands()
97   autocmd VimEnter * call s:compatibility_check()
98 augroup END
99
100 let s:abstract_prototype = {}
101
102 " }}}1
103 " Initialization {{{1
104
105 function! s:ExtractGitDir(path) abort
106   let path = s:shellslash(a:path)
107   if path =~? '^fugitive://.*//'
108     return matchstr(path,'fugitive://\zs.\{-\}\ze//')
109   endif
110   let fn = fnamemodify(path,':s?[\/]$??')
111   let ofn = ""
112   let nfn = fn
113   while fn != ofn
114     if filereadable(fn . '/.git/HEAD')
115       return s:sub(simplify(fnamemodify(fn . '/.git',':p')),'\W$','')
116     elseif fn =~ '\.git$' && filereadable(fn . '/HEAD')
117       return s:sub(simplify(fnamemodify(fn,':p')),'\W$','')
118     endif
119     let ofn = fn
120     let fn = fnamemodify(ofn,':h')
121   endwhile
122   return ''
123 endfunction
124
125 function! s:Detect(path)
126   if exists('b:git_dir') && b:git_dir ==# ''
127     unlet b:git_dir
128   endif
129   if !exists('b:git_dir')
130     let dir = s:ExtractGitDir(a:path)
131     if dir != ''
132       let b:git_dir = dir
133     endif
134   endif
135   if exists('b:git_dir')
136     silent doautocmd User Fugitive
137     cnoremap <expr> <buffer> <C-R><C-G> fugitive#buffer().rev()
138     let buffer = fugitive#buffer()
139     if expand('%:p') =~# '//'
140       call buffer.setvar('&path',s:sub(buffer.getvar('&path'),'^\.%(,|$)',''))
141     endif
142     if b:git_dir !~# ',' && stridx(buffer.getvar('&tags'),b:git_dir.'/tags') == -1
143       if &filetype != ''
144         call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/'.&filetype.'.tags')
145       endif
146       call buffer.setvar('&tags',buffer.getvar('&tags').','.b:git_dir.'/tags')
147     endif
148   endif
149 endfunction
150
151 augroup fugitive
152   autocmd!
153   autocmd BufNewFile,BufReadPost * call s:Detect(expand('<amatch>:p'))
154   autocmd FileType           netrw call s:Detect(expand('<afile>:p'))
155   autocmd VimEnter * if expand('<amatch>')==''|call s:Detect(getcwd())|endif
156   autocmd BufWinLeave * execute getwinvar(+winnr(), 'fugitive_restore')
157 augroup END
158
159 " }}}1
160 " Repository {{{1
161
162 let s:repo_prototype = {}
163 let s:repos = {}
164
165 function! s:repo(...) abort
166   let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : s:ExtractGitDir(expand('%:p')))
167   if dir !=# ''
168     if has_key(s:repos,dir)
169       let repo = get(s:repos,dir)
170     else
171       let repo = {'git_dir': dir}
172       let s:repos[dir] = repo
173     endif
174     return extend(extend(repo,s:repo_prototype,'keep'),s:abstract_prototype,'keep')
175   endif
176   call s:throw('not a git repository: '.expand('%:p'))
177 endfunction
178
179 function! s:repo_dir(...) dict abort
180   return join([self.git_dir]+a:000,'/')
181 endfunction
182
183 function! s:repo_tree(...) dict abort
184   if !self.bare()
185     let dir = fnamemodify(self.git_dir,':h')
186     return join([dir]+a:000,'/')
187   endif
188   call s:throw('no work tree')
189 endfunction
190
191 function! s:repo_bare() dict abort
192   return self.dir() !~# '/\.git$'
193 endfunction
194
195 function! s:repo_translate(spec) dict abort
196   if a:spec ==# '.' || a:spec ==# '/.'
197     return self.bare() ? self.dir() : self.tree()
198   elseif a:spec =~# '^/'
199     return fnamemodify(self.dir(),':h').a:spec
200   elseif a:spec =~# '^:[0-3]:'
201     return 'fugitive://'.self.dir().'//'.a:spec[1].'/'.a:spec[3:-1]
202   elseif a:spec ==# ':'
203     if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(s:repo().dir())] ==# s:repo().dir('') && filereadable($GIT_INDEX_FILE)
204       return fnamemodify($GIT_INDEX_FILE,':p')
205     else
206       return self.dir('index')
207     endif
208   elseif a:spec =~# '^:/'
209     let ref = self.rev_parse(matchstr(a:spec,'.[^:]*'))
210     return 'fugitive://'.self.dir().'//'.ref
211   elseif a:spec =~# '^:'
212     return 'fugitive://'.self.dir().'//0/'.a:spec[1:-1]
213   elseif a:spec =~# 'HEAD\|^refs/' && a:spec !~ ':' && filereadable(self.dir(a:spec))
214     return self.dir(a:spec)
215   elseif filereadable(s:repo().dir('refs/'.a:spec))
216     return self.dir('refs/'.a:spec)
217   elseif filereadable(s:repo().dir('refs/tags/'.a:spec))
218     return self.dir('refs/tags/'.a:spec)
219   elseif filereadable(s:repo().dir('refs/heads/'.a:spec))
220     return self.dir('refs/heads/'.a:spec)
221   elseif filereadable(s:repo().dir('refs/remotes/'.a:spec))
222     return self.dir('refs/remotes/'.a:spec)
223   elseif filereadable(s:repo().dir('refs/remotes/'.a:spec.'/HEAD'))
224     return self.dir('refs/remotes/'.a:spec,'/HEAD')
225   else
226     try
227       let ref = self.rev_parse(matchstr(a:spec,'[^:]*'))
228       let path = s:sub(matchstr(a:spec,':.*'),'^:','/')
229       return 'fugitive://'.self.dir().'//'.ref.path
230     catch /^fugitive:/
231       return self.tree(a:spec)
232     endtry
233   endif
234 endfunction
235
236 call s:add_methods('repo',['dir','tree','bare','translate'])
237
238 function! s:repo_git_command(...) dict abort
239   let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
240   return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
241 endfunction
242
243 function! s:repo_git_chomp(...) dict abort
244   return s:sub(system(call(self.git_command,a:000,self)),'\n$','')
245 endfunction
246
247 function! s:repo_git_chomp_in_tree(...) dict abort
248   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
249   let dir = getcwd()
250   try
251     execute cd.'`=s:repo().tree()`'
252     return call(s:repo().git_chomp, a:000, s:repo())
253   finally
254     execute cd.'`=dir`'
255   endtry
256 endfunction
257
258 function! s:repo_rev_parse(rev) dict abort
259   let hash = self.git_chomp('rev-parse','--verify',a:rev)
260   if hash =~ '\<\x\{40\}$'
261     return matchstr(hash,'\<\x\{40\}$')
262   endif
263   call s:throw('rev-parse '.a:rev.': '.hash)
264 endfunction
265
266 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
267
268 function! s:repo_dirglob(base) dict abort
269   let base = s:sub(a:base,'^/','')
270   let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*/')),"\n")
271   call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
272   return matches
273 endfunction
274
275 function! s:repo_superglob(base) dict abort
276   if a:base =~# '^/' || a:base !~# ':'
277     let results = []
278     if a:base !~# '^/'
279       let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"]
280       let heads += sort(split(s:repo().git_chomp("rev-parse","--symbolic","--branches","--tags","--remotes"),"\n"))
281       call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
282       let results += heads
283     endif
284     if !self.bare()
285       let base = s:sub(a:base,'^/','')
286       let matches = split(glob(self.tree(s:gsub(base,'/','*&').'*')),"\n")
287       call map(matches,'s:shellslash(v:val)')
288       call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
289       call map(matches,'v:val[ strlen(self.tree())+(a:base !~ "^/") : -1 ]')
290       let results += matches
291     endif
292     return results
293
294   elseif a:base =~# '^:'
295     let entries = split(self.git_chomp('ls-files','--stage'),"\n")
296     call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
297     if a:base !~# '^:[0-3]\%(:\|$\)'
298       call filter(entries,'v:val[1] == "0"')
299       call map(entries,'v:val[2:-1]')
300     endif
301     call filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
302     return entries
303
304   else
305     let tree = matchstr(a:base,'.*[:/]')
306     let entries = split(self.git_chomp('ls-tree',tree),"\n")
307     call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
308     call map(entries,'tree.s:sub(v:val,".*\t","")')
309     return filter(entries,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
310   endif
311 endfunction
312
313 call s:add_methods('repo',['dirglob','superglob'])
314
315 function! s:repo_keywordprg() dict abort
316   let args = ' --git-dir='.escape(self.dir(),"\\\"' ").' show'
317   if has('gui_running') && !has('win32')
318     return g:fugitive_git_executable . ' --no-pager' . args
319   else
320     return g:fugitive_git_executable . args
321   endif
322 endfunction
323
324 call s:add_methods('repo',['keywordprg'])
325
326 " }}}1
327 " Buffer {{{1
328
329 let s:buffer_prototype = {}
330
331 function! s:buffer(...) abort
332   let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
333   call extend(extend(buffer,s:buffer_prototype,'keep'),s:abstract_prototype,'keep')
334   if buffer.getvar('git_dir') !=# ''
335     return buffer
336   endif
337   call s:throw('not a git repository: '.expand('%:p'))
338 endfunction
339
340 function! fugitive#buffer(...) abort
341   return s:buffer(a:0 ? a:1 : '%')
342 endfunction
343
344 function! s:buffer_getvar(var) dict abort
345   return getbufvar(self['#'],a:var)
346 endfunction
347
348 function! s:buffer_setvar(var,value) dict abort
349   return setbufvar(self['#'],a:var,a:value)
350 endfunction
351
352 function! s:buffer_getline(lnum) dict abort
353   return getbufline(self['#'],a:lnum)[0]
354 endfunction
355
356 function! s:buffer_repo() dict abort
357   return s:repo(self.getvar('git_dir'))
358 endfunction
359
360 function! s:buffer_type(...) dict abort
361   if self.getvar('fugitive_type') != ''
362     let type = self.getvar('fugitive_type')
363   elseif fnamemodify(self.spec(),':p') =~# '.\git/refs/\|\.git/\w*HEAD$'
364     let type = 'head'
365   elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
366     let type = 'tree'
367   elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
368     let type = 'tree'
369   elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
370     let type = 'index'
371   elseif isdirectory(self.spec())
372     let type = 'directory'
373   elseif self.spec() == ''
374     let type = 'null'
375   elseif filereadable(self.spec())
376     let type = 'file'
377   else
378     let type = ''
379   endif
380   if a:0
381     return !empty(filter(copy(a:000),'v:val ==# type'))
382   else
383     return type
384   endif
385 endfunction
386
387 if has('win32')
388
389   function! s:buffer_spec() dict abort
390     let bufname = bufname(self['#'])
391     let retval = ''
392     for i in split(bufname,'[^:]\zs\\')
393       let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
394     endfor
395     return s:shellslash(fnamemodify(retval,':p'))
396   endfunction
397
398 else
399
400   function! s:buffer_spec() dict abort
401     let bufname = bufname(self['#'])
402     return s:shellslash(bufname == '' ? '' : fnamemodify(bufname,':p'))
403   endfunction
404
405 endif
406
407 function! s:buffer_name() dict abort
408   return self.spec()
409 endfunction
410
411 function! s:buffer_commit() dict abort
412   return matchstr(self.spec(),'^fugitive://.\{-\}//\zs\w*')
413 endfunction
414
415 function! s:buffer_path(...) dict abort
416   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
417   if rev != ''
418     let rev = s:sub(rev,'\w*','')
419   else
420     let rev = self.spec()[strlen(self.repo().tree()) : -1]
421   endif
422   return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
423 endfunction
424
425 function! s:buffer_rev() dict abort
426   let rev = matchstr(self.spec(),'^fugitive://.\{-\}//\zs.*')
427   if rev =~ '^\x/'
428     return ':'.rev[0].':'.rev[2:-1]
429   elseif rev =~ '.'
430     return s:sub(rev,'/',':')
431   elseif self.spec() =~ '\.git/index$'
432     return ':'
433   elseif self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
434     return self.spec()[strlen(self.repo().dir())+1 : -1]
435   else
436     return self.path()
437   endif
438 endfunction
439
440 function! s:buffer_sha1() dict abort
441   if self.spec() =~ '^fugitive://' || self.spec() =~ '\.git/refs/\|\.git/.*HEAD$'
442     return self.repo().rev_parse(self.rev())
443   else
444     return ''
445   endif
446 endfunction
447
448 function! s:buffer_expand(rev) dict abort
449   if a:rev =~# '^:[0-3]$'
450     let file = a:rev.self.path(':')
451   elseif a:rev =~# '^[-:]/$'
452     let file = '/'.self.path()
453   elseif a:rev =~# '^-'
454     let file = 'HEAD^{}'.a:rev[1:-1].self.path(':')
455   elseif a:rev =~# '^@{'
456     let file = 'HEAD'.a:rev.self.path(':')
457   elseif a:rev =~# '^[~^]'
458     let commit = s:sub(self.commit(),'^\d=$','HEAD')
459     let file = commit.a:rev.self.path(':')
460   else
461     let file = a:rev
462   endif
463   return s:sub(s:sub(file,'\%$',self.path()),'\.\@<=/$','')
464 endfunction
465
466 function! s:buffer_containing_commit() dict abort
467   if self.commit() =~# '^\d$'
468     return ':'
469   elseif self.commit() =~# '.'
470     return self.commit()
471   else
472     return 'HEAD'
473   endif
474 endfunction
475
476 call s:add_methods('buffer',['getvar','setvar','getline','repo','type','spec','name','commit','path','rev','sha1','expand','containing_commit'])
477
478 " }}}1
479 " Git {{{1
480
481 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,<q-args>)")
482
483 function! s:ExecuteInTree(cmd) abort
484   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
485   let dir = getcwd()
486   try
487     execute cd.'`=s:repo().tree()`'
488     execute a:cmd
489   finally
490     execute cd.'`=dir`'
491   endtry
492 endfunction
493
494 function! s:Git(bang,cmd) abort
495   let git = s:repo().git_command()
496   if has('gui_running') && !has('win32')
497     let git .= ' --no-pager'
498   endif
499   let cmd = matchstr(a:cmd,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
500   call s:ExecuteInTree('!'.git.' '.cmd)
501   call fugitive#reload_status()
502   return matchstr(a:cmd,'\v\C\\@<!%(\\\\)*\|\zs.*')
503 endfunction
504
505 function! s:GitComplete(A,L,P) abort
506   if !exists('s:exec_path')
507     let s:exec_path = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
508   endif
509   let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
510   if a:L =~ ' [[:alnum:]-]\+ '
511     return s:repo().superglob(a:A)
512   elseif a:A == ''
513     return cmds
514   else
515     return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
516   endif
517 endfunction
518
519 " }}}1
520 " Gcd, Glcd {{{1
521
522 function! s:DirComplete(A,L,P) abort
523   let matches = s:repo().dirglob(a:A)
524   return matches
525 endfunction
526
527 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>)`")
528 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>)`")
529
530 " }}}1
531 " Gstatus {{{1
532
533 call s:command("-bar Gstatus :execute s:Status()")
534
535 function! s:Status() abort
536   try
537     Gpedit :
538     wincmd P
539     nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
540   catch /^fugitive:/
541     return 'echoerr v:errmsg'
542   endtry
543   return ''
544 endfunction
545
546 function! fugitive#reload_status() abort
547   let mytab = tabpagenr()
548   for tab in [mytab] + range(1,tabpagenr('$'))
549     for winnr in range(1,tabpagewinnr(tab,'$'))
550       if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
551         execute 'tabnext '.tab
552         if winnr != winnr()
553           execute winnr.'wincmd w'
554           let restorewinnr = 1
555         endif
556         try
557           if !&modified
558             call s:BufReadIndex()
559           endif
560         finally
561           if exists('restorewinnr')
562             wincmd p
563           endif
564           execute 'tabnext '.mytab
565         endtry
566       endif
567     endfor
568   endfor
569 endfunction
570
571 function! s:StageDiff(...) abort
572   let cmd = a:0 ? a:1 : 'Gdiff'
573   let section = getline(search('^# .*:$','bnW'))
574   let line = getline('.')
575   let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
576   if filename ==# '' && section == '# Changes to be committed:'
577     return 'Git diff --cached'
578   elseif filename ==# ''
579     return 'Git diff'
580   elseif line =~# '^#\trenamed:' && filename =~ ' -> '
581     let [old, new] = split(filename,' -> ')
582     execute 'Gedit '.s:fnameescape(':0:'.new)
583     return cmd.' HEAD:'.s:fnameescape(old)
584   elseif section == '# Changes to be committed:'
585     execute 'Gedit '.s:fnameescape(':0:'.filename)
586     return cmd.' -'
587   else
588     execute 'Gedit '.s:fnameescape('/'.filename)
589     return cmd
590   endif
591 endfunction
592
593 function! s:StageToggle(lnum1,lnum2) abort
594   try
595     let output = ''
596     for lnum in range(a:lnum1,a:lnum2)
597       let line = getline(lnum)
598       let repo = s:repo()
599       if line ==# '# Changes to be committed:'
600         call repo.git_chomp_in_tree('reset','-q')
601         silent! edit!
602         1
603         if !search('^# Untracked files:$','W')
604           call search('^# Change','W')
605         endif
606         return ''
607       elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
608         call repo.git_chomp_in_tree('add','-u')
609         silent! edit!
610         1
611         if !search('^# Untracked files:$','W')
612           call search('^# Change','W')
613         endif
614         return ''
615       elseif line ==# '# Untracked files:'
616         " Work around Vim parser idiosyncrasy
617         call repo.git_chomp_in_tree('add','-N','.')
618         silent! edit!
619         1
620         if !search('^# Change\%(d but not updated\|s not staged for commit\):$','W')
621           call search('^# Change','W')
622         endif
623         return ''
624       endif
625       let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (\a\+ [[:alpha:], ]\+)\)\=$')
626       if filename ==# ''
627         continue
628       endif
629       if !exists('first_filename')
630         let first_filename = filename
631       endif
632       execute lnum
633       let section = getline(search('^# .*:$','bnW'))
634       if line =~# '^#\trenamed:' && filename =~ ' -> '
635         let cmd = ['mv','--'] + reverse(split(filename,' -> '))
636         let filename = cmd[-1]
637       elseif section =~? ' to be '
638         let cmd = ['reset','-q','--',filename]
639       elseif line =~# '^#\tdeleted:'
640         let cmd = ['rm','--',filename]
641       else
642         let cmd = ['add','--',filename]
643       endif
644       let output .= call(repo.git_chomp_in_tree,cmd,s:repo())."\n"
645     endfor
646     if exists('first_filename')
647       let jump = first_filename
648       let f = matchstr(getline(a:lnum1-1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
649       if f !=# '' | let jump = f | endif
650       let f = matchstr(getline(a:lnum2+1),'^#\t\%([[:alpha:] ]\+: *\)\=\zs.*')
651       if f !=# '' | let jump = f | endif
652       silent! edit!
653       1
654       redraw
655       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.jump.'\%( (new commits)\)\=\$','W')
656     endif
657     echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
658   catch /^fugitive:/
659     return 'echoerr v:errmsg'
660   endtry
661   return 'checktime'
662 endfunction
663
664 function! s:StagePatch(lnum1,lnum2) abort
665   let add = []
666   let reset = []
667
668   for lnum in range(a:lnum1,a:lnum2)
669     let line = getline(lnum)
670     if line ==# '# Changes to be committed:'
671       return 'Git reset --patch'
672     elseif line =~# '^# Change\%(d but not updated\|s not staged for commit\):$'
673       return 'Git add --patch'
674     endif
675     let filename = matchstr(line,'^#\t\%([[:alpha:] ]\+: *\)\=\zs.\{-\}\ze\%( (new commits)\)\=$')
676     if filename ==# ''
677       continue
678     endif
679     if !exists('first_filename')
680       let first_filename = filename
681     endif
682     execute lnum
683     let section = getline(search('^# .*:$','bnW'))
684     if line =~# '^#\trenamed:' && filename =~ ' -> '
685       let reset += [split(filename,' -> ')[1]]
686     elseif section =~? ' to be '
687       let reset += [filename]
688     elseif line !~# '^#\tdeleted:'
689       let add += [filename]
690     endif
691   endfor
692   try
693     if !empty(add)
694       execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
695     endif
696     if !empty(reset)
697       execute "Git reset --patch -- ".join(map(add,'s:shellesc(v:val)'))
698     endif
699     if exists('first_filename')
700       silent! edit!
701       1
702       redraw
703       call search('^#\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( (new commits)\)\=\$','W')
704     endif
705   catch /^fugitive:/
706     return 'echoerr v:errmsg'
707   endtry
708   return 'checktime'
709 endfunction
710
711 " }}}1
712 " Gcommit {{{1
713
714 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit(<q-args>)")
715
716 function! s:Commit(args) abort
717   let old_type = s:buffer().type()
718   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
719   let dir = getcwd()
720   let msgfile = s:repo().dir('COMMIT_EDITMSG')
721   let outfile = tempname()
722   let errorfile = tempname()
723   try
724     execute cd.'`=s:repo().tree()`'
725     if &shell =~# 'cmd'
726       let command = ''
727       let old_editor = $GIT_EDITOR
728       let $GIT_EDITOR = 'false'
729     else
730       let command = 'env GIT_EDITOR=false '
731     endif
732     let command .= s:repo().git_command('commit').' '.a:args
733     if &shell =~# 'csh'
734       silent execute '!('.command.' > '.outfile.') >& '.errorfile
735     elseif a:args =~# '\%(^\| \)--interactive\>'
736       execute '!'.command.' 2> '.errorfile
737     else
738       silent execute '!'.command.' > '.outfile.' 2> '.errorfile
739     endif
740     if !v:shell_error
741       if filereadable(outfile)
742         for line in readfile(outfile)
743           echo line
744         endfor
745       endif
746       return ''
747     else
748       let errors = readfile(errorfile)
749       let error = get(errors,-2,get(errors,-1,'!'))
750       if error =~# '\<false''\=\.$'
751         let args = a:args
752         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[se]|--edit|--interactive)%($| )','')
753         let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
754         let args = s:gsub(args,'%(^| )@<=[%#]%(:\w)*','\=expand(submatch(0))')
755         let args = '-F '.s:shellesc(msgfile).' '.args
756         if args !~# '\%(^\| \)--cleanup\>'
757           let args = '--cleanup=strip '.args
758         endif
759         let old_nr = bufnr('')
760         if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
761           edit `=msgfile`
762         else
763           keepalt split `=msgfile`
764         endif
765         if old_type ==# 'index'
766           execute 'bdelete '.old_nr
767         endif
768         let b:fugitive_commit_arguments = args
769         setlocal bufhidden=delete filetype=gitcommit
770         return '1'
771       elseif error ==# '!'
772         return s:Status()
773       else
774         call s:throw(error)
775       endif
776     endif
777   catch /^fugitive:/
778     return 'echoerr v:errmsg'
779   finally
780     if exists('old_editor')
781       let $GIT_EDITOR = old_editor
782     endif
783     call delete(outfile)
784     call delete(errorfile)
785     execute cd.'`=dir`'
786     call fugitive#reload_status()
787   endtry
788 endfunction
789
790 function! s:CommitComplete(A,L,P) abort
791   if a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
792     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']
793     return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
794   else
795     return s:repo().superglob(a:A)
796   endif
797 endfunction
798
799 function! s:FinishCommit()
800   let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
801   if !empty(args)
802     call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
803     return s:Commit(args)
804   endif
805   return ''
806 endfunction
807
808 augroup fugitive_commit
809   autocmd!
810   autocmd VimLeavePre,BufDelete *.git/COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
811 augroup END
812
813 " }}}1
814 " Ggrep, Glog {{{1
815
816 if !exists('g:fugitive_summary_format')
817   let g:fugitive_summary_format = '%s'
818 endif
819
820 call s:command("-bang -nargs=? -complete=customlist,s:EditComplete Ggrep :execute s:Grep(<bang>0,<q-args>)")
821 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditComplete Glog :execute s:Log('grep<bang>',<f-args>)")
822
823 function! s:Grep(bang,arg) abort
824   let grepprg = &grepprg
825   let grepformat = &grepformat
826   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
827   let dir = getcwd()
828   try
829     execute cd.'`=s:repo().tree()`'
830     let &grepprg = s:repo().git_command('--no-pager', 'grep', '-n')
831     let &grepformat = '%f:%l:%m'
832     exe 'grep! '.escape(matchstr(a:arg,'\v\C.{-}%($|[''" ]\@=\|)@='),'|')
833     let list = getqflist()
834     for entry in list
835       if bufname(entry.bufnr) =~ ':'
836         let entry.filename = s:repo().translate(bufname(entry.bufnr))
837         unlet! entry.bufnr
838       elseif a:arg =~# '\%(^\| \)--cached\>'
839         let entry.filename = s:repo().translate(':0:'.bufname(entry.bufnr))
840         unlet! entry.bufnr
841       endif
842     endfor
843     call setqflist(list,'r')
844     if !a:bang && !empty(list)
845       return 'cfirst'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
846     else
847       return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
848     endif
849   finally
850     let &grepprg = grepprg
851     let &grepformat = grepformat
852     execute cd.'`=dir`'
853   endtry
854 endfunction
855
856 function! s:Log(cmd,...)
857   let path = s:buffer().path('/')
858   if path =~# '^/\.git\%(/\|$\)' || index(a:000,'--') != -1
859     let path = ''
860   endif
861   let cmd = ['--no-pager', 'log', '--no-color']
862   let cmd += [escape('--pretty=format:fugitive://'.s:repo().dir().'//%H'.path.'::'.g:fugitive_summary_format,'%')]
863   if empty(filter(a:000[0 : index(a:000,'--')],'v:val !~# "^-"'))
864     if s:buffer().commit() =~# '\x\{40\}'
865       let cmd += [s:buffer().commit()]
866     elseif s:buffer().path() =~# '^\.git/refs/\|^\.git/.*HEAD$'
867       let cmd += [s:buffer().path()[5:-1]]
868     endif
869   end
870   let cmd += map(copy(a:000),'s:sub(v:val,"^\\%(%(:\\w)*)","\\=fnamemodify(s:buffer().path(),submatch(1))")')
871   if path =~# '/.'
872     let cmd += ['--',path[1:-1]]
873   endif
874   let grepformat = &grepformat
875   let grepprg = &grepprg
876   let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
877   let dir = getcwd()
878   try
879     execute cd.'`=s:repo().tree()`'
880     let &grepprg = call(s:repo().git_command,cmd,s:repo())
881     let &grepformat = '%f::%m'
882     exe a:cmd
883   finally
884     let &grepformat = grepformat
885     let &grepprg = grepprg
886     execute cd.'`=dir`'
887   endtry
888 endfunction
889
890 " }}}1
891 " Gedit, Gpedit, Gsplit, Gvsplit, Gtabedit, Gread {{{1
892
893 function! s:Edit(cmd,...) abort
894   if a:0 && a:1 == ''
895     return ''
896   elseif a:0
897     let file = s:buffer().expand(a:1)
898   elseif s:buffer().commit() ==# '' && s:buffer().path('/') !~# '^/.git\>'
899     let file = s:buffer().path(':')
900   else
901     let file = s:buffer().path('/')
902   endif
903   try
904     let file = s:repo().translate(file)
905   catch /^fugitive:/
906     return 'echoerr v:errmsg'
907   endtry
908   if a:cmd ==# 'read'
909     return 'silent %delete_|read '.s:fnameescape(file).'|silent 1delete_|diffupdate|'.line('.')
910   else
911     if &previewwindow && getbufvar('','fugitive_type') ==# 'index'
912       wincmd p
913     endif
914     return a:cmd.' '.s:fnameescape(file)
915   endif
916 endfunction
917
918 function! s:EditComplete(A,L,P) abort
919   return s:repo().superglob(a:A)
920 endfunction
921
922 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Ge       :execute s:Edit('edit<bang>',<f-args>)")
923 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gedit    :execute s:Edit('edit<bang>',<f-args>)")
924 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gpedit   :execute s:Edit('pedit<bang>',<f-args>)")
925 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gsplit   :execute s:Edit('split<bang>',<f-args>)")
926 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gvsplit  :execute s:Edit('vsplit<bang>',<f-args>)")
927 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gtabedit :execute s:Edit('tabedit<bang>',<f-args>)")
928 call s:command("-bar -bang -nargs=? -count -complete=customlist,s:EditComplete Gread :execute s:Edit((!<count> && <line1> ? '' : <count>).'read<bang>',<f-args>)")
929
930 " }}}1
931 " Gwrite, Gwq {{{1
932
933 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwrite :execute s:Write(<bang>0,<f-args>)")
934 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gw :execute s:Write(<bang>0,<f-args>)")
935 call s:command("-bar -bang -nargs=? -complete=customlist,s:EditComplete Gwq :execute s:Wq(<bang>0,<f-args>)")
936
937 function! s:Write(force,...) abort
938   if exists('b:fugitive_commit_arguments')
939     return 'write|bdelete'
940   elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
941     return 'wq'
942   elseif s:buffer().type() == 'index'
943     return 'Gcommit'
944   endif
945   let mytab = tabpagenr()
946   let mybufnr = bufnr('')
947   let path = a:0 ? a:1 : s:buffer().path()
948   if path =~# '^:\d\>'
949     return 'write'.(a:force ? '! ' : ' ').s:fnameescape(s:repo().translate(s:buffer().expand(path)))
950   endif
951   let always_permitted = (s:buffer().path() ==# path && s:buffer().commit() =~# '^0\=$')
952   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) !=# ''
953     let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
954     return 'echoerr v:errmsg'
955   endif
956   let file = s:repo().translate(path)
957   let treebufnr = 0
958   for nr in range(1,bufnr('$'))
959     if fnamemodify(bufname(nr),':p') ==# file
960       let treebufnr = nr
961     endif
962   endfor
963
964   if treebufnr > 0 && treebufnr != bufnr('')
965     let temp = tempname()
966     silent execute '%write '.temp
967     for tab in [mytab] + range(1,tabpagenr('$'))
968       for winnr in range(1,tabpagewinnr(tab,'$'))
969         if tabpagebuflist(tab)[winnr-1] == treebufnr
970           execute 'tabnext '.tab
971           if winnr != winnr()
972             execute winnr.'wincmd w'
973             let restorewinnr = 1
974           endif
975           try
976             let lnum = line('.')
977             let last = line('$')
978             silent execute '$read '.temp
979             silent execute '1,'.last.'delete_'
980             silent write!
981             silent execute lnum
982             let did = 1
983           finally
984             if exists('restorewinnr')
985               wincmd p
986             endif
987             execute 'tabnext '.mytab
988           endtry
989         endif
990       endfor
991     endfor
992     if !exists('did')
993       call writefile(readfile(temp,'b'),file,'b')
994     endif
995   else
996     execute 'write! '.s:fnameescape(s:repo().translate(path))
997   endif
998
999   if a:force
1000     let error = s:repo().git_chomp_in_tree('add', '--force', file)
1001   else
1002     let error = s:repo().git_chomp_in_tree('add', file)
1003   endif
1004   if v:shell_error
1005     let v:errmsg = 'fugitive: '.error
1006     return 'echoerr v:errmsg'
1007   endif
1008   if s:buffer().path() ==# path && s:buffer().commit() =~# '^\d$'
1009     set nomodified
1010   endif
1011
1012   let one = s:repo().translate(':1:'.path)
1013   let two = s:repo().translate(':2:'.path)
1014   let three = s:repo().translate(':3:'.path)
1015   for nr in range(1,bufnr('$'))
1016     if bufloaded(nr) && !getbufvar(nr,'&modified') && (bufname(nr) == one || bufname(nr) == two || bufname(nr) == three)
1017       execute nr.'bdelete'
1018     endif
1019   endfor
1020
1021   unlet! restorewinnr
1022   let zero = s:repo().translate(':0:'.path)
1023   for tab in range(1,tabpagenr('$'))
1024     for winnr in range(1,tabpagewinnr(tab,'$'))
1025       let bufnr = tabpagebuflist(tab)[winnr-1]
1026       let bufname = bufname(bufnr)
1027       if bufname ==# zero && bufnr != mybufnr
1028         execute 'tabnext '.tab
1029         if winnr != winnr()
1030           execute winnr.'wincmd w'
1031           let restorewinnr = 1
1032         endif
1033         try
1034           let lnum = line('.')
1035           let last = line('$')
1036           silent $read `=file`
1037           silent execute '1,'.last.'delete_'
1038           silent execute lnum
1039           set nomodified
1040           diffupdate
1041         finally
1042           if exists('restorewinnr')
1043             wincmd p
1044           endif
1045           execute 'tabnext '.mytab
1046         endtry
1047         break
1048       endif
1049     endfor
1050   endfor
1051   call fugitive#reload_status()
1052   return 'checktime'
1053 endfunction
1054
1055 function! s:Wq(force,...) abort
1056   let bang = a:force ? '!' : ''
1057   if exists('b:fugitive_commit_arguments')
1058     return 'wq'.bang
1059   endif
1060   let result = call(s:function('s:Write'),[a:force]+a:000)
1061   if result =~# '^\%(write\|wq\|echoerr\)'
1062     return s:sub(result,'^write','wq')
1063   else
1064     return result.'|quit'.bang
1065   endif
1066 endfunction
1067
1068 " }}}1
1069 " Gdiff {{{1
1070
1071 call s:command("-bang -bar -nargs=? -complete=customlist,s:EditComplete Gdiff :execute s:Diff(<bang>0,<f-args>)")
1072 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gvdiff :execute s:Diff(0,<f-args>)")
1073 call s:command("-bar -nargs=? -complete=customlist,s:EditComplete Gsdiff :execute s:Diff(1,<f-args>)")
1074
1075 augroup fugitive_diff
1076   autocmd!
1077   autocmd BufWinLeave * if s:diff_window_count() == 2 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | call s:diff_off_all(getbufvar(+expand('<abuf>'), 'git_dir')) | endif
1078   autocmd BufWinEnter * if s:diff_window_count() == 1 && &diff && getbufvar(+expand('<abuf>'), 'git_dir') !=# '' | diffoff | endif
1079 augroup END
1080
1081 function! s:diff_window_count()
1082   let c = 0
1083   for nr in range(1,winnr('$'))
1084     let c += getwinvar(nr,'&diff')
1085   endfor
1086   return c
1087 endfunction
1088
1089 function! s:diff_off_all(dir)
1090   for nr in range(1,winnr('$'))
1091     if getwinvar(nr,'&diff')
1092       if nr != winnr()
1093         execute nr.'wincmd w'
1094         let restorewinnr = 1
1095       endif
1096       if exists('b:git_dir') && b:git_dir ==# a:dir
1097         diffoff
1098       endif
1099       if exists('restorewinnr')
1100         wincmd p
1101       endif
1102     endif
1103   endfor
1104 endfunction
1105
1106 function! s:buffer_compare_age(commit) dict abort
1107   let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
1108   let my_score    = get(scores,':'.self.commit(),0)
1109   let their_score = get(scores,':'.a:commit,0)
1110   if my_score || their_score
1111     return my_score < their_score ? -1 : my_score != their_score
1112   elseif self.commit() ==# a:commit
1113     return 0
1114   endif
1115   let base = self.repo().git_chomp('merge-base',self.commit(),a:commit)
1116   if base ==# self.commit()
1117     return -1
1118   elseif base ==# a:commit
1119     return 1
1120   endif
1121   let my_time    = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',self.commit())
1122   let their_time = +self.repo().git_chomp('log','--max-count=1','--pretty=format:%at',a:commit)
1123   return my_time < their_time ? -1 : my_time != their_time
1124 endfunction
1125
1126 call s:add_methods('buffer',['compare_age'])
1127
1128 function! s:Diff(bang,...) abort
1129   let split = a:bang ? 'split' : 'vsplit'
1130   if exists(':DiffGitCached')
1131     return 'DiffGitCached'
1132   elseif (!a:0 || a:1 == ':') && s:buffer().commit() =~# '^[0-1]\=$' && s:repo().git_chomp_in_tree('ls-files', '--unmerged', '--', s:buffer().path()) !=# ''
1133     let nr = bufnr('')
1134     execute 'leftabove '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':2''))`'
1135     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1136     diffthis
1137     wincmd p
1138     execute 'rightbelow '.split.' `=fugitive#buffer().repo().translate(s:buffer().expand('':3''))`'
1139     execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
1140     diffthis
1141     wincmd p
1142     diffthis
1143     return ''
1144   elseif a:0
1145     if a:1 ==# ''
1146       return ''
1147     elseif a:1 ==# '/'
1148       let file = s:buffer().path('/')
1149     elseif a:1 ==# ':'
1150       let file = s:buffer().path(':0:')
1151     elseif a:1 =~# '^:/.'
1152       try
1153         let file = s:repo().rev_parse(a:1).s:buffer().path(':')
1154       catch /^fugitive:/
1155         return 'echoerr v:errmsg'
1156       endtry
1157     else
1158       let file = s:buffer().expand(a:1)
1159     endif
1160     if file !~# ':' && file !~# '^/' && s:repo().git_chomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
1161       let file = file.s:buffer().path(':')
1162     endif
1163   else
1164     let file = s:buffer().path(s:buffer().commit() == '' ? ':0:' : '/')
1165   endif
1166   try
1167     let spec = s:repo().translate(file)
1168     let commit = matchstr(spec,'\C[^:/]//\zs\x\+')
1169     if s:buffer().compare_age(commit) < 0
1170       execute 'rightbelow '.split.' `=spec`'
1171     else
1172       execute 'leftabove '.split.' `=spec`'
1173     endif
1174     diffthis
1175     wincmd p
1176     diffthis
1177     return ''
1178   catch /^fugitive:/
1179     return 'echoerr v:errmsg'
1180   endtry
1181 endfunction
1182
1183 " }}}1
1184 " Gmove, Gremove {{{1
1185
1186 function! s:Move(force,destination)
1187   if a:destination =~# '^/'
1188     let destination = a:destination[1:-1]
1189   else
1190     let destination = fnamemodify(s:sub(a:destination,'[%#]%(:\w)*','\=expand(submatch(0))'),':p')
1191     if destination[0:strlen(s:repo().tree())] ==# s:repo().tree('')
1192       let destination = destination[strlen(s:repo().tree('')):-1]
1193     endif
1194   endif
1195   if isdirectory(s:buffer().name())
1196     " Work around Vim parser idiosyncrasy
1197     let discarded = s:buffer().setvar('&swapfile',0)
1198   endif
1199   let message = call(s:repo().git_chomp_in_tree,['mv']+(a:force ? ['-f'] : [])+['--', s:buffer().path(), destination], s:repo())
1200   if v:shell_error
1201     let v:errmsg = 'fugitive: '.message
1202     return 'echoerr v:errmsg'
1203   endif
1204   let destination = s:repo().tree(destination)
1205   if isdirectory(destination)
1206     let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
1207   endif
1208   call fugitive#reload_status()
1209   if s:buffer().commit() == ''
1210     if isdirectory(destination)
1211       return 'edit '.s:fnameescape(destination)
1212     else
1213       return 'saveas! '.s:fnameescape(destination)
1214     endif
1215   else
1216     return 'file '.s:fnameescape(s:repo().translate(':0:'.destination)
1217   endif
1218 endfunction
1219
1220 function! s:MoveComplete(A,L,P)
1221   if a:A =~ '^/'
1222     return s:repo().superglob(a:A)
1223   else
1224     let matches = split(glob(a:A.'*'),"\n")
1225     call map(matches,'v:val !~ "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1226     return matches
1227   endif
1228 endfunction
1229
1230 function! s:Remove(force)
1231   if s:buffer().commit() ==# ''
1232     let cmd = ['rm']
1233   elseif s:buffer().commit() ==# '0'
1234     let cmd = ['rm','--cached']
1235   else
1236     let v:errmsg = 'fugitive: rm not supported here'
1237     return 'echoerr v:errmsg'
1238   endif
1239   if a:force
1240     let cmd += ['--force']
1241   endif
1242   let message = call(s:repo().git_chomp_in_tree,cmd+['--',s:buffer().path()],s:repo())
1243   if v:shell_error
1244     let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
1245     return 'echoerr '.string(v:errmsg)
1246   else
1247     call fugitive#reload_status()
1248     return 'bdelete'.(a:force ? '!' : '')
1249   endif
1250 endfunction
1251
1252 augroup fugitive_remove
1253   autocmd!
1254   autocmd User Fugitive if s:buffer().commit() =~# '^0\=$' |
1255         \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:MoveComplete Gmove :execute s:Move(<bang>0,<q-args>)" |
1256         \ exe "command! -buffer -bar -bang Gremove :execute s:Remove(<bang>0)" |
1257         \ endif
1258 augroup END
1259
1260 " }}}1
1261 " Gblame {{{1
1262
1263 augroup fugitive_blame
1264   autocmd!
1265   autocmd BufReadPost *.fugitiveblame setfiletype fugitiveblame
1266   autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:repo().keywordprg() | endif
1267   autocmd Syntax fugitiveblame call s:BlameSyntax()
1268   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
1269 augroup END
1270
1271 function! s:Blame(bang,line1,line2,count,args) abort
1272   try
1273     if s:buffer().path() == ''
1274       call s:throw('file or blob required')
1275     endif
1276     if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltwfs]\\|[MC]\\d*\\)\\+\\)$"') != []
1277       call s:throw('unsupported option')
1278     endif
1279     call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
1280     let git_dir = s:repo().dir()
1281     let cmd = ['--no-pager', 'blame', '--show-number'] + a:args
1282     if s:buffer().commit() =~# '\D\|..'
1283       let cmd += [s:buffer().commit()]
1284     else
1285       let cmd += ['--contents', '-']
1286     endif
1287     let basecmd = call(s:repo().git_command,cmd+['--',s:buffer().path()],s:repo())
1288     try
1289       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1290       if !s:repo().bare()
1291         let dir = getcwd()
1292         execute cd.'`=s:repo().tree()`'
1293       endif
1294       if a:count
1295         execute 'write !'.substitute(basecmd,' blame ',' blame -L '.a:line1.','.a:line2.' ','g')
1296       else
1297         let error = tempname()
1298         let temp = error.'.fugitiveblame'
1299         if &shell =~# 'csh'
1300           silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
1301         else
1302           silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
1303         endif
1304         if exists('l:dir')
1305           execute cd.'`=dir`'
1306           unlet dir
1307         endif
1308         if v:shell_error
1309           call s:throw(join(readfile(error),"\n"))
1310         endif
1311         let bufnr = bufnr('')
1312         let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
1313         if &l:wrap
1314           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
1315         endif
1316         if &l:foldenable
1317           let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
1318         endif
1319         let winnr = winnr()
1320         windo set noscrollbind
1321         exe winnr.'wincmd w'
1322         setlocal scrollbind nowrap nofoldenable
1323         let top = line('w0') + &scrolloff
1324         let current = line('.')
1325         exe 'leftabove vsplit '.temp
1326         let b:git_dir = git_dir
1327         let b:fugitive_type = 'blame'
1328         let b:fugitive_blamed_bufnr = bufnr
1329         let w:fugitive_restore = restore
1330         let b:fugitive_blame_arguments = join(a:args,' ')
1331         call s:Detect(expand('%:p'))
1332         execute top
1333         normal! zt
1334         execute current
1335         execute "vertical resize ".(match(getline('.'),'\s\+\d\+)')+1)
1336         setlocal nomodified nomodifiable bufhidden=delete nonumber scrollbind nowrap foldcolumn=0 nofoldenable filetype=fugitiveblame
1337         nnoremap <buffer> <silent> q    :<C-U>bdelete<CR>
1338         nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameJump('')<CR>
1339         nnoremap <buffer> <silent> P    :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
1340         nnoremap <buffer> <silent> ~    :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
1341         nnoremap <buffer> <silent> o    :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft")." split", matchstr(getline('.'),'\x\+'))<CR>
1342         nnoremap <buffer> <silent> O    :<C-U>exe <SID>Edit("tabedit", matchstr(getline('.'),'\x\+'))<CR>
1343         syncbind
1344       endif
1345     finally
1346       if exists('l:dir')
1347         execute cd.'`=dir`'
1348       endif
1349     endtry
1350     return ''
1351   catch /^fugitive:/
1352     return 'echoerr v:errmsg'
1353   endtry
1354 endfunction
1355
1356 function! s:BlameJump(suffix) abort
1357   let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
1358   if commit =~# '^0\+$'
1359     let commit = ':0'
1360   endif
1361   let lnum = matchstr(getline('.'),'\d\+\ze\s\+[([:digit:]]')
1362   let path = matchstr(getline('.'),'^\^\=\zs\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
1363   if path ==# ''
1364     let path = s:buffer(b:fugitive_blamed_bufnr).path()
1365   endif
1366   let args = b:fugitive_blame_arguments
1367   let offset = line('.') - line('w0')
1368   let bufnr = bufnr('%')
1369   let winnr = bufwinnr(b:fugitive_blamed_bufnr)
1370   if winnr > 0
1371     exe winnr.'wincmd w'
1372   endif
1373   execute s:Edit('edit',commit.a:suffix.':'.path)
1374   if winnr > 0
1375     exe bufnr.'bdelete'
1376   endif
1377   execute 'Gblame '.args
1378   execute lnum
1379   let delta = line('.') - line('w0') - offset
1380   if delta > 0
1381     execute 'norm! 'delta."\<C-E>"
1382   elseif delta < 0
1383     execute 'norm! '(-delta)."\<C-Y>"
1384   endif
1385   syncbind
1386   return ''
1387 endfunction
1388
1389 function! s:BlameSyntax() abort
1390   let b:current_syntax = 'fugitiveblame'
1391   syn match FugitiveblameBoundary "^\^"
1392   syn match FugitiveblameBlank                      "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
1393   syn match FugitiveblameHash       "\%(^\^\=\)\@<=\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1394   syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
1395   syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
1396   syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
1397   syn match FugitiveblameLineNumber         " \@<=\d\+)\@=" contained containedin=FugitiveblameAnnotation
1398   syn match FugitiveblameOriginalFile       " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite
1399   syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite
1400   syn match FugitiveblameOriginalLineNumber " \@<=\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite
1401   syn match FugitiveblameShort              "\d\+)" contained contains=FugitiveblameLineNumber
1402   syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
1403   hi def link FugitiveblameBoundary           Keyword
1404   hi def link FugitiveblameHash               Identifier
1405   hi def link FugitiveblameUncommitted        Function
1406   hi def link FugitiveblameTime               PreProc
1407   hi def link FugitiveblameLineNumber         Number
1408   hi def link FugitiveblameOriginalFile       String
1409   hi def link FugitiveblameOriginalLineNumber Float
1410   hi def link FugitiveblameShort              FugitiveblameDelimiter
1411   hi def link FugitiveblameDelimiter          Delimiter
1412   hi def link FugitiveblameNotCommittedYet    Comment
1413 endfunction
1414
1415 " }}}1
1416 " Gbrowse {{{1
1417
1418 call s:command("-bar -bang -count=0 -nargs=? -complete=customlist,s:EditComplete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
1419
1420 function! s:Browse(bang,line1,count,...) abort
1421   try
1422     let rev = a:0 ? substitute(a:1,'@[[:alnum:]_-]*\%(://.\{-\}\)\=$','','') : ''
1423     if rev ==# ''
1424       let expanded = s:buffer().rev()
1425     elseif rev ==# ':'
1426       let expanded = s:buffer().path('/')
1427     else
1428       let expanded = s:buffer().expand(rev)
1429     endif
1430     let full = s:repo().translate(expanded)
1431     let commit = ''
1432     if full =~# '^fugitive://'
1433       let commit = matchstr(full,'://.*//\zs\w\+')
1434       let path = matchstr(full,'://.*//\w\+\zs/.*')
1435       if commit =~ '..'
1436         let type = s:repo().git_chomp('cat-file','-t',commit.s:sub(path,'^/',':'))
1437       else
1438         let type = 'blob'
1439       endif
1440       let path = path[1:-1]
1441     elseif s:repo().bare()
1442       let path = '.git/' . full[strlen(s:repo().dir())+1:-1]
1443       let type = ''
1444     else
1445       let path = full[strlen(s:repo().tree())+1:-1]
1446       if path =~# '^\.git/'
1447         let type = ''
1448       elseif isdirectory(full)
1449         let type = 'tree'
1450       else
1451         let type = 'blob'
1452       endif
1453     endif
1454     if path =~# '^\.git/.*HEAD' && filereadable(s:repo().dir(path[5:-1]))
1455       let body = readfile(s:repo().dir(path[5:-1]))[0]
1456       if body =~# '^\x\{40\}$'
1457         let commit = body
1458         let type = 'commit'
1459         let path = ''
1460       elseif body =~# '^ref: refs/'
1461         let path = '.git/' . matchstr(body,'ref: \zs.*')
1462       endif
1463     endif
1464
1465     if a:0 && a:1 =~# '@[[:alnum:]_-]*\%(://.\{-\}\)\=$'
1466       let remote = matchstr(a:1,'@\zs[[:alnum:]_-]\+\%(://.\{-\}\)\=$')
1467     elseif path =~# '^\.git/refs/remotes/.'
1468       let remote = matchstr(path,'^\.git/refs/remotes/\zs[^/]\+')
1469     else
1470       let remote = 'origin'
1471       let branch = matchstr(rev,'^[[:alnum:]/._-]\+\ze[:^~@]')
1472       if branch ==# '' && path =~# '^\.git/refs/\w\+/'
1473         let branch = s:sub(path,'^\.git/refs/\w+/','')
1474       endif
1475       if filereadable(s:repo().dir('refs/remotes/'.branch))
1476         let remote = matchstr(branch,'[^/]\+')
1477         let rev = rev[strlen(remote)+1:-1]
1478       else
1479         if branch ==# ''
1480           let branch = matchstr(s:repo().head_ref(),'\<refs/heads/\zs.*')
1481         endif
1482         if branch != ''
1483           let remote = s:repo().git_chomp('config','branch.'.branch.'.remote')
1484           if remote ==# ''
1485             let remote = 'origin'
1486           elseif rev[0:strlen(branch)-1] ==# branch && rev[strlen(branch)] =~# '[:^~@]'
1487             let rev = s:repo().git_chomp('config','branch.'.branch.'.merge')[11:-1] . rev[strlen(branch):-1]
1488           endif
1489         endif
1490       endif
1491     endif
1492
1493     let raw = s:repo().git_chomp('config','remote.'.remote.'.url')
1494     if raw ==# ''
1495       let raw = remote
1496     endif
1497
1498     let url = s:github_url(s:repo(),raw,rev,commit,path,type,a:line1,a:count)
1499     if url == ''
1500       let url = s:instaweb_url(s:repo(),rev,commit,path,type,a:count ? a:line1 : 0)
1501     endif
1502
1503     if url == ''
1504       call s:throw("Instaweb failed to start and '".remote."' is not a GitHub remote")
1505     endif
1506
1507     if a:bang
1508       let @* = url
1509       return 'echomsg '.string(url)
1510     else
1511       return 'echomsg '.string(url).'|silent Git web--browse '.shellescape(url,1)
1512     endif
1513   catch /^fugitive:/
1514     return 'echoerr v:errmsg'
1515   endtry
1516 endfunction
1517
1518 function! s:github_url(repo,url,rev,commit,path,type,line1,line2) abort
1519   let path = a:path
1520   let repo_path = matchstr(a:url,'^\%(https\=://\|git://\|git@\)github\.com[/:]\zs.\{-\}\ze\%(\.git\)\=$')
1521   if repo_path ==# ''
1522     return ''
1523   endif
1524   let root = 'https://github.com/' . repo_path
1525   if path =~# '^\.git/refs/heads/'
1526     let branch = a:repo.git_chomp('config','branch.'.path[16:-1].'.merge')[11:-1]
1527     if branch ==# ''
1528       return root . '/commits/' . path[16:-1]
1529     else
1530       return root . '/commits/' . branch
1531     endif
1532   elseif path =~# '^\.git/refs/.'
1533     return root . '/commits/' . matchstr(path,'[^/]\+$')
1534   elseif path =~# '.git/\%(config$\|hooks\>\)'
1535     return root . '/admin'
1536   elseif path =~# '^\.git\>'
1537     return root
1538   endif
1539   if a:rev =~# '^[[:alnum:]._-]\+:'
1540     let commit = matchstr(a:rev,'^[^:]*')
1541   elseif a:commit =~# '^\d\=$'
1542     let local = matchstr(a:repo.head_ref(),'\<refs/heads/\zs.*')
1543     let commit = a:repo.git_chomp('config','branch.'.local.'.merge')[11:-1]
1544     if commit ==# ''
1545       let commit = local
1546     endif
1547   else
1548     let commit = a:commit
1549   endif
1550   if a:type == 'tree'
1551     let url = s:sub(root . '/tree/' . commit . '/' . path,'/$','')
1552   elseif a:type == 'blob'
1553     let url = root . '/blob/' . commit . '/' . path
1554     if a:line2 && a:line1 == a:line2
1555       let url .= '#L' . a:line1
1556     elseif a:line2
1557       let url .= '#L' . a:line1 . '-' . a:line2
1558     endif
1559   elseif a:type == 'tag'
1560     let commit = matchstr(getline(3),'^tag \zs.*')
1561     let url = root . '/tree/' . commit
1562   else
1563     let url = root . '/commit/' . commit
1564   endif
1565   return url
1566 endfunction
1567
1568 function! s:instaweb_url(repo,rev,commit,path,type,...) abort
1569   let output = a:repo.git_chomp('instaweb','-b','unknown')
1570   if output =~# 'http://'
1571     let root = matchstr(output,'http://.*').'/?p='.fnamemodify(a:repo.dir(),':t')
1572   else
1573     return ''
1574   endif
1575   if a:path =~# '^\.git/refs/.'
1576     return root . ';a=shortlog;h=' . matchstr(a:path,'^\.git/\zs.*')
1577   elseif a:path =~# '^\.git\>'
1578     return root
1579   endif
1580   let url = root
1581   if a:commit =~# '^\x\{40\}$'
1582     if a:type ==# 'commit'
1583       let url .= ';a=commit'
1584     endif
1585     let url .= ';h=' . a:repo.rev_parse(a:commit . (a:path == '' ? '' : ':' . a:path))
1586   else
1587     if a:type ==# 'blob'
1588       let tmp = tempname()
1589       silent execute 'write !'.a:repo.git_command('hash-object','-w','--stdin').' > '.tmp
1590       let url .= ';h=' . readfile(tmp)[0]
1591     else
1592       try
1593         let url .= ';h=' . a:repo.rev_parse((a:commit == '' ? 'HEAD' : ':' . a:commit) . ':' . a:path)
1594       catch /^fugitive:/
1595         call s:throw('fugitive: cannot browse uncommitted file')
1596       endtry
1597     endif
1598     let root .= ';hb=' . matchstr(a:repo.head_ref(),'[^ ]\+$')
1599   endif
1600   if a:path !=# ''
1601     let url .= ';f=' . a:path
1602   endif
1603   if a:0 && a:1
1604     let url .= '#l' . a:1
1605   endif
1606   return url
1607 endfunction
1608
1609 " }}}1
1610 " File access {{{1
1611
1612 function! s:ReplaceCmd(cmd,...) abort
1613   let fn = bufname('')
1614   let tmp = tempname()
1615   let aw = &autowrite
1616   let prefix = ''
1617   try
1618     if a:0 && a:1 != ''
1619       if &shell =~# 'cmd'
1620         let old_index = $GIT_INDEX_FILE
1621         let $GIT_INDEX_FILE = a:1
1622       else
1623         let prefix = 'env GIT_INDEX_FILE='.s:shellesc(a:1).' '
1624       endif
1625     endif
1626     set noautowrite
1627     silent exe '!'.escape(prefix.a:cmd,'%#').' > '.tmp
1628   finally
1629     let &autowrite = aw
1630     if exists('old_index')
1631       let $GIT_INDEX_FILE = old_index
1632     endif
1633   endtry
1634   silent exe 'keepalt file '.tmp
1635   silent edit!
1636   silent exe 'keepalt file '.s:fnameescape(fn)
1637   call delete(tmp)
1638   silent exe 'doau BufReadPost '.s:fnameescape(fn)
1639 endfunction
1640
1641 function! s:BufReadIndex()
1642   if !exists('b:fugitive_display_format')
1643     let b:fugitive_display_format = filereadable(expand('%').'.lock')
1644   endif
1645   let b:fugitive_display_format = b:fugitive_display_format % 2
1646   let b:fugitive_type = 'index'
1647   try
1648     let b:git_dir = s:repo().dir()
1649     setlocal noro ma
1650     if fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p') ==# expand('%:p')
1651       let index = ''
1652     else
1653       let index = expand('%:p')
1654     endif
1655     if b:fugitive_display_format
1656       call s:ReplaceCmd(s:repo().git_command('ls-files','--stage'),index)
1657       set ft=git nospell
1658     else
1659       let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
1660       let dir = getcwd()
1661       try
1662         execute cd.'`=s:repo().tree()`'
1663         call s:ReplaceCmd(s:repo().git_command('status'),index)
1664       finally
1665         execute cd.'`=dir`'
1666       endtry
1667       set ft=gitcommit
1668     endif
1669     setlocal ro noma nomod nomodeline bufhidden=delete
1670     nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe <SID>BufReadIndex()<CR>
1671     nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe <SID>BufReadIndex()<CR>
1672     nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff()<CR>
1673     nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff()<CR>
1674     nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1675     nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1676     nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff()<CR>
1677     nnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>
1678     xnoremap <buffer> <silent> - :<C-U>execute <SID>StageToggle(line("'<"),line("'>"))<CR>
1679     nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1680     xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1681     nnoremap <buffer> <silent> <C-N> :call search('^#\t.*','W')<Bar>.<CR>
1682     nnoremap <buffer> <silent> <C-P> :call search('^#\t.*','Wbe')<Bar>.<CR>
1683     call s:JumpInit()
1684     nunmap   <buffer>          P
1685     nunmap   <buffer>          ~
1686     nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>
1687   catch /^fugitive:/
1688     return 'echoerr v:errmsg'
1689   endtry
1690 endfunction
1691
1692 function! s:FileRead()
1693   try
1694     let repo = s:repo(s:ExtractGitDir(expand('<amatch>')))
1695     let path = s:sub(s:sub(matchstr(expand('<amatch>'),'fugitive://.\{-\}//\zs.*'),'/',':'),'^\d:',':&')
1696     let hash = repo.rev_parse(path)
1697     if path =~ '^:'
1698       let type = 'blob'
1699     else
1700       let type = repo.git_chomp('cat-file','-t',hash)
1701     endif
1702     " TODO: use count, if possible
1703     return "read !".escape(repo.git_command('cat-file',type,hash),'%#\')
1704   catch /^fugitive:/
1705     return 'echoerr v:errmsg'
1706   endtry
1707 endfunction
1708
1709 function! s:BufReadIndexFile()
1710   try
1711     let b:fugitive_type = 'blob'
1712     let b:git_dir = s:repo().dir()
1713     call s:ReplaceCmd(s:repo().git_command('cat-file','blob',s:buffer().sha1()))
1714     return ''
1715   catch /^fugitive: rev-parse/
1716     silent exe 'doau BufNewFile '.s:fnameescape(bufname(''))
1717     return ''
1718   catch /^fugitive:/
1719     return 'echoerr v:errmsg'
1720   endtry
1721 endfunction
1722
1723 function! s:BufWriteIndexFile()
1724   let tmp = tempname()
1725   try
1726     let path = matchstr(expand('<amatch>'),'//\d/\zs.*')
1727     let stage = matchstr(expand('<amatch>'),'//\zs\d')
1728     silent execute 'write !'.s:repo().git_command('hash-object','-w','--stdin').' > '.tmp
1729     let sha1 = readfile(tmp)[0]
1730     let old_mode = matchstr(s:repo().git_chomp('ls-files','--stage',path),'^\d\+')
1731     if old_mode == ''
1732       let old_mode = executable(s:repo().tree(path)) ? '100755' : '100644'
1733     endif
1734     let info = old_mode.' '.sha1.' '.stage."\t".path
1735     call writefile([info],tmp)
1736     if has('win32')
1737       let error = system('type '.tmp.'|'.s:repo().git_command('update-index','--index-info'))
1738     else
1739       let error = system(s:repo().git_command('update-index','--index-info').' < '.tmp)
1740     endif
1741     if v:shell_error == 0
1742       setlocal nomodified
1743       silent execute 'doautocmd BufWritePost '.s:fnameescape(expand('%:p'))
1744       call fugitive#reload_status()
1745       return ''
1746     else
1747       return 'echoerr '.string('fugitive: '.error)
1748     endif
1749   finally
1750     call delete(tmp)
1751   endtry
1752 endfunction
1753
1754 function! s:BufReadObject()
1755   try
1756     setlocal noro ma
1757     let b:git_dir = s:repo().dir()
1758     let hash = s:buffer().sha1()
1759     if !exists("b:fugitive_type")
1760       let b:fugitive_type = s:repo().git_chomp('cat-file','-t',hash)
1761     endif
1762     if b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1763       return "echoerr 'fugitive: unrecognized git type'"
1764     endif
1765     let firstline = getline('.')
1766     if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1767       let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1768     endif
1769
1770     let pos = getpos('.')
1771     silent %delete
1772     setlocal endofline
1773
1774     if b:fugitive_type == 'tree'
1775       let b:fugitive_display_format = b:fugitive_display_format % 2
1776       if b:fugitive_display_format
1777         call s:ReplaceCmd(s:repo().git_command('ls-tree',hash))
1778       else
1779         call s:ReplaceCmd(s:repo().git_command('show',hash))
1780       endif
1781     elseif b:fugitive_type == 'tag'
1782       let b:fugitive_display_format = b:fugitive_display_format % 2
1783       if b:fugitive_display_format
1784         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1785       else
1786         call s:ReplaceCmd(s:repo().git_command('cat-file','-p',hash))
1787       endif
1788     elseif b:fugitive_type == 'commit'
1789       let b:fugitive_display_format = b:fugitive_display_format % 2
1790       if b:fugitive_display_format
1791         call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1792       else
1793         call s:ReplaceCmd(s:repo().git_command('show','--pretty=format:tree %T%nparent %P%nauthor %an <%ae> %ad%ncommitter %cn <%ce> %cd%nencoding %e%n%n%s%n%n%b',hash))
1794         call search('^parent ')
1795         if getline('.') ==# 'parent '
1796           silent delete_
1797         else
1798           silent s/\%(^parent\)\@<! /\rparent /ge
1799         endif
1800         if search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1801           silent delete_
1802         end
1803         1
1804       endif
1805     elseif b:fugitive_type ==# 'blob'
1806       call s:ReplaceCmd(s:repo().git_command('cat-file',b:fugitive_type,hash))
1807     endif
1808     call setpos('.',pos)
1809     setlocal ro noma nomod nomodeline
1810     if b:fugitive_type !=# 'blob'
1811       set filetype=git
1812       nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe <SID>BufReadObject()<CR>
1813       nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe <SID>BufReadObject()<CR>
1814     else
1815       call s:JumpInit()
1816     endif
1817
1818     return ''
1819   catch /^fugitive:/
1820     return 'echoerr v:errmsg'
1821   endtry
1822 endfunction
1823
1824 augroup fugitive_files
1825   autocmd!
1826   autocmd BufReadCmd  *.git/index                      exe s:BufReadIndex()
1827   autocmd BufReadCmd  *.git/*index*.lock               exe s:BufReadIndex()
1828   autocmd FileReadCmd fugitive://**//[0-3]/**          exe s:FileRead()
1829   autocmd BufReadCmd  fugitive://**//[0-3]/**          exe s:BufReadIndexFile()
1830   autocmd BufWriteCmd fugitive://**//[0-3]/**          exe s:BufWriteIndexFile()
1831   autocmd BufReadCmd  fugitive://**//[0-9a-f][0-9a-f]* exe s:BufReadObject()
1832   autocmd FileReadCmd fugitive://**//[0-9a-f][0-9a-f]* exe s:FileRead()
1833   autocmd FileType git       call s:JumpInit()
1834 augroup END
1835
1836 " }}}1
1837 " Go to file {{{1
1838
1839 function! s:JumpInit() abort
1840   nnoremap <buffer> <silent> <CR>    :<C-U>exe <SID>GF("edit")<CR>
1841   if !&modifiable
1842     nnoremap <buffer> <silent> o     :<C-U>exe <SID>GF("split")<CR>
1843     nnoremap <buffer> <silent> O     :<C-U>exe <SID>GF("tabedit")<CR>
1844     nnoremap <buffer> <silent> P     :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'^'.v:count1.<SID>buffer().path(':'))<CR>
1845     nnoremap <buffer> <silent> ~     :<C-U>exe <SID>Edit('edit',<SID>buffer().commit().'~'.v:count1.<SID>buffer().path(':'))<CR>
1846     nnoremap <buffer> <silent> C     :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1847     nnoremap <buffer> <silent> cc    :<C-U>exe <SID>Edit('edit',<SID>buffer().containing_commit())<CR>
1848     nnoremap <buffer> <silent> co    :<C-U>exe <SID>Edit('split',<SID>buffer().containing_commit())<CR>
1849     nnoremap <buffer> <silent> cO    :<C-U>exe <SID>Edit('tabedit',<SID>buffer().containing_commit())<CR>
1850     nnoremap <buffer> <silent> cp    :<C-U>exe <SID>Edit('pedit',<SID>buffer().containing_commit())<CR>
1851   endif
1852 endfunction
1853
1854 function! s:GF(mode) abort
1855   try
1856     let buffer = s:buffer()
1857     let myhash = buffer.sha1()
1858
1859     if buffer.type('tree')
1860       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1861       if showtree && line('.') == 1
1862         return ""
1863       elseif showtree && line('.') > 2
1864         return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(getline('.'),'/$',''))
1865       elseif getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
1866         return s:Edit(a:mode,buffer.commit().':'.s:buffer().path().(buffer.path() =~# '^$\|/$' ? '' : '/').s:sub(matchstr(getline('.'),'\t\zs.*'),'/$',''))
1867       endif
1868
1869     elseif buffer.type('blob')
1870       let ref = expand("<cfile>")
1871       try
1872         let sha1 = buffer.repo().rev_parse(ref)
1873       catch /^fugitive:/
1874       endtry
1875       if exists('sha1')
1876         return s:Edit(a:mode,ref)
1877       endif
1878
1879     else
1880
1881       " Index
1882       if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
1883         let ref = matchstr(getline('.'),'\x\{40\}')
1884         let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
1885         return s:Edit(a:mode,file)
1886
1887       elseif getline('.') =~# '^#\trenamed:.* -> '
1888         let file = '/'.matchstr(getline('.'),' -> \zs.*')
1889         return s:Edit(a:mode,file)
1890       elseif getline('.') =~# '^#\t[[:alpha:] ]\+: *.'
1891         let file = '/'.matchstr(getline('.'),': *\zs.\{-\}\ze\%( (new commits)\)\=$')
1892         return s:Edit(a:mode,file)
1893       elseif getline('.') =~# '^#\t.'
1894         let file = '/'.matchstr(getline('.'),'#\t\zs.*')
1895         return s:Edit(a:mode,file)
1896       elseif getline('.') =~# ': needs merge$'
1897         let file = '/'.matchstr(getline('.'),'.*\ze: needs merge$')
1898         return s:Edit(a:mode,file).'|Gdiff'
1899
1900       elseif getline('.') ==# '# Not currently on any branch.'
1901         return s:Edit(a:mode,'HEAD')
1902       elseif getline('.') =~# '^# On branch '
1903         let file = 'refs/heads/'.getline('.')[12:]
1904         return s:Edit(a:mode,file)
1905       elseif getline('.') =~# "^# Your branch .*'"
1906         let file = matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
1907         return s:Edit(a:mode,file)
1908       endif
1909
1910       let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
1911
1912       if getline('.') =~# '^ref: '
1913         let ref = strpart(getline('.'),5)
1914
1915       elseif getline('.') =~# '^parent \x\{40\}\>'
1916         let ref = matchstr(getline('.'),'\x\{40\}')
1917         let line = line('.')
1918         let parent = 0
1919         while getline(line) =~# '^parent '
1920           let parent += 1
1921           let line -= 1
1922         endwhile
1923         return s:Edit(a:mode,ref)
1924
1925       elseif getline('.') =~ '^tree \x\{40\}$'
1926         let ref = matchstr(getline('.'),'\x\{40\}')
1927         if s:repo().rev_parse(myhash.':') == ref
1928           let ref = myhash.':'
1929         endif
1930         return s:Edit(a:mode,ref)
1931
1932       elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
1933         let ref = matchstr(getline('.'),'\x\{40\}')
1934         let type = matchstr(getline(line('.')+1),'type \zs.*')
1935
1936       elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
1937         return ''
1938
1939       elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
1940         let ref = matchstr(getline('.'),'\x\{40\}')
1941         echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
1942
1943       elseif getline('.') =~# '^[+-]\{3\} [ab/]'
1944         let ref = getline('.')[4:]
1945
1946       elseif getline('.') =~# '^rename from '
1947         let ref = 'a/'.getline('.')[12:]
1948       elseif getline('.') =~# '^rename to '
1949         let ref = 'b/'.getline('.')[10:]
1950
1951       elseif getline('.') =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1952         let dref = matchstr(getline('.'),'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1953         let ref = matchstr(getline('.'),'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1954         let dcmd = 'Gdiff'
1955
1956       elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%(a/.*\|/dev/null\) \%(b/.*\|/dev/null\)'
1957         let line = getline(line('.')-1)
1958         let dref = matchstr(line,'\Cdiff --git \zs\%(a/.*\|/dev/null\)\ze \%(b/.*\|/dev/null\)')
1959         let ref = matchstr(line,'\Cdiff --git \%(a/.*\|/dev/null\) \zs\%(b/.*\|/dev/null\)')
1960         let dcmd = 'Gdiff!'
1961
1962       elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
1963         let ref = getline('.')
1964       else
1965         let ref = ''
1966       endif
1967
1968       if myhash ==# ''
1969         let ref = s:sub(ref,'^a/','HEAD:')
1970         let ref = s:sub(ref,'^b/',':0:')
1971         if exists('dref')
1972           let dref = s:sub(dref,'^a/','HEAD:')
1973         endif
1974       else
1975         let ref = s:sub(ref,'^a/',myhash.'^:')
1976         let ref = s:sub(ref,'^b/',myhash.':')
1977         if exists('dref')
1978           let dref = s:sub(dref,'^a/',myhash.'^:')
1979         endif
1980       endif
1981
1982       if ref ==# '/dev/null'
1983         " Empty blob
1984         let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
1985       endif
1986
1987       if exists('dref')
1988         return s:Edit(a:mode,ref) . '|'.dcmd.' '.s:fnameescape(dref)
1989       elseif ref != ""
1990         return s:Edit(a:mode,ref)
1991       endif
1992
1993     endif
1994     return ''
1995   catch /^fugitive:/
1996     return 'echoerr v:errmsg'
1997   endtry
1998 endfunction
1999
2000 " }}}1
2001 " Statusline {{{1
2002
2003 function! s:repo_head_ref() dict abort
2004   return readfile(s:repo().dir('HEAD'))[0]
2005 endfunction
2006
2007 call s:add_methods('repo',['head_ref'])
2008
2009 function! fugitive#statusline(...)
2010   if !exists('b:git_dir')
2011     return ''
2012   endif
2013   let status = ''
2014   if s:buffer().commit() != ''
2015     let status .= ':' . s:buffer().commit()[0:7]
2016   endif
2017   let head = s:repo().head_ref()
2018   if head =~# '^ref: '
2019     let status .= s:sub(head,'^ref: %(refs/%(heads/|remotes/|tags/)=)=','(').')'
2020   elseif head =~# '^\x\{40\}$'
2021     let status .= '('.head[0:7].')'
2022   endif
2023   if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
2024     return ',GIT'.status
2025   else
2026     return '[Git'.status.']'
2027   endif
2028 endfunction
2029
2030 function! s:repo_config(conf) dict abort
2031   return matchstr(system(s:repo().git_command('config').' '.a:conf),"[^\r\n]*")
2032 endfun
2033
2034 function! s:repo_user() dict abort
2035   let username = s:repo().config('user.name')
2036   let useremail = s:repo().config('user.email')
2037   return username.' <'.useremail.'>'
2038 endfun
2039
2040 call s:add_methods('repo',['config', 'user'])
2041
2042 " }}}1
2043
2044 " vim:set ft=vim ts=8 sw=2 sts=2: