1 " Location: autoload/fugitive.vim
2 " Maintainer: Tim Pope <http://tpo.pe/>
4 if exists('g:autoloaded_fugitive')
7 let g:autoloaded_fugitive = 1
9 if !exists('g:fugitive_git_executable')
10 let g:fugitive_git_executable = 'git'
15 function! s:function(name) abort
16 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),''))
19 function! s:sub(str,pat,rep) abort
20 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
23 function! s:gsub(str,pat,rep) abort
24 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
27 function! s:Uniq(list) abort
31 let str = string(a:list[i])
33 call remove(a:list, i)
42 function! s:winshell() abort
43 return has('win32') && &shellcmdflag !~# '^-'
46 function! s:shellesc(arg) abort
47 if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
50 return '"'.s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"').'"'
52 return shellescape(a:arg)
56 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
57 function! s:fnameescape(file) abort
58 if exists('*fnameescape')
59 return fnameescape(a:file)
61 return escape(a:file, s:fnameescape)
65 function! s:throw(string) abort
66 let v:errmsg = 'fugitive: '.a:string
70 function! s:warn(str) abort
74 let v:warningmsg = a:str
77 function! s:Slash(path) abort
78 if exists('+shellslash')
79 return tr(a:path, '\', '/')
85 function! s:PlatformSlash(path) abort
86 if exists('+shellslash') && !&shellslash
87 return tr(a:path, '/', '\')
93 function! s:Resolve(path) abort
94 let path = resolve(a:path)
96 let path = s:PlatformSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
101 function! s:cpath(path, ...) abort
102 if exists('+fileignorecase') && &fileignorecase
103 let path = s:PlatformSlash(tolower(a:path))
105 let path = s:PlatformSlash(a:path)
107 return a:0 ? path ==# s:cpath(a:1) : path
110 let s:executables = {}
112 function! s:executable(binary) abort
113 if !has_key(s:executables, a:binary)
114 let s:executables[a:binary] = executable(a:binary)
116 return s:executables[a:binary]
119 function! s:map(mode, lhs, rhs, ...) abort
120 let flags = (a:0 ? a:1 : '') . (a:rhs =~# '<Plug>' ? '' : '<script>')
123 let keys = get(g:, a:mode.'remap', {})
124 if type(keys) == type([])
128 if has_key(keys, head)
129 let head = keys[head]
135 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
136 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
138 if flags !~# '<unique>' || empty(mapcheck(head.tail, a:mode))
139 exe a:mode.'map <buffer>' flags head.tail a:rhs
141 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
142 \ '|sil! exe "' . a:mode . 'unmap <buffer> ' . head.tail . '"'
147 function! s:System(cmd) abort
150 catch /^Vim\%((\a\+)\)\=:E484:/
151 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
152 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
153 call map(opts, 'v:val."=".eval("&".v:val)')
154 call s:throw('failed to run `' . a:cmd . '` with ' . join(opts, ' '))
160 function! s:UserCommand() abort
161 return get(g:, 'fugitive_git_command', g:fugitive_git_executable)
164 function! s:Prepare(dir, ...) abort
165 if type(a:dir) == type([])
166 let args = ['--git-dir=' . (a:0 ? a:1 : get(b:, 'git_dir', ''))] + a:dir
168 let args = ['--git-dir=' . a:dir] + (a:000)
170 return g:fugitive_git_executable . ' ' . join(map(args, 's:shellesc(v:val)'))
173 let s:git_versions = {}
174 function! fugitive#GitVersion(...) abort
175 if !has_key(s:git_versions, g:fugitive_git_executable)
176 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
178 return s:git_versions[g:fugitive_git_executable]
181 let s:commondirs = {}
182 function! fugitive#CommonDir(dir) abort
186 if !has_key(s:commondirs, a:dir)
187 if getfsize(a:dir . '/HEAD') < 10
188 let s:commondirs[a:dir] = ''
189 elseif filereadable(a:dir . '/commondir')
190 let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
191 if dir =~# '^/\|^\a:/'
192 let s:commondirs[a:dir] = dir
194 let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
197 let s:commondirs[a:dir] = a:dir
200 return s:commondirs[a:dir]
203 function! s:Tree(...) abort
204 return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
207 function! s:PreparePathArgs(cmd, dir) abort
208 if fugitive#GitVersion() !~# '^[01]\.'
209 call insert(a:cmd, '--literal-pathspecs')
211 let split = index(a:cmd, '--')
212 let tree = s:Tree(a:dir)
213 if empty(tree) || split < 0
216 for i in range(split + 1, len(a:cmd) - 1)
217 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
222 function! s:TreeChomp(...) abort
223 let args = copy(type(a:1) == type([]) ? a:1 : a:000)
224 let dir = a:0 > 1 && type(a:1) == type([]) ? a:2 : b:git_dir
225 call s:PreparePathArgs(args, dir)
226 let tree = s:Tree(dir)
229 let args = ['--git-dir=' . dir] + args
230 elseif s:cpath(tree) !=# s:cpath(getcwd())
231 if fugitive#GitVersion() =~# '^[01]\.'
232 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
234 let args = ['-C', tree] + args
237 return s:sub(s:System(pre . g:fugitive_git_executable . ' ' .
238 \ join(map(args, 's:shellesc(v:val)'))), '\n$', '')
241 function! fugitive#Prepare(cmd, ...) abort
242 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
243 let tree = s:Tree(dir)
244 let args = type(a:cmd) == type([]) ? join(map(s:PreparePathArgs(copy(a:cmd), dir), 's:shellesc(v:val)')) : a:cmd
246 if empty(tree) || (type(a:cmd) == type([]) && index(a:cmd, '--') == len(a:cmd) - 1)
247 let args = s:shellesc('--git-dir=' . dir) . ' ' . args
248 elseif fugitive#GitVersion() =~# '^[01]\.'
249 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ')
251 let args = '-C ' . s:shellesc(tree) . ' ' . args
253 return pre . g:fugitive_git_executable . ' ' . args
256 function! fugitive#Head(...) abort
257 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
258 if empty(dir) || !filereadable(dir . '/HEAD')
261 let head = readfile(dir . '/HEAD')[0]
263 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
264 elseif head =~# '^\x\{40\}$'
265 let len = a:0 ? a:1 : 0
266 return len < 0 ? head : len ? head[0:len-1] : ''
272 function! fugitive#RevParse(rev, ...) abort
273 let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev, '--'))[0:-2]
274 if !v:shell_error && hash =~# '^\x\{40\}$'
277 call s:throw('rev-parse '.a:rev.': '.hash)
280 function! fugitive#Config(name, ...) abort
281 let cmd = s:Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), 'config', '--get', a:name)
282 let out = matchstr(system(cmd), "[^\r\n]*")
283 return v:shell_error ? '' : out
286 function! s:Remote(dir) abort
287 let head = FugitiveHead(0, a:dir)
288 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
290 while remote ==# '.' && i > 0
291 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
292 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
295 return remote =~# '^\.\=$' ? 'origin' : remote
298 function! fugitive#RemoteUrl(...) abort
299 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
300 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
301 if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
302 return fugitive#Config('remote.' . remote . '.url')
304 let cmd = s:Prepare(dir, 'remote', 'get-url', remote, '--')
305 let out = substitute(system(cmd), "\n$", '', '')
306 return v:shell_error ? '' : out
309 " Section: Repository Object
311 function! s:add_methods(namespace, method_names) abort
312 for name in a:method_names
313 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
318 function! s:command(definition) abort
319 let s:commands += [a:definition]
322 function! s:define_commands() abort
323 for command in s:commands
324 exe 'command! -buffer '.command
328 let s:repo_prototype = {}
331 function! fugitive#repo(...) abort
332 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
334 if has_key(s:repos, dir)
335 let repo = get(s:repos, dir)
337 let repo = {'git_dir': dir}
338 let s:repos[dir] = repo
340 return extend(repo, s:repo_prototype, 'keep')
342 call s:throw('not a git repository: '.expand('%:p'))
345 function! s:repo_dir(...) dict abort
346 return join([self.git_dir]+a:000,'/')
349 function! s:repo_tree(...) dict abort
350 let dir = s:Tree(self.git_dir)
352 call s:throw('no work tree')
354 return join([dir]+a:000,'/')
358 function! s:repo_bare() dict abort
359 if self.dir() =~# '/\.git$'
362 return s:Tree(self.git_dir) ==# ''
366 function! s:repo_route(object) dict abort
367 return fugitive#Route(a:object, self.git_dir)
370 function! s:repo_translate(rev) dict abort
371 return s:Slash(fugitive#Route(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
374 function! s:repo_head(...) dict abort
375 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
378 call s:add_methods('repo',['dir','tree','bare','route','translate','head'])
380 function! s:repo_git_command(...) dict abort
381 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
382 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
385 function! s:repo_git_chomp(...) dict abort
386 return s:sub(s:System(s:Prepare(a:000, self.git_dir)), '\n$', '')
389 function! s:repo_git_chomp_in_tree(...) dict abort
390 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
393 execute cd s:fnameescape(self.tree())
394 return call(self.git_chomp, a:000, self)
396 execute cd s:fnameescape(dir)
400 function! s:repo_rev_parse(rev) dict abort
401 return fugitive#RevParse(a:rev, self.git_dir)
404 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
406 function! s:repo_superglob(base) dict abort
407 return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
410 call s:add_methods('repo',['superglob'])
412 function! s:repo_config(name) dict abort
413 return fugitive#Config(a:name, self.git_dir)
416 function! s:repo_user() dict abort
417 let username = self.config('user.name')
418 let useremail = self.config('user.email')
419 return username.' <'.useremail.'>'
422 call s:add_methods('repo',['config', 'user'])
426 function! s:DirCommitFile(path) abort
427 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
434 function! s:DirRev(url) abort
435 let [dir, commit, file] = s:DirCommitFile(a:url)
436 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
439 function! s:Owner(path, ...) abort
440 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
444 let [pdir, commit, file] = s:DirCommitFile(a:path)
445 if s:cpath(dir, pdir) && commit =~# '^\x\{40\}$'
448 let path = fnamemodify(a:path, ':p')
449 if s:cpath(dir . '/', path[0 : len(dir)]) && a:path =~# 'HEAD$'
450 return strpart(path, len(dir) + 1)
452 let refs = fugitive#CommonDir(dir) . '/refs'
453 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
454 return strpart(path, len(refs) - 4)
459 function! fugitive#Real(url) abort
463 let [dir, commit, file] = s:DirCommitFile(a:url)
465 let tree = s:Tree(dir)
466 return s:PlatformSlash((len(tree) ? tree : dir) . file)
468 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
469 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
470 let url = {pre}Real(a:url)
472 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
474 return s:PlatformSlash(empty(url) ? a:url : url)
477 function! fugitive#Path(url, ...) abort
481 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
482 let tree = s:Tree(dir)
484 return fugitive#Real(a:url)
486 let path = s:Slash(fugitive#Real(a:url))
489 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
490 if s:cpath(cwd . '/', path[0 : len(cwd)])
491 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
494 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
496 let cwd = fnamemodify(cwd, ':h')
499 return a:1[0:-2] . path
501 let url = s:Slash(fnamemodify(a:url, ':p'))
502 if url =~# '/$' && s:Slash(a:url) !~# '/$'
505 let [argdir, commit, file] = s:DirCommitFile(a:url)
506 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
508 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
509 let file = '/.git'.url[strlen(dir) : -1]
510 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
511 let file = url[len(tree) : -1]
512 elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
515 if empty(file) && a:1 =~# '^$\|^[.:]/$'
516 return s:Slash(fugitive#Real(a:url))
518 return substitute(file, '^/', a:1, '')
521 function! s:Relative(...) abort
522 return fugitive#Path(@%, a:0 ? a:1 : ':(top)')
525 function! fugitive#Route(object, ...) abort
526 if type(a:object) == type(0)
527 let name = bufname(a:object)
528 return s:PlatformSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
529 elseif a:object =~# '^[~$]'
530 let prefix = matchstr(a:object, '^[~$]\i*')
531 let owner = expand(prefix)
532 return s:PlatformSlash((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
533 elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
534 return s:PlatformSlash(a:object)
535 elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
536 return s:PlatformSlash(simplify(getcwd() . '/' . a:object))
538 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
540 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
541 let dir = FugitiveExtractGitDir(file)
543 return fnamemodify(len(file) ? file : a:object, ':p')
546 let rev = s:Slash(a:object)
547 let tree = s:Tree(dir)
548 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
550 let f = len(tree) ? tree . '/.git' : dir
551 elseif rev =~# '^\.git/'
552 let f = substitute(rev, '^\.git', '', '')
553 let cdir = fugitive#CommonDir(dir)
554 if f =~# '^/\.\./\.\.\%(/\|$\)'
555 let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
556 elseif f =~# '^/\.\.\%(/\|$\)'
557 let f = base . f[3:-1]
558 elseif cdir !=# dir && (
559 \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
560 \ f !~# '^/logs$\|/\w*HEAD$' && getftime(dir . f) < 0 && getftime(cdir . f) >= 0)
561 let f = simplify(cdir . f)
563 let f = simplify(dir . f)
567 elseif rev =~# '^\.\%(/\|$\)'
568 let f = base . rev[1:-1]
569 elseif rev =~# '^::\%(/\|\a\+\:\)'
571 elseif rev =~# '^::\.\.\=\%(/\|$\)'
572 let f = simplify(getcwd() . '/' . rev[2:-1])
574 let f = base . '/' . rev[2:-1]
575 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
576 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
577 if s:cpath(base . '/', (f . '/')[0 : len(base)])
578 let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
580 let altdir = FugitiveExtractGitDir(f)
581 if len(altdir) && !s:cpath(dir, altdir)
582 return fugitive#Route(a:object, altdir)
585 elseif rev =~# '^:[0-3]:'
586 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
588 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
589 let f = fnamemodify($GIT_INDEX_FILE, ':p')
591 let f = dir . '/index'
593 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
594 let f = base . '/' . matchstr(rev, ')\zs.*')
595 elseif rev =~# '^:/\@!'
596 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
598 if rev =~# 'HEAD$\|^refs/' && rev !~# ':'
599 let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
600 if filereadable(cdir . '/' . rev)
601 let f = simplify(cdir . '/' . rev)
605 let commit = substitute(matchstr(rev, '^[^:]\+\|^:.*'), '^@\%($|[^~]\)\@=', 'HEAD', '')
606 let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
607 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
608 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
609 if s:cpath(base . '/', (file . '/')[0 : len(base)])
610 let file = '/' . strpart(file, len(base) + 1)
612 let altdir = FugitiveExtractGitDir(file)
613 if len(altdir) && !s:cpath(dir, altdir)
614 return fugitive#Route(a:object, altdir)
619 if commit !~# '^[0-9a-f]\{40\}$'
620 let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit, '--'))[0:-2]
621 let commit = v:shell_error ? '' : commit
624 let f = 'fugitive://' . dir . '//' . commit . file
626 let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
630 return s:PlatformSlash(f)
633 function! s:Generate(rev, ...) abort
634 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
635 let tree = s:Tree(dir)
637 if a:rev =~# '^/\.git\%(/\|$\)'
638 let object = a:rev[1:-1]
639 elseif a:rev =~# '^/' && len(tree) && getftime(tree . a:rev) >= 0 && getftime(a:rev) < 0
640 let object = ':(top)' . a:rev[1:-1]
642 return fugitive#Route(object, dir)
645 function! s:DotRelative(path) abort
647 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
648 if s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
649 return '.' . strpart(path, len(cwd))
654 function! fugitive#Object(...) abort
655 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
656 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
657 if s:cpath(dir) !=# s:cpath(fdir)
660 let tree = s:Tree(dir)
661 if empty(rev) && empty(tree)
663 let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
664 let cdir = fugitive#CommonDir(dir)
665 if rev =~# '^\./\.git/refs/\%(tags\|heads\|remotes\)/.\|^\./\.git/\w*HEAD$'
667 elseif s:cpath(cdir . '/refs/', rev[0 : len(cdir)])
668 let rev = strpart(rev, len(cdir)+1)
669 elseif rev =~# '^\./.git\%(/\|$\)'
670 return fnamemodify(a:0 ? a:1 : @%, ':p')
673 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
676 return tree . rev[1:-1]
680 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
681 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
682 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
684 function! s:BufName(var) abort
686 return bufname(get(b:, 'fugitive_blamed_bufnr', ''))
687 elseif a:var =~# '^#\d*$'
688 let nr = getbufvar(+a:var[1:-1], 'fugitive_blamed_bufnr', '')
689 return bufname(nr ? nr : +a:var[1:-1])
695 function! s:ExpandVar(other, var, flags, esc) abort
698 elseif a:other =~# '^!'
699 let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
700 let owner = s:Owner(buffer)
701 return len(owner) ? owner : '@'
704 let file = s:DotRelative(fugitive#Real(s:BufName(a:var)))
706 let flag = matchstr(flags, s:flag)
707 let flags = strpart(flags, len(flag))
709 let file = s:DotRelative(file)
711 let file = fnamemodify(file, flag)
714 let file = s:Slash(file)
715 return (len(a:esc) ? shellescape(file) : file)
718 function! s:Expand(rev) abort
719 if a:rev =~# '^:[0-3]$'
720 let file = a:rev . s:Relative(':')
721 elseif a:rev =~# '^-'
722 let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
723 elseif a:rev =~# '^@{'
724 let file = 'HEAD' . a:rev. s:Relative(':')
725 elseif a:rev =~# '^\^[0-9~^{]\|^\~[0-9~^]'
726 let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
727 let file = commit . a:rev . s:Relative(':')
731 return substitute(file,
732 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
733 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"")', 'g')
736 function! fugitive#Expand(object) abort
737 return substitute(a:object,
738 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
739 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
742 function! s:ShellExpand(cmd) abort
743 return substitute(a:cmd, '\(\\[!#%]\|!\d*\)\|' . s:expand,
744 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
749 function! s:TreeInfo(dir, commit) abort
750 let git = s:Prepare(a:dir)
751 if a:commit =~# '^:\=[0-3]$'
752 let index = get(s:indexes, a:dir, [])
753 let newftime = getftime(a:dir . '/index')
754 if get(index, 0, -1) < newftime
755 let out = system(git . ' ls-files --stage')
756 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
760 for line in split(out, "\n")
761 let [info, filename] = split(line, "\t")
762 let [mode, sha, stage] = split(info, '\s\+')
763 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
764 while filename =~# '/'
765 let filename = substitute(filename, '/[^/]*$', '', '')
766 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
770 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
771 elseif a:commit =~# '^\x\{40\}$'
772 if !has_key(s:trees, a:dir)
773 let s:trees[a:dir] = {}
775 if !has_key(s:trees[a:dir], a:commit)
776 let ftime = +system(git . ' log -1 --pretty=format:%ct ' . a:commit)
778 let s:trees[a:dir][a:commit] = [{}, -1]
779 return s:trees[a:dir][a:commit]
781 let s:trees[a:dir][a:commit] = [{}, +ftime]
782 let out = system(git . ' ls-tree -rtl --full-name ' . a:commit)
784 return s:trees[a:dir][a:commit]
786 for line in split(out, "\n")
787 let [info, filename] = split(line, "\t")
788 let [mode, type, sha, size] = split(info, '\s\+')
789 let s:trees[a:dir][a:commit][0][filename] = [ftime, mode, type, sha, +size, filename]
792 return s:trees[a:dir][a:commit]
797 function! s:PathInfo(url) abort
798 let [dir, commit, file] = s:DirCommitFile(a:url)
799 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
800 return [-1, '000000', '', '', -1]
802 let path = substitute(file[1:-1], '/*$', '', '')
803 let [tree, ftime] = s:TreeInfo(dir, commit)
804 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
805 if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
806 return [-1, '000000', '', '', -1]
812 function! fugitive#simplify(url) abort
813 let [dir, commit, file] = s:DirCommitFile(a:url)
817 if file =~# '/\.\.\%(/\|$\)'
818 let tree = s:Tree(dir)
820 let path = simplify(tree . file)
821 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
822 return s:PlatformSlash(path)
826 return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
829 function! fugitive#resolve(url) abort
830 let url = fugitive#simplify(a:url)
831 if url =~? '^fugitive:'
838 function! fugitive#getftime(url) abort
839 return s:PathInfo(a:url)[0]
842 function! fugitive#getfsize(url) abort
843 let entry = s:PathInfo(a:url)
844 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
845 let dir = s:DirCommitFile(a:url)[0]
846 let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3], '--'))
847 let entry[4] = v:shell_error ? -1 : size
852 function! fugitive#getftype(url) abort
853 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
856 function! fugitive#filereadable(url) abort
857 return s:PathInfo(a:url)[2] ==# 'blob'
860 function! fugitive#filewritable(url) abort
861 let [dir, commit, file] = s:DirCommitFile(a:url)
862 if commit !~# '^\d$' || !filewritable(dir . '/index')
865 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
868 function! fugitive#isdirectory(url) abort
869 return s:PathInfo(a:url)[2] ==# 'tree'
872 function! fugitive#getfperm(url) abort
873 let [dir, commit, file] = s:DirCommitFile(a:url)
874 let perm = getfperm(dir)
875 let fperm = s:PathInfo(a:url)[1]
876 if fperm ==# '040000'
880 let perm = tr(perm, 'x', '-')
883 let perm = tr(perm, 'rw', '--')
886 let perm = tr(perm, 'w', '-')
888 return perm ==# '---------' ? '' : perm
891 function! fugitive#setfperm(url, perm) abort
892 let [dir, commit, file] = s:DirCommitFile(a:url)
893 let entry = s:PathInfo(a:url)
894 let perm = fugitive#getfperm(a:url)
895 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
896 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
899 call system(s:Prepare(dir, 'update-index', '--index-info'),
900 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
901 return v:shell_error ? -1 : 0
904 function! s:TempCmd(out, cmd) abort
907 let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
908 let redir = ' > ' . a:out
910 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
911 return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
912 elseif &shell =~# 'fish'
913 return s:System(' begin;' . prefix . cmd . redir . ';end ')
915 return s:System(' (' . prefix . cmd . redir . ') ')
920 if !exists('s:blobdirs')
923 function! s:BlobTemp(url) abort
924 let [dir, commit, file] = s:DirCommitFile(a:url)
928 if !has_key(s:blobdirs, dir)
929 let s:blobdirs[dir] = tempname()
931 let tempfile = s:blobdirs[dir] . '/' . commit . file
932 let tempparent = fnamemodify(tempfile, ':h')
933 if !isdirectory(tempparent)
934 call mkdir(tempparent, 'p')
936 if commit =~# '^\d$' || !filereadable(tempfile)
937 let rev = s:DirRev(a:url)[1]
938 let command = s:Prepare(dir, 'cat-file', 'blob', rev, '--')
939 call s:TempCmd(tempfile, command)
941 call delete(tempfile)
945 return s:Resolve(tempfile)
948 function! fugitive#readfile(url, ...) abort
949 let entry = s:PathInfo(a:url)
950 if entry[2] !=# 'blob'
953 let temp = s:BlobTemp(a:url)
957 return call('readfile', [temp] + a:000)
960 function! fugitive#writefile(lines, url, ...) abort
961 let url = type(a:url) ==# type('') ? a:url : ''
962 let [dir, commit, file] = s:DirCommitFile(url)
963 let entry = s:PathInfo(url)
964 if commit =~# '^\d$' && entry[2] !=# 'tree'
965 let temp = tempname()
966 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
967 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
969 call call('writefile', [a:lines, temp] + a:000)
970 let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
971 let mode = len(entry[1]) ? entry[1] : '100644'
972 if !v:shell_error && hash =~# '^\x\{40\}$'
973 call system(s:Prepare(dir, 'update-index', '--index-info'),
974 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
980 return call('writefile', [a:lines, a:url] + a:000)
984 \ '/**/': '/\%([^./][^/]*/\)*',
985 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
986 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
988 \ '/*': '/[^/.][^/]*',
991 function! fugitive#glob(url, ...) abort
992 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
993 let append = matchstr(glob, '/*$')
994 let glob = substitute(glob, '/*$', '', '')
995 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
997 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
998 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
1001 let files = items(s:TreeInfo(dir, commit)[0])
1003 call filter(files, 'v:val[1][2] ==# "tree"')
1005 call map(files, 'v:val[0]')
1006 call filter(files, 'v:val =~# pattern')
1007 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1009 call map(files, 's:PlatformSlash(prepend . v:val . append)')
1010 call extend(results, files)
1015 return join(results, "\n")
1019 function! fugitive#delete(url, ...) abort
1020 let [dir, commit, file] = s:DirCommitFile(a:url)
1021 if a:0 && len(a:1) || commit !~# '^\d$'
1024 let entry = s:PathInfo(a:url)
1025 if entry[2] !=# 'blob'
1028 call system(s:Prepare(dir, 'update-index', '--index-info'),
1029 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
1030 return v:shell_error ? -1 : 0
1033 " Section: Buffer Object
1035 let s:buffer_prototype = {}
1037 function! fugitive#buffer(...) abort
1038 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1039 call extend(buffer, s:buffer_prototype, 'keep')
1040 if buffer.getvar('git_dir') !=# ''
1043 call s:throw('not a git repository: '.bufname(buffer['#']))
1046 function! s:buffer_getvar(var) dict abort
1047 return getbufvar(self['#'],a:var)
1050 function! s:buffer_getline(lnum) dict abort
1051 return get(getbufline(self['#'], a:lnum), 0, '')
1054 function! s:buffer_repo() dict abort
1055 return fugitive#repo(self.getvar('git_dir'))
1058 function! s:buffer_type(...) dict abort
1059 if !empty(self.getvar('fugitive_type'))
1060 let type = self.getvar('fugitive_type')
1061 elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
1063 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
1065 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
1067 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
1069 elseif isdirectory(self.spec())
1070 let type = 'directory'
1071 elseif self.spec() == ''
1077 return !empty(filter(copy(a:000),'v:val ==# type'))
1085 function! s:buffer_spec() dict abort
1086 let bufname = bufname(self['#'])
1088 for i in split(bufname,'[^:]\zs\\')
1089 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
1091 return s:Slash(fnamemodify(retval,':p'))
1096 function! s:buffer_spec() dict abort
1097 let bufname = bufname(self['#'])
1098 return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
1103 function! s:buffer_name() dict abort
1107 function! s:buffer_commit() dict abort
1108 return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
1111 function! s:buffer_relative(...) dict abort
1112 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
1114 let rev = s:sub(rev,'\w*','')
1115 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
1116 \ s:cpath(self.repo().dir() . '/')
1117 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
1118 elseif !self.repo().bare() &&
1119 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
1120 \ s:cpath(self.repo().tree() . '/')
1121 let rev = self.spec()[strlen(self.repo().tree()) : -1]
1123 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
1126 function! s:buffer_path(...) dict abort
1128 return self.relative(a:1)
1130 return self.relative()
1133 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative'])
1135 " Section: Completion
1137 function! s:GlobComplete(lead, pattern) abort
1139 let results = glob(a:lead . a:pattern, 0, 1)
1141 let results = split(glob(a:lead . a:pattern), "\n")
1143 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1144 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1148 function! fugitive#PathComplete(base, ...) abort
1149 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1150 let tree = s:Tree(dir) . '/'
1151 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1152 let base = substitute(a:base, strip, '', '')
1153 if base =~# '^\.git/'
1154 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1155 let matches = s:GlobComplete(dir . '/', pattern)
1156 let cdir = fugitive#CommonDir(dir)
1157 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1158 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1160 call s:Uniq(matches)
1161 call map(matches, "'.git/' . v:val")
1162 elseif base =~# '^\~/'
1163 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1164 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1165 let matches = s:GlobComplete('', base . '*')
1166 elseif len(tree) > 1
1167 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1171 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1175 function! fugitive#Complete(base, ...) abort
1176 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1177 let cwd = a:0 == 1 ? s:Tree(dir) : getcwd()
1178 let tree = s:Tree(dir) . '/'
1180 if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1181 let subdir = strpart(cwd, len(tree)) . '/'
1184 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1186 if a:base =~# '^refs/'
1187 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1188 elseif a:base !~# '^\.\=/\|^:('
1189 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1190 let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1191 if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1192 let heads += ["stash"]
1193 let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1195 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1196 let results += heads
1198 call map(results, 's:fnameescape(v:val)')
1200 let results += a:0 == 1 ? fugitive#PathComplete(a:base, dir) : fugitive#PathComplete(a:base)
1204 elseif a:base =~# '^:'
1205 let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1206 if a:base =~# ':\./'
1207 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1209 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1210 if a:base !~# '^:[0-3]\%(:\|$\)'
1211 call filter(entries,'v:val[1] == "0"')
1212 call map(entries,'v:val[2:-1]')
1216 let tree = matchstr(a:base, '.*[:/]')
1217 let entries = split(s:TreeChomp(['ls-tree', substitute(tree, ':\zs\./', '\=subdir', '')], dir),"\n")
1218 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1219 call map(entries,'tree.s:sub(v:val,".*\t","")')
1222 call filter(entries, 'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1223 return map(entries, 's:fnameescape(v:val)')
1226 " Section: Buffer auto-commands
1228 function! s:ReplaceCmd(cmd) abort
1229 let temp = tempname()
1230 let err = s:TempCmd(temp, a:cmd)
1232 call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1234 let temp = s:Resolve(temp)
1235 let fn = expand('%:p')
1236 silent exe 'doau BufReadPre '.s:fnameescape(fn)
1237 silent exe 'keepalt file '.temp
1239 silent noautocmd edit!
1242 silent exe 'keepalt file '.s:fnameescape(fn)
1243 catch /^Vim\%((\a\+)\)\=:E302:/
1246 if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1247 silent execute 'bwipeout '.bufnr('$')
1249 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1253 function! fugitive#BufReadStatus() abort
1254 let amatch = s:Slash(expand('%:p'))
1255 if !exists('b:fugitive_display_format')
1256 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1258 let b:fugitive_display_format = b:fugitive_display_format % 2
1259 let b:fugitive_type = 'index'
1261 let dir = fnamemodify(amatch, ':h')
1262 setlocal noro ma nomodeline
1264 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1266 let old_index = $GIT_INDEX_FILE
1268 let prefix = 'env GIT_INDEX_FILE='.s:shellesc(amatch).' '
1271 if b:fugitive_display_format
1272 let cmd = ['ls-files', '--stage']
1273 elseif fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.'
1274 let cmd = ['status']
1277 \ '-c', 'status.displayCommentPrefix=true',
1278 \ '-c', 'color.status=false',
1279 \ '-c', 'status.short=false',
1282 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1284 let cmd_str = prefix . s:Prepare(cmd, dir)
1286 if exists('old_index')
1287 let $GIT_INDEX_FILE = amatch
1289 execute cd s:fnameescape(s:Tree(dir))
1290 call s:ReplaceCmd(cmd_str)
1292 if exists('old_index')
1293 let $GIT_INDEX_FILE = old_index
1295 execute cd s:fnameescape(cwd)
1297 if b:fugitive_display_format
1298 if &filetype !=# 'git'
1303 if &filetype !=# 'gitcommit'
1304 set filetype=gitcommit
1306 set foldtext=fugitive#Foldtext()
1308 setlocal readonly nomodifiable nomodified noswapfile
1309 if &bufhidden ==# ''
1310 setlocal bufhidden=delete
1312 call fugitive#MapJumps()
1313 let nowait = v:version >= 704 ? '<nowait>' : ''
1316 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1317 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1318 exe "nnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
1319 exe "xnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
1320 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1321 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1322 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1323 nnoremap <buffer> <silent> cA :<C-U>Gcommit --amend --reuse-message=HEAD<CR>:echohl WarningMsg<Bar>echo ':Gstatus cA is deprecated in favor of ce'<Bar>echohl NONE<CR>
1324 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1325 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1326 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1327 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1328 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1329 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1330 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1331 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1332 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1333 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1334 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1335 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1336 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1337 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1338 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1339 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1340 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1341 nnoremap <buffer> <silent> r :<C-U>edit<CR>
1342 nnoremap <buffer> <silent> R :<C-U>edit<CR>
1343 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1344 nnoremap <buffer> . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
1345 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
1346 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1348 return 'echoerr v:errmsg'
1352 function! fugitive#FileReadCmd(...) abort
1353 let amatch = a:0 ? a:1 : expand('<amatch>')
1354 let [dir, rev] = s:DirRev(amatch)
1355 let line = a:0 > 1 ? a:2 : line("'[")
1357 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1360 let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1362 let cmd = s:Prepare(dir, 'cat-file', '-p', rev, '--')
1364 return line . 'read !' . escape(cmd, '!#%')
1367 function! fugitive#FileWriteCmd(...) abort
1368 let tmp = tempname()
1369 let amatch = a:0 ? a:1 : expand('<amatch>')
1370 let autype = a:0 > 1 ? 'Buf' : 'File'
1371 if exists('#' . autype . 'WritePre')
1372 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1375 let [dir, commit, file] = s:DirCommitFile(amatch)
1376 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1377 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1379 silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1380 let sha1 = readfile(tmp)[0]
1381 let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', '--', '.' . file)), '^\d\+')
1383 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1385 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1386 let error = system(s:Prepare(dir, 'update-index', '--index-info'), info . "\n")
1387 if v:shell_error == 0
1389 if exists('#' . autype . 'WritePost')
1390 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1392 call fugitive#ReloadStatus()
1395 return 'echoerr '.string('fugitive: '.error)
1402 function! fugitive#BufReadCmd(...) abort
1403 let amatch = a:0 ? a:1 : expand('<amatch>')
1405 let [dir, rev] = s:DirRev(amatch)
1407 return 'echo "Invalid Fugitive URL"'
1410 let b:fugitive_type = 'stage'
1412 let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1413 if v:shell_error && rev =~# '^:0'
1414 let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1415 let b:fugitive_type = 'tree'
1418 unlet b:fugitive_type
1420 let &readonly = !filewritable(dir . '/index')
1421 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1423 setlocal readonly nomodifiable
1426 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1427 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1429 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1430 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1434 if b:fugitive_type !=# 'blob'
1438 setlocal noreadonly modifiable
1439 let pos = getpos('.')
1440 silent keepjumps %delete_
1444 if b:fugitive_type ==# 'tree'
1445 let b:fugitive_display_format = b:fugitive_display_format % 2
1446 if b:fugitive_display_format
1447 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1450 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev))[0:-2]
1452 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1454 elseif b:fugitive_type ==# 'tag'
1455 let b:fugitive_display_format = b:fugitive_display_format % 2
1456 if b:fugitive_display_format
1457 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1459 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1461 elseif b:fugitive_type ==# 'commit'
1462 let b:fugitive_display_format = b:fugitive_display_format % 2
1463 if b:fugitive_display_format
1464 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1466 call s:ReplaceCmd([dir, 'show', '--no-color', '--pretty=format:tree%x20%T%nparent%x20%P%nauthor%x20%an%x20<%ae>%x20%ad%ncommitter%x20%cn%x20<%ce>%x20%cd%nencoding%x20%e%n%n%s%n%n%b', rev])
1467 keepjumps call search('^parent ')
1468 if getline('.') ==# 'parent '
1469 silent keepjumps delete_
1471 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1473 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1475 silent keepjumps delete_
1477 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1480 elseif b:fugitive_type ==# 'stage'
1481 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1482 elseif b:fugitive_type ==# 'blob'
1483 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1487 keepjumps call setpos('.',pos)
1488 setlocal nomodified noswapfile
1490 setlocal nomodifiable
1492 let &modifiable = b:fugitive_type !=# 'tree'
1494 let &readonly = !&modifiable || !filewritable(dir . '/index')
1495 if &bufhidden ==# ''
1496 setlocal bufhidden=delete
1498 if b:fugitive_type !=# 'blob'
1499 setlocal filetype=git foldmethod=syntax
1500 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1501 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1503 call fugitive#MapJumps()
1509 return 'echoerr v:errmsg'
1513 function! fugitive#BufWriteCmd(...) abort
1514 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1517 function! fugitive#SourceCmd(...) abort
1518 let amatch = a:0 ? a:1 : expand('<amatch>')
1519 let temp = s:BlobTemp(amatch)
1521 return 'noautocmd source ' . s:fnameescape(amatch)
1523 if !exists('g:virtual_scriptnames')
1524 let g:virtual_scriptnames = {}
1526 let g:virtual_scriptnames[temp] = amatch
1527 return 'source ' . s:fnameescape(temp)
1530 " Section: Temp files
1532 if !exists('s:temp_files')
1533 let s:temp_files = {}
1536 function! s:SetupTemp(file) abort
1537 if has_key(s:temp_files, s:cpath(a:file))
1538 let dict = s:temp_files[s:cpath(a:file)]
1539 let b:git_dir = dict.dir
1540 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1541 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1542 let &l:filetype = dict.filetype
1544 setlocal foldmarker=<<<<<<<,>>>>>>>
1545 setlocal bufhidden=delete nobuflisted
1546 setlocal buftype=nowrite
1547 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1548 if getline(1) !~# '^diff '
1549 setlocal nomodifiable
1551 call FugitiveDetect(a:file)
1556 augroup fugitive_temp
1558 autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1563 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1565 function! s:Git(bang, mods, args) abort
1567 return s:Edit('edit', 1, a:mods, a:args)
1569 let git = s:UserCommand()
1570 if has('gui_running') && !has('win32')
1571 let git .= ' --no-pager'
1573 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1574 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1577 let after = '|call fugitive#ReloadStatus()' . after
1579 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1585 execute 'lcd' fnameescape(tree)
1586 let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1587 return 'exe ' . string('terminal ' . exec) . after
1589 let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1590 if s:cpath(tree) !=# s:cpath(getcwd())
1591 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1592 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1598 let s:exec_paths = {}
1599 function! s:Subcommands() abort
1600 if !has_key(s:exec_paths, g:fugitive_git_executable)
1601 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1603 let exec_path = s:exec_paths[g:fugitive_git_executable]
1604 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1608 function! s:Aliases() abort
1609 if !has_key(s:aliases, b:git_dir)
1610 let s:aliases[b:git_dir] = {}
1611 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1612 for line in v:shell_error ? [] : lines
1613 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1616 return s:aliases[b:git_dir]
1619 function! s:GitComplete(A, L, P) abort
1620 let pre = strpart(a:L, 0, a:P)
1621 if pre !~# ' [[:alnum:]-]\+ '
1622 let cmds = s:Subcommands()
1623 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1624 elseif pre =~# ' -- '
1625 return fugitive#PathComplete(a:A, b:git_dir)
1627 return fugitive#Complete(a:A, b:git_dir)
1631 " Section: :Gcd, :Glcd
1633 function! s:DirComplete(A, L, P) abort
1634 return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1637 function! s:DirArg(path) abort
1638 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1639 if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
1642 return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1646 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1647 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1651 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1652 augroup fugitive_status
1655 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1656 autocmd BufDelete term://* call fugitive#ReloadStatus()
1660 function! s:Status(bang, count, mods) abort
1662 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1664 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1665 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1667 return 'echoerr v:errmsg'
1672 function! fugitive#ReloadStatus() abort
1673 if exists('s:reloading_status')
1677 let s:reloading_status = 1
1678 let mytab = tabpagenr()
1679 for tab in [mytab] + range(1,tabpagenr('$'))
1680 for winnr in range(1,tabpagewinnr(tab,'$'))
1681 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1682 execute 'tabnext '.tab
1684 execute winnr.'wincmd w'
1685 let restorewinnr = 1
1689 call fugitive#BufReadStatus()
1692 if exists('restorewinnr')
1695 execute 'tabnext '.mytab
1701 unlet! s:reloading_status
1705 function! fugitive#reload_status() abort
1706 return fugitive#ReloadStatus()
1709 function! s:stage_info(lnum) abort
1710 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1712 if has('multi_byte_encoding')
1713 let colon = '\%(:\|\%uff1a\)'
1717 while lnum && getline(lnum) !~# colon.'$'
1722 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1723 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1724 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1725 return [filename, 'untracked']
1726 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1727 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1728 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1729 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1731 return ['', 'unknown']
1735 function! s:StageNext(count) abort
1736 for i in range(a:count)
1737 call search('^.\=\t.*','W')
1742 function! s:StagePrevious(count) abort
1743 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1744 return 'CtrlP '.fnameescape(s:Tree())
1746 for i in range(a:count)
1747 call search('^.\=\t.*','Wbe')
1753 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1755 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1756 if f !=# '' | let jump = f | endif
1757 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1758 if f !=# '' | let jump = f | endif
1762 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1765 function! s:StageUndo() abort
1766 let [filename, section] = s:stage_info(line('.'))
1770 let hash = s:TreeChomp('hash-object', '-w', './' . filename)
1772 if section ==# 'untracked'
1773 call s:TreeChomp('clean', '-f', './' . filename)
1774 elseif section ==# 'unmerged'
1775 call s:TreeChomp('rm', './' . filename)
1776 elseif section ==# 'unstaged'
1777 call s:TreeChomp('checkout', './' . filename)
1779 call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
1781 call s:StageReloadSeek(filename, line('.'), line('.'))
1783 return 'checktime|redraw|echomsg ' .
1784 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1788 function! s:StageDiff(diff) abort
1789 let [filename, section] = s:stage_info(line('.'))
1790 if filename ==# '' && section ==# 'staged'
1791 return 'Git! diff --no-ext-diff --cached'
1792 elseif filename ==# ''
1793 return 'Git! diff --no-ext-diff'
1794 elseif filename =~# ' -> '
1795 let [old, new] = split(filename,' -> ')
1796 execute 'Gedit '.s:fnameescape(':0:'.new)
1797 return a:diff.' HEAD:'.s:fnameescape(old)
1798 elseif section ==# 'staged'
1799 execute 'Gedit '.s:fnameescape(':0:'.filename)
1802 execute 'Gedit '.s:fnameescape('/'.filename)
1807 function! s:StageDiffEdit() abort
1808 let [filename, section] = s:stage_info(line('.'))
1809 let arg = (filename ==# '' ? '.' : filename)
1810 if section ==# 'staged'
1811 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1812 elseif section ==# 'untracked'
1813 call s:TreeChomp('add', '--intent-to-add', './' . arg)
1817 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1818 call search(':$','W')
1821 call s:StageReloadSeek(arg,line('.'),line('.'))
1825 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1829 function! s:StageToggle(lnum1,lnum2) abort
1830 if a:lnum1 == 1 && a:lnum2 == 1
1831 return 'Gedit /.git|call search("^index$", "wc")'
1835 for lnum in range(a:lnum1,a:lnum2)
1836 let [filename, section] = s:stage_info(lnum)
1837 if getline('.') =~# ':$'
1838 if section ==# 'staged'
1839 call s:TreeChomp('reset','-q')
1842 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1843 call search(':$','W')
1846 elseif section ==# 'unstaged'
1847 call s:TreeChomp('add','-u')
1850 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1851 call search(':$','W')
1855 call s:TreeChomp('add', '.')
1858 call search(':$','W')
1866 if section ==# 'staged'
1867 if filename =~ ' -> '
1868 let files_to_unstage = split(filename,' -> ')
1870 let files_to_unstage = [filename]
1872 let filename = files_to_unstage[-1]
1873 let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
1874 elseif getline(lnum) =~# '^.\=\tdeleted:'
1875 let cmd = ['rm', './' . filename]
1876 elseif getline(lnum) =~# '^.\=\tmodified:'
1877 let cmd = ['add', './' . filename]
1879 let cmd = ['add','-A', './' . filename]
1881 if !exists('first_filename')
1882 let first_filename = filename
1884 let output .= call('s:TreeChomp', cmd)."\n"
1886 if exists('first_filename')
1887 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1889 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1891 return 'echoerr v:errmsg'
1896 function! s:StagePatch(lnum1,lnum2) abort
1900 for lnum in range(a:lnum1,a:lnum2)
1901 let [filename, section] = s:stage_info(lnum)
1902 if getline('.') =~# ':$' && section ==# 'staged'
1903 return 'Git reset --patch'
1904 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1905 return 'Git add --patch'
1906 elseif getline('.') =~# ':$' && section ==# 'untracked'
1907 return 'Git add -N .'
1908 elseif filename ==# ''
1911 if !exists('first_filename')
1912 let first_filename = filename
1915 if filename =~ ' -> '
1916 let reset += [split(filename,' -> ')[1]]
1917 elseif section ==# 'staged'
1918 let reset += [filename]
1919 elseif getline(lnum) !~# '^.\=\tdeleted:'
1920 let add += [filename]
1925 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1928 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1930 if exists('first_filename')
1934 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1937 return 'echoerr v:errmsg'
1944 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1946 function! s:Commit(mods, args, ...) abort
1947 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1948 let dir = a:0 ? a:1 : b:git_dir
1949 let tree = s:Tree(dir)
1950 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1952 let msgfile = dir . '/COMMIT_EDITMSG'
1953 let outfile = tempname()
1954 let errorfile = tempname()
1956 let guioptions = &guioptions
1958 if &guioptions =~# '!'
1959 setglobal guioptions-=!
1961 execute cd s:fnameescape(tree)
1964 let old_editor = $GIT_EDITOR
1965 let $GIT_EDITOR = 'false'
1967 let command = 'env GIT_EDITOR=false '
1969 let args = s:ShellExpand(a:args)
1970 let command .= s:UserCommand() . ' commit ' . args
1972 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1973 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1974 noautocmd execute '!'.command.' 2> '.errorfile
1976 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1978 let error = v:shell_error
1980 execute cd s:fnameescape(cwd)
1981 let &guioptions = guioptions
1983 if !has('gui_running')
1987 if filereadable(outfile)
1988 for line in readfile(outfile)
1994 let errors = readfile(errorfile)
1995 let error = get(errors,-2,get(errors,-1,'!'))
1996 if error =~# 'false''\=\.$'
1997 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
1998 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
1999 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2001 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
2002 let args = '-F '.s:shellesc(msgfile).' '.args
2003 if args !~# '\%(^\| \)--cleanup\>'
2004 let args = '--cleanup=strip '.args
2006 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
2007 execute mods 'keepalt edit' s:fnameescape(msgfile)
2008 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
2009 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
2010 elseif get(b:, 'fugitive_type', '') ==# 'index'
2011 execute mods 'keepalt edit' s:fnameescape(msgfile)
2012 execute (search('^#','n')+1).'wincmd+'
2013 setlocal nopreviewwindow
2015 execute mods 'keepalt split' s:fnameescape(msgfile)
2017 let b:fugitive_commit_arguments = args
2018 setlocal bufhidden=wipe filetype=gitcommit
2020 elseif error ==# '!'
2023 call s:throw(empty(error)?join(errors, ' '):error)
2027 return 'echoerr v:errmsg'
2029 if exists('old_editor')
2030 let $GIT_EDITOR = old_editor
2032 call delete(outfile)
2033 call delete(errorfile)
2034 call fugitive#ReloadStatus()
2038 function! s:CommitComplete(A,L,P) abort
2039 if a:A =~# '^--fixup=\|^--squash='
2040 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
2042 let pre = matchstr(a:A, '^--\w*=') . ':/^'
2043 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
2045 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
2046 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=', '--fixup=', '--include', '--interactive', '--message=', '--no-verify', '--only', '--quiet', '--reedit-message=', '--reuse-message=', '--signoff', '--squash=', '--template=', '--untracked-files', '--verbose']
2047 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
2049 return fugitive#PathComplete(a:A, b:git_dir)
2054 function! s:FinishCommit() abort
2055 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
2057 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
2058 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
2063 " Section: :Gmerge, :Grebase, :Gpull
2065 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
2066 \ "execute s:Merge('merge', <bang>0, <q-args>)")
2067 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
2068 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
2069 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
2070 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
2072 function! s:RevisionComplete(A, L, P) abort
2073 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
2074 \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
2077 function! s:RemoteComplete(A, L, P) abort
2078 let remote = matchstr(a:L, ' \zs\S\+\ze ')
2080 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
2081 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2082 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2084 let matches = split(s:TreeChomp('remote'), "\n")
2086 return join(matches, "\n")
2089 function! fugitive#Cwindow() abort
2090 if &buftype == 'quickfix'
2094 if &buftype == 'quickfix'
2100 let s:common_efm = ''
2102 \ . '%+Eusage:%.%#,'
2103 \ . '%+Eerror:%.%#,'
2104 \ . '%+Efatal:%.%#,'
2105 \ . '%-G%.%#%\e[K%.%#,'
2106 \ . '%-G%.%#%\r%.%\+'
2108 function! s:Merge(cmd, bang, args) abort
2109 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
2110 return 'echoerr "git rebase --interactive not supported"'
2112 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2114 let [mp, efm] = [&l:mp, &l:efm]
2115 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
2117 let &l:errorformat = ''
2118 \ . '%-Gerror:%.%#false''.,'
2119 \ . '%-G%.%# ''git commit'' %.%#,'
2120 \ . '%+Emerge:%.%#,'
2121 \ . s:common_efm . ','
2122 \ . '%+ECannot %.%#: You have unstaged changes.,'
2123 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
2124 \ . '%+EThere is no tracking information for the current branch.,'
2125 \ . '%+EYou are not currently on a branch. Please specify which,'
2126 \ . 'CONFLICT (%m): %f deleted in %.%#,'
2127 \ . 'CONFLICT (%m): Merge conflict in %f,'
2128 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2129 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2130 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2131 \ . '%+ECONFLICT %.%#,'
2132 \ . '%+EKONFLIKT %.%#,'
2133 \ . '%+ECONFLIT %.%#,'
2134 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2135 \ . "%+E\u51b2\u7a81 %.%#,"
2137 if a:cmd =~# '^merge' && empty(a:args) &&
2138 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2139 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2140 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2142 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2143 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2144 \ ' ' . a:args, ' *$', '')
2146 if !empty($GIT_EDITOR) || has('win32')
2147 let old_editor = $GIT_EDITOR
2148 let $GIT_EDITOR = 'false'
2150 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2152 execute cd fnameescape(s:Tree())
2153 silent noautocmd make!
2154 catch /^Vim\%((\a\+)\)\=:E211/
2155 let err = v:exception
2158 let [&l:mp, &l:efm] = [mp, efm]
2159 if exists('old_editor')
2160 let $GIT_EDITOR = old_editor
2162 execute cd fnameescape(cwd)
2164 call fugitive#ReloadStatus()
2165 if empty(filter(getqflist(),'v:val.valid'))
2166 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2168 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2171 let qflist = getqflist()
2176 let e.pattern = '^<<<<<<<'
2179 call fugitive#Cwindow()
2181 call setqflist(qflist, 'r')
2186 return exists('err') ? 'echoerr '.string(err) : ''
2189 " Section: :Ggrep, :Glog
2191 if !exists('g:fugitive_summary_format')
2192 let g:fugitive_summary_format = '%s'
2195 function! s:GrepComplete(A, L, P) abort
2196 if strpart(a:L, 0, a:P) =~# ' -- '
2197 return fugitive#PathComplete(a:A, b:git_dir)
2199 return fugitive#Complete(a:A, b:git_dir)
2203 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2204 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2205 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2206 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2208 function! s:Grep(cmd,bang,arg) abort
2209 let grepprg = &grepprg
2210 let grepformat = &grepformat
2211 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2214 execute cd s:fnameescape(s:Tree())
2215 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2216 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2217 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2218 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2220 if bufname(entry.bufnr) =~ ':'
2221 let entry.filename = s:Generate(bufname(entry.bufnr))
2224 elseif a:arg =~# '\%(^\| \)--cached\>'
2225 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2230 if a:cmd =~# '^l' && exists('changed')
2231 call setloclist(0, list, 'r')
2232 elseif exists('changed')
2233 call setqflist(list, 'r')
2235 if !a:bang && !empty(list)
2236 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2238 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2241 let &grepprg = grepprg
2242 let &grepformat = grepformat
2243 execute cd s:fnameescape(dir)
2247 function! s:Log(cmd, bang, line1, line2, ...) abort
2248 let args = ' ' . join(a:000, ' ')
2249 let before = substitute(args, ' --\S\@!.*', '', '')
2250 let after = strpart(args, len(before))
2251 let path = s:Relative('/')
2252 let relative = path[1:-1]
2253 if path =~# '^/\.git\%(/\|$\)' || len(after)
2256 if before !~# '\s[^[:space:]-]'
2257 let owner = s:Owner(@%)
2259 let before .= ' ' . s:shellesc(owner)
2262 if relative =~# '^\.git\%(/\|$\)'
2265 if len(relative) && a:line2 > 0
2266 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2267 elseif len(relative) && (empty(after) || a:line2 == 0)
2268 let after = (len(after) > 3 ? after : ' -- ') . relative
2270 let grepformat = &grepformat
2271 let grepprg = &grepprg
2272 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2275 execute cd s:fnameescape(s:Tree())
2276 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2277 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2278 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2279 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2281 let &grepformat = grepformat
2282 let &grepprg = grepprg
2283 execute cd s:fnameescape(dir)
2287 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
2289 function! s:UsableWin(nr) abort
2290 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2291 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2294 function! s:EditParse(args) abort
2296 let args = copy(a:args)
2297 while !empty(args) && args[0] =~# '^+'
2298 call add(pre, ' ' . escape(remove(args, 0), ' |"'))
2301 let file = join(args)
2302 elseif empty(expand('%'))
2304 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2309 return [s:Expand(file), join(pre)]
2312 function! s:BlurStatus() abort
2313 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2314 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2316 exe winnrs[0].'wincmd w'
2317 elseif winnr('$') == 1
2318 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2319 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2324 let mywinnr = winnr()
2325 for winnr in range(winnr('$'),1,-1)
2326 if winnr != mywinnr && getwinvar(winnr,'&diff')
2327 execute winnr.'wincmd w'
2339 function! s:Edit(cmd, bang, mods, args, ...) abort
2340 let mods = a:mods ==# '<mods>' ? '' : a:mods
2343 let temp = tempname()
2344 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2347 execute cd s:fnameescape(s:Tree())
2348 let git = s:UserCommand()
2349 let args = s:ShellExpand(a:args)
2350 silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2351 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2353 execute cd s:fnameescape(cwd)
2355 let temp = s:Resolve(temp)
2356 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2360 silent execute mods a:cmd temp
2361 call fugitive#ReloadStatus()
2362 return 'redraw|echo ' . string(':!' . git . ' ' . args)
2365 let [file, pre] = s:EditParse(a:000)
2367 let file = s:Generate(file)
2369 return 'echoerr v:errmsg'
2371 if file !~# '^\a\a\+:'
2372 let file = s:sub(file, '/$', '')
2377 return mods . ' ' . a:cmd . pre . ' ' . s:fnameescape(file)
2380 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2381 let mods = a:mods ==# '<mods>' ? '' : a:mods
2384 let delete = 'silent 1,' . line('$') . 'delete_|'
2385 let after = line('$')
2387 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2392 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2395 execute cd s:fnameescape(s:Tree())
2396 let git = s:UserCommand()
2397 let args = s:ShellExpand(a:args)
2398 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2400 execute cd s:fnameescape(cwd)
2402 execute delete . 'diffupdate'
2403 call fugitive#ReloadStatus()
2404 return 'redraw|echo '.string(':!'.git.' '.args)
2406 let [file, pre] = s:EditParse(a:000)
2408 let file = s:Generate(file)
2410 return 'echoerr v:errmsg'
2412 if file =~# '^fugitive:' && after is# 0
2413 return 'exe ' .string(mods . ' ' . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
2415 return mods . ' ' . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2418 function! s:EditRunComplete(A,L,P) abort
2420 return s:GitComplete(a:A, a:L, a:P)
2422 return fugitive#Complete(a:A, a:L, a:P)
2426 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2427 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2428 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2429 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete Gsplit execute s:Edit((<count> ? <count> : '').'split', <bang>0, '<mods>', <q-args>, <f-args>)")
2430 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete Gvsplit execute s:Edit((<count> ? <count> : '').'vsplit', <bang>0, '<mods>', <q-args>, <f-args>)")
2431 call s:command("-bar -bang -nargs=* -range=0 -complete=customlist,s:EditRunComplete" . (has('patch-7.4.542') ? ' -addr=tabs' : '') . " Gtabedit execute s:Edit((<count> ? <count> : '').'tabedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2432 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:EditRunComplete Gread execute s:Read(<count>, <line1>, <line2>, +'<range>', <bang>0, '<mods>', <q-args>, <f-args>)")
2434 " Section: :Gwrite, :Gwq
2436 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2437 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2438 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2440 function! s:Write(force,...) abort
2441 if exists('b:fugitive_commit_arguments')
2442 return 'write|bdelete'
2443 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2445 elseif get(b:, 'fugitive_type', '') ==# 'index'
2447 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
2448 let filename = getline(4)[6:-1]
2451 setlocal buftype=nowrite
2452 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2453 let err = s:TreeChomp('apply', '--cached', '--reverse', '--', expand('%:p'))
2455 let err = s:TreeChomp('apply', '--cached', '--', expand('%:p'))
2458 let v:errmsg = split(err,"\n")[0]
2459 return 'echoerr v:errmsg'
2463 return 'Gedit '.fnameescape(filename)
2466 let mytab = tabpagenr()
2467 let mybufnr = bufnr('')
2468 let file = a:0 ? s:Generate(s:Expand(join(a:000, ' '))) : fugitive#Real(@%)
2470 return 'echoerr '.string('fugitive: cannot determine file path')
2472 if file =~# '^fugitive:'
2473 return 'write' . (a:force ? '! ' : ' ') . s:fnameescape(file)
2475 let always_permitted = s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^0\=$'
2476 if !always_permitted && !a:force && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
2477 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2478 return 'echoerr v:errmsg'
2481 for nr in range(1,bufnr('$'))
2482 if fnamemodify(bufname(nr),':p') ==# file
2487 if treebufnr > 0 && treebufnr != bufnr('')
2488 let temp = tempname()
2489 silent execute '%write '.temp
2490 for tab in [mytab] + range(1,tabpagenr('$'))
2491 for winnr in range(1,tabpagewinnr(tab,'$'))
2492 if tabpagebuflist(tab)[winnr-1] == treebufnr
2493 execute 'tabnext '.tab
2495 execute winnr.'wincmd w'
2496 let restorewinnr = 1
2499 let lnum = line('.')
2500 let last = line('$')
2501 silent execute '$read '.temp
2502 silent execute '1,'.last.'delete_'
2507 if exists('restorewinnr')
2510 execute 'tabnext '.mytab
2516 call writefile(readfile(temp,'b'),file,'b')
2519 execute 'write! '.s:fnameescape(file)
2523 let error = s:TreeChomp('add', '--force', '--', file)
2525 let error = s:TreeChomp('add', '--', file)
2528 let v:errmsg = 'fugitive: '.error
2529 return 'echoerr v:errmsg'
2531 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
2535 let one = s:Generate(':1:'.file)
2536 let two = s:Generate(':2:'.file)
2537 let three = s:Generate(':3:'.file)
2538 for nr in range(1,bufnr('$'))
2539 let name = fnamemodify(bufname(nr), ':p')
2540 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2541 execute nr.'bdelete'
2546 let zero = s:Generate(':0:'.file)
2547 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2548 for tab in range(1,tabpagenr('$'))
2549 for winnr in range(1,tabpagewinnr(tab,'$'))
2550 let bufnr = tabpagebuflist(tab)[winnr-1]
2551 let bufname = fnamemodify(bufname(bufnr), ':p')
2552 if bufname ==# zero && bufnr != mybufnr
2553 execute 'tabnext '.tab
2555 execute winnr.'wincmd w'
2556 let restorewinnr = 1
2559 let lnum = line('.')
2560 let last = line('$')
2561 silent execute '$read '.s:fnameescape(file)
2562 silent execute '1,'.last.'delete_'
2567 if exists('restorewinnr')
2570 execute 'tabnext '.mytab
2576 call fugitive#ReloadStatus()
2580 function! s:Wq(force,...) abort
2581 let bang = a:force ? '!' : ''
2582 if exists('b:fugitive_commit_arguments')
2585 let result = call(s:function('s:Write'),[a:force]+a:000)
2586 if result =~# '^\%(write\|wq\|echoerr\)'
2587 return s:sub(result,'^write','wq')
2589 return result.'|quit'.bang
2593 augroup fugitive_commit
2595 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2598 " Section: :Gpush, :Gfetch
2600 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2601 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2603 function! s:Dispatch(bang, args)
2604 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2606 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2608 let b:current_compiler = 'git'
2609 let &l:errorformat = s:common_efm
2610 execute cd fnameescape(s:Tree())
2611 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2612 if exists(':Make') == 2
2615 silent noautocmd make!
2617 return 'call fugitive#Cwindow()'
2621 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2622 if empty(cc) | unlet! b:current_compiler | endif
2623 execute cd fnameescape(cwd)
2629 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2630 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2631 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2633 augroup fugitive_diff
2635 autocmd BufWinLeave *
2636 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2637 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2639 autocmd BufWinEnter *
2640 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2641 \ call s:diffoff() |
2645 function! s:can_diffoff(buf) abort
2646 return getwinvar(bufwinnr(a:buf), '&diff') &&
2647 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2648 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2651 function! fugitive#CanDiffoff(buf) abort
2652 return s:can_diffoff(a:buf)
2655 function! s:diff_modifier(count) abort
2656 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2657 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2659 elseif &diffopt =~# 'vertical'
2660 return 'keepalt vert '
2661 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2664 return 'keepalt vert '
2668 function! s:diff_window_count() abort
2670 for nr in range(1,winnr('$'))
2671 let c += getwinvar(nr,'&diff')
2676 function! s:diff_restore() abort
2677 let restore = 'setlocal nodiff noscrollbind'
2678 \ . ' scrollopt=' . &l:scrollopt
2679 \ . (&l:wrap ? ' wrap' : ' nowrap')
2680 \ . ' foldlevel=999'
2681 \ . ' foldmethod=' . &l:foldmethod
2682 \ . ' foldcolumn=' . &l:foldcolumn
2683 \ . ' foldlevel=' . &l:foldlevel
2684 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2685 if has('cursorbind')
2686 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2691 function! s:diffthis() abort
2693 let w:fugitive_diff_restore = s:diff_restore()
2698 function! s:diffoff() abort
2699 if exists('w:fugitive_diff_restore')
2700 execute w:fugitive_diff_restore
2701 unlet w:fugitive_diff_restore
2707 function! s:diffoff_all(dir) abort
2708 let curwin = winnr()
2709 for nr in range(1,winnr('$'))
2710 if getwinvar(nr,'&diff')
2712 execute nr.'wincmd w'
2713 let restorewinnr = 1
2715 if exists('b:git_dir') && b:git_dir ==# a:dir
2720 execute curwin.'wincmd w'
2723 function! s:CompareAge(mine, theirs) abort
2724 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2725 let mine = substitute(a:mine, '^:', '', '')
2726 let theirs = substitute(a:theirs, '^:', '', '')
2727 let my_score = get(scores, ':'.mine, 0)
2728 let their_score = get(scores, ':'.theirs, 0)
2729 if my_score || their_score
2730 return my_score < their_score ? -1 : my_score != their_score
2731 elseif mine ==# theirs
2734 let base = s:TreeChomp('merge-base', mine, theirs)
2737 elseif base ==# theirs
2740 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
2741 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
2742 return my_time < their_time ? -1 : my_time != their_time
2745 function! s:Diff(vert,keepfocus,...) abort
2746 let args = copy(a:000)
2748 if get(args, 0) =~# '^+'
2749 let post = remove(args, 0)[1:-1]
2751 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2752 let commit = s:DirCommitFile(@%)[1]
2753 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2754 if exists(':DiffGitCached')
2755 return 'DiffGitCached'
2756 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
2758 return 'echoerr ' . string("fugitive: error determining merge status of the current buffer")
2760 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2762 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2763 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2767 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2768 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2773 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2774 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2777 let arg = join(args, ' ')
2781 let file = s:Relative()
2783 let file = s:Relative(':0:')
2784 elseif arg =~# '^:/.'
2786 let file = fugitive#RevParse(arg).s:Relative(':')
2788 return 'echoerr v:errmsg'
2791 let file = s:Expand(arg)
2793 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2794 let file = file.s:Relative(':')
2797 let file = empty(commit) ? s:Relative(':0:') : s:Relative()
2800 let spec = s:Generate(file)
2801 let restore = s:diff_restore()
2802 if exists('+cursorbind')
2805 let w:fugitive_diff_restore = restore
2806 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2807 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2809 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2811 let &l:readonly = &l:readonly
2813 let w:fugitive_diff_restore = restore
2815 if getwinvar('#', '&diff')
2818 call feedkeys(winnr."\<C-W>w", 'n')
2823 return 'echoerr v:errmsg'
2827 " Section: :Gmove, :Gremove
2829 function! s:Move(force, rename, destination) abort
2830 if a:destination =~# '^\.\.\=\%(/\|$\)'
2831 let destination = simplify(getcwd() . '/' . a:destination)
2832 elseif a:destination =~# '^\a\+:\|^/'
2833 let destination = a:destination
2834 elseif a:destination =~# '^:/:\='
2835 let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
2836 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
2837 let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
2838 elseif a:destination =~# '^:(literal)'
2839 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
2841 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
2843 let destination = s:Tree() . '/' . a:destination
2845 let destination = s:Slash(destination)
2849 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
2851 let v:errmsg = 'fugitive: '.message
2852 return 'echoerr v:errmsg'
2854 if isdirectory(destination)
2855 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2857 call fugitive#ReloadStatus()
2858 if empty(s:DirCommitFile(@%)[1])
2859 if isdirectory(destination)
2860 return 'keepalt edit '.s:fnameescape(destination)
2862 return 'keepalt saveas! '.s:fnameescape(destination)
2865 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2869 function! s:RenameComplete(A,L,P) abort
2870 if a:A =~# '^[.:]\=/'
2871 return fugitive#PathComplete(a:A)
2873 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
2874 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2878 function! s:Remove(after, force) abort
2879 if s:DirCommitFile(@%)[1] ==# ''
2881 elseif s:DirCommitFile(@%)[1] ==# '0'
2882 let cmd = ['rm','--cached']
2884 let v:errmsg = 'fugitive: rm not supported here'
2885 return 'echoerr v:errmsg'
2888 let cmd += ['--force']
2890 let message = call('s:TreeChomp', cmd + ['--', expand('%:p')])
2892 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2893 return 'echoerr '.string(v:errmsg)
2895 call fugitive#ReloadStatus()
2896 return a:after . (a:force ? '!' : '')
2900 augroup fugitive_remove
2902 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2903 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2904 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2905 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2906 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2912 function! s:Keywordprg() abort
2913 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2914 if has('gui_running') && !has('win32')
2915 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2917 return s:UserCommand() . args . ' show'
2921 augroup fugitive_blame
2923 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2924 autocmd Syntax fugitiveblame call s:BlameSyntax()
2925 autocmd User Fugitive
2926 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2927 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2929 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2930 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2933 function! s:linechars(pattern) abort
2934 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2935 if exists('*synconcealed') && &conceallevel > 1
2936 for col in range(1, chars)
2937 let chars -= synconcealed(line('.'), col)[0]
2943 function! s:Blame(bang, line1, line2, count, mods, args) abort
2944 if exists('b:fugitive_blamed_bufnr')
2948 if empty(s:Relative('/'))
2949 call s:throw('file or blob required')
2951 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2952 call s:throw('unsupported option')
2954 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2955 let cmd = ['--no-pager', 'blame', '--show-number']
2957 let cmd += ['-L', a:line1 . ',' . a:line1]
2960 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2961 let cmd += [s:DirCommitFile(@%)[1]]
2963 let cmd += ['--contents', '-']
2965 let cmd += ['--', s:Relative('')]
2966 let basecmd = escape(s:Prepare(cmd), '!#%')
2968 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2970 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2972 execute cd s:fnameescape(tree)
2974 let error = tempname()
2975 let temp = error.'.fugitiveblame'
2977 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
2979 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
2982 execute cd s:fnameescape(cwd)
2986 call s:throw(join(readfile(error),"\n"))
2989 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
2990 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
2992 for winnr in range(winnr('$'),1,-1)
2993 call setwinvar(winnr, '&scrollbind', 0)
2994 if exists('+cursorbind')
2995 call setwinvar(winnr, '&cursorbind', 0)
2997 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
2998 execute winbufnr(winnr).'bdelete'
3001 let bufnr = bufnr('')
3002 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
3003 if exists('+cursorbind')
3004 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
3007 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
3010 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
3012 setlocal scrollbind nowrap nofoldenable
3013 if exists('+cursorbind')
3016 let top = line('w0') + &scrolloff
3017 let current = line('.')
3018 let temp = s:Resolve(temp)
3019 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
3020 exe 'keepalt leftabove vsplit '.temp
3021 let b:fugitive_blamed_bufnr = bufnr
3022 let b:fugitive_type = 'blame'
3023 let w:fugitive_leave = restore
3024 let b:fugitive_blame_arguments = join(a:args,' ')
3028 if exists('+cursorbind')
3031 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
3032 if exists('+concealcursor')
3033 setlocal concealcursor=nc conceallevel=2
3035 if exists('+relativenumber')
3036 setlocal norelativenumber
3038 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
3039 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
3040 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
3041 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
3042 nnoremap <buffer> <silent> gq :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete<Bar>if expand("%:p") =~# "^fugitive:[\\/][\\/]"<Bar>Gedit<Bar>endif','^-1','','')<CR>
3043 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3044 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
3045 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
3046 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
3047 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3048 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
3049 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
3050 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
3051 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
3052 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
3053 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
3059 execute cd s:fnameescape(cwd)
3064 return 'echoerr v:errmsg'
3068 function! s:BlameCommit(cmd, ...) abort
3069 let line = a:0 ? a:1 : getline('.')
3070 if line =~# '^0\{4,40\} '
3071 return 'echoerr ' . string('Not Committed Yet')
3073 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
3074 if cmd =~# '^echoerr'
3077 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
3078 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3080 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
3083 if a:cmd ==# 'pedit'
3086 if search('^diff .* b/\M'.escape(path,'\').'$','W')
3088 let head = line('.')
3089 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
3090 let top = +matchstr(getline('.'),' +\zs\d\+')
3091 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
3092 if lnum >= top && lnum <= top + len
3093 let offset = lnum - top
3101 while offset > 0 && line('.') < line('$')
3103 if getline('.') =~# '^[ +]'
3116 function! s:BlameJump(suffix) abort
3117 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
3118 if commit =~# '^0\+$'
3121 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
3122 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3124 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
3126 let args = b:fugitive_blame_arguments
3127 let offset = line('.') - line('w0')
3128 let bufnr = bufnr('%')
3129 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
3131 exe winnr.'wincmd w'
3133 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3138 if exists(':Gblame')
3139 execute 'Gblame '.args
3141 let delta = line('.') - line('w0') - offset
3143 execute 'normal! '.delta."\<C-E>"
3145 execute 'normal! '.(-delta)."\<C-Y>"
3152 let s:hash_colors = {}
3154 function! s:BlameSyntax() abort
3155 let b:current_syntax = 'fugitiveblame'
3156 let conceal = has('conceal') ? ' conceal' : ''
3157 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3158 syn match FugitiveblameBoundary "^\^"
3159 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3160 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3161 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3162 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3163 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3164 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3165 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3166 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3167 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3168 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
3169 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3170 hi def link FugitiveblameBoundary Keyword
3171 hi def link FugitiveblameHash Identifier
3172 hi def link FugitiveblameUncommitted Ignore
3173 hi def link FugitiveblameTime PreProc
3174 hi def link FugitiveblameLineNumber Number
3175 hi def link FugitiveblameOriginalFile String
3176 hi def link FugitiveblameOriginalLineNumber Float
3177 hi def link FugitiveblameShort FugitiveblameDelimiter
3178 hi def link FugitiveblameDelimiter Delimiter
3179 hi def link FugitiveblameNotCommittedYet Comment
3181 for lnum in range(1, line('$'))
3182 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3183 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3187 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3188 \ && empty(get(s:hash_colors, hash))
3189 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3190 let color = csapprox#per_component#Approximate(r, g, b)
3191 if color == 16 && &background ==# 'dark'
3194 let s:hash_colors[hash] = ' ctermfg='.color
3196 let s:hash_colors[hash] = ''
3198 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3200 call s:RehighlightBlame()
3203 function! s:RehighlightBlame() abort
3204 for [hash, cterm] in items(s:hash_colors)
3205 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3206 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3208 exe 'hi link FugitiveblameHash'.hash.' Identifier'
3215 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3217 let s:redirects = {}
3219 function! s:Browse(bang,line1,count,...) abort
3221 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3223 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3224 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3230 let rev = s:DirRev(@%)[1]
3233 let expanded = s:Relative()
3235 let expanded = s:Expand(rev)
3237 let cdir = fugitive#CommonDir(b:git_dir)
3238 for dir in ['tags/', 'heads/', 'remotes/']
3239 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3240 let expanded = '.git/refs/' . dir . expanded
3243 let full = s:Generate(expanded)
3245 if full =~? '^fugitive:'
3246 let [dir, commit, path] = s:DirCommitFile(full)
3247 if commit =~# '^:\=\d$'
3251 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3252 let branch = matchstr(expanded, '^[^:]*')
3256 let path = path[1:-1]
3257 elseif empty(s:Tree())
3258 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3261 let path = full[strlen(s:Tree())+1:-1]
3262 if path =~# '^\.git/'
3264 elseif isdirectory(full)
3270 if type ==# 'tree' && !empty(path)
3271 let path = s:sub(path, '/\=$', '/')
3273 if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3274 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3275 if body =~# '^\x\{40\}$'
3279 elseif body =~# '^ref: refs/'
3280 let path = '.git/' . matchstr(body,'ref: \zs.*')
3285 if path =~# '^\.git/refs/remotes/.'
3287 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3288 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3290 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3291 let path = '.git/refs/heads/'.merge
3293 elseif path =~# '^\.git/refs/heads/.'
3294 let branch = path[16:-1]
3295 elseif !exists('branch')
3296 let branch = FugitiveHead()
3299 let r = fugitive#Config('branch.'.branch.'.remote')
3300 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3301 if r ==# '.' && !empty(m)
3302 let r2 = fugitive#Config('branch.'.m.'.remote')
3305 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3311 if r ==# '.' || r ==# remote
3313 if path =~# '^\.git/refs/heads/.'
3314 let path = '.git/refs/heads/'.merge
3319 let line1 = a:count > 0 ? a:line1 : 0
3320 let line2 = a:count > 0 ? a:count : 0
3321 if empty(commit) && path !~# '^\.git/'
3322 if a:line1 && !a:count && !empty(merge)
3327 let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3328 let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3329 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3330 let blame_list = tempname()
3331 call writefile([commit, ''], blame_list, 'b')
3332 let blame_in = tempname()
3333 silent exe '%write' blame_in
3334 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path), "\n")
3336 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3337 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3338 let line1 = +matchstr(blame[0], blame_regex)
3339 let line2 = +matchstr(blame[-1], blame_regex)
3341 call s:throw("Can't browse to uncommitted change")
3348 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3351 while commit =~# '^ref: ' && i < 10
3352 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3360 let raw = fugitive#RemoteUrl(remote)
3365 if raw =~# '^https\=://' && s:executable('curl')
3366 if !has_key(s:redirects, raw)
3367 let s:redirects[raw] = matchstr(system('curl -I ' .
3368 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3369 \ 'Location: \zs\S\+\ze/info/refs?')
3371 if len(s:redirects[raw])
3372 let raw = s:redirects[raw]
3378 \ 'repo': fugitive#repo(),
3380 \ 'revision': 'No longer provided',
3387 for Handler in get(g:, 'fugitive_browse_handlers', [])
3388 let url = call(Handler, [copy(opts)])
3395 call s:throw("No Gbrowse handler found for '".raw."'")
3398 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3403 return 'echomsg '.string(url)
3404 elseif exists(':Browse') == 2
3405 return 'echomsg '.string(url).'|Browse '.url
3407 if !exists('g:loaded_netrw')
3408 runtime! autoload/netrw.vim
3410 if exists('*netrw#BrowseX')
3411 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3413 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3417 return 'echoerr v:errmsg'
3421 " Section: Go to file
3423 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3424 function! fugitive#MapCfile(...) abort
3425 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3426 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3427 if !exists('g:fugitive_no_maps')
3428 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3429 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3430 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3431 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3432 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3436 function! s:ContainingCommit() abort
3437 let commit = s:Owner(@%)
3438 return empty(commit) ? 'HEAD' : commit
3441 function! s:NavigateUp(count) abort
3442 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3446 let rev = matchstr(rev, '.*\ze/.\+', '')
3447 elseif rev =~# '.:.'
3448 let rev = matchstr(rev, '^.[^:]*:')
3461 function! fugitive#MapJumps(...) abort
3462 if get(b:, 'fugitive_type', '') ==# 'blob'
3463 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3465 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3468 let nowait = v:version >= 704 ? '<nowait>' : ''
3469 if get(b:, 'fugitive_type', '') ==# 'blob'
3470 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3471 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3472 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3473 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3475 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3476 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3477 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3478 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3480 exe "nnoremap <buffer> <silent>" nowait "- :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>NavigateUp(v:count1))<Bar> if getline(1) =~# '^tree \x\{40\}$' && empty(getline(2))<Bar>call search('^'.escape(expand('#:t'),'.*[]~\').'/\=$','wc')<Bar>endif<CR>"
3481 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3482 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3483 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3484 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3485 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3486 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3487 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3488 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3489 nmap <buffer> . <SID>: <Plug><cfile><Home>
3493 function! s:StatusCfile(...) abort
3495 let tree = FugitiveTreeForGitDir(b:git_dir)
3496 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3497 if getline('.') =~# '^.\=\trenamed:.* -> '
3498 return lead . matchstr(getline('.'),' -> \zs.*')
3499 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3500 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3501 elseif getline('.') =~# '^.\=\t.'
3502 return lead . matchstr(getline('.'),'\t\zs.*')
3503 elseif getline('.') =~# ': needs merge$'
3504 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3505 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3507 elseif getline('.') =~# '^\%(. \)\=On branch '
3508 return 'refs/heads/'.getline('.')[12:]
3509 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3510 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3516 function! fugitive#StatusCfile() abort
3517 let file = s:Generate(s:StatusCfile())
3518 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
3521 function! s:cfile() abort
3523 let myhash = s:DirRev(@%)[1]
3526 let myhash = fugitive#RevParse(myhash)
3531 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3532 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3535 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3537 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3538 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3540 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3541 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3543 return [treebase . s:sub(getline('.'),'/$','')]
3550 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3551 let ref = matchstr(getline('.'),'\x\{40\}')
3552 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3556 if getline('.') =~# '^ref: '
3557 let ref = strpart(getline('.'),5)
3559 elseif getline('.') =~# '^commit \x\{40\}\>'
3560 let ref = matchstr(getline('.'),'\x\{40\}')
3563 elseif getline('.') =~# '^parent \x\{40\}\>'
3564 let ref = matchstr(getline('.'),'\x\{40\}')
3565 let line = line('.')
3567 while getline(line) =~# '^parent '
3573 elseif getline('.') =~# '^tree \x\{40\}$'
3574 let ref = matchstr(getline('.'),'\x\{40\}')
3575 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3576 let ref = myhash.':'
3580 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3581 let ref = matchstr(getline('.'),'\x\{40\}')
3582 let type = matchstr(getline(line('.')+1),'type \zs.*')
3584 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3585 let ref = s:DirRev(@%)[1]
3587 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3588 let ref = matchstr(getline('.'),'\x\{40\}')
3589 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3591 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3592 let ref = getline('.')[4:]
3594 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3595 let type = getline('.')[0]
3596 let lnum = line('.') - 1
3598 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3599 if getline(lnum) =~# '^[ '.type.']'
3604 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3605 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3606 let dcmds = [offset, 'normal!zv']
3608 elseif getline('.') =~# '^rename from '
3609 let ref = 'a/'.getline('.')[12:]
3610 elseif getline('.') =~# '^rename to '
3611 let ref = 'b/'.getline('.')[10:]
3613 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3614 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3615 let offset = matchstr(getline('.'), '+\zs\d\+')
3617 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3618 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3619 let dcmd = 'Gdiff! +'.offset
3621 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3622 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3623 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3626 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3627 let line = getline(line('.')-1)
3628 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3629 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3632 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3633 let ref = getline('.')
3635 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3636 return [expand('<cword>')]
3651 let prefixes.a = myhash.'^:'
3652 let prefixes.b = myhash.':'
3654 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3656 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3659 if ref ==# '/dev/null'
3661 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3665 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3667 return [ref] + dcmds
3675 function! s:GF(mode) abort
3677 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3679 return 'echoerr v:errmsg'
3682 return 'G' . a:mode .
3683 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3684 \ s:fnameescape(results[0])
3686 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3692 function! fugitive#Cfile() abort
3694 let results = s:cfile()
3696 let cfile = expand('<cfile>')
3697 if &includeexpr =~# '\<v:fname\>'
3698 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3701 elseif len(results) > 1
3702 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3704 return pre . s:fnameescape(s:Generate(results[0]))
3707 " Section: Statusline
3709 function! fugitive#Statusline(...) abort
3710 if !exists('b:git_dir')
3714 let commit = s:DirCommitFile(@%)[1]
3716 let status .= ':' . commit[0:7]
3718 let status .= '('.FugitiveHead(7).')'
3719 return '[Git'.status.']'
3722 function! fugitive#statusline(...) abort
3723 return fugitive#Statusline()
3726 function! fugitive#head(...) abort
3727 if !exists('b:git_dir')
3731 return fugitive#Head(a:0 ? a:1 : 0)
3736 function! fugitive#Foldtext() abort
3737 if &foldmethod !=# 'syntax'
3741 let line_foldstart = getline(v:foldstart)
3742 if line_foldstart =~# '^diff '
3743 let [add, remove] = [-1, -1]
3745 for lnum in range(v:foldstart, v:foldend)
3746 let line = getline(lnum)
3747 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3748 let filename = line[6:-1]
3752 elseif line =~# '^-'
3754 elseif line =~# '^Binary '
3759 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3762 let filename = line_foldstart[5:-1]
3765 return 'Binary: '.filename
3767 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3769 elseif line_foldstart =~# '^# .*:$'
3770 let lines = getline(v:foldstart, v:foldend)
3771 call filter(lines, 'v:val =~# "^#\t"')
3772 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3773 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3774 return line_foldstart.' '.join(lines, ', ')
3779 function! fugitive#foldtext() abort
3780 return fugitive#Foldtext()
3783 augroup fugitive_folding
3785 autocmd User Fugitive
3786 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3787 \ set foldtext=fugitive#Foldtext() |
3791 " Section: Initialization
3793 function! fugitive#Init() abort
3794 if exists('#User#FugitiveBoot')
3796 let [save_mls, &modelines] = [&mls, 0]
3797 doautocmd User FugitiveBoot
3802 if !exists('g:fugitive_no_maps')
3803 call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3804 call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3806 if expand('%:p') =~# ':[\/][\/]'
3807 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3809 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3810 if filereadable(b:git_dir.'/tags')
3811 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3813 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3814 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3818 let [save_mls, &modelines] = [&mls, 0]
3819 call s:define_commands()
3820 doautocmd User Fugitive
3826 function! fugitive#is_git_dir(path) abort
3827 return FugitiveIsGitDir(a:path)
3830 function! fugitive#extract_git_dir(path) abort
3831 return FugitiveExtractGitDir(a:path)
3834 function! fugitive#detect(path) abort
3835 return FugitiveDetect(a:path)