1 " Location: autoload/fugitive.vim
2 " Maintainer: Tim Pope <http://tpo.pe/>
4 " The functions contained within this file are for internal use only. For the
5 " official API, see the commented functions in plugin/fugitive.vim.
7 if exists('g:autoloaded_fugitive')
10 let g:autoloaded_fugitive = 1
14 function! s:function(name) abort
15 return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_'),''))
18 function! s:sub(str,pat,rep) abort
19 return substitute(a:str,'\v\C'.a:pat,a:rep,'')
22 function! s:gsub(str,pat,rep) abort
23 return substitute(a:str,'\v\C'.a:pat,a:rep,'g')
26 function! s:Uniq(list) abort
30 let str = string(a:list[i])
32 call remove(a:list, i)
41 function! s:JoinChomp(list) abort
43 return join(a:list[0:-2], "\n")
45 return join(a:list, "\n")
49 function! s:winshell() abort
50 return has('win32') && &shellcmdflag !~# '^-'
53 function! s:WinShellEsc(arg) abort
54 if type(a:arg) == type([])
55 return join(map(copy(a:arg), 's:WinShellEsc(v:val)'))
56 elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
59 return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
63 function! s:shellesc(arg) abort
64 if type(a:arg) == type([])
65 return join(map(copy(a:arg), 's:shellesc(v:val)'))
66 elseif a:arg =~# '^[A-Za-z0-9_/:.-]\+$'
69 return '"' . s:gsub(s:gsub(a:arg, '"', '""'), '\%', '"%"') . '"'
71 return shellescape(a:arg)
75 function! s:fnameescape(file) abort
76 if type(a:file) == type([])
77 return join(map(copy(a:file), 's:fnameescape(v:val)'))
79 return fnameescape(a:file)
83 function! fugitive#UrlDecode(str) abort
84 return substitute(a:str, '%\(\x\x\)', '\=iconv(nr2char("0x".submatch(1)), "utf-8", "latin1")', 'g')
87 function! s:UrlEncode(str) abort
88 return substitute(a:str, '[%#?&;+=\<> [:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
91 function! s:PathUrlEncode(str) abort
92 return substitute(a:str, '[%#?[:cntrl:]]', '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
95 function! s:PathJoin(prefix, str) abort
97 return a:prefix . s:PathUrlEncode(a:str)
99 return a:prefix . a:str
103 function! s:throw(string) abort
104 throw 'fugitive: '.a:string
107 function! s:VersionCheck() abort
109 return 'return ' . string('echoerr "fugitive: Vim 7.4 or newer required"')
110 elseif empty(fugitive#GitVersion())
111 let exe = get(s:GitCmd(), 0, '')
112 if len(exe) && !executable(exe)
113 return 'return ' . string('echoerr "fugitive: cannot find ' . string(exe) . ' in PATH"')
115 return 'return ' . string('echoerr "fugitive: cannot execute Git"')
116 elseif !fugitive#GitVersion(1, 8, 5)
117 return 'return ' . string('echoerr "fugitive: Git 1.8.5 or newer required"')
119 if exists('b:git_dir') && empty(b:git_dir)
126 let s:worktree_error = "core.worktree is required when using an external Git dir"
127 function! s:DirCheck(...) abort
128 let dir = call('FugitiveGitDir', a:000)
129 if !empty(dir) && FugitiveWorkTree(dir, 1) is# 0
130 return 'return ' . string('echoerr "fugitive: ' . s:worktree_error . '"')
133 elseif empty(bufname(''))
134 return 'return ' . string('echoerr "fugitive: working directory does not belong to a Git repository"')
136 return 'return ' . string('echoerr "fugitive: file does not belong to a Git repository"')
140 function! s:Mods(mods, ...) abort
141 let mods = substitute(a:mods, '\C<mods>', '', '')
142 let mods = mods =~# '\S$' ? mods . ' ' : mods
143 if a:0 && mods !~# '\<\d*\%(aboveleft\|belowright\|leftabove\|rightbelow\|topleft\|botright\|tab\)\>'
145 if default ==# 'SpanOrigin'
146 if s:OriginBufnr() > 0 && (mods =~# '\<vertical\>' ? &winfixheight : &winfixwidth)
152 if default ==# 'Edge'
153 if mods =~# '\<vertical\>' ? &splitright : &splitbelow
154 let mods = 'botright ' . mods
156 let mods = 'topleft ' . mods
159 let mods = default . ' ' . mods
162 return substitute(mods, '\s\+', ' ', 'g')
165 if exists('+shellslash')
167 let s:dir_commit_file = '\c^fugitive://\%(/[^/]\@=\)\=\([^?#]\{-1,\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
169 function! s:Slash(path) abort
170 return tr(a:path, '\', '/')
173 function! s:VimSlash(path) abort
174 return tr(a:path, '\/', &shellslash ? '//' : '\\')
179 let s:dir_commit_file = '\c^fugitive://\([^?#]\{-\}\)//\%(\(\x\{40,\}\|[0-3]\)\(/[^?#]*\)\=\)\=$'
181 function! s:Slash(path) abort
185 function! s:VimSlash(path) abort
191 function! s:AbsoluteVimPath(...) abort
192 if a:0 && type(a:1) == type('')
195 let path = bufname(a:0 && a:1 > 0 ? a:1 : '')
196 if getbufvar(a:0 && a:1 > 0 ? a:1 : '', '&buftype') !~# '^\%(nowrite\|acwrite\)\=$'
200 if s:Slash(path) =~# '^/\|^\a\+:'
203 return getcwd() . matchstr(getcwd(), '[\\/]') . path
207 function! s:Resolve(path) abort
208 let path = resolve(a:path)
210 let path = s:VimSlash(fnamemodify(fnamemodify(path, ':h'), ':p') . fnamemodify(path, ':t'))
215 function! s:FileIgnoreCase(for_completion) abort
216 return (exists('+fileignorecase') && &fileignorecase)
217 \ || (a:for_completion && exists('+wildignorecase') && &wildignorecase)
220 function! s:cpath(path, ...) abort
221 if s:FileIgnoreCase(0)
222 let path = s:VimSlash(tolower(a:path))
224 let path = s:VimSlash(a:path)
226 return a:0 ? path ==# s:cpath(a:1) : path
229 let s:quote_chars = {
230 \ "\007": 'a', "\010": 'b', "\011": 't', "\012": 'n', "\013": 'v', "\014": 'f', "\015": 'r',
231 \ '"': '"', '\': '\'}
233 let s:unquote_chars = {
234 \ 'a': "\007", 'b': "\010", 't': "\011", 'n': "\012", 'v': "\013", 'f': "\014", 'r': "\015",
235 \ '"': '"', '\': '\'}
237 function! s:Quote(string) abort
238 let string = substitute(a:string, "[\001-\037\"\\\177]", '\="\\" . get(s:quote_chars, submatch(0), printf("%03o", char2nr(submatch(0))))', 'g')
239 if string !=# a:string
240 return '"' . string . '"'
246 function! fugitive#Unquote(string) abort
247 let string = substitute(a:string, "\t*$", '', '')
248 if string =~# '^".*"$'
249 return substitute(string[1:-2], '\\\(\o\o\o\|.\)', '\=get(s:unquote_chars, submatch(1), iconv(nr2char("0" . submatch(1)), "utf-8", "latin1"))', 'g')
255 let s:executables = {}
257 function! s:executable(binary) abort
258 if !has_key(s:executables, a:binary)
259 let s:executables[a:binary] = executable(a:binary)
261 return s:executables[a:binary]
264 if !exists('s:temp_scripts')
265 let s:temp_scripts = {}
267 function! s:TempScript(...) abort
268 let body = join(a:000, "\n")
269 if !has_key(s:temp_scripts, body)
270 let s:temp_scripts[body] = tempname() . '.sh'
272 let temp = s:temp_scripts[body]
273 if !filereadable(temp)
274 call writefile(['#!/bin/sh'] + a:000, temp)
277 let temp = '"' . temp . '"'
279 return FugitiveGitPath(temp)
282 function! s:DoAutocmd(...) abort
283 return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
286 function! s:Map(mode, lhs, rhs, ...) abort
288 let flags = a:0 && type(a:1) == type('') ? a:1 : ''
289 let defer = flags =~# '<unique>'
290 let flags = substitute(flags, '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
291 for mode in split(a:mode, '\zs')
293 call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
298 let keys = get(g:, mode.'remap', {})
299 if type(keys) == type([])
303 if has_key(keys, head)
304 let head = keys[head]
305 let skip = empty(head)
308 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
309 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
311 if !skip && (!defer || empty(mapcheck(head.tail, mode)))
312 call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
314 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
315 \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
323 function! fugitive#Autowrite() abort
324 if &autowrite || &autowriteall
332 if exists('reconfirm')
340 function! fugitive#Wait(job_or_jobs, ...) abort
341 let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
342 let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
343 call filter(jobs, 'type(v:val) !=# type("")')
344 let timeout_ms = a:0 ? a:1 : -1
345 if exists('*jobwait')
346 call map(copy(jobs), 'chanclose(v:val, "stdin")')
347 call jobwait(jobs, timeout_ms)
348 let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
349 call filter(jobs, 'type(v:val) !=# type("")')
355 if ch_status(job) ==# 'open'
356 call ch_close_in(job)
361 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
373 function! s:JobVimExit(dict, callback, temp, job, status) abort
374 let a:dict.exit_status = a:status
375 let a:dict.stderr = readfile(a:temp . '.err', 'b')
376 call delete(a:temp . '.err')
377 let a:dict.stdout = readfile(a:temp . '.out', 'b')
378 call delete(a:temp . '.out')
379 call delete(a:temp . '.in')
380 call remove(a:dict, 'job')
381 call call(a:callback[0], [a:dict] + a:callback[1:-1])
384 function! s:JobNvimExit(dict, callback, job, data, type) dict abort
385 let a:dict.stdout = self.stdout
386 let a:dict.stderr = self.stderr
387 let a:dict.exit_status = a:data
388 call remove(a:dict, 'job')
389 call call(a:callback[0], [a:dict] + a:callback[1:-1])
392 function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
393 let dict = a:0 ? a:1 : {}
394 let cb = len(a:callback) ? a:callback : [function('len')]
395 if exists('*jobstart')
396 call extend(a:jopts, {
397 \ 'stdout_buffered': v:true,
398 \ 'stderr_buffered': v:true,
399 \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
401 let dict.job = jobstart(a:argv, a:jopts)
403 call chansend(dict.job, a:stdin)
405 call chanclose(dict.job, 'stdin')
406 catch /^Vim\%((\a\+)\)\=:E475:/
407 let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
409 elseif exists('*ch_close_in')
410 let temp = tempname()
411 call extend(a:jopts, {
413 \ 'out_name': temp . '.out',
415 \ 'err_name': temp . '.err',
416 \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
418 let a:jopts.in_io = 'null'
419 elseif !empty(a:stdin)
420 let a:jopts.in_io = 'file'
421 let a:jopts.in_name = temp . '.in'
422 call writefile(a:stdin, a:jopts.in_name, 'b')
424 let dict.job = job_start(a:argv, a:jopts)
425 if job_status(dict.job) ==# 'fail'
426 let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
429 elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
430 throw 'fugitive: Vim 8 or higher required to use ' . &shell
432 let cmd = s:shellesc(a:argv)
433 let outfile = tempname()
436 call writefile(a:stdin, outfile . '.in', 'b')
437 let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
439 let cmd = ' (' . cmd . ' >' . outfile . ') '
441 let dict.stderr = split(system(cmd), "\n", 1)
442 let dict.exit_status = v:shell_error
443 let dict.stdout = readfile(outfile, 'b')
444 call call(cb[0], [dict] + cb[1:-1])
447 call delete(outfile . '.in')
451 call fugitive#Wait(dict)
456 function! s:add_methods(namespace, method_names) abort
457 for name in a:method_names
458 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
464 let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
466 function! s:GitCmd() abort
467 if !exists('g:fugitive_git_executable')
469 elseif type(g:fugitive_git_executable) == type([])
470 return g:fugitive_git_executable
472 let dquote = '"\%([^"]\|""\|\\"\)*"\|'
473 let string = g:fugitive_git_executable
475 if string =~# '^\w\+='
476 call add(list, '/usr/bin/env')
478 while string =~# '\S'
479 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
480 let string = strpart(string, len(arg))
481 let arg = substitute(arg, '^\s\+', '', '')
482 let arg = substitute(arg,
483 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\)\|' . s:expand,
484 \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
491 function! s:GitShellCmd() abort
492 if !exists('g:fugitive_git_executable')
494 elseif type(g:fugitive_git_executable) == type([])
495 return s:shellesc(g:fugitive_git_executable)
497 return g:fugitive_git_executable
501 function! s:UserCommandCwd(dir) abort
502 let tree = s:Tree(a:dir)
503 return len(tree) ? s:VimSlash(tree) : getcwd()
506 function! s:UserCommandList(...) abort
507 if !fugitive#GitVersion(1, 8, 5)
508 throw 'fugitive: Git 1.8.5 or higher required'
510 if !exists('g:fugitive_git_command')
512 elseif type(g:fugitive_git_command) == type([])
513 let git = g:fugitive_git_command
515 let git = split(g:fugitive_git_command, '\s\+')
518 if a:0 && type(a:1) == type({})
519 let git = copy(get(a:1, 'git', git))
520 let flags = get(a:1, 'flags', flags)
521 let dir = a:1.git_dir
523 let dir = s:GitDir(a:1)
528 let tree = s:Tree(dir)
530 call add(git, '--git-dir=' . FugitiveGitPath(dir))
532 if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
533 call add(git, '--git-dir=' . FugitiveGitPath(dir))
535 if !s:cpath(tree, getcwd())
536 call extend(git, ['-C', FugitiveGitPath(tree)])
543 let s:git_versions = {}
544 function! fugitive#GitVersion(...) abort
545 let git = s:GitShellCmd()
546 if !has_key(s:git_versions, git)
547 let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
550 return s:git_versions[git]
552 let components = split(s:git_versions[git], '\D\+')
556 for i in range(len(a:000))
557 if a:000[i] > +get(components, i)
559 elseif a:000[i] < +get(components, i)
563 return a:000[i] ==# get(components, i)
566 function! s:Dir(...) abort
567 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
570 function! s:GitDir(...) abort
571 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
574 function! s:InitializeBuffer(repo) abort
575 let b:git_dir = s:GitDir(a:repo)
578 function! s:SameRepo(one, two) abort
579 let one = s:GitDir(a:one)
580 return !empty(one) && one ==# s:GitDir(a:two)
583 if exists('+shellslash')
584 function! s:DirUrlPrefix(dir) abort
585 let gd = s:GitDir(a:dir)
586 return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
589 function! s:DirUrlPrefix(dir) abort
590 return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
594 function! s:Tree(...) abort
595 return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
598 function! s:HasOpt(args, ...) abort
599 let args = a:args[0 : index(a:args, '--')]
600 let opts = copy(a:000)
601 if type(opts[0]) == type([])
602 if empty(args) || index(opts[0], args[0]) == -1
608 if index(args, opt) != -1
614 function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
616 call insert(a:cmd, '--literal-pathspecs')
618 let split = index(a:cmd, '--')
619 for i in range(split < 0 ? len(a:cmd) : split)
620 if type(a:cmd[i]) == type(0)
622 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
624 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
631 for i in range(split + 1, len(a:cmd) - 1)
632 if type(a:cmd[i]) == type(0)
634 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
636 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
639 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
645 let s:git_index_file_env = {}
646 function! s:GitIndexFileEnv() abort
647 if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
648 let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
650 return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
653 function! s:PrepareEnv(env, dir) abort
654 if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
655 let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
656 let our_dir = fugitive#Find('.git/', a:dir)
657 if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
658 let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
661 if len($GIT_WORK_TREE)
662 let a:env['GIT_WORK_TREE'] = '.'
666 let s:prepare_env = {
667 \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
668 \ 'core.editor': 'GIT_EDITOR',
669 \ 'core.askpass': 'GIT_ASKPASS',
671 function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
672 if !fugitive#GitVersion(1, 8, 5)
673 throw 'fugitive: Git 1.8.5 or higher required'
676 if a:0 == 1 && type(a:1) == type({}) && (has_key(a:1, 'fugitive_dir') || has_key(a:1, 'git_dir')) && has_key(a:1, 'flags') && has_key(a:1, 'args')
677 let cmd = a:1.flags + a:1.args
679 if has_key(a:1, 'git')
682 let env = get(a:1, 'env', {})
687 if type(arg) ==# type([])
688 call extend(list_args, arg)
693 call extend(cmd, list_args)
697 let explicit_pathspec_option = 0
698 let literal_pathspecs = 1
702 if type(cmd[i]) == type({})
703 if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
704 let dir = s:Dir(cmd[i])
706 if has_key(cmd[i], 'git')
709 if has_key(cmd[i], 'env')
710 call extend(env, cmd[i].env)
713 elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
714 let dir = s:Dir(remove(cmd, i))
715 elseif cmd[i] =~# '^--git-dir='
716 let dir = s:Dir(remove(cmd, i)[10:-1])
717 elseif type(cmd[i]) ==# type(0)
718 let dir = s:Dir(remove(cmd, i))
719 elseif cmd[i] ==# '-c' && len(cmd) > i + 1
720 let key = matchstr(cmd[i+1], '^[^=]*')
721 if has_key(s:prepare_env, tolower(key))
722 let var = s:prepare_env[tolower(key)]
723 let val = matchstr(cmd[i+1], '=\zs.*')
724 let autoenv[var] = val
727 elseif cmd[i] =~# '^--.*pathspecs$'
728 let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
729 let explicit_pathspec_option = 1
731 elseif cmd[i] !~# '^-'
732 let arg_count = len(cmd) - i
741 call extend(autoenv, env)
742 call s:PrepareEnv(autoenv, dir)
743 if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
744 let autoenv.GPG_TTY = ''
746 call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
747 return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
750 function! s:BuildEnvPrefix(env) abort
752 let env = items(a:env)
755 elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
756 return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
758 return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
760 return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
764 function! s:JobOpts(cmd, env) abort
767 elseif has('patch-8.2.0239') ||
768 \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
769 \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
770 return [a:cmd, {'env': a:env}]
772 let envlist = map(items(a:env), 'join(v:val, "=")')
774 return [['/usr/bin/env'] + envlist + a:cmd, {}]
776 let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
777 if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
778 return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
780 return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
785 function! s:PrepareJob(opts) abort
786 let dict = {'argv': a:opts.argv}
787 if has_key(a:opts, 'env')
788 let dict.env = a:opts.env
790 let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
791 if has_key(a:opts, 'cwd')
792 if has('patch-8.0.0902')
793 let jopts.cwd = a:opts.cwd
794 let dict.cwd = a:opts.cwd
796 throw 'fugitive: cwd unsupported'
799 return [argv, jopts, dict]
802 function! fugitive#PrepareJob(...) abort
803 if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
804 return s:PrepareJob(a:1)
806 let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
807 let dir = s:GitDir(repo)
808 let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
810 let dict.env = user_env
812 let cmd = flags + args
813 let tree = s:Tree(repo)
814 if empty(tree) || index(cmd, '--') == len(cmd) - 1
815 let dict.cwd = getcwd()
816 call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
818 let dict.cwd = s:VimSlash(tree)
819 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
820 if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
821 call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
824 call extend(cmd, git, 'keep')
825 return s:JobOpts(cmd, exec_env) + [dict]
828 function! fugitive#Execute(...) abort
832 while len(cb) && type(cb[0]) !=# type(function('tr'))
833 if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
834 if type(cb[0].stdin) == type([])
835 call extend(stdin, cb[0].stdin)
836 elseif type(cb[0].stdin) == type('')
837 call extend(stdin, readfile(cb[0].stdin, 'b'))
839 if len(keys(cb[0])) == 1
844 call add(cmd, remove(cb, 0))
846 let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
847 return s:JobExecute(argv, jopts, stdin, cb, dict)
850 function! s:BuildShell(dir, env, git, args) abort
851 let cmd = copy(a:args)
852 let tree = s:Tree(a:dir)
853 let pre = s:BuildEnvPrefix(a:env)
854 if empty(tree) || index(cmd, '--') == len(cmd) - 1
855 call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
857 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
858 if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
859 call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
862 return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
865 function! s:JobNvimCallback(lines, job, data, type) abort
866 let a:lines[-1] .= remove(a:data, 0)
867 call extend(a:lines, a:data)
870 function! s:SystemList(cmd) abort
872 if exists('*jobstart')
875 \ 'on_stdout': function('s:JobNvimCallback', [lines]),
876 \ 'on_stderr': function('s:JobNvimCallback', [lines]),
877 \ 'on_exit': { j, code, _ -> add(exit, code) }}
878 let job = jobstart(a:cmd, jopts)
879 call chanclose(job, 'stdin')
882 call remove(lines, -1)
884 return [lines, exit[0]]
885 elseif exists('*ch_close_in')
888 \ 'out_cb': { j, str -> add(lines, str) },
889 \ 'err_cb': { j, str -> add(lines, str) },
890 \ 'exit_cb': { j, code -> add(exit, code) }}
891 let job = job_start(a:cmd, jopts)
892 call ch_close_in(job)
893 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
896 return [lines, exit[0]]
898 let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
899 let lines = split(output, "\n", 1)
901 call remove(lines, -1)
903 return [lines, v:shell_error]
907 function! fugitive#ShellCommand(...) abort
908 let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
909 return s:BuildShell(s:GitDir(repo), env, git, flags + args)
912 function! s:SystemError(cmd, ...) abort
913 let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
915 if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
916 let shellredir = &shellredir
920 set shellredir=>%s\ 2>&1
923 if exists('+guioptions') && &guioptions =~# '!'
924 let guioptions = &guioptions
927 let out = call('system', [cmd] + a:000)
928 return [out, v:shell_error]
929 catch /^Vim\%((\a\+)\)\=:E484:/
930 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
931 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
932 call map(opts, 'v:val."=".eval("&".v:val)')
933 call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
935 if exists('shellredir')
936 let &shellredir = shellredir
938 if exists('guioptions')
939 let &guioptions = guioptions
944 function! s:ChompStderr(...) abort
945 let r = call('fugitive#Execute', a:000)
946 return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
949 function! s:ChompDefault(default, ...) abort
950 let r = call('fugitive#Execute', a:000)
951 return r.exit_status ? a:default : s:JoinChomp(r.stdout)
954 function! s:LinesError(...) abort
955 let r = call('fugitive#Execute', a:000)
956 if empty(r.stdout[-1])
957 call remove(r.stdout, -1)
959 return [r.exit_status ? [] : r.stdout, r.exit_status]
962 function! s:TreeChomp(...) abort
963 let r = call('fugitive#Execute', a:000)
965 return s:JoinChomp(r.stdout)
967 throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
970 function! s:StdoutToFile(out, cmd, ...) abort
971 let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
973 if exists('*jobstart')
975 \ 'stdout_buffered': v:true,
976 \ 'stderr_buffered': v:true,
977 \ 'on_exit': { j, code, _ -> add(exit, code) }})
978 let job = jobstart(argv, jopts)
980 call chansend(job, a:1)
982 call chanclose(job, 'stdin')
985 call writefile(jopts.stdout, a:out, 'b')
987 return [join(jopts.stderr, "\n"), exit[0]]
988 elseif exists('*ch_close_in')
992 \ 'out_io': len(a:out) ? 'file' : 'null',
996 \ 'exit_cb': { j, code -> add(exit, code) }})
997 let job = job_start(argv, jopts)
999 call ch_sendraw(job, a:1)
1001 call ch_close_in(job)
1002 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
1005 return [join(readfile(err, 'b'), "\n"), exit[0]]
1009 elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
1010 throw 'fugitive: Vim 8 or higher required to use ' . &shell
1012 let cmd = fugitive#ShellCommand(a:cmd)
1013 return call('s:SystemError', [' (' . cmd . ' >' . (len(a:out) ? a:out : '/dev/null') . ') '] + a:000)
1017 let s:head_cache = {}
1019 function! fugitive#Head(...) abort
1020 let dir = a:0 > 1 ? a:2 : s:Dir()
1024 let file = FugitiveActualDir(dir) . '/HEAD'
1025 let ftime = getftime(file)
1028 elseif ftime != get(s:head_cache, file, [-1])[0]
1029 let s:head_cache[file] = [ftime, readfile(file)[0]]
1031 let head = s:head_cache[file][1]
1032 let len = a:0 ? a:1 : 0
1033 if head =~# '^ref: '
1035 return strpart(head, 5)
1037 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
1039 elseif head =~# '^\x\{40,\}$'
1040 return len < 0 ? head : strpart(head, 0, len)
1046 function! fugitive#RevParse(rev, ...) abort
1047 let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
1048 if hash =~# '^\x\{40,\}$'
1051 throw 'fugitive: failed to parse revision ' . a:rev
1054 " Section: Git config
1056 function! s:ConfigTimestamps(dir, dict) abort
1057 let files = ['/etc/gitconfig', '~/.gitconfig',
1058 \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
1060 call add(files, fugitive#Find('.git/config', a:dir))
1062 call extend(files, get(a:dict, 'include.path', []))
1063 return join(map(files, 'getftime(expand(v:val))'), ',')
1066 function! s:ConfigCallback(r, into) abort
1067 let dict = a:into[1]
1068 if has_key(dict, 'job')
1069 call remove(dict, 'job')
1071 let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
1073 let key = matchstr(line, "^[^\n]*")
1074 if !has_key(dict, key)
1077 if len(key) ==# len(line)
1078 call add(dict[key], 1)
1080 call add(dict[key], strpart(line, len(key) + 1))
1083 let callbacks = remove(dict, 'callbacks')
1085 let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
1086 for callback in callbacks
1087 call call(callback[0], [dict] + callback[1:-1])
1091 let s:config_prototype = {}
1094 function! fugitive#ExpireConfig(...) abort
1095 if !a:0 || a:1 is# 0
1098 let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
1099 if len(key) && has_key(s:config, key)
1100 call remove(s:config, key)
1105 function! fugitive#Config(...) abort
1107 let default = get(a:, 3, '')
1108 if a:0 && type(a:1) == type(function('tr'))
1110 let callback = a:000
1111 elseif a:0 > 1 && type(a:2) == type(function('tr'))
1112 if type(a:1) == type({}) && has_key(a:1, 'GetAll')
1113 if has_key(a:1, 'callbacks')
1114 call add(a:1.callbacks, a:000[1:-1])
1116 call call(a:2, [a:1] + a:000[2:-1])
1120 let dir = s:Dir(a:1)
1121 let callback = a:000[1:-1]
1123 elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
1124 return get(fugitive#ConfigGetAll(a:1, a:2), -1, default)
1126 let dir = s:Dir(a:2)
1128 elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
1130 elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
1134 let dir = s:Dir(a:1)
1138 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1139 let git_dir = s:GitDir(dir)
1140 let dir_key = len(git_dir) ? git_dir : '_'
1141 let [ts, dict] = get(s:config, dir_key, ['new', {}])
1142 if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
1143 let dict = copy(s:config_prototype)
1144 let dict.git_dir = git_dir
1145 let into = ['running', dict]
1146 let dict.callbacks = []
1147 let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
1148 if has_key(exec, 'job')
1149 let dict.job = exec.job
1151 let s:config[dir_key] = into
1153 if !exists('l:callback')
1154 call fugitive#Wait(dict)
1155 elseif has_key(dict, 'callbacks')
1156 call add(dict.callbacks, callback)
1158 call call(callback[0], [dict] + callback[1:-1])
1160 return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
1163 function! fugitive#ConfigGetAll(name, ...) abort
1164 if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
1165 let config = fugitive#Config(a:name)
1168 let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1171 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1172 call fugitive#Wait(config)
1173 return name =~# '\.' ? copy(get(config, name, [])) : []
1176 function! fugitive#ConfigGetRegexp(pattern, ...) abort
1177 if type(a:pattern) !=# type('')
1178 let config = fugitive#Config(a:name)
1179 let pattern = a:0 ? a:1 : '.*'
1181 let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1182 let pattern = a:pattern
1184 call fugitive#Wait(config)
1185 let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
1186 if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
1189 let transformed = {}
1190 for [k, v] in items(filtered)
1191 let k = matchstr(k, pattern)
1193 let transformed[k] = v
1199 function! s:config_GetAll(name) dict abort
1200 let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1201 call fugitive#Wait(self)
1202 return name =~# '\.' ? copy(get(self, name, [])) : []
1205 function! s:config_Get(name, ...) dict abort
1206 return get(self.GetAll(a:name), -1, a:0 ? a:1 : '')
1209 function! s:config_GetRegexp(pattern) dict abort
1210 return fugitive#ConfigGetRegexp(self, a:pattern)
1213 call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
1215 function! s:RemoteDefault(dir) abort
1216 let head = FugitiveHead(0, a:dir)
1217 let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1219 while remote ==# '.' && i > 0
1220 let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
1221 let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1224 return remote =~# '^\.\=$' ? 'origin' : remote
1227 function! s:SshParseHost(value) abort
1230 for host in split(a:value, '\s\+')
1231 let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
1232 if pattern[0] ==# '!'
1233 call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
1235 call add(patterns, pattern)
1238 return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
1241 function! s:SshParseConfig(into, root, file) abort
1243 let lines = readfile(a:file)
1247 let host = '^\%(.*\)$'
1249 let line = remove(lines, 0)
1250 let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
1251 let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
1253 let host = value ==# 'all' ? '^\%(.*\)$' : ''
1254 elseif key ==# 'host'
1255 let host = s:SshParseHost(value)
1256 elseif key ==# 'include'
1257 for glob in split(value)
1259 let glob = a:root . glob
1261 for included in reverse(split(glob(glob), "\n"))
1262 call extend(lines, readfile(included), 'keep')
1265 elseif len(key) && len(host)
1266 call extend(a:into, {key : []}, 'keep')
1267 call add(a:into[key], [host, value])
1274 function! fugitive#SshConfig(host, ...) abort
1275 if !exists('s:ssh_config')
1276 let s:ssh_config = {}
1277 for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
1278 call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
1281 let host_config = {}
1282 for key in a:0 ? a:1 : keys(s:ssh_config)
1283 for [host_pattern, value] in get(s:ssh_config, key, [])
1284 if a:host =~# host_pattern
1285 let host_config[key] = value
1293 function! fugitive#SshHostAlias(authority) abort
1294 let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
1295 let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
1297 let user = get(c, 'user', '')
1300 let port = get(c, 'port', '')
1302 return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
1305 function! s:CurlResponse(result) abort
1306 let a:result.headers = {}
1307 for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
1308 let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
1310 let k = tolower(header[1])
1311 if has_key(a:result.headers, k)
1312 let a:result.headers[k] .= ', ' . header[2]
1314 let a:result.headers[k] = header[2]
1322 let s:remote_headers = {}
1324 function! fugitive#RemoteHttpHeaders(remote) abort
1325 let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
1326 if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
1329 let remote = substitute(remote, '#.*', '', '')
1330 if !has_key(s:remote_headers, remote)
1331 let url = remote . '/info/refs?service=git-upload-pack'
1332 let exec = s:JobExecute(
1333 \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
1334 \ url], {}, [], [function('s:CurlResponse')], {})
1335 call fugitive#Wait(exec)
1336 let s:remote_headers[remote] = exec.headers
1338 return s:remote_headers[remote]
1341 function! s:UrlParse(url) abort
1342 let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
1343 if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
1344 let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
1345 \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
1347 let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
1349 let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
1351 let url = {'scheme': 'file', 'authority': '', 'hash': '',
1352 \ 'path': s:UrlEncode(a:url)}
1354 let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
1355 let url.path = empty(match[3]) ? '/' : match[3]
1361 function! s:UrlPopulate(string, into) abort
1363 let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
1364 let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
1365 let url.host = substitute(url.authority, '.\{-\}@', '', '')
1366 let url.hostname = substitute(url.host, ':\d\+$', '', '')
1367 let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
1368 let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
1369 let url.search = matchstr(url.path, '?.*')
1370 let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
1371 if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
1372 let url.path = strpart(url.path, 1)
1374 if url.path =~# '^/'
1375 let url.href = url.scheme . '://' . url.authority . url.path . url.hash
1376 elseif url.path =~# '^\~'
1377 let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
1378 elseif url.scheme ==# 'ssh' && url.authority !~# ':'
1379 let url.href = url.authority . ':' . url.path . url.hash
1381 let url.href = a:string
1383 let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
1384 let url.url = matchstr(url.href, '^[^#]*')
1387 function! s:RemoteResolve(url, flags) abort
1388 let remote = s:UrlParse(a:url)
1389 if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
1390 let headers = fugitive#RemoteHttpHeaders(a:url)
1391 let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
1393 let remote = s:UrlParse(loc)
1395 let remote.headers = headers
1397 elseif remote.scheme ==# 'ssh'
1398 let remote.authority = fugitive#SshHostAlias(remote.authority)
1403 function! s:ConfigLengthSort(i1, i2) abort
1404 return len(a:i2[0]) - len(a:i1[0])
1407 function! s:RemoteCallback(config, into, flags, cb) abort
1408 if a:into.remote_name =~# '^\.\=$'
1409 let a:into.remote_name = s:RemoteDefault(a:config)
1411 let url = a:into.remote_name
1414 let url = s:GitDir(a:config)
1415 elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
1416 let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
1419 for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
1421 call add(instead_of, [v, k])
1424 call sort(instead_of, 's:ConfigLengthSort')
1425 for [orig, replacement] in instead_of
1426 if strpart(url, 0, len(orig)) ==# orig
1427 let url = replacement . strpart(url, len(orig))
1431 if index(a:flags, ':noresolve') < 0
1432 call extend(a:into, s:RemoteResolve(url, a:flags))
1434 call extend(a:into, s:UrlParse(url))
1436 call s:UrlPopulate(url, a:into)
1438 call call(a:cb[0], [a:into] + a:cb[1:-1])
1442 function! s:Remote(dir, remote, flags, cb) abort
1443 let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
1444 let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
1448 call fugitive#Wait(config)
1453 function! s:RemoteParseArgs(args) abort
1454 " Extract ':noresolve' style flags and an optional callback
1457 let cb = copy(a:args)
1459 if type(cb[0]) ==# type(function('tr'))
1461 elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
1462 call add(flags, remove(cb, 0))
1464 call add(args, remove(cb, 0))
1468 " From the remaining 0-2 arguments, extract the remote and Git config
1471 let dir_or_config = s:Dir()
1472 elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
1473 let dir_or_config = s:Dir()
1474 let remote = args[0]
1475 elseif len(args) == 1
1476 let dir_or_config = args[0]
1477 if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
1478 let remote = args[0].remote_name
1480 elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
1481 let dir_or_config = args[1]
1482 let remote = args[0]
1484 let dir_or_config = args[0]
1485 let remote = args[1]
1487 return [dir_or_config, remote, flags, cb]
1490 function! fugitive#Remote(...) abort
1491 let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
1492 return s:Remote(dir_or_config, remote, flags, cb)
1495 function! s:RemoteUrlCallback(remote, callback) abort
1496 return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
1499 function! fugitive#RemoteUrl(...) abort
1500 let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
1502 let cb = [function('s:RemoteUrlCallback'), cb]
1504 let remote = s:Remote(dir_or_config, remote_url, flags, cb)
1505 return get(remote, 'url', remote_url)
1510 function! s:QuickfixGet(nr, ...) abort
1512 return call('getqflist', a:000)
1514 return call('getloclist', [a:nr] + a:000)
1518 function! s:QuickfixSet(nr, ...) abort
1520 return call('setqflist', a:000)
1522 return call('setloclist', [a:nr] + a:000)
1526 function! s:QuickfixCreate(nr, opts) abort
1527 if has('patch-7.4.2200')
1528 call s:QuickfixSet(a:nr, [], ' ', a:opts)
1530 call s:QuickfixSet(a:nr, [], ' ')
1534 function! s:QuickfixOpen(nr, mods) abort
1535 let mods = substitute(s:Mods(a:mods), '\<\d*tab\>', '', '')
1536 return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
1539 function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
1541 let opts = {'title': a:title, 'context': {'items': []}}
1542 call s:QuickfixCreate(a:nr, opts)
1543 let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
1544 exe s:DoAutocmd('QuickFixCmdPre ' . event)
1546 exe s:QuickfixOpen(a:nr, a:mods)
1552 let lines = s:SystemList(a:cmd)[0]
1554 call extend(buffer, call(a:callback, a:000 + [line]))
1555 if len(buffer) >= 20
1556 let contexts = map(copy(buffer), 'get(v:val, "context", {})')
1558 call extend(opts.context.items, contexts)
1560 call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
1561 if a:mods !~# '\<silent\>'
1566 call extend(buffer, call(a:callback, a:000 + [0]))
1567 call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
1568 lockvar opts.context.items
1569 call s:QuickfixSet(a:nr, buffer, 'a')
1571 exe s:DoAutocmd('QuickFixCmdPost ' . event)
1573 let list = s:QuickfixGet(a:nr)
1574 for index in range(len(list))
1575 if list[index].valid
1576 return (index+1) . (a:nr < 0 ? 'cfirst' : 'lfirst')
1583 function! fugitive#Cwindow() abort
1584 if &buftype == 'quickfix'
1588 if &buftype == 'quickfix'
1594 " Section: Repository Object
1596 let s:repo_prototype = {}
1598 function! fugitive#repo(...) abort
1599 let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
1601 return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
1603 throw 'fugitive: not a Git repository'
1606 function! s:repo_dir(...) dict abort
1610 throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
1613 function! s:repo_tree(...) dict abort
1614 let tree = s:Tree(self.git_dir)
1616 throw 'fugitive: no work tree'
1620 throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
1623 function! s:repo_bare() dict abort
1624 throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
1627 function! s:repo_find(object) dict abort
1628 throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
1631 function! s:repo_translate(rev) dict abort
1632 throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
1635 function! s:repo_head(...) dict abort
1636 throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
1639 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
1641 function! s:repo_git_command(...) dict abort
1642 throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
1645 function! s:repo_git_chomp(...) dict abort
1646 silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
1649 function! s:repo_git_chomp_in_tree(...) dict abort
1650 return call(self.git_chomp, a:000, self)
1653 function! s:repo_rev_parse(rev) dict abort
1654 throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
1657 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
1659 function! s:repo_config(name) dict abort
1660 return FugitiveConfigGet(a:name, self.git_dir)
1663 call s:add_methods('repo',['config'])
1667 function! s:DirCommitFile(path) abort
1668 let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
1672 return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
1675 function! s:DirRev(url) abort
1676 let [dir, commit, file] = s:DirCommitFile(a:url)
1677 return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
1680 function! fugitive#Parse(url) abort
1681 return reverse(s:DirRev(a:url))
1684 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
1685 function! s:MergeHead(dir) abort
1686 let dir = fugitive#Find('.git/', a:dir)
1687 for head in s:merge_heads
1688 if filereadable(dir . head)
1695 function! s:Owner(path, ...) abort
1696 let dir = a:0 ? s:Dir(a:1) : s:Dir()
1700 let actualdir = fugitive#Find('.git/', dir)
1701 let [pdir, commit, file] = s:DirCommitFile(a:path)
1702 if s:SameRepo(dir, pdir)
1703 if commit =~# '^\x\{40,\}$'
1705 elseif commit ==# '2'
1707 elseif commit ==# '0'
1710 let merge_head = s:MergeHead(dir)
1711 if empty(merge_head)
1716 elseif commit ==# '1'
1717 return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
1720 let path = fnamemodify(a:path, ':p')
1721 if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
1722 return strpart(path, len(actualdir))
1724 let refs = fugitive#Find('.git/refs', dir)
1725 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
1726 return strpart(path, len(refs) - 4)
1731 function! fugitive#Real(url) abort
1735 let [dir, commit, file] = s:DirCommitFile(a:url)
1737 let tree = s:Tree(dir)
1738 return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
1740 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
1741 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
1742 let url = {pre}Real(a:url)
1744 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
1746 return s:VimSlash(empty(url) ? a:url : url)
1749 function! fugitive#Path(url, ...) abort
1753 let repo = call('s:Dir', a:000[1:-1])
1754 let dir_s = fugitive#Find('.git/', repo)
1755 let tree = fugitive#Find(':/', repo)
1757 return fugitive#Real(a:url)
1758 elseif a:1 =~# '\.$'
1759 let path = s:Slash(fugitive#Real(a:url))
1762 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
1763 if s:cpath(cwd . '/', path[0 : len(cwd)])
1764 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
1767 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
1769 let cwd = fnamemodify(cwd, ':h')
1772 return a:1[0:-2] . path
1775 let temp_state = s:TempState(url)
1776 if has_key(temp_state, 'origin_bufnr')
1777 let url = bufname(temp_state.origin_bufnr)
1779 let url = s:Slash(fnamemodify(url, ':p'))
1780 if url =~# '/$' && s:Slash(a:url) !~# '/$'
1783 let [argdir, commit, file] = s:DirCommitFile(url)
1784 if !empty(argdir) && !s:SameRepo(argdir, repo)
1786 elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
1787 let file = '/.git' . strpart(url, len(dir_s)-1)
1788 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
1789 let file = url[len(tree) : -1]
1790 elseif s:cpath(url) ==# s:cpath(tree)
1793 if empty(file) && a:1 =~# '^$\|^[.:]/$'
1794 return FugitiveGitPath(fugitive#Real(a:url))
1796 return substitute(file, '^/', '\=a:1', '')
1799 function! s:Relative(...) abort
1800 return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
1803 function! fugitive#Find(object, ...) abort
1804 if type(a:object) == type(0)
1805 let name = bufname(a:object)
1806 return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
1807 elseif a:object =~# '^[~$]'
1808 let prefix = matchstr(a:object, '^[~$]\i*')
1809 let owner = expand(prefix)
1810 return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
1812 let rev = s:Slash(a:object)
1813 if rev =~# '^\a\+://' && rev !~# '^fugitive:'
1815 elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1816 return s:VimSlash(a:object)
1817 elseif rev =~# '^\.\.\=\%(/\|$\)'
1818 return s:VimSlash(simplify(getcwd() . '/' . a:object))
1820 let dir = call('s:GitDir', a:000)
1822 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
1823 let dir = FugitiveExtractGitDir(file)
1828 let tree = s:Tree(dir)
1829 let urlprefix = s:DirUrlPrefix(dir)
1830 let base = len(tree) ? tree : urlprefix . '0'
1832 let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
1833 elseif rev =~# '^\.git/'
1834 let f = strpart(rev, 5)
1835 let fdir = simplify(FugitiveActualDir(dir) . '/')
1836 let cdir = simplify(FugitiveCommonDir(dir) . '/')
1837 if f =~# '^\.\./\.\.\%(/\|$\)'
1838 let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
1839 elseif f =~# '^\.\.\%(/\|$\)'
1840 let f = s:PathJoin(base, f[2:-1])
1841 elseif cdir !=# fdir && (
1842 \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
1843 \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
1844 \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
1845 let f = simplify(cdir . f)
1847 let f = simplify(fdir . f)
1851 elseif rev =~# '^\.\%(/\|$\)'
1852 let f = s:PathJoin(base, rev[1:-1])
1853 elseif rev =~# '^::\%(/\|\a\+\:\)'
1855 elseif rev =~# '^::\.\.\=\%(/\|$\)'
1856 let f = simplify(getcwd() . '/' . rev[2:-1])
1857 elseif rev =~# '^::'
1858 let f = s:PathJoin(base, '/' . rev[2:-1])
1859 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
1860 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
1861 if s:cpath(base . '/', (f . '/')[0 : len(base)])
1862 let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
1864 let altdir = FugitiveExtractGitDir(f)
1865 if len(altdir) && !s:cpath(dir, altdir)
1866 return fugitive#Find(a:object, altdir)
1869 elseif rev =~# '^:[0-3]:'
1870 let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
1873 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
1874 let f = matchstr(rev, ')\zs.*')
1875 if f=~# '^\.\.\=\%(/\|$\)'
1876 let f = simplify(getcwd() . '/' . f)
1877 elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1878 let f = s:PathJoin(base, '/' . f)
1880 elseif rev =~# '^:/\@!'
1881 let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
1884 let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
1885 let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
1886 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
1887 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
1888 if s:cpath(base . '/', (file . '/')[0 : len(base)])
1889 let file = '/' . strpart(file, len(base) + 1)
1891 let altdir = FugitiveExtractGitDir(file)
1892 if len(altdir) && !s:cpath(dir, altdir)
1893 return fugitive#Find(a:object, altdir)
1898 let commits = split(commit, '\.\.\.-\@!', 1)
1899 if len(commits) == 2
1900 call map(commits, 'empty(v:val) ? "@" : v:val')
1901 let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
1903 if commit !~# '^[0-9a-f]\{40,\}$\|^$'
1904 let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
1905 if empty(commit) && len(file)
1906 let commit = repeat('0', 40)
1910 let f = s:PathJoin(urlprefix, commit . file)
1912 let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
1916 return s:VimSlash(f)
1919 function! s:Generate(object, ...) abort
1920 let dir = a:0 ? a:1 : s:Dir()
1921 let f = fugitive#Find(a:object, dir)
1924 elseif a:object ==# ':/'
1925 return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
1927 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
1928 return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
1931 function! s:DotRelative(path, ...) abort
1932 let cwd = a:0 ? a:1 : getcwd()
1933 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
1934 if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
1935 return '.' . strpart(path, len(cwd))
1940 function! fugitive#Object(...) abort
1941 let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
1942 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
1943 if !s:SameRepo(dir, fdir)
1946 let tree = s:Tree(dir)
1947 let full = a:0 ? a:1 : s:BufName('%')
1948 let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
1949 if empty(rev) && empty(tree)
1950 return FugitiveGitPath(full)
1952 let rev = fugitive#Path(full, './', dir)
1953 if rev =~# '^\./.git\%(/\|$\)'
1954 return FugitiveGitPath(full)
1957 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
1960 return FugitiveGitPath(tree . rev[1:-1])
1964 let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
1965 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
1966 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
1967 let s:commit_expand = '!\\\@!#\=\d*\|!%'
1969 function! s:BufName(var) abort
1971 return bufname(get(s:TempState(), 'origin_bufnr', ''))
1972 elseif a:var =~# '^#\d*$'
1973 let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
1974 return bufname(nr ? nr : +a:var[1:-1])
1976 return expand(a:var)
1980 function! s:ExpandVar(other, var, flags, esc, ...) abort
1981 let cwd = a:0 ? a:1 : getcwd()
1983 return a:other[1:-1]
1984 elseif a:other =~# '^'''
1985 return substitute(a:other[1:-2], "''", "'", "g")
1986 elseif a:other =~# '^"'
1987 return substitute(a:other[1:-2], '""', '"', "g")
1988 elseif a:other =~# '^[!`]'
1989 let buffer = s:BufName(a:other =~# '[0-9#]' ? '#' . matchstr(a:other, '\d\+') : '%')
1990 let owner = s:Owner(buffer)
1991 return len(owner) ? owner : '@'
1992 elseif a:other =~# '^\~[~.]$'
1993 return s:Slash(getcwd())
1995 return expand(a:other)
1996 elseif a:var ==# '<cfile>'
1997 let bufnames = [expand('<cfile>')]
1998 if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
2000 let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
2001 if bufnames[0] ==# "\<C-R>\<C-F>"
2002 let bufnames = [expand('<cfile>')]
2007 elseif a:var =~# '^<'
2008 let bufnames = [s:BufName(a:var)]
2009 elseif a:var ==# '##'
2010 let bufnames = map(argv(), 'fugitive#Real(v:val)')
2012 let bufnames = [fugitive#Real(s:BufName(a:var))]
2015 for bufname in bufnames
2017 let file = s:DotRelative(bufname, cwd)
2019 let flag = matchstr(flags, s:flag)
2020 let flags = strpart(flags, len(flag))
2022 let file = s:DotRelative(fugitive#Real(file), cwd)
2024 let file = fnamemodify(file, flag)
2027 let file = s:Slash(file)
2028 if file =~# '^fugitive://'
2029 let [dir, commit, file_candidate] = s:DirCommitFile(file)
2030 let tree = s:Tree(dir)
2031 if len(tree) && len(file_candidate)
2032 let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
2033 \ s:DotRelative(tree . file_candidate)
2034 elseif empty(file_candidate) && commit !~# '^.$'
2038 call add(files, len(a:esc) ? shellescape(file) : file)
2040 return join(files, "\1")
2044 let s:fnameescape = " \t\n*?`%#'\"|!<"
2046 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
2049 function! s:Expand(rev, ...) abort
2050 if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
2052 elseif a:rev =~# '^>\=:[0-3]$'
2053 let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
2054 elseif a:rev =~# '^>\%(:\=/\)\=$'
2056 elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
2058 elseif a:rev ==# '>:'
2059 let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
2060 elseif a:rev =~# '^>[> ]\@!'
2061 let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
2062 let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
2066 let file = len(expand('%')) ? rev . ':%' : '%'
2068 elseif s:Slash(a:rev) =~# '^\a\a\+://'
2069 let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
2073 return substitute(file,
2074 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2075 \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
2078 function! fugitive#Expand(object) abort
2079 return substitute(a:object,
2080 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2081 \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
2084 function! s:SplitExpandChain(string, ...) abort
2086 let string = a:string
2087 let dquote = '"\%([^"]\|""\|\\"\)*"\|'
2088 let cwd = a:0 ? a:1 : getcwd()
2089 while string =~# '\S'
2090 if string =~# '^\s*|'
2091 return [list, substitute(string, '^\s*', '', '')]
2093 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
2094 let string = strpart(string, len(arg))
2095 let arg = substitute(arg, '^\s\+', '', '')
2096 if !exists('seen_separator')
2097 let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
2098 \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
2100 let arg = substitute(arg,
2101 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
2102 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
2103 call extend(list, split(arg, "\1", 1))
2105 let seen_separator = 1
2113 function! s:TreeInfo(dir, commit) abort
2114 let key = s:GitDir(a:dir)
2115 if a:commit =~# '^:\=[0-3]$'
2116 let index = get(s:indexes, key, [])
2117 let newftime = getftime(fugitive#Find('.git/index', a:dir))
2118 if get(index, 0, -2) < newftime
2119 let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
2120 let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
2125 let [info, filename] = split(line, "\t")
2126 let [mode, sha, stage] = split(info, '\s\+')
2127 let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
2128 while filename =~# '/'
2129 let filename = substitute(filename, '/[^/]*$', '', '')
2130 let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
2134 return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
2135 elseif a:commit =~# '^\x\{40,\}$'
2136 if !has_key(s:trees, key)
2137 let s:trees[key] = {}
2139 if !has_key(s:trees[key], a:commit)
2140 let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
2142 let s:trees[key][a:commit] = [{}, -1]
2143 return s:trees[key][a:commit]
2145 let s:trees[key][a:commit] = [{}, +ftime]
2146 let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
2148 return s:trees[key][a:commit]
2151 let [info, filename] = split(line, "\t")
2152 let [mode, type, sha, size] = split(info, '\s\+')
2153 let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
2156 return s:trees[key][a:commit]
2161 function! s:PathInfo(url) abort
2162 let [dir, commit, file] = s:DirCommitFile(a:url)
2163 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
2164 return [-1, '000000', '', '', -1]
2166 let path = substitute(file[1:-1], '/*$', '', '')
2167 let [tree, ftime] = s:TreeInfo(dir, commit)
2168 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
2169 if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
2170 return [-1, '000000', '', '', -1]
2176 function! fugitive#simplify(url) abort
2177 let [dir, commit, file] = s:DirCommitFile(a:url)
2180 elseif empty(commit)
2181 return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
2183 if file =~# '/\.\.\%(/\|$\)'
2184 let tree = s:Tree(dir)
2186 let path = simplify(tree . file)
2187 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
2188 return s:VimSlash(path)
2192 return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
2195 function! fugitive#resolve(url) abort
2196 let url = fugitive#simplify(a:url)
2197 if url =~? '^fugitive:'
2204 function! fugitive#getftime(url) abort
2205 return s:PathInfo(a:url)[0]
2208 function! fugitive#getfsize(url) abort
2209 let entry = s:PathInfo(a:url)
2210 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
2211 let dir = s:DirCommitFile(a:url)[0]
2212 let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
2217 function! fugitive#getftype(url) abort
2218 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
2221 function! fugitive#filereadable(url) abort
2222 return s:PathInfo(a:url)[2] ==# 'blob'
2225 function! fugitive#filewritable(url) abort
2226 let [dir, commit, file] = s:DirCommitFile(a:url)
2227 if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
2230 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
2233 function! fugitive#isdirectory(url) abort
2234 return s:PathInfo(a:url)[2] ==# 'tree'
2237 function! fugitive#getfperm(url) abort
2238 let [dir, commit, file] = s:DirCommitFile(a:url)
2239 let perm = getfperm(dir)
2240 let fperm = s:PathInfo(a:url)[1]
2241 if fperm ==# '040000'
2242 let fperm = '000755'
2245 let perm = tr(perm, 'x', '-')
2247 if fperm !~# '[45]$'
2248 let perm = tr(perm, 'rw', '--')
2250 if commit !~# '^\d$'
2251 let perm = tr(perm, 'w', '-')
2253 return perm ==# '---------' ? '' : perm
2256 function! s:UpdateIndex(dir, info) abort
2257 let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
2258 let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
2259 return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
2262 function! fugitive#setfperm(url, perm) abort
2263 let [dir, commit, file] = s:DirCommitFile(a:url)
2264 let entry = s:PathInfo(a:url)
2265 let perm = fugitive#getfperm(a:url)
2266 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
2267 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
2270 let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
2271 return len(error) ? -1 : 0
2274 if !exists('s:blobdirs')
2277 function! s:BlobTemp(url) abort
2278 let [dir, commit, file] = s:DirCommitFile(a:url)
2282 let key = s:GitDir(dir)
2283 if !has_key(s:blobdirs, key)
2284 let s:blobdirs[key] = tempname()
2286 let tempfile = s:blobdirs[key] . '/' . commit . file
2287 let tempparent = fnamemodify(tempfile, ':h')
2288 if !isdirectory(tempparent)
2289 call mkdir(tempparent, 'p')
2290 elseif isdirectory(tempfile)
2291 if commit =~# '^\d$' && has('patch-7.4.1107')
2292 call delete(tempfile, 'rf')
2297 if commit =~# '^\d$' || !filereadable(tempfile)
2298 let rev = s:DirRev(a:url)[1]
2299 let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
2300 let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
2302 call delete(tempfile)
2306 return s:Resolve(tempfile)
2309 function! fugitive#readfile(url, ...) abort
2310 let entry = s:PathInfo(a:url)
2311 if entry[2] !=# 'blob'
2314 let temp = s:BlobTemp(a:url)
2318 return call('readfile', [temp] + a:000)
2321 function! fugitive#writefile(lines, url, ...) abort
2322 let url = type(a:url) ==# type('') ? a:url : ''
2323 let [dir, commit, file] = s:DirCommitFile(url)
2324 let entry = s:PathInfo(url)
2325 if commit =~# '^\d$' && entry[2] !=# 'tree'
2326 let temp = tempname()
2327 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
2328 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
2330 call call('writefile', [a:lines, temp] + a:000)
2331 let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
2332 let mode = entry[1] !=# '000000' ? entry[1] : '100644'
2333 if hash =~# '^\x\{40,\}$'
2334 let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
2340 return call('writefile', [a:lines, a:url] + a:000)
2344 \ '/**/': '/\%([^./][^/]*/\)*',
2345 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
2346 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
2348 \ '/*': '/[^/.][^/]*',
2351 function! fugitive#glob(url, ...) abort
2352 let [repo, commit, glob] = s:DirCommitFile(a:url)
2353 let dirglob = s:GitDir(repo)
2354 let append = matchstr(glob, '/*$')
2355 let glob = substitute(glob, '/*$', '', '')
2356 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
2358 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
2359 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
2362 let files = items(s:TreeInfo(dir, commit)[0])
2364 call filter(files, 'v:val[1][2] ==# "tree"')
2366 call map(files, 'v:val[0]')
2367 call filter(files, 'v:val =~# pattern')
2368 let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
2370 call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
2371 call extend(results, files)
2376 return join(results, "\n")
2380 function! fugitive#delete(url, ...) abort
2381 let [dir, commit, file] = s:DirCommitFile(a:url)
2382 if a:0 && len(a:1) || commit !~# '^\d$'
2385 let entry = s:PathInfo(a:url)
2386 if entry[2] !=# 'blob'
2389 let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
2390 return len(error) ? -1 : 0
2393 " Section: Completion
2395 function! s:FilterEscape(items, ...) abort
2396 let items = copy(a:items)
2397 call map(items, 'fnameescape(v:val)')
2398 if !a:0 || type(a:1) != type('')
2401 let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
2403 let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
2404 return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
2407 function! s:GlobComplete(lead, pattern, ...) abort
2411 let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
2413 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
2414 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
2418 function! fugitive#CompletePath(base, ...) abort
2419 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2420 let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
2421 let base = strpart(a:base, len(stripped))
2422 if len(stripped) || a:0 < 4
2423 let root = s:Tree(dir)
2427 if root !=# '/' && len(root)
2431 let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
2432 let base = strpart(a:base, len(stripped))
2434 if base =~# '^\.git/' && len(dir)
2435 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
2436 let fdir = fugitive#Find('.git/', dir)
2437 let matches = s:GlobComplete(fdir, pattern)
2438 let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2439 if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
2440 call extend(matches, s:GlobComplete(cdir, pattern))
2442 call s:Uniq(matches)
2443 call map(matches, "'.git/' . v:val")
2444 elseif base =~# '^\~/'
2445 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
2446 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
2447 let matches = s:GlobComplete('', base . '*')
2449 let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
2453 call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
2457 function! fugitive#PathComplete(...) abort
2458 return call('fugitive#CompletePath', a:000)
2461 function! s:CompleteHeads(dir) abort
2465 let dir = fugitive#Find('.git/', a:dir)
2466 return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
2467 \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
2470 function! fugitive#CompleteObject(base, ...) abort
2471 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2472 let tree = s:Tree(dir)
2475 if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
2476 let subdir = strpart(cwd, len(tree) + 1) . '/'
2478 let base = s:Expand(a:base)
2480 if a:base =~# '^!\d*$' && base !~# '^!'
2482 elseif base =~# '^\.\=/\|^:(' || base !~# ':'
2484 if base =~# '^refs/'
2485 let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2486 let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
2487 call map(results, 's:fnameescape(v:val)')
2488 elseif base !~# '^\.\=/\|^:('
2489 let heads = s:CompleteHeads(dir)
2490 if filereadable(fugitive#Find('.git/refs/stash', dir))
2491 let heads += ["stash"]
2492 let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
2494 let results += s:FilterEscape(heads, fnameescape(base))
2496 let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
2499 elseif base =~# '^:'
2500 let entries = s:LinesError(['ls-files','--stage'], dir)[0]
2502 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
2504 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
2505 if base !~# '^:[0-3]\%(:\|$\)'
2506 call filter(entries,'v:val[1] == "0"')
2507 call map(entries,'v:val[2:-1]')
2511 let parent = matchstr(base, '.*[:/]')
2512 let entries = s:LinesError(['ls-tree', substitute(parent, ':\zs\./', '\=subdir', '')], dir)[0]
2513 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
2514 call map(entries,'parent.s:sub(v:val,".*\t","")')
2516 return s:FilterEscape(entries, fnameescape(base))
2519 function! s:CompleteSub(subcommand, A, L, P, ...) abort
2520 let pre = strpart(a:L, 0, a:P)
2522 return fugitive#CompletePath(a:A)
2523 elseif a:A =~# '^-' || a:A is# 0
2524 return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
2526 return fugitive#CompleteObject(a:A, s:Dir())
2527 elseif type(a:1) == type(function('tr'))
2528 return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
2530 return s:FilterEscape(a:1, a:A)
2534 function! s:CompleteRevision(A, L, P, ...) abort
2535 return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
2538 function! s:CompleteRemote(A, L, P, ...) abort
2539 let dir = a:0 ? a:1 : s:Dir()
2540 let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
2542 let matches = s:LinesError([dir, 'ls-remote', remote])[0]
2543 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2544 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2546 let matches = s:LinesError([dir, 'remote'])[0]
2548 return s:FilterEscape(matches, a:A)
2551 " Section: Buffer auto-commands
2553 augroup fugitive_dummy_events
2555 autocmd User Fugitive* "
2556 autocmd BufWritePre,FileWritePre,FileWritePost * "
2557 autocmd BufNewFile * "
2558 autocmd QuickfixCmdPre,QuickfixCmdPost * "
2561 function! s:ReplaceCmd(cmd) abort
2562 let temp = tempname()
2563 let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
2565 throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
2568 silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
2569 if &foldenable && foldlevel('$') > 0
2571 silent keepjumps $delete _
2574 silent keepjumps $delete _
2577 if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
2578 silent! noautocmd execute bufnr('$') . 'bwipeout'
2582 function! s:FormatLog(dict) abort
2583 return a:dict.commit . ' ' . a:dict.subject
2586 function! s:FormatRebase(dict) abort
2587 return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
2590 function! s:FormatFile(dict) abort
2591 return a:dict.status . ' ' . a:dict.filename
2594 function! s:Format(val) abort
2595 if type(a:val) == type({})
2596 return s:Format{a:val.type}(a:val)
2597 elseif type(a:val) == type([])
2598 return map(copy(a:val), 's:Format(v:val)')
2604 function! s:AddHeader(to, key, value) abort
2608 call add(a:to.lines, a:key . ':' . (len(a:value) ? ' ' . a:value : ''))
2611 function! s:AddSection(to, label, lines, ...) abort
2612 let note = a:0 ? a:1 : ''
2613 if empty(a:lines) && empty(note)
2616 call extend(a:to.lines, ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
2619 function! s:AddDiffSection(to, stat, label, files) abort
2623 let diff_section = a:stat.diff[a:label]
2624 let expanded = a:stat.expanded[a:label]
2625 let was_expanded = get(getbufvar(a:stat.bufnr, 'fugitive_expanded', {}), a:label, {})
2626 call extend(a:to.lines, ['', a:label . ' (' . len(a:files) . ')'])
2628 call add(a:to.lines, s:Format(file))
2629 if has_key(was_expanded, file.filename)
2630 let [diff, start] = s:StageInlineGetDiff(diff_section, file)
2632 let expanded[file.filename] = [start]
2633 call extend(a:to.lines, diff)
2639 function! s:QueryLog(refspec, limit, dir) abort
2640 let [log, exec_error] = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'], a:dir)
2641 call map(log, 'split(v:val, "\t", 1)')
2642 call map(log, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
2643 let result = {'error': exec_error ? 1 : 0, 'overflow': 0, 'entries': log}
2644 if len(log) == a:limit
2645 call remove(log, -1)
2646 let result.overflow = 1
2651 function! s:QueryLogRange(old, new, dir) abort
2652 if empty(a:old) || empty(a:new)
2653 return {'error': 2, 'overflow': 0, 'entries': []}
2655 return s:QueryLog([a:old . '..' . a:new], 256, a:dir)
2658 function! s:AddLogSection(to, label, log) abort
2659 if empty(a:log.entries)
2662 let label = a:label . ' (' . len(a:log.entries) . (a:log.overflow ? '+' : '') . ')'
2663 call extend(a:to.lines, ['', label] + s:Format(a:log.entries))
2666 let s:rebase_abbrevs = {
2680 function! s:MapStatus() abort
2681 call fugitive#MapJumps()
2682 call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2683 call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
2684 call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
2685 call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
2686 call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
2687 call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
2688 call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
2689 call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
2690 call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
2691 call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
2692 call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
2693 call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
2694 call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
2695 call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
2696 call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2697 call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
2698 call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
2699 call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
2700 call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
2701 call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2702 call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2703 call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2704 call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
2705 call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
2706 call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2707 call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2708 call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
2709 call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
2710 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
2711 call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
2712 call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2713 call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, P for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
2714 call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2715 call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'))<CR>", '<silent>')
2716 call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2717 call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
2718 call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
2719 call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2720 call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2721 call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
2722 call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2723 call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
2724 call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2725 call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
2726 call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
2729 function! fugitive#BufReadStatus(cmdbang) abort
2730 exe s:VersionCheck()
2731 let amatch = s:Slash(expand('%:p'))
2733 unlet! b:fugitive_expanded
2735 unlet! b:fugitive_reltime b:fugitive_type
2737 let stat = {'bufnr': bufnr(''), 'reltime': reltime(), 'work_tree': s:Tree(dir)}
2739 let config = fugitive#Config(dir)
2742 if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index', dir)) !=# s:cpath(amatch)
2743 let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
2746 if fugitive#GitVersion(2, 15)
2747 call add(cmd, '--no-optional-locks')
2750 if !empty(stat.work_tree)
2751 let status_cmd = cmd + ['status', '-bz']
2752 call add(status_cmd, fugitive#GitVersion(2, 11) ? '--porcelain=v2' : '--porcelain')
2753 let status_exec = fugitive#Execute(status_cmd, function('len'))
2756 doautocmd <nomodeline> BufReadPre
2758 setlocal readonly nomodifiable noswapfile nomodeline buftype=nowrite
2761 let [staged, unstaged, untracked] = [[], [], []]
2764 if !exists('status_exec')
2765 let branch = FugitiveHead(0, dir)
2766 let head = FugitiveHead(11, dir)
2768 elseif fugitive#Wait(status_exec).exit_status
2769 return 'echoerr ' . string('fugitive: ' . s:JoinChomp(status_exec.stderr))
2771 elseif status_exec.args[-1] ==# '--porcelain=v2'
2772 let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2774 while i < len(output)
2775 let line = output[i]
2776 let prop = matchlist(line, '# \(\S\+\) \(.*\)')
2778 let stat.props[prop[1]] = prop[2]
2779 elseif line[0] ==# '?'
2780 call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
2781 elseif line[0] !=# '#'
2783 let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
2785 let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
2789 let file = matchstr(file, ' \zs.*')
2790 let relative = [file, output[i]]
2792 let relative = [file]
2794 let filename = join(reverse(copy(relative)), ' -> ')
2795 let sub = matchstr(line, '^[12u] .. \zs....')
2797 call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
2800 let sub = matchstr(line, '^[12u] .. \zs....')
2801 call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
2806 let branch = substitute(get(stat.props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
2809 elseif has_key(stat.props, 'branch.oid')
2810 let head = stat.props['branch.oid'][0:10]
2812 let head = FugitiveHead(11, dir)
2816 let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2817 while get(output, 0, '') =~# '^\l\+:'
2818 call remove(output, 0)
2820 let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
2821 if head =~# '\.\.\.'
2822 let head = split(head, '\.\.\.')[0]
2824 elseif head ==# 'HEAD' || empty(head)
2825 let head = FugitiveHead(11, dir)
2832 while i < len(output)
2833 let line = output[i]
2834 let file = line[3:-1]
2839 if line[0:1] =~# '[RC]'
2840 let relative = [file, output[i]]
2843 let relative = [file]
2845 let filename = join(reverse(copy(relative)), ' -> ')
2846 if line[0] !~# '[ ?!#]'
2847 call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
2849 if line[0:1] ==# '??'
2850 call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
2851 elseif line[1] !~# '[ !#]'
2852 call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
2857 let diff_cmd = cmd + ['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix']
2858 let stat.diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
2860 let stat.diff['Staged'] = fugitive#Execute(diff_cmd + ['--cached'], function('len'))
2863 let stat.diff['Unstaged'] = fugitive#Execute(diff_cmd + ['--'] + map(copy(unstaged), 'stat.work_tree . "/" . v:val.relative[0]'), function('len'))
2866 let stat.files = {'Staged': {}, 'Unstaged': {}}
2868 let stat.files['Staged'][dict.filename] = dict
2870 for dict in unstaged
2871 let stat.files['Unstaged'][dict.filename] = dict
2874 let fetch_remote = config.Get('branch.' . branch . '.remote', 'origin')
2875 let push_remote = config.Get('branch.' . branch . '.pushRemote',
2876 \ config.Get('remote.pushDefault', fetch_remote))
2877 if fetch_remote !=# '.' && empty(config.Get('remote.' . fetch_remote . '.fetch'))
2878 let fetch_remote = ''
2880 if push_remote !=# '.' && empty(config.Get('remote.' . push_remote . '.push', config.Get('remote.' . push_remote . '.fetch')))
2881 let push_remote = ''
2884 let pull_type = 'Pull'
2885 if empty(fetch_remote) || empty(branch)
2887 elseif fetch_remote ==# '.'
2888 let pull_ref = config.Get('branch.' . branch . '.merge', '')
2890 let pull_ref = substitute(config.Get('branch.' . branch . '.merge', ''), '^refs/heads/', 'refs/remotes/' . fetch_remote . '/', '')
2893 let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
2895 let rebase = FugitiveConfigGet('pull.rebase', config)
2897 if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
2898 let pull_type = 'Rebase'
2899 elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
2900 let pull_type = 'Merge'
2904 let push_default = FugitiveConfigGet('push.default', config)
2905 if empty(push_default)
2906 let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
2908 if push_default ==# 'upstream'
2909 let push_ref = pull_ref
2910 elseif empty(push_remote) || empty(branch)
2912 elseif push_remote ==# '.'
2913 let push_ref = 'refs/heads/' . branch
2915 let push_ref = 'refs/remotes/' . push_remote . '/' . branch
2918 let push_short = substitute(push_ref, '^refs/\w\+/', '', '')
2919 let pull_short = substitute(pull_ref, '^refs/\w\+/', '', '')
2921 if isdirectory(fugitive#Find('.git/rebase-merge/', dir))
2922 let rebasing_dir = fugitive#Find('.git/rebase-merge/', dir)
2923 elseif isdirectory(fugitive#Find('.git/rebase-apply/', dir))
2924 let rebasing_dir = fugitive#Find('.git/rebase-apply/', dir)
2928 let rebasing_head = 'detached HEAD'
2929 if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
2930 let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
2932 let lines = readfile(rebasing_dir . 'git-rebase-todo')
2934 let hash = matchstr(line, '^[^a-z].*\s\zs[0-9a-f]\{4,\}\ze\.\.')
2940 if getfsize(rebasing_dir . 'done') > 0
2941 let done = readfile(rebasing_dir . 'done')
2942 call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
2943 let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
2944 let lines = done + lines
2948 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2949 if len(match) && match[1] !~# 'exec\|merge\|label'
2950 call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
2956 if filereadable(fugitive#Find('.git/sequencer/todo', dir))
2957 for line in reverse(readfile(fugitive#Find('.git/sequencer/todo', dir)))
2958 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2959 if len(match) && match[1] !~# 'exec\|merge\|label'
2960 call add(sequencing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': match[2], 'subject': match[3]})
2963 elseif filereadable(fugitive#Find('.git/MERGE_MSG', dir))
2964 if filereadable(fugitive#Find('.git/CHERRY_PICK_HEAD', dir))
2965 let pick_head = fugitive#Execute(['rev-parse', '--short', 'CHERRY_PICK_HEAD', '--'], dir).stdout[0]
2966 call add(sequencing, {'type': 'Rebase', 'status': 'pick', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2967 elseif filereadable(fugitive#Find('.git/REVERT_HEAD', dir))
2968 let pick_head = fugitive#Execute(['rev-parse', '--short', 'REVERT_HEAD', '--'], dir).stdout[0]
2969 call add(sequencing, {'type': 'Rebase', 'status': 'revert', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2973 let stat.expanded = {'Staged': {}, 'Unstaged': {}}
2974 let to = {'lines': []}
2975 call s:AddHeader(to, 'Head', head)
2976 call s:AddHeader(to, pull_type, pull_short)
2977 if push_ref !=# pull_ref
2978 call s:AddHeader(to, 'Push', push_short)
2980 if empty(stat.work_tree)
2981 if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
2982 call s:AddHeader(to, 'Bare', 'yes')
2984 call s:AddHeader(to, 'Error', s:worktree_error)
2987 if get(fugitive#ConfigGetAll('advice.tousHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
2988 call s:AddHeader(to, 'Help', 'g?')
2991 call s:AddSection(to, 'Rebasing ' . rebasing_head, rebasing)
2992 call s:AddSection(to, get(get(sequencing, 0, {}), 'tous', '') ==# 'revert' ? 'Reverting' : 'Cherry Picking', sequencing)
2993 call s:AddSection(to, 'Untracked', untracked)
2994 call s:AddDiffSection(to, stat, 'Unstaged', unstaged)
2995 call s:AddDiffSection(to, stat, 'Staged', staged)
2997 let unique_push_ref = push_ref ==# pull_ref ? '' : push_ref
2998 let unpushed_push = s:QueryLogRange(unique_push_ref, head, dir)
2999 if get(stat.props, 'branch.ab') =~# '^+0 '
3000 let unpushed_pull = {'error': 0, 'overflow': 0, 'entries': []}
3002 let unpushed_pull = s:QueryLogRange(pull_ref, head, dir)
3004 " If the push ref is defined but nowhere to be found at the remote,
3005 " pretend it's the same as the pull ref
3006 if unpushed_push.error == 1
3007 let unpushed_push = unpushed_pull
3009 call s:AddLogSection(to, 'Unpushed to ' . push_short, unpushed_push)
3010 call s:AddLogSection(to, 'Unpushed to ' . pull_short, unpushed_pull)
3011 if unpushed_push.error && unpushed_pull.error && empty(rebasing) &&
3012 \ !empty(push_remote . fetch_remote)
3013 call s:AddLogSection(to, 'Unpushed to *', s:QueryLog([head, '--not', '--remotes'], 256, dir))
3015 call s:AddLogSection(to, 'Unpulled from ' . push_short, s:QueryLogRange(head, unique_push_ref, dir))
3016 if len(pull_ref) && get(stat.props, 'branch.ab') !~# ' -0$'
3017 call s:AddLogSection(to, 'Unpulled from ' . pull_short, s:QueryLogRange(head, pull_ref, dir))
3020 let b:fugitive_files = stat.files
3021 let b:fugitive_diff = stat.diff
3022 let b:fugitive_expanded = stat.expanded
3023 let b:fugitive_reltime = stat.reltime
3024 setlocal noreadonly modifiable
3025 if len(to.lines) < line('$')
3026 silent keepjumps execute (len(to.lines)+1) . ',$delete_'
3028 call setline(1, to.lines)
3029 setlocal nomodified readonly nomodifiable
3031 doautocmd <nomodeline> BufReadPost
3032 if &bufhidden ==# ''
3033 setlocal bufhidden=delete
3035 if !exists('b:dispatch')
3036 let b:dispatch = ':Git fetch --all'
3038 setlocal filetype=fugitive
3040 return s:DoAutocmd('User FugitiveIndex')
3042 let b:fugitive_type = 'index'
3046 function! fugitive#FileReadCmd(...) abort
3047 let amatch = a:0 ? a:1 : expand('<amatch>')
3048 let [dir, rev] = s:DirRev(amatch)
3049 let line = a:0 > 1 ? a:2 : line("'[")
3051 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3053 if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
3054 let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
3056 let cmd = [dir, 'status', '--short']
3058 let cmd = [dir, 'cat-file', '-p', rev, '--']
3060 let temp = tempname()
3061 let [err, exec_error] = s:StdoutToFile(temp, cmd)
3064 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3066 return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
3070 function! fugitive#FileWriteCmd(...) abort
3071 let temp = tempname()
3072 let amatch = a:0 ? a:1 : expand('<amatch>')
3073 let autype = a:0 > 1 ? 'Buf' : 'File'
3074 if exists('#' . autype . 'WritePre')
3075 execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
3078 let [dir, commit, file] = s:DirCommitFile(amatch)
3079 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
3080 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
3082 silent execute "noautocmd keepalt '[,']write ".temp
3083 let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
3084 let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
3086 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
3088 let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
3091 if exists('#' . autype . 'WritePost')
3092 execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
3094 exe s:DoAutocmdChanged(dir)
3097 return 'echoerr '.string('fugitive: '.error)
3100 return 'echoerr ' . string(v:exception)
3106 function! fugitive#BufReadCmd(...) abort
3107 let amatch = a:0 ? a:1 : expand('<amatch>')
3108 let [dir, rev] = s:DirRev(amatch)
3110 return 'echo "Invalid Fugitive URL"'
3112 call s:InitializeBuffer(dir)
3114 return fugitive#BufReadStatus(v:cmdbang)
3118 let b:fugitive_type = 'stage'
3120 let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
3121 let b:fugitive_type = get(r.stdout, 0, '')
3122 if r.exit_status && rev =~# '^:0'
3123 let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
3124 let sha = get(r.stdout, 0, '')
3125 let b:fugitive_type = 'tree'
3128 let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
3129 unlet b:fugitive_type
3131 if empty(&bufhidden)
3132 setlocal bufhidden=delete
3135 let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
3136 return 'doautocmd BufNewFile'
3138 setlocal readonly nomodifiable
3139 return 'doautocmd BufNewFile|echo ' . string(error)
3141 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
3142 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
3144 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
3145 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
3149 if b:fugitive_type !=# 'blob'
3153 setlocal noreadonly modifiable
3154 let pos = getpos('.')
3155 silent keepjumps %delete_
3158 let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
3161 if b:fugitive_type !=# 'blob'
3162 setlocal foldmarker=<<<<<<<<,>>>>>>>>
3164 exe s:DoAutocmd('BufReadPre')
3165 if b:fugitive_type ==# 'tree'
3166 let b:fugitive_display_format = b:fugitive_display_format % 2
3167 if b:fugitive_display_format
3168 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
3171 let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
3173 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
3175 elseif b:fugitive_type ==# 'tag'
3176 let b:fugitive_display_format = b:fugitive_display_format % 2
3177 if b:fugitive_display_format
3178 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3180 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
3182 elseif b:fugitive_type ==# 'commit'
3183 let b:fugitive_display_format = b:fugitive_display_format % 2
3184 if b:fugitive_display_format
3185 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3187 call s:ReplaceCmd([dir, '-c', 'diff.noprefix=false', '-c', 'log.showRoot=false', 'show', '--no-color', '-m', '--first-parent', '--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])
3189 keepjumps call search('^parent ')
3190 if getline('.') ==# 'parent '
3191 silent lockmarks keepjumps delete_
3193 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
3195 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
3197 silent lockmarks keepjumps delete_
3199 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
3202 elseif b:fugitive_type ==# 'stage'
3203 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
3204 elseif b:fugitive_type ==# 'blob'
3205 let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
3206 call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
3209 keepjumps call setpos('.',pos)
3210 setlocal nomodified noswapfile
3211 let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
3213 let events = ['User FugitiveStageBlob']
3215 let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
3216 if empty(&bufhidden)
3217 setlocal bufhidden=delete
3219 let &l:modifiable = modifiable
3220 call fugitive#MapJumps()
3221 if b:fugitive_type !=# 'blob'
3222 call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3223 call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3224 setlocal filetype=git
3230 return s:DoAutocmd('BufReadPost') .
3231 \ (modifiable ? '' : '|setl nomodifiable') . '|' .
3232 \ call('s:DoAutocmd', events)
3234 return 'echoerr ' . string(v:exception)
3238 function! fugitive#BufWriteCmd(...) abort
3239 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
3242 function! fugitive#SourceCmd(...) abort
3243 let amatch = a:0 ? a:1 : expand('<amatch>')
3244 let temp = s:BlobTemp(amatch)
3246 return 'noautocmd source ' . s:fnameescape(amatch)
3248 if !exists('g:virtual_scriptnames')
3249 let g:virtual_scriptnames = {}
3251 let g:virtual_scriptnames[temp] = amatch
3252 return 'source ' . s:fnameescape(temp)
3255 " Section: Temp files
3257 if !exists('s:temp_files')
3258 let s:temp_files = {}
3261 function! s:TempState(...) abort
3262 return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
3265 function! fugitive#Result(...) abort
3266 if !a:0 && exists('g:fugitive_event')
3267 return get(g:, 'fugitive_result', {})
3268 elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
3269 return get(g:, '_fugitive_last_job', {})
3270 elseif type(a:1) == type(0)
3271 return s:TempState(a:1)
3272 elseif type(a:1) == type('')
3273 return s:TempState(a:1)
3274 elseif type(a:1) == type({}) && has_key(a:1, 'file')
3275 return s:TempState(a:1.file)
3281 function! s:TempDotMap() abort
3282 let cfile = s:cfile()
3284 if getline('.') =~# '^[*+] \+\f' && col('.') < 2
3285 return matchstr(getline('.'), '^. \+\zs\f\+')
3287 return expand('<cfile>')
3290 let name = fugitive#Find(cfile[0])
3291 let [dir, commit, file] = s:DirCommitFile(name)
3292 if len(commit) && empty(file)
3294 elseif s:cpath(s:Tree(), getcwd())
3295 return fugitive#Path(name, "./")
3297 return fugitive#Real(name)
3301 function! s:TempReadPre(file) abort
3302 let key = s:cpath(s:AbsoluteVimPath(a:file))
3303 if has_key(s:temp_files, key)
3304 let dict = s:temp_files[key]
3306 if empty(&bufhidden)
3307 setlocal bufhidden=delete
3309 setlocal buftype=nowrite
3310 setlocal nomodifiable
3311 call s:InitializeBuffer(dict)
3312 if len(dict.git_dir)
3313 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
3319 function! s:TempReadPost(file) abort
3320 let key = s:cpath(s:AbsoluteVimPath(a:file))
3321 if has_key(s:temp_files, key)
3322 let dict = s:temp_files[key]
3323 if !has_key(dict, 'job')
3324 setlocal nobuflisted
3326 if get(dict, 'filetype', '') ==# 'git'
3327 call fugitive#MapJumps()
3328 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3329 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3331 if has_key(dict, 'filetype')
3332 if dict.filetype ==# 'man' && has('nvim')
3333 let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
3335 if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
3338 let &l:filetype = dict.filetype
3340 setlocal foldmarker=<<<<<<<<,>>>>>>>>
3342 call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
3345 return s:DoAutocmd('User FugitivePager')
3348 function! s:TempDelete(file) abort
3349 let key = s:cpath(s:AbsoluteVimPath(a:file))
3350 if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
3352 call remove(s:temp_files, key)
3357 function! s:OriginBufnr(...) abort
3358 let state = s:TempState(a:0 ? a:1 : bufnr(''))
3359 return get(state, 'origin_bufnr', -1)
3362 augroup fugitive_temp
3364 autocmd BufReadPre * exe s:TempReadPre( +expand('<abuf>'))
3365 autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
3366 autocmd BufWipeout * exe s:TempDelete( +expand('<abuf>'))
3371 function! s:AskPassArgs(dir) abort
3372 if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
3373 \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
3374 if s:executable(s:VimExecPath() . '/git-gui--askpass')
3375 return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
3376 elseif s:executable('ssh-askpass')
3377 return ['-c', 'core.askPass=ssh-askpass']
3383 function! s:RunSave(state) abort
3384 let s:temp_files[s:cpath(a:state.file)] = a:state
3387 function! s:RunFinished(state, ...) abort
3388 if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
3389 exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
3391 let g:_fugitive_last_job = a:state
3392 let first = join(readfile(a:state.file, '', 2), "\n")
3393 if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3394 let a:state.filetype = 'man'
3396 if !has_key(a:state, 'capture_bufnr')
3399 call fugitive#DidChange(a:state)
3402 function! s:RunEdit(state, tmp, job) abort
3403 if get(a:state, 'request', '') !=# 'edit'
3406 call remove(a:state, 'request')
3407 let sentinel = a:state.file . '.edit'
3408 let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
3410 if !&equalalways && a:state.mods !~# '\<\d*tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
3411 let noequalalways = 1
3412 setglobal equalalways
3414 let mods = s:Mods(a:state.mods, 'SpanOrigin')
3415 exe substitute(mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
3417 if exists('l:noequalalways')
3418 setglobal noequalalways
3422 call s:InitializeBuffer(a:state)
3423 let bufnr = bufnr('')
3424 let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
3425 call fugitive#DidChange(a:state.git_dir)
3426 if bufnr == bufnr('') && !exists('g:fugitive_event')
3428 let g:fugitive_event = a:state.git_dir
3429 let g:fugitive_result = a:state
3430 exe s:DoAutocmd('User FugitiveEditor')
3432 unlet! g:fugitive_event g:fugitive_result
3438 function! s:RunReceive(state, tmp, type, job, data, ...) abort
3439 if a:type ==# 'err' || a:state.pty
3440 let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3441 let data = a:tmp.escape . data
3442 let escape = "\033]51;[^\007]*"
3443 let a:tmp.escape = matchstr(data, escape . '$')
3444 if len(a:tmp.escape)
3445 let data = strpart(data, 0, len(data) - len(a:tmp.escape))
3447 let cmd = matchstr(data, escape . "\007")[5:-2]
3448 let data = substitute(data, escape . "\007", '', 'g')
3449 if cmd =~# '^fugitive:'
3450 let a:state.request = strpart(cmd, 9)
3452 let lines = split(a:tmp.err . data, "\r\\=\n", 1)
3453 let a:tmp.err = lines[-1]
3455 call map(lines, 'substitute(v:val, ".*\r", "", "")')
3457 let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
3459 let lines[0] = a:tmp.out . lines[0]
3461 let a:tmp.out = lines[-1]
3464 call writefile(lines, a:state.file, 'ba')
3465 if has_key(a:tmp, 'echo')
3466 if !exists('l:data')
3467 let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3469 let a:tmp.echo .= data
3471 let line_count = a:tmp.line_count
3472 let a:tmp.line_count += len(lines) - 1
3473 if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
3476 call remove(lines, -1)
3478 call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3479 if !line_count && len(lines) > 1000
3480 let first = remove(lines, 0, 999)
3481 call setbufline(a:state.capture_bufnr, 1, first)
3483 call setbufline(a:state.capture_bufnr, 1001, lines)
3485 call setbufline(a:state.capture_bufnr, line_count + 1, lines)
3487 call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3488 if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
3489 let winnr = bufwinnr(a:state.capture_bufnr)
3491 let old_winnr = winnr()
3492 exe 'noautocmd' winnr.'wincmd w'
3494 exe 'noautocmd' old_winnr.'wincmd w'
3501 function! s:RunExit(state, tmp, job, exit_status) abort
3502 let a:state.exit_status = a:exit_status
3503 if has_key(a:state, 'job')
3506 call s:RunFinished(a:state)
3509 function! s:RunClose(state, tmp, job, ...) abort
3511 call s:RunExit(a:state, a:tmp, a:job, a:1)
3513 let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
3514 call writefile([noeol], a:state.file, 'ba')
3515 call remove(a:state, 'job')
3516 if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
3518 call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3519 call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
3520 call setbufvar(a:state.capture_bufnr, '&eol', 0)
3521 call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3523 call setbufvar(a:state.capture_bufnr, '&modified', 0)
3524 call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
3525 if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
3526 call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
3529 if !has_key(a:state, 'exit_status')
3532 call s:RunFinished(a:state)
3535 function! s:RunSend(job, str) abort
3537 if type(a:job) == type(0)
3538 call chansend(a:job, a:str)
3540 call ch_sendraw(a:job, a:str)
3543 catch /^Vim\%((\a\+)\)\=:E90[06]:/
3548 function! s:RunCloseIn(job) abort
3550 if type(a:job) ==# type(0)
3551 call chanclose(a:job, 'stdin')
3553 call ch_close_in(a:job)
3556 catch /^Vim\%((\a\+)\)\=:E90[06]:/
3561 function! s:RunEcho(tmp) abort
3562 if !has_key(a:tmp, 'echo')
3565 let data = a:tmp.echo
3566 let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
3568 let data = strpart(data, 0, len(data) - len(a:tmp.echo))
3570 echon substitute(data, "\r\\ze\n", '', 'g')
3573 function! s:RunTick(job) abort
3574 if type(a:job) == v:t_number
3575 return jobwait([a:job], 1)[0] == -1
3576 elseif type(a:job) == 8
3577 let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
3583 if !exists('s:edit_jobs')
3584 let s:edit_jobs = {}
3586 function! s:RunWait(state, tmp, job, ...) abort
3587 if a:0 && filereadable(a:1)
3591 if a:tmp.no_more && &more
3595 while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
3596 call s:RunEcho(a:tmp)
3597 if !get(a:tmp, 'closed_in')
3598 let peek = getchar(1)
3599 if peek != 0 && !(has('win32') && peek == 128)
3601 let c = type(c) == type(0) ? nr2char(c) : c
3602 if c ==# "\<C-D>" || c ==# "\<Esc>"
3603 let a:tmp.closed_in = 1
3604 let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
3605 for winnr in range(1, winnr('$'))
3606 if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
3611 if has_key(a:tmp, 'echo')
3612 call remove(a:tmp, 'echo')
3614 call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
3615 exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
3616 let a:state.capture_bufnr = bufnr(a:state.file)
3617 call setbufvar(a:state.capture_bufnr, '&modified', 1)
3623 call s:RunSend(a:job, c)
3631 if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
3632 throw 'fugitive: close callback did not fire; this should never happen'
3634 call s:RunEcho(a:tmp)
3635 if has_key(a:tmp, 'echo')
3636 let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
3639 let finished = !s:RunEdit(a:state, a:tmp, a:job)
3644 if !exists('finished')
3646 if a:state.pty && !get(a:tmp, 'closed_in')
3647 call s:RunSend(a:job, "\<C-C>")
3648 elseif type(a:job) == type(0)
3651 call job_stop(a:job)
3656 call fugitive#DidChange(a:state)
3662 if !exists('s:resume_queue')
3663 let s:resume_queue = []
3665 function! fugitive#Resume() abort
3666 while len(s:resume_queue)
3667 let enqueued = remove(s:resume_queue, 0)
3668 if enqueued[2] isnot# ''
3670 call call('s:RunWait', enqueued)
3676 function! s:RunBufDelete(bufnr) abort
3677 let state = s:TempState(+a:bufnr)
3678 if has_key(state, 'job')
3680 if type(state.job) == type(0)
3681 call jobstop(state.job)
3683 call job_stop(state.job)
3688 if has_key(s:edit_jobs, a:bufnr) |
3689 call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
3690 call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
3691 \ ")|call fugitive#Resume()|checktime\r", 'n')
3695 augroup fugitive_job
3697 autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
3699 \ for s:jobbuf in keys(s:edit_jobs) |
3700 \ call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
3702 \ call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
3706 function! fugitive#CanPty() abort
3707 return get(g:, 'fugitive_pty_debug_override',
3708 \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
3711 function! fugitive#PagerFor(argv, ...) abort
3715 elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
3718 if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
3719 \ !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
3720 \ args[0] =~# '^\%(tag\|branch\)$' && (
3721 \ s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
3722 \ len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
3723 \ !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
3726 let config = a:0 ? a:1 : fugitive#Config()
3727 let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
3728 if value =~# '^\%(true\|yes\|on\|1\)$'
3730 elseif value =~# '^\%(false\|no|off\|0\|\)$'
3732 elseif type(value) == type('')
3734 elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
3735 \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
3736 \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
3737 \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
3744 let s:disable_colors = []
3745 for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
3746 call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
3749 function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
3750 exe s:VersionCheck()
3751 let dir = call('s:Dir', a:000)
3755 let config = copy(fugitive#Config(dir))
3756 let curwin = a:arg =~# '^++curwin\>' || !a:line2
3757 let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
3760 let explicit_pathspec_option = 0
3762 if args[0] ==# '-c' && len(args) > 1
3763 call extend(flags, remove(args, 0, 1))
3764 elseif args[0] =~# '^-p$\|^--paginate$'
3766 call remove(args, 0)
3767 elseif args[0] =~# '^-P$\|^--no-pager$'
3769 call remove(args, 0)
3770 elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
3771 let explicit_pathspec_option = 1
3772 call add(flags, remove(args, 0))
3773 elseif args[0] =~# '^\%(--no-optional-locks\)$'
3774 call add(flags, remove(args, 0))
3775 elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
3776 return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
3781 if !explicit_pathspec_option
3782 call insert(flags, '--no-literal-pathspecs')
3784 let no_pager = pager is# 0
3786 call add(flags, '--no-pager')
3790 while i < len(flags) - 1
3791 if flags[i] ==# '-c'
3793 let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
3794 if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
3795 let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
3798 let config[config_name] = [matchstr(flags[i], '=\zs.*')]
3800 let config[config_name] = [1]
3805 let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
3806 if empty(args) && pager is# -1
3807 let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
3808 return (empty(cmd) ? 'exe' : cmd) . after
3810 let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
3811 if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
3812 \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
3813 call remove(args, 0)
3814 call extend(args, split(alias, '\s\+'), 'keep')
3816 let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
3817 if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
3819 let overrides = s:{name}Subcommand(a:line1, a:line2, a:range, a:bang, a:mods, extend({'subcommand': args[0], 'subcommand_args': args[1:-1]}, options))
3820 if type(overrides) == type('')
3821 return 'exe ' . string(overrides) . after
3823 let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
3825 return 'echoerr ' . string(v:exception)
3830 call extend(env, get(overrides, 'env', {}))
3831 call s:PrepareEnv(env, dir)
3833 let pager = fugitive#PagerFor(args, config)
3835 let wants_terminal = type(pager) ==# type('') ||
3836 \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
3837 \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
3839 let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
3840 let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
3841 let argv = s:UserCommandList(options) + args
3842 let term_opts = len(env) ? {'env': env} : {}
3844 call fugitive#Autowrite()
3845 return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
3846 elseif exists('*term_start')
3847 call fugitive#Autowrite()
3849 let term_opts.curwin = 1
3851 return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
3855 \ 'git': options.git,
3858 \ 'git_dir': options.git_dir,
3859 \ 'cwd': s:UserCommandCwd(dir),
3860 \ 'filetype': 'git',
3861 \ 'mods': s:Mods(a:mods),
3862 \ 'file': s:Resolve(tempname())}
3866 if a:bang && pager isnot# 2
3867 let state.pager = pager
3869 let stream = exists('*setbufline')
3870 let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
3872 let allow_pty = get(args, 0, '') is# 'shortlog'
3873 if pager is# 2 && a:bang && a:line2 >= 0
3874 let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
3875 elseif pager is# 2 && a:bang
3876 let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'pedit'
3878 let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'split'
3880 let do_edit = s:Mods(a:mods) . 'edit'
3883 call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
3886 call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
3887 let state.pty = allow_pty && fugitive#CanPty()
3889 let args = s:AskPassArgs(dir) + args
3892 \ 'no_more': no_pager || get(overrides, 'no_more'),
3897 let env.FUGITIVE = state.file
3898 let editor = 'sh ' . s:TempScript(
3899 \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
3900 \ 'echo "$1" > "$FUGITIVE.edit"',
3901 \ 'printf "\033]51;fugitive:edit\007" >&2',
3902 \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
3906 \ 'GIT_EDITOR': editor,
3907 \ 'GIT_SEQUENCE_EDITOR': editor,
3908 \ 'GIT_PAGER': 'cat',
3909 \ 'PAGER': 'cat'}, 'keep')
3910 if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
3911 let env.GPG_TTY = ''
3912 let did_override_gpg_tty = 1
3915 call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
3917 call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
3918 let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
3920 let env.GIT_MERGE_AUTOEDIT = '1'
3923 let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
3924 let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
3925 let [argv, jobopts] = s:JobOpts(argv, env)
3926 call fugitive#Autowrite()
3927 call writefile([], state.file, 'b')
3928 call s:RunSave(state)
3929 if has_key(tmp, 'echo')
3932 if exists('*ch_close_in')
3933 call extend(jobopts, {
3935 \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
3936 \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
3937 \ 'close_cb': function('s:RunClose', [state, tmp]),
3938 \ 'exit_cb': function('s:RunExit', [state, tmp]),
3943 let job = job_start(argv, jobopts)
3945 let job = jobstart(argv, extend(jobopts, {
3948 \ 'stdout_buffered': pager,
3949 \ 'stderr_buffered': pager,
3950 \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
3951 \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
3952 \ 'on_exit': function('s:RunClose', [state, tmp]),
3957 let tmp.closed_in = 1
3958 call s:RunCloseIn(job)
3961 exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
3962 let state.capture_bufnr = bufnr(state.file)
3963 call setbufvar(state.capture_bufnr, '&modified', 1)
3964 return (after_edit . after)[1:-1]
3966 call add(s:resume_queue, [state, tmp, job])
3967 return 'call fugitive#Resume()|checktime' . after
3969 let pre = s:BuildEnvPrefix(env)
3971 if exists('+guioptions') && &guioptions =~# '!'
3972 let guioptions = &guioptions
3975 silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
3976 \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
3977 let state.exit_status = v:shell_error
3979 if exists('guioptions')
3980 let &guioptions = guioptions
3984 call s:RunSave(state)
3985 call s:RunFinished(state)
3986 return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
3987 \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
3989 return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
3990 elseif has('gui_running')
3991 return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
3993 if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
3994 call remove(options.flags, 0)
3996 if exists('l:did_override_gpg_tty')
3997 call remove(env, 'GPG_TTY')
3999 let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
4000 let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
4001 if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
4002 let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
4005 if &more && no_pager
4009 echo substitute(output, "\n$", "", "")
4016 return 'checktime' . after
4018 return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
4023 let s:exec_paths = {}
4024 function! s:ExecPath() abort
4025 let git = s:GitShellCmd()
4026 if !has_key(s:exec_paths, git)
4027 let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
4028 let s:exec_paths[git] = [path, FugitiveVimPath(path)]
4030 return s:exec_paths[git]
4033 function! s:VimExecPath() abort
4034 return s:ExecPath()[1]
4037 let s:subcommands_before_2_5 = [
4038 \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
4039 \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
4040 \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
4041 \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
4042 \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
4043 \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
4044 \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
4045 \ 'tag', 'whatchanged',
4047 let s:path_subcommands = {}
4048 function! s:CompletableSubcommands(dir) abort
4049 let c_exec_path = s:cpath(s:VimExecPath())
4050 if !has_key(s:path_subcommands, c_exec_path)
4051 if fugitive#GitVersion(2, 18)
4052 let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
4053 call filter(lines, 'v:val =~# "^\\S\\+$"')
4054 if !exec_error && len(lines)
4055 let s:path_subcommands[c_exec_path] = lines
4057 let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4058 \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
4061 let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4062 \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
4065 let commands = copy(s:path_subcommands[c_exec_path])
4066 for path in split($PATH, has('win32') ? ';' : ':')
4067 if path !~# '^/\|^\a:[\\/]'
4070 let cpath = s:cpath(path)
4071 if !has_key(s:path_subcommands, cpath)
4072 let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
4074 call extend(commands, s:path_subcommands[cpath])
4076 call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
4077 let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
4079 for command in configured
4080 if command =~# '^-.'
4081 let rejected[strpart(command, 1)] = 1
4084 call filter(configured, 'v:val !~# "^-"')
4085 let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
4087 return uniq(results)
4090 while i < len(results)
4091 if results[i] ==# results[i-1]
4092 call remove(results, i)
4101 function! fugitive#Complete(lead, ...) abort
4102 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
4103 let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
4104 let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
4105 let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
4106 if empty(subcmd) && a:lead =~# '^+'
4107 let results = ['++curwin']
4108 elseif empty(subcmd) && a:lead =~# '^-'
4109 let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
4110 elseif empty(subcmd)
4111 let results = s:CompletableSubcommands(dir)
4112 elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
4113 let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
4114 let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
4115 return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
4116 elseif pre =~# ' -- '
4117 return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
4118 elseif a:lead =~# '^-'
4119 let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
4121 return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
4123 return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
4126 function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
4127 let path = a:0 ? a:1 : getcwd()
4128 return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
4131 " Section: :Gcd, :Glcd
4133 function! fugitive#CdComplete(A, L, P) abort
4134 return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
4137 function! fugitive#Cd(path, ...) abort
4138 exe s:VersionCheck()
4139 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
4140 if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
4143 let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
4145 return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
4150 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
4151 let dir = a:0 ? s:Dir(a:1) : s:Dir()
4154 let mods = s:Mods(a:mods, 'Edge')
4155 let file = fugitive#Find(':', dir)
4156 let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
4157 \ s:fnameescape(file)
4158 for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
4159 let bufs = tabpagebuflist(tabnr)
4160 for winnr in range(1, tabpagewinnr(tabnr, '$'))
4161 if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
4162 if tabnr == tabpagenr() && winnr == winnr()
4163 call s:ReloadStatus()
4165 call s:ExpireStatus(dir)
4166 exe tabnr . 'tabnext'
4167 exe winnr . 'wincmd w'
4169 let w:fugitive_status = dir
4176 return mods . 'edit' . (a:bang ? '!' : '') . arg
4178 return mods . 'pedit' . arg . '|wincmd P'
4180 return mods . 'keepalt split' . arg
4183 return 'echoerr ' . string(v:exception)
4188 function! s:StageJump(offset, section, ...) abort
4189 let line = search('^\%(' . a:section . '\)', 'nw')
4191 let line = search('^\%(' . a:1 . '\)', 'nw')
4196 for i in range(a:offset)
4197 call search(s:file_commit_pattern . '\|^$', 'W')
4198 if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
4199 call search(s:file_commit_pattern . '\|^$', 'W')
4201 if empty(getline('.'))
4205 call s:StageReveal()
4207 call s:StageReveal()
4214 function! s:StageSeek(info, fallback) abort
4216 if empty(info.heading)
4219 let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
4221 for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
4222 let line = search('^' . section, 'wn')
4224 return line + (info.index > 0 ? 1 : 0)
4230 while len(getline(line))
4231 let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
4233 \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
4234 \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
4235 \ filename ==# info.filename)
4239 if getline(line+1) !~# '^@'
4240 exe s:StageInline('show', line)
4242 if getline(line+1) !~# '^@'
4245 let type = info.sigil ==# '-' ? '-' : '+'
4247 while offset < info.offset
4249 if getline(line) =~# '^@'
4250 let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
4251 elseif getline(line) =~# '^[ ' . type . ']'
4253 elseif getline(line) !~# '^[ @\+-]'
4260 let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
4261 if len(commit) && commit ==# info.commit
4267 let i += getline(line) !~# '^[ @\+-]'
4270 return exists('backup') ? backup : line - 1
4273 function! s:DoAutocmdChanged(dir) abort
4274 let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
4275 if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
4279 let g:fugitive_event = dir
4280 if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
4281 let g:fugitive_result = a:dir
4283 exe s:DoAutocmd('User FugitiveChanged')
4285 unlet! g:fugitive_event g:fugitive_result
4286 " Force statusline reload with the buffer's Git dir
4287 if dir isnot# FugitiveGitDir()
4294 function! s:ReloadStatusBuffer() abort
4295 if get(b:, 'fugitive_type', '') !=# 'index'
4298 let original_lnum = line('.')
4299 let info = s:StageInfo(original_lnum)
4300 exe fugitive#BufReadStatus(0)
4301 call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
4305 function! s:ReloadStatus() abort
4306 call s:ExpireStatus(-1)
4307 call s:ReloadStatusBuffer()
4308 exe s:DoAutocmdChanged(-1)
4312 let s:last_time = reltime()
4313 if !exists('s:last_times')
4314 let s:last_times = {}
4317 function! s:ExpireStatus(bufnr) abort
4318 if a:bufnr is# -2 || a:bufnr is# 0
4319 let s:head_cache = {}
4320 let s:last_time = reltime()
4323 let head_file = fugitive#Find('.git/HEAD', a:bufnr)
4324 if !empty(head_file)
4325 let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
4326 if has_key(s:head_cache, head_file)
4327 call remove(s:head_cache, head_file)
4333 function! s:ReloadWinStatus(...) abort
4334 if get(b:, 'fugitive_type', '') !=# 'index' || &modified
4337 if !exists('b:fugitive_reltime')
4338 exe call('s:ReloadStatusBuffer', a:000)
4341 let t = b:fugitive_reltime
4342 if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
4343 \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
4344 exe call('s:ReloadStatusBuffer', a:000)
4348 function! s:ReloadTabStatus() abort
4349 if !exists('g:fugitive_did_change_at')
4351 elseif exists('t:fugitive_reloaded_at')
4352 let time_ahead = reltime(g:fugitive_did_change_at, t:fugitive_reloaded_at)
4353 if reltimefloat(time_ahead) >= 0
4357 let t:fugitive_reloaded_at = reltime()
4359 while winnr <= winnr('$')
4360 if getbufvar(winbufnr(winnr), 'fugitive_type') ==# 'index'
4362 execute 'noautocmd' winnr.'wincmd w'
4363 let restorewinnr = 1
4366 call s:ReloadWinStatus()
4368 if exists('restorewinnr')
4378 function! fugitive#DidChange(...) abort
4379 call s:ExpireStatus(a:0 ? a:1 : -1)
4380 if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
4381 let g:fugitive_did_change_at = reltime()
4382 call s:ReloadTabStatus()
4384 call s:ReloadWinStatus()
4387 exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
4391 function! fugitive#ReloadStatus(...) abort
4392 return call('fugitive#DidChange', a:000)
4395 function! fugitive#EfmDir(...) abort
4396 let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
4397 let dir = substitute(dir, '%%', '%', 'g')
4398 let dir = substitute(dir, '\\\ze[\,]', '', 'g')
4402 augroup fugitive_status
4404 autocmd BufWritePost * call fugitive#DidChange(+expand('<abuf>'), 0)
4405 autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
4406 autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
4407 autocmd BufDelete * nested
4408 \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
4409 \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
4410 \ call fugitive#DidChange(+expand('<abuf>')) |
4412 \ call fugitive#DidChange(0) |
4415 autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
4416 \ call fugitive#DidChange(fugitive#EfmDir())
4417 autocmd FocusGained *
4418 \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
4419 \ call fugitive#DidChange(0) |
4421 autocmd BufEnter index,index.lock,fugitive://*//
4422 \ call s:ReloadWinStatus()
4424 \ call s:ReloadTabStatus()
4427 function! s:StatusSectionFile(heading, filename) abort
4428 return get(get(get(b:, 'fugitive_files', {}), a:heading, {}), a:filename, {})
4431 function! s:StageInfo(...) abort
4432 let lnum = a:0 ? a:1 : line('.')
4433 let sigil = matchstr(getline(lnum), '^[ @\+-]')
4436 let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
4437 let offset = sigil ==# '-' ? old_lnum : new_lnum
4438 while getline(lnum) =~# '^[ @\+-]'
4442 let slnum = lnum + 1
4445 while len(getline(slnum - 1)) && empty(heading)
4447 let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4448 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4452 let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
4453 let file = s:StatusSectionFile(heading, text)
4454 let relative = get(file, 'relative', len(text) ? [text] : [])
4455 return {'section': matchstr(heading, '^\u\l\+'),
4456 \ 'heading': heading,
4460 \ 'relative': copy(relative),
4461 \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
4462 \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
4463 \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
4464 \ 'submodule': get(file, 'submodule', ''),
4468 function! s:Selection(arg1, ...) abort
4470 let arg1 = line('.')
4472 elseif a:arg1 ==# 'v'
4473 let arg1 = line("'<")
4474 let arg2 = line("'>")
4477 let arg2 = a:0 ? a:1 : 0
4481 let last = first - arg2 - 1
4487 while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
4490 if first > last || &filetype !=# 'fugitive'
4494 while getline(flnum) =~# '^[ @\+-]'
4497 let slnum = flnum + 1
4500 while empty(heading)
4502 let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4503 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4509 \ 'heading': heading,
4510 \ 'section': matchstr(heading, '^\u\l\+'),
4518 let line = getline(flnum)
4519 let lnum = first - (arg1 == flnum ? 0 : 1)
4520 let root = s:Tree() . '/'
4522 let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
4524 let template.heading = heading
4525 let template.section = matchstr(heading, '^\u\l\+')
4526 let template.index = 0
4527 elseif line =~# '^[ @\+-]'
4528 let template.index -= 1
4529 if !results[-1].patch
4530 let results[-1].patch = lnum
4532 let results[-1].lnum = lnum
4533 elseif line =~# '^[A-Z?] '
4534 let text = matchstr(line, '^[A-Z?] \zs.*')
4535 let file = s:StatusSectionFile(template.heading, text)
4536 let relative = get(file, 'relative', len(text) ? [text] : [])
4537 call add(results, extend(deepcopy(template), {
4540 \ 'relative': copy(relative),
4541 \ 'paths': map(copy(relative), 'root . v:val'),
4542 \ 'status': matchstr(line, '^[A-Z?]'),
4544 elseif line =~# '^\x\x\x\+ '
4545 call add(results, extend({
4547 \ 'commit': matchstr(line, '^\x\x\x\+'),
4548 \ }, template, 'keep'))
4549 elseif line =~# '^\l\+ \x\x\x\+ '
4550 call add(results, extend({
4552 \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
4553 \ 'status': matchstr(line, '^\l\+'),
4554 \ }, template, 'keep'))
4557 let template.index += 1
4558 let line = getline(lnum)
4560 if len(results) && results[0].patch && arg2 == 0
4561 while getline(results[0].patch) =~# '^[ \+-]'
4562 let results[0].patch -= 1
4564 while getline(results[0].lnum + 1) =~# '^[ \+-]'
4565 let results[0].lnum += 1
4571 function! s:StageArgs(visual) abort
4574 for record in s:Selection(a:visual ? 'v' : 'n')
4575 if len(record.commit)
4576 call add(commits, record.commit)
4578 call extend(paths, record.paths)
4580 if s:cpath(s:Tree(), getcwd())
4581 call map(paths, 'fugitive#Path(v:val, "./")')
4583 return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
4586 function! s:Do(action, visual) abort
4587 let line = getline('.')
4589 if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
4590 let header = matchstr(line, '^\S\+\ze:')
4591 if len(header) && exists('*s:Do' . a:action . header . 'Header')
4592 let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
4594 let section = matchstr(line, '^\S\+')
4595 if exists('*s:Do' . a:action . section . 'Heading')
4596 let reload = s:Do{a:action}{section}Heading(line) > 0
4599 return reload ? s:ReloadStatus() : ''
4601 let selection = s:Selection(a:visual ? 'v' : 'n')
4605 call filter(selection, 'v:val.section ==# selection[0].section')
4609 for record in selection
4610 if exists('*s:Do' . a:action . record.section)
4611 let status = s:Do{a:action}{record.section}(record)
4618 let reload = reload || (status > 0)
4621 execute record.lnum + 1
4625 return 'echoerr ' . string(v:exception)
4628 execute s:ReloadStatus()
4630 if exists('success')
4631 call s:StageReveal()
4637 function! s:StageReveal() abort
4639 let begin = line('.')
4640 if getline(begin) =~# '^@'
4642 while getline(end) =~# '^[ \+-]'
4645 elseif getline(begin) =~# '^commit '
4647 while end < line('$') && getline(end + 1) !~# '^commit '
4650 elseif getline(begin) =~# s:section_pattern
4652 while len(getline(end + 1))
4657 while line('.') > line('w0') + &scrolloff && end > line('w$')
4658 execute "normal! \<C-E>"
4663 let s:file_pattern = '^[A-Z?] .\|^diff --'
4664 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
4665 let s:item_pattern = s:file_commit_pattern . '\|^@@'
4667 function! s:NextHunk(count) abort
4668 if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
4669 exe s:StageInline('show')
4671 for i in range(a:count)
4672 if &filetype ==# 'fugitive'
4673 call search(s:file_pattern . '\|^@', 'W')
4674 if getline('.') =~# s:file_pattern
4675 exe s:StageInline('show')
4676 if getline(line('.') + 1) =~# '^@'
4681 call search('^@@', 'W')
4684 call s:StageReveal()
4688 function! s:PreviousHunk(count) abort
4690 for i in range(a:count)
4691 if &filetype ==# 'fugitive'
4692 if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
4695 let lnum = search(s:file_pattern . '\|^@','Wbn')
4696 call s:StageInline('show', lnum)
4697 call search('^? .\|^@','Wb')
4699 call search('^@@', 'Wb')
4702 call s:StageReveal()
4706 function! s:NextFile(count) abort
4707 for i in range(a:count)
4708 exe s:StageInline('hide')
4709 if !search(s:file_pattern, 'W')
4713 exe s:StageInline('hide')
4717 function! s:PreviousFile(count) abort
4718 exe s:StageInline('hide')
4720 for i in range(a:count)
4721 if !search(s:file_pattern, 'Wb')
4724 exe s:StageInline('hide')
4729 function! s:NextItem(count) abort
4730 for i in range(a:count)
4731 if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
4732 call search('^commit ', 'W')
4735 call s:StageReveal()
4739 function! s:PreviousItem(count) abort
4741 for i in range(a:count)
4742 if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
4743 call search('^commit ', 'Wb')
4746 call s:StageReveal()
4750 let s:section_pattern = '^[A-Z][a-z][^:]*$'
4751 let s:section_commit_pattern = s:section_pattern . '\|^commit '
4753 function! s:NextSection(count) abort
4754 let orig = line('.')
4755 if getline('.') !~# '^commit '
4758 for i in range(a:count)
4759 if !search(s:section_commit_pattern, 'W')
4763 if getline('.') =~# s:section_commit_pattern
4764 call s:StageReveal()
4765 return getline('.') =~# s:section_pattern ? '+' : ':'
4771 function! s:PreviousSection(count) abort
4772 let orig = line('.')
4773 if getline('.') !~# '^commit '
4777 for i in range(a:count)
4778 if !search(s:section_commit_pattern . '\|\%^', 'bW')
4782 if getline('.') =~# s:section_commit_pattern || line('.') == 1
4783 call s:StageReveal()
4784 return getline('.') =~# s:section_pattern ? '+' : ':'
4790 function! s:NextSectionEnd(count) abort
4792 if empty(getline('.'))
4795 for i in range(a:count)
4796 if !search(s:section_commit_pattern, 'W')
4800 return search('^.', 'Wb')
4803 function! s:PreviousSectionEnd(count) abort
4805 for i in range(a:count)
4806 if search(s:section_commit_pattern, 'Wb') <= 1
4816 return search('^.', 'Wb')
4819 function! s:PatchSearchExpr(reverse) abort
4820 let line = getline('.')
4821 if col('.') ==# 1 && line =~# '^[+-]'
4822 if line =~# '^[+-]\{3\} '
4823 let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
4825 let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
4828 return '?' . escape(pattern, '/?') . "\<CR>"
4830 return '/' . escape(pattern, '/') . "\<CR>"
4833 return a:reverse ? '#' : '*'
4836 function! s:StageInlineGetDiff(diff_section, info) abort
4838 if a:info.status ==# 'U'
4839 let diff_header = 'diff --cc ' . s:Quote(a:info.relative[0])
4841 let diff_header = 'diff --git ' . s:Quote(a:info.relative[-1]) . ' ' . s:Quote(a:info.relative[0])
4843 let stdout = fugitive#Wait(a:diff_section).stdout
4844 let start = index(stdout, diff_header)
4848 let index = start + 1
4849 while get(stdout, index, '@@') !~# '^@@\|^diff '
4852 while get(stdout, index, '') =~# '^[@ \+-]'
4853 call add(diff, stdout[index])
4856 return [diff, start]
4859 function! s:StageInline(mode, ...) abort
4860 if &filetype !=# 'fugitive'
4863 let lnum1 = a:0 ? a:1 : line('.')
4864 let lnum = lnum1 + 1
4865 if a:0 > 1 && a:2 == 0 && lnum1 == 1
4866 let lnum = line('$') - 1
4867 elseif a:0 > 1 && a:2 == 0
4868 let info = s:StageInfo(lnum - 1)
4869 if empty(info.paths) && len(info.section)
4870 while len(getline(lnum))
4879 while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
4882 let info = s:StageInfo(lnum)
4883 let diff_section = get(get(b:, 'fugitive_diff', {}), info.section, {})
4884 if empty(diff_section)
4887 if getline(lnum + 1) =~# '^[ @\+-]'
4888 let lnum2 = lnum + 1
4889 while getline(lnum2 + 1) =~# '^[ @\+-]'
4892 if a:mode !=# 'show'
4893 setlocal modifiable noreadonly
4894 exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
4895 call remove(b:fugitive_expanded[info.section], info.filename)
4896 setlocal nomodifiable readonly nomodified
4900 if info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
4903 let [diff, start] = s:StageInlineGetDiff(diff_section, info)
4905 setlocal modifiable noreadonly
4906 silent call append(lnum, diff)
4907 let b:fugitive_expanded[info.section][info.filename] = [start]
4908 setlocal nomodifiable readonly nomodified
4909 if foldclosed(lnum+1) > 0
4910 silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
4917 function! s:NextExpandedHunk(count) abort
4918 for i in range(a:count)
4919 call s:StageInline('show', line('.'), 1)
4920 call search(s:file_pattern . '\|^@','W')
4925 function! s:StageDiff(diff) abort
4926 let lnum = line('.')
4927 let info = s:StageInfo(lnum)
4928 let prefix = info.offset > 0 ? '+' . info.offset : ''
4929 if info.submodule =~# '^S'
4930 if info.section ==# 'Staged'
4931 return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
4932 elseif info.submodule =~# '^SC'
4933 return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
4935 return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
4937 elseif empty(info.paths) && info.section ==# 'Staged'
4938 return 'Git --paginate diff --no-ext-diff --cached'
4939 elseif empty(info.paths)
4940 return 'Git --paginate diff --no-ext-diff'
4941 elseif len(info.paths) > 1
4942 execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
4943 return a:diff . '! @:'.s:fnameescape(info.paths[1])
4944 elseif info.section ==# 'Staged' && info.sigil ==# '-'
4945 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4946 return a:diff . '! :0:%'
4947 elseif info.section ==# 'Staged'
4948 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4949 return a:diff . '! @:%'
4950 elseif info.sigil ==# '-'
4951 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4952 return a:diff . '! :(top)%'
4954 execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
4959 function! s:StageDiffEdit() abort
4960 let info = s:StageInfo(line('.'))
4961 let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
4962 if info.section ==# 'Staged'
4963 return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
4964 elseif info.status ==# '?'
4965 call s:TreeChomp('add', '--intent-to-add', '--', arg)
4966 return s:ReloadStatus()
4968 return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
4972 function! s:StageApply(info, reverse, extra) abort
4973 if a:info.status ==# 'R'
4974 throw 'fugitive: patching renamed file not yet supported'
4976 let cmd = ['apply', '-p0', '--recount'] + a:extra
4978 let start = info.patch
4980 let lines = getline(start, end)
4981 if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
4984 while getline(end) =~# '^[-+\ ]'
4986 if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
4987 call add(lines, ' ' . getline(end)[1:-1])
4990 while start > 0 && getline(start) !~# '^@'
4992 if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
4993 call insert(lines, ' ' . getline(start)[1:-1])
4994 elseif getline(start) =~# '^@'
4995 call insert(lines, getline(start))
4999 throw 'fugitive: could not find hunk'
5000 elseif getline(start) !~# '^@@ '
5001 throw 'fugitive: cannot apply conflict hunk'
5003 let i = b:fugitive_expanded[info.section][info.filename][0]
5005 let diff_lines = fugitive#Wait(b:fugitive_diff[info.section]).stdout
5006 while get(diff_lines, i, '@') !~# '^@'
5007 let line = diff_lines[i]
5008 if line ==# '--- /dev/null'
5009 call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
5010 elseif line !~# '^new file '
5011 call add(head, line)
5015 call extend(lines, head, 'keep')
5016 let temp = tempname()
5017 call writefile(lines, temp)
5019 call add(cmd, '--reverse')
5021 call extend(cmd, ['--', temp])
5022 let output = s:ChompStderr(cmd)
5026 call s:throw(output)
5029 function! s:StageDelete(lnum1, lnum2, count) abort
5033 let did_conflict_err = 0
5034 let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
5036 for info in s:Selection(a:lnum1, a:lnum2)
5037 if empty(info.paths)
5039 let reset_commit = info.commit . '^'
5043 let sub = get(s:StatusSectionFile(info.section, info.filename), 'submodule', '')
5044 if sub =~# '^S' && info.status ==# 'M'
5045 let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
5047 let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
5049 elseif info.status ==# 'D'
5050 let undo = 'GRemove'
5051 elseif info.paths[0] =~# '/$'
5052 let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
5055 let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
5058 call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
5060 if info.section ==# 'Staged'
5061 call s:TreeChomp('reset', '--', info.paths[0])
5063 call s:TreeChomp('submodule', 'update', '--', info.paths[0])
5064 elseif info.status ==# '?'
5065 call s:TreeChomp('clean', '-f', '--', info.paths[0])
5067 if get(s:StatusSectionFile('Staged', info.filename), 'status', '') ==# 'D'
5068 call delete(info.paths[0])
5070 call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
5073 if get(s:StatusSectionFile('Unstaged', info.filename), 'status', '') ==# 'D'
5074 call delete(info.paths[0])
5076 call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
5078 elseif info.status =~# '[ADU]' &&
5079 \ get(s:StatusSectionFile(info.section ==# 'Staged' ? 'Unstaged' : 'Staged', info.filename), 'status', '') =~# '[AU]'
5080 if get(g:, 'fugitive_conflict_x', 0)
5081 call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
5083 if !did_conflict_err
5084 let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
5085 let did_conflict_err = 1
5089 elseif info.status ==# 'U'
5090 call delete(info.paths[0])
5091 elseif info.status ==# 'A'
5092 call s:TreeChomp('rm', '-f', '--', info.paths[0])
5093 elseif info.section ==# 'Unstaged'
5094 call s:TreeChomp('checkout', '--', info.paths[0])
5096 call s:TreeChomp('checkout', '@', '--', info.paths[0])
5099 call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
5103 let err .= '|echoerr ' . string(v:exception)
5106 if len(reset_commit) && empty(err)
5107 call feedkeys(':Git reset ' . reset_commit)
5111 exe s:ReloadStatus()
5112 call s:StageReveal()
5113 return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
5116 function! s:StageIgnore(lnum1, lnum2, count) abort
5118 for info in s:Selection(a:lnum1, a:lnum2)
5119 call extend(paths, info.relative)
5121 call map(paths, '"/" . v:val')
5123 let dir = fugitive#Find('.git/info/')
5124 if !isdirectory(dir)
5131 exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
5132 let last = line('$')
5133 if last == 1 && empty(getline(1))
5134 call setline(last, paths)
5136 call append(last, paths)
5142 function! s:DoToggleHeadHeader(value) abort
5143 exe 'edit' fnameescape(fugitive#Find('.git/'))
5144 call search('\C^index$', 'wc')
5147 function! s:DoToggleHelpHeader(value) abort
5148 exe 'help fugitive-map'
5151 function! s:DoStagePushHeader(value) abort
5152 let remote = matchstr(a:value, '\zs[^/]\+\ze/')
5156 let branch = matchstr(a:value, '\%([^/]\+/\)\=\zs\S\+')
5157 call feedkeys(':Git push ' . remote . ' ' . branch)
5160 function! s:DoTogglePushHeader(value) abort
5161 return s:DoStagePushHeader(a:value)
5164 function! s:DoStageUnpushedHeading(heading) abort
5165 let remote = matchstr(a:heading, 'to \zs[^/]\+\ze/')
5169 let branch = matchstr(a:heading, 'to \%([^/]\+/\)\=\zs\S\+')
5173 call feedkeys(':Git push ' . remote . ' ' . '@:' . 'refs/heads/' . branch)
5176 function! s:DoToggleUnpushedHeading(heading) abort
5177 return s:DoStageUnpushedHeading(a:heading)
5180 function! s:DoStageUnpushed(record) abort
5181 let remote = matchstr(a:record.heading, 'to \zs[^/]\+\ze/')
5185 let branch = matchstr(a:record.heading, 'to \%([^/]\+/\)\=\zs\S\+')
5189 call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . 'refs/heads/' . branch)
5192 function! s:DoToggleUnpushed(record) abort
5193 return s:DoStageUnpushed(a:record)
5196 function! s:DoUnstageUnpulledHeading(heading) abort
5197 call feedkeys(':Git rebase')
5200 function! s:DoToggleUnpulledHeading(heading) abort
5201 call s:DoUnstageUnpulledHeading(a:heading)
5204 function! s:DoUnstageUnpulled(record) abort
5205 call feedkeys(':Git rebase ' . a:record.commit)
5208 function! s:DoToggleUnpulled(record) abort
5209 call s:DoUnstageUnpulled(a:record)
5212 function! s:DoUnstageUnpushed(record) abort
5213 call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
5216 function! s:DoToggleStagedHeading(...) abort
5217 call s:TreeChomp('reset', '-q')
5221 function! s:DoUnstageStagedHeading(heading) abort
5222 return s:DoToggleStagedHeading(a:heading)
5225 function! s:DoToggleUnstagedHeading(...) abort
5226 call s:TreeChomp('add', '-u')
5230 function! s:DoStageUnstagedHeading(heading) abort
5231 return s:DoToggleUnstagedHeading(a:heading)
5234 function! s:DoToggleUntrackedHeading(...) abort
5235 call s:TreeChomp('add', '.')
5239 function! s:DoStageUntrackedHeading(heading) abort
5240 return s:DoToggleUntrackedHeading(a:heading)
5243 function! s:DoToggleStaged(record) abort
5245 return s:StageApply(a:record, 1, ['--cached'])
5247 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5252 function! s:DoUnstageStaged(record) abort
5253 return s:DoToggleStaged(a:record)
5256 function! s:DoToggleUnstaged(record) abort
5258 return s:StageApply(a:record, 0, ['--cached'])
5260 call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
5265 function! s:DoStageUnstaged(record) abort
5266 return s:DoToggleUnstaged(a:record)
5269 function! s:DoUnstageUnstaged(record) abort
5270 if a:record.status ==# 'A'
5271 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5278 function! s:DoToggleUntracked(record) abort
5279 call s:TreeChomp(['add', '--'] + a:record.paths)
5283 function! s:DoStageUntracked(record) abort
5284 return s:DoToggleUntracked(a:record)
5287 function! s:StagePatch(lnum1,lnum2) abort
5292 for lnum in range(a:lnum1,a:lnum2)
5293 let info = s:StageInfo(lnum)
5294 if empty(info.paths) && info.section ==# 'Staged'
5295 execute 'tab Git reset --patch'
5297 elseif empty(info.paths) && info.section ==# 'Unstaged'
5298 execute 'tab Git add --patch'
5300 elseif empty(info.paths) && info.section ==# 'Untracked'
5301 execute 'tab Git add --interactive'
5303 elseif empty(info.paths)
5307 if info.section ==# 'Staged'
5308 let reset += info.relative
5309 elseif info.section ==# 'Untracked'
5310 let intend += info.paths
5311 elseif info.status !~# '^D'
5312 let add += info.relative
5317 call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
5320 execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
5323 execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
5326 return 'echoerr ' . string(v:exception)
5328 return s:ReloadStatus()
5331 " Section: :Git commit, :Git revert
5333 function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
5334 let status = s:StatusCommand(a:line1, a:line2, a:range, get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, a:bang, a:mods, '', '', [], a:options)
5335 let status = len(status) ? status . '|' : ''
5337 return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
5339 return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
5343 function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
5344 let argv = copy(a:options.subcommand_args)
5346 while get(argv, i, '--') !=# '--'
5347 if argv[i] =~# '^-[apzsneiovq].'
5348 call insert(argv, argv[i][0:1])
5349 let argv[i+1] = '-' . argv[i+1][2:-1]
5354 if s:HasOpt(argv, '-i', '--interactive')
5355 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
5356 elseif s:HasOpt(argv, '-p', '--patch')
5357 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
5363 function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
5364 return {'insert_args': ['--edit']}
5367 function! fugitive#CommitComplete(A, L, P, ...) abort
5368 let dir = a:0 ? a:1 : s:Dir()
5369 if a:A =~# '^--fixup=\|^--squash='
5370 let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
5371 let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
5373 call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
5374 call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
5377 return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
5380 return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
5385 function! fugitive#RevertComplete(A, L, P, ...) abort
5386 return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5389 " Section: :Git merge, :Git rebase, :Git pull
5391 function! fugitive#MergeComplete(A, L, P, ...) abort
5392 return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5395 function! fugitive#RebaseComplete(A, L, P, ...) abort
5396 return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5399 function! fugitive#PullComplete(A, L, P, ...) abort
5400 return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
5403 function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
5404 if empty(a:options.subcommand_args) && (
5405 \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
5406 \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
5407 \ !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
5408 return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
5413 function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
5414 let args = a:options.subcommand_args
5415 if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
5416 return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
5421 " Section: :Git bisect
5423 function! s:CompleteBisect(A, L, P, ...) abort
5424 let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
5425 if empty(bisect_subcmd)
5426 let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
5427 return s:FilterEscape(subcmds, a:A)
5429 let dir = a:0 ? a:1 : s:Dir()
5430 return fugitive#CompleteObject(a:A, dir)
5433 function! fugitive#BisectComplete(A, L, P, ...) abort
5434 return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
5437 " Section: :Git difftool, :Git mergetool
5439 function! s:ToolItems(state, from, to, offsets, text, ...) abort
5441 for i in range(len(a:state.diff))
5442 let diff = a:state.diff[i]
5443 let path = (i == len(a:state.diff) - 1) ? a:to : a:from
5448 \ 'valid': a:0 ? a:1 : 1,
5449 \ 'filename': diff.filename . s:VimSlash(path),
5450 \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
5452 if len(get(diff, 'module', ''))
5453 let item.module = diff.module . path
5455 call add(items, item)
5457 let items[-1].context = {'diff': items[0:-2]}
5461 function! s:ToolToFrom(str) abort
5463 let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
5464 return [substitute(str, '{.* => \(.*\)}', '\1', ''),
5465 \ substitute(str, '{\(.*\) => .*}', '\1', '')]
5467 return [a:str, a:str]
5471 function! s:ToolParse(state, line) abort
5472 if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
5474 elseif a:line =~# '^diff '
5475 let a:state.mode = 'diffhead'
5476 let a:state.from = ''
5478 elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
5479 let a:state.from = a:line[4:-1]
5480 let a:state.to = a:state.from
5481 elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
5482 let a:state.to = a:line[4:-1]
5483 if empty(get(a:state, 'from', ''))
5484 let a:state.from = a:state.to
5486 elseif a:line[0] ==# '@'
5487 let a:state.mode = 'hunk'
5488 if has_key(a:state, 'from')
5489 let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
5490 return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
5492 elseif a:line =~# '^\* Unmerged path .'
5493 let file = a:line[16:-1]
5494 return s:ToolItems(a:state, file, file, [], '')
5495 elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
5496 " --raw, --name-status
5497 let [status; files] = split(a:line, "\t")
5498 return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
5499 elseif a:line =~# '^ \S.* |'
5501 let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
5502 let [to, from] = s:ToolToFrom(to)
5503 return s:ToolItems(a:state, from, to, [], changes)
5504 elseif a:line =~# '^ *\([0-9.]\+%\) .'
5506 let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
5507 return s:ToolItems(a:state, to, to, [], changes)
5508 elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
5510 let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
5511 let [to, from] = s:ToolToFrom(to)
5512 return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
5513 elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
5514 return [{'text': a:line}]
5519 function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
5521 let argv = copy(a:args)
5525 let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5526 if len(match) && len(match[2])
5527 call insert(argv, match[1])
5528 let argv[i+1] = '-' . match[2]
5532 if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
5534 elseif arg =~# '^-y$\|^--no-prompt$'
5536 call remove(argv, i)
5538 elseif arg ==# '--prompt'
5540 call remove(argv, i)
5542 elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
5543 call remove(argv, i)
5550 call fugitive#Autowrite()
5551 let a:state.mode = 'init'
5552 let a:state.from = ''
5554 let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
5555 let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
5557 let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
5558 return s:QuickfixStream(get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2, 'difftool', title, exec, !a:bang, a:mods, s:function('s:ToolParse'), a:state)
5562 let tabnr = tabpagenr() + 1
5563 for line in s:SystemList(exec)[0]
5564 for item in s:ToolParse(a:state, line)
5565 if len(get(item, 'filename', '')) && item.filename != filename
5566 call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
5567 for i in reverse(range(len(get(item.context, 'diff', []))))
5568 call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
5570 call add(cmd, 'wincmd =')
5571 let filename = item.filename
5575 return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
5579 function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
5580 let dir = a:options.git_dir
5584 let cmd = ['diff', '--diff-filter=U']
5585 let state = {'name_only': 0}
5586 let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
5587 call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5588 return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
5591 function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
5592 let dir = s:Dir(a:options)
5595 let argv = copy(a:options.subcommand_args)
5600 let state = {'name_only': 0}
5601 let merge_base_against = {}
5602 let dash = (index(argv, '--') > i ? ['--'] : [])
5604 let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5605 if len(match) && len(match[2])
5606 call insert(argv, match[1])
5607 let argv[i+1] = '-' . match[2]
5611 if arg ==# '--cached'
5615 elseif arg ==# '--name-only'
5616 let state.name_only = 1
5617 let argv[0] = '--name-status'
5620 elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
5621 let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
5622 call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
5623 let merge_base_against = {}
5624 if arg =~# '\.\.\.' && len(parsed) > 2
5625 let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
5626 if len(display) == 2
5627 let parsed[0].module = display[1] . ':'
5628 let parsed[1].module = display[0] . ':'
5630 let parsed[2].module = arg . ':'
5632 let merge_base_against = parsed[0]
5633 let parsed = [parsed[2]]
5635 elseif arg =~# '\.\.' && len(parsed) == 2
5636 let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
5637 if len(display) == 2
5638 let parsed[0].module = display[0] . ':'
5639 let parsed[1].module = display[1] . ':'
5641 elseif len(parsed) == 1
5642 let parsed[0].module = arg . ':'
5644 call extend(commits, parsed)
5648 if len(merge_base_against)
5649 call add(commits, merge_base_against)
5651 let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
5654 call add(commits, {'prefix': '@:', 'module': '@:'})
5656 call add(commits, {'prefix': ':0:', 'module': ':0:'})
5657 elseif len(commits) < 2
5658 call add(commits, {'prefix': ':(top)'})
5660 call insert(commits, {'prefix': ':0:', 'module': ':0:'})
5664 let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
5665 call reverse(commits)
5668 call add(commits, remove(commits, 0))
5670 call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5671 let state.diff = commits
5672 return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
5675 " Section: :Ggrep, :Glog
5677 if !exists('g:fugitive_summary_format')
5678 let g:fugitive_summary_format = '%s'
5681 function! fugitive#GrepComplete(A, L, P) abort
5682 return s:CompleteSub('grep', a:A, a:L, a:P)
5685 function! fugitive#LogComplete(A, L, P) abort
5686 return s:CompleteSub('log', a:A, a:L, a:P)
5689 function! s:GrepParseLine(options, quiet, dir, line) abort
5693 let entry = {'valid': 1}
5694 let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
5695 if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
5696 return {'text': a:line}
5698 let entry.module = match[1]
5699 let entry.lnum = +match[2]
5700 let entry.col = +match[3]
5701 let entry.text = match[4]
5703 let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
5704 if len(entry.module)
5705 let entry.text = 'Binary file'
5709 if empty(entry.module) && !a:options.line_number
5710 let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
5712 let entry.module = match[1]
5713 let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
5716 if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
5717 let entry.text = matchstr(a:line, '\d\+$')
5718 let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
5720 if empty(entry.module) && a:options.name_only
5721 let entry.module = a:line
5723 if empty(entry.module)
5724 return {'text': a:line}
5726 if entry.module !~# ':'
5727 let entry.filename = s:PathJoin(a:options.prefix, entry.module)
5729 let entry.filename = fugitive#Find(matchstr(entry.module, '^[^:]*:') .
5730 \ substitute(matchstr(entry.module, ':\zs.*'), '/\=:', '/', 'g'), a:dir)
5735 let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
5736 function! s:GrepOptions(args, dir) abort
5737 let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
5738 let tree = s:Tree(a:dir)
5739 let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
5740 \ s:VimSlash(tree . '/')
5741 let options.prefix = prefix
5746 if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
5747 let options.name_count = 1
5749 if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
5750 let options.line_number = 1
5751 elseif arg =~# '^\%(--no-line-number\)$'
5752 let options.line_number = 0
5754 if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
5755 let options.name_only = 1
5757 if arg ==# '--cached'
5758 let options.prefix = fugitive#Find(':0:', a:dir)
5759 elseif arg ==# '--no-cached'
5760 let options.prefix = prefix
5766 function! s:GrepCfile(result) abort
5767 let options = s:GrepOptions(a:result.args, a:result)
5768 let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
5769 if get(entry, 'col')
5770 return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
5771 elseif has_key(entry, 'lnum')
5772 return [entry.filename, entry.lnum]
5773 elseif has_key(entry, 'pattern')
5774 return [entry.filename, '', 'silent /' . entry.pattern]
5775 elseif has_key(entry, 'filename')
5776 return [entry.filename]
5782 function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
5783 let args = copy(a:options.subcommand_args)
5787 while i < len(args) && args[i] !=# '--'
5788 let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
5789 if len(partition) > 1
5790 call insert(args, '-' . strpart(args[i], len(partition)), i+1)
5791 let args[i] = partition
5792 elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
5794 elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
5796 call remove(args, i)
5798 elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
5800 elseif args[i] =~# '^-[qz].'
5801 let args[i] = '-' . args[i][2:-1]
5803 elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
5805 call remove(args, i)
5807 elseif args[i] =~# '^--no-quiet$'
5809 elseif args[i] =~# '^\%(--heading\)$'
5810 call remove(args, i)
5815 if handle < 0 ? !quiet : !handle
5818 call fugitive#Autowrite()
5819 let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
5820 if s:HasOpt(args, '--no-line-number')
5823 let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
5825 let cmd = ['grep', '--no-color', '--full-name'] + lc
5826 let dir = s:Dir(a:options)
5827 let options = s:GrepOptions(lc + args, dir)
5829 exe listnr 'wincmd w'
5833 let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
5834 call s:QuickfixCreate(listnr, {'title': title})
5835 let tempfile = tempname()
5837 \ 'git': a:options.git,
5838 \ 'flags': a:options.flags,
5839 \ 'args': cmd + args,
5840 \ 'git_dir': s:GitDir(a:options),
5841 \ 'cwd': s:UserCommandCwd(a:options),
5842 \ 'filetype': 'git',
5843 \ 'mods': s:Mods(a:mods),
5844 \ 'file': s:Resolve(tempfile)}
5845 let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
5846 exe s:DoAutocmd('QuickFixCmdPre ' . event)
5855 let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
5856 call writefile(list + [''], tempfile, 'b')
5857 call s:RunSave(state)
5858 call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
5859 call s:QuickfixSet(listnr, list, 'a')
5860 let press_enter_shortfall = &cmdheight - len(list)
5861 if press_enter_shortfall > 0 && !quiet
5862 echo repeat("\n", press_enter_shortfall - 1)
5869 call s:RunFinished(state)
5870 exe s:DoAutocmd('QuickFixCmdPost ' . event)
5872 let bufnr = bufnr('')
5873 exe s:QuickfixOpen(listnr, a:mods)
5874 if bufnr != bufnr('') && !a:bang
5878 if !a:bang && !empty(list)
5879 return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
5885 function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
5886 return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
5887 \ "grep -O " . a:arg)
5890 let s:log_diff_context = '{"filename": fugitive#Find(v:val . from, a:dir), "lnum": get(offsets, v:key), "module": strpart(v:val, 0, len(a:state.base_module)) . from}'
5892 function! s:LogFlushQueue(state, dir) abort
5893 let queue = remove(a:state, 'queue')
5894 if a:state.child_found && get(a:state, 'ignore_commit')
5895 call remove(queue, 0)
5896 elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
5897 let from = substitute(a:state.target, '^/', ':', '')
5899 let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
5901 if len(queue) && queue[-1] ==# {'text': ''}
5902 call remove(queue, -1)
5907 function! s:LogParse(state, dir, prefix, line) abort
5908 if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
5911 let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
5913 let queue = s:LogFlushQueue(a:state, a:dir)
5914 let a:state.mode = 'commit'
5915 let a:state.base = a:prefix . list[2]
5917 let [a:state.base_module; a:state.parents] = split(list[1], ' ')
5919 let a:state.base_module = list[2]
5920 let a:state.parents = []
5922 let a:state.message = list[3]
5923 let a:state.from = ''
5926 let a:state.queue = [{
5928 \ 'context': context,
5929 \ 'filename': s:PathJoin(a:state.base, a:state.target),
5930 \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
5931 \ 'text': a:state.message}]
5932 let a:state.child_found = 0
5934 elseif type(a:line) == type(0)
5935 return s:LogFlushQueue(a:state, a:dir)
5936 elseif a:line =~# '^diff'
5937 let a:state.mode = 'diffhead'
5938 let a:state.from = ''
5940 elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
5941 let a:state.from = a:line[6:-1]
5942 let a:state.to = a:state.from
5943 elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
5944 let a:state.to = a:line[6:-1]
5945 if empty(get(a:state, 'from', ''))
5946 let a:state.from = a:state.to
5948 elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
5949 let a:state.mode = 'hunk'
5950 if empty(a:state.target) || a:state.target ==# '/' . a:state.to
5951 if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
5952 call remove(a:state.queue, -1)
5954 let a:state.child_found = 1
5955 let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
5957 if len(a:state.parents)
5958 let from = ":" . a:state.from
5959 let context.diff = map(copy(a:state.parents), s:log_diff_context)
5961 call add(a:state.queue, {
5963 \ 'context': context,
5964 \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
5965 \ 'module': a:state.base_module . ':' . a:state.to,
5966 \ 'lnum': offsets[-1],
5967 \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
5969 elseif a:state.follow &&
5970 \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
5971 let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
5973 let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
5974 if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
5975 let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
5978 if !get(a:state, 'ignore_summary')
5979 call add(a:state.queue, {'text': a:line})
5981 elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
5982 call add(a:state.queue, {'text': a:line})
5987 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
5988 exe s:VersionCheck()
5991 let listnr = a:type =~# '^l' ? 0 : -1
5992 let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
5993 call remove(args, 0)
5994 let split = index(args, '--')
5996 let paths = args[split : -1]
5997 let args = args[0 : split - 1]
6004 if a:line1 == 0 && a:count
6005 let path = fugitive#Path(bufname(a:count), '/', dir)
6006 let titlepre = ':0,' . a:count
6008 let path = fugitive#Path(@%, '/', dir)
6009 let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
6016 let extra_paths = []
6017 let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
6018 if path =~# '^/\.git\%(/\|$\)\|^$'
6021 let range = "0," . (a:count ? a:count : bufnr(''))
6022 let extra_paths = ['.' . path]
6023 if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
6024 let state.follow = 1
6025 if !s:HasOpt(args, '--follow')
6026 call insert(extra_args, '--follow')
6028 if !s:HasOpt(args, '--summary')
6029 call insert(extra_args, '--summary')
6030 let state.ignore_summary = 1
6033 let state.ignore_commit = 1
6035 if !s:HasOpt(args, '--merges', '--no-merges')
6036 call insert(extra_args, '--no-merges')
6038 call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
6039 let state.ignore_commit = 1
6041 if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
6042 let owner = s:Owner(@%, dir)
6044 call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
6047 if empty(extra_paths)
6050 if s:HasOpt(args, '-g', '--walk-reflogs')
6051 let format = "%gd %P\t%H %gs"
6053 let format = "%h %P\t%H " . g:fugitive_summary_format
6055 let cmd = ['--no-pager']
6056 call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
6057 \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
6058 \ args + extra_args + paths + extra_paths)
6059 let state.target = path
6060 let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
6061 return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
6064 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
6066 function! s:UsableWin(nr) abort
6067 return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
6068 \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
6069 \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
6070 \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
6073 function! s:ArgSplit(string) abort
6074 let string = a:string
6076 while string =~# '\S'
6077 let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
6078 let string = strpart(string, len(arg))
6079 let arg = substitute(arg, '^\s\+', '', '')
6080 call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
6085 function! s:PlusEscape(string) abort
6086 return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
6089 function! s:OpenParse(string, wants_cmd, wants_multiple) abort
6092 let args = s:ArgSplit(a:string)
6094 if args[0] =~# '^++'
6095 call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
6096 elseif a:wants_cmd && args[0] ==# '+'
6097 call remove(args, 0)
6099 elseif a:wants_cmd && args[0] =~# '^+'
6100 call add(cmds, remove(args, 0)[1:-1])
6105 if !a:wants_multiple && empty(args)
6109 let wants_cmd = a:wants_cmd
6112 let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
6114 call insert(cmds, lnum)
6120 let pre = join(opts, '')
6122 let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
6124 let pre .= ' +' . s:PlusEscape(cmds[0])
6126 return [a:wants_multiple ? urls : urls[0], pre]
6129 function! s:OpenExpand(dir, file, wants_cmd) abort
6131 let result = fugitive#Result()
6132 if has_key(result, 'file')
6133 let efile = result.file
6135 throw 'fugitive: no previous command output'
6138 let efile = s:Expand(a:file)
6140 if efile =~# '^https\=://'
6141 let [url, lnum] = s:ResolveUrl(efile, a:dir)
6142 return [url, a:wants_cmd ? lnum : 0]
6144 let url = s:Generate(efile, a:dir)
6145 if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
6146 let line = line('.')
6147 if s:Slash(expand('%:p')) !=# s:Slash(url)
6148 let diffcmd = 'diff'
6149 let from = s:DirRev(@%)[1]
6150 let to = s:DirRev(url)[1]
6151 if empty(from) && empty(to)
6152 let diffcmd = 'diff-files'
6153 let args = ['--', expand('%:p'), url]
6155 let args = [from, '--', url]
6157 let args = [to, '--', expand('%:p')]
6160 let args = [from, to]
6162 let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
6164 call filter(res, 'v:val =~# "^@@ "')
6165 call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
6166 call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
6167 if exists('reverse')
6168 call map(res, 'v:val[2:3] + v:val[0:1]')
6170 call filter(res, 'v:val[0] < '.line('.'))
6171 let hunk = get(res, -1, [0,0,0,0])
6172 if hunk[0] + hunk[1] > line('.')
6173 let line = hunk[2] + max([1 - hunk[3], 0])
6175 let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
6184 function! fugitive#DiffClose() abort
6185 let mywinnr = winnr()
6186 for winnr in [winnr('#')] + range(winnr('$'),1,-1)
6187 if winnr != mywinnr && getwinvar(winnr,'&diff')
6188 execute winnr.'wincmd w'
6198 function! s:BlurStatus() abort
6199 if (&previewwindow || exists('w:fugitive_status')) && get(b:,'fugitive_type', '') ==# 'index'
6200 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
6202 exe winnrs[0].'wincmd w'
6204 belowright new +setl\ bufhidden=delete
6207 call fugitive#DiffClose()
6212 let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
6213 function! fugitive#Open(cmd, bang, mods, arg, ...) abort
6214 exe s:VersionCheck()
6216 return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
6220 let [file, pre] = s:OpenParse(a:arg, 1, 0)
6222 return 'echoerr ' . string(v:exception)
6224 let mods = s:Mods(a:mods)
6228 return mods . a:cmd . pre . ' ' . s:fnameescape(file)
6231 function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
6232 exe s:VersionCheck()
6234 let mods = s:Mods(a:mods)
6236 let [files, pre] = s:OpenParse(a:arg, 1, 1)
6238 return 'echoerr ' . string(v:exception)
6244 return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
6247 function! s:ReadPrepare(line1, count, range, mods) abort
6248 let mods = s:Mods(a:mods)
6251 let delete = 'silent 1,' . line('$') . 'delete_|'
6252 let after = line('$')
6254 let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
6259 let pre = after . 'foldopen!|'
6263 return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
6266 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
6267 exe s:VersionCheck()
6268 let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
6270 let [file, pre] = s:OpenParse(a:arg, 0, 0)
6272 return 'echoerr ' . string(v:exception)
6274 if file =~# '^fugitive:' && a:count is# 0
6275 return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
6277 return read . ' ' . pre . ' ' . s:fnameescape(file) . post
6280 function! fugitive#EditComplete(A, L, P) abort
6282 return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
6284 return fugitive#CompleteObject(a:A, a:L, a:P)
6288 function! fugitive#ReadComplete(A, L, P) abort
6289 return fugitive#EditComplete(a:A, a:L, a:P)
6292 " Section: :Gwrite, :Gwq
6294 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
6295 exe s:VersionCheck()
6296 if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
6297 return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
6298 elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
6300 elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
6301 return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
6303 let mytab = tabpagenr()
6304 let mybufnr = bufnr('')
6305 let args = s:ArgSplit(a:arg)
6307 if get(args, 0) =~# '^+'
6308 let after = '|' . remove(args, 0)[1:-1]
6311 let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
6313 return 'echoerr ' . string(v:exception)
6316 return 'echoerr '.string('fugitive: cannot determine file path')
6318 if file =~# '^fugitive:'
6319 return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
6322 let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
6323 if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
6324 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
6325 return 'echoerr v:errmsg'
6328 for nr in range(1,bufnr('$'))
6329 if fnamemodify(bufname(nr),':p') ==# file
6334 if treebufnr > 0 && treebufnr != bufnr('')
6335 let temp = tempname()
6336 silent execute 'keepalt %write '.temp
6337 for tab in [mytab] + range(1,tabpagenr('$'))
6338 for winnr in range(1,tabpagewinnr(tab,'$'))
6339 if tabpagebuflist(tab)[winnr-1] == treebufnr
6340 execute 'tabnext '.tab
6342 execute winnr.'wincmd w'
6343 let restorewinnr = 1
6346 let lnum = line('.')
6347 let last = line('$')
6348 silent execute '$read '.temp
6349 silent execute '1,'.last.'delete_'
6355 if exists('restorewinnr')
6358 execute 'tabnext '.mytab
6365 call writefile(readfile(temp,'b'),file,'b')
6368 execute 'write! '.s:fnameescape(file)
6371 let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
6373 let v:errmsg = 'fugitive: '.message
6374 return 'echoerr v:errmsg'
6376 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
6380 let one = fugitive#Find(':1:'.file)
6381 let two = fugitive#Find(':2:'.file)
6382 let three = fugitive#Find(':3:'.file)
6383 for nr in range(1,bufnr('$'))
6384 let name = fnamemodify(bufname(nr), ':p')
6385 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
6386 execute nr.'bdelete'
6391 let zero = fugitive#Find(':0:'.file)
6392 exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
6393 for tab in range(1,tabpagenr('$'))
6394 for winnr in range(1,tabpagewinnr(tab,'$'))
6395 let bufnr = tabpagebuflist(tab)[winnr-1]
6396 let bufname = fnamemodify(bufname(bufnr), ':p')
6397 if bufname ==# zero && bufnr != mybufnr
6398 execute 'tabnext '.tab
6400 execute winnr.'wincmd w'
6401 let restorewinnr = 1
6404 let lnum = line('.')
6405 let last = line('$')
6406 silent execute '$read '.s:fnameescape(file)
6407 silent execute '1,'.last.'delete_'
6412 if exists('restorewinnr')
6415 execute 'tabnext '.mytab
6421 call fugitive#DidChange()
6422 return 'checktime' . after
6425 function! fugitive#WqCommand(...) abort
6426 let bang = a:4 ? '!' : ''
6427 if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
6430 let result = call('fugitive#WriteCommand', a:000)
6431 if result =~# '^\%(write\|wq\|echoerr\)'
6432 return s:sub(result,'^write','wq')
6434 return result.'|quit'.bang
6438 " Section: :Git push, :Git fetch
6440 function! s:CompletePush(A, L, P, ...) abort
6441 let dir = a:0 ? a:1 : s:Dir()
6442 let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
6444 let matches = s:LinesError([dir, 'remote'])[0]
6446 let lead = matchstr(a:A, '^[^:]*:')
6447 let matches = s:LinesError([dir, 'ls-remote', remote])[0]
6448 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
6449 call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
6451 let matches = s:CompleteHeads(dir)
6452 if a:A =~# '^[\''"]\=+'
6453 call map(matches, '"+" . v:val')
6456 return s:FilterEscape(matches, a:A)
6459 function! fugitive#PushComplete(A, L, P, ...) abort
6460 return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
6463 function! fugitive#FetchComplete(A, L, P, ...) abort
6464 return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
6467 function! s:PushSubcommand(...) abort
6468 return {'no_more': 1}
6471 function! s:FetchSubcommand(...) abort
6472 return {'no_more': 1}
6477 augroup fugitive_diff
6479 autocmd BufWinLeave * nested
6480 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
6481 \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
6483 autocmd BufWinEnter * nested
6484 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
6485 \ call s:diffoff() |
6489 function! s:can_diffoff(buf) abort
6490 return getwinvar(bufwinnr(a:buf), '&diff') &&
6491 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
6494 function! fugitive#CanDiffoff(buf) abort
6495 return s:can_diffoff(bufnr(a:buf))
6498 function! s:DiffModifier(count, default) abort
6499 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
6500 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
6502 elseif &diffopt =~# 'vertical'
6504 elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
6506 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
6513 function! s:diff_window_count() abort
6515 for nr in range(1,winnr('$'))
6516 let c += getwinvar(nr,'&diff')
6521 function! s:diffthis() abort
6523 let w:fugitive_diff_restore = 1
6528 function! s:diffoff() abort
6529 unlet! w:fugitive_diff_restore
6533 function! s:diffoff_all(dir) abort
6534 let curwin = winnr()
6535 for nr in range(1,winnr('$'))
6536 if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
6537 call setwinvar(nr, 'fugitive_diff_restore', '')
6540 if curwin != winnr()
6541 execute curwin.'wincmd w'
6546 function! s:IsConflicted() abort
6547 return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
6550 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
6551 exe s:VersionCheck()
6552 let args = s:ArgSplit(a:arg)
6554 let autodir = a:autodir
6555 while get(args, 0, '') =~# '^++'
6556 if args[0] =~? '^++novertical$'
6559 return 'echoerr ' . string('fugitive: unknown option ' . args[0])
6561 call remove(args, 0)
6563 if get(args, 0) =~# '^+'
6564 let post = remove(args, 0)[1:-1]
6566 if exists(':DiffGitCached') && empty(args)
6567 return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
6569 let commit = s:DirCommitFile(@%)[1]
6570 if a:mods =~# '\<\d*tab\>'
6571 let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
6572 let pre = matchstr(a:mods, '\<\d*tab\>') . 'edit'
6574 let mods = 'keepalt ' . a:mods
6577 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
6578 if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
6580 if commit =~# '^1\=$' && s:IsConflicted()
6581 let parents = [s:Relative(':2:'), s:Relative(':3:')]
6582 elseif empty(commit)
6583 let parents = [s:Relative(':0:')]
6584 elseif commit =~# '^\d\=$'
6585 let parents = [s:Relative('@:')]
6586 elseif commit =~# '^\x\x\+$'
6587 let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
6588 call map(parents, 's:Relative(v:val . ":")')
6592 if exists('parents') && len(parents) > 1
6594 let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
6596 if len(parents) > 1 && !&equalalways
6600 execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
6601 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6605 call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
6606 let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
6607 for i in range(len(parents)-1, 1, -1)
6608 execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
6609 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6613 call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
6618 let arg = join(args, ' ')
6623 let file = s:Relative()
6626 let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
6627 elseif arg =~# '^:\d$'
6629 let file = s:Relative(arg . ':')
6630 elseif arg =~# '^[~^]\d*$'
6631 return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
6634 let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
6636 return 'echoerr ' . string(v:exception)
6639 if a:keepfocus || arg =~# '^>'
6640 let mods = s:Mods(a:mods, 'leftabove')
6642 let mods = s:Mods(a:mods)
6644 elseif exists('parents')
6645 let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
6646 let mods = s:Mods(a:mods, 'leftabove')
6648 let file = s:Relative()
6649 let mods = s:Mods(a:mods, 'rightbelow')
6650 elseif s:IsConflicted()
6651 let file = s:Relative(':1:')
6652 let mods = s:Mods(a:mods, 'leftabove')
6653 if get(g:, 'fugitive_legacy_commands', 1)
6654 let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
6658 let file = s:Relative(':0:')
6659 let mods = s:Mods(a:mods, 'leftabove')
6661 let spec = s:Generate(file)
6662 if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
6663 let spec = s:VimSlash(spec . s:Relative('/'))
6666 let w:fugitive_diff_restore = 1
6667 let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
6668 if &diffopt =~# 'vertical'
6669 let diffopt = &diffopt
6670 set diffopt-=vertical
6672 execute mods 'diffsplit' s:fnameescape(spec)
6673 let w:fugitive_diff_restore = 1
6675 if getwinvar('#', '&diff')
6682 return 'echoerr ' . string(v:exception)
6684 if exists('l:equalalways')
6685 let &g:equalalways = equalalways
6687 if exists('diffopt')
6688 let &diffopt = diffopt
6693 " Section: :GMove, :GRemove
6695 function! s:Move(force, rename, destination) abort
6696 exe s:VersionCheck()
6699 if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
6700 return 'echoerr ' . string('fugitive: mv not supported for this buffer')
6703 let default_root = expand('%:p:s?[\/]$??:h') . '/'
6705 let default_root = s:Tree(dir) . '/'
6707 if a:destination =~# '^:/:\='
6708 let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
6709 elseif a:destination =~# '^:(top)'
6710 let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
6711 if destination !~# '^/\|^\a\+:'
6712 let destination = s:Tree(dir) . '/' . destination
6714 let destination = s:Tree(dir) .
6715 elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
6716 let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
6717 elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
6718 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
6719 elseif a:destination =~# '^:(literal)'
6720 let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
6722 let destination = s:Expand(a:destination)
6723 if destination =~# '^\.\.\=\%(/\|$\)'
6724 let destination = simplify(getcwd() . '/' . destination)
6725 elseif destination !~# '^\a\+:\|^/'
6726 let destination = default_root . destination
6729 let destination = s:Slash(destination)
6733 let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
6734 if exec.exit_status && exec.stderr !=# ['']
6735 return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
6737 if isdirectory(destination)
6738 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
6740 let reload = '|call fugitive#DidChange(' . string(exec) . ')'
6741 if empty(s:DirCommitFile(@%)[1])
6742 if isdirectory(destination)
6743 return 'keepalt edit '.s:fnameescape(destination) . reload
6745 return 'keepalt saveas! '.s:fnameescape(destination) . reload
6748 return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
6752 function! fugitive#RenameComplete(A,L,P) abort
6753 if a:A =~# '^[.:]\=/'
6754 return fugitive#CompletePath(a:A)
6756 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
6757 return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
6761 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6762 return s:Move(a:bang, 0, a:arg)
6765 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
6766 return s:Move(a:bang, 1, a:arg)
6769 function! s:Remove(after, force) abort
6770 exe s:VersionCheck()
6773 if len(@%) && s:DirCommitFile(@%)[1] ==# ''
6775 elseif s:DirCommitFile(@%)[1] ==# '0'
6776 let cmd = ['rm','--cached']
6778 return 'echoerr ' . string('fugitive: rm not supported for this buffer')
6781 let cmd += ['--force']
6783 let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
6785 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
6786 return 'echoerr '.string(v:errmsg)
6788 return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
6792 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6793 return s:Remove('edit', a:bang)
6796 function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
6797 return s:Remove('edit', a:bang)
6800 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
6801 return s:Remove('bdelete', a:bang)
6804 " Section: :Git blame
6806 function! s:Keywordprg() abort
6807 let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
6808 if has('gui_running') && !has('win32')
6809 return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
6811 return s:GitShellCmd() . args . ' show'
6815 function! s:linechars(pattern) abort
6816 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
6817 if &conceallevel > 1
6818 for col in range(1, chars)
6819 let chars -= synconcealed(line('.'), col)[0]
6825 function! s:BlameBufnr(...) abort
6826 let state = s:TempState(a:0 ? a:1 : bufnr(''))
6827 if get(state, 'filetype', '') ==# 'fugitiveblame'
6828 return get(state, 'origin_bufnr', -1)
6834 function! s:BlameCommitFileLnum(...) abort
6835 let line = a:0 ? a:1 : getline('.')
6836 let state = a:0 > 1 ? a:2 : s:TempState()
6837 if get(state, 'filetype', '') !=# 'fugitiveblame'
6840 let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
6841 if commit =~# '^0\+$'
6843 elseif has_key(state, 'blame_reverse_end')
6844 let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
6846 let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
6847 let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
6848 if empty(path) && lnum
6849 let path = get(state, 'blame_file', '')
6851 return [commit, path, lnum]
6854 function! s:BlameLeave() abort
6855 let state = s:TempState()
6856 let bufwinnr = exists('*win_id2win') ? win_id2win(get(state, 'origin_winid')) : 0
6858 let bufwinnr = bufwinnr(get(state, 'origin_bufnr', -1))
6860 if get(state, 'filetype', '') ==# 'fugitiveblame' && bufwinnr > 0
6861 let bufnr = bufnr('')
6862 exe bufwinnr . 'wincmd w'
6863 return bufnr . 'bdelete'
6868 function! s:BlameQuit() abort
6869 let cmd = s:BlameLeave()
6872 elseif len(s:DirCommitFile(@%)[1])
6873 return cmd . '|Gedit'
6879 function! fugitive#BlameComplete(A, L, P) abort
6880 return s:CompleteSub('blame', a:A, a:L, a:P)
6883 function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
6884 let dir = s:Dir(a:options)
6886 let flags = copy(a:options.subcommand_args)
6892 if a:line1 > 0 && a:count > 0 && a:range != 1
6893 call extend(ranges, ['-L', a:line1 . ',' . a:count])
6895 while i < len(flags)
6896 let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
6897 if len(match) && len(match[2])
6898 call insert(flags, match[1])
6899 let flags[i+1] = '-' . match[2]
6903 if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
6905 elseif arg ==# '--contents' && i + 1 < len(flags)
6906 call extend(commits, remove(flags, i, i+1))
6908 elseif arg ==# '-L' && i + 1 < len(flags)
6909 call extend(ranges, remove(flags, i, i+1))
6911 elseif arg =~# '^--contents='
6912 call add(commits, remove(flags, i))
6914 elseif arg =~# '^-L.'
6915 call add(ranges, remove(flags, i))
6917 elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
6921 echo s:ChompStderr([dir, 'blame', arg])
6926 if i + 1 < len(flags)
6927 call extend(files, remove(flags, i + 1, -1))
6929 call remove(flags, i)
6931 elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
6932 if index(flags, '--') >= 0
6933 call add(commits, remove(flags, i))
6936 if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
6937 call add(commits, remove(flags, i))
6941 let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
6942 if len(dcf[1]) && empty(dcf[2])
6943 call add(commits, remove(flags, i))
6948 call add(files, remove(flags, i))
6953 let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
6954 if empty(commits) && len(files) > 1
6955 call add(commits, remove(files, 1))
6959 let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
6960 call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
6961 if a:count > 0 && empty(ranges)
6962 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
6964 call extend(cmd, ranges)
6965 let tempname = tempname()
6966 let temp = tempname . (raw ? '' : '.fugitiveblame')
6969 elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
6970 let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
6971 elseif empty(files) && !s:HasOpt(flags, '--reverse')
6972 if &modified || !empty(s:DirCommitFile(@%)[1])
6973 let cmd += ['--contents', tempname . '.in']
6974 silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
6977 exe 'checktime ' . bufnr('')
6980 call fugitive#Autowrite()
6982 let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
6983 let [err, exec_error] = s:StdoutToFile(temp, basecmd)
6984 if exists('delete_in')
6985 call delete(tempname . '.in')
6990 let lines = split(err, "\n")
6992 let lines = readfile(temp)
6994 for i in range(len(lines))
6995 if lines[i] =~# '^error: \|^fatal: '
7003 if i != len(lines) - 1
7010 \ 'git': a:options.git,
7011 \ 'flags': a:options.flags,
7012 \ 'args': [a:options.subcommand] + a:options.subcommand_args,
7013 \ 'git_dir': s:GitDir(a:options),
7014 \ 'cwd': s:UserCommandCwd(a:options),
7015 \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
7016 \ 'blame_options': a:options,
7017 \ 'blame_flags': flags,
7018 \ 'blame_file': file}
7019 if s:HasOpt(flags, '--reverse')
7020 let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
7022 if a:line1 == 0 && a:count == 1
7023 if get(a:options, 'curwin')
7030 return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
7031 elseif (a:line1 == 0 || a:range == 1) && a:count > 0
7032 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
7033 return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
7035 let temp = s:Resolve(temp)
7036 let temp_state.file = temp
7037 call s:RunSave(temp_state)
7038 if len(ranges + commits + files) || raw
7039 let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
7040 let mods = s:Mods(a:mods)
7042 exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
7043 elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
7044 exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
7046 return mods . 'edit ' . s:fnameescape(temp) . reload
7048 return reload[1 : -1]
7050 let tabmod = matchstr(a:mods, '\<\d*tab\>')
7051 let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
7053 silent execute tabmod . 'edit %'
7055 let temp_state.origin_bufnr = bufnr('')
7056 if exists('*win_getid')
7057 let temp_state.origin_winid = win_getid()
7060 for winnr in range(winnr('$'),1,-1)
7061 if getwinvar(winnr, '&scrollbind')
7063 call setwinvar(winnr, '&scrollbind', 0)
7064 elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
7065 call setwinvar(winnr, '&foldenable', 0)
7066 call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
7069 let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
7070 if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == temp_state.origin_bufnr : win_blame_bufnr > 0
7071 execute winbufnr(winnr).'bdelete'
7074 let restore_winnr = get(temp_state, 'origin_winid', 'bufwinnr(' . temp_state.origin_bufnr . ')')
7076 call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
7079 call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
7082 call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
7084 setlocal scrollbind nowrap nofoldenable
7085 let top = line('w0') + &scrolloff
7086 let current = line('.')
7087 exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
7088 let w:fugitive_leave = join(restore, '|')
7092 setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
7093 if exists('+relativenumber')
7094 setlocal norelativenumber
7096 if exists('+signcolumn')
7097 setlocal signcolumn=no
7099 execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
7102 exe s:DoAutocmdChanged(temp_state)
7107 return 'echoerr ' . string(v:exception)
7111 function! s:BlameCommit(cmd, ...) abort
7112 let line = a:0 ? a:1 : getline('.')
7113 let state = a:0 ? a:2 : s:TempState()
7114 let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
7115 let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
7116 let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
7117 if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
7118 let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
7119 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7121 if commit =~# '^0*$'
7122 return 'echoerr ' . string('fugitive: no commit')
7124 if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
7125 let path = commit . ':' . path
7126 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7128 let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
7129 if cmd =~# '^echoerr'
7133 if a:cmd ==# 'pedit' || empty(path)
7136 if search('^diff .* b/\M'.escape(path,'\').'$','W')
7138 let head = line('.')
7139 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
7140 let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
7141 let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
7142 if lnum >= top && lnum <= top + len
7143 let offset = lnum - top
7151 while offset > 0 && line('.') < line('$')
7153 if getline('.') =~# '^[ ' . sigil . ']'
7166 function! s:BlameJump(suffix, ...) abort
7167 let suffix = a:suffix
7168 let [commit, path, lnum] = s:BlameCommitFileLnum()
7170 return 'echoerr ' . string('fugitive: could not determine filename for blame')
7172 if commit =~# '^0*$'
7176 let offset = line('.') - line('w0')
7177 let state = s:TempState()
7178 let flags = get(state, 'blame_flags', [])
7179 let blame_bufnr = s:BlameBufnr()
7181 let bufnr = bufnr('')
7182 let winnr = bufwinnr(blame_bufnr)
7184 exe winnr.'wincmd w'
7187 execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
7190 let my_bufnr = bufnr('')
7192 let blame_args = flags + [commit . suffix, '--', path]
7193 let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7195 let blame_args = flags
7196 let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7198 if bufnr('') == my_bufnr
7203 let delta = line('.') - line('w0') - offset
7205 execute 'normal! '.delta."\<C-E>"
7207 execute 'normal! '.(-delta)."\<C-Y>"
7211 echo ':Git blame' s:fnameescape(blame_args)
7215 let s:hash_colors = {}
7217 function! fugitive#BlameSyntax() abort
7218 let conceal = has('conceal') ? ' conceal' : ''
7219 let flags = get(s:TempState(), 'blame_flags', [])
7220 syn spell notoplevel
7221 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
7222 syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7223 if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
7224 syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7226 syn match FugitiveblameBoundary "^\^"
7228 syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
7229 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
7230 syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
7231 exec 'syn match FugitiveblameLineNumber "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
7232 exec 'syn match FugitiveblameOriginalFile "\s\%(\f\+\D\@<=\|\D\@=\f\+\)\%(\%(\s\+\d\+\)\=\s\%((\|\s*\d\+)\)\)\@=" contained nextgroup=FugitiveblameOriginalLineNumber,FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-name', '-f') ? '' : conceal)
7233 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7234 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7235 syn match FugitiveblameShort " \+\d\+)" contained contains=FugitiveblameLineNumber
7236 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
7237 hi def link FugitiveblameBoundary Keyword
7238 hi def link FugitiveblameHash Identifier
7239 hi def link FugitiveblameBoundaryIgnore Ignore
7240 hi def link FugitiveblameUncommitted Ignore
7241 hi def link FugitiveblameScoreDebug Debug
7242 hi def link FugitiveblameTime PreProc
7243 hi def link FugitiveblameLineNumber Number
7244 hi def link FugitiveblameOriginalFile String
7245 hi def link FugitiveblameOriginalLineNumber Float
7246 hi def link FugitiveblameShort FugitiveblameDelimiter
7247 hi def link FugitiveblameDelimiter Delimiter
7248 hi def link FugitiveblameNotCommittedYet Comment
7249 if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
7253 for x in split('01234567890abcdef', '\zs')
7254 exe 'syn match FugitiveblameHashGroup'.x '"\%(^\^\=[*?]*\)\@<='.x.'\x\{5,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
7256 for lnum in range(1, line('$'))
7257 let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
7258 let hash = orig_hash
7259 let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
7260 let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
7261 if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
7266 let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
7267 let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
7273 let s:hash_colors[hash] = ' ctermfg='.color
7275 let s:hash_colors[hash] = ''
7277 let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*'
7278 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=[*?]*\)\@<='.pattern.'" contained containedin=FugitiveblameHashGroup' . orig_hash[0]
7280 syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7281 call s:BlameRehighlight()
7284 function! s:BlameRehighlight() abort
7285 for [hash, cterm] in items(s:hash_colors)
7286 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
7287 exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
7289 exe 'hi link FugitiveblameHash'.hash.' Identifier'
7294 function! s:BlameMaps(is_ftplugin) abort
7295 let ft = a:is_ftplugin
7296 call s:MapGitOps(ft)
7297 call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
7298 call s:Map('n', 'g?', ':help :Git_blame<CR>', '<silent>', ft)
7299 call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
7300 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7301 call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7302 call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7303 call s:Map('n', 's', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7304 call s:Map('n', 'u', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7305 call s:Map('n', 'P', ':<C-U>exe <SID>BlameJump("^".v:count1)<CR>', '<silent>', ft)
7306 call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
7307 call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7308 call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
7309 call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
7310 call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
7311 exe s:Map('n', '.', ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
7312 exe s:Map('n', '(', "-", '', ft)
7313 exe s:Map('n', ')', "+", '', ft)
7314 call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
7315 call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
7316 call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
7319 function! fugitive#BlameFileType() abort
7321 setlocal foldmethod=manual
7323 let &l:keywordprg = s:Keywordprg()
7325 let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
7326 if exists('+concealcursor')
7327 setlocal concealcursor=nc conceallevel=2
7328 let b:undo_ftplugin .= ' concealcursor< conceallevel<'
7336 function! s:BlameCursorSync(bufnr, line) abort
7337 if a:line == line('.')
7340 if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
7344 let pos = getpos('.')
7346 call setpos('.', pos)
7351 augroup fugitive_blame
7353 autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
7354 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
7355 autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
7356 autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
7361 function! s:BrowserOpen(url, mods, echo_copy) abort
7362 let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
7363 let url = main . tr(query, ' ', '+') . anchor
7364 let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
7365 let mods = s:Mods(a:mods)
7370 return 'echo '.string(url)
7371 elseif exists(':Browse') == 2
7372 return 'echo '.string(url).'|' . mods . 'Browse '.url
7373 elseif exists(':OpenBrowser') == 2
7374 return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
7376 if !exists('g:loaded_netrw')
7377 runtime! autoload/netrw.vim
7379 if exists('*netrw#BrowseX')
7380 return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
7381 elseif exists('*netrw#NetrwBrowseX')
7382 return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
7384 return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
7389 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
7390 exe s:VersionCheck()
7394 if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
7395 let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
7396 let arg = matchstr(arg, '\s\zs\S.*')
7398 let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
7402 let result = fugitive#Result()
7403 if filereadable(get(result, 'file', ''))
7404 let rev = s:fnameescape(result.file)
7406 return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
7408 elseif !exists('l:remote')
7409 let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
7410 let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
7414 let expanded = s:Expand(rev)
7415 if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
7416 return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
7418 if !exists('l:result')
7419 let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
7421 if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
7422 for line in readfile(result.file, '', 4096)
7423 let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
7425 return s:BrowserOpen(rev, a:mods, a:bang)
7428 return 'echoerr ' . string('fugitive: no URL found in output of :Git')
7430 if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(dir)
7431 let config = fugitive#Config(dir)
7432 if !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
7433 let remote = expanded
7438 let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
7439 let expanded = s:DirRev(bufname)[1]
7441 let expanded = fugitive#Path(bufname, ':(top)', dir)
7443 if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
7444 let blame = s:BlameCommitFileLnum(getline(a:count))
7446 let expanded = blame[0]
7450 let full = s:Generate(expanded, dir)
7453 let forbid_ref_as_commit = 0
7454 if full =~# '^fugitive:'
7455 let [dir, commit, path] = s:DirCommitFile(full)
7456 if commit =~# '^\d\=$'
7458 let type = path =~# '^/\=$' ? 'tree' : 'blob'
7460 let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
7461 let ref = get(ref_match, 1, '')
7462 let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
7463 if empty(path) && !forbid_ref_as_commit
7466 let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
7467 \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
7470 let path = path[1:-1]
7471 elseif !empty(s:Tree(dir))
7472 let relevant_dir = FugitiveExtractGitDir(full)
7473 if !empty(relevant_dir)
7474 let dir = relevant_dir
7476 let path = fugitive#Path(full, '/', dir)[1:-1]
7477 if empty(path) || isdirectory(full)
7483 let path = '.git/' . full[strlen(dir)+1:-1]
7487 if path =~# '^\.git/'
7488 let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
7489 let type = empty(ref) ? 'root': 'ref'
7492 if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
7493 let ref = fugitive#Head(-1, dir)
7495 if ref =~# '^\x\{40,\}$'
7497 elseif !empty(ref) && ref !~# '^refs/'
7498 let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
7504 if !exists('l:config') || s:Dir(config) !=# dir
7505 let config = fugitive#Config(dir)
7508 if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
7509 let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
7510 let ref = 'refs/heads/' . merge
7511 elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
7512 let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
7513 let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
7514 let ref = 'refs/heads/' . merge
7515 elseif ref =~# '^refs/heads/[^/]'
7516 let merge = strpart(ref, 11)
7517 let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
7518 let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
7519 if r ==# '.' && !empty(m)
7520 let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
7523 let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
7530 let remote_ref = 'refs/remotes/' . remote . '/' . merge
7531 if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
7532 \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
7534 let ref = 'refs/heads/' . merge
7539 if empty(remote) || remote ==# '.'
7540 let remote = s:RemoteDefault(config)
7542 if empty(merge) || empty(remote)
7543 let provider_ref = ref
7545 let provider_ref = 'refs/remotes/' . remote . '/' . merge
7547 if forbid_ref_as_commit || a:count >= 0
7552 elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
7553 let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7556 let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
7557 let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
7558 if empty(commit) && type =~# '^\%(tree\|blob\)$'
7560 let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7561 elseif len(provider_ref)
7562 let owner = s:Owner(@%, dir)
7563 let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
7564 if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
7565 let blame_list = tempname()
7566 call writefile([commit, ''], blame_list, 'b')
7567 let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
7568 if !&l:modified || has_key(result, 'origin_bufnr')
7569 let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
7571 let blame_in = tempname()
7572 silent exe 'noautocmd keepalt %write' blame_in
7573 let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
7574 call delete(blame_in)
7576 call delete(blame_list)
7578 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
7579 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
7580 let line1 = +matchstr(blame[0], blame_regex)
7581 let line2 = +matchstr(blame[-1], blame_regex)
7583 throw "fugitive: can't browse to unpushed change"
7589 let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
7594 let remote_url = remote
7596 let remote_url = fugitive#RemoteUrl(remote, config)
7598 let raw = empty(remote_url) ? remote : remote_url
7599 let git_dir = s:GitDir(dir)
7602 \ 'git_dir': git_dir,
7603 \ 'repo': {'git_dir': git_dir},
7605 \ 'remote_name': remote,
7606 \ 'commit': s:UrlEncode(commit),
7607 \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
7613 if type ==# 'ref' && ref =~# '^refs/'
7614 let opts.path = '.git/' . s:UrlEncode(ref)
7616 elseif type ==# 'root'
7617 let opts.path ='.git/index'
7620 elseif type ==# 'tree' && !empty(path)
7621 let opts.path = s:sub(opts.path, '/\=$', '/')
7624 for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7625 let l:.url = call(Handler, [copy(opts)])
7626 if type(url) == type('') && url =~# '://'
7627 return s:BrowserOpen(url, a:mods, a:bang)
7631 if !empty(remote_url)
7632 return 'echoerr ' . string("fugitive: no GBrowse handler installed for '".remote_url."'")
7634 return 'echoerr ' . string("fugitive: could not find remote named '".remote."'")
7637 return 'echoerr ' . string(v:exception)
7641 function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort
7642 let ref_path = substitute(a:ref_path, ':', '/', '')
7644 if ref_path =~# '^\x\{40,\}\%(/\|$\)'
7645 let rev = substitute(ref_path, '/', ':', '')
7646 elseif ref_path =~# '^[^:/^~]\+'
7647 let first_component = matchstr(ref_path, '^[^:/^~]\+')
7648 let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2]
7650 let full = matchstr(line, "\t\\zs.*")
7651 for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')]
7652 if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/'
7653 let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '')
7661 let commitish = matchstr(rev, '^[^:^~]*')
7662 let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7663 if rev_parse.exit_status
7664 if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status
7667 let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7669 if rev_parse.exit_status
7672 return rev_parse.stdout[0] . matchstr(rev, ':.*')
7675 function! fugitive#ResolveUrl(target, ...) abort
7676 let repo = call('s:Dir', a:000)
7677 let origins = get(g:, 'fugitive_url_origins', {})
7678 let prefix = substitute(s:Slash(a:target), '#.*', '', '')
7679 while prefix =~# '://'
7680 let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, '')))
7681 if !empty(extracted)
7682 let repo = s:Dir(extracted)
7685 let prefix = matchstr(prefix, '.*\ze/')
7687 let git_dir = s:GitDir(repo)
7688 for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo))
7689 let remote_url = fugitive#RemoteUrl(remote_name, repo)
7690 for [no_anchor; variant] in [[1, 'commit'], [1, 'tree'], [1, 'tree', 1], [1, 'blob', 1], [0, 'blob', 1, '1`line1`', '1`line1`'], [0, 'blob', 1, '1`line1`', '2`line2`']]
7691 let handler_opts = {
7692 \ 'git_dir': git_dir,
7693 \ 'repo': {'git_dir': git_dir},
7694 \ 'remote': remote_url,
7695 \ 'remote_name': remote_name,
7696 \ 'commit': '1`commit`',
7697 \ 'type': get(variant, 0),
7698 \ 'path': get(variant, 1) ? '1`path`' : '',
7699 \ 'line1': get(variant, 2),
7700 \ 'line2': get(variant, 3)}
7702 for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7703 let l:.url = call(Handler, [copy(handler_opts)])
7704 if type(url) == type('') && url =~# '://'
7708 if type(url) != type('') || url !~# '://'
7711 let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`')
7712 let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g')
7713 let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$'
7714 let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target)
7715 let values = matchlist(s:Slash(a:target), pattern)[1:-1]
7720 for i in range(len(keys))
7721 let kvs[keys[i]] = values[i]
7723 if has_key(kvs, 'commit') && has_key(kvs, 'path')
7724 let ref_path = kvs.commit . '/' . kvs.path
7725 elseif has_key(kvs, 'commit') && variant[0] ==# 'tree'
7726 let ref_path = kvs.commit . '/'
7727 elseif has_key(kvs, 'commit')
7728 let ref_path = kvs.commit
7732 let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path))
7733 return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')]
7739 function! s:ResolveUrl(target, ...) abort
7741 let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000)
7747 return [substitute(a:target, '#.*', '', ''), 0]
7752 let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
7754 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
7755 function! fugitive#MapCfile(...) abort
7756 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
7757 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
7758 if !exists('g:fugitive_no_maps')
7759 call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
7760 call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7761 call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7762 call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
7763 call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
7767 function! s:ContainingCommit() abort
7768 let commit = s:Owner(@%)
7769 return empty(commit) ? '@' : commit
7772 function! s:SquashArgument(...) abort
7773 if &filetype == 'fugitive'
7774 let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
7775 elseif has_key(s:temp_files, s:cpath(expand('%:p')))
7776 let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\S\@!')
7778 let commit = s:Owner(@%)
7780 return len(commit) && a:0 ? printf(a:1, commit) : commit
7783 function! s:RebaseArgument() abort
7784 return s:SquashArgument(' %s^')
7787 function! s:NavigateUp(count) abort
7788 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
7792 let rev = matchstr(rev, '.*\ze/.\+', '')
7793 elseif rev =~# '.:.'
7794 let rev = matchstr(rev, '^.[^:]*:')
7807 function! s:ParseDiffHeader(str) abort
7808 let list = matchlist(a:str, '\Cdiff --git \("\=\w/.*\|/dev/null\) \("\=\w/.*\|/dev/null\)$')
7810 let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
7812 return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
7815 function! s:HunkPosition(lnum) abort
7816 let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
7817 let offsets = {' ': -1, '+': 0, '-': 0}
7818 let sigil = getline(lnum)[0]
7819 let line_char = sigil
7820 while has_key(offsets, line_char)
7821 let offsets[line_char] += 1
7823 let line_char = getline(lnum)[0]
7825 let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\)\%(,\d\+\)\= +\(\d\+\)[ ,]')
7830 \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
7831 \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
7834 function! s:MapMotion(lhs, rhs) abort
7836 \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7837 \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7838 \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
7839 call filter(maps, '!empty(v:val)')
7840 return join(maps, '|')
7843 function! s:MapGitOps(is_ftplugin) abort
7844 let ft = a:is_ftplugin
7848 exe s:Map('n', 'c<Space>', ':Git commit<Space>', '', ft)
7849 exe s:Map('n', 'c<CR>', ':Git commit<CR>', '', ft)
7850 exe s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>', '', ft)
7851 exe s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>', '', ft)
7852 exe s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>', ft)
7853 exe s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>', ft)
7854 exe s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>', ft)
7855 exe s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>', ft)
7856 exe s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>', ft)
7857 exe s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>', ft)
7858 exe s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>', ft)
7859 exe s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>', ft)
7860 exe s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>', ft)
7861 exe s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7862 exe s:Map('n', 'cF', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7863 exe s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7864 exe s:Map('n', 'cS', ':<C-U><Bar>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><Home>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7865 exe s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7866 exe s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>', ft)
7868 exe s:Map('n', 'cr<Space>', ':Git revert<Space>', '', ft)
7869 exe s:Map('n', 'cr<CR>', ':Git revert<CR>', '', ft)
7870 exe s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7871 exe s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7872 exe s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>', ft)
7874 exe s:Map('n', 'cm<Space>', ':Git merge<Space>', '', ft)
7875 exe s:Map('n', 'cm<CR>', ':Git merge<CR>', '', ft)
7876 exe s:Map('n', 'cmt', ':Git mergetool', '', ft)
7877 exe s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>', ft)
7879 exe s:Map('n', 'cz<Space>', ':Git stash<Space>', '', ft)
7880 exe s:Map('n', 'cz<CR>', ':Git stash<CR>', '', ft)
7881 exe s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7882 exe s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7883 exe s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7884 exe s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7885 exe s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>', '', ft)
7886 exe s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>', ft)
7887 exe s:Map('n', 'czw', ':<C-U>Git stash push --keep-index<C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7888 exe s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7889 exe s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>', ft)
7891 exe s:Map('n', 'co<Space>', ':Git checkout<Space>', '', ft)
7892 exe s:Map('n', 'co<CR>', ':Git checkout<CR>', '', ft)
7893 exe s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>', '', ft)
7894 exe s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>', ft)
7896 exe s:Map('n', 'cb<Space>', ':Git branch<Space>', '', ft)
7897 exe s:Map('n', 'cb<CR>', ':Git branch<CR>', '', ft)
7898 exe s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>', ft)
7900 exe s:Map('n', 'r<Space>', ':Git rebase<Space>', '', ft)
7901 exe s:Map('n', 'r<CR>', ':Git rebase<CR>', '', ft)
7902 exe s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7903 exe s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7904 exe s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>', ft)
7905 exe s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>', ft)
7906 exe s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>', ft)
7907 exe s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>', ft)
7908 exe s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7909 exe s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7910 exe s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7911 exe s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>', ft)
7912 exe s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>', ft)
7913 exe s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>', ft)
7914 exe s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>', ft)
7915 exe s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>', ft)
7918 function! fugitive#MapJumps(...) abort
7920 if get(b:, 'fugitive_type', '') ==# 'blob'
7921 let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
7922 exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7923 exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7924 exe s:Map('n', 'o', ':<C-U>0,1Git blame' . blame_tail, '<silent>')
7925 exe s:Map('n', 'p', ':<C-U>0,1Git! blame' . blame_tail, '<silent>')
7926 if has('patch-7.4.1898')
7927 exe s:Map('n', 'gO', ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
7928 exe s:Map('n', 'O', ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
7930 exe s:Map('n', 'gO', ':<C-U>0,4Git blame' . blame_tail, '<silent>')
7931 exe s:Map('n', 'O', ':<C-U>0,5Git blame' . blame_tail, '<silent>')
7934 call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
7935 call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
7936 call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
7937 call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
7938 call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
7939 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
7942 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
7943 call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
7944 call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
7945 call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
7946 call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
7947 call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
7949 if !exists('g:fugitive_no_maps')
7950 call s:Map('n', '<C-P>', ':exe <SID>PreviousItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-P is deprecated in favor of ("<Bar>echohl NONE<CR>', '<unique>')
7951 call s:Map('n', '<C-N>', ':exe <SID>NextItem(v:count1)<Bar>echohl WarningMsg<Bar>echo "CTRL-N is deprecated in favor of )"<Bar>echohl NONE<CR>', '<unique>')
7953 call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
7954 call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
7955 call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
7956 call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
7957 call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
7958 call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
7959 call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
7960 call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
7961 call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
7962 call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
7963 call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
7964 call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
7965 call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
7966 call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
7967 call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
7968 call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
7970 call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
7971 call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
7972 call s:Map('n', '-', ":<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>", '<silent>')
7973 call s:Map('n', 'P', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
7974 call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
7975 call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7976 call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
7977 call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7978 call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
7979 call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
7980 call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
7982 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
7983 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
7984 call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
7985 call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
7988 let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
7989 let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
7990 let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
7991 if new_browsex !=# old_browsex
7992 exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
7997 function! fugitive#GX() abort
7999 let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
8000 if len(results) && len(results[0])
8001 return FugitiveReal(s:Generate(results[0]))
8005 return expand(get(g:, 'netrw_gx', expand('<cfile>')))
8008 function! s:CfilePorcelain(...) abort
8013 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8014 let info = s:StageInfo()
8015 let line = getline('.')
8016 if len(info.sigil) && len(info.section) && len(info.paths)
8017 if info.section ==# 'Unstaged' && info.sigil !=# '-'
8018 return [lead . info.relative[0], info.offset, 'normal!zv']
8019 elseif info.section ==# 'Staged' && info.sigil ==# '-'
8020 return ['@:' . info.relative[0], info.offset, 'normal!zv']
8022 return [':0:' . info.relative[0], info.offset, 'normal!zv']
8024 elseif len(info.paths)
8025 return [lead . info.relative[0]]
8026 elseif len(info.commit)
8027 return [info.commit]
8028 elseif line =~# '^' . s:ref_header . ': \|^Head: '
8029 return [matchstr(line, ' \zs.*')]
8035 function! fugitive#PorcelainCfile() abort
8036 let file = fugitive#Find(s:CfilePorcelain()[0])
8037 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8040 function! s:StatusCfile(...) abort
8045 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8046 if getline('.') =~# '^.\=\trenamed:.* -> '
8047 return [lead . matchstr(getline('.'),' -> \zs.*')]
8048 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
8049 return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
8050 elseif getline('.') =~# '^.\=\t.'
8051 return [lead . matchstr(getline('.'),'\t\zs.*')]
8052 elseif getline('.') =~# ': needs merge$'
8053 return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
8054 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
8056 elseif getline('.') =~# '^\%(. \)\=On branch '
8057 return ['refs/heads/'.getline('.')[12:]]
8058 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
8059 return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
8065 function! fugitive#MessageCfile() abort
8066 let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
8067 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8070 function! s:BranchCfile(result) abort
8071 return matchstr(getline('.'), '^. \zs\S\+')
8074 let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
8075 function! s:cfile() abort
8076 let temp_state = s:TempState()
8077 let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
8078 if exists('*s:' . name . 'Cfile')
8079 let cfile = s:{name}Cfile(temp_state)
8081 return type(cfile) == type('') ? [cfile] : cfile
8084 if empty(FugitiveGitDir())
8088 let myhash = s:DirRev(@%)[1]
8091 let myhash = fugitive#RevParse(myhash)
8096 if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
8097 let lnum = line('.')
8099 if getline(lnum) =~# '^\%(commit\|tag\) \w'
8100 let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
8107 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
8109 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
8110 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
8112 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
8113 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
8115 return [treebase . s:sub(getline('.'),'/$','')]
8122 if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
8123 let ref = matchstr(getline('.'),'\x\{40,\}')
8124 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
8128 if getline('.') =~# '^ref: '
8129 let ref = strpart(getline('.'),5)
8131 elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
8132 let ref = matchstr(getline('.'),'\x\{40,\}')
8135 elseif getline('.') =~# '^parent \x\{40,\}\>'
8136 let ref = matchstr(getline('.'),'\x\{40,\}')
8137 let line = line('.')
8139 while getline(line) =~# '^parent '
8145 elseif getline('.') =~# '^tree \x\{40,\}$'
8146 let ref = matchstr(getline('.'),'\x\{40,\}')
8147 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
8148 let ref = myhash.':'
8152 elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
8153 let ref = matchstr(getline('.'),'\x\{40,\}')
8154 let type = matchstr(getline(line('.')+1),'type \zs.*')
8156 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
8157 let ref = s:DirRev(@%)[1]
8159 elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
8160 let ref = matchstr(getline('.'),'\x\{40,\}')
8161 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
8163 elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
8164 let files = split(getline('.'), "\t")[1:-1]
8165 let ref = 'b/' . files[-1]
8166 if getline('.') =~# '^D'
8167 let ref = 'a/' . files[0]
8168 elseif getline('.') !~# '^A'
8169 let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
8172 elseif getline('.') =~# '^[+-]'
8173 let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
8175 let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
8176 let dcmds = [new_lnum, 'normal!zv']
8178 let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
8179 let dcmds = [old_lnum, 'normal!zv']
8181 let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
8184 elseif getline('.') =~# '^rename from '
8185 let ref = 'a/'.getline('.')[12:]
8186 elseif getline('.') =~# '^rename to '
8187 let ref = 'b/'.getline('.')[10:]
8189 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
8190 let diff = getline(search(s:diff_header_pattern, 'bcnW'))
8191 let offset = matchstr(getline('.'), '+\zs\d\+')
8193 let [dref, ref] = s:ParseDiffHeader(diff)
8194 let dcmd = 'Gdiffsplit! +'.offset
8196 elseif getline('.') =~# s:diff_header_pattern
8197 let [dref, ref] = s:ParseDiffHeader(getline('.'))
8198 let dcmd = 'Gdiffsplit!'
8200 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
8201 let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
8202 let dcmd = 'Gdiffsplit!'
8204 elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
8205 let ref = getline('.')
8207 elseif expand('<cword>') =~# '^\x\{7,\}\>'
8208 return [expand('<cword>')]
8223 let prefixes.a = myhash.'^:'
8224 let prefixes.b = myhash.':'
8226 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8228 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8231 if ref ==# '/dev/null'
8233 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
8237 return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
8239 return [ref] + dcmds
8247 function! s:GF(mode) abort
8249 let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
8251 return 'echoerr ' . string(v:exception)
8254 let cmd = 'G' . a:mode .
8255 \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
8256 \ fnameescape(results[0])
8257 let tail = join(map(results[2:-1], '"|" . v:val'), '')
8258 if a:mode ==# 'pedit' && len(tail)
8259 return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
8263 elseif len(results) && len(results[0])
8264 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
8270 function! fugitive#Cfile() abort
8272 let results = s:cfile()
8274 if !empty(s:TempState())
8275 let cfile = s:TempDotMap()
8277 return fnameescape(s:Generate(cfile))
8280 let cfile = expand('<cfile>')
8281 if &includeexpr =~# '\<v:fname\>'
8282 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
8285 elseif len(results) > 1
8286 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
8288 return pre . fnameescape(s:Generate(results[0]))
8291 " Section: Statusline
8293 function! fugitive#Statusline(...) abort
8294 let dir = s:Dir(bufnr(''))
8299 let commit = s:DirCommitFile(@%)[1]
8301 let status .= ':' . commit[0:6]
8303 let status .= '('.fugitive#Head(7, dir).')'
8304 return '[Git'.status.']'
8307 function! fugitive#statusline(...) abort
8308 return fugitive#Statusline()
8313 function! fugitive#Foldtext() abort
8314 if &foldmethod !=# 'syntax'
8318 let line_foldstart = getline(v:foldstart)
8319 if line_foldstart =~# '^diff '
8320 let [add, remove] = [-1, -1]
8322 for lnum in range(v:foldstart, v:foldend)
8323 let line = getline(lnum)
8324 if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
8325 let filename = fugitive#Unquote(line[4:-1])[2:-1]
8329 elseif line =~# '^-'
8331 elseif line =~# '^Binary '
8336 let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
8339 let filename = line_foldstart[5:-1]
8342 return 'Binary: '.filename
8344 return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
8346 elseif line_foldstart =~# '^@@\+ .* @@'
8347 return '+-' . v:folddashes . ' ' . line_foldstart
8348 elseif &filetype ==# 'fugitive' && line_foldstart =~# '^[A-Z][a-z].* (\d\+)$'
8349 let c = +matchstr(line_foldstart, '(\zs\d\+\ze)$')
8350 return '+-' . v:folddashes . printf('%3d item', c) . (c == 1 ? ': ' : 's: ') . matchstr(line_foldstart, '.*\ze (\d\+)$')
8351 elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
8352 let lines = getline(v:foldstart, v:foldend)
8353 call filter(lines, 'v:val =~# "^#\t"')
8354 call map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
8355 call map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
8356 return line_foldstart.' '.join(lines, ', ')
8361 function! fugitive#foldtext() abort
8362 return fugitive#Foldtext()