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 unlet b:fugitive_type
1445 let &readonly = !filewritable(dir . '/index')
1446 return 'silent doautocmd BufNewFile '.s:fnameescape(amatch)
1448 setlocal readonly nomodifiable
1451 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
1452 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
1454 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
1455 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
1459 if b:fugitive_type !=# 'blob'
1463 setlocal noreadonly modifiable
1464 let pos = getpos('.')
1465 silent keepjumps %delete_
1469 if b:fugitive_type ==# 'tree'
1470 let b:fugitive_display_format = b:fugitive_display_format % 2
1471 if b:fugitive_display_format
1472 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
1475 let sha = system(s:Prepare(dir, 'rev-parse', '--verify', rev, '--'))[0:-2]
1477 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
1479 elseif b:fugitive_type ==# 'tag'
1480 let b:fugitive_display_format = b:fugitive_display_format % 2
1481 if b:fugitive_display_format
1482 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1484 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
1486 elseif b:fugitive_type ==# 'commit'
1487 let b:fugitive_display_format = b:fugitive_display_format % 2
1488 if b:fugitive_display_format
1489 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1491 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])
1492 keepjumps call search('^parent ')
1493 if getline('.') ==# 'parent '
1494 silent keepjumps delete_
1496 silent exe 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
1498 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
1500 silent keepjumps delete_
1502 silent keepjumps 1,/^diff --git\|\%$/g/\r$/s///
1505 elseif b:fugitive_type ==# 'stage'
1506 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
1507 elseif b:fugitive_type ==# 'blob'
1508 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
1512 keepjumps call setpos('.',pos)
1513 setlocal nomodified noswapfile
1515 setlocal nomodifiable
1517 let &modifiable = b:fugitive_type !=# 'tree'
1519 let &readonly = !&modifiable || !filewritable(dir . '/index')
1520 if &bufhidden ==# ''
1521 setlocal bufhidden=delete
1523 if b:fugitive_type !=# 'blob'
1524 setlocal filetype=git foldmethod=syntax
1525 nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1526 nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>
1528 call fugitive#MapJumps()
1534 return 'echoerr v:errmsg'
1538 function! fugitive#BufWriteCmd(...) abort
1539 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
1542 function! fugitive#SourceCmd(...) abort
1543 let amatch = a:0 ? a:1 : expand('<amatch>')
1544 let temp = s:BlobTemp(amatch)
1546 return 'noautocmd source ' . s:fnameescape(amatch)
1548 if !exists('g:virtual_scriptnames')
1549 let g:virtual_scriptnames = {}
1551 let g:virtual_scriptnames[temp] = amatch
1552 return 'source ' . s:fnameescape(temp)
1555 " Section: Temp files
1557 if !exists('s:temp_files')
1558 let s:temp_files = {}
1561 function! s:SetupTemp(file) abort
1562 if has_key(s:temp_files, s:cpath(a:file))
1563 let dict = s:temp_files[s:cpath(a:file)]
1564 let b:git_dir = dict.dir
1565 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
1566 if has_key(dict, 'filetype') && dict.filetype !=# &l:filetype
1567 let &l:filetype = dict.filetype
1569 setlocal foldmarker=<<<<<<<,>>>>>>>
1570 setlocal bufhidden=delete nobuflisted
1571 setlocal buftype=nowrite
1572 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1573 if getline(1) !~# '^diff '
1574 setlocal nomodifiable
1576 call FugitiveDetect(a:file)
1581 augroup fugitive_temp
1583 autocmd BufNewFile,BufReadPost * exe s:SetupTemp(expand('<amatch>:p'))
1588 call s:command("-bang -nargs=? -complete=customlist,s:GitComplete Git :execute s:Git(<bang>0,'<mods>',<q-args>)")
1590 function! s:Git(bang, mods, args) abort
1592 return s:Edit('edit', 1, a:mods, a:args)
1594 let git = s:UserCommand()
1595 if has('gui_running') && !has('win32')
1596 let git .= ' --no-pager'
1598 let args = matchstr(a:args,'\v\C.{-}%($|\\@<!%(\\\\)*\|)@=')
1599 let after = matchstr(a:args, '\v\C\\@<!%(\\\\)*\zs\|.*')
1602 let after = '|call fugitive#ReloadStatus()' . after
1604 if exists(':terminal') && has('nvim') && !get(g:, 'fugitive_force_bang_command')
1610 execute 'lcd' fnameescape(tree)
1611 let exec = escape(git . ' ' . s:ShellExpand(args), '#%')
1612 return 'exe ' . string('terminal ' . exec) . after
1614 let cmd = "exe '!'.escape(" . string(git) . " . ' ' . s:ShellExpand(" . string(args) . "),'!#%')"
1615 if s:cpath(tree) !=# s:cpath(getcwd())
1616 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1617 let cmd = 'try|' . cd . ' ' . tree . '|' . cmd . '|finally|' . cd . ' ' . s:fnameescape(getcwd()) . '|endtry'
1623 let s:exec_paths = {}
1624 function! s:Subcommands() abort
1625 if !has_key(s:exec_paths, g:fugitive_git_executable)
1626 let s:exec_paths[g:fugitive_git_executable] = s:sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
1628 let exec_path = s:exec_paths[g:fugitive_git_executable]
1629 return map(split(glob(exec_path.'/git-*'),"\n"),'s:sub(v:val[strlen(exec_path)+5 : -1],"\\.exe$","")')
1633 function! s:Aliases() abort
1634 if !has_key(s:aliases, b:git_dir)
1635 let s:aliases[b:git_dir] = {}
1636 let lines = split(s:TreeChomp('config','-z','--get-regexp','^alias[.]'),"\1")
1637 for line in v:shell_error ? [] : lines
1638 let s:aliases[b:git_dir][matchstr(line, '\.\zs.\{-}\ze\n')] = matchstr(line, '\n\zs.*')
1641 return s:aliases[b:git_dir]
1644 function! s:GitComplete(A, L, P) abort
1645 let pre = strpart(a:L, 0, a:P)
1646 if pre !~# ' [[:alnum:]-]\+ '
1647 let cmds = s:Subcommands()
1648 return filter(sort(cmds+keys(s:Aliases())), 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
1649 elseif pre =~# ' -- '
1650 return fugitive#PathComplete(a:A, b:git_dir)
1652 return fugitive#Complete(a:A, b:git_dir)
1656 " Section: :Gcd, :Glcd
1658 function! s:DirComplete(A, L, P) abort
1659 return filter(fugitive#PathComplete(a:A), 'v:val =~# "/$"')
1662 function! s:DirArg(path) abort
1663 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
1664 if path =~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
1667 return (empty(s:Tree()) ? b:git_dir : s:Tree()) . '/' . path
1671 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Gcd :exe 'cd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1672 call s:command("-bar -bang -nargs=? -complete=customlist,s:DirComplete Glcd :exe 'lcd<bang>' s:fnameescape(s:DirArg(<q-args>))")
1676 call s:command("-bar -bang -range=-1 Gstatus :execute s:Status(<bang>0, <count>, '<mods>')")
1677 augroup fugitive_status
1680 autocmd FocusGained,ShellCmdPost * call fugitive#ReloadStatus()
1681 autocmd BufDelete term://* call fugitive#ReloadStatus()
1685 function! s:Status(bang, count, mods) abort
1687 exe (a:mods ==# '<mods>' ? '' : a:mods) 'Gpedit :'
1689 setlocal foldmethod=syntax foldlevel=1 buftype=nowrite
1690 nnoremap <buffer> <silent> q :<C-U>bdelete<CR>
1692 return 'echoerr v:errmsg'
1697 function! fugitive#ReloadStatus() abort
1698 if exists('s:reloading_status')
1702 let s:reloading_status = 1
1703 let mytab = tabpagenr()
1704 for tab in [mytab] + range(1,tabpagenr('$'))
1705 for winnr in range(1,tabpagewinnr(tab,'$'))
1706 if getbufvar(tabpagebuflist(tab)[winnr-1],'fugitive_type') ==# 'index'
1707 execute 'tabnext '.tab
1709 execute winnr.'wincmd w'
1710 let restorewinnr = 1
1714 call fugitive#BufReadStatus()
1717 if exists('restorewinnr')
1720 execute 'tabnext '.mytab
1726 unlet! s:reloading_status
1730 function! fugitive#reload_status() abort
1731 return fugitive#ReloadStatus()
1734 function! s:stage_info(lnum) abort
1735 let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
1737 if has('multi_byte_encoding')
1738 let colon = '\%(:\|\%uff1a\)'
1742 while lnum && getline(lnum) !~# colon.'$'
1747 elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
1748 return [matchstr(filename, colon.' *\zs.*'), 'staged']
1749 elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
1750 return [filename, 'untracked']
1751 elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
1752 return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
1753 elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
1754 return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
1756 return ['', 'unknown']
1760 function! s:StageNext(count) abort
1761 for i in range(a:count)
1762 call search('^.\=\t.*','W')
1767 function! s:StagePrevious(count) abort
1768 if line('.') == 1 && exists(':CtrlP') && get(g:, 'ctrl_p_map') =~? '^<c-p>$'
1769 return 'CtrlP '.fnameescape(s:Tree())
1771 for i in range(a:count)
1772 call search('^.\=\t.*','Wbe')
1778 function! s:StageReloadSeek(target,lnum1,lnum2) abort
1780 let f = matchstr(getline(a:lnum1-1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1781 if f !=# '' | let jump = f | endif
1782 let f = matchstr(getline(a:lnum2+1),'^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\zs.*')
1783 if f !=# '' | let jump = f | endif
1787 call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.jump.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1790 function! s:StageUndo() abort
1791 let [filename, section] = s:stage_info(line('.'))
1795 let hash = s:TreeChomp('hash-object', '-w', './' . filename)
1797 if section ==# 'untracked'
1798 call s:TreeChomp('clean', '-f', './' . filename)
1799 elseif section ==# 'unmerged'
1800 call s:TreeChomp('rm', './' . filename)
1801 elseif section ==# 'unstaged'
1802 call s:TreeChomp('checkout', './' . filename)
1804 call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
1806 call s:StageReloadSeek(filename, line('.'), line('.'))
1808 return 'checktime|redraw|echomsg ' .
1809 \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
1813 function! s:StageDiff(diff) abort
1814 let [filename, section] = s:stage_info(line('.'))
1815 if filename ==# '' && section ==# 'staged'
1816 return 'Git! diff --no-ext-diff --cached'
1817 elseif filename ==# ''
1818 return 'Git! diff --no-ext-diff'
1819 elseif filename =~# ' -> '
1820 let [old, new] = split(filename,' -> ')
1821 execute 'Gedit '.s:fnameescape(':0:'.new)
1822 return a:diff.' HEAD:'.s:fnameescape(old)
1823 elseif section ==# 'staged'
1824 execute 'Gedit '.s:fnameescape(':0:'.filename)
1827 execute 'Gedit '.s:fnameescape('/'.filename)
1832 function! s:StageDiffEdit() abort
1833 let [filename, section] = s:stage_info(line('.'))
1834 let arg = (filename ==# '' ? '.' : filename)
1835 if section ==# 'staged'
1836 return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
1837 elseif section ==# 'untracked'
1838 call s:TreeChomp('add', '--intent-to-add', './' . arg)
1842 if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
1843 call search(':$','W')
1846 call s:StageReloadSeek(arg,line('.'),line('.'))
1850 return 'Git! diff --no-ext-diff '.s:shellesc(arg)
1854 function! s:StageToggle(lnum1,lnum2) abort
1855 if a:lnum1 == 1 && a:lnum2 == 1
1856 return 'Gedit .git/|call search("^index$", "wc")'
1860 for lnum in range(a:lnum1,a:lnum2)
1861 let [filename, section] = s:stage_info(lnum)
1862 if getline('.') =~# ':$'
1863 if section ==# 'staged'
1864 call s:TreeChomp('reset','-q')
1867 if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
1868 call search(':$','W')
1871 elseif section ==# 'unstaged'
1872 call s:TreeChomp('add','-u')
1875 if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
1876 call search(':$','W')
1880 call s:TreeChomp('add', '.')
1883 call search(':$','W')
1891 if section ==# 'staged'
1892 let files_to_unstage = split(filename, ' -> ')
1893 let filename = files_to_unstage[-1]
1894 let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
1895 elseif getline(lnum) =~# '^.\=\tdeleted:'
1896 let cmd = ['rm', './' . filename]
1897 elseif getline(lnum) =~# '^.\=\tmodified:'
1898 let cmd = ['add', './' . filename]
1900 let cmd = ['add','-A', './' . filename]
1902 if !exists('first_filename')
1903 let first_filename = filename
1905 let output .= call('s:TreeChomp', cmd)."\n"
1907 if exists('first_filename')
1908 call s:StageReloadSeek(first_filename,a:lnum1,a:lnum2)
1910 echo s:sub(s:gsub(output,'\n+','\n'),'\n$','')
1912 return 'echoerr v:errmsg'
1917 function! s:StagePatch(lnum1,lnum2) abort
1921 for lnum in range(a:lnum1,a:lnum2)
1922 let [filename, section] = s:stage_info(lnum)
1923 if getline('.') =~# ':$' && section ==# 'staged'
1924 return 'Git reset --patch'
1925 elseif getline('.') =~# ':$' && section ==# 'unstaged'
1926 return 'Git add --patch'
1927 elseif getline('.') =~# ':$' && section ==# 'untracked'
1928 return 'Git add -N .'
1929 elseif filename ==# ''
1932 if !exists('first_filename')
1933 let first_filename = filename
1936 if filename =~ ' -> '
1937 let reset += [split(filename,' -> ')[1]]
1938 elseif section ==# 'staged'
1939 let reset += [filename]
1940 elseif getline(lnum) !~# '^.\=\tdeleted:'
1941 let add += [filename]
1946 execute "Git add --patch -- ".join(map(add,'s:shellesc(v:val)'))
1949 execute "Git reset --patch -- ".join(map(reset,'s:shellesc(v:val)'))
1951 if exists('first_filename')
1955 call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
1958 return 'echoerr v:errmsg'
1965 call s:command("-nargs=? -complete=customlist,s:CommitComplete Gcommit :execute s:Commit('<mods>', <q-args>)")
1967 function! s:Commit(mods, args, ...) abort
1968 let mods = s:gsub(a:mods ==# '<mods>' ? '' : a:mods, '<tab>', '-tab')
1969 let dir = a:0 ? a:1 : b:git_dir
1970 let tree = s:Tree(dir)
1971 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
1973 let msgfile = dir . '/COMMIT_EDITMSG'
1974 let outfile = tempname()
1975 let errorfile = tempname()
1977 let guioptions = &guioptions
1979 if &guioptions =~# '!'
1980 setglobal guioptions-=!
1982 execute cd s:fnameescape(tree)
1985 let old_editor = $GIT_EDITOR
1986 let $GIT_EDITOR = 'false'
1988 let command = 'env GIT_EDITOR=false '
1990 let args = s:ShellExpand(a:args)
1991 let command .= s:UserCommand() . ' commit ' . args
1993 noautocmd silent execute '!('.escape(command, '!#%').' > '.outfile.') >& '.errorfile
1994 elseif a:args =~# '\%(^\| \)-\%(-interactive\|p\|-patch\)\>'
1995 noautocmd execute '!'.command.' 2> '.errorfile
1997 noautocmd silent execute '!'.command.' > '.outfile.' 2> '.errorfile
1999 let error = v:shell_error
2001 execute cd s:fnameescape(cwd)
2002 let &guioptions = guioptions
2004 if !has('gui_running')
2008 if filereadable(outfile)
2009 for line in readfile(outfile)
2015 let errors = readfile(errorfile)
2016 let error = get(errors,-2,get(errors,-1,'!'))
2017 if error =~# 'false''\=\.$'
2018 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-[esp]|--edit|--interactive|--patch|--signoff)%($| )','')
2019 let args = s:gsub(args,'%(%(^| )-- )@<!%(^| )@<=%(-c|--reedit-message|--reuse-message|-F|--file|-m|--message)%(\s+|\=)%(''[^'']*''|"%(\\.|[^"])*"|\\.|\S)*','')
2020 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2022 let args = s:sub(args, '\ze -- |$', ' --no-edit --no-interactive --no-signoff')
2023 let args = '-F '.s:shellesc(msgfile).' '.args
2024 if args !~# '\%(^\| \)--cleanup\>'
2025 let args = '--cleanup=strip '.args
2027 if bufname('%') == '' && line('$') == 1 && getline(1) == '' && !&mod
2028 execute mods 'keepalt edit' s:fnameescape(msgfile)
2029 elseif a:args =~# '\%(^\| \)-\w*v' || mods =~# '\<tab\>'
2030 execute mods 'keepalt -tabedit' s:fnameescape(msgfile)
2031 elseif get(b:, 'fugitive_type', '') ==# 'index'
2032 execute mods 'keepalt edit' s:fnameescape(msgfile)
2033 execute (search('^#','n')+1).'wincmd+'
2034 setlocal nopreviewwindow
2036 execute mods 'keepalt split' s:fnameescape(msgfile)
2038 let b:fugitive_commit_arguments = args
2039 setlocal bufhidden=wipe filetype=gitcommit
2041 elseif error ==# '!'
2044 call s:throw(empty(error)?join(errors, ' '):error)
2048 return 'echoerr v:errmsg'
2050 if exists('old_editor')
2051 let $GIT_EDITOR = old_editor
2053 call delete(outfile)
2054 call delete(errorfile)
2055 call fugitive#ReloadStatus()
2059 function! s:CommitComplete(A,L,P) abort
2060 if a:A =~# '^--fixup=\|^--squash='
2061 let commits = split(s:TreeChomp('log', '--pretty=format:%s', '@{upstream}..'), "\n")
2063 let pre = matchstr(a:A, '^--\w*=') . ':/^'
2064 return map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")')
2066 elseif a:A =~ '^-' || type(a:A) == type(0) " a:A is 0 on :Gcommit -<Tab>
2067 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']
2068 return filter(args,'v:val[0 : strlen(a:A)-1] ==# a:A')
2070 return fugitive#PathComplete(a:A, b:git_dir)
2075 function! s:FinishCommit() abort
2076 let args = getbufvar(+expand('<abuf>'),'fugitive_commit_arguments')
2078 call setbufvar(+expand('<abuf>'),'fugitive_commit_arguments','')
2079 return s:Commit('', args, getbufvar(+expand('<abuf>'),'git_dir'))
2084 " Section: :Gmerge, :Grebase, :Gpull
2086 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Gmerge " .
2087 \ "execute s:Merge('merge', <bang>0, <q-args>)")
2088 call s:command("-nargs=? -bang -complete=custom,s:RevisionComplete Grebase " .
2089 \ "execute s:Merge('rebase', <bang>0, <q-args>)")
2090 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpull " .
2091 \ "execute s:Merge('pull --progress', <bang>0, <q-args>)")
2093 function! s:RevisionComplete(A, L, P) abort
2094 return s:TreeChomp('rev-parse', '--symbolic', '--branches', '--tags', '--remotes')
2095 \ . "\nHEAD\nFETCH_HEAD\nMERGE_HEAD\nORIG_HEAD"
2098 function! s:RemoteComplete(A, L, P) abort
2099 let remote = matchstr(a:L, ' \zs\S\+\ze ')
2101 let matches = split(s:TreeChomp('ls-remote', remote), "\n")
2102 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2103 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2105 let matches = split(s:TreeChomp('remote'), "\n")
2107 return join(matches, "\n")
2110 function! fugitive#Cwindow() abort
2111 if &buftype == 'quickfix'
2115 if &buftype == 'quickfix'
2121 let s:common_efm = ''
2123 \ . '%+Eusage:%.%#,'
2124 \ . '%+Eerror:%.%#,'
2125 \ . '%+Efatal:%.%#,'
2126 \ . '%-G%.%#%\e[K%.%#,'
2127 \ . '%-G%.%#%\r%.%\+'
2129 function! s:Merge(cmd, bang, args) abort
2130 if a:cmd =~# '^rebase' && ' '.a:args =~# ' -i\| --interactive\| --edit-todo'
2131 return 'echoerr "git rebase --interactive not supported"'
2133 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2135 let [mp, efm] = [&l:mp, &l:efm]
2136 let had_merge_msg = filereadable(b:git_dir . '/MERGE_MSG')
2138 let &l:errorformat = ''
2139 \ . '%-Gerror:%.%#false''.,'
2140 \ . '%-G%.%# ''git commit'' %.%#,'
2141 \ . '%+Emerge:%.%#,'
2142 \ . s:common_efm . ','
2143 \ . '%+ECannot %.%#: You have unstaged changes.,'
2144 \ . '%+ECannot %.%#: Your index contains uncommitted changes.,'
2145 \ . '%+EThere is no tracking information for the current branch.,'
2146 \ . '%+EYou are not currently on a branch. Please specify which,'
2147 \ . 'CONFLICT (%m): %f deleted in %.%#,'
2148 \ . 'CONFLICT (%m): Merge conflict in %f,'
2149 \ . 'CONFLICT (%m): Rename \"%f\"->%.%#,'
2150 \ . 'CONFLICT (%m): Rename %.%#->%f %.%#,'
2151 \ . 'CONFLICT (%m): There is a directory with name %f in %.%#,'
2152 \ . '%+ECONFLICT %.%#,'
2153 \ . '%+EKONFLIKT %.%#,'
2154 \ . '%+ECONFLIT %.%#,'
2155 \ . "%+EXUNG \u0110\u1ed8T %.%#,"
2156 \ . "%+E\u51b2\u7a81 %.%#,"
2158 if a:cmd =~# '^merge' && empty(a:args) &&
2159 \ (had_merge_msg || isdirectory(b:git_dir . '/rebase-apply') ||
2160 \ !empty(s:TreeChomp('diff-files', '--diff-filter=U')))
2161 let &l:makeprg = g:fugitive_git_executable.' diff-files --name-status --diff-filter=U'
2163 let &l:makeprg = s:sub(s:UserCommand() . ' ' . a:cmd .
2164 \ (a:args =~# ' \%(--no-edit\|--abort\|-m\)\>' || a:cmd =~# '^rebase' ? '' : ' --edit') .
2165 \ ' ' . a:args, ' *$', '')
2167 if !empty($GIT_EDITOR) || has('win32')
2168 let old_editor = $GIT_EDITOR
2169 let $GIT_EDITOR = 'false'
2171 let &l:makeprg = 'env GIT_EDITOR=false ' . &l:makeprg
2173 execute cd fnameescape(s:Tree())
2174 silent noautocmd make!
2175 catch /^Vim\%((\a\+)\)\=:E211/
2176 let err = v:exception
2179 let [&l:mp, &l:efm] = [mp, efm]
2180 if exists('old_editor')
2181 let $GIT_EDITOR = old_editor
2183 execute cd fnameescape(cwd)
2185 call fugitive#ReloadStatus()
2186 if empty(filter(getqflist(),'v:val.valid'))
2187 if !had_merge_msg && filereadable(b:git_dir . '/MERGE_MSG')
2189 return 'Gcommit --no-status -n -t '.s:shellesc(b:git_dir . '/MERGE_MSG')
2192 let qflist = getqflist()
2197 let e.pattern = '^<<<<<<<'
2200 call fugitive#Cwindow()
2202 call setqflist(qflist, 'r')
2207 return exists('err') ? 'echoerr '.string(err) : ''
2210 " Section: :Ggrep, :Glog
2212 if !exists('g:fugitive_summary_format')
2213 let g:fugitive_summary_format = '%s'
2216 function! s:GrepComplete(A, L, P) abort
2217 if strpart(a:L, 0, a:P) =~# ' -- '
2218 return fugitive#PathComplete(a:A, b:git_dir)
2220 return fugitive#Complete(a:A, b:git_dir)
2224 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Ggrep :execute s:Grep('grep',<bang>0,<q-args>)")
2225 call s:command("-bang -nargs=? -complete=customlist,s:GrepComplete Glgrep :execute s:Grep('lgrep',<bang>0,<q-args>)")
2226 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Glog :call s:Log('grep',<bang>0,<line1>,<count>,<q-args>)")
2227 call s:command("-bar -bang -nargs=* -range=-1 -complete=customlist,s:GrepComplete Gllog :call s:Log('lgrep',<bang>0,<line1>,<count>,<q-args>)")
2229 function! s:Grep(cmd,bang,arg) abort
2230 let grepprg = &grepprg
2231 let grepformat = &grepformat
2232 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2235 execute cd s:fnameescape(s:Tree())
2236 let &grepprg = s:UserCommand() . ' --no-pager grep -n --no-color'
2237 let &grepformat = '%f:%l:%m,%m %f match%ts,%f'
2238 exe a:cmd.'! '.escape(s:ShellExpand(matchstr(a:arg, '\v\C.{-}%($|[''" ]\@=\|)@=')), '|#%')
2239 let list = a:cmd =~# '^l' ? getloclist(0) : getqflist()
2241 if bufname(entry.bufnr) =~ ':'
2242 let entry.filename = s:Generate(bufname(entry.bufnr))
2245 elseif a:arg =~# '\%(^\| \)--cached\>'
2246 let entry.filename = s:Generate(':0:'.bufname(entry.bufnr))
2251 if a:cmd =~# '^l' && exists('changed')
2252 call setloclist(0, list, 'r')
2253 elseif exists('changed')
2254 call setqflist(list, 'r')
2256 if !a:bang && !empty(list)
2257 return (a:cmd =~# '^l' ? 'l' : 'c').'first'.matchstr(a:arg,'\v\C[''" ]\zs\|.*')
2259 return matchstr(a:arg,'\v\C[''" ]\|\zs.*')
2262 let &grepprg = grepprg
2263 let &grepformat = grepformat
2264 execute cd s:fnameescape(dir)
2268 function! s:Log(cmd, bang, line1, line2, ...) abort
2269 let args = ' ' . join(a:000, ' ')
2270 let before = substitute(args, ' --\S\@!.*', '', '')
2271 let after = strpart(args, len(before))
2272 let path = s:Relative('/')
2273 let relative = path[1:-1]
2274 if path =~# '^/\.git\%(/\|$\)' || len(after)
2277 if before !~# '\s[^[:space:]-]'
2278 let owner = s:Owner(@%)
2280 let before .= ' ' . s:shellesc(owner)
2283 if relative =~# '^\.git\%(/\|$\)'
2286 if len(relative) && a:line2 > 0
2287 let before .= ' -L ' . s:shellesc(a:line1 . ',' . a:line2 . ':' . relative)
2288 elseif len(relative) && (empty(after) || a:line2 == 0)
2289 let after = (len(after) > 3 ? after : ' -- ') . relative
2291 let grepformat = &grepformat
2292 let grepprg = &grepprg
2293 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2296 execute cd s:fnameescape(s:Tree())
2297 let &grepprg = escape(s:UserCommand() . ' --no-pager log --no-color ' .
2298 \ s:shellesc('--pretty=format:fugitive://'.b:git_dir.'//%H'.path.'::'.g:fugitive_summary_format), '%#')
2299 let &grepformat = '%Cdiff %.%#,%C--- %.%#,%C+++ %.%#,%Z@@ -%\d%\+\,%\d%\+ +%l\,%\d%\+ @@,%-G-%.%#,%-G+%.%#,%-G %.%#,%A%f::%m,%-G%.%#'
2300 exe a:cmd . (a:bang ? '! ' : ' ') . s:ShellExpand(before . after)
2302 let &grepformat = grepformat
2303 let &grepprg = grepprg
2304 execute cd s:fnameescape(dir)
2308 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
2310 function! s:UsableWin(nr) abort
2311 return a:nr && !getwinvar(a:nr, '&previewwindow') &&
2312 \ index(['nofile','help','quickfix'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
2315 function! s:EditParse(args) abort
2317 let args = copy(a:args)
2318 while !empty(args) && args[0] =~# '^+'
2319 call add(pre, ' ' . escape(remove(args, 0), ' |"'))
2322 let file = join(args)
2323 elseif empty(expand('%'))
2325 elseif empty(s:DirCommitFile(@%)[1]) && s:Relative('./') !~# '^\./\.git\>'
2330 return [s:Expand(file), join(pre)]
2333 function! s:BlurStatus() abort
2334 if &previewwindow && get(b:,'fugitive_type', '') ==# 'index'
2335 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
2337 exe winnrs[0].'wincmd w'
2338 elseif winnr('$') == 1
2339 let tabs = (&go =~# 'e' || !has('gui_running')) && &stal && (tabpagenr('$') >= &stal)
2340 execute 'rightbelow' (&lines - &previewheight - &cmdheight - tabs - 1 - !!&laststatus).'new'
2345 let mywinnr = winnr()
2346 for winnr in range(winnr('$'),1,-1)
2347 if winnr != mywinnr && getwinvar(winnr,'&diff')
2348 execute winnr.'wincmd w'
2360 function! s:Edit(cmd, bang, mods, args, ...) abort
2361 let mods = a:mods ==# '<mods>' ? '' : a:mods
2364 let temp = tempname()
2365 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2368 execute cd s:fnameescape(s:Tree())
2369 let git = s:UserCommand()
2370 let args = s:ShellExpand(a:args)
2371 silent! execute '!' . escape(git . ' --no-pager ' . args, '!#%') .
2372 \ (&shell =~# 'csh' ? ' >& ' . temp : ' > ' . temp . ' 2>&1')
2374 execute cd s:fnameescape(cwd)
2376 let temp = s:Resolve(temp)
2377 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'git' }
2381 silent execute mods a:cmd temp
2382 call fugitive#ReloadStatus()
2383 return 'redraw|echo ' . string(':!' . git . ' ' . args)
2386 let [file, pre] = s:EditParse(a:000)
2388 let file = s:Generate(file)
2390 return 'echoerr v:errmsg'
2392 if file !~# '^\a\a\+:'
2393 let file = s:sub(file, '/$', '')
2398 return mods . ' ' . a:cmd . pre . ' ' . s:fnameescape(file)
2401 function! s:Read(count, line1, line2, range, bang, mods, args, ...) abort
2402 let mods = a:mods ==# '<mods>' ? '' : a:mods
2405 let delete = 'silent 1,' . line('$') . 'delete_|'
2406 let after = line('$')
2408 let delete = 'silent ' . a:line1 . ',' . a:line2 . 'delete_|'
2413 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2416 execute cd s:fnameescape(s:Tree())
2417 let git = s:UserCommand()
2418 let args = s:ShellExpand(a:args)
2419 silent execute mods after.'read!' escape(git . ' --no-pager ' . args, '!#%')
2421 execute cd s:fnameescape(cwd)
2423 execute delete . 'diffupdate'
2424 call fugitive#ReloadStatus()
2425 return 'redraw|echo '.string(':!'.git.' '.args)
2427 let [file, pre] = s:EditParse(a:000)
2429 let file = s:Generate(file)
2431 return 'echoerr v:errmsg'
2433 if file =~# '^fugitive:' && after is# 0
2434 return 'exe ' .string(mods . ' ' . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
2436 return mods . ' ' . after . 'read' . pre . ' ' . s:fnameescape(file) . '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')
2439 function! s:EditRunComplete(A,L,P) abort
2441 return s:GitComplete(a:A, a:L, a:P)
2443 return fugitive#Complete(a:A, a:L, a:P)
2447 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Ge execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2448 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gedit execute s:Edit('edit<bang>', 0, '<mods>', <q-args>, <f-args>)")
2449 call s:command("-bar -bang -nargs=* -complete=customlist,s:EditRunComplete Gpedit execute s:Edit('pedit', <bang>0, '<mods>', <q-args>, <f-args>)")
2450 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>)")
2451 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>)")
2452 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>)")
2453 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>)")
2455 " Section: :Gwrite, :Gwq
2457 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwrite :execute s:Write(<bang>0,<f-args>)")
2458 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gw :execute s:Write(<bang>0,<f-args>)")
2459 call s:command("-bar -bang -nargs=* -complete=customlist,fugitive#Complete Gwq :execute s:Wq(<bang>0,<f-args>)")
2461 function! s:Write(force,...) abort
2462 if exists('b:fugitive_commit_arguments')
2463 return 'write|bdelete'
2464 elseif expand('%:t') == 'COMMIT_EDITMSG' && $GIT_INDEX_FILE != ''
2466 elseif get(b:, 'fugitive_type', '') ==# 'index'
2468 elseif &buftype ==# 'nowrite' && getline(4) =~# '^+++ '
2469 let filename = getline(4)[6:-1]
2472 setlocal buftype=nowrite
2473 if matchstr(getline(2),'index [[:xdigit:]]\+\.\.\zs[[:xdigit:]]\{7\}') ==# fugitive#RevParse(':0:'.filename)[0:6]
2474 let err = s:TreeChomp('apply', '--cached', '--reverse', '--', expand('%:p'))
2476 let err = s:TreeChomp('apply', '--cached', '--', expand('%:p'))
2479 let v:errmsg = split(err,"\n")[0]
2480 return 'echoerr v:errmsg'
2484 return 'Gedit '.fnameescape(filename)
2487 let mytab = tabpagenr()
2488 let mybufnr = bufnr('')
2489 let file = a:0 ? s:Generate(s:Expand(join(a:000, ' '))) : fugitive#Real(@%)
2491 return 'echoerr '.string('fugitive: cannot determine file path')
2493 if file =~# '^fugitive:'
2494 return 'write' . (a:force ? '! ' : ' ') . s:fnameescape(file)
2496 let always_permitted = s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^0\=$'
2497 if !always_permitted && !a:force && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
2498 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
2499 return 'echoerr v:errmsg'
2502 for nr in range(1,bufnr('$'))
2503 if fnamemodify(bufname(nr),':p') ==# file
2508 if treebufnr > 0 && treebufnr != bufnr('')
2509 let temp = tempname()
2510 silent execute '%write '.temp
2511 for tab in [mytab] + range(1,tabpagenr('$'))
2512 for winnr in range(1,tabpagewinnr(tab,'$'))
2513 if tabpagebuflist(tab)[winnr-1] == treebufnr
2514 execute 'tabnext '.tab
2516 execute winnr.'wincmd w'
2517 let restorewinnr = 1
2520 let lnum = line('.')
2521 let last = line('$')
2522 silent execute '$read '.temp
2523 silent execute '1,'.last.'delete_'
2528 if exists('restorewinnr')
2531 execute 'tabnext '.mytab
2537 call writefile(readfile(temp,'b'),file,'b')
2540 execute 'write! '.s:fnameescape(file)
2544 let error = s:TreeChomp('add', '--force', '--', file)
2546 let error = s:TreeChomp('add', '--', file)
2549 let v:errmsg = 'fugitive: '.error
2550 return 'echoerr v:errmsg'
2552 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
2556 let one = s:Generate(':1:'.file)
2557 let two = s:Generate(':2:'.file)
2558 let three = s:Generate(':3:'.file)
2559 for nr in range(1,bufnr('$'))
2560 let name = fnamemodify(bufname(nr), ':p')
2561 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
2562 execute nr.'bdelete'
2567 let zero = s:Generate(':0:'.file)
2568 silent execute 'doautocmd BufWritePost' s:fnameescape(zero)
2569 for tab in range(1,tabpagenr('$'))
2570 for winnr in range(1,tabpagewinnr(tab,'$'))
2571 let bufnr = tabpagebuflist(tab)[winnr-1]
2572 let bufname = fnamemodify(bufname(bufnr), ':p')
2573 if bufname ==# zero && bufnr != mybufnr
2574 execute 'tabnext '.tab
2576 execute winnr.'wincmd w'
2577 let restorewinnr = 1
2580 let lnum = line('.')
2581 let last = line('$')
2582 silent execute '$read '.s:fnameescape(file)
2583 silent execute '1,'.last.'delete_'
2588 if exists('restorewinnr')
2591 execute 'tabnext '.mytab
2597 call fugitive#ReloadStatus()
2601 function! s:Wq(force,...) abort
2602 let bang = a:force ? '!' : ''
2603 if exists('b:fugitive_commit_arguments')
2606 let result = call(s:function('s:Write'),[a:force]+a:000)
2607 if result =~# '^\%(write\|wq\|echoerr\)'
2608 return s:sub(result,'^write','wq')
2610 return result.'|quit'.bang
2614 augroup fugitive_commit
2616 autocmd VimLeavePre,BufDelete COMMIT_EDITMSG execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
2619 " Section: :Gpush, :Gfetch
2621 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gpush execute s:Dispatch('<bang>', 'push '.<q-args>)")
2622 call s:command("-nargs=? -bang -complete=custom,s:RemoteComplete Gfetch execute s:Dispatch('<bang>', 'fetch '.<q-args>)")
2624 function! s:Dispatch(bang, args)
2625 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2627 let [mp, efm, cc] = [&l:mp, &l:efm, get(b:, 'current_compiler', '')]
2629 let b:current_compiler = 'git'
2630 let &l:errorformat = s:common_efm
2631 execute cd fnameescape(s:Tree())
2632 let &l:makeprg = substitute(s:UserCommand() . ' ' . a:args, '\s\+$', '', '')
2633 if exists(':Make') == 2
2636 silent noautocmd make!
2638 return 'call fugitive#Cwindow()'
2642 let [&l:mp, &l:efm, b:current_compiler] = [mp, efm, cc]
2643 if empty(cc) | unlet! b:current_compiler | endif
2644 execute cd fnameescape(cwd)
2650 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gdiff :execute s:Diff('',<bang>0,<f-args>)")
2651 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gvdiff :execute s:Diff('keepalt vert ',<bang>0,<f-args>)")
2652 call s:command("-bang -bar -nargs=* -complete=customlist,fugitive#Complete Gsdiff :execute s:Diff('keepalt ',<bang>0,<f-args>)")
2654 augroup fugitive_diff
2656 autocmd BufWinLeave *
2657 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
2658 \ call s:diffoff_all(getbufvar(+expand('<abuf>'), 'git_dir')) |
2660 autocmd BufWinEnter *
2661 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
2662 \ call s:diffoff() |
2666 function! s:can_diffoff(buf) abort
2667 return getwinvar(bufwinnr(a:buf), '&diff') &&
2668 \ !empty(getbufvar(a:buf, 'git_dir')) &&
2669 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
2672 function! fugitive#CanDiffoff(buf) abort
2673 return s:can_diffoff(a:buf)
2676 function! s:diff_modifier(count) abort
2677 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
2678 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
2680 elseif &diffopt =~# 'vertical'
2681 return 'keepalt vert '
2682 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
2685 return 'keepalt vert '
2689 function! s:diff_window_count() abort
2691 for nr in range(1,winnr('$'))
2692 let c += getwinvar(nr,'&diff')
2697 function! s:diff_restore() abort
2698 let restore = 'setlocal nodiff noscrollbind'
2699 \ . ' scrollopt=' . &l:scrollopt
2700 \ . (&l:wrap ? ' wrap' : ' nowrap')
2701 \ . ' foldlevel=999'
2702 \ . ' foldmethod=' . &l:foldmethod
2703 \ . ' foldcolumn=' . &l:foldcolumn
2704 \ . ' foldlevel=' . &l:foldlevel
2705 \ . (&l:foldenable ? ' foldenable' : ' nofoldenable')
2706 if has('cursorbind')
2707 let restore .= (&l:cursorbind ? ' ' : ' no') . 'cursorbind'
2712 function! s:diffthis() abort
2714 let w:fugitive_diff_restore = s:diff_restore()
2719 function! s:diffoff() abort
2720 if exists('w:fugitive_diff_restore')
2721 execute w:fugitive_diff_restore
2722 unlet w:fugitive_diff_restore
2728 function! s:diffoff_all(dir) abort
2729 let curwin = winnr()
2730 for nr in range(1,winnr('$'))
2731 if getwinvar(nr,'&diff')
2733 execute nr.'wincmd w'
2734 let restorewinnr = 1
2736 if exists('b:git_dir') && b:git_dir ==# a:dir
2741 execute curwin.'wincmd w'
2744 function! s:CompareAge(mine, theirs) abort
2745 let scores = {':0': 1, ':1': 2, ':2': 3, ':': 4, ':3': 5}
2746 let mine = substitute(a:mine, '^:', '', '')
2747 let theirs = substitute(a:theirs, '^:', '', '')
2748 let my_score = get(scores, ':'.mine, 0)
2749 let their_score = get(scores, ':'.theirs, 0)
2750 if my_score || their_score
2751 return my_score < their_score ? -1 : my_score != their_score
2752 elseif mine ==# theirs
2755 let base = s:TreeChomp('merge-base', mine, theirs)
2758 elseif base ==# theirs
2761 let my_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:mine, '--')
2762 let their_time = +s:TreeChomp('log', '--max-count=1', '--pretty=format:%at', a:theirs, '--')
2763 return my_time < their_time ? -1 : my_time != their_time
2766 function! s:Diff(vert,keepfocus,...) abort
2767 let args = copy(a:000)
2769 if get(args, 0) =~# '^+'
2770 let post = remove(args, 0)[1:-1]
2772 let vert = empty(a:vert) ? s:diff_modifier(2) : a:vert
2773 let commit = s:DirCommitFile(@%)[1]
2774 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
2775 if exists(':DiffGitCached')
2776 return 'DiffGitCached'
2777 elseif (empty(args) || args[0] ==# ':') && commit =~# '^[0-1]\=$' && !empty(s:TreeChomp('ls-files', '--unmerged', '--', expand('%:p')))
2779 return 'echoerr ' . string("fugitive: error determining merge status of the current buffer")
2781 let vert = empty(a:vert) ? s:diff_modifier(3) : a:vert
2783 execute 'leftabove '.vert.'split' s:fnameescape(s:Generate(s:Relative(':2:')))
2784 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2788 execute 'rightbelow '.vert.'split' s:fnameescape(s:Generate(s:Relative(':3:')))
2789 execute 'nnoremap <buffer> <silent> dp :diffput '.nr.'<Bar>diffupdate<CR>'
2794 execute 'nnoremap <buffer> <silent> d2o :diffget '.nr2.'<Bar>diffupdate<CR>'
2795 execute 'nnoremap <buffer> <silent> d3o :diffget '.nr3.'<Bar>diffupdate<CR>'
2798 let arg = join(args, ' ')
2802 let file = s:Relative()
2804 let file = s:Relative(':0:')
2805 elseif arg =~# '^:/.'
2807 let file = fugitive#RevParse(arg).s:Relative(':')
2809 return 'echoerr v:errmsg'
2812 let file = s:Expand(arg)
2814 if file !~# ':' && file !~# '^/' && s:TreeChomp('cat-file','-t',file) =~# '^\%(tag\|commit\)$'
2815 let file = file.s:Relative(':')
2818 let file = empty(commit) ? s:Relative(':0:') : s:Relative()
2821 let spec = s:Generate(file)
2822 let restore = s:diff_restore()
2823 if exists('+cursorbind')
2826 let w:fugitive_diff_restore = restore
2827 if s:CompareAge(commit, s:DirCommitFile(spec)[1]) < 0
2828 execute 'rightbelow '.vert.'diffsplit '.s:fnameescape(spec)
2830 execute 'leftabove '.vert.'diffsplit '.s:fnameescape(spec)
2832 let &l:readonly = &l:readonly
2834 let w:fugitive_diff_restore = restore
2836 if getwinvar('#', '&diff')
2839 call feedkeys(winnr."\<C-W>w", 'n')
2844 return 'echoerr v:errmsg'
2848 " Section: :Gmove, :Gremove
2850 function! s:Move(force, rename, destination) abort
2851 if a:destination =~# '^\.\.\=\%(/\|$\)'
2852 let destination = simplify(getcwd() . '/' . a:destination)
2853 elseif a:destination =~# '^\a\+:\|^/'
2854 let destination = a:destination
2855 elseif a:destination =~# '^:/:\='
2856 let destination = s:Tree() . substitute(a:destination, '^:/:\=', '', '')
2857 elseif a:destination =~# '^:(\%(top\|top,literal\|literal,top\))'
2858 let destination = s:Tree() . matchstr(a:destination, ')\zs.*')
2859 elseif a:destination =~# '^:(literal)'
2860 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
2862 let destination = expand('%:p:s?[\/]$??:h') . '/' . a:destination
2864 let destination = s:Tree() . '/' . a:destination
2866 let destination = s:Slash(destination)
2870 let message = call('s:TreeChomp', ['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination])
2872 let v:errmsg = 'fugitive: '.message
2873 return 'echoerr v:errmsg'
2875 if isdirectory(destination)
2876 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
2878 call fugitive#ReloadStatus()
2879 if empty(s:DirCommitFile(@%)[1])
2880 if isdirectory(destination)
2881 return 'keepalt edit '.s:fnameescape(destination)
2883 return 'keepalt saveas! '.s:fnameescape(destination)
2886 return 'file '.s:fnameescape(s:Generate(':0:'.destination))
2890 function! s:RenameComplete(A,L,P) abort
2891 if a:A =~# '^[.:]\=/'
2892 return fugitive#PathComplete(a:A)
2894 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
2895 return map(fugitive#PathComplete(pre.a:A), 'strpart(v:val, len(pre))')
2899 function! s:Remove(after, force) abort
2900 if s:DirCommitFile(@%)[1] ==# ''
2902 elseif s:DirCommitFile(@%)[1] ==# '0'
2903 let cmd = ['rm','--cached']
2905 let v:errmsg = 'fugitive: rm not supported here'
2906 return 'echoerr v:errmsg'
2909 let cmd += ['--force']
2911 let message = call('s:TreeChomp', cmd + ['--', expand('%:p')])
2913 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
2914 return 'echoerr '.string(v:errmsg)
2916 call fugitive#ReloadStatus()
2917 return a:after . (a:force ? '!' : '')
2921 augroup fugitive_remove
2923 autocmd User Fugitive if s:DirCommitFile(@%)[1] =~# '^0\=$' |
2924 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,fugitive#PathComplete Gmove :execute s:Move(<bang>0,0,<q-args>)" |
2925 \ exe "command! -buffer -bar -bang -nargs=1 -complete=customlist,s:RenameComplete Grename :execute s:Move(<bang>0,1,<q-args>)" |
2926 \ exe "command! -buffer -bar -bang Gremove :execute s:Remove('edit',<bang>0)" |
2927 \ exe "command! -buffer -bar -bang Gdelete :execute s:Remove('bdelete',<bang>0)" |
2933 function! s:Keywordprg() abort
2934 let args = ' --git-dir='.escape(b:git_dir,"\\\"' ")
2935 if has('gui_running') && !has('win32')
2936 return s:UserCommand() . ' --no-pager' . args . ' log -1'
2938 return s:UserCommand() . args . ' show'
2942 augroup fugitive_blame
2944 autocmd FileType fugitiveblame setlocal nomodeline | if exists('b:git_dir') | let &l:keywordprg = s:Keywordprg() | endif
2945 autocmd Syntax fugitiveblame call s:BlameSyntax()
2946 autocmd User Fugitive
2947 \ if get(b:, 'fugitive_type') =~# '^\%(file\|blob\|blame\)$' || filereadable(@%) |
2948 \ exe "command! -buffer -bar -bang -range=0 -nargs=* Gblame :execute s:Blame(<bang>0,<line1>,<line2>,<count>,'<mods>',[<f-args>])" |
2950 autocmd ColorScheme,GUIEnter * call s:RehighlightBlame()
2951 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
2954 function! s:linechars(pattern) abort
2955 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
2956 if exists('*synconcealed') && &conceallevel > 1
2957 for col in range(1, chars)
2958 let chars -= synconcealed(line('.'), col)[0]
2964 function! s:Blame(bang, line1, line2, count, mods, args) abort
2965 if exists('b:fugitive_blamed_bufnr')
2969 if empty(s:Relative('/'))
2970 call s:throw('file or blob required')
2972 if filter(copy(a:args),'v:val !~# "^\\%(--root\|--show-name\\|-\\=\\%([ltfnsew]\\|[MC]\\d*\\)\\+\\)$"') != []
2973 call s:throw('unsupported option')
2975 call map(a:args,'s:sub(v:val,"^\\ze[^-]","-")')
2976 let cmd = ['--no-pager', 'blame', '--show-number']
2978 let cmd += ['-L', a:line1 . ',' . a:line1]
2981 if s:DirCommitFile(@%)[1] =~# '\D\|..'
2982 let cmd += [s:DirCommitFile(@%)[1]]
2984 let cmd += ['--contents', '-']
2986 let cmd += ['--', expand('%:p')]
2987 let basecmd = escape(fugitive#Prepare(cmd), '!#%')
2989 let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
2991 if len(tree) && s:cpath(tree) !=# s:cpath(getcwd())
2993 execute cd s:fnameescape(tree)
2995 let error = tempname()
2996 let temp = error.'.fugitiveblame'
2998 silent! execute '%write !('.basecmd.' > '.temp.') >& '.error
3000 silent! execute '%write !'.basecmd.' > '.temp.' 2> '.error
3003 execute cd s:fnameescape(cwd)
3007 call s:throw(join(readfile(error),"\n"))
3010 let edit = substitute(a:mods, '^<mods>$', '', '') . get(['edit', 'split', 'pedit'], a:line2 - a:line1, ' split')
3011 return s:BlameCommit(edit, get(readfile(temp), 0, ''))
3013 for winnr in range(winnr('$'),1,-1)
3014 call setwinvar(winnr, '&scrollbind', 0)
3015 if exists('+cursorbind')
3016 call setwinvar(winnr, '&cursorbind', 0)
3018 if getbufvar(winbufnr(winnr), 'fugitive_blamed_bufnr')
3019 execute winbufnr(winnr).'bdelete'
3022 let bufnr = bufnr('')
3023 let restore = 'call setwinvar(bufwinnr('.bufnr.'),"&scrollbind",0)'
3024 if exists('+cursorbind')
3025 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&cursorbind",0)'
3028 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&wrap",1)'
3031 let restore .= '|call setwinvar(bufwinnr('.bufnr.'),"&foldenable",1)'
3033 setlocal scrollbind nowrap nofoldenable
3034 if exists('+cursorbind')
3037 let top = line('w0') + &scrolloff
3038 let current = line('.')
3039 let temp = s:Resolve(temp)
3040 let s:temp_files[s:cpath(temp)] = { 'dir': b:git_dir, 'filetype': 'fugitiveblame', 'args': cmd, 'bufnr': bufnr }
3041 exe 'keepalt leftabove vsplit '.temp
3042 let b:fugitive_blamed_bufnr = bufnr
3043 let b:fugitive_type = 'blame'
3044 let w:fugitive_leave = restore
3045 let b:fugitive_blame_arguments = join(a:args,' ')
3049 if exists('+cursorbind')
3052 setlocal nomodified nomodifiable nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth filetype=fugitiveblame buftype=nowrite
3053 if exists('+concealcursor')
3054 setlocal concealcursor=nc conceallevel=2
3056 if exists('+relativenumber')
3057 setlocal norelativenumber
3059 execute "vertical resize ".(s:linechars('.\{-\}\ze\s\+\d\+)')+1)
3060 nnoremap <buffer> <silent> <F1> :help fugitive-:Gblame<CR>
3061 nnoremap <buffer> <silent> g? :help fugitive-:Gblame<CR>
3062 nnoremap <buffer> <silent> q :exe substitute(bufwinnr(b:fugitive_blamed_bufnr).' wincmd w<Bar>'.bufnr('').'bdelete','^-1','','')<CR>
3063 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>
3064 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3065 nnoremap <buffer> <silent> - :<C-U>exe <SID>BlameJump('')<CR>
3066 nnoremap <buffer> <silent> P :<C-U>exe <SID>BlameJump('^'.v:count1)<CR>
3067 nnoremap <buffer> <silent> ~ :<C-U>exe <SID>BlameJump('~'.v:count1)<CR>
3068 nnoremap <buffer> <silent> i :<C-U>exe <SID>BlameCommit("exe 'norm q'<Bar>edit")<CR>
3069 nnoremap <buffer> <silent> o :<C-U>exe <SID>BlameCommit((&splitbelow ? "botright" : "topleft")." split")<CR>
3070 nnoremap <buffer> <silent> O :<C-U>exe <SID>BlameCommit("tabedit")<CR>
3071 nnoremap <buffer> <silent> p :<C-U>exe <SID>Edit((&splitbelow ? "botright" : "topleft").' pedit', 0, '', matchstr(getline('.'), '\x\+'), matchstr(getline('.'), '\x\+'))<CR>
3072 nnoremap <buffer> <silent> A :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze [0-9:/+-][0-9:/+ -]* \d\+)')+1+v:count)<CR>
3073 nnoremap <buffer> <silent> C :<C-u>exe "vertical resize ".(<SID>linechars('^\S\+')+1+v:count)<CR>
3074 nnoremap <buffer> <silent> D :<C-u>exe "vertical resize ".(<SID>linechars('.\{-\}\ze\d\ze\s\+\d\+)')+1-v:count)<CR>
3080 execute cd s:fnameescape(cwd)
3085 return 'echoerr v:errmsg'
3089 function! s:BlameCommit(cmd, ...) abort
3090 let line = a:0 ? a:1 : getline('.')
3091 if line =~# '^0\{4,40\} '
3092 return 'echoerr ' . string('Not Committed Yet')
3094 let cmd = s:Edit(a:cmd, 0, '', matchstr(line, '\x\+'), matchstr(line, '\x\+'))
3095 if cmd =~# '^echoerr'
3098 let lnum = matchstr(line, ' \zs\d\+\ze\s\+[([:digit:]]')
3099 let path = matchstr(line, '^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3101 let path = fugitive#Path(a:0 ? @% : bufname(b:fugitive_blamed_bufnr), '')
3104 if a:cmd ==# 'pedit'
3107 if search('^diff .* b/\M'.escape(path,'\').'$','W')
3109 let head = line('.')
3110 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
3111 let top = +matchstr(getline('.'),' +\zs\d\+')
3112 let len = +matchstr(getline('.'),' +\d\+,\zs\d\+')
3113 if lnum >= top && lnum <= top + len
3114 let offset = lnum - top
3122 while offset > 0 && line('.') < line('$')
3124 if getline('.') =~# '^[ +]'
3137 function! s:BlameJump(suffix) abort
3138 let commit = matchstr(getline('.'),'^\^\=\zs\x\+')
3139 if commit =~# '^0\+$'
3142 let lnum = matchstr(getline('.'),' \zs\d\+\ze\s\+[([:digit:]]')
3143 let path = matchstr(getline('.'),'^\^\=\x\+\s\+\zs.\{-\}\ze\s*\d\+ ')
3145 let path = fugitive#Path(bufname(b:fugitive_blamed_bufnr), '')
3147 let args = b:fugitive_blame_arguments
3148 let offset = line('.') - line('w0')
3149 let bufnr = bufnr('%')
3150 let winnr = bufwinnr(b:fugitive_blamed_bufnr)
3152 exe winnr.'wincmd w'
3154 execute 'Gedit' s:fnameescape(commit . a:suffix . ':' . path)
3159 if exists(':Gblame')
3160 execute 'Gblame '.args
3162 let delta = line('.') - line('w0') - offset
3164 execute 'normal! '.delta."\<C-E>"
3166 execute 'normal! '.(-delta)."\<C-Y>"
3173 let s:hash_colors = {}
3175 function! s:BlameSyntax() abort
3176 let b:current_syntax = 'fugitiveblame'
3177 let conceal = has('conceal') ? ' conceal' : ''
3178 let arg = exists('b:fugitive_blame_arguments') ? b:fugitive_blame_arguments : ''
3179 syn match FugitiveblameBoundary "^\^"
3180 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,fugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
3181 syn match FugitiveblameHash "\%(^\^\=\)\@<=\<\x\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3182 syn match FugitiveblameUncommitted "\%(^\^\=\)\@<=\<0\{7,40\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite
3183 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%( \d\+\)\@<=)" contained keepend oneline
3184 syn match FugitiveblameTime "[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%( \+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
3185 exec 'syn match FugitiveblameLineNumber " *\d\+)\@=" contained containedin=FugitiveblameAnnotation'.conceal
3186 exec 'syn match FugitiveblameOriginalFile " \%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite'.(arg =~# 'f' ? '' : conceal)
3187 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite'.(arg =~# 'n' ? '' : conceal)
3188 exec 'syn match FugitiveblameOriginalLineNumber " *\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite'.(arg =~# 'n' ? '' : conceal)
3189 syn match FugitiveblameShort " \d\+)" contained contains=FugitiveblameLineNumber
3190 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
3191 hi def link FugitiveblameBoundary Keyword
3192 hi def link FugitiveblameHash Identifier
3193 hi def link FugitiveblameUncommitted Ignore
3194 hi def link FugitiveblameTime PreProc
3195 hi def link FugitiveblameLineNumber Number
3196 hi def link FugitiveblameOriginalFile String
3197 hi def link FugitiveblameOriginalLineNumber Float
3198 hi def link FugitiveblameShort FugitiveblameDelimiter
3199 hi def link FugitiveblameDelimiter Delimiter
3200 hi def link FugitiveblameNotCommittedYet Comment
3202 for lnum in range(1, line('$'))
3203 let hash = matchstr(getline(lnum), '^\^\=\zs\x\{6\}')
3204 if hash ==# '' || hash ==# '000000' || has_key(seen, hash)
3208 if &t_Co > 16 && get(g:, 'CSApprox_loaded') && !empty(findfile('autoload/csapprox/per_component.vim', escape(&rtp, ' ')))
3209 \ && empty(get(s:hash_colors, hash))
3210 let [s, r, g, b; __] = map(matchlist(hash, '\(\x\x\)\(\x\x\)\(\x\x\)'), 'str2nr(v:val,16)')
3211 let color = csapprox#per_component#Approximate(r, g, b)
3212 if color == 16 && &background ==# 'dark'
3215 let s:hash_colors[hash] = ' ctermfg='.color
3217 let s:hash_colors[hash] = ''
3219 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=\)\@<='.hash.'\x\{1,34\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
3221 call s:RehighlightBlame()
3224 function! s:RehighlightBlame() abort
3225 for [hash, cterm] in items(s:hash_colors)
3226 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
3227 exe 'hi FugitiveblameHash'.hash.' guifg=#'.hash.get(s:hash_colors, hash, '')
3229 exe 'hi link FugitiveblameHash'.hash.' Identifier'
3236 call s:command("-bar -bang -range=0 -nargs=* -complete=customlist,fugitive#Complete Gbrowse :execute s:Browse(<bang>0,<line1>,<count>,<f-args>)")
3238 let s:redirects = {}
3240 function! s:Browse(bang,line1,count,...) abort
3242 let validremote = '\.\|\.\=/.*\|[[:alnum:]_-]\+\%(://.\{-\}\)\='
3244 let remote = matchstr(join(a:000, ' '),'@\zs\%('.validremote.'\)$')
3245 let rev = substitute(join(a:000, ' '),'@\%('.validremote.'\)$','','')
3251 let rev = s:DirRev(@%)[1]
3254 let expanded = s:Relative()
3256 let expanded = s:Expand(rev)
3258 let cdir = fugitive#CommonDir(b:git_dir)
3259 for dir in ['tags/', 'heads/', 'remotes/']
3260 if expanded !~# '^[./]' && filereadable(cdir . '/refs/' . dir . expanded)
3261 let expanded = '.git/refs/' . dir . expanded
3264 let full = s:Generate(expanded)
3266 if full =~? '^fugitive:'
3267 let [dir, commit, path] = s:DirCommitFile(full)
3268 if commit =~# '^:\=\d$'
3272 let type = s:TreeChomp('cat-file','-t',commit.s:sub(path,'^/',':'))
3273 let branch = matchstr(expanded, '^[^:]*')
3277 let path = path[1:-1]
3278 elseif empty(s:Tree())
3279 let path = '.git/' . full[strlen(b:git_dir)+1:-1]
3282 let path = full[strlen(s:Tree())+1:-1]
3283 if path =~# '^\.git/'
3285 elseif isdirectory(full)
3291 if type ==# 'tree' && !empty(path)
3292 let path = s:sub(path, '/\=$', '/')
3294 if path =~# '^\.git/.*HEAD$' && filereadable(b:git_dir . '/' . path[5:-1])
3295 let body = readfile(b:git_dir . '/' . path[5:-1])[0]
3296 if body =~# '^\x\{40\}$'
3300 elseif body =~# '^ref: refs/'
3301 let path = '.git/' . matchstr(body,'ref: \zs.*')
3306 if path =~# '^\.git/refs/remotes/.'
3308 let remote = matchstr(path, '^\.git/refs/remotes/\zs[^/]\+')
3309 let branch = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3311 let merge = matchstr(path, '^\.git/refs/remotes/[^/]\+/\zs.\+')
3312 let path = '.git/refs/heads/'.merge
3314 elseif path =~# '^\.git/refs/heads/.'
3315 let branch = path[16:-1]
3316 elseif !exists('branch')
3317 let branch = FugitiveHead()
3320 let r = fugitive#Config('branch.'.branch.'.remote')
3321 let m = fugitive#Config('branch.'.branch.'.merge')[11:-1]
3322 if r ==# '.' && !empty(m)
3323 let r2 = fugitive#Config('branch.'.m.'.remote')
3326 let m = fugitive#Config('branch.'.m.'.merge')[11:-1]
3332 if r ==# '.' || r ==# remote
3334 if path =~# '^\.git/refs/heads/.'
3335 let path = '.git/refs/heads/'.merge
3340 let line1 = a:count > 0 ? a:line1 : 0
3341 let line2 = a:count > 0 ? a:count : 0
3342 if empty(commit) && path !~# '^\.git/'
3343 if a:line1 && !a:count && !empty(merge)
3348 let remotehead = cdir . '/refs/remotes/' . remote . '/' . merge
3349 let commit = filereadable(remotehead) ? get(readfile(remotehead), 0, '') : ''
3350 if a:count && !a:0 && commit =~# '^\x\{40\}$'
3351 let blame_list = tempname()
3352 call writefile([commit, ''], blame_list, 'b')
3353 let blame_in = tempname()
3354 silent exe '%write' blame_in
3355 let blame = split(s:TreeChomp('blame', '--contents', blame_in, '-L', a:line1.','.a:count, '-S', blame_list, '-s', '--show-number', './' . path), "\n")
3357 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
3358 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
3359 let line1 = +matchstr(blame[0], blame_regex)
3360 let line2 = +matchstr(blame[-1], blame_regex)
3362 call s:throw("Can't browse to uncommitted change")
3369 let commit = readfile(b:git_dir . '/HEAD', '', 1)[0]
3372 while commit =~# '^ref: ' && i < 10
3373 let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0]
3381 let raw = fugitive#RemoteUrl(remote)
3386 if raw =~# '^https\=://' && s:executable('curl')
3387 if !has_key(s:redirects, raw)
3388 let s:redirects[raw] = matchstr(system('curl -I ' .
3389 \ s:shellesc(raw . '/info/refs?service=git-upload-pack')),
3390 \ 'Location: \zs\S\+\ze/info/refs?')
3392 if len(s:redirects[raw])
3393 let raw = s:redirects[raw]
3399 \ 'repo': fugitive#repo(),
3401 \ 'revision': 'No longer provided',
3408 for Handler in get(g:, 'fugitive_browse_handlers', [])
3409 let url = call(Handler, [copy(opts)])
3416 call s:throw("No Gbrowse handler found for '".raw."'")
3419 let url = s:gsub(url, '[ <>]', '\="%".printf("%02X",char2nr(submatch(0)))')
3424 return 'echomsg '.string(url)
3425 elseif exists(':Browse') == 2
3426 return 'echomsg '.string(url).'|Browse '.url
3428 if !exists('g:loaded_netrw')
3429 runtime! autoload/netrw.vim
3431 if exists('*netrw#BrowseX')
3432 return 'echomsg '.string(url).'|call netrw#BrowseX('.string(url).', 0)'
3434 return 'echomsg '.string(url).'|call netrw#NetrwBrowseX('.string(url).', 0)'
3438 return 'echoerr v:errmsg'
3442 " Section: Go to file
3444 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
3445 function! fugitive#MapCfile(...) abort
3446 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
3447 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
3448 if !exists('g:fugitive_no_maps')
3449 call s:map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
3450 call s:map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3451 call s:map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
3452 call s:map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
3453 call s:map('c', '<C-R><C-F>', '<Plug><cfile>', '<silent><unique>', 1)
3457 function! s:ContainingCommit() abort
3458 let commit = s:Owner(@%)
3459 return empty(commit) ? 'HEAD' : commit
3462 function! s:NavigateUp(count) abort
3463 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
3467 let rev = matchstr(rev, '.*\ze/.\+', '')
3468 elseif rev =~# '.:.'
3469 let rev = matchstr(rev, '^.[^:]*:')
3482 function! fugitive#MapJumps(...) abort
3483 if get(b:, 'fugitive_type', '') ==# 'blob'
3484 nnoremap <buffer> <silent> <CR> :<C-U>.Gblame<CR>
3486 nnoremap <buffer> <silent> <CR> :<C-U>exe <SID>GF("edit")<CR>
3489 let nowait = v:version >= 704 ? '<nowait>' : ''
3490 if get(b:, 'fugitive_type', '') ==# 'blob'
3491 nnoremap <buffer> <silent> o :<C-U>.,.+1Gblame<CR>
3492 nnoremap <buffer> <silent> S :<C-U>vertical .,.+1Gblame<CR>
3493 nnoremap <buffer> <silent> O :<C-U>tab .,.+1Gblame<CR>
3494 nnoremap <buffer> <silent> p :<C-U>.,.+2Gblame<CR>
3496 nnoremap <buffer> <silent> o :<C-U>exe <SID>GF("split")<CR>
3497 nnoremap <buffer> <silent> S :<C-U>exe <SID>GF("vsplit")<CR>
3498 nnoremap <buffer> <silent> O :<C-U>exe <SID>GF("tabedit")<CR>
3499 nnoremap <buffer> <silent> p :<C-U>exe <SID>GF("pedit")<CR>
3501 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>"
3502 nnoremap <buffer> <silent> P :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>
3503 nnoremap <buffer> <silent> ~ :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>
3504 nnoremap <buffer> <silent> C :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3505 nnoremap <buffer> <silent> cc :<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3506 nnoremap <buffer> <silent> co :<C-U>exe 'Gsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3507 nnoremap <buffer> <silent> cS :<C-U>exe 'Gvsplit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3508 nnoremap <buffer> <silent> cO :<C-U>exe 'Gtabedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3509 nnoremap <buffer> <silent> cp :<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>
3510 nmap <buffer> . <SID>: <Plug><cfile><Home>
3514 function! s:StatusCfile(...) abort
3516 let tree = FugitiveTreeForGitDir(b:git_dir)
3517 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
3518 if getline('.') =~# '^.\=\trenamed:.* -> '
3519 return lead . matchstr(getline('.'),' -> \zs.*')
3520 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
3521 return lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
3522 elseif getline('.') =~# '^.\=\t.'
3523 return lead . matchstr(getline('.'),'\t\zs.*')
3524 elseif getline('.') =~# ': needs merge$'
3525 return lead . matchstr(getline('.'),'.*\ze: needs merge$')
3526 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
3528 elseif getline('.') =~# '^\%(. \)\=On branch '
3529 return 'refs/heads/'.getline('.')[12:]
3530 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
3531 return matchstr(getline('.'),"'\\zs\\S\\+\\ze'")
3537 function! fugitive#StatusCfile() abort
3538 let file = s:Generate(s:StatusCfile())
3539 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
3542 function! s:cfile() abort
3544 let myhash = s:DirRev(@%)[1]
3547 let myhash = fugitive#RevParse(myhash)
3552 if empty(myhash) && getline(1) =~# '^\%(commit\|tag\) \w'
3553 let myhash = matchstr(getline(1),'^\w\+ \zs\S\+')
3556 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
3558 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
3559 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
3561 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40\}\t'
3562 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
3564 return [treebase . s:sub(getline('.'),'/$','')]
3571 if getline('.') =~# '^\d\{6\} \x\{40\} \d\t'
3572 let ref = matchstr(getline('.'),'\x\{40\}')
3573 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
3577 if getline('.') =~# '^ref: '
3578 let ref = strpart(getline('.'),5)
3580 elseif getline('.') =~# '^commit \x\{40\}\>'
3581 let ref = matchstr(getline('.'),'\x\{40\}')
3584 elseif getline('.') =~# '^parent \x\{40\}\>'
3585 let ref = matchstr(getline('.'),'\x\{40\}')
3586 let line = line('.')
3588 while getline(line) =~# '^parent '
3594 elseif getline('.') =~# '^tree \x\{40\}$'
3595 let ref = matchstr(getline('.'),'\x\{40\}')
3596 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
3597 let ref = myhash.':'
3601 elseif getline('.') =~# '^object \x\{40\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
3602 let ref = matchstr(getline('.'),'\x\{40\}')
3603 let type = matchstr(getline(line('.')+1),'type \zs.*')
3605 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
3606 let ref = s:DirRev(@%)[1]
3608 elseif getline('.') =~# '^\l\{3,8\} \x\{40\}\>'
3609 let ref = matchstr(getline('.'),'\x\{40\}')
3610 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
3612 elseif getline('.') =~# '^[+-]\{3\} [abciow12]\=/'
3613 let ref = getline('.')[4:]
3615 elseif getline('.') =~# '^[+-]' && search('^@@ -\d\+\%(,\d\+\)\= +\d\+','bnW')
3616 let type = getline('.')[0]
3617 let lnum = line('.') - 1
3619 while getline(lnum) !~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3620 if getline(lnum) =~# '^[ '.type.']'
3625 let offset += matchstr(getline(lnum), type.'\zs\d\+')
3626 let ref = getline(search('^'.type.'\{3\} [abciow12]/','bnW'))[4:-1]
3627 let dcmds = [offset, 'normal!zv']
3629 elseif getline('.') =~# '^rename from '
3630 let ref = 'a/'.getline('.')[12:]
3631 elseif getline('.') =~# '^rename to '
3632 let ref = 'b/'.getline('.')[10:]
3634 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
3635 let diff = getline(search('^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)', 'bcnW'))
3636 let offset = matchstr(getline('.'), '+\zs\d\+')
3638 let dref = matchstr(diff, '\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3639 let ref = matchstr(diff, '\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3640 let dcmd = 'Gdiff! +'.offset
3642 elseif getline('.') =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3643 let dref = matchstr(getline('.'),'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3644 let ref = matchstr(getline('.'),'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3647 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# '^diff --git \%([abciow12]/.*\|/dev/null\) \%([abciow12]/.*\|/dev/null\)'
3648 let line = getline(line('.')-1)
3649 let dref = matchstr(line,'\Cdiff --git \zs\%([abciow12]/.*\|/dev/null\)\ze \%([abciow12]/.*\|/dev/null\)')
3650 let ref = matchstr(line,'\Cdiff --git \%([abciow12]/.*\|/dev/null\) \zs\%([abciow12]/.*\|/dev/null\)')
3653 elseif line('$') == 1 && getline('.') =~ '^\x\{40\}$'
3654 let ref = getline('.')
3656 elseif expand('<cword>') =~# '^\x\{7,40\}\>'
3657 return [expand('<cword>')]
3672 let prefixes.a = myhash.'^:'
3673 let prefixes.b = myhash.':'
3675 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3677 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "HEAD:")', '')
3680 if ref ==# '/dev/null'
3682 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
3686 return [ref, dcmd . ' ' . s:fnameescape(dref)] + dcmds
3688 return [ref] + dcmds
3696 function! s:GF(mode) abort
3698 let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
3700 return 'echoerr v:errmsg'
3703 return 'G' . a:mode .
3704 \ ' +' . escape(join(results[1:-1], '|'), '| ') . ' ' .
3705 \ s:fnameescape(results[0])
3707 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
3713 function! fugitive#Cfile() abort
3715 let results = s:cfile()
3717 let cfile = expand('<cfile>')
3718 if &includeexpr =~# '\<v:fname\>'
3719 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
3722 elseif len(results) > 1
3723 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
3725 return pre . s:fnameescape(s:Generate(results[0]))
3728 " Section: Statusline
3730 function! fugitive#Statusline(...) abort
3731 if !exists('b:git_dir')
3735 let commit = s:DirCommitFile(@%)[1]
3737 let status .= ':' . commit[0:7]
3739 let status .= '('.FugitiveHead(7).')'
3740 return '[Git'.status.']'
3743 function! fugitive#statusline(...) abort
3744 return fugitive#Statusline()
3747 function! fugitive#head(...) abort
3748 if !exists('b:git_dir')
3752 return fugitive#Head(a:0 ? a:1 : 0)
3757 function! fugitive#Foldtext() abort
3758 if &foldmethod !=# 'syntax'
3762 let line_foldstart = getline(v:foldstart)
3763 if line_foldstart =~# '^diff '
3764 let [add, remove] = [-1, -1]
3766 for lnum in range(v:foldstart, v:foldend)
3767 let line = getline(lnum)
3768 if filename ==# '' && line =~# '^[+-]\{3\} [abciow12]/'
3769 let filename = line[6:-1]
3773 elseif line =~# '^-'
3775 elseif line =~# '^Binary '
3780 let filename = matchstr(line_foldstart, '^diff .\{-\} [abciow12]/\zs.*\ze [abciow12]/')
3783 let filename = line_foldstart[5:-1]
3786 return 'Binary: '.filename
3788 return (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
3790 elseif line_foldstart =~# '^# .*:$'
3791 let lines = getline(v:foldstart, v:foldend)
3792 call filter(lines, 'v:val =~# "^#\t"')
3793 cal map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
3794 cal map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
3795 return line_foldstart.' '.join(lines, ', ')
3800 function! fugitive#foldtext() abort
3801 return fugitive#Foldtext()
3804 augroup fugitive_folding
3806 autocmd User Fugitive
3807 \ if &filetype =~# '^git\%(commit\)\=$' && &foldtext ==# 'foldtext()' |
3808 \ set foldtext=fugitive#Foldtext() |
3812 " Section: Initialization
3814 function! fugitive#Init() abort
3815 if exists('#User#FugitiveBoot')
3817 let [save_mls, &modelines] = [&mls, 0]
3818 doautocmd User FugitiveBoot
3823 if !exists('g:fugitive_no_maps')
3824 call s:map('c', '<C-R><C-G>', '<SID>fnameescape(fugitive#Object(@%))', '<expr>')
3825 call s:map('n', 'y<C-G>', ':<C-U>call setreg(v:register, fugitive#Object(@%))<CR>', '<silent>')
3827 if expand('%:p') =~# ':[\/][\/]'
3828 let &l:path = s:sub(&path, '^\.%(,|$)', '')
3830 if stridx(&tags, escape(b:git_dir, ', ')) == -1
3831 if filereadable(b:git_dir.'/tags')
3832 let &l:tags = escape(b:git_dir.'/tags', ', ').','.&tags
3834 if &filetype !=# '' && filereadable(b:git_dir.'/'.&filetype.'.tags')
3835 let &l:tags = escape(b:git_dir.'/'.&filetype.'.tags', ', ').','.&tags
3839 let [save_mls, &modelines] = [&mls, 0]
3840 call s:define_commands()
3841 doautocmd User Fugitive
3847 function! fugitive#is_git_dir(path) abort
3848 return FugitiveIsGitDir(a:path)
3851 function! fugitive#extract_git_dir(path) abort
3852 return FugitiveExtractGitDir(a:path)
3855 function! fugitive#detect(path) abort
3856 return FugitiveDetect(a:path)