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(...) abort
165 return call('fugitive#Prepare', a:000)
168 let s:git_versions = {}
169 function! fugitive#GitVersion(...) abort
170 if !has_key(s:git_versions, g:fugitive_git_executable)
171 let s:git_versions[g:fugitive_git_executable] = matchstr(system(g:fugitive_git_executable.' --version'), "\\S\\+\\ze\n")
173 return s:git_versions[g:fugitive_git_executable]
176 let s:commondirs = {}
177 function! fugitive#CommonDir(dir) abort
181 if !has_key(s:commondirs, a:dir)
182 if getfsize(a:dir . '/HEAD') < 10
183 let s:commondirs[a:dir] = ''
184 elseif filereadable(a:dir . '/commondir')
185 let dir = get(readfile(a:dir . '/commondir', 1), 0, '')
186 if dir =~# '^/\|^\a:/'
187 let s:commondirs[a:dir] = dir
189 let s:commondirs[a:dir] = simplify(a:dir . '/' . dir)
192 let s:commondirs[a:dir] = a:dir
195 return s:commondirs[a:dir]
198 function! s:Tree(...) abort
199 return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', ''))
202 function! s:PreparePathArgs(cmd, dir, literal) abort
203 let literal_supported = fugitive#GitVersion() !~# '^0\|^1\.[1-8]\.'
204 if a:literal && literal_supported
205 call insert(a:cmd, '--literal-pathspecs')
207 let split = index(a:cmd, '--')
211 for i in range(split + 1, len(a:cmd) - 1)
212 if type(a:cmd[i]) == type(0)
213 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
215 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
216 elseif !literal_supported
217 let a:cmd[i] = substitute(a:cmd[i], '^:\%(/\|([^)]*)\)\=:\=', './', '')
223 let s:prepare_env = {
224 \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
225 \ 'core.editor': 'GIT_EDITOR',
226 \ 'core.askpass': 'GIT_ASKPASS',
228 function! fugitive#Prepare(...) abort
230 return g:fugitive_git_executable
232 if type(a:1) ==# type([])
233 let cmd = a:000[1:-1] + a:1
235 let cmd = copy(a:000)
240 if cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
241 let dir = remove(cmd, 0)
242 elseif type(cmd[i]) ==# type(0)
243 let dir = getbufvar(remove(cmd, i), 'git_dir')
244 elseif cmd[i] ==# '-c' && len(cmd) > i + 1
245 let key = matchstr(cmd[i+1], '^[^=]*')
246 if has_key(s:prepare_env, tolower(key)) || key !~# '\.'
247 let var = get(s:prepare_env, tolower(key), key)
248 let val = matchstr(cmd[i+1], '=\zs.*')
250 let pre .= 'set ' . var . '=' . s:shellesc(val) . ' & '
252 let pre = (len(pre) ? pre : 'env ') . var . '=' . s:shellesc(val) . ' '
255 if fugitive#GitVersion() =~# '^0\|^1\.[1-7]\.' || cmd[i+1] !~# '\.'
256 call remove(cmd, i, i + 1)
260 elseif cmd[i] =~# '^--.*pathspecs$'
261 let explicit_pathspec_option = 1
262 if fugitive#GitVersion() =~# '^0\|^1\.[1-8]\.'
267 elseif cmd[i] !~# '^-'
274 let dir = get(b:, 'git_dir', '')
276 let tree = s:Tree(dir)
277 call s:PreparePathArgs(cmd, dir, !exists('explicit_pathspec_option'))
278 let args = join(map(copy(cmd), 's:shellesc(v:val)'))
279 if empty(tree) || index(cmd, '--') == len(cmd) - 1
280 let args = s:shellesc('--git-dir=' . dir) . ' ' . args
281 elseif fugitive#GitVersion() =~# '^0\|^1\.[1-8]\.'
282 let pre = 'cd ' . s:shellesc(tree) . (s:winshell() ? ' & ' : '; ') . pre
284 let args = '-C ' . s:shellesc(tree) . ' ' . args
286 return pre . g:fugitive_git_executable . ' ' . args
289 function! s:TreeChomp(...) abort
290 return s:sub(s:System(call('fugitive#Prepare', a:000)), '\n$', '')
293 function! fugitive#Head(...) abort
294 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
295 if empty(dir) || !filereadable(dir . '/HEAD')
298 let head = readfile(dir . '/HEAD')[0]
300 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
301 elseif head =~# '^\x\{40\}$'
302 let len = a:0 ? a:1 : 0
303 return len < 0 ? head : len ? head[0:len-1] : ''
309 function! fugitive#RevParse(rev, ...) abort
310 let hash = system(s:Prepare(a:0 ? a:1 : b:git_dir, 'rev-parse', '--verify', a:rev, '--'))[0:-2]
311 if !v:shell_error && hash =~# '^\x\{40\}$'
314 call s:throw('rev-parse '.a:rev.': '.hash)
317 function! fugitive#Config(name, ...) abort
318 let cmd = fugitive#Prepare(a:0 ? a:1 : get(b:, 'git_dir', ''), '--no-literal-pathspecs', 'config', '--get', '--', a:name)
319 let out = matchstr(system(cmd), "[^\n]*")
320 return v:shell_error ? '' : out
323 function! s:Remote(dir) abort
324 let head = FugitiveHead(0, a:dir)
325 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
327 while remote ==# '.' && i > 0
328 let head = matchstr(fugitive#Config('branch.' . head . '.merge'), 'refs/heads/\zs.*')
329 let remote = len(head) ? fugitive#Config('branch.' . head . '.remote') : ''
332 return remote =~# '^\.\=$' ? 'origin' : remote
335 function! fugitive#RemoteUrl(...) abort
336 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
337 let remote = !a:0 || a:1 =~# '^\.\=$' ? s:Remote(dir) : a:1
338 if fugitive#GitVersion() =~# '^[01]\.\|^2\.[0-6]\.'
339 return fugitive#Config('remote.' . remote . '.url')
341 let cmd = s:Prepare(dir, 'remote', 'get-url', remote, '--')
342 let out = substitute(system(cmd), "\n$", '', '')
343 return v:shell_error ? '' : out
346 " Section: Repository Object
348 function! s:add_methods(namespace, method_names) abort
349 for name in a:method_names
350 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
355 function! s:command(definition) abort
356 let s:commands += [a:definition]
359 function! s:define_commands() abort
360 for command in s:commands
361 exe 'command! -buffer '.command
365 let s:repo_prototype = {}
368 function! fugitive#repo(...) abort
369 let dir = a:0 ? a:1 : (exists('b:git_dir') && b:git_dir !=# '' ? b:git_dir : FugitiveExtractGitDir(expand('%:p')))
371 if has_key(s:repos, dir)
372 let repo = get(s:repos, dir)
374 let repo = {'git_dir': dir}
375 let s:repos[dir] = repo
377 return extend(repo, s:repo_prototype, 'keep')
379 call s:throw('not a git repository: '.expand('%:p'))
382 function! s:repo_dir(...) dict abort
383 return join([self.git_dir]+a:000,'/')
386 function! s:repo_tree(...) dict abort
387 let dir = s:Tree(self.git_dir)
389 call s:throw('no work tree')
391 return join([dir]+a:000,'/')
395 function! s:repo_bare() dict abort
396 if self.dir() =~# '/\.git$'
399 return s:Tree(self.git_dir) ==# ''
403 function! s:repo_route(object) dict abort
404 return fugitive#Route(a:object, self.git_dir)
407 function! s:repo_translate(rev) dict abort
408 return s:Slash(fugitive#Route(substitute(a:rev, '^/', ':(top)', ''), self.git_dir))
411 function! s:repo_head(...) dict abort
412 return fugitive#Head(a:0 ? a:1 : 0, self.git_dir)
415 call s:add_methods('repo',['dir','tree','bare','route','translate','head'])
417 function! s:repo_prepare(...) dict abort
418 return call('fugitive#Prepare', [self.git_dir] + a:000)
421 function! s:repo_git_command(...) dict abort
422 let git = s:UserCommand() . ' --git-dir='.s:shellesc(self.git_dir)
423 return git.join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
426 function! s:repo_git_chomp(...) dict abort
427 let git = g:fugitive_git_executable . ' --git-dir='.s:shellesc(self.git_dir)
428 let output = git . join(map(copy(a:000),'" ".s:shellesc(v:val)'),'')
429 return s:sub(s:System(output),'\n$','')
432 function! s:repo_git_chomp_in_tree(...) dict abort
433 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
436 execute cd s:fnameescape(self.tree())
437 return call(self.git_chomp, a:000, self)
439 execute cd s:fnameescape(dir)
443 function! s:repo_rev_parse(rev) dict abort
444 return fugitive#RevParse(a:rev, self.git_dir)
447 call s:add_methods('repo',['prepare','git_command','git_chomp','git_chomp_in_tree','rev_parse'])
449 function! s:repo_superglob(base) dict abort
450 return map(fugitive#Complete(a:base, self.git_dir), 'substitute(v:val, ''\\\(.\)'', ''\1'', "g")')
453 call s:add_methods('repo',['superglob'])
455 function! s:repo_config(name) dict abort
456 return fugitive#Config(a:name, self.git_dir)
459 function! s:repo_user() dict abort
460 let username = self.config('user.name')
461 let useremail = self.config('user.email')
462 return username.' <'.useremail.'>'
465 call s:add_methods('repo',['config', 'user'])
469 function! s:DirCommitFile(path) abort
470 let vals = matchlist(s:Slash(a:path), '\c^fugitive:\%(//\)\=\(.\{-\}\)\%(//\|::\)\(\x\{40\}\|[0-3]\)\(/.*\)\=$')
477 function! s:DirRev(url) abort
478 let [dir, commit, file] = s:DirCommitFile(a:url)
479 return [dir, (commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
482 function! s:Owner(path, ...) abort
483 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
487 let [pdir, commit, file] = s:DirCommitFile(a:path)
488 if s:cpath(dir, pdir) && commit =~# '^\x\{40\}$'
491 let path = fnamemodify(a:path, ':p')
492 if s:cpath(dir . '/', path[0 : len(dir)]) && a:path =~# 'HEAD$'
493 return strpart(path, len(dir) + 1)
495 let refs = fugitive#CommonDir(dir) . '/refs'
496 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
497 return strpart(path, len(refs) - 4)
502 function! fugitive#Real(url) abort
506 let [dir, commit, file] = s:DirCommitFile(a:url)
508 let tree = s:Tree(dir)
509 return s:PlatformSlash((len(tree) ? tree : dir) . file)
511 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
512 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
513 let url = {pre}Real(a:url)
515 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
517 return s:PlatformSlash(empty(url) ? a:url : url)
520 function! fugitive#Path(url, ...) abort
524 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
525 let tree = s:Tree(dir)
527 return fugitive#Real(a:url)
529 let path = s:Slash(fugitive#Real(a:url))
532 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
533 if s:cpath(cwd . '/', path[0 : len(cwd)])
534 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
537 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
539 let cwd = fnamemodify(cwd, ':h')
542 return a:1[0:-2] . path
544 let url = s:Slash(fnamemodify(a:url, ':p'))
545 if url =~# '/$' && s:Slash(a:url) !~# '/$'
548 let [argdir, commit, file] = s:DirCommitFile(a:url)
549 if len(argdir) && s:cpath(argdir) !=# s:cpath(dir)
551 elseif len(dir) && s:cpath(url[0 : len(dir)]) ==# s:cpath(dir . '/')
552 let file = '/.git'.url[strlen(dir) : -1]
553 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
554 let file = url[len(tree) : -1]
555 elseif s:cpath(url) ==# s:cpath(tree) || len(argdir) && empty(file)
558 if empty(file) && a:1 =~# '^$\|^[.:]/$'
559 return s:Slash(fugitive#Real(a:url))
561 return substitute(file, '^/', a:1, '')
564 function! s:Relative(...) abort
565 return fugitive#Path(@%, a:0 ? a:1 : ':(top)')
568 function! fugitive#Route(object, ...) abort
569 if type(a:object) == type(0)
570 let name = bufname(a:object)
571 return s:PlatformSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
572 elseif a:object =~# '^[~$]'
573 let prefix = matchstr(a:object, '^[~$]\i*')
574 let owner = expand(prefix)
575 return s:PlatformSlash((len(owner) ? owner : prefix) . strpart(a:object, len(prefix)))
576 elseif s:Slash(a:object) =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
577 return s:PlatformSlash(a:object)
578 elseif s:Slash(a:object) =~# '^\.\.\=\%(/\|$\)'
579 return s:PlatformSlash(simplify(getcwd() . '/' . a:object))
581 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
583 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs.*', '', '')
584 let dir = FugitiveExtractGitDir(file)
586 return fnamemodify(len(file) ? file : a:object, ':p')
589 let rev = s:Slash(a:object)
590 let tree = s:Tree(dir)
591 let base = len(tree) ? tree : 'fugitive://' . dir . '//0'
593 let f = len(tree) ? tree . '/.git' : dir
594 elseif rev =~# '^\.git/'
595 let f = substitute(rev, '^\.git', '', '')
596 let cdir = fugitive#CommonDir(dir)
597 if f =~# '^/\.\./\.\.\%(/\|$\)'
598 let f = simplify(len(tree) ? tree . f[3:-1] : dir . f)
599 elseif f =~# '^/\.\.\%(/\|$\)'
600 let f = base . f[3:-1]
601 elseif cdir !=# dir && (
602 \ f =~# '^/\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
603 \ f !~# '^/logs$\|/\w*HEAD$' && getftime(dir . f) < 0 && getftime(cdir . f) >= 0)
604 let f = simplify(cdir . f)
606 let f = simplify(dir . f)
610 elseif rev =~# '^\.\%(/\|$\)'
611 let f = base . rev[1:-1]
612 elseif rev =~# '^::\%(/\|\a\+\:\)'
614 elseif rev =~# '^::\.\.\=\%(/\|$\)'
615 let f = simplify(getcwd() . '/' . rev[2:-1])
617 let f = base . '/' . rev[2:-1]
618 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
619 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
620 if s:cpath(base . '/', (f . '/')[0 : len(base)])
621 let f = 'fugitive://' . dir . '//' . +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1)
623 let altdir = FugitiveExtractGitDir(f)
624 if len(altdir) && !s:cpath(dir, altdir)
625 return fugitive#Route(a:object, altdir)
628 elseif rev =~# '^:[0-3]:'
629 let f = 'fugitive://' . dir . '//' . rev[1] . '/' . rev[3:-1]
631 if $GIT_INDEX_FILE =~# '/[^/]*index[^/]*\.lock$' && s:cpath(fnamemodify($GIT_INDEX_FILE,':p')[0:strlen(dir)]) ==# s:cpath(dir . '/') && filereadable($GIT_INDEX_FILE)
632 let f = fnamemodify($GIT_INDEX_FILE, ':p')
634 let f = dir . '/index'
636 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
637 let f = base . '/' . matchstr(rev, ')\zs.*')
638 elseif rev =~# '^:/\@!'
639 let f = 'fugitive://' . dir . '//0/' . rev[1:-1]
641 if rev =~# 'HEAD$\|^refs/' && rev !~# ':'
642 let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir
643 if filereadable(cdir . '/' . rev)
644 let f = simplify(cdir . '/' . rev)
648 let commit = substitute(matchstr(rev, '^[^:]\+\|^:.*'), '^@\%($\|[~^]\|@{\)\@=', 'HEAD', '')
649 let file = substitute(matchstr(rev, '^[^:]\+\zs:.*'), '^:', '/', '')
650 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
651 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
652 if s:cpath(base . '/', (file . '/')[0 : len(base)])
653 let file = '/' . strpart(file, len(base) + 1)
655 let altdir = FugitiveExtractGitDir(file)
656 if len(altdir) && !s:cpath(dir, altdir)
657 return fugitive#Route(a:object, altdir)
662 if commit !~# '^[0-9a-f]\{40\}$'
663 let commit = system(s:Prepare(dir, 'rev-parse', '--verify', commit, '--'))[0:-2]
664 let commit = v:shell_error ? '' : commit
667 let f = 'fugitive://' . dir . '//' . commit . file
669 let f = base . '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', '')
673 return s:PlatformSlash(f)
676 function! s:Generate(rev, ...) abort
677 let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
678 let tree = s:Tree(dir)
680 if a:rev =~# '^/\.git\%(/\|$\)'
681 let object = a:rev[1:-1]
682 elseif a:rev =~# '^/' && len(tree) && getftime(tree . a:rev) >= 0 && getftime(a:rev) < 0
683 let object = ':(top)' . a:rev[1:-1]
685 return fugitive#Route(object, dir)
688 function! s:DotRelative(path) abort
690 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
691 if s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
692 return '.' . strpart(path, len(cwd))
697 function! fugitive#Object(...) abort
698 let dir = a:0 > 1 ? a:2 : get(b:, 'git_dir', '')
699 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
700 if s:cpath(dir) !=# s:cpath(fdir)
703 let tree = s:Tree(dir)
704 if empty(rev) && empty(tree)
706 let rev = fugitive#Path(a:0 ? a:1 : @%, './', dir)
707 let cdir = fugitive#CommonDir(dir)
708 if rev =~# '^\./\.git/refs/\%(tags\|heads\|remotes\)/.\|^\./\.git/\w*HEAD$'
710 elseif s:cpath(cdir . '/refs/', rev[0 : len(cdir)])
711 let rev = strpart(rev, len(cdir)+1)
712 elseif rev =~# '^\./.git\%(/\|$\)'
713 return fnamemodify(a:0 ? a:1 : @%, ':p')
716 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
719 return tree . rev[1:-1]
723 let s:var = '\%(%\|#<\=\d\+\|##\=\)'
724 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
725 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
727 function! s:BufName(var) abort
729 return bufname(get(b:, 'fugitive_blamed_bufnr', ''))
730 elseif a:var =~# '^#\d*$'
731 let nr = getbufvar(+a:var[1:-1], 'fugitive_blamed_bufnr', '')
732 return bufname(nr ? nr : +a:var[1:-1])
738 function! s:ExpandVar(other, var, flags, esc) abort
741 elseif a:other =~# '^!'
742 let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
743 let owner = s:Owner(buffer)
744 return len(owner) ? owner : '@'
747 let file = s:DotRelative(fugitive#Real(s:BufName(a:var)))
749 let flag = matchstr(flags, s:flag)
750 let flags = strpart(flags, len(flag))
752 let file = s:DotRelative(file)
754 let file = fnamemodify(file, flag)
757 let file = s:Slash(file)
758 return (len(a:esc) ? shellescape(file) : file)
761 function! s:Expand(rev) abort
762 if a:rev =~# '^:[0-3]$'
763 let file = a:rev . s:Relative(':')
764 elseif a:rev =~# '^-'
765 let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
766 elseif a:rev =~# '^@{'
767 let file = 'HEAD' . a:rev. s:Relative(':')
768 elseif a:rev =~# '^\^[0-9~^{]\|^\~[0-9~^]'
769 let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
770 let file = commit . a:rev . s:Relative(':')
774 return substitute(file,
775 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
776 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"")', 'g')
779 function! fugitive#Expand(object) abort
780 return substitute(a:object,
781 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
782 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
785 function! s:ShellExpand(cmd) abort
786 return substitute(a:cmd, '\(\\[!#%]\|!\d*\)\|' . s:expand,
787 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
792 function! s:TreeInfo(dir, commit) abort
793 if a:commit =~# '^:\=[0-3]$'
794 let index = get(s:indexes, a:dir, [])
795 let newftime = getftime(a:dir . '/index')
796 if get(index, 0, -1) < newftime
797 let out = system(fugitive#Prepare(a:dir, 'ls-files', '--stage', '--'))
798 let s:indexes[a:dir] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
802 for line in split(out, "\n")
803 let [info, filename] = split(line, "\t")
804 let [mode, sha, stage] = split(info, '\s\+')
805 let s:indexes[a:dir][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
806 while filename =~# '/'
807 let filename = substitute(filename, '/[^/]*$', '', '')
808 let s:indexes[a:dir][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
812 return [get(s:indexes[a:dir][1], a:commit[-1:-1], {}), newftime]
813 elseif a:commit =~# '^\x\{40\}$'
814 if !has_key(s:trees, a:dir)
815 let s:trees[a:dir] = {}
817 if !has_key(s:trees[a:dir], a:commit)
818 let ftime = +system(fugitive#Prepare(a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'))
820 let s:trees[a:dir][a:commit] = [{}, -1]
821 return s:trees[a:dir][a:commit]
823 let s:trees[a:dir][a:commit] = [{}, +ftime]
824 let out = system(fugitive#Prepare(a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'))
826 return s:trees[a:dir][a:commit]
828 for line in split(out, "\n")
829 let [info, filename] = split(line, "\t")
830 let [mode, type, sha, size] = split(info, '\s\+')
831 let s:trees[a:dir][a:commit][0][filename] = [ftime, mode, type, sha, +size, filename]
834 return s:trees[a:dir][a:commit]
839 function! s:PathInfo(url) abort
840 let [dir, commit, file] = s:DirCommitFile(a:url)
841 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
842 return [-1, '000000', '', '', -1]
844 let path = substitute(file[1:-1], '/*$', '', '')
845 let [tree, ftime] = s:TreeInfo(dir, commit)
846 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
847 if empty(entry) || file =~# '/$' && entry[1] !=# 'tree'
848 return [-1, '000000', '', '', -1]
854 function! fugitive#simplify(url) abort
855 let [dir, commit, file] = s:DirCommitFile(a:url)
859 if file =~# '/\.\.\%(/\|$\)'
860 let tree = s:Tree(dir)
862 let path = simplify(tree . file)
863 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
864 return s:PlatformSlash(path)
868 return s:PlatformSlash('fugitive://' . simplify(dir) . '//' . commit . simplify(file))
871 function! fugitive#resolve(url) abort
872 let url = fugitive#simplify(a:url)
873 if url =~? '^fugitive:'
880 function! fugitive#getftime(url) abort
881 return s:PathInfo(a:url)[0]
884 function! fugitive#getfsize(url) abort
885 let entry = s:PathInfo(a:url)
886 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
887 let dir = s:DirCommitFile(a:url)[0]
888 let size = +system(s:Prepare(dir, 'cat-file', '-s', entry[3]))
889 let entry[4] = v:shell_error ? -1 : size
894 function! fugitive#getftype(url) abort
895 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
898 function! fugitive#filereadable(url) abort
899 return s:PathInfo(a:url)[2] ==# 'blob'
902 function! fugitive#filewritable(url) abort
903 let [dir, commit, file] = s:DirCommitFile(a:url)
904 if commit !~# '^\d$' || !filewritable(dir . '/index')
907 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
910 function! fugitive#isdirectory(url) abort
911 return s:PathInfo(a:url)[2] ==# 'tree'
914 function! fugitive#getfperm(url) abort
915 let [dir, commit, file] = s:DirCommitFile(a:url)
916 let perm = getfperm(dir)
917 let fperm = s:PathInfo(a:url)[1]
918 if fperm ==# '040000'
922 let perm = tr(perm, 'x', '-')
925 let perm = tr(perm, 'rw', '--')
928 let perm = tr(perm, 'w', '-')
930 return perm ==# '---------' ? '' : perm
933 function! fugitive#setfperm(url, perm) abort
934 let [dir, commit, file] = s:DirCommitFile(a:url)
935 let entry = s:PathInfo(a:url)
936 let perm = fugitive#getfperm(a:url)
937 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
938 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
941 call system(s:Prepare(dir, 'update-index', '--index-info'),
942 \ (a:perm =~# 'x' ? '000755 ' : '000644 ') . entry[3] . ' ' . commit . "\t" . file[1:-1])
943 return v:shell_error ? -1 : 0
946 function! s:TempCmd(out, cmd) abort
949 let cmd = (type(a:cmd) == type([]) ? call('s:Prepare', a:cmd) : a:cmd)
950 let redir = ' > ' . a:out
952 let cmd_escape_char = &shellxquote == '(' ? '^' : '^^^'
953 return s:System('cmd /c "' . prefix . s:gsub(cmd, '[<>]', cmd_escape_char . '&') . redir . '"')
954 elseif &shell =~# 'fish'
955 return s:System(' begin;' . prefix . cmd . redir . ';end ')
957 return s:System(' (' . prefix . cmd . redir . ') ')
962 if !exists('s:blobdirs')
965 function! s:BlobTemp(url) abort
966 let [dir, commit, file] = s:DirCommitFile(a:url)
970 if !has_key(s:blobdirs, dir)
971 let s:blobdirs[dir] = tempname()
973 let tempfile = s:blobdirs[dir] . '/' . commit . file
974 let tempparent = fnamemodify(tempfile, ':h')
975 if !isdirectory(tempparent)
976 call mkdir(tempparent, 'p')
978 if commit =~# '^\d$' || !filereadable(tempfile)
979 let rev = s:DirRev(a:url)[1]
980 let command = s:Prepare(dir, 'cat-file', 'blob', rev)
981 call s:TempCmd(tempfile, command)
983 call delete(tempfile)
987 return s:Resolve(tempfile)
990 function! fugitive#readfile(url, ...) abort
991 let entry = s:PathInfo(a:url)
992 if entry[2] !=# 'blob'
995 let temp = s:BlobTemp(a:url)
999 return call('readfile', [temp] + a:000)
1002 function! fugitive#writefile(lines, url, ...) abort
1003 let url = type(a:url) ==# type('') ? a:url : ''
1004 let [dir, commit, file] = s:DirCommitFile(url)
1005 let entry = s:PathInfo(url)
1006 if commit =~# '^\d$' && entry[2] !=# 'tree'
1007 let temp = tempname()
1008 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
1009 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
1011 call call('writefile', [a:lines, temp] + a:000)
1012 let hash = system(s:Prepare(dir, 'hash-object', '-w', temp))[0:-2]
1013 let mode = len(entry[1]) ? entry[1] : '100644'
1014 if !v:shell_error && hash =~# '^\x\{40\}$'
1015 call system(s:Prepare(dir, 'update-index', '--index-info'),
1016 \ mode . ' ' . hash . ' ' . commit . "\t" . file[1:-1])
1022 return call('writefile', [a:lines, a:url] + a:000)
1026 \ '/**/': '/\%([^./][^/]*/\)*',
1027 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
1028 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
1030 \ '/*': '/[^/.][^/]*',
1033 function! fugitive#glob(url, ...) abort
1034 let [dirglob, commit, glob] = s:DirCommitFile(a:url)
1035 let append = matchstr(glob, '/*$')
1036 let glob = substitute(glob, '/*$', '', '')
1037 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\^$]', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
1039 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
1040 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(dir . '/HEAD')
1043 let files = items(s:TreeInfo(dir, commit)[0])
1045 call filter(files, 'v:val[1][2] ==# "tree"')
1047 call map(files, 'v:val[0]')
1048 call filter(files, 'v:val =~# pattern')
1049 let prepend = 'fugitive://' . dir . '//' . substitute(commit, '^:', '', '') . '/'
1051 call map(files, 's:PlatformSlash(prepend . v:val . append)')
1052 call extend(results, files)
1057 return join(results, "\n")
1061 function! fugitive#delete(url, ...) abort
1062 let [dir, commit, file] = s:DirCommitFile(a:url)
1063 if a:0 && len(a:1) || commit !~# '^\d$'
1066 let entry = s:PathInfo(a:url)
1067 if entry[2] !=# 'blob'
1070 call system(s:Prepare(dir, 'update-index', '--index-info'),
1071 \ '000000 0000000000000000000000000000000000000000 ' . commit . "\t" . file[1:-1])
1072 return v:shell_error ? -1 : 0
1075 " Section: Buffer Object
1077 let s:buffer_prototype = {}
1079 function! fugitive#buffer(...) abort
1080 let buffer = {'#': bufnr(a:0 ? a:1 : '%')}
1081 call extend(buffer, s:buffer_prototype, 'keep')
1082 if buffer.getvar('git_dir') !=# ''
1085 call s:throw('not a git repository: '.bufname(buffer['#']))
1088 function! s:buffer_getvar(var) dict abort
1089 return getbufvar(self['#'],a:var)
1092 function! s:buffer_getline(lnum) dict abort
1093 return get(getbufline(self['#'], a:lnum), 0, '')
1096 function! s:buffer_repo() dict abort
1097 return fugitive#repo(self.getvar('git_dir'))
1100 function! s:buffer_type(...) dict abort
1101 if !empty(self.getvar('fugitive_type'))
1102 let type = self.getvar('fugitive_type')
1103 elseif fnamemodify(self.spec(),':p') =~# '\.git/refs/\|\.git/\w*HEAD$'
1105 elseif self.getline(1) =~ '^tree \x\{40\}$' && self.getline(2) == ''
1107 elseif self.getline(1) =~ '^\d\{6\} \w\{4\} \x\{40\}\>\t'
1109 elseif self.getline(1) =~ '^\d\{6\} \x\{40\}\> \d\t'
1111 elseif isdirectory(self.spec())
1112 let type = 'directory'
1113 elseif self.spec() == ''
1119 return !empty(filter(copy(a:000),'v:val ==# type'))
1127 function! s:buffer_spec() dict abort
1128 let bufname = bufname(self['#'])
1130 for i in split(bufname,'[^:]\zs\\')
1131 let retval = fnamemodify((retval==''?'':retval.'\').i,':.')
1133 return s:Slash(fnamemodify(retval,':p'))
1138 function! s:buffer_spec() dict abort
1139 let bufname = bufname(self['#'])
1140 return s:Slash(bufname == '' ? '' : fnamemodify(bufname,':p'))
1145 function! s:buffer_name() dict abort
1149 function! s:buffer_commit() dict abort
1150 return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*')
1153 function! s:buffer_relative(...) dict abort
1154 let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*')
1156 let rev = s:sub(rev,'\w*','')
1157 elseif s:cpath(self.spec()[0 : len(self.repo().dir())]) ==#
1158 \ s:cpath(self.repo().dir() . '/')
1159 let rev = '/.git'.self.spec()[strlen(self.repo().dir()) : -1]
1160 elseif !self.repo().bare() &&
1161 \ s:cpath(self.spec()[0 : len(self.repo().tree())]) ==#
1162 \ s:cpath(self.repo().tree() . '/')
1163 let rev = self.spec()[strlen(self.repo().tree()) : -1]
1165 return s:sub(s:sub(rev,'.\zs/$',''),'^/',a:0 ? a:1 : '')
1168 function! s:buffer_path(...) dict abort
1170 return self.relative(a:1)
1172 return self.relative()
1175 call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','commit','path','relative'])
1177 " Section: Completion
1179 function! s:GlobComplete(lead, pattern) abort
1181 let results = glob(a:lead . a:pattern, 0, 1)
1183 let results = split(glob(a:lead . a:pattern), "\n")
1185 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
1186 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
1190 function! fugitive#PathComplete(base, ...) abort
1191 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1192 let tree = s:Tree(dir) . '/'
1193 let strip = '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\|:(literal)\)'
1194 let base = substitute(a:base, strip, '', '')
1195 if base =~# '^\.git/'
1196 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
1197 let matches = s:GlobComplete(dir . '/', pattern)
1198 let cdir = fugitive#CommonDir(dir)
1199 if len(cdir) && s:cpath(dir) !=# s:cpath(cdir)
1200 call extend(matches, s:GlobComplete(cdir . '/', pattern))
1202 call s:Uniq(matches)
1203 call map(matches, "'.git/' . v:val")
1204 elseif base =~# '^\~/'
1205 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
1206 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/\|^:(literal)'
1207 let matches = s:GlobComplete('', base . '*')
1208 elseif len(tree) > 1
1209 let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*')
1213 call map(matches, 's:fnameescape(s:Slash(matchstr(a:base, strip) . v:val))')
1217 function! fugitive#Complete(base, ...) abort
1218 let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '')
1219 let cwd = a:0 == 1 ? s:Tree(dir) : getcwd()
1220 let tree = s:Tree(dir) . '/'
1222 if len(tree) > 1 && s:cpath(tree, cwd[0 : len(tree) - 1])
1223 let subdir = strpart(cwd, len(tree)) . '/'
1226 if a:base =~# '^\.\=/\|^:(' || a:base !~# ':'
1228 if a:base =~# '^refs/'
1229 let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:Slash(v:val)')
1230 elseif a:base !~# '^\.\=/\|^:('
1231 let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/']
1232 let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n"))
1233 if filereadable(fugitive#CommonDir(dir) . '/refs/stash')
1234 let heads += ["stash"]
1235 let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n"))
1237 call filter(heads,'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1238 let results += heads
1240 call map(results, 's:fnameescape(v:val)')
1242 let results += a:0 == 1 ? fugitive#PathComplete(a:base, dir) : fugitive#PathComplete(a:base)
1246 elseif a:base =~# '^:'
1247 let entries = split(s:TreeChomp(['ls-files','--stage'], dir),"\n")
1248 if a:base =~# ':\./'
1249 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
1251 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
1252 if a:base !~# '^:[0-3]\%(:\|$\)'
1253 call filter(entries,'v:val[1] == "0"')
1254 call map(entries,'v:val[2:-1]')
1258 let tree = matchstr(a:base, '.*[:/]')
1259 let entries = split(s:TreeChomp(['ls-tree', substitute(tree, ':\zs\./', '\=subdir', '')], dir),"\n")
1260 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
1261 call map(entries,'tree.s:sub(v:val,".*\t","")')
1264 call filter(entries, 'v:val[ 0 : strlen(a:base)-1 ] ==# a:base')
1265 return map(entries, 's:fnameescape(v:val)')
1268 " Section: Buffer auto-commands
1270 function! s:ReplaceCmd(cmd, ...) abort
1271 let temp = tempname()
1272 let err = s:TempCmd(temp, a:cmd)
1274 call s:throw((len(err) ? err : filereadable(temp) ? join(readfile(temp), ' ') : 'unknown error running ' . a:cmd))
1276 let temp = s:Resolve(temp)
1277 let fn = expand('%:p')
1278 silent exe 'doau BufReadPre '.s:fnameescape(fn)
1279 silent exe 'keepalt file '.temp
1282 silent noautocmd edit!
1288 silent exe 'keepalt file '.s:fnameescape(fn)
1289 catch /^Vim\%((\a\+)\)\=:E302:/
1292 if s:cpath(fnamemodify(bufname('$'), ':p'), temp)
1293 silent execute 'bwipeout '.bufnr('$')
1295 silent exe 'doau BufReadPost '.s:fnameescape(fn)
1299 function! fugitive#BufReadStatus() abort
1300 let amatch = s:Slash(expand('%:p'))
1301 if !exists('b:fugitive_display_format')
1302 let b:fugitive_display_format = filereadable(expand('%').'.lock')
1304 let b:fugitive_display_format = b:fugitive_display_format % 2
1305 let b:fugitive_type = 'index'
1307 let cmd = [fnamemodify(amatch, ':h')]
1308 setlocal noro ma nomodeline
1309 if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
1310 let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
1312 if b:fugitive_display_format
1313 let cmd += ['ls-files', '--stage']
1316 \ '-c', 'status.displayCommentPrefix=true',
1317 \ '-c', 'color.status=false',
1318 \ '-c', 'status.short=false',
1321 call s:ReplaceCmd(call('fugitive#Prepare', cmd), 1)
1322 if b:fugitive_display_format
1323 if &filetype !=# 'git'
1328 if &filetype !=# 'gitcommit'
1329 set filetype=gitcommit
1331 set foldtext=fugitive#Foldtext()
1333 setlocal readonly nomodifiable nomodified noswapfile
1334 if &bufhidden ==# ''
1335 setlocal bufhidden=delete
1337 call fugitive#MapJumps()
1338 let nowait = v:version >= 704 ? '<nowait>' : ''
1341 nnoremap <buffer> <silent> <C-N> :<C-U>execute <SID>StageNext(v:count1)<CR>
1342 nnoremap <buffer> <silent> <C-P> :<C-U>execute <SID>StagePrevious(v:count1)<CR>
1343 exe "nnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
1344 exe "xnoremap <buffer> <silent>" nowait "- :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
1345 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
1346 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
1347 nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
1348 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>
1349 nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
1350 nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
1351 nnoremap <buffer> <silent> ce :<C-U>Gcommit --amend --no-edit<CR>
1352 nnoremap <buffer> <silent> cw :<C-U>Gcommit --amend --only<CR>
1353 nnoremap <buffer> <silent> cva :<C-U>Gcommit -v --amend<CR>
1354 nnoremap <buffer> <silent> cvc :<C-U>Gcommit -v<CR>
1355 nnoremap <buffer> <silent> D :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1356 nnoremap <buffer> <silent> dd :<C-U>execute <SID>StageDiff('Gdiff')<CR>
1357 nnoremap <buffer> <silent> dh :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1358 nnoremap <buffer> <silent> ds :<C-U>execute <SID>StageDiff('Gsdiff')<CR>
1359 nnoremap <buffer> <silent> dp :<C-U>execute <SID>StageDiffEdit()<CR>
1360 nnoremap <buffer> <silent> dv :<C-U>execute <SID>StageDiff('Gvdiff')<CR>
1361 nnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1362 xnoremap <buffer> <silent> p :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1363 nnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>
1364 xnoremap <buffer> <silent> P :<C-U>execute <SID>StagePatch(line("'<"),line("'>"))<CR>
1365 nnoremap <buffer> <silent> q :<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>
1366 nnoremap <buffer> <silent> r :<C-U>edit<CR>
1367 nnoremap <buffer> <silent> R :<C-U>edit<CR>
1368 nnoremap <buffer> <silent> U :<C-U>execute <SID>StageUndo()<CR>
1369 nnoremap <buffer> . : <C-R>=<SID>fnameescape(<SID>StatusCfile())<CR><Home>
1370 nnoremap <buffer> <silent> g? :help fugitive-:Gstatus<CR>
1371 nnoremap <buffer> <silent> <F1> :help fugitive-:Gstatus<CR>
1373 return 'echoerr v:errmsg'
1377 function! fugitive#FileReadCmd(...) abort
1378 let amatch = a:0 ? a:1 : expand('<amatch>')
1379 let [dir, rev] = s:DirRev(amatch)
1380 let line = a:0 > 1 ? a:2 : line("'[")
1382 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
1385 let cmd = s:Prepare(dir, 'log', '--pretty=format:%B', '-1', rev, '--')
1387 let cmd = s:Prepare(dir, 'cat-file', '-p', rev)
1389 return line . 'read !' . escape(cmd, '!#%')
1392 function! fugitive#FileWriteCmd(...) abort
1393 let tmp = tempname()
1394 let amatch = a:0 ? a:1 : expand('<amatch>')
1395 let autype = a:0 > 1 ? 'Buf' : 'File'
1396 if exists('#' . autype . 'WritePre')
1397 execute 'doautocmd ' . autype . 'WritePre ' . s:fnameescape(amatch)
1400 let [dir, commit, file] = s:DirCommitFile(amatch)
1401 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
1402 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
1404 silent execute "'[,']write !".s:Prepare(dir, 'hash-object', '-w', '--stdin', '--').' > '.tmp
1405 let sha1 = readfile(tmp)[0]
1406 let old_mode = matchstr(system(s:Prepare(dir, 'ls-files', '--stage', '.' . file)), '^\d\+')
1408 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
1410 let info = old_mode.' '.sha1.' '.commit."\t".file[1:-1]
1411 let error = system(s:Prepare(dir, 'update-index', '--index-info'), info . "\n")
1412 if v:shell_error == 0
1414 if exists('#' . autype . 'WritePost')
1415 execute 'doautocmd ' . autype . 'WritePost ' . s:fnameescape(amatch)
1417 call fugitive#ReloadStatus()
1420 return 'echoerr '.string('fugitive: '.error)
1427 function! fugitive#BufReadCmd(...) abort
1428 let amatch = a:0 ? a:1 : expand('<amatch>')
1430 let [dir, rev] = s:DirRev(amatch)
1432 return 'echo "Invalid Fugitive URL"'
1435 let b:fugitive_type = 'stage'
1437 let b:fugitive_type = system(s:Prepare(dir, 'cat-file', '-t', rev))[0:-2]
1438 if v:shell_error && rev =~# '^:0'
1439 let sha = system(s:Prepare(dir, 'write-tree', '--prefix=' . rev[3:-1]))[0:-2]
1440 let b:fugitive_type = 'tree'
1443 let error = b:fugitive_type
1444 unlet b:fugitive_type
1446 let &readonly = !filewritable(dir . '/index')
1447 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1449 setlocal readonly nomodifiable
1450 return 'echo ' . string(error)
1452 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1453 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1455 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1456 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1460 if b:fugitive_type !=# 'blob'
1464 setlocal noreadonly modifiable
1465 let pos = getpos('.')
1466 silent keepjumps %delete_
1470 if b:fugitive_type ==# 'tree'
1471 let b:fugitive_display_format = b:fugitive_display_format % 2
1472 if b:fugitive_display_format
1473 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1476 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev, '--'))[0:-2]
1478 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1480 elseif b:fugitive_type ==# 'tag'
1481 let b:fugitive_display_format = b:fugitive_display_format % 2
1482 if b:fugitive_display_format
1483 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1485 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1487 elseif b:fugitive_type ==# 'commit'
1488 let b:fugitive_display_format = b:fugitive_display_format % 2
1489 if b:fugitive_display_format
1490 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1492 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])
1493 keepjumps call search('^parent ')
1494 if getline('.') ==# 'parent '
1495 silent keepjumps delete_
1497 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1499 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1501 silent keepjumps delete_
1503 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/g/\r$/s///'
1506 elseif b:fugitive_type ==# 'stage'
1507 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1508 elseif b:fugitive_type ==# 'blob'
1509 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1513 keepjumps call setpos('.',pos)
1514 setlocal nomodified noswapfile
1516 setlocal nomodifiable
1518 let &modifiable = b:fugitive_type !=# 'tree'
1520 let &readonly = !&modifiable || !filewritable(dir . '/index')
1521 if &bufhidden ==# ''
1522 setlocal bufhidden=delete
1524 if b:fugitive_type !=# 'blob'
1525 setlocal filetype=git foldmethod=syntax
1526 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1527 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1529 call fugitive#MapJumps()
1535 return 'echoerr v:errmsg'
1539 function! fugitive#BufWriteCmd(...) abort
1540 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1543 function! fugitive#SourceCmd(...) abort
1544 let amatch = a:0 ? a:1 : expand('<amatch>')
1545 let temp = s:BlobTemp(amatch)
1547 return 'noautocmd source ' . s:fnameescape(amatch)
1549 if !exists('g:virtual_scriptnames')
1550 let g:virtual_scriptnames = {}
1552 let g:virtual_scriptnames[temp] = amatch
1553 return 'source ' . s:fnameescape(temp)
1556 " Section: Temp files
1558 if !exists('s:temp_files')
1559 let s:temp_files = {}
1562 function! s:SetupTemp(file) abort
1563 if has_key(s:temp_files, s:cpath(a:file))
1564 let dict = s:temp_files[s:cpath(a:file)]
1565 let b:git_dir = dict.dir
1566 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1567 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1568 let &l:filetype = dict.filetype
1570 setlocal foldmarker=<<<<<<<,>>>>>>>
1571 setlocal bufhidden=delete nobuflisted
1572 setlocal buftype=nowrite
1573 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1574 if getline(1) !~# '^diff '
1575 setlocal nomodifiable
1577 call FugitiveDetect(a:file)
1582 augroup fugitive_temp
1584 autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1589 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1591 function! s:Git(bang, mods, args) abort
1593 return s:Edit('edit', 1, a:mods, a:args)
1595 let git = s:UserCommand()
1596 if has('gui_running') && !has('win32')
1597 let git .= ' --no-pager'
1599 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1600 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1603 let after = '|call fugitive#ReloadStatus()' . after
1605 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1611 execute 'lcd' fnameescape(tree)
1612 let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1613 return 'exe ' . string('terminal ' . exec) . after
1615 let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1616 if s:cpath(tree) !=# s:cpath(getcwd())
1617 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1618 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1624 let s:exec_paths = {}
1625 function! s:Subcommands() abort
1626 if !has_key(s:exec_paths, g:fugitive_git_executable)
1627 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1629 let exec_path = s:exec_paths[g:fugitive_git_executable]
1630 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1634 function! s:Aliases() abort
1635 if !has_key(s:aliases, b:git_dir)
1636 let s:aliases[b:git_dir] = {}
1637 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1638 for line in v:shell_error ? [] : lines
1639 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1642 return s:aliases[b:git_dir]
1645 function! s:GitComplete(A, L, P) abort
1646 let pre = strpart(a:L, 0, a:P)
1647 if pre !~# ' [[:alnum:]-]\+ '
1648 let cmds = s:Subcommands()
1649 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1650 elseif pre =~# ' -- '
1651 return fugitive#PathComplete(a:A, b:git_dir)
1653 return fugitive#Complete(a:A, b:git_dir)
1657 " Section: :Gcd, :Glcd
1659 function! s:DirComplete(A, L, P) abort
1660 return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1663 function! s:DirArg(path) abort
1664 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1665 if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
1668 return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1672 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1673 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1677 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1678 augroup fugitive_status
1681 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1682 autocmd BufDelete term://* call fugitive#ReloadStatus()
1686 function! s:Status(bang, count, mods) abort
1688 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1690 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1691 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1693 return 'echoerr v:errmsg'
1698 function! fugitive#ReloadStatus() abort
1699 if exists('s:reloading_status')
1703 let s:reloading_status = 1
1704 let mytab = tabpagenr()
1705 for tab in [mytab] + range(1,tabpagenr('$'))
1706 for winnr in range(1,tabpagewinnr(tab,'$'))
1707 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1708 execute 'tabnext '.tab
1710 execute winnr.'wincmd w'
1711 let restorewinnr = 1
1715 call fugitive#BufReadStatus()
1718 if exists('restorewinnr')
1721 execute 'tabnext '.mytab
1727 unlet! s:reloading_status
1731 function! fugitive#reload_status() abort
1732 return fugitive#ReloadStatus()
1735 function! s:stage_info(lnum) abort
1736 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1738 if has('multi_byte_encoding')
1739 let colon = '\%(:\|\%uff1a\)'
1743 while lnum && getline(lnum) !~# colon.'$'
1748 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1749 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1750 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1751 return [filename, 'untracked']
1752 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1753 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1754 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1755 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1757 return ['', 'unknown']
1761 function! s:StageNext(count) abort
1762 for i in range(a:count)
1763 call search('^.\=\t.*','W')
1768 function! s:StagePrevious(count) abort
1769 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1770 return 'CtrlP '.fnameescape(s:Tree())
1772 for i in range(a:count)
1773 call search('^.\=\t.*','Wbe')
1779 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1781 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1782 if f !=# '' | let jump = f | endif
1783 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1784 if f !=# '' | let jump = f | endif
1788 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1791 function! s:StageUndo() abort
1792 let [filename, section] = s:stage_info(line('.'))
1796 let hash = s:TreeChomp('hash-object', '-w', './' . filename)
1798 if section ==# 'untracked'
1799 call s:TreeChomp('clean', '-f', './' . filename)
1800 elseif section ==# 'unmerged'
1801 call s:TreeChomp('rm', './' . filename)
1802 elseif section ==# 'unstaged'
1803 call s:TreeChomp('checkout', './' . filename)
1805 call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
1807 call s:StageReloadSeek(filename, line('.'), line('.'))
1809 return 'checktime|redraw|echomsg ' .
1810 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1814 function! s:StageDiff(diff) abort
1815 let [filename, section] = s:stage_info(line('.'))
1816 if filename ==# '' && section ==# 'staged'
1817 return 'Git! diff --no-ext-diff --cached'
1818 elseif filename ==# ''
1819 return 'Git! diff --no-ext-diff'
1820 elseif filename =~# ' -> '
1821 let [old, new] = split(filename,' -> ')
1822 execute 'Gedit '.s:fnameescape(':0:'.new)
1823 return a:diff.' HEAD:'.s:fnameescape(old)
1824 elseif section ==# 'staged'
1825 execute 'Gedit '.s:fnameescape(':0:'.filename)
1828 execute 'Gedit '.s:fnameescape('/'.filename)
1833 function! s:StageDiffEdit() abort
1834 let [filename, section] = s:stage_info(line('.'))
1835 let arg = (filename ==# '' ? '.' : filename)
1836 if section ==# 'staged'
1837 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1838 elseif section ==# 'untracked'
1839 call s:TreeChomp('add', '--intent-to-add', './' . arg)
1843 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1844 call search(':$','W')
1847 call s:StageReloadSeek(arg,line('.'),line('.'))
1851 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1855 function! s:StageToggle(lnum1,lnum2) abort
1856 if a:lnum1 == 1 && a:lnum2 == 1
1857 return 'Gedit .git/|call search("^index$", "wc")'
1861 for lnum in range(a:lnum1,a:lnum2)
1862 let [filename, section] = s:stage_info(lnum)
1863 if getline('.') =~# ':$'
1864 if section ==# 'staged'
1865 call s:TreeChomp('reset','-q')
1868 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1869 call search(':$','W')
1872 elseif section ==# 'unstaged'
1873 call s:TreeChomp('add','-u')
1876 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1877 call search(':$','W')
1881 call s:TreeChomp('add', '.')
1884 call search(':$','W')
1892 if section ==# 'staged'
1893 let files_to_unstage = split(filename, ' -> ')
1894 let filename = files_to_unstage[-1]
1895 let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
1896 elseif getline(lnum) =~# '^.\=\tdeleted:'
1897 let cmd = ['rm', './' . filename]
1898 elseif getline(lnum) =~# '^.\=\tmodified:'
1899 let cmd = ['add', './' . filename]
1901 let cmd = ['add','-A', './' . filename]
1903 if !exists('first_filename')
1904 let first_filename = filename
1906 let output .= call('s:TreeChomp', cmd)."\n"
1908 if exists('first_filename')
1909 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1911 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1913 return 'echoerr v:errmsg'
1918 function! s:StagePatch(lnum1,lnum2) abort
1922 for lnum in range(a:lnum1,a:lnum2)
1923 let [filename, section] = s:stage_info(lnum)
1924 if getline('.') =~# ':$' && section ==# 'staged'
1925 return 'Git reset --patch'
1926 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1927 return 'Git add --patch'
1928 elseif getline('.') =~# ':$' && section ==# 'untracked'
1929 return 'Git add -N .'
1930 elseif filename ==# ''
1933 if !exists('first_filename')
1934 let first_filename = filename
1937 if filename =~ ' -> '
1938 let reset += [split(filename,' -> ')[1]]
1939 elseif section ==# 'staged'
1940 let reset += [filename]
1941 elseif getline(lnum) !~# '^.\=\tdeleted:'
1942 let add += [filename]
1947 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1950 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1952 if exists('first_filename')
1956 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1959 return 'echoerr v:errmsg'
1966 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1968 function! s:Commit(mods, args, ...) abort
1969 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1970 let dir = a:0 ? a:1 : b:git_dir
1971 let tree = s:Tree(dir)
1972 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1974 let msgfile = dir . '/COMMIT_EDITMSG'
1975 let outfile = tempname()
1976 let errorfile = tempname()
1978 let guioptions = &guioptions
1980 if &guioptions =~# '!'
1981 setglobal guioptions-=!
1983 execute cd s:fnameescape(tree)
1986 let old_editor = $GIT_EDITOR
1987 let $GIT_EDITOR = 'false'
1989 let command = 'env GIT_EDITOR=false '
1991 let args = s:ShellExpand(a:args)
1992 let command .= s:UserCommand() . ' commit ' . args
1994 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1995 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1996 noautocmd execute '!'.command.' 2> '.errorfile
1998 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
2000 let error = v:shell_error
2002 execute cd s:fnameescape(cwd)
2003 let &guioptions = guioptions
2005 if !has('gui_running')
2009 if filereadable(outfile)
2010 for line in readfile(outfile)
2016 let errors = readfile(errorfile)
2017 let error = get(errors,-2,get(errors,-1,'!'))
2018 if error =~# 'false''\=\.$'
2019 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
2020 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
2021 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2023 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
2024 let args = '-F '.s:shellesc(msgfile).' '.args
2025 if args !~# '\%(^\| \)--cleanup\>'
2026 let args = '--cleanup=strip '.args
2028 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
2029 execute mods 'keepalt edit' s:fnameescape(msgfile)
2030 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
2031 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
2032 elseif get(b:, 'fugitive_type', '') ==# 'index'
2033 execute mods 'keepalt edit' s:fnameescape(msgfile)
2034 execute (search('^#','n')+1).'wincmd+'
2035 setlocal nopreviewwindow
2037 execute mods 'keepalt split' s:fnameescape(msgfile)
2039 let b:fugitive_commit_arguments = args
2040 setlocal bufhidden=wipe filetype=gitcommit
2042 elseif error ==# '!'
2045 call s:throw(empty(error)?join(errors, ' '):error)
2049 return 'echoerr v:errmsg'
2051 if exists('old_editor')
2052 let $GIT_EDITOR = old_editor
2054 call delete(outfile)
2055 call delete(errorfile)
2056 call fugitive#ReloadStatus()
2060 function! s:CommitComplete(A,L,P) abort
2061 if a:A =~# '^--fixup=\|^--squash='
2062 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
2064 let pre = matchstr(a:A, '^--\w*=') . ':/^'
2065 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
2067 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
2068 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']
2069 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
2071 return fugitive#PathComplete(a:A, b:git_dir)
2076 function! s:FinishCommit() abort
2077 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
2079 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
2080 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
2085 " Section: :Gmerge, :Grebase, :Gpull
2087 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
2088 \ "execute s:Merge('merge', <bang>0, <q-args>)")
2089 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
2090 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
2091 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
2092 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
2094 function! s:RevisionComplete(A, L, P) abort
2095 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
2096 \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
2099 function! s:RemoteComplete(A, L, P) abort
2100 let remote = matchstr(a:L, ' \zs\S\+\ze ')
2102 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
2103 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2104 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2106 let matches = split(s:TreeChomp('remote'), "\n")
2108 return join(matches, "\n")
2111 function! fugitive#Cwindow() abort
2112 if &buftype == 'quickfix'
2116 if &buftype == 'quickfix'
2122 let s:common_efm = ''
2124 \ . '%+Eusage:%.%#,'
2125 \ . '%+Eerror:%.%#,'
2126 \ . '%+Efatal:%.%#,'
2127 \ . '%-G%.%#%\e[K%.%#,'
2128 \ . '%-G%.%#%\r%.%\+'
2130 function! s:Merge(cmd, bang, args) abort
2131 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
2132 return 'echoerr "git rebase --interactive not supported"'
2134 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2136 let [mp, efm] = [&l:mp, &l:efm]
2137 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
2139 let &l:errorformat = ''
2140 \ . '%-Gerror:%.%#false''.,'
2141 \ . '%-G%.%# ''git commit'' %.%#,'
2142 \ . '%+Emerge:%.%#,'
2143 \ . s:common_efm . ','
2144 \ . '%+ECannot %.%#: You have unstaged changes.,'
2145 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
2146 \ . '%+EThere is no tracking information for the current branch.,'
2147 \ . '%+EYou are not currently on a branch. Please specify which,'
2148 \ . 'CONFLICT (%m): %f deleted in %.%#,'
2149 \ . 'CONFLICT (%m): Merge conflict in %f,'
2150 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2151 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2152 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2153 \ . '%+ECONFLICT %.%#,'
2154 \ . '%+EKONFLIKT %.%#,'
2155 \ . '%+ECONFLIT %.%#,'
2156 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2157 \ . "%+E\u51b2\u7a81 %.%#,"
2159 if a:cmd =~# '^merge' && empty(a:args) &&
2160 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2161 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2162 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2164 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2165 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2166 \ ' ' . a:args, ' *$', '')
2168 if !empty($GIT_EDITOR) || has('win32')
2169 let old_editor = $GIT_EDITOR
2170 let $GIT_EDITOR = 'false'
2172 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2174 execute cd fnameescape(s:Tree())
2175 silent noautocmd make!
2176 catch /^Vim\%((\a\+)\)\=:E211/
2177 let err = v:exception
2180 let [&l:mp, &l:efm] = [mp, efm]
2181 if exists('old_editor')
2182 let $GIT_EDITOR = old_editor
2184 execute cd fnameescape(cwd)
2186 call fugitive#ReloadStatus()
2187 if empty(filter(getqflist(),'v:val.valid'))
2188 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2190 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2193 let qflist = getqflist()
2198 let e.pattern = '^<<<<<<<'
2201 call fugitive#Cwindow()
2203 call setqflist(qflist, 'r')
2208 return exists('err') ? 'echoerr '.string(err) : ''
2211 " Section: :Ggrep, :Glog
2213 if !exists('g:fugitive_summary_format')
2214 let g:fugitive_summary_format = '%s'
2217 function! s:GrepComplete(A, L, P) abort
2218 if strpart(a:L, 0, a:P) =~# ' -- '
2219 return fugitive#PathComplete(a:A, b:git_dir)
2221 return fugitive#Complete(a:A, b:git_dir)
2225 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2226 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2227 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2228 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2230 function! s:Grep(cmd,bang,arg) abort
2231 let grepprg = &grepprg
2232 let grepformat = &grepformat
2233 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2236 execute cd s:fnameescape(s:Tree())
2237 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2238 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2239 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2240 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2242 if bufname(entry.bufnr) =~ ':'
2243 let entry.filename = s:Generate(bufname(entry.bufnr))
2246 elseif a:arg =~# '\%(^\| \)--cached\>'
2247 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2252 if a:cmd =~# '^l' && exists('changed')
2253 call setloclist(0, list, 'r')
2254 elseif exists('changed')
2255 call setqflist(list, 'r')
2257 if !a:bang && !empty(list)
2258 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2260 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2263 let &grepprg = grepprg
2264 let &grepformat = grepformat
2265 execute cd s:fnameescape(dir)
2269 function! s:Log(cmd, bang, line1, line2, ...) abort
2270 let args = ' ' . join(a:000, ' ')
2271 let before = substitute(args, ' --\S\@!.*', '', '')
2272 let after = strpart(args, len(before))
2273 let path = s:Relative('/')
2274 let relative = path[1:-1]
2275 if path =~# '^/\.git\%(/\|$\)' || len(after)
2278 if before !~# '\s[^[:space:]-]'
2279 let owner = s:Owner(@%)
2281 let before .= ' ' . s:shellesc(owner)
2284 if relative =~# '^\.git\%(/\|$\)'
2287 if len(relative) && a:line2 > 0
2288 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2289 elseif len(relative) && (empty(after) || a:line2 == 0)
2290 let after = (len(after) > 3 ? after : ' -- ') . relative
2292 let grepformat = &grepformat
2293 let grepprg = &grepprg
2294 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2297 execute cd s:fnameescape(s:Tree())
2298 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2299 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2300 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2301 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2303 let &grepformat = grepformat
2304 let &grepprg = grepprg
2305 execute cd s:fnameescape(dir)
2309 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
2311 function! s:UsableWin(nr) abort
2312 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2313 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2316 function! s:EditParse(args) abort
2318 let args = copy(a:args)
2319 while !empty(args) && args[0] =~# '^+'
2320 call add(pre, ' ' . escape(remove(args, 0), ' |"'))
2323 let file = join(args)
2324 elseif empty(expand('%'))
2326 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2331 return [s:Expand(file), join(pre)]
2334 function! s:BlurStatus() abort
2335 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2336 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2338 exe winnrs[0].'wincmd w'
2339 elseif winnr('$') == 1
2340 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2341 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2346 let mywinnr = winnr()
2347 for winnr in range(winnr('$'),1,-1)
2348 if winnr != mywinnr && getwinvar(winnr,'&diff')
2349 execute winnr.'wincmd w'
2361 function! s:Edit(cmd, bang, mods, args, ...) abort
2362 let mods = a:mods ==# '<mods>' ? '' : a:mods
2365 let temp = tempname()
2366 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2369 execute cd s:fnameescape(s:Tree())
2370 let git = s:UserCommand()
2371 let args = s:ShellExpand(a:args)
2372 silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2373 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2375 execute cd s:fnameescape(cwd)
2377 let temp = s:Resolve(temp)
2378 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2382 silent execute mods a:cmd temp
2383 call fugitive#ReloadStatus()
2384 return 'redraw|echo ' . string(':!' . git . ' ' . args)
2387 let [file, pre] = s:EditParse(a:000)
2389 let file = s:Generate(file)
2391 return 'echoerr v:errmsg'
2393 if file !~# '^\a\a\+:'
2394 let file = s:sub(file, '/$', '')
2399 return mods . ' ' . a:cmd . pre . ' ' . s:fnameescape(file)
2402 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2403 let mods = a:mods ==# '<mods>' ? '' : a:mods
2406 let delete = 'silent 1,' . line('$') . 'delete_|'
2407 let after = line('$')
2409 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2414 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2417 execute cd s:fnameescape(s:Tree())
2418 let git = s:UserCommand()
2419 let args = s:ShellExpand(a:args)
2420 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2422 execute cd s:fnameescape(cwd)
2424 execute delete . 'diffupdate'
2425 call fugitive#ReloadStatus()
2426 return 'redraw|echo '.string(':!'.git.' '.args)
2428 let [file, pre] = s:EditParse(a:000)
2430 let file = s:Generate(file)
2432 return 'echoerr v:errmsg'
2434 if file =~# '^fugitive:' && after is# 0
2435 return 'exe ' .string(mods . ' ' . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
2437 return mods . ' ' . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2440 function! s:EditRunComplete(A,L,P) abort
2442 return s:GitComplete(a:A, a:L, a:P)
2444 return fugitive#Complete(a:A, a:L, a:P)
2448 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2449 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2450 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2451 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>)")
2452 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>)")
2453 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>)")
2454 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>)")
2456 " Section: :Gwrite, :Gwq
2458 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2459 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2460 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2462 function! s:Write(force,...) abort
2463 if exists('b:fugitive_commit_arguments')
2464 return 'write|bdelete'
2465 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2467 elseif get(b:, 'fugitive_type', '') ==# 'index'
2469 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
2470 let filename = getline(4)[6:-1]
2473 setlocal buftype=nowrite
2474 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2475 let err = s:TreeChomp('apply', '--cached', '--reverse', '--', expand('%:p'))
2477 let err = s:TreeChomp('apply', '--cached', '--', expand('%:p'))
2480 let v:errmsg = split(err,"\n")[0]
2481 return 'echoerr v:errmsg'
2485 return 'Gedit '.fnameescape(filename)
2488 let mytab = tabpagenr()
2489 let mybufnr = bufnr('')
2490 let file = a:0 ? s:Generate(s:Expand(join(a:000, ' '))) : fugitive#Real(@%)
2492 return 'echoerr '.string('fugitive: cannot determine file path')
2494 if file =~# '^fugitive:'
2495 return 'write' . (a:force ? '! ' : ' ') . s:fnameescape(file)
2497 let always_permitted = s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^0\=$'
2498 if !always_permitted && !a:force && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
2499 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2500 return 'echoerr v:errmsg'
2503 for nr in range(1,bufnr('$'))
2504 if fnamemodify(bufname(nr),':p') ==# file
2509 if treebufnr > 0 && treebufnr != bufnr('')
2510 let temp = tempname()
2511 silent execute '%write '.temp
2512 for tab in [mytab] + range(1,tabpagenr('$'))
2513 for winnr in range(1,tabpagewinnr(tab,'$'))
2514 if tabpagebuflist(tab)[winnr-1] == treebufnr
2515 execute 'tabnext '.tab
2517 execute winnr.'wincmd w'
2518 let restorewinnr = 1
2521 let lnum = line('.')
2522 let last = line('$')
2523 silent execute '$read '.temp
2524 silent execute '1,'.last.'delete_'
2529 if exists('restorewinnr')
2532 execute 'tabnext '.mytab
2538 call writefile(readfile(temp,'b'),file,'b')
2541 execute 'write! '.s:fnameescape(file)
2545 let error = s:TreeChomp('add', '--force', '--', file)
2547 let error = s:TreeChomp('add', '--', file)
2550 let v:errmsg = 'fugitive: '.error
2551 return 'echoerr v:errmsg'
2553 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
2557 let one = s:Generate(':1:'.file)
2558 let two = s:Generate(':2:'.file)
2559 let three = s:Generate(':3:'.file)
2560 for nr in range(1,bufnr('$'))
2561 let name = fnamemodify(bufname(nr), ':p')
2562 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2563 execute nr.'bdelete'
2568 let zero = s:Generate(':0:'.file)
2569 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2570 for tab in range(1,tabpagenr('$'))
2571 for winnr in range(1,tabpagewinnr(tab,'$'))
2572 let bufnr = tabpagebuflist(tab)[winnr-1]
2573 let bufname = fnamemodify(bufname(bufnr), ':p')
2574 if bufname ==# zero && bufnr != mybufnr
2575 execute 'tabnext '.tab
2577 execute winnr.'wincmd w'
2578 let restorewinnr = 1
2581 let lnum = line('.')
2582 let last = line('$')
2583 silent execute '$read '.s:fnameescape(file)
2584 silent execute '1,'.last.'delete_'
2589 if exists('restorewinnr')
2592 execute 'tabnext '.mytab
2598 call fugitive#ReloadStatus()
2602 function! s:Wq(force,...) abort
2603 let bang = a:force ? '!' : ''
2604 if exists('b:fugitive_commit_arguments')
2607 let result = call(s:function('s:Write'),[a:force]+a:000)
2608 if result =~# '^\%(write\|wq\|echoerr\)'
2609 return s:sub(result,'^write','wq')
2611 return result.'|quit'.bang
2615 augroup fugitive_commit
2617 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2620 " Section: :Gpush, :Gfetch
2622 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2623 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2625 function! s:Dispatch(bang, args)
2626 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2628 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2630 let b:current_compiler = 'git'
2631 let &l:errorformat = s:common_efm
2632 execute cd fnameescape(s:Tree())
2633 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2634 if exists(':Make') == 2
2637 silent noautocmd make!
2639 return 'call fugitive#Cwindow()'
2643 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2644 if empty(cc) | unlet! b:current_compiler | endif
2645 execute cd fnameescape(cwd)
2651 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2652 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2653 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2655 augroup fugitive_diff
2657 autocmd BufWinLeave *
2658 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2659 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2661 autocmd BufWinEnter *
2662 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2663 \ call s:diffoff() |
2667 function! s:can_diffoff(buf) abort
2668 return getwinvar(bufwinnr(a:buf), '&diff') &&
2669 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2670 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2673 function! fugitive#CanDiffoff(buf) abort
2674 return s:can_diffoff(a:buf)
2677 function! s:diff_modifier(count) abort
2678 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2679 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2681 elseif &diffopt =~# 'vertical'
2682 return 'keepalt vert '
2683 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2686 return 'keepalt vert '
2690 function! s:diff_window_count() abort
2692 for nr in range(1,winnr('$'))
2693 let c += getwinvar(nr,'&diff')
2698 function! s:diff_restore() abort
2699 let restore = 'setlocal nodiff noscrollbind'
2700 \ . ' scrollopt=' . &l:scrollopt
2701 \ . (&l:wrap ? ' wrap' : ' nowrap')
2702 \ . ' foldlevel=999'
2703 \ . ' foldmethod=' . &l:foldmethod
2704 \ . ' foldcolumn=' . &l:foldcolumn
2705 \ . ' foldlevel=' . &l:foldlevel
2706 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2707 if has('cursorbind')
2708 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2713 function! s:diffthis() abort
2715 let w:fugitive_diff_restore = s:diff_restore()
2720 function! s:diffoff() abort
2721 if exists('w:fugitive_diff_restore')
2722 execute w:fugitive_diff_restore
2723 unlet w:fugitive_diff_restore
2729 function! s:diffoff_all(dir) abort
2730 let curwin = winnr()
2731 for nr in range(1,winnr('$'))
2732 if getwinvar(nr,'&diff')
2734 execute nr.'wincmd w'
2735 let restorewinnr = 1
2737 if exists('b:git_dir') && b:git_dir ==# a:dir
2742 execute curwin.'wincmd w'
2745 function! s:CompareAge(mine, theirs) abort
2746 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2747 let mine = substitute(a:mine, '^:', '', '')
2748 let theirs = substitute(a:theirs, '^:', '', '')
2749 let my_score = get(scores, ':'.mine, 0)
2750 let their_score = get(scores, ':'.theirs, 0)
2751 if my_score || their_score
2752 return my_score < their_score ? -1 : my_score != their_score
2753 elseif mine ==# theirs
2756 let base = s:TreeChomp('merge-base', mine, theirs)
2759 elseif base ==# theirs
2762 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
2763 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
2764 return my_time < their_time ? -1 : my_time != their_time
2767 function! s:Diff(vert,keepfocus,...) abort
2768 let args = copy(a:000)
2770 if get(args, 0) =~# '^+'
2771 let post = remove(args, 0)[1:-1]
2773 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2774 let commit = s:DirCommitFile(@%)[1]
2775 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2776 if exists(':DiffGitCached')
2777 return 'DiffGitCached'
2778 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
2780 return 'echoerr ' . string("fugitive: error determining merge status of the current buffer")
2782 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2784 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2785 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2789 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2790 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2795 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2796 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2799 let arg = join(args, ' ')
2803 let file = s:Relative()
2805 let file = s:Relative(':0:')
2806 elseif arg =~# '^:/.'
2808 let file = fugitive#RevParse(arg).s:Relative(':')
2810 return 'echoerr v:errmsg'
2813 let file = s:Expand(arg)
2815 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2816 let file = file.s:Relative(':')
2819 let file = empty(commit) ? s:Relative(':0:') : s:Relative()
2822 let spec = s:Generate(file)
2823 let restore = s:diff_restore()
2824 if exists('+cursorbind')
2827 let w:fugitive_diff_restore = restore
2828 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2829 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2831 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2833 let &l:readonly = &l:readonly
2835 let w:fugitive_diff_restore = restore
2837 if getwinvar('#', '&diff')
2840 call feedkeys(winnr."\<C-W>w", 'n')
2845 return 'echoerr v:errmsg'
2849 " Section: :Gmove, :Gremove
2851 function! s:Move(force, rename, destination) abort
2852 if a:destination =~# '^\.\.\=\%(/\|$\)'
2853 let destination = simplify(getcwd() . '/' . a:destination)
2854 elseif a:destination =~# '^\a\+:\|^/'
2855 let destination = a:destination
2856 elseif a:destination =~# '^:/:\='
2857 let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
2858 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
2859 let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
2860 elseif a:destination =~# '^:(literal)'
2861 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
2863 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
2865 let destination = s:Tree() . '/' . a:destination
2867 let destination = s:Slash(destination)
2871 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
2873 let v:errmsg = 'fugitive: '.message
2874 return 'echoerr v:errmsg'
2876 if isdirectory(destination)
2877 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2879 call fugitive#ReloadStatus()
2880 if empty(s:DirCommitFile(@%)[1])
2881 if isdirectory(destination)
2882 return 'keepalt edit '.s:fnameescape(destination)
2884 return 'keepalt saveas! '.s:fnameescape(destination)
2887 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2891 function! s:RenameComplete(A,L,P) abort
2892 if a:A =~# '^[.:]\=/'
2893 return fugitive#PathComplete(a:A)
2895 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
2896 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2900 function! s:Remove(after, force) abort
2901 if s:DirCommitFile(@%)[1] ==# ''
2903 elseif s:DirCommitFile(@%)[1] ==# '0'
2904 let cmd = ['rm','--cached']
2906 let v:errmsg = 'fugitive: rm not supported here'
2907 return 'echoerr v:errmsg'
2910 let cmd += ['--force']
2912 let message = call('s:TreeChomp', cmd + ['--', expand('%:p')])
2914 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2915 return 'echoerr '.string(v:errmsg)
2917 call fugitive#ReloadStatus()
2918 return a:after . (a:force ? '!' : '')
2922 augroup fugitive_remove
2924 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2925 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2926 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2927 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2928 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2934 function! s:Keywordprg() abort
2935 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2936 if has('gui_running') && !has('win32')
2937 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2939 return s:UserCommand() . args . ' show'
2943 augroup fugitive_blame
2945 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2946 autocmd Syntax fugitiveblame call s:BlameSyntax()
2947 autocmd User Fugitive
2948 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2949 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2951 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2952 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2955 function! s:linechars(pattern) abort
2956 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2957 if exists('*synconcealed') && &conceallevel > 1
2958 for col in range(1, chars)
2959 let chars -= synconcealed(line('.'), col)[0]
2965 function! s:Blame(bang, line1, line2, count, mods, args) abort
2966 if exists('b:fugitive_blamed_bufnr')
2970 if empty(s:Relative('/'))
2971 call s:throw('file or blob required')
2973 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2974 call s:throw('unsupported option')
2976 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2977 let cmd = ['--no-pager', 'blame', '--show-number']
2979 let cmd += ['-L', a:line1 . ',' . a:line1]
2982 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2983 let cmd += [s:DirCommitFile(@%)[1]]
2985 let cmd += ['--contents', '-']
2987 let cmd += ['--', expand('%:p')]
2988 let basecmd = escape(fugitive#Prepare(cmd), '!#%')
2990 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2992 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2994 execute cd s:fnameescape(tree)
2996 let error = tempname()
2997 let temp = error.'.fugitiveblame'
2999 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
3001 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
3004 execute cd s:fnameescape(cwd)
3008 call s:throw(join(readfile(error),"\n"))
3011 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
3012 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
3014 for winnr in range(winnr('$'),1,-1)
3015 call setwinvar(winnr, '&scrollbind', 0)
3016 if exists('+cursorbind')
3017 call setwinvar(winnr, '&cursorbind', 0)
3019 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
3020 execute winbufnr(winnr).'bdelete'
3023 let bufnr = bufnr('')
3024 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
3025 if exists('+cursorbind')
3026 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
3029 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
3032 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
3034 setlocal scrollbind nowrap nofoldenable
3035 if exists('+cursorbind')
3038 let top = line('w0') + &scrolloff
3039 let current = line('.')
3040 let temp = s:Resolve(temp)
3041 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
3042 exe 'keepalt leftabove vsplit '.temp
3043 let b:fugitive_blamed_bufnr = bufnr
3044 let b:fugitive_type = 'blame'
3045 let w:fugitive_leave = restore
3046 let b:fugitive_blame_arguments = join(a:args,' ')
3050 if exists('+cursorbind')
3053 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
3054 if exists('+concealcursor')
3055 setlocal concealcursor=nc conceallevel=2
3057 if exists('+relativenumber')
3058 setlocal norelativenumber
3060 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
3061 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
3062 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
3063 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
3064 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>
3065 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3066 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
3067 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
3068 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
3069 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3070 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
3071 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
3072 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
3073 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
3074 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
3075 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
3081 execute cd s:fnameescape(cwd)
3086 return 'echoerr v:errmsg'
3090 function! s:BlameCommit(cmd, ...) abort
3091 let line = a:0 ? a:1 : getline('.')
3092 if line =~# '^0\{4,40\} '
3093 return 'echoerr ' . string('Not Committed Yet')
3095 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
3096 if cmd =~# '^echoerr'
3099 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
3100 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3102 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
3105 if a:cmd ==# 'pedit'
3108 if search('^diff .* b/\M'.escape(path,'\').'$','W')
3110 let head = line('.')
3111 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
3112 let top = +matchstr(getline('.'),' +\zs\d\+')
3113 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
3114 if lnum >= top && lnum <= top + len
3115 let offset = lnum - top
3123 while offset > 0 && line('.') < line('$')
3125 if getline('.') =~# '^[ +]'
3138 function! s:BlameJump(suffix) abort
3139 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
3140 if commit =~# '^0\+$'
3143 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
3144 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3146 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
3148 let args = b:fugitive_blame_arguments
3149 let offset = line('.') - line('w0')
3150 let bufnr = bufnr('%')
3151 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
3153 exe winnr.'wincmd w'
3155 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3160 if exists(':Gblame')
3161 execute 'Gblame '.args
3163 let delta = line('.') - line('w0') - offset
3165 execute 'normal! '.delta."\<C-E>"
3167 execute 'normal! '.(-delta)."\<C-Y>"
3174 let s:hash_colors = {}
3176 function! s:BlameSyntax() abort
3177 let b:current_syntax = 'fugitiveblame'
3178 let conceal = has('conceal') ? ' conceal' : ''
3179 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3180 syn match FugitiveblameBoundary "^\^"
3181 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3182 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3183 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3184 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3185 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3186 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3187 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3188 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3189 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3190 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
3191 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3192 hi def link FugitiveblameBoundary Keyword
3193 hi def link FugitiveblameHash Identifier
3194 hi def link FugitiveblameUncommitted Ignore
3195 hi def link FugitiveblameTime PreProc
3196 hi def link FugitiveblameLineNumber Number
3197 hi def link FugitiveblameOriginalFile String
3198 hi def link FugitiveblameOriginalLineNumber Float
3199 hi def link FugitiveblameShort FugitiveblameDelimiter
3200 hi def link FugitiveblameDelimiter Delimiter
3201 hi def link FugitiveblameNotCommittedYet Comment
3203 for lnum in range(1, line('$'))
3204 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3205 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3209 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3210 \ && empty(get(s:hash_colors, hash))
3211 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3212 let color = csapprox#per_component#Approximate(r, g, b)
3213 if color == 16 && &background ==# 'dark'
3216 let s:hash_colors[hash] = ' ctermfg='.color
3218 let s:hash_colors[hash] = ''
3220 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3222 call s:RehighlightBlame()
3225 function! s:RehighlightBlame() abort
3226 for [hash, cterm] in items(s:hash_colors)
3227 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3228 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3230 exe 'hi link FugitiveblameHash'.hash.' Identifier'
3237 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3239 let s:redirects = {}
3241 function! s:Browse(bang,line1,count,...) abort
3243 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3245 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3246 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3252 let rev = s:DirRev(@%)[1]
3255 let expanded = s:Relative()
3257 let expanded = s:Expand(rev)
3259 let cdir = fugitive#CommonDir(b:git_dir)
3260 for dir in ['tags/', 'heads/', 'remotes/']
3261 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3262 let expanded = '.git/refs/' . dir . expanded
3265 let full = s:Generate(expanded)
3267 if full =~? '^fugitive:'
3268 let [dir, commit, path] = s:DirCommitFile(full)
3269 if commit =~# '^:\=\d$'
3273 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3274 let branch = matchstr(expanded, '^[^:]*')
3278 let path = path[1:-1]
3279 elseif empty(s:Tree())
3280 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3283 let path = full[strlen(s:Tree())+1:-1]
3284 if path =~# '^\.git/'
3286 elseif isdirectory(full)
3292 if type ==# 'tree' && !empty(path)
3293 let path = s:sub(path, '/\=$', '/')
3295 if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3296 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3297 if body =~# '^\x\{40\}$'
3301 elseif body =~# '^ref: refs/'
3302 let path = '.git/' . matchstr(body,'ref: \zs.*')
3307 if path =~# '^\.git/refs/remotes/.'
3309 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3310 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3312 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3313 let path = '.git/refs/heads/'.merge
3315 elseif path =~# '^\.git/refs/heads/.'
3316 let branch = path[16:-1]
3317 elseif !exists('branch')
3318 let branch = FugitiveHead()
3321 let r = fugitive#Config('branch.'.branch.'.remote')
3322 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3323 if r ==# '.' && !empty(m)
3324 let r2 = fugitive#Config('branch.'.m.'.remote')
3327 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3333 if r ==# '.' || r ==# remote
3335 if path =~# '^\.git/refs/heads/.'
3336 let path = '.git/refs/heads/'.merge
3341 let line1 = a:count > 0 ? a:line1 : 0
3342 let line2 = a:count > 0 ? a:count : 0
3343 if empty(commit) && path !~# '^\.git/'
3344 if a:line1 && !a:count && !empty(merge)
3349 let owner = s:Owner(@%)
3350 let commit = s:TreeChomp('merge-base', 'refs/remotes/' . remote . '/' . merge, empty(owner) ? 'HEAD' : owner, '--')
3354 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3355 let blame_list = tempname()
3356 call writefile([commit, ''], blame_list, 'b')
3357 let blame_in = tempname()
3358 silent exe '%write' blame_in
3359 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path), "\n")
3361 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3362 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3363 let line1 = +matchstr(blame[0], blame_regex)
3364 let line2 = +matchstr(blame[-1], blame_regex)
3366 call s:throw("Can't browse to uncommitted change")
3373 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3376 while commit =~# '^ref: ' && i < 10
3377 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3385 let raw = fugitive#RemoteUrl(remote)
3390 if raw =~# '^https\=://' && s:executable('curl')
3391 if !has_key(s:redirects, raw)
3392 let s:redirects[raw] = matchstr(system('curl -I ' .
3393 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3394 \ 'Location: \zs\S\+\ze/info/refs?')
3396 if len(s:redirects[raw])
3397 let raw = s:redirects[raw]
3403 \ 'repo': fugitive#repo(),
3405 \ 'revision': 'No longer provided',
3413 for Handler in get(g:, 'fugitive_browse_handlers', [])
3414 let url = call(Handler, [copy(opts)])
3421 call s:throw("No Gbrowse handler found for '".raw."'")
3424 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3429 return 'echomsg '.string(url)
3430 elseif exists(':Browse') == 2
3431 return 'echomsg '.string(url).'|Browse '.url
3433 if !exists('g:loaded_netrw')
3434 runtime! autoload/netrw.vim
3436 if exists('*netrw#BrowseX')
3437 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3439 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3443 return 'echoerr v:errmsg'
3447 " Section: Go to file
3449 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3450 function! fugitive#MapCfile(...) abort
3451 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3452 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3453 if !exists('g:fugitive_no_maps')
3454 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3455 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3456 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3457 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3458 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3462 function! s:ContainingCommit() abort
3463 let commit = s:Owner(@%)
3464 return empty(commit) ? 'HEAD' : commit
3467 function! s:NavigateUp(count) abort
3468 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3472 let rev = matchstr(rev, '.*\ze/.\+', '')
3473 elseif rev =~# '.:.'
3474 let rev = matchstr(rev, '^.[^:]*:')
3487 function! fugitive#MapJumps(...) abort
3488 if get(b:, 'fugitive_type', '') ==# 'blob'
3489 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3491 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3494 let nowait = v:version >= 704 ? '<nowait>' : ''
3495 if get(b:, 'fugitive_type', '') ==# 'blob'
3496 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3497 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3498 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3499 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3501 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3502 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3503 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3504 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3506 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>"
3507 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3508 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3509 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3510 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3511 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3512 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3513 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3514 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3515 nmap <buffer> . <SID>: <Plug><cfile><Home>
3519 function! s:StatusCfile(...) abort
3521 let tree = FugitiveTreeForGitDir(b:git_dir)
3522 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3523 if getline('.') =~# '^.\=\trenamed:.* -> '
3524 return lead . matchstr(getline('.'),' -> \zs.*')
3525 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3526 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3527 elseif getline('.') =~# '^.\=\t.'
3528 return lead . matchstr(getline('.'),'\t\zs.*')
3529 elseif getline('.') =~# ': needs merge$'
3530 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3531 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3533 elseif getline('.') =~# '^\%(. \)\=On branch '
3534 return 'refs/heads/'.getline('.')[12:]
3535 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3536 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3542 function! fugitive#StatusCfile() abort
3543 let file = s:Generate(s:StatusCfile())
3544 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
3547 function! s:cfile() abort
3549 let myhash = s:DirRev(@%)[1]
3552 let myhash = fugitive#RevParse(myhash)
3557 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3558 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3561 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3563 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3564 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3566 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3567 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3569 return [treebase . s:sub(getline('.'),'/$','')]
3576 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3577 let ref = matchstr(getline('.'),'\x\{40\}')
3578 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3582 if getline('.') =~# '^ref: '
3583 let ref = strpart(getline('.'),5)
3585 elseif getline('.') =~# '^commit \x\{40\}\>'
3586 let ref = matchstr(getline('.'),'\x\{40\}')
3589 elseif getline('.') =~# '^parent \x\{40\}\>'
3590 let ref = matchstr(getline('.'),'\x\{40\}')
3591 let line = line('.')
3593 while getline(line) =~# '^parent '
3599 elseif getline('.') =~# '^tree \x\{40\}$'
3600 let ref = matchstr(getline('.'),'\x\{40\}')
3601 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3602 let ref = myhash.':'
3606 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3607 let ref = matchstr(getline('.'),'\x\{40\}')
3608 let type = matchstr(getline(line('.')+1),'type \zs.*')
3610 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3611 let ref = s:DirRev(@%)[1]
3613 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3614 let ref = matchstr(getline('.'),'\x\{40\}')
3615 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3617 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3618 let ref = getline('.')[4:]
3620 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3621 let type = getline('.')[0]
3622 let lnum = line('.') - 1
3624 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3625 if getline(lnum) =~# '^[ '.type.']'
3630 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3631 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3632 let dcmds = [offset, 'normal!zv']
3634 elseif getline('.') =~# '^rename from '
3635 let ref = 'a/'.getline('.')[12:]
3636 elseif getline('.') =~# '^rename to '
3637 let ref = 'b/'.getline('.')[10:]
3639 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3640 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3641 let offset = matchstr(getline('.'), '+\zs\d\+')
3643 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3644 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3645 let dcmd = 'Gdiff! +'.offset
3647 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3648 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3649 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3652 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3653 let line = getline(line('.')-1)
3654 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3655 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3658 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3659 let ref = getline('.')
3661 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3662 return [expand('<cword>')]
3677 let prefixes.a = myhash.'^:'
3678 let prefixes.b = myhash.':'
3680 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3682 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3685 if ref ==# '/dev/null'
3687 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3691 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3693 return [ref] + dcmds
3701 function! s:GF(mode) abort
3703 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3705 return 'echoerr v:errmsg'
3708 return 'G' . a:mode .
3709 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3710 \ s:fnameescape(results[0])
3712 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3718 function! fugitive#Cfile() abort
3720 let results = s:cfile()
3722 let cfile = expand('<cfile>')
3723 if &includeexpr =~# '\<v:fname\>'
3724 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3727 elseif len(results) > 1
3728 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3730 return pre . s:fnameescape(s:Generate(results[0]))
3733 " Section: Statusline
3735 function! fugitive#Statusline(...) abort
3736 if !exists('b:git_dir')
3740 let commit = s:DirCommitFile(@%)[1]
3742 let status .= ':' . commit[0:6]
3744 let status .= '('.FugitiveHead(7).')'
3745 return '[Git'.status.']'
3748 function! fugitive#statusline(...) abort
3749 return fugitive#Statusline()
3752 function! fugitive#head(...) abort
3753 if !exists('b:git_dir')
3757 return fugitive#Head(a:0 ? a:1 : 0)
3762 function! fugitive#Foldtext() abort
3763 if &foldmethod !=# 'syntax'
3767 let line_foldstart = getline(v:foldstart)
3768 if line_foldstart =~# '^diff '
3769 let [add, remove] = [-1, -1]
3771 for lnum in range(v:foldstart, v:foldend)
3772 let line = getline(lnum)
3773 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3774 let filename = line[6:-1]
3778 elseif line =~# '^-'
3780 elseif line =~# '^Binary '
3785 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3788 let filename = line_foldstart[5:-1]
3791 return 'Binary: '.filename
3793 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3795 elseif line_foldstart =~# '^# .*:$'
3796 let lines = getline(v:foldstart, v:foldend)
3797 call filter(lines, 'v:val =~# "^#\t"')
3798 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3799 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3800 return line_foldstart.' '.join(lines, ', ')
3805 function! fugitive#foldtext() abort
3806 return fugitive#Foldtext()
3809 augroup fugitive_folding
3811 autocmd User Fugitive
3812 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3813 \ set foldtext=fugitive#Foldtext() |
3817 " Section: Initialization
3819 function! fugitive#Init() abort
3820 if exists('#User#FugitiveBoot')
3822 let [save_mls, &modelines] = [&mls, 0]
3823 doautocmd User FugitiveBoot
3828 if !exists('g:fugitive_no_maps')
3829 call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3830 call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3832 if expand('%:p') =~# ':[\/][\/]'
3833 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3835 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3836 if filereadable(b:git_dir.'/tags')
3837 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3839 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3840 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3844 let [save_mls, &modelines] = [&mls, 0]
3845 call s:define_commands()
3846 doautocmd User Fugitive
3852 function! fugitive#is_git_dir(path) abort
3853 return FugitiveIsGitDir(a:path)
3856 function! fugitive#extract_git_dir(path) abort
3857 return FugitiveExtractGitDir(a:path)
3860 function! fugitive#detect(path) abort
3861 return FugitiveDetect(a:path)