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