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)
276 let temp = FugitiveGitPath(temp)
278 let temp = '"' . temp . '"'
283 function! s:DoAutocmd(...) abort
284 return join(map(copy(a:000), "'doautocmd <nomodeline>' . v:val"), '|')
287 function! s:Map(mode, lhs, rhs, ...) abort
289 let flags = a:0 && type(a:1) == type('') ? a:1 : ''
290 let defer = flags =~# '<unique>'
291 let flags = substitute(flags, '<unique>', '', '') . (a:rhs =~# '<Plug>' ? '' : '<script>') . '<nowait>'
292 for mode in split(a:mode, '\zs')
294 call add(maps, mode.'map <buffer>' . substitute(flags, '<unique>', '', '') . ' <Plug>fugitive:' . a:lhs . ' ' . a:rhs)
299 let keys = get(g:, mode.'remap', {})
300 if type(keys) == type([])
304 if has_key(keys, head)
305 let head = keys[head]
306 let skip = empty(head)
309 let tail = matchstr(head, '<[^<>]*>$\|.$') . tail
310 let head = substitute(head, '<[^<>]*>$\|.$', '', '')
312 if !skip && (!defer || empty(mapcheck(head.tail, mode)))
313 call add(maps, mode.'map <buffer>' . flags . ' ' . head.tail . ' ' . a:rhs)
315 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') .
316 \ '|sil! exe "' . mode . 'unmap <buffer> ' . head.tail . '"'
324 function! fugitive#Autowrite() abort
325 if &autowrite || &autowriteall
333 if exists('reconfirm')
341 function! fugitive#Wait(job_or_jobs, ...) abort
342 let original = type(a:job_or_jobs) == type([]) ? copy(a:job_or_jobs) : [a:job_or_jobs]
343 let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
344 call filter(jobs, 'type(v:val) !=# type("")')
345 let timeout_ms = a:0 ? a:1 : -1
346 if exists('*jobwait')
347 call map(copy(jobs), 'chanclose(v:val, "stdin")')
348 call jobwait(jobs, timeout_ms)
349 let jobs = map(copy(original), 'type(v:val) ==# type({}) ? get(v:val, "job", "") : v:val')
350 call filter(jobs, 'type(v:val) !=# type("")')
356 if ch_status(job) ==# 'open'
357 call ch_close_in(job)
362 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
374 function! s:JobVimExit(dict, callback, temp, job, status) abort
375 let a:dict.exit_status = a:status
376 let a:dict.stderr = readfile(a:temp . '.err', 'b')
377 call delete(a:temp . '.err')
378 let a:dict.stdout = readfile(a:temp . '.out', 'b')
379 call delete(a:temp . '.out')
380 call delete(a:temp . '.in')
381 call remove(a:dict, 'job')
382 call call(a:callback[0], [a:dict] + a:callback[1:-1])
385 function! s:JobNvimExit(dict, callback, job, data, type) dict abort
386 let a:dict.stdout = self.stdout
387 let a:dict.stderr = self.stderr
388 let a:dict.exit_status = a:data
389 call remove(a:dict, 'job')
390 call call(a:callback[0], [a:dict] + a:callback[1:-1])
393 function! s:JobExecute(argv, jopts, stdin, callback, ...) abort
394 let dict = a:0 ? a:1 : {}
395 let cb = len(a:callback) ? a:callback : [function('len')]
396 if exists('*jobstart')
397 call extend(a:jopts, {
398 \ 'stdout_buffered': v:true,
399 \ 'stderr_buffered': v:true,
400 \ 'on_exit': function('s:JobNvimExit', [dict, cb])})
402 let dict.job = jobstart(a:argv, a:jopts)
404 call chansend(dict.job, a:stdin)
406 call chanclose(dict.job, 'stdin')
407 catch /^Vim\%((\a\+)\)\=:E475:/
408 let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
410 elseif exists('*ch_close_in')
411 let temp = tempname()
412 call extend(a:jopts, {
414 \ 'out_name': temp . '.out',
416 \ 'err_name': temp . '.err',
417 \ 'exit_cb': function('s:JobVimExit', [dict, cb, temp])})
419 let a:jopts.in_io = 'null'
420 elseif !empty(a:stdin)
421 let a:jopts.in_io = 'file'
422 let a:jopts.in_name = temp . '.in'
423 call writefile(a:stdin, a:jopts.in_name, 'b')
425 let dict.job = job_start(a:argv, a:jopts)
426 if job_status(dict.job) ==# 'fail'
427 let [dict.exit_status, dict.stdout, dict.stderr] = [122, [''], ['']]
430 elseif &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
431 throw 'fugitive: Vim 8 or higher required to use ' . &shell
433 let cmd = s:shellesc(a:argv)
434 let outfile = tempname()
437 call writefile(a:stdin, outfile . '.in', 'b')
438 let cmd = ' (' . cmd . ' >' . outfile . ' <' . outfile . '.in) '
440 let cmd = ' (' . cmd . ' >' . outfile . ') '
442 let dict.stderr = split(system(cmd), "\n", 1)
443 let dict.exit_status = v:shell_error
444 let dict.stdout = readfile(outfile, 'b')
445 call call(cb[0], [dict] + cb[1:-1])
448 call delete(outfile . '.in')
452 call fugitive#Wait(dict)
457 function! s:add_methods(namespace, method_names) abort
458 for name in a:method_names
459 let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name)
465 let s:run_jobs = (exists('*ch_close_in') || exists('*jobstart')) && exists('*bufwinid')
467 function! s:GitCmd() abort
468 if !exists('g:fugitive_git_executable')
470 elseif type(g:fugitive_git_executable) == type([])
471 return g:fugitive_git_executable
473 let dquote = '"\%([^"]\|""\|\\"\)*"\|'
474 let string = g:fugitive_git_executable
476 if string =~# '^\w\+='
477 call add(list, '/usr/bin/env')
479 while string =~# '\S'
480 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
481 let string = strpart(string, len(arg))
482 let arg = substitute(arg, '^\s\+', '', '')
483 let arg = substitute(arg,
484 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\)\|' . s:expand,
485 \ '\=submatch(0)[0] ==# "\\" ? submatch(0)[1] : submatch(0)[1:-2]', 'g')
492 function! s:GitShellCmd() abort
493 if !exists('g:fugitive_git_executable')
495 elseif type(g:fugitive_git_executable) == type([])
496 return s:shellesc(g:fugitive_git_executable)
498 return g:fugitive_git_executable
502 function! s:UserCommandCwd(dir) abort
503 let tree = s:Tree(a:dir)
504 return len(tree) ? s:VimSlash(tree) : getcwd()
507 function! s:UserCommandList(...) abort
508 if !fugitive#GitVersion(1, 8, 5)
509 throw 'fugitive: Git 1.8.5 or higher required'
511 if !exists('g:fugitive_git_command')
513 elseif type(g:fugitive_git_command) == type([])
514 let git = g:fugitive_git_command
516 let git = split(g:fugitive_git_command, '\s\+')
519 if a:0 && type(a:1) == type({})
520 let git = copy(get(a:1, 'git', git))
521 let flags = get(a:1, 'flags', flags)
522 let dir = a:1.git_dir
524 let dir = s:GitDir(a:1)
529 let tree = s:Tree(dir)
531 call add(git, '--git-dir=' . FugitiveGitPath(dir))
533 if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
534 call add(git, '--git-dir=' . FugitiveGitPath(dir))
536 if !s:cpath(tree, getcwd())
537 call extend(git, ['-C', FugitiveGitPath(tree)])
544 let s:git_versions = {}
545 function! fugitive#GitVersion(...) abort
546 let git = s:GitShellCmd()
547 if !has_key(s:git_versions, git)
548 let s:git_versions[git] = matchstr(get(s:JobExecute(s:GitCmd() + ['--version'], {}, [], [], {}).stdout, 0, ''), '\d[^[:space:]]\+')
551 return s:git_versions[git]
553 let components = split(s:git_versions[git], '\D\+')
557 for i in range(len(a:000))
558 if a:000[i] > +get(components, i)
560 elseif a:000[i] < +get(components, i)
564 return a:000[i] ==# get(components, i)
567 function! s:Dir(...) abort
568 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
571 function! s:GitDir(...) abort
572 return a:0 ? FugitiveGitDir(a:1) : FugitiveGitDir()
575 function! s:InitializeBuffer(repo) abort
576 let b:git_dir = s:GitDir(a:repo)
579 function! s:SameRepo(one, two) abort
580 let one = s:GitDir(a:one)
581 return !empty(one) && one ==# s:GitDir(a:two)
584 if exists('+shellslash')
585 function! s:DirUrlPrefix(dir) abort
586 let gd = s:GitDir(a:dir)
587 return 'fugitive://' . (gd =~# '^[^/]' ? '/' : '') . s:PathUrlEncode(gd) . '//'
590 function! s:DirUrlPrefix(dir) abort
591 return 'fugitive://' . s:PathUrlEncode(s:GitDir(a:dir)) . '//'
595 function! s:Tree(...) abort
596 return a:0 ? FugitiveWorkTree(a:1) : FugitiveWorkTree()
599 function! s:HasOpt(args, ...) abort
600 let args = a:args[0 : index(a:args, '--')]
601 let opts = copy(a:000)
602 if type(opts[0]) == type([])
603 if empty(args) || index(opts[0], args[0]) == -1
609 if index(args, opt) != -1
615 function! s:PreparePathArgs(cmd, dir, literal, explicit) abort
617 call insert(a:cmd, '--literal-pathspecs')
619 let split = index(a:cmd, '--')
620 for i in range(split < 0 ? len(a:cmd) : split)
621 if type(a:cmd[i]) == type(0)
623 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
625 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
632 for i in range(split + 1, len(a:cmd) - 1)
633 if type(a:cmd[i]) == type(0)
635 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), './', a:dir)
637 let a:cmd[i] = fugitive#Path(bufname(a:cmd[i]), ':(top,literal)', a:dir)
640 let a:cmd[i] = fugitive#Path(a:cmd[i], './', a:dir)
646 let s:git_index_file_env = {}
647 function! s:GitIndexFileEnv() abort
648 if $GIT_INDEX_FILE =~# '^/\|^\a:' && !has_key(s:git_index_file_env, $GIT_INDEX_FILE)
649 let s:git_index_file_env[$GIT_INDEX_FILE] = s:Slash(FugitiveVimPath($GIT_INDEX_FILE))
651 return get(s:git_index_file_env, $GIT_INDEX_FILE, '')
654 function! s:PrepareEnv(env, dir) abort
655 if len($GIT_INDEX_FILE) && len(s:Tree(a:dir)) && !has_key(a:env, 'GIT_INDEX_FILE')
656 let index_dir = substitute(s:GitIndexFileEnv(), '[^/]\+$', '', '')
657 let our_dir = fugitive#Find('.git/', a:dir)
658 if !s:cpath(index_dir, our_dir) && !s:cpath(resolve(index_dir), our_dir)
659 let a:env['GIT_INDEX_FILE'] = FugitiveGitPath(fugitive#Find('.git/index', a:dir))
662 if len($GIT_WORK_TREE)
663 let a:env['GIT_WORK_TREE'] = '.'
667 let s:prepare_env = {
668 \ 'sequence.editor': 'GIT_SEQUENCE_EDITOR',
669 \ 'core.editor': 'GIT_EDITOR',
670 \ 'core.askpass': 'GIT_ASKPASS',
672 function! fugitive#PrepareDirEnvGitFlagsArgs(...) abort
673 if !fugitive#GitVersion(1, 8, 5)
674 throw 'fugitive: Git 1.8.5 or higher required'
677 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')
678 let cmd = a:1.flags + a:1.args
680 if has_key(a:1, 'git')
683 let env = get(a:1, 'env', {})
688 if type(arg) ==# type([])
689 call extend(list_args, arg)
694 call extend(cmd, list_args)
698 let explicit_pathspec_option = 0
699 let literal_pathspecs = 1
703 if type(cmd[i]) == type({})
704 if has_key(cmd[i], 'fugitive_dir') || has_key(cmd[i], 'git_dir')
705 let dir = s:Dir(cmd[i])
707 if has_key(cmd[i], 'git')
710 if has_key(cmd[i], 'env')
711 call extend(env, cmd[i].env)
714 elseif cmd[i] =~# '^$\|[\/.]' && cmd[i] !~# '^-'
715 let dir = s:Dir(remove(cmd, i))
716 elseif cmd[i] =~# '^--git-dir='
717 let dir = s:Dir(remove(cmd, i)[10:-1])
718 elseif type(cmd[i]) ==# type(0)
719 let dir = s:Dir(remove(cmd, i))
720 elseif cmd[i] ==# '-c' && len(cmd) > i + 1
721 let key = matchstr(cmd[i+1], '^[^=]*')
722 if has_key(s:prepare_env, tolower(key))
723 let var = s:prepare_env[tolower(key)]
724 let val = matchstr(cmd[i+1], '=\zs.*')
725 let autoenv[var] = val
728 elseif cmd[i] =~# '^--.*pathspecs$'
729 let literal_pathspecs = (cmd[i] ==# '--literal-pathspecs')
730 let explicit_pathspec_option = 1
732 elseif cmd[i] !~# '^-'
733 let arg_count = len(cmd) - i
742 call extend(autoenv, env)
743 call s:PrepareEnv(autoenv, dir)
744 if len($GPG_TTY) && !has_key(autoenv, 'GPG_TTY')
745 let autoenv.GPG_TTY = ''
747 call s:PreparePathArgs(cmd, dir, literal_pathspecs, explicit_pathspec_option)
748 return [dir, env, extend(autoenv, env), git, cmd[0 : -arg_count-1], arg_count ? cmd[-arg_count : -1] : []]
751 function! s:BuildEnvPrefix(env) abort
753 let env = items(a:env)
756 elseif &shell =~# '\%(powershell\|pwsh\)\%(\.exe\)\=$'
757 return join(map(env, '"$Env:" . v:val[0] . " = ''" . substitute(v:val[1], "''", "''''", "g") . "''; "'), '')
759 return join(map(env, '"set " . substitute(join(v:val, "="), "[&|<>^]", "^^^&", "g") . "& "'), '')
761 return '/usr/bin/env ' . s:shellesc(map(env, 'join(v:val, "=")')) . ' '
765 function! s:JobOpts(cmd, env) abort
768 elseif has('patch-8.2.0239') ||
769 \ has('nvim') && api_info().version.api_level - api_info().version.api_prerelease >= 7 ||
770 \ has('patch-8.0.0902') && !has('nvim') && (!has('win32') || empty(filter(keys(a:env), 'exists("$" . v:val)')))
771 return [a:cmd, {'env': a:env}]
773 let envlist = map(items(a:env), 'join(v:val, "=")')
775 return [['/usr/bin/env'] + envlist + a:cmd, {}]
777 let pre = join(map(envlist, '"set " . substitute(v:val, "[&|<>^]", "^^^&", "g") . "& "'), '')
778 if len(a:cmd) == 3 && a:cmd[0] ==# 'cmd.exe' && a:cmd[1] ==# '/c'
779 return [a:cmd[0:1] + [pre . a:cmd[2]], {}]
781 return [['cmd.exe', '/c', pre . s:WinShellEsc(a:cmd)], {}]
786 function! s:PrepareJob(opts) abort
787 let dict = {'argv': a:opts.argv}
788 if has_key(a:opts, 'env')
789 let dict.env = a:opts.env
791 let [argv, jopts] = s:JobOpts(a:opts.argv, get(a:opts, 'env', {}))
792 if has_key(a:opts, 'cwd')
793 if has('patch-8.0.0902')
794 let jopts.cwd = a:opts.cwd
795 let dict.cwd = a:opts.cwd
797 throw 'fugitive: cwd unsupported'
800 return [argv, jopts, dict]
803 function! fugitive#PrepareJob(...) abort
804 if a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'argv') && !has_key(a:1, 'args')
805 return s:PrepareJob(a:1)
807 let [repo, user_env, exec_env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
808 let dir = s:GitDir(repo)
809 let dict = {'git': git, 'git_dir': dir, 'flags': flags, 'args': args}
811 let dict.env = user_env
813 let cmd = flags + args
814 let tree = s:Tree(repo)
815 if empty(tree) || index(cmd, '--') == len(cmd) - 1
816 let dict.cwd = getcwd()
817 call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
819 let dict.cwd = s:VimSlash(tree)
820 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
821 if !s:cpath(tree . '/.git', dir) || len($GIT_DIR)
822 call extend(cmd, ['--git-dir=' . FugitiveGitPath(dir)], 'keep')
825 call extend(cmd, git, 'keep')
826 return s:JobOpts(cmd, exec_env) + [dict]
829 function! fugitive#Execute(...) abort
833 while len(cb) && type(cb[0]) !=# type(function('tr'))
834 if type(cb[0]) ==# type({}) && has_key(cb[0], 'stdin')
835 if type(cb[0].stdin) == type([])
836 call extend(stdin, cb[0].stdin)
837 elseif type(cb[0].stdin) == type('')
838 call extend(stdin, readfile(cb[0].stdin, 'b'))
840 if len(keys(cb[0])) == 1
845 call add(cmd, remove(cb, 0))
847 let [argv, jopts, dict] = call('fugitive#PrepareJob', cmd)
848 return s:JobExecute(argv, jopts, stdin, cb, dict)
851 function! s:BuildShell(dir, env, git, args) abort
852 let cmd = copy(a:args)
853 let tree = s:Tree(a:dir)
854 let pre = s:BuildEnvPrefix(a:env)
855 if empty(tree) || index(cmd, '--') == len(cmd) - 1
856 call insert(cmd, '--git-dir=' . FugitiveGitPath(a:dir))
858 call extend(cmd, ['-C', FugitiveGitPath(tree)], 'keep')
859 if !s:cpath(tree . '/.git', a:dir) || len($GIT_DIR)
860 call extend(cmd, ['--git-dir=' . FugitiveGitPath(a:dir)], 'keep')
863 return pre . join(map(a:git + cmd, 's:shellesc(v:val)'))
866 function! s:JobNvimCallback(lines, job, data, type) abort
867 let a:lines[-1] .= remove(a:data, 0)
868 call extend(a:lines, a:data)
871 function! s:SystemList(cmd) abort
873 if exists('*jobstart')
876 \ 'on_stdout': function('s:JobNvimCallback', [lines]),
877 \ 'on_stderr': function('s:JobNvimCallback', [lines]),
878 \ 'on_exit': { j, code, _ -> add(exit, code) }}
879 let job = jobstart(a:cmd, jopts)
880 call chanclose(job, 'stdin')
883 call remove(lines, -1)
885 return [lines, exit[0]]
886 elseif exists('*ch_close_in')
889 \ 'out_cb': { j, str -> add(lines, str) },
890 \ 'err_cb': { j, str -> add(lines, str) },
891 \ 'exit_cb': { j, code -> add(exit, code) }}
892 let job = job_start(a:cmd, jopts)
893 call ch_close_in(job)
894 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
897 return [lines, exit[0]]
899 let [output, exec_error] = s:SystemError(s:shellesc(a:cmd))
900 let lines = split(output, "\n", 1)
902 call remove(lines, -1)
904 return [lines, v:shell_error]
908 function! fugitive#ShellCommand(...) abort
909 let [repo, _, env, git, flags, args] = call('fugitive#PrepareDirEnvGitFlagsArgs', a:000)
910 return s:BuildShell(s:GitDir(repo), env, git, flags + args)
913 function! s:SystemError(cmd, ...) abort
914 let cmd = type(a:cmd) == type([]) ? s:shellesc(a:cmd) : a:cmd
916 if &shellredir ==# '>' && &shell =~# 'sh\|cmd'
917 let shellredir = &shellredir
921 set shellredir=>%s\ 2>&1
924 if exists('+guioptions') && &guioptions =~# '!'
925 let guioptions = &guioptions
928 let out = call('system', [cmd] + a:000)
929 return [out, v:shell_error]
930 catch /^Vim\%((\a\+)\)\=:E484:/
931 let opts = ['shell', 'shellcmdflag', 'shellredir', 'shellquote', 'shellxquote', 'shellxescape', 'shellslash']
932 call filter(opts, 'exists("+".v:val) && !empty(eval("&".v:val))')
933 call map(opts, 'v:val."=".eval("&".v:val)')
934 call s:throw('failed to run `' . cmd . '` with ' . join(opts, ' '))
936 if exists('shellredir')
937 let &shellredir = shellredir
939 if exists('guioptions')
940 let &guioptions = guioptions
945 function! s:ChompStderr(...) abort
946 let r = call('fugitive#Execute', a:000)
947 return !r.exit_status ? '' : len(r.stderr) > 1 ? s:JoinChomp(r.stderr) : 'unknown Git error' . string(r)
950 function! s:ChompDefault(default, ...) abort
951 let r = call('fugitive#Execute', a:000)
952 return r.exit_status ? a:default : s:JoinChomp(r.stdout)
955 function! s:LinesError(...) abort
956 let r = call('fugitive#Execute', a:000)
957 if empty(r.stdout[-1])
958 call remove(r.stdout, -1)
960 return [r.exit_status ? [] : r.stdout, r.exit_status]
963 function! s:TreeChomp(...) abort
964 let r = call('fugitive#Execute', a:000)
966 return s:JoinChomp(r.stdout)
968 throw 'fugitive: error running `' . call('fugitive#ShellCommand', a:000) . '`: ' . s:JoinChomp(r.stderr)
971 function! s:StdoutToFile(out, cmd, ...) abort
972 let [argv, jopts, _] = fugitive#PrepareJob(a:cmd)
974 if exists('*jobstart')
976 \ 'stdout_buffered': v:true,
977 \ 'stderr_buffered': v:true,
978 \ 'on_exit': { j, code, _ -> add(exit, code) }})
979 let job = jobstart(argv, jopts)
981 call chansend(job, a:1)
983 call chanclose(job, 'stdin')
986 call writefile(jopts.stdout, a:out, 'b')
988 return [join(jopts.stderr, "\n"), exit[0]]
989 elseif exists('*ch_close_in')
993 \ 'out_io': len(a:out) ? 'file' : 'null',
997 \ 'exit_cb': { j, code -> add(exit, code) }})
998 let job = job_start(argv, jopts)
1000 call ch_sendraw(job, a:1)
1002 call ch_close_in(job)
1003 while ch_status(job) !~# '^closed$\|^fail$' || job_status(job) ==# 'run'
1006 return [join(readfile(err, 'b'), "\n"), exit[0]]
1010 elseif s:winshell() || &shell !~# 'sh' || &shell =~# 'fish\|\%(powershell\|pwsh\)\%(\.exe\)\=$'
1011 throw 'fugitive: Vim 8 or higher required to use ' . &shell
1013 let cmd = fugitive#ShellCommand(a:cmd)
1014 return call('s:SystemError', [' (' . cmd . ' >' . (len(a:out) ? a:out : '/dev/null') . ') '] + a:000)
1018 let s:head_cache = {}
1020 function! fugitive#Head(...) abort
1021 let dir = a:0 > 1 ? a:2 : s:Dir()
1025 let file = FugitiveActualDir(dir) . '/HEAD'
1026 let ftime = getftime(file)
1029 elseif ftime != get(s:head_cache, file, [-1])[0]
1030 let s:head_cache[file] = [ftime, readfile(file)[0]]
1032 let head = s:head_cache[file][1]
1033 let len = a:0 ? a:1 : 0
1034 if head =~# '^ref: '
1036 return strpart(head, 5)
1038 return substitute(head, '\C^ref: \%(refs/\%(heads/\|remotes/\|tags/\)\=\)\=', '', '')
1040 elseif head =~# '^\x\{40,\}$'
1041 return len < 0 ? head : strpart(head, 0, len)
1047 function! fugitive#RevParse(rev, ...) abort
1048 let hash = s:ChompDefault('', [a:0 ? a:1 : s:Dir(), 'rev-parse', '--verify', a:rev, '--'])
1049 if hash =~# '^\x\{40,\}$'
1052 throw 'fugitive: failed to parse revision ' . a:rev
1055 " Section: Git config
1057 function! s:ConfigTimestamps(dir, dict) abort
1058 let files = ['/etc/gitconfig', '~/.gitconfig',
1059 \ len($XDG_CONFIG_HOME) ? $XDG_CONFIG_HOME . '/git/config' : '~/.config/git/config']
1061 call add(files, fugitive#Find('.git/config', a:dir))
1063 call extend(files, get(a:dict, 'include.path', []))
1064 return join(map(files, 'getftime(expand(v:val))'), ',')
1067 function! s:ConfigCallback(r, into) abort
1068 let dict = a:into[1]
1069 if has_key(dict, 'job')
1070 call remove(dict, 'job')
1072 let lines = a:r.exit_status ? [] : split(tr(join(a:r.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
1074 let key = matchstr(line, "^[^\n]*")
1075 if !has_key(dict, key)
1078 if len(key) ==# len(line)
1079 call add(dict[key], 1)
1081 call add(dict[key], strpart(line, len(key) + 1))
1084 let callbacks = remove(dict, 'callbacks')
1086 let a:into[0] = s:ConfigTimestamps(dict.git_dir, dict)
1087 for callback in callbacks
1088 call call(callback[0], [dict] + callback[1:-1])
1092 let s:config_prototype = {}
1095 function! fugitive#ExpireConfig(...) abort
1096 if !a:0 || a:1 is# 0
1099 let key = a:1 is# '' ? '_' : s:GitDir(a:0 ? a:1 : -1)
1100 if len(key) && has_key(s:config, key)
1101 call remove(s:config, key)
1106 function! fugitive#Config(...) abort
1108 let default = get(a:, 3, '')
1109 if a:0 && type(a:1) == type(function('tr'))
1111 let callback = a:000
1112 elseif a:0 > 1 && type(a:2) == type(function('tr'))
1113 if type(a:1) == type({}) && has_key(a:1, 'GetAll')
1114 if has_key(a:1, 'callbacks')
1115 call add(a:1.callbacks, a:000[1:-1])
1117 call call(a:2, [a:1] + a:000[2:-1])
1121 let dir = s:Dir(a:1)
1122 let callback = a:000[1:-1]
1124 elseif a:0 >= 2 && type(a:2) == type({}) && has_key(a:2, 'GetAll')
1125 return get(fugitive#ConfigGetAll(a:1, a:2), -1, default)
1127 let dir = s:Dir(a:2)
1129 elseif a:0 == 1 && type(a:1) == type({}) && has_key(a:1, 'GetAll')
1131 elseif a:0 == 1 && type(a:1) == type('') && a:1 =~# '^[[:alnum:]-]\+\.'
1135 let dir = s:Dir(a:1)
1139 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1140 let git_dir = s:GitDir(dir)
1141 let dir_key = len(git_dir) ? git_dir : '_'
1142 let [ts, dict] = get(s:config, dir_key, ['new', {}])
1143 if !has_key(dict, 'job') && ts !=# s:ConfigTimestamps(git_dir, dict)
1144 let dict = copy(s:config_prototype)
1145 let dict.git_dir = git_dir
1146 let into = ['running', dict]
1147 let dict.callbacks = []
1148 let exec = fugitive#Execute([dir, 'config', '--list', '-z', '--'], function('s:ConfigCallback'), into)
1149 if has_key(exec, 'job')
1150 let dict.job = exec.job
1152 let s:config[dir_key] = into
1154 if !exists('l:callback')
1155 call fugitive#Wait(dict)
1156 elseif has_key(dict, 'callbacks')
1157 call add(dict.callbacks, callback)
1159 call call(callback[0], [dict] + callback[1:-1])
1161 return len(name) ? get(fugitive#ConfigGetAll(name, dict), 0, default) : dict
1164 function! fugitive#ConfigGetAll(name, ...) abort
1165 if a:0 && (type(a:name) !=# type('') || a:name !~# '^[[:alnum:]-]\+\.' && type(a:1) ==# type('') && a:1 =~# '^[[:alnum:]-]\+\.')
1166 let config = fugitive#Config(a:name)
1169 let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1172 let name = substitute(name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1173 call fugitive#Wait(config)
1174 return name =~# '\.' ? copy(get(config, name, [])) : []
1177 function! fugitive#ConfigGetRegexp(pattern, ...) abort
1178 if type(a:pattern) !=# type('')
1179 let config = fugitive#Config(a:name)
1180 let pattern = a:0 ? a:1 : '.*'
1182 let config = fugitive#Config(a:0 ? a:1 : s:Dir())
1183 let pattern = a:pattern
1185 call fugitive#Wait(config)
1186 let filtered = map(filter(copy(config), 'v:key =~# "\\." && v:key =~# pattern'), 'copy(v:val)')
1187 if pattern !~# '\\\@<!\%(\\\\\)*\\z[se]'
1190 let transformed = {}
1191 for [k, v] in items(filtered)
1192 let k = matchstr(k, pattern)
1194 let transformed[k] = v
1200 function! s:config_GetAll(name) dict abort
1201 let name = substitute(a:name, '^[^.]\+\|[^.]\+$', '\L&', 'g')
1202 call fugitive#Wait(self)
1203 return name =~# '\.' ? copy(get(self, name, [])) : []
1206 function! s:config_Get(name, ...) dict abort
1207 return get(self.GetAll(a:name), -1, a:0 ? a:1 : '')
1210 function! s:config_GetRegexp(pattern) dict abort
1211 return fugitive#ConfigGetRegexp(self, a:pattern)
1214 call s:add_methods('config', ['GetAll', 'Get', 'GetRegexp'])
1216 function! s:RemoteDefault(dir) abort
1217 let head = FugitiveHead(0, a:dir)
1218 let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1220 while remote ==# '.' && i > 0
1221 let head = matchstr(FugitiveConfigGet('branch.' . head . '.merge', a:dir), 'refs/heads/\zs.*')
1222 let remote = len(head) ? FugitiveConfigGet('branch.' . head . '.remote', a:dir) : ''
1225 return remote =~# '^\.\=$' ? 'origin' : remote
1228 function! s:SshParseHost(value) abort
1231 for host in split(a:value, '\s\+')
1232 let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
1233 if pattern[0] ==# '!'
1234 call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
1236 call add(patterns, pattern)
1239 return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
1242 function! s:SshParseConfig(into, root, file) abort
1244 let lines = readfile(a:file)
1248 let host = '^\%(.*\)$'
1250 let line = remove(lines, 0)
1251 let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
1252 let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
1254 let host = value ==# 'all' ? '^\%(.*\)$' : ''
1255 elseif key ==# 'host'
1256 let host = s:SshParseHost(value)
1257 elseif key ==# 'include'
1258 for glob in split(value)
1260 let glob = a:root . glob
1262 for included in reverse(split(glob(glob), "\n"))
1264 call extend(lines, readfile(included), 'keep')
1269 elseif len(key) && len(host)
1270 call extend(a:into, {key : []}, 'keep')
1271 call add(a:into[key], [host, value])
1278 function! fugitive#SshConfig(host, ...) abort
1279 if !exists('s:ssh_config')
1280 let s:ssh_config = {}
1281 for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
1282 call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
1285 let host_config = {}
1286 for key in a:0 ? a:1 : keys(s:ssh_config)
1287 for [host_pattern, value] in get(s:ssh_config, key, [])
1288 if a:host =~# host_pattern
1289 let host_config[key] = value
1297 function! fugitive#SshHostAlias(authority) abort
1298 let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
1299 let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
1301 let user = get(c, 'user', '')
1304 let port = get(c, 'port', '')
1306 return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
1309 function! s:CurlResponse(result) abort
1310 let a:result.headers = {}
1311 for line in a:result.exit_status ? [] : remove(a:result, 'stdout')
1312 let header = matchlist(line, '^\([[:alnum:]-]\+\):\s\(.\{-\}\)'. "\r\\=$")
1314 let k = tolower(header[1])
1315 if has_key(a:result.headers, k)
1316 let a:result.headers[k] .= ', ' . header[2]
1318 let a:result.headers[k] = header[2]
1326 let s:remote_headers = {}
1328 function! fugitive#RemoteHttpHeaders(remote) abort
1329 let remote = type(a:remote) ==# type({}) ? get(a:remote, 'remote', '') : a:remote
1330 if type(remote) !=# type('') || remote !~# '^https\=://.' || !s:executable('curl')
1333 let remote = substitute(remote, '#.*', '', '')
1334 if !has_key(s:remote_headers, remote)
1335 let url = remote . '/info/refs?service=git-upload-pack'
1336 let exec = s:JobExecute(
1337 \ ['curl', '--disable', '--silent', '--max-time', '5', '-X', 'GET', '-I',
1338 \ url], {}, [], [function('s:CurlResponse')], {})
1339 call fugitive#Wait(exec)
1340 let s:remote_headers[remote] = exec.headers
1342 return s:remote_headers[remote]
1345 function! s:UrlParse(url) abort
1346 let scp_authority = matchstr(a:url, '^[^:/]\+\ze:\%(//\)\@!')
1347 if len(scp_authority) && !(has('win32') && scp_authority =~# '^\a:[\/]')
1348 let url = {'scheme': 'ssh', 'authority': s:UrlEncode(scp_authority), 'hash': '',
1349 \ 'path': s:UrlEncode(strpart(a:url, len(scp_authority) + 1))}
1351 let url = {'scheme': '', 'authority': '', 'path': '', 'hash': ''}
1353 let match = matchlist(a:url, '^\([[:alnum:].+-]\+\)://\([^/]*\)\(/[^#]*\)\=\(#.*\)\=$')
1355 let url = {'scheme': 'file', 'authority': '', 'hash': '',
1356 \ 'path': s:UrlEncode(a:url)}
1358 let url = {'scheme': match[1], 'authority': match[2], 'hash': match[4]}
1359 let url.path = empty(match[3]) ? '/' : match[3]
1365 function! s:UrlPopulate(string, into) abort
1367 let url.protocol = substitute(url.scheme, '.\zs$', ':', '')
1368 let url.user = fugitive#UrlDecode(matchstr(url.authority, '.\{-\}\ze@', '', ''))
1369 let url.host = substitute(url.authority, '.\{-\}@', '', '')
1370 let url.hostname = substitute(url.host, ':\d\+$', '', '')
1371 let url.port = matchstr(url.host, ':\zs\d\+$', '', '')
1372 let url.origin = substitute(url.scheme, '.\zs$', '://', '') . url.host
1373 let url.search = matchstr(url.path, '?.*')
1374 let url.pathname = '/' . matchstr(url.path, '^/\=\zs[^?]*')
1375 if (url.scheme ==# 'ssh' || url.scheme ==# 'git') && url.path[0:1] ==# '/~'
1376 let url.path = strpart(url.path, 1)
1378 if url.path =~# '^/'
1379 let url.href = url.scheme . '://' . url.authority . url.path . url.hash
1380 elseif url.path =~# '^\~'
1381 let url.href = url.scheme . '://' . url.authority . '/' . url.path . url.hash
1382 elseif url.scheme ==# 'ssh' && url.authority !~# ':'
1383 let url.href = url.authority . ':' . url.path . url.hash
1385 let url.href = a:string
1387 let url.path = fugitive#UrlDecode(matchstr(url.path, '^[^?]*'))
1388 let url.url = matchstr(url.href, '^[^#]*')
1391 function! s:RemoteResolve(url, flags) abort
1392 let remote = s:UrlParse(a:url)
1393 if remote.scheme =~# '^https\=$' && index(a:flags, ':nohttp') < 0
1394 let headers = fugitive#RemoteHttpHeaders(a:url)
1395 let loc = matchstr(get(headers, 'location', ''), '^https\=://.\{-\}\ze/info/refs?')
1397 let remote = s:UrlParse(loc)
1399 let remote.headers = headers
1401 elseif remote.scheme ==# 'ssh'
1402 let remote.authority = fugitive#SshHostAlias(remote.authority)
1407 function! s:ConfigLengthSort(i1, i2) abort
1408 return len(a:i2[0]) - len(a:i1[0])
1411 function! s:RemoteCallback(config, into, flags, cb) abort
1412 if a:into.remote_name =~# '^\.\=$'
1413 let a:into.remote_name = s:RemoteDefault(a:config)
1415 let url = a:into.remote_name
1418 let url = s:GitDir(a:config)
1419 elseif url !~# ':\|^/\|^\a:[\/]\|^\.\.\=/'
1420 let url = FugitiveConfigGet('remote.' . url . '.url', a:config)
1423 for [k, vs] in items(fugitive#ConfigGetRegexp('^url\.\zs.\{-\}\ze\.insteadof$', a:config))
1425 call add(instead_of, [v, k])
1428 call sort(instead_of, 's:ConfigLengthSort')
1429 for [orig, replacement] in instead_of
1430 if strpart(url, 0, len(orig)) ==# orig
1431 let url = replacement . strpart(url, len(orig))
1435 if index(a:flags, ':noresolve') < 0
1436 call extend(a:into, s:RemoteResolve(url, a:flags))
1438 call extend(a:into, s:UrlParse(url))
1440 call s:UrlPopulate(url, a:into)
1442 call call(a:cb[0], [a:into] + a:cb[1:-1])
1446 function! s:Remote(dir, remote, flags, cb) abort
1447 let into = {'remote_name': a:remote, 'git_dir': s:GitDir(a:dir)}
1448 let config = fugitive#Config(a:dir, function('s:RemoteCallback'), into, a:flags, a:cb)
1452 call fugitive#Wait(config)
1457 function! s:RemoteParseArgs(args) abort
1458 " Extract ':noresolve' style flags and an optional callback
1461 let cb = copy(a:args)
1463 if type(cb[0]) ==# type(function('tr'))
1465 elseif len(args) > 1 || type(cb[0]) ==# type('') && cb[0] =~# '^:'
1466 call add(flags, remove(cb, 0))
1468 call add(args, remove(cb, 0))
1472 " From the remaining 0-2 arguments, extract the remote and Git config
1475 let dir_or_config = s:Dir()
1476 elseif len(args) == 1 && type(args[0]) ==# type('') && args[0] !~# '^/\|^\a:[\\/]'
1477 let dir_or_config = s:Dir()
1478 let remote = args[0]
1479 elseif len(args) == 1
1480 let dir_or_config = args[0]
1481 if type(args[0]) ==# type({}) && has_key(args[0], 'remote_name')
1482 let remote = args[0].remote_name
1484 elseif type(args[1]) !=# type('') || args[1] =~# '^/\|^\a:[\\/]'
1485 let dir_or_config = args[1]
1486 let remote = args[0]
1488 let dir_or_config = args[0]
1489 let remote = args[1]
1491 return [dir_or_config, remote, flags, cb]
1494 function! fugitive#Remote(...) abort
1495 let [dir_or_config, remote, flags, cb] = s:RemoteParseArgs(a:000)
1496 return s:Remote(dir_or_config, remote, flags, cb)
1499 function! s:RemoteUrlCallback(remote, callback) abort
1500 return call(a:callback[0], [a:remote.url] + a:callback[1:-1])
1503 function! fugitive#RemoteUrl(...) abort
1504 let [dir_or_config, remote_url, flags, cb] = s:RemoteParseArgs(a:000)
1506 let cb = [function('s:RemoteUrlCallback'), cb]
1508 let remote = s:Remote(dir_or_config, remote_url, flags, cb)
1509 return get(remote, 'url', remote_url)
1514 function! s:QuickfixGet(nr, ...) abort
1516 return call('getqflist', a:000)
1518 return call('getloclist', [a:nr] + a:000)
1522 function! s:QuickfixSet(nr, ...) abort
1524 return call('setqflist', a:000)
1526 return call('setloclist', [a:nr] + a:000)
1530 function! s:QuickfixCreate(nr, opts) abort
1531 if has('patch-7.4.2200')
1532 call s:QuickfixSet(a:nr, [], ' ', a:opts)
1534 call s:QuickfixSet(a:nr, [], ' ')
1538 function! s:QuickfixOpen(nr, mods) abort
1539 let mods = substitute(s:Mods(a:mods), '\<\d*tab\>', '', '')
1540 return mods . (a:nr < 0 ? 'c' : 'l').'open' . (mods =~# '\<vertical\>' ? ' 20' : '')
1543 function! s:QuickfixStream(nr, event, title, cmd, first, mods, callback, ...) abort
1545 let opts = {'title': a:title, 'context': {'items': []}}
1546 call s:QuickfixCreate(a:nr, opts)
1547 let event = (a:nr < 0 ? 'c' : 'l') . 'fugitive-' . a:event
1548 exe s:DoAutocmd('QuickFixCmdPre ' . event)
1550 exe s:QuickfixOpen(a:nr, a:mods)
1556 let lines = s:SystemList(a:cmd)[0]
1558 call extend(buffer, call(a:callback, a:000 + [line]))
1559 if len(buffer) >= 20
1560 let contexts = map(copy(buffer), 'get(v:val, "context", {})')
1562 call extend(opts.context.items, contexts)
1564 call s:QuickfixSet(a:nr, remove(buffer, 0, -1), 'a')
1565 if a:mods !~# '\<silent\>'
1570 call extend(buffer, call(a:callback, a:000 + [0]))
1571 call extend(opts.context.items, map(copy(buffer), 'get(v:val, "context", {})'))
1572 lockvar opts.context.items
1573 call s:QuickfixSet(a:nr, buffer, 'a')
1575 exe s:DoAutocmd('QuickFixCmdPost ' . event)
1577 let list = s:QuickfixGet(a:nr)
1578 for index in range(len(list))
1579 if list[index].valid
1580 return (index+1) . (a:nr < 0 ? 'cfirst' : 'lfirst')
1587 function! fugitive#Cwindow() abort
1588 if &buftype == 'quickfix'
1592 if &buftype == 'quickfix'
1598 " Section: Repository Object
1600 let s:repo_prototype = {}
1602 function! fugitive#repo(...) abort
1603 let dir = a:0 ? s:GitDir(a:1) : (len(s:GitDir()) ? s:GitDir() : FugitiveExtractGitDir(expand('%:p')))
1605 return extend({'git_dir': dir, 'fugitive_dir': dir}, s:repo_prototype, 'keep')
1607 throw 'fugitive: not a Git repository'
1610 function! s:repo_dir(...) dict abort
1614 throw 'fugitive: fugitive#repo().dir("...") has been replaced by FugitiveFind(".git/...")'
1617 function! s:repo_tree(...) dict abort
1618 let tree = s:Tree(self.git_dir)
1620 throw 'fugitive: no work tree'
1624 throw 'fugitive: fugitive#repo().tree("...") has been replaced by FugitiveFind(":(top)...")'
1627 function! s:repo_bare() dict abort
1628 throw 'fugitive: fugitive#repo().bare() has been replaced by !empty(FugitiveWorkTree())'
1631 function! s:repo_find(object) dict abort
1632 throw 'fugitive: fugitive#repo().find(...) has been replaced by FugitiveFind(...)'
1635 function! s:repo_translate(rev) dict abort
1636 throw 'fugitive: fugitive#repo().translate(...) has been replaced by FugitiveFind(...)'
1639 function! s:repo_head(...) dict abort
1640 throw 'fugitive: fugitive#repo().head(...) has been replaced by FugitiveHead(...)'
1643 call s:add_methods('repo',['dir','tree','bare','find','translate','head'])
1645 function! s:repo_git_command(...) dict abort
1646 throw 'fugitive: fugitive#repo().git_command(...) has been replaced by FugitiveShellCommand(...)'
1649 function! s:repo_git_chomp(...) dict abort
1650 silent return substitute(system(fugitive#ShellCommand(a:000, self.git_dir)), '\n$', '', '')
1653 function! s:repo_git_chomp_in_tree(...) dict abort
1654 return call(self.git_chomp, a:000, self)
1657 function! s:repo_rev_parse(rev) dict abort
1658 throw 'fugitive: fugitive#repo().rev_parse(...) has been replaced by FugitiveExecute("rev-parse", "--verify", ...).stdout'
1661 call s:add_methods('repo',['git_command','git_chomp','git_chomp_in_tree','rev_parse'])
1663 function! s:repo_config(name) dict abort
1664 return FugitiveConfigGet(a:name, self.git_dir)
1667 call s:add_methods('repo',['config'])
1671 function! s:DirCommitFile(path) abort
1672 let vals = matchlist(s:Slash(a:path), s:dir_commit_file)
1676 return [s:Dir(fugitive#UrlDecode(vals[1])), vals[2], empty(vals[2]) ? '/.git/index' : fugitive#UrlDecode(vals[3])]
1679 function! s:DirRev(url) abort
1680 let [dir, commit, file] = s:DirCommitFile(a:url)
1681 return [dir, commit . file ==# '/.git/index' ? ':' : (!empty(dir) && commit =~# '^.$' ? ':' : '') . commit . substitute(file, '^/', ':', '')]
1684 function! fugitive#Parse(url) abort
1685 return reverse(s:DirRev(a:url))
1688 let s:merge_heads = ['MERGE_HEAD', 'REBASE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD']
1689 function! s:MergeHead(dir) abort
1690 let dir = fugitive#Find('.git/', a:dir)
1691 for head in s:merge_heads
1692 if filereadable(dir . head)
1699 function! s:Owner(path, ...) abort
1700 let dir = a:0 ? s:Dir(a:1) : s:Dir()
1704 let actualdir = fugitive#Find('.git/', dir)
1705 let [pdir, commit, file] = s:DirCommitFile(a:path)
1706 if s:SameRepo(dir, pdir)
1707 if commit =~# '^\x\{40,\}$'
1709 elseif commit ==# '2'
1711 elseif commit ==# '0'
1714 let merge_head = s:MergeHead(dir)
1715 if empty(merge_head)
1720 elseif commit ==# '1'
1721 return s:TreeChomp('merge-base', 'HEAD', merge_head, '--')
1724 let path = fnamemodify(a:path, ':p')
1725 if s:cpath(actualdir, strpart(path, 0, len(actualdir))) && a:path =~# 'HEAD$'
1726 return strpart(path, len(actualdir))
1728 let refs = fugitive#Find('.git/refs', dir)
1729 if s:cpath(refs . '/', path[0 : len(refs)]) && path !~# '[\/]$'
1730 return strpart(path, len(refs) - 4)
1735 function! fugitive#Real(url) abort
1739 let [dir, commit, file] = s:DirCommitFile(a:url)
1741 let tree = s:Tree(dir)
1742 return s:VimSlash((len(tree) ? tree : s:GitDir(dir)) . file)
1744 let pre = substitute(matchstr(a:url, '^\a\a\+\ze:'), '^.', '\u&', '')
1745 if len(pre) && pre !=? 'fugitive' && exists('*' . pre . 'Real')
1746 let url = {pre}Real(a:url)
1748 let url = fnamemodify(a:url, ':p' . (a:url =~# '[\/]$' ? '' : ':s?[\/]$??'))
1750 return s:VimSlash(empty(url) ? a:url : url)
1753 function! fugitive#Path(url, ...) abort
1757 let repo = call('s:Dir', a:000[1:-1])
1758 let dir_s = fugitive#Find('.git/', repo)
1759 let tree = fugitive#Find(':/', repo)
1761 return fugitive#Real(a:url)
1762 elseif a:1 =~# '\.$'
1763 let path = s:Slash(fugitive#Real(a:url))
1766 while s:cpath(tree . '/', (cwd . '/')[0 : len(tree)])
1767 if s:cpath(cwd . '/', path[0 : len(cwd)])
1768 if strpart(path, len(cwd) + 1) =~# '^\.git\%(/\|$\)'
1771 return a:1[0:-2] . (empty(lead) ? './' : lead) . strpart(path, len(cwd) + 1)
1773 let cwd = fnamemodify(cwd, ':h')
1776 return a:1[0:-2] . path
1779 let temp_state = s:TempState(url)
1780 if has_key(temp_state, 'origin_bufnr')
1781 let url = bufname(temp_state.origin_bufnr)
1783 let url = s:Slash(fnamemodify(url, ':p'))
1784 if url =~# '/$' && s:Slash(a:url) !~# '/$'
1787 let [argdir, commit, file] = s:DirCommitFile(url)
1788 if !empty(argdir) && !s:SameRepo(argdir, repo)
1790 elseif len(dir_s) && s:cpath(strpart(url, 0, len(dir_s)), dir_s)
1791 let file = '/.git' . strpart(url, len(dir_s)-1)
1792 elseif len(tree) && s:cpath(url[0 : len(tree)]) ==# s:cpath(tree . '/')
1793 let file = url[len(tree) : -1]
1794 elseif s:cpath(url) ==# s:cpath(tree)
1797 if empty(file) && a:1 =~# '^$\|^[.:]/$'
1798 return FugitiveGitPath(fugitive#Real(a:url))
1800 return substitute(file, '^/', '\=a:1', '')
1803 function! s:Relative(...) abort
1804 return fugitive#Path(@%, a:0 ? a:1 : ':(top)', a:0 > 1 ? a:2 : s:Dir())
1807 function! fugitive#Find(object, ...) abort
1808 if type(a:object) == type(0)
1809 let name = bufname(a:object)
1810 return s:VimSlash(name =~# '^$\|^/\|^\a\+:' ? name : getcwd() . '/' . name)
1811 elseif a:object =~# '^[~$]'
1812 let prefix = matchstr(a:object, '^[~$]\i*')
1813 let owner = expand(prefix)
1814 return s:VimSlash(FugitiveVimPath((len(owner) ? owner : prefix) . strpart(a:object, len(prefix))))
1816 let rev = s:Slash(a:object)
1817 if rev =~# '^\a\+://' && rev !~# '^fugitive:'
1819 elseif rev =~# '^$\|^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1820 return s:VimSlash(a:object)
1821 elseif rev =~# '^\.\.\=\%(/\|$\)'
1822 return s:VimSlash(simplify(getcwd() . '/' . a:object))
1824 let dir = call('s:GitDir', a:000)
1826 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\zs\%(\.\.\=$\|\.\.\=/.*\|/.*\|\w:/.*\)')
1827 let dir = FugitiveExtractGitDir(file)
1832 let tree = s:Tree(dir)
1833 let urlprefix = s:DirUrlPrefix(dir)
1834 let base = len(tree) ? tree : urlprefix . '0'
1836 let f = len(tree) && len(getftype(tree . '/.git')) ? tree . '/.git' : dir
1837 elseif rev =~# '^\.git/'
1838 let f = strpart(rev, 5)
1839 let fdir = simplify(FugitiveActualDir(dir) . '/')
1840 let cdir = simplify(FugitiveCommonDir(dir) . '/')
1841 if f =~# '^\.\./\.\.\%(/\|$\)'
1842 let f = simplify(len(tree) ? tree . f[2:-1] : fdir . f)
1843 elseif f =~# '^\.\.\%(/\|$\)'
1844 let f = s:PathJoin(base, f[2:-1])
1845 elseif cdir !=# fdir && (
1846 \ f =~# '^\%(config\|hooks\|info\|logs/refs\|objects\|refs\|worktrees\)\%(/\|$\)' ||
1847 \ f !~# '^\%(index$\|index\.lock$\|\w*MSG$\|\w*HEAD$\|logs/\w*HEAD$\|logs$\|rebase-\w\+\)\%(/\|$\)' &&
1848 \ getftime(fdir . f) < 0 && getftime(cdir . f) >= 0)
1849 let f = simplify(cdir . f)
1851 let f = simplify(fdir . f)
1855 elseif rev =~# '^\.\%(/\|$\)'
1856 let f = s:PathJoin(base, rev[1:-1])
1857 elseif rev =~# '^::\%(/\|\a\+\:\)'
1859 elseif rev =~# '^::\.\.\=\%(/\|$\)'
1860 let f = simplify(getcwd() . '/' . rev[2:-1])
1861 elseif rev =~# '^::'
1862 let f = s:PathJoin(base, '/' . rev[2:-1])
1863 elseif rev =~# '^:\%([0-3]:\)\=\.\.\=\%(/\|$\)\|^:[0-3]:\%(/\|\a\+:\)'
1864 let f = rev =~# '^:\%([0-3]:\)\=\.' ? simplify(getcwd() . '/' . matchstr(rev, '\..*')) : rev[3:-1]
1865 if s:cpath(base . '/', (f . '/')[0 : len(base)])
1866 let f = s:PathJoin(urlprefix, +matchstr(rev, '^:\zs\d\ze:') . '/' . strpart(f, len(base) + 1))
1868 let altdir = FugitiveExtractGitDir(f)
1869 if len(altdir) && !s:cpath(dir, altdir)
1870 return fugitive#Find(a:object, altdir)
1873 elseif rev =~# '^:[0-3]:'
1874 let f = s:PathJoin(urlprefix, rev[1] . '/' . rev[3:-1])
1877 elseif rev =~# '^:(\%(top\|top,literal\|literal,top\|literal\))'
1878 let f = matchstr(rev, ')\zs.*')
1879 if f=~# '^\.\.\=\%(/\|$\)'
1880 let f = simplify(getcwd() . '/' . f)
1881 elseif f !~# '^/\|^\%(\a\a\+:\).*\%(//\|::\)' . (has('win32') ? '\|^\a:/' : '')
1882 let f = s:PathJoin(base, '/' . f)
1884 elseif rev =~# '^:/\@!'
1885 let f = s:PathJoin(urlprefix, '0/' . rev[1:-1])
1888 let commit = matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\|^:.*')
1889 let file = substitute(matchstr(rev, '^\%([^:.-]\|\.\.[^/:]\)[^:]*\zs:.*'), '^:', '/', '')
1890 if file =~# '^/\.\.\=\%(/\|$\)\|^//\|^/\a\+:'
1891 let file = file =~# '^/\.' ? simplify(getcwd() . file) : file[1:-1]
1892 if s:cpath(base . '/', (file . '/')[0 : len(base)])
1893 let file = '/' . strpart(file, len(base) + 1)
1895 let altdir = FugitiveExtractGitDir(file)
1896 if len(altdir) && !s:cpath(dir, altdir)
1897 return fugitive#Find(a:object, altdir)
1902 let commits = split(commit, '\.\.\.-\@!', 1)
1903 if len(commits) == 2
1904 call map(commits, 'empty(v:val) ? "@" : v:val')
1905 let commit = matchstr(s:ChompDefault('', [dir, 'merge-base'] + commits + ['--']), '\<[0-9a-f]\{40,\}\>')
1907 if commit !~# '^[0-9a-f]\{40,\}$\|^$'
1908 let commit = matchstr(s:ChompDefault('', [dir, 'rev-parse', '--verify', commit . (len(file) ? '^{}' : ''), '--']), '\<[0-9a-f]\{40,\}\>')
1909 if empty(commit) && len(file)
1910 let commit = repeat('0', 40)
1914 let f = s:PathJoin(urlprefix, commit . file)
1916 let f = s:PathJoin(base, '/' . substitute(rev, '^:/:\=\|^[^:]\+:', '', ''))
1920 return s:VimSlash(f)
1923 function! s:Generate(object, ...) abort
1924 let dir = a:0 ? a:1 : s:Dir()
1925 let f = fugitive#Find(a:object, dir)
1928 elseif a:object ==# ':/'
1929 return len(dir) ? s:VimSlash(s:DirUrlPrefix(dir) . '0') : '.'
1931 let file = matchstr(a:object, '^\%(:\d:\|[^:]*:\)\=\zs.*')
1932 return empty(file) ? '' : fnamemodify(s:VimSlash(file), ':p')
1935 function! s:DotRelative(path, ...) abort
1936 let cwd = a:0 ? a:1 : getcwd()
1937 let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
1938 if len(cwd) && s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
1939 return '.' . strpart(path, len(cwd))
1944 function! fugitive#Object(...) abort
1945 let dir = a:0 > 1 ? s:Dir(a:2) : s:Dir()
1946 let [fdir, rev] = s:DirRev(a:0 ? a:1 : @%)
1947 if !s:SameRepo(dir, fdir)
1950 let tree = s:Tree(dir)
1951 let full = a:0 ? a:1 : s:BufName('%')
1952 let full = fnamemodify(full, ':p' . (s:Slash(full) =~# '/$' ? '' : ':s?/$??'))
1953 if empty(rev) && empty(tree)
1954 return FugitiveGitPath(full)
1956 let rev = fugitive#Path(full, './', dir)
1957 if rev =~# '^\./.git\%(/\|$\)'
1958 return FugitiveGitPath(full)
1961 if rev !~# '^\.\%(/\|$\)' || s:cpath(getcwd(), tree)
1964 return FugitiveGitPath(tree . rev[1:-1])
1968 let s:var = '\%(<\%(cword\|cWORD\|cexpr\|cfile\|sfile\|slnum\|afile\|abuf\|amatch' . (has('clientserver') ? '\|client' : '') . '\)>\|%\|#<\=\d\+\|##\=\)'
1969 let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
1970 let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
1971 let s:commit_expand = '!\\\@!#\=\d*\|!%'
1973 function! s:BufName(var) abort
1975 return bufname(get(s:TempState(), 'origin_bufnr', ''))
1976 elseif a:var =~# '^#\d*$'
1977 let nr = get(s:TempState(+a:var[1:-1]), 'origin_bufnr', '')
1978 return bufname(nr ? nr : +a:var[1:-1])
1980 return expand(a:var)
1984 function! s:ExpandVar(other, var, flags, esc, ...) abort
1985 let cwd = a:0 ? a:1 : getcwd()
1987 return a:other[1:-1]
1988 elseif a:other =~# '^'''
1989 return substitute(a:other[1:-2], "''", "'", "g")
1990 elseif a:other =~# '^"'
1991 return substitute(a:other[1:-2], '""', '"', "g")
1992 elseif a:other =~# '^[!`]'
1993 let buffer = s:BufName(a:other =~# '[0-9#]' ? '#' . matchstr(a:other, '\d\+') : '%')
1994 let owner = s:Owner(buffer)
1995 return len(owner) ? owner : '@'
1996 elseif a:other =~# '^\~[~.]$'
1997 return s:Slash(getcwd())
1999 return expand(a:other)
2000 elseif a:var ==# '<cfile>'
2001 let bufnames = [expand('<cfile>')]
2002 if get(maparg('<Plug><cfile>', 'c', 0, 1), 'expr')
2004 let bufnames = [eval(maparg('<Plug><cfile>', 'c'))]
2005 if bufnames[0] ==# "\<C-R>\<C-F>"
2006 let bufnames = [expand('<cfile>')]
2011 elseif a:var =~# '^<'
2012 let bufnames = [s:BufName(a:var)]
2013 elseif a:var ==# '##'
2014 let bufnames = map(argv(), 'fugitive#Real(v:val)')
2016 let bufnames = [fugitive#Real(s:BufName(a:var))]
2019 for bufname in bufnames
2021 let file = s:DotRelative(bufname, cwd)
2023 let flag = matchstr(flags, s:flag)
2024 let flags = strpart(flags, len(flag))
2026 let file = s:DotRelative(fugitive#Real(file), cwd)
2028 let file = fnamemodify(file, flag)
2031 let file = s:Slash(file)
2032 if file =~# '^fugitive://'
2033 let [dir, commit, file_candidate] = s:DirCommitFile(file)
2034 let tree = s:Tree(dir)
2035 if len(tree) && len(file_candidate)
2036 let file = (commit =~# '^.$' ? ':' : '') . commit . ':' .
2037 \ s:DotRelative(tree . file_candidate)
2038 elseif empty(file_candidate) && commit !~# '^.$'
2042 call add(files, len(a:esc) ? shellescape(file) : file)
2044 return join(files, "\1")
2048 let s:fnameescape = " \t\n*?`%#'\"|!<"
2050 let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
2053 function! s:Expand(rev, ...) abort
2054 if a:rev =~# '^>' && s:Slash(@%) =~# '^fugitive://' && empty(s:DirCommitFile(@%)[1])
2056 elseif a:rev =~# '^>\=:[0-3]$'
2057 let file = len(expand('%')) ? a:rev[-2:-1] . ':%' : '%'
2058 elseif a:rev =~# '^>\%(:\=/\)\=$'
2060 elseif a:rev =~# '^>[> ]\@!' && @% !~# '^fugitive:' && s:Slash(@%) =~# '://\|^$'
2062 elseif a:rev ==# '>:'
2063 let file = empty(s:DirCommitFile(@%)[0]) ? ':0:%' : '%'
2064 elseif a:rev =~# '^>[> ]\@!'
2065 let rev = (a:rev =~# '^>[~^]' ? '!' : '') . a:rev[1:-1]
2066 let prefix = matchstr(rev, '^\%(\\.\|{[^{}]*}\|[^:]\)*')
2070 let file = len(expand('%')) ? rev . ':%' : '%'
2072 elseif s:Slash(a:rev) =~# '^\a\a\+://'
2073 let file = substitute(a:rev, '\\\@<!\%(#\a\|%\x\x\)', '\\&', 'g')
2077 return substitute(file,
2078 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2079 \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),"", a:0 ? a:1 : getcwd()), "\1", " ")', 'g')
2082 function! fugitive#Expand(object) abort
2083 return substitute(a:object,
2084 \ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~.]\)\|' . s:expand,
2085 \ '\=tr(s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5)), "\1", " ")', 'g')
2088 function! s:SplitExpandChain(string, ...) abort
2090 let string = a:string
2091 let dquote = '"\%([^"]\|""\|\\"\)*"\|'
2092 let cwd = a:0 ? a:1 : getcwd()
2093 while string =~# '\S'
2094 if string =~# '^\s*|'
2095 return [list, substitute(string, '^\s*', '', '')]
2097 let arg = matchstr(string, '^\s*\%(' . dquote . '''[^'']*''\|\\.\|[^' . "\t" . ' |]\)\+')
2098 let string = strpart(string, len(arg))
2099 let arg = substitute(arg, '^\s\+', '', '')
2100 if !exists('seen_separator')
2101 let arg = substitute(arg, '^\%([^:.][^:]*:\|^:\%((literal)\)\=\|^:[0-3]:\)\=\zs\.\.\=\%(/.*\)\=$',
2102 \ '\=s:DotRelative(s:Slash(simplify(getcwd() . "/" . submatch(0))), cwd)', '')
2104 let arg = substitute(arg,
2105 \ '\(' . dquote . '''\%(''''\|[^'']\)*''\|\\[' . s:fnameescape . ']\|^\\[>+-]\|' . s:commit_expand . '\|^\~[~]\|^\~\w*\|\$\w\+\)\|' . s:expand,
2106 \ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5), cwd)', 'g')
2107 call extend(list, split(arg, "\1", 1))
2109 let seen_separator = 1
2117 function! s:TreeInfo(dir, commit) abort
2118 let key = s:GitDir(a:dir)
2119 if a:commit =~# '^:\=[0-3]$'
2120 let index = get(s:indexes, key, [])
2121 let newftime = getftime(fugitive#Find('.git/index', a:dir))
2122 if get(index, 0, -2) < newftime
2123 let [lines, exec_error] = s:LinesError([a:dir, 'ls-files', '--stage', '--'])
2124 let s:indexes[key] = [newftime, {'0': {}, '1': {}, '2': {}, '3': {}}]
2129 let [info, filename] = split(line, "\t")
2130 let [mode, sha, stage] = split(info, '\s\+')
2131 let s:indexes[key][1][stage][filename] = [newftime, mode, 'blob', sha, -2]
2132 while filename =~# '/'
2133 let filename = substitute(filename, '/[^/]*$', '', '')
2134 let s:indexes[key][1][stage][filename] = [newftime, '040000', 'tree', '', 0]
2138 return [get(s:indexes[key][1], a:commit[-1:-1], {}), newftime]
2139 elseif a:commit =~# '^\x\{40,\}$'
2140 if !has_key(s:trees, key)
2141 let s:trees[key] = {}
2143 if !has_key(s:trees[key], a:commit)
2144 let ftime = s:ChompDefault('', [a:dir, 'log', '-1', '--pretty=format:%ct', a:commit, '--'])
2146 let s:trees[key][a:commit] = [{}, -1]
2147 return s:trees[key][a:commit]
2149 let s:trees[key][a:commit] = [{}, +ftime]
2150 let [lines, exec_error] = s:LinesError([a:dir, 'ls-tree', '-rtl', '--full-name', a:commit, '--'])
2152 return s:trees[key][a:commit]
2155 let [info, filename] = split(line, "\t")
2156 let [mode, type, sha, size] = split(info, '\s\+')
2157 let s:trees[key][a:commit][0][filename] = [+ftime, mode, type, sha, +size, filename]
2160 return s:trees[key][a:commit]
2165 function! s:PathInfo(url) abort
2166 let [dir, commit, file] = s:DirCommitFile(a:url)
2167 if empty(dir) || !get(g:, 'fugitive_file_api', 1)
2168 return [-1, '000000', '', '', -1]
2170 let path = substitute(file[1:-1], '/*$', '', '')
2171 let [tree, ftime] = s:TreeInfo(dir, commit)
2172 let entry = empty(path) ? [ftime, '040000', 'tree', '', -1] : get(tree, path, [])
2173 if empty(entry) || file =~# '/$' && entry[2] !=# 'tree'
2174 return [-1, '000000', '', '', -1]
2180 function! fugitive#simplify(url) abort
2181 let [dir, commit, file] = s:DirCommitFile(a:url)
2184 elseif empty(commit)
2185 return s:VimSlash(s:DirUrlPrefix(simplify(s:GitDir(dir))))
2187 if file =~# '/\.\.\%(/\|$\)'
2188 let tree = s:Tree(dir)
2190 let path = simplify(tree . file)
2191 if strpart(path . '/', 0, len(tree) + 1) !=# tree . '/'
2192 return s:VimSlash(path)
2196 return s:VimSlash(s:PathJoin(s:DirUrlPrefix(simplify(s:GitDir(dir))), commit . simplify(file)))
2199 function! fugitive#resolve(url) abort
2200 let url = fugitive#simplify(a:url)
2201 if url =~? '^fugitive:'
2208 function! fugitive#getftime(url) abort
2209 return s:PathInfo(a:url)[0]
2212 function! fugitive#getfsize(url) abort
2213 let entry = s:PathInfo(a:url)
2214 if entry[4] == -2 && entry[2] ==# 'blob' && len(entry[3])
2215 let dir = s:DirCommitFile(a:url)[0]
2216 let entry[4] = +s:ChompDefault(-1, [dir, 'cat-file', '-s', entry[3]])
2221 function! fugitive#getftype(url) abort
2222 return get({'tree': 'dir', 'blob': 'file'}, s:PathInfo(a:url)[2], '')
2225 function! fugitive#filereadable(url) abort
2226 return s:PathInfo(a:url)[2] ==# 'blob'
2229 function! fugitive#filewritable(url) abort
2230 let [dir, commit, file] = s:DirCommitFile(a:url)
2231 if commit !~# '^\d$' || !filewritable(fugitive#Find('.git/index', dir))
2234 return s:PathInfo(a:url)[2] ==# 'blob' ? 1 : 2
2237 function! fugitive#isdirectory(url) abort
2238 return s:PathInfo(a:url)[2] ==# 'tree'
2241 function! fugitive#getfperm(url) abort
2242 let [dir, commit, file] = s:DirCommitFile(a:url)
2243 let perm = getfperm(dir)
2244 let fperm = s:PathInfo(a:url)[1]
2245 if fperm ==# '040000'
2246 let fperm = '000755'
2249 let perm = tr(perm, 'x', '-')
2251 if fperm !~# '[45]$'
2252 let perm = tr(perm, 'rw', '--')
2254 if commit !~# '^\d$'
2255 let perm = tr(perm, 'w', '-')
2257 return perm ==# '---------' ? '' : perm
2260 function! s:UpdateIndex(dir, info) abort
2261 let info = join(a:info[0:-2]) . "\t" . a:info[-1] . "\n"
2262 let [error, exec_error] = s:StdoutToFile('', [a:dir, 'update-index', '--index-info'], info)
2263 return !exec_error ? '' : len(error) ? error : 'unknown update-index error'
2266 function! fugitive#setfperm(url, perm) abort
2267 let [dir, commit, file] = s:DirCommitFile(a:url)
2268 let entry = s:PathInfo(a:url)
2269 let perm = fugitive#getfperm(a:url)
2270 if commit !~# '^\d$' || entry[2] !=# 'blob' ||
2271 \ substitute(perm, 'x', '-', 'g') !=# substitute(a:perm, 'x', '-', 'g')
2274 let error = s:UpdateIndex(dir, [a:perm =~# 'x' ? '000755' : '000644', entry[3], commit, file[1:-1]])
2275 return len(error) ? -1 : 0
2278 if !exists('s:blobdirs')
2281 function! s:BlobTemp(url) abort
2282 let [dir, commit, file] = s:DirCommitFile(a:url)
2286 let key = s:GitDir(dir)
2287 if !has_key(s:blobdirs, key)
2288 let s:blobdirs[key] = tempname()
2290 let tempfile = s:blobdirs[key] . '/' . commit . file
2291 let tempparent = fnamemodify(tempfile, ':h')
2292 if !isdirectory(tempparent)
2293 call mkdir(tempparent, 'p')
2294 elseif isdirectory(tempfile)
2295 if commit =~# '^\d$' && has('patch-7.4.1107')
2296 call delete(tempfile, 'rf')
2301 if commit =~# '^\d$' || !filereadable(tempfile)
2302 let rev = s:DirRev(a:url)[1]
2303 let blob_or_filters = fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
2304 let exec_error = s:StdoutToFile(tempfile, [dir, 'cat-file', blob_or_filters, rev])[1]
2306 call delete(tempfile)
2310 return s:Resolve(tempfile)
2313 function! fugitive#readfile(url, ...) abort
2314 let entry = s:PathInfo(a:url)
2315 if entry[2] !=# 'blob'
2318 let temp = s:BlobTemp(a:url)
2322 return call('readfile', [temp] + a:000)
2325 function! fugitive#writefile(lines, url, ...) abort
2326 let url = type(a:url) ==# type('') ? a:url : ''
2327 let [dir, commit, file] = s:DirCommitFile(url)
2328 let entry = s:PathInfo(url)
2329 if commit =~# '^\d$' && entry[2] !=# 'tree'
2330 let temp = tempname()
2331 if a:0 && a:1 =~# 'a' && entry[2] ==# 'blob'
2332 call writefile(fugitive#readfile(url, 'b'), temp, 'b')
2334 call call('writefile', [a:lines, temp] + a:000)
2335 let hash = s:ChompDefault('', [dir, '--literal-pathspecs', 'hash-object', '-w', FugitiveGitPath(temp)])
2336 let mode = entry[1] !=# '000000' ? entry[1] : '100644'
2337 if hash =~# '^\x\{40,\}$'
2338 let error = s:UpdateIndex(dir, [mode, hash, commit, file[1:-1]])
2344 return call('writefile', [a:lines, a:url] + a:000)
2348 \ '/**/': '/\%([^./][^/]*/\)*',
2349 \ '/**': '/\%([^./][^/]\+/\)*[^./][^/]*',
2350 \ '**/': '[^/]*\%(/[^./][^/]*\)*',
2352 \ '/*': '/[^/.][^/]*',
2355 function! fugitive#glob(url, ...) abort
2356 let [repo, commit, glob] = s:DirCommitFile(a:url)
2357 let dirglob = s:GitDir(repo)
2358 let append = matchstr(glob, '/*$')
2359 let glob = substitute(glob, '/*$', '', '')
2360 let pattern = '^' . substitute(glob, '/\=\*\*/\=\|/\=\*\|[.?\$]\|^^', '\=get(s:globsubs, submatch(0), "\\" . submatch(0))', 'g')[1:-1] . '$'
2362 for dir in dirglob =~# '[*?]' ? split(glob(dirglob), "\n") : [dirglob]
2363 if empty(dir) || !get(g:, 'fugitive_file_api', 1) || !filereadable(fugitive#Find('.git/HEAD', dir))
2366 let files = items(s:TreeInfo(dir, commit)[0])
2368 call filter(files, 'v:val[1][2] ==# "tree"')
2370 call map(files, 'v:val[0]')
2371 call filter(files, 'v:val =~# pattern')
2372 let prepend = s:DirUrlPrefix(dir) . substitute(commit, '^:', '', '') . '/'
2374 call map(files, 's:VimSlash(s:PathJoin(prepend, v:val . append))')
2375 call extend(results, files)
2380 return join(results, "\n")
2384 function! fugitive#delete(url, ...) abort
2385 let [dir, commit, file] = s:DirCommitFile(a:url)
2386 if a:0 && len(a:1) || commit !~# '^\d$'
2389 let entry = s:PathInfo(a:url)
2390 if entry[2] !=# 'blob'
2393 let error = s:UpdateIndex(dir, ['000000', '0000000000000000000000000000000000000000', commit, file[1:-1]])
2394 return len(error) ? -1 : 0
2397 " Section: Completion
2399 function! s:FilterEscape(items, ...) abort
2400 let items = copy(a:items)
2401 call map(items, 'fnameescape(v:val)')
2402 if !a:0 || type(a:1) != type('')
2405 let match = substitute(a:1, '^[+>]\|\\\@<![' . substitute(s:fnameescape, '\\', '', '') . ']', '\\&', 'g')
2407 let cmp = s:FileIgnoreCase(1) ? '==?' : '==#'
2408 return filter(items, 'strpart(v:val, 0, strlen(match)) ' . cmp . ' match')
2411 function! s:GlobComplete(lead, pattern, ...) abort
2415 let results = glob(substitute(a:lead . a:pattern, '[\{}]', '\\&', 'g'), a:0 ? a:1 : 0, 1)
2417 call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val')
2418 call map(results, 'v:val[ strlen(a:lead) : -1 ]')
2422 function! fugitive#CompletePath(base, ...) abort
2423 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2424 let stripped = matchstr(a:base, '^\%(:/:\=\|:(top)\|:(top,literal)\|:(literal,top)\)')
2425 let base = strpart(a:base, len(stripped))
2426 if len(stripped) || a:0 < 4
2427 let root = s:Tree(dir)
2431 if root !=# '/' && len(root)
2435 let stripped = matchstr(a:base, '^\%(:(literal)\|:\)')
2436 let base = strpart(a:base, len(stripped))
2438 if base =~# '^\.git/' && len(dir)
2439 let pattern = s:gsub(base[5:-1], '/', '*&').'*'
2440 let fdir = fugitive#Find('.git/', dir)
2441 let matches = s:GlobComplete(fdir, pattern)
2442 let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2443 if len(cdir) && s:cpath(fdir) !=# s:cpath(cdir)
2444 call extend(matches, s:GlobComplete(cdir, pattern))
2446 call s:Uniq(matches)
2447 call map(matches, "'.git/' . v:val")
2448 elseif base =~# '^\~/'
2449 let matches = map(s:GlobComplete(expand('~/'), base[2:-1] . '*'), '"~/" . v:val')
2450 elseif a:base =~# '^/\|^\a\+:\|^\.\.\=/'
2451 let matches = s:GlobComplete('', base . '*')
2453 let matches = s:GlobComplete(root, s:gsub(base, '/', '*&').'*')
2457 call map(matches, 's:fnameescape(s:Slash(stripped . v:val))')
2461 function! fugitive#PathComplete(...) abort
2462 return call('fugitive#CompletePath', a:000)
2465 function! s:CompleteHeads(dir) abort
2469 let dir = fugitive#Find('.git/', a:dir)
2470 return sort(filter(['HEAD', 'FETCH_HEAD', 'ORIG_HEAD'] + s:merge_heads, 'filereadable(dir . v:val)')) +
2471 \ sort(s:LinesError([a:dir, 'rev-parse', '--symbolic', '--branches', '--tags', '--remotes'])[0])
2474 function! fugitive#CompleteObject(base, ...) abort
2475 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? a:3 : s:Dir()
2476 let tree = s:Tree(dir)
2479 if len(tree) && s:cpath(tree . '/', cwd[0 : len(tree)])
2480 let subdir = strpart(cwd, len(tree) + 1) . '/'
2482 let base = s:Expand(a:base)
2484 if a:base =~# '^!\d*$' && base !~# '^!'
2486 elseif base =~# '^\.\=/\|^:(' || base !~# ':'
2488 if base =~# '^refs/'
2489 let cdir = fugitive#Find('.git/refs', dir)[0 : -5]
2490 let results += map(s:GlobComplete(cdir, base . '*'), 's:Slash(v:val)')
2491 call map(results, 's:fnameescape(v:val)')
2492 elseif base !~# '^\.\=/\|^:('
2493 let heads = s:CompleteHeads(dir)
2494 if filereadable(fugitive#Find('.git/refs/stash', dir))
2495 let heads += ["stash"]
2496 let heads += sort(s:LinesError(["stash","list","--pretty=format:%gd"], dir)[0])
2498 let results += s:FilterEscape(heads, fnameescape(base))
2500 let results += a:0 == 1 || a:0 >= 3 ? fugitive#CompletePath(base, 0, '', dir, a:0 >= 4 ? a:4 : tree) : fugitive#CompletePath(base)
2503 elseif base =~# '^:'
2504 let entries = s:LinesError(['ls-files','--stage'], dir)[0]
2506 call map(entries, 'substitute(v:val, "\\M\t\\zs" . subdir, "./", "")')
2508 call map(entries,'s:sub(v:val,".*(\\d)\\t(.*)",":\\1:\\2")')
2509 if base !~# '^:[0-3]\%(:\|$\)'
2510 call filter(entries,'v:val[1] == "0"')
2511 call map(entries,'v:val[2:-1]')
2515 let parent = matchstr(base, '.*[:/]')
2516 let entries = s:LinesError(['ls-tree', substitute(parent, ':\zs\./', '\=subdir', '')], dir)[0]
2517 call map(entries,'s:sub(v:val,"^04.*\\zs$","/")')
2518 call map(entries,'parent.s:sub(v:val,".*\t","")')
2520 return s:FilterEscape(entries, fnameescape(base))
2523 function! s:CompleteSub(subcommand, A, L, P, ...) abort
2524 let pre = strpart(a:L, 0, a:P)
2526 return fugitive#CompletePath(a:A)
2527 elseif a:A =~# '^-' || a:A is# 0
2528 return s:FilterEscape(split(s:ChompDefault('', [a:subcommand, '--git-completion-helper']), ' '), a:A)
2530 return fugitive#CompleteObject(a:A, s:Dir())
2531 elseif type(a:1) == type(function('tr'))
2532 return call(a:1, [a:A, a:L, a:P] + (a:0 > 1 ? a:2 : []))
2534 return s:FilterEscape(a:1, a:A)
2538 function! s:CompleteRevision(A, L, P, ...) abort
2539 return s:FilterEscape(s:CompleteHeads(a:0 ? a:1 : s:Dir()), a:A)
2542 function! s:CompleteRemote(A, L, P, ...) abort
2543 let dir = a:0 ? a:1 : s:Dir()
2544 let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
2546 let matches = s:LinesError([dir, 'ls-remote', remote])[0]
2547 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
2548 call map(matches, 's:sub(v:val, "^.*\t%(refs/%(heads/|tags/)=)=", "")')
2550 let matches = s:LinesError([dir, 'remote'])[0]
2552 return s:FilterEscape(matches, a:A)
2555 " Section: Buffer auto-commands
2557 augroup fugitive_dummy_events
2559 autocmd User Fugitive* "
2560 autocmd BufWritePre,FileWritePre,FileWritePost * "
2561 autocmd BufNewFile * "
2562 autocmd QuickfixCmdPre,QuickfixCmdPost * "
2565 function! s:ReplaceCmd(cmd) abort
2566 let temp = tempname()
2567 let [err, exec_error] = s:StdoutToFile(temp, a:cmd)
2569 throw 'fugitive: ' . (len(err) ? substitute(err, "\n$", '', '') : 'unknown error running ' . string(a:cmd))
2572 silent exe 'lockmarks keepalt noautocmd 0read ++edit' s:fnameescape(temp)
2573 if &foldenable && foldlevel('$') > 0
2575 silent keepjumps $delete _
2578 silent keepjumps $delete _
2581 if s:cpath(s:AbsoluteVimPath(bufnr('$')), temp)
2582 silent! noautocmd execute bufnr('$') . 'bwipeout'
2586 function! s:FormatLog(dict) abort
2587 return a:dict.commit . ' ' . a:dict.subject
2590 function! s:FormatRebase(dict) abort
2591 return a:dict.status . ' ' . a:dict.commit . ' ' . a:dict.subject
2594 function! s:FormatFile(dict) abort
2595 return a:dict.status . ' ' . a:dict.filename
2598 function! s:Format(val) abort
2599 if type(a:val) == type({})
2600 return s:Format{a:val.type}(a:val)
2601 elseif type(a:val) == type([])
2602 return map(copy(a:val), 's:Format(v:val)')
2608 function! s:AddHeader(to, key, value) abort
2612 call add(a:to.lines, a:key . ':' . (len(a:value) ? ' ' . a:value : ''))
2615 function! s:AddSection(to, label, lines, ...) abort
2616 let note = a:0 ? a:1 : ''
2617 if empty(a:lines) && empty(note)
2620 call extend(a:to.lines, ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
2623 function! s:AddDiffSection(to, stat, label, files) abort
2627 let diff_section = a:stat.diff[a:label]
2628 let expanded = a:stat.expanded[a:label]
2629 let was_expanded = get(getbufvar(a:stat.bufnr, 'fugitive_expanded', {}), a:label, {})
2630 call extend(a:to.lines, ['', a:label . ' (' . len(a:files) . ')'])
2632 call add(a:to.lines, s:Format(file))
2633 if has_key(was_expanded, file.filename)
2634 let [diff, start] = s:StageInlineGetDiff(diff_section, file)
2636 let expanded[file.filename] = [start]
2637 call extend(a:to.lines, diff)
2643 function! s:QueryLog(refspec, limit, dir) abort
2644 let [log, exec_error] = s:LinesError(['log', '-n', '' . a:limit, '--pretty=format:%h%x09%s'] + a:refspec + ['--'], a:dir)
2645 call map(log, 'split(v:val, "\t", 1)')
2646 call map(log, '{"type": "Log", "commit": v:val[0], "subject": join(v:val[1 : -1], "\t")}')
2647 let result = {'error': exec_error ? 1 : 0, 'overflow': 0, 'entries': log}
2648 if len(log) == a:limit
2649 call remove(log, -1)
2650 let result.overflow = 1
2655 function! s:QueryLogRange(old, new, dir) abort
2656 if empty(a:old) || empty(a:new)
2657 return {'error': 2, 'overflow': 0, 'entries': []}
2659 return s:QueryLog([a:old . '..' . a:new], 256, a:dir)
2662 function! s:AddLogSection(to, label, log) abort
2663 if empty(a:log.entries)
2666 let label = a:label . ' (' . len(a:log.entries) . (a:log.overflow ? '+' : '') . ')'
2667 call extend(a:to.lines, ['', label] + s:Format(a:log.entries))
2670 let s:rebase_abbrevs = {
2684 function! s:MapStatus() abort
2685 call fugitive#MapJumps()
2686 call s:Map('n', '-', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2687 call s:Map('x', '-', ":<C-U>execute <SID>Do('Toggle',1)<CR>", '<silent>')
2688 call s:Map('n', 's', ":<C-U>execute <SID>Do('Stage',0)<CR>", '<silent>')
2689 call s:Map('x', 's', ":<C-U>execute <SID>Do('Stage',1)<CR>", '<silent>')
2690 call s:Map('n', 'u', ":<C-U>execute <SID>Do('Unstage',0)<CR>", '<silent>')
2691 call s:Map('x', 'u', ":<C-U>execute <SID>Do('Unstage',1)<CR>", '<silent>')
2692 call s:Map('n', 'U', ":<C-U>Git reset -q<CR>", '<silent>')
2693 call s:MapMotion('gu', "exe <SID>StageJump(v:count, 'Untracked', 'Unstaged')")
2694 call s:MapMotion('gU', "exe <SID>StageJump(v:count, 'Unstaged', 'Untracked')")
2695 call s:MapMotion('gs', "exe <SID>StageJump(v:count, 'Staged')")
2696 call s:MapMotion('gp', "exe <SID>StageJump(v:count, 'Unpushed')")
2697 call s:MapMotion('gP', "exe <SID>StageJump(v:count, 'Unpulled')")
2698 call s:MapMotion('gr', "exe <SID>StageJump(v:count, 'Rebasing')")
2699 call s:Map('n', 'C', ":echoerr 'fugitive: C has been removed in favor of cc'<CR>", '<silent><unique>')
2700 call s:Map('n', 'a', ":<C-U>execute <SID>Do('Toggle',0)<CR>", '<silent>')
2701 call s:Map('n', 'i', ":<C-U>execute <SID>NextExpandedHunk(v:count1)<CR>", '<silent>')
2702 call s:Map('n', "=", ":<C-U>execute <SID>StageInline('toggle',line('.'),v:count)<CR>", '<silent>')
2703 call s:Map('n', "<", ":<C-U>execute <SID>StageInline('hide', line('.'),v:count)<CR>", '<silent>')
2704 call s:Map('n', ">", ":<C-U>execute <SID>StageInline('show', line('.'),v:count)<CR>", '<silent>')
2705 call s:Map('x', "=", ":<C-U>execute <SID>StageInline('toggle',line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2706 call s:Map('x', "<", ":<C-U>execute <SID>StageInline('hide', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2707 call s:Map('x', ">", ":<C-U>execute <SID>StageInline('show', line(\"'<\"),line(\"'>\")-line(\"'<\")+1)<CR>", '<silent>')
2708 call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
2709 call s:Map('n', 'dd', ":<C-U>execute <SID>StageDiff('Gdiffsplit')<CR>", '<silent>')
2710 call s:Map('n', 'dh', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2711 call s:Map('n', 'ds', ":<C-U>execute <SID>StageDiff('Ghdiffsplit')<CR>", '<silent>')
2712 call s:Map('n', 'dp', ":<C-U>execute <SID>StageDiffEdit()<CR>", '<silent>')
2713 call s:Map('n', 'dv', ":<C-U>execute <SID>StageDiff('Gvdiffsplit')<CR>", '<silent>')
2714 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
2715 call s:Map('n', 'P', ":<C-U>execute <SID>StagePatch(line('.'),line('.')+v:count1-1)<CR>", '<silent>')
2716 call s:Map('x', 'P', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2717 call s:Map('n', 'p', ":<C-U>if v:count<Bar>silent exe <SID>GF('pedit')<Bar>else<Bar>echoerr 'Use = for inline diff, I for :Git add/reset --patch, 1p for :pedit'<Bar>endif<CR>", '<silent>')
2718 call s:Map('x', 'p', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"))<CR>", '<silent>')
2719 call s:Map('n', 'I', ":<C-U>execute <SID>StagePatch(line('.'),line('.'), 1)<CR>", '<silent>')
2720 call s:Map('x', 'I', ":<C-U>execute <SID>StagePatch(line(\"'<\"),line(\"'>\"), 1)<CR>", '<silent>')
2721 call s:Map('n', 'gq', ":<C-U>if bufnr('$') == 1<Bar>quit<Bar>else<Bar>bdelete<Bar>endif<CR>", '<silent>')
2722 call s:Map('n', 'R', ":echohl WarningMsg<Bar>echo 'Reloading is automatic. Use :e to force'<Bar>echohl NONE<CR>", '<silent>')
2723 call s:Map('n', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2724 call s:Map('x', 'g<Bar>', ":<C-U>echoerr 'Changed to X'<CR>", '<silent><unique>')
2725 call s:Map('n', 'X', ":<C-U>execute <SID>StageDelete(line('.'), 0, v:count)<CR>", '<silent>')
2726 call s:Map('x', 'X', ":<C-U>execute <SID>StageDelete(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2727 call s:Map('n', 'gI', ":<C-U>execute <SID>StageIgnore(line('.'), line('.'), v:count)<CR>", '<silent>')
2728 call s:Map('x', 'gI', ":<C-U>execute <SID>StageIgnore(line(\"'<\"), line(\"'>\"), v:count)<CR>", '<silent>')
2729 call s:Map('n', '.', ':<C-U> <C-R>=<SID>StageArgs(0)<CR><Home>')
2730 call s:Map('x', '.', ':<C-U> <C-R>=<SID>StageArgs(1)<CR><Home>')
2733 function! s:StatusProcess(result, stat) abort
2735 let status_exec = a:stat.status
2736 let config = a:stat.config
2737 let dir = s:Dir(config)
2739 let [staged, unstaged, untracked] = [[], [], []]
2742 if empty(status_exec)
2743 let stat.branch = FugitiveHead(0, config)
2745 elseif status_exec.exit_status
2746 let stat.error = s:JoinChomp(status_exec.stderr)
2749 elseif status_exec.args[-1] ==# '--porcelain=v2'
2750 let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2752 while i < len(output)
2753 let line = output[i]
2754 let prop = matchlist(line, '# \(\S\+\) \(.*\)')
2756 let stat.props[prop[1]] = prop[2]
2757 elseif line[0] ==# '?'
2758 call add(untracked, {'type': 'File', 'status': line[0], 'filename': line[2:-1], 'relative': [line[2:-1]]})
2759 elseif line[0] !=# '#'
2761 let file = matchstr(line, '^.\{37\} \x\{40,\} \x\{40,\} \x\{40,\} \zs.*$')
2763 let file = matchstr(line, '^.\{30\} \x\{40,\} \x\{40,\} \zs.*$')
2767 let file = matchstr(file, ' \zs.*')
2768 let relative = [file, output[i]]
2770 let relative = [file]
2772 let filename = join(reverse(copy(relative)), ' -> ')
2773 let sub = matchstr(line, '^[12u] .. \zs....')
2775 call add(staged, {'type': 'File', 'status': line[2], 'filename': filename, 'relative': relative, 'submodule': sub})
2778 let sub = matchstr(line, '^[12u] .. \zs....')
2779 call add(unstaged, {'type': 'File', 'status': get({'C':'M','M':'?','U':'?'}, matchstr(sub, 'S\.*\zs[CMU]'), line[3]), 'filename': file, 'relative': [file], 'submodule': sub})
2784 let stat.branch = substitute(get(stat.props, 'branch.head', '(unknown)'), '\C^(\%(detached\|unknown\))$', '', '')
2787 let output = split(tr(join(status_exec.stdout, "\1"), "\1\n", "\n\1"), "\1", 1)[0:-2]
2788 while get(output, 0, '') =~# '^\l\+:'
2789 call remove(output, 0)
2791 let branch = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
2792 if branch =~# '\.\.\.'
2793 let stat.branch = split(branch, '\.\.\.')[0]
2795 let stat.branch = branch ==# 'HEAD' ? '' : branch
2799 while i < len(output)
2800 let line = output[i]
2801 let file = line[3:-1]
2806 if line[0:1] =~# '[RC]'
2807 let relative = [file, output[i]]
2810 let relative = [file]
2812 let filename = join(reverse(copy(relative)), ' -> ')
2813 if line[0] !~# '[ ?!#]'
2814 call add(staged, {'type': 'File', 'status': line[0], 'filename': filename, 'relative': relative, 'submodule': ''})
2816 if line[0:1] ==# '??'
2817 call add(untracked, {'type': 'File', 'status': line[1], 'filename': filename, 'relative': relative})
2818 elseif line[1] !~# '[ !#]'
2819 call add(unstaged, {'type': 'File', 'status': line[1], 'filename': file, 'relative': [file], 'submodule': ''})
2824 let diff_cmd = stat.cmd + ['-c', 'diff.suppressBlankEmpty=false', '-c', 'core.quotePath=false', 'diff', '--color=never', '--no-ext-diff', '--no-prefix']
2825 let stat.diff = {'Staged': {'stdout': ['']}, 'Unstaged': {'stdout': ['']}}
2827 let stat.diff['Staged'] = fugitive#Execute(diff_cmd + ['--cached'], function('len'))
2830 let stat.diff['Unstaged'] = fugitive#Execute(diff_cmd + ['--'] + map(copy(unstaged), 'stat.work_tree . "/" . v:val.relative[0]'), function('len'))
2833 let [stat.staged, stat.unstaged, stat.untracked] = [staged, unstaged, untracked]
2835 let stat.files = {'Staged': {}, 'Unstaged': {}}
2837 let stat.files['Staged'][dict.filename] = dict
2839 for dict in unstaged
2840 let stat.files['Unstaged'][dict.filename] = dict
2843 let branch = stat.branch
2844 let fetch_remote = config.Get('branch.' . branch . '.remote', 'origin')
2845 let push_remote = config.Get('branch.' . branch . '.pushRemote',
2846 \ config.Get('remote.pushDefault', fetch_remote))
2847 if fetch_remote !=# '.' && empty(config.Get('remote.' . fetch_remote . '.fetch'))
2848 let fetch_remote = ''
2850 if push_remote !=# '.' && empty(config.Get('remote.' . push_remote . '.push', config.Get('remote.' . push_remote . '.fetch')))
2851 let push_remote = ''
2853 let stat.fetch_remote = fetch_remote
2854 let stat.push_remote = push_remote
2856 if empty(stat.fetch_remote) || empty(branch)
2859 let stat.merge = config.Get('branch.' . branch . '.merge')
2862 let push_default = FugitiveConfigGet('push.default', config)
2863 if empty(push_default)
2864 let push_default = fugitive#GitVersion(2) ? 'simple' : 'matching'
2866 if push_default ==# 'upstream'
2867 let stat.push = stat.merge
2868 elseif empty(stat.push_remote) || empty(branch)
2871 let stat.push = 'refs/heads/' . branch
2874 let stat.pull_type = 'Pull'
2876 let rebase = FugitiveConfigGet('branch.' . branch . '.rebase', config)
2878 let rebase = FugitiveConfigGet('pull.rebase', config)
2880 if rebase =~# '^\%(true\|yes\|on\|1\|interactive\|merges\|preserve\)$'
2881 let stat.pull_type = 'Rebase'
2882 elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
2883 let stat.pull_type = 'Merge'
2889 function! s:StatusRender(stat) abort
2892 call fugitive#Wait(stat.running)
2893 if has_key(stat, 'error')
2894 return 'echoerr ' . string('fugitive: ' . stat.error)
2896 let [staged, unstaged, untracked, config] = [stat.staged, stat.unstaged, stat.untracked, stat.config]
2897 let dir = s:Dir(config)
2899 let pull_ref = stat.merge
2900 if stat.fetch_remote !=# '.'
2901 let pull_ref = substitute(pull_ref, '^refs/heads/', 'refs/remotes/' . stat.fetch_remote . '/', '')
2904 let push_ref = stat.push
2905 if stat.push_remote !=# '.'
2906 let push_ref = substitute(push_ref, '^refs/heads/', 'refs/remotes/' . stat.push_remote . '/', '')
2909 let push_short = substitute(push_ref, '^refs/\w\+/', '', '')
2910 let pull_short = substitute(pull_ref, '^refs/\w\+/', '', '')
2912 if isdirectory(fugitive#Find('.git/rebase-merge/', dir))
2913 let rebasing_dir = fugitive#Find('.git/rebase-merge/', dir)
2914 elseif isdirectory(fugitive#Find('.git/rebase-apply/', dir))
2915 let rebasing_dir = fugitive#Find('.git/rebase-apply/', dir)
2918 call fugitive#Wait(stat.rev_parse)
2919 let head = empty(stat.branch) ? stat.rev_parse.stdout[0] : stat.branch
2922 let rebasing_head = 'detached HEAD'
2923 if exists('rebasing_dir') && filereadable(rebasing_dir . 'git-rebase-todo')
2924 let rebasing_head = substitute(readfile(rebasing_dir . 'head-name')[0], '\C^refs/heads/', '', '')
2925 let len = len(stat.rev_parse.stdout[0])
2926 let lines = readfile(rebasing_dir . 'git-rebase-todo')
2927 if getfsize(rebasing_dir . 'done') > 0
2928 let done = readfile(rebasing_dir . 'done')
2929 call map(done, 'substitute(v:val, ''^\l\+\>'', "done", "")')
2930 let done[-1] = substitute(done[-1], '^\l\+\>', 'stop', '')
2931 let lines = done + lines
2935 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2936 if len(match) && match[1] !~# 'exec\|merge\|label'
2937 call add(rebasing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': strpart(match[2], 0, len), 'subject': match[3]})
2943 if filereadable(fugitive#Find('.git/sequencer/todo', dir))
2944 for line in reverse(readfile(fugitive#Find('.git/sequencer/todo', dir)))
2945 let match = matchlist(line, '^\(\l\+\)\s\+\(\x\{4,\}\)\s\+\(.*\)')
2946 if len(match) && match[1] !~# 'exec\|merge\|label'
2947 call add(sequencing, {'type': 'Rebase', 'status': get(s:rebase_abbrevs, match[1], match[1]), 'commit': match[2], 'subject': match[3]})
2950 elseif filereadable(fugitive#Find('.git/MERGE_MSG', dir))
2951 if filereadable(fugitive#Find('.git/CHERRY_PICK_HEAD', dir))
2952 let pick_head = fugitive#Execute(['rev-parse', '--short', 'CHERRY_PICK_HEAD', '--'], dir).stdout[0]
2953 call add(sequencing, {'type': 'Rebase', 'status': 'pick', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2954 elseif filereadable(fugitive#Find('.git/REVERT_HEAD', dir))
2955 let pick_head = fugitive#Execute(['rev-parse', '--short', 'REVERT_HEAD', '--'], dir).stdout[0]
2956 call add(sequencing, {'type': 'Rebase', 'status': 'revert', 'commit': pick_head, 'subject': get(readfile(fugitive#Find('.git/MERGE_MSG', dir)), 0, '')})
2960 let stat.expanded = {'Staged': {}, 'Unstaged': {}}
2961 let to = {'lines': []}
2962 call s:AddHeader(to, 'Head', head)
2963 call s:AddHeader(to, stat.pull_type, pull_short)
2964 if push_ref !=# pull_ref
2965 call s:AddHeader(to, 'Push', push_short)
2967 if empty(stat.work_tree)
2968 if get(fugitive#ConfigGetAll('core.bare', config), 0, '') !~# '^\%(false\|no|off\|0\|\)$'
2969 call s:AddHeader(to, 'Bare', 'yes')
2971 call s:AddHeader(to, 'Error', s:worktree_error)
2974 if get(fugitive#ConfigGetAll('advice.statusHints', config), 0, 'true') !~# '^\%(false\|no|off\|0\|\)$'
2975 call s:AddHeader(to, 'Help', 'g?')
2978 call s:AddSection(to, 'Rebasing ' . rebasing_head, rebasing)
2979 call s:AddSection(to, get(get(sequencing, 0, {}), 'tous', '') ==# 'revert' ? 'Reverting' : 'Cherry Picking', sequencing)
2980 call s:AddSection(to, 'Untracked', untracked)
2981 call s:AddDiffSection(to, stat, 'Unstaged', unstaged)
2982 call s:AddDiffSection(to, stat, 'Staged', staged)
2984 let unique_push_ref = push_ref ==# pull_ref ? '' : push_ref
2985 let unpushed_push = s:QueryLogRange(unique_push_ref, head, dir)
2986 if get(stat.props, 'branch.ab') =~# '^+0 '
2987 let unpushed_pull = {'error': 0, 'overflow': 0, 'entries': []}
2989 let unpushed_pull = s:QueryLogRange(pull_ref, head, dir)
2991 " If the push ref is defined but nowhere to be found at the remote,
2992 " pretend it's the same as the pull ref
2993 if unpushed_push.error == 1
2994 let unpushed_push = unpushed_pull
2996 call s:AddLogSection(to, 'Unpushed to ' . push_short, unpushed_push)
2997 call s:AddLogSection(to, 'Unpushed to ' . pull_short, unpushed_pull)
2998 if unpushed_push.error && unpushed_pull.error && empty(rebasing) &&
2999 \ !empty(stat.push_remote . stat.fetch_remote)
3000 call s:AddLogSection(to, 'Unpushed to *', s:QueryLog([head, '--not', '--remotes'], 256, dir))
3002 call s:AddLogSection(to, 'Unpulled from ' . push_short, s:QueryLogRange(head, unique_push_ref, dir))
3003 if len(pull_ref) && get(stat.props, 'branch.ab') !~# ' -0$'
3004 call s:AddLogSection(to, 'Unpulled from ' . pull_short, s:QueryLogRange(head, pull_ref, dir))
3007 let bufnr = stat.bufnr
3008 setlocal noreadonly modifiable
3009 if len(to.lines) < line('$')
3010 silent keepjumps execute (len(to.lines)+1) . ',$delete_'
3012 call setline(1, to.lines)
3013 call setbufvar(bufnr, 'fugitive_status', stat)
3014 call setbufvar(bufnr, 'fugitive_expanded', stat.expanded)
3015 setlocal nomodified readonly nomodifiable
3018 let b:fugitive_type = 'index'
3022 function! s:StatusRetrieve(bufnr, ...) abort
3023 let amatch = s:Slash(fnamemodify(bufname(a:bufnr), ':p'))
3024 let dir = s:Dir(a:bufnr)
3025 let config = fugitive#Config(dir, function('len'))
3028 if amatch !~# '^fugitive:' && s:cpath($GIT_INDEX_FILE !=# '' ? resolve(s:GitIndexFileEnv()) : fugitive#Find('.git/index', dir)) !=# s:cpath(amatch)
3029 let cmd += [{'env': {'GIT_INDEX_FILE': FugitiveGitPath(amatch)}}]
3032 if fugitive#GitVersion(2, 15)
3033 call add(cmd, '--no-optional-locks')
3036 let rev_parse_cmd = cmd + ['rev-parse', '--short', 'HEAD', '--']
3038 let stat = {'bufnr': a:bufnr, 'reltime': reltime(), 'work_tree': s:Tree(dir), 'cmd': cmd, 'config': config}
3039 if empty(stat.work_tree)
3040 let stat.rev_parse = call('fugitive#Execute', [rev_parse_cmd, function('s:StatusProcess'), stat] + a:000)
3041 let stat.status = {}
3042 let stat.running = stat.rev_parse
3044 let stat.rev_parse = fugitive#Execute(rev_parse_cmd)
3045 let status_cmd = cmd + ['status', '-bz', fugitive#GitVersion(2, 11) ? '--porcelain=v2' : '--porcelain']
3046 let stat.status = call('fugitive#Execute', [status_cmd, function('s:StatusProcess'), stat] + a:000)
3047 let stat.running = stat.status
3052 function! fugitive#BufReadStatus(cmdbang) abort
3053 exe s:VersionCheck()
3055 unlet! b:fugitive_expanded
3057 let b:fugitive_type = 'index'
3058 let stat = s:StatusRetrieve(bufnr(''))
3060 let b:fugitive_loading = stat
3061 doautocmd <nomodeline> BufReadPre
3063 setlocal readonly nomodifiable noswapfile nomodifiable buftype=nowrite
3066 call s:StatusRender(stat)
3068 doautocmd <nomodeline> BufReadPost
3069 if &bufhidden ==# ''
3070 setlocal bufhidden=delete
3072 if !exists('b:dispatch')
3073 let b:dispatch = ':Git fetch --all'
3075 setlocal filetype=fugitive
3077 return s:DoAutocmd('User FugitiveIndex')
3079 call setbufvar(stat.bufnr, 'fugitive_loading', {})
3083 function! fugitive#FileReadCmd(...) abort
3084 let amatch = a:0 ? a:1 : expand('<amatch>')
3085 let [dir, rev] = s:DirRev(amatch)
3086 let line = a:0 > 1 ? a:2 : line("'[")
3088 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3090 if rev !~# ':' && s:ChompDefault('', [dir, 'cat-file', '-t', rev]) =~# '^\%(commit\|tag\)$'
3091 let cmd = [dir, 'log', '--pretty=format:%B', '-1', rev, '--']
3093 let cmd = [dir, 'status', '--short']
3095 let cmd = [dir, 'cat-file', '-p', rev, '--']
3097 let temp = tempname()
3098 let [err, exec_error] = s:StdoutToFile(temp, cmd)
3101 return 'noautocmd ' . line . 'read ' . s:fnameescape(amatch)
3103 return 'silent keepalt ' . line . 'read ' . s:fnameescape(temp) . '|call delete(' . string(temp) . ')'
3107 function! fugitive#FileWriteCmd(...) abort
3108 let temp = tempname()
3109 let amatch = a:0 ? a:1 : expand('<amatch>')
3110 let autype = a:0 > 1 ? 'Buf' : 'File'
3111 if exists('#' . autype . 'WritePre')
3112 execute s:DoAutocmd(autype . 'WritePre ' . s:fnameescape(amatch))
3115 let [dir, commit, file] = s:DirCommitFile(amatch)
3116 if commit !~# '^[0-3]$' || !v:cmdbang && (line("'[") != 1 || line("']") != line('$'))
3117 return "noautocmd '[,']write" . (v:cmdbang ? '!' : '') . ' ' . s:fnameescape(amatch)
3119 silent execute "noautocmd keepalt '[,']write ".temp
3120 let hash = s:TreeChomp([dir, '--literal-pathspecs', 'hash-object', '-w', '--', FugitiveGitPath(temp)])
3121 let old_mode = matchstr(s:ChompDefault('', ['ls-files', '--stage', '.' . file], dir), '^\d\+')
3123 let old_mode = executable(s:Tree(dir) . file) ? '100755' : '100644'
3125 let error = s:UpdateIndex(dir, [old_mode, hash, commit, file[1:-1]])
3128 if exists('#' . autype . 'WritePost')
3129 execute s:DoAutocmd(autype . 'WritePost ' . s:fnameescape(amatch))
3131 exe s:DoAutocmdChanged(dir)
3134 return 'echoerr '.string('fugitive: '.error)
3137 return 'echoerr ' . string(v:exception)
3143 function! fugitive#BufReadCmd(...) abort
3144 let amatch = a:0 ? a:1 : expand('<amatch>')
3145 let [dir, rev] = s:DirRev(amatch)
3147 return 'echo "Invalid Fugitive URL"'
3149 call s:InitializeBuffer(dir)
3151 return fugitive#BufReadStatus(v:cmdbang)
3155 let b:fugitive_type = 'stage'
3157 let r = fugitive#Execute([dir, 'cat-file', '-t', rev])
3158 let b:fugitive_type = get(r.stdout, 0, '')
3159 if r.exit_status && rev =~# '^:0'
3160 let r = fugitive#Execute([dir, 'write-tree', '--prefix=' . rev[3:-1]])
3161 let sha = get(r.stdout, 0, '')
3162 let b:fugitive_type = 'tree'
3165 let error = substitute(join(r.stderr, "\n"), "\n*$", '', '')
3166 unlet b:fugitive_type
3168 if empty(&bufhidden)
3169 setlocal bufhidden=delete
3172 let &l:readonly = !filewritable(fugitive#Find('.git/index', dir))
3173 return 'doautocmd BufNewFile'
3175 setlocal readonly nomodifiable
3176 return 'doautocmd BufNewFile|echo ' . string(error)
3178 elseif b:fugitive_type !~# '^\%(tag\|commit\|tree\|blob\)$'
3179 return "echoerr ".string("fugitive: unrecognized git type '".b:fugitive_type."'")
3181 if !exists('b:fugitive_display_format') && b:fugitive_type != 'blob'
3182 let b:fugitive_display_format = +getbufvar('#','fugitive_display_format')
3186 if b:fugitive_type !=# 'blob'
3190 setlocal noreadonly modifiable
3191 let pos = getpos('.')
3192 silent keepjumps %delete_
3195 let events = ['User FugitiveObject', 'User Fugitive' . substitute(b:fugitive_type, '^\l', '\u&', '')]
3198 if b:fugitive_type !=# 'blob'
3199 setlocal foldmarker=<<<<<<<<,>>>>>>>>
3201 exe s:DoAutocmd('BufReadPre')
3202 if b:fugitive_type ==# 'tree'
3203 let b:fugitive_display_format = b:fugitive_display_format % 2
3204 if b:fugitive_display_format
3205 call s:ReplaceCmd([dir, 'ls-tree', exists('sha') ? sha : rev])
3208 let sha = s:TreeChomp(dir, 'rev-parse', '--verify', rev, '--')
3210 call s:ReplaceCmd([dir, 'show', '--no-color', sha])
3212 elseif b:fugitive_type ==# 'tag'
3213 let b:fugitive_display_format = b:fugitive_display_format % 2
3214 if b:fugitive_display_format
3215 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3217 call s:ReplaceCmd([dir, 'cat-file', '-p', rev])
3219 elseif b:fugitive_type ==# 'commit'
3220 let b:fugitive_display_format = b:fugitive_display_format % 2
3221 if b:fugitive_display_format
3222 call s:ReplaceCmd([dir, 'cat-file', b:fugitive_type, rev])
3224 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%B', rev])
3226 keepjumps call search('^parent ')
3227 if getline('.') ==# 'parent '
3228 silent lockmarks keepjumps delete_
3230 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps s/\m\C\%(^parent\)\@<! /\rparent /e' . (&gdefault ? '' : 'g')
3232 keepjumps let lnum = search('^encoding \%(<unknown>\)\=$','W',line('.')+3)
3234 silent lockmarks keepjumps delete_
3236 silent exe (exists(':keeppatterns') ? 'keeppatterns' : '') 'keepjumps 1,/^diff --git\|\%$/s/\r$//e'
3239 elseif b:fugitive_type ==# 'stage'
3240 call s:ReplaceCmd([dir, 'ls-files', '--stage'])
3241 elseif b:fugitive_type ==# 'blob'
3242 let blob_or_filters = rev =~# ':' && fugitive#GitVersion(2, 11) ? '--filters' : 'blob'
3243 call s:ReplaceCmd([dir, 'cat-file', blob_or_filters, rev])
3246 keepjumps call setpos('.',pos)
3247 setlocal nomodified noswapfile
3248 let modifiable = rev =~# '^:.:' && b:fugitive_type !=# 'tree'
3250 let events = ['User FugitiveStageBlob']
3252 let &l:readonly = !modifiable || !filewritable(fugitive#Find('.git/index', dir))
3253 if empty(&bufhidden)
3254 setlocal bufhidden=delete
3256 let &l:modifiable = modifiable
3257 call fugitive#MapJumps()
3258 if b:fugitive_type !=# 'blob'
3259 call s:Map('n', 'a', ":<C-U>let b:fugitive_display_format += v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3260 call s:Map('n', 'i', ":<C-U>let b:fugitive_display_format -= v:count1<Bar>exe fugitive#BufReadCmd(@%)<CR>", '<silent>')
3261 setlocal filetype=git
3267 return s:DoAutocmd('BufReadPost') .
3268 \ (modifiable ? '' : '|setl nomodifiable') . '|' .
3269 \ call('s:DoAutocmd', events)
3271 return 'echoerr ' . string(v:exception)
3275 function! fugitive#BufWriteCmd(...) abort
3276 return fugitive#FileWriteCmd(a:0 ? a:1 : expand('<amatch>'), 1)
3279 function! fugitive#SourceCmd(...) abort
3280 let amatch = a:0 ? a:1 : expand('<amatch>')
3281 let temp = s:BlobTemp(amatch)
3283 return 'noautocmd source ' . s:fnameescape(amatch)
3285 if !exists('g:virtual_scriptnames')
3286 let g:virtual_scriptnames = {}
3288 let g:virtual_scriptnames[temp] = amatch
3289 return 'source ' . s:fnameescape(temp)
3292 " Section: Temp files
3294 if !exists('s:temp_files')
3295 let s:temp_files = {}
3298 function! s:TempState(...) abort
3299 return get(s:temp_files, s:cpath(s:AbsoluteVimPath(a:0 ? a:1 : -1)), {})
3302 function! fugitive#Result(...) abort
3303 if !a:0 && exists('g:fugitive_event')
3304 return get(g:, 'fugitive_result', {})
3305 elseif !a:0 || type(a:1) == type('') && a:1 =~# '^-\=$'
3306 return get(g:, '_fugitive_last_job', {})
3307 elseif type(a:1) == type(0)
3308 return s:TempState(a:1)
3309 elseif type(a:1) == type('')
3310 return s:TempState(a:1)
3311 elseif type(a:1) == type({}) && has_key(a:1, 'file')
3312 return s:TempState(a:1.file)
3318 function! s:TempDotMap() abort
3319 let cfile = s:cfile()
3321 if getline('.') =~# '^[*+] \+\f' && col('.') < 2
3322 return matchstr(getline('.'), '^. \+\zs\f\+')
3324 return expand('<cfile>')
3327 let name = fugitive#Find(cfile[0])
3328 let [dir, commit, file] = s:DirCommitFile(name)
3329 if len(commit) && empty(file)
3331 elseif s:cpath(s:Tree(), getcwd())
3332 return fugitive#Path(name, "./")
3334 return fugitive#Real(name)
3338 function! s:TempReadPre(file) abort
3339 let key = s:cpath(s:AbsoluteVimPath(a:file))
3340 if has_key(s:temp_files, key)
3341 let dict = s:temp_files[key]
3343 if empty(&bufhidden)
3344 setlocal bufhidden=delete
3346 setlocal buftype=nowrite
3347 setlocal nomodifiable
3348 call s:InitializeBuffer(dict)
3349 if len(dict.git_dir)
3350 call extend(b:, {'fugitive_type': 'temp'}, 'keep')
3356 function! s:TempReadPost(file) abort
3357 let key = s:cpath(s:AbsoluteVimPath(a:file))
3358 if has_key(s:temp_files, key)
3359 let dict = s:temp_files[key]
3360 if !has_key(dict, 'job')
3361 setlocal nobuflisted
3363 if get(dict, 'filetype', '') ==# 'git'
3364 call fugitive#MapJumps()
3365 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3366 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(<SID>TempDotMap())<CR><Home>")
3368 if has_key(dict, 'filetype')
3369 if dict.filetype ==# 'man' && has('nvim')
3370 let b:man_sect = matchstr(getline(1), '^\w\+(\zs\d\+\ze)')
3372 if !get(g:, 'did_load_ftplugin') && dict.filetype ==# 'fugitiveblame'
3375 let &l:filetype = dict.filetype
3377 setlocal foldmarker=<<<<<<<<,>>>>>>>>
3379 call s:Map('n', 'gq', ":<C-U>bdelete<CR>", '<silent> <unique>')
3381 return 'doautocmd <nomodeline> User FugitivePager'
3386 function! s:TempDelete(file) abort
3387 let key = s:cpath(s:AbsoluteVimPath(a:file))
3388 if has_key(s:temp_files, key) && !has_key(s:temp_files[key], 'job') && key !=# s:cpath(get(get(g:, '_fugitive_last_job', {}), 'file', ''))
3390 call remove(s:temp_files, key)
3395 function! s:OriginBufnr(...) abort
3396 let state = s:TempState(a:0 ? a:1 : bufnr(''))
3397 return get(state, 'origin_bufnr', -1)
3400 augroup fugitive_temp
3402 autocmd BufReadPre * exe s:TempReadPre( +expand('<abuf>'))
3403 autocmd BufReadPost * exe s:TempReadPost(+expand('<abuf>'))
3404 autocmd BufWipeout * exe s:TempDelete( +expand('<abuf>'))
3409 function! s:AskPassArgs(dir) abort
3410 if (len($DISPLAY) || len($TERM_PROGRAM) || has('gui_running')) &&
3411 \ empty($GIT_ASKPASS) && empty($SSH_ASKPASS) && empty(fugitive#ConfigGetAll('core.askpass', a:dir))
3412 if s:executable(s:VimExecPath() . '/git-gui--askpass')
3413 return ['-c', 'core.askPass=' . s:ExecPath()[0] . '/git-gui--askpass']
3414 elseif s:executable('ssh-askpass')
3415 return ['-c', 'core.askPass=ssh-askpass']
3421 function! s:RunSave(state) abort
3422 let s:temp_files[s:cpath(a:state.file)] = a:state
3425 function! s:RunFinished(state, ...) abort
3426 if has_key(get(g:, '_fugitive_last_job', {}), 'file') && bufnr(g:_fugitive_last_job.file) < 0
3427 exe s:TempDelete(remove(g:, '_fugitive_last_job').file)
3429 let g:_fugitive_last_job = a:state
3430 let first = join(readfile(a:state.file, '', 2), "\n")
3431 if get(a:state, 'filetype', '') ==# 'git' && first =~# '\<\([[:upper:][:digit:]_-]\+(\d\+)\).*\1'
3432 let a:state.filetype = 'man'
3434 if !has_key(a:state, 'capture_bufnr')
3437 call fugitive#DidChange(a:state)
3440 function! s:RunEdit(state, tmp, job) abort
3441 if get(a:state, 'request', '') !=# 'edit'
3444 call remove(a:state, 'request')
3445 let sentinel = a:state.file . '.edit'
3446 let file = FugitiveVimPath(readfile(sentinel, '', 1)[0])
3448 if !&equalalways && a:state.mods !~# '\<\d*tab\>' && 3 > (a:state.mods =~# '\<vert' ? winwidth(0) : winheight(0))
3449 let noequalalways = 1
3450 setglobal equalalways
3452 let mods = s:Mods(a:state.mods, 'SpanOrigin')
3453 exe substitute(mods, '\<tab\>', '-tab', 'g') 'keepalt split' s:fnameescape(file)
3455 if exists('l:noequalalways')
3456 setglobal noequalalways
3460 call s:InitializeBuffer(a:state)
3461 let bufnr = bufnr('')
3462 let s:edit_jobs[bufnr] = [a:state, a:tmp, a:job, sentinel]
3463 call fugitive#DidChange(a:state.git_dir)
3464 if bufnr == bufnr('') && !exists('g:fugitive_event')
3466 let g:fugitive_event = a:state.git_dir
3467 let g:fugitive_result = a:state
3468 exe s:DoAutocmd('User FugitiveEditor')
3470 unlet! g:fugitive_event g:fugitive_result
3476 function! s:RunReceive(state, tmp, type, job, data, ...) abort
3477 if a:type ==# 'err' || a:state.pty
3478 let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3479 let data = a:tmp.escape . data
3480 let escape = "\033]51;[^\007]*"
3481 let a:tmp.escape = matchstr(data, escape . '$')
3482 if len(a:tmp.escape)
3483 let data = strpart(data, 0, len(data) - len(a:tmp.escape))
3485 let cmd = matchstr(data, escape . "\007")[5:-2]
3486 let data = substitute(data, escape . "\007", '', 'g')
3487 if cmd =~# '^fugitive:'
3488 let a:state.request = strpart(cmd, 9)
3490 let lines = split(a:tmp.err . data, "\r\\=\n", 1)
3491 let a:tmp.err = lines[-1]
3493 call map(lines, 'substitute(v:val, ".*\r", "", "")')
3495 let lines = type(a:data) == type([]) ? a:data : split(a:data, "\n", 1)
3497 let lines[0] = a:tmp.out . lines[0]
3499 let a:tmp.out = lines[-1]
3502 call writefile(lines, a:state.file, 'ba')
3503 if has_key(a:tmp, 'echo')
3504 if !exists('l:data')
3505 let data = type(a:data) == type([]) ? join(a:data, "\n") : a:data
3507 let a:tmp.echo .= data
3509 let line_count = a:tmp.line_count
3510 let a:tmp.line_count += len(lines) - 1
3511 if !has_key(a:state, 'capture_bufnr') || !bufloaded(a:state.capture_bufnr)
3514 call remove(lines, -1)
3516 call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3517 if !line_count && len(lines) > 1000
3518 let first = remove(lines, 0, 999)
3519 call setbufline(a:state.capture_bufnr, 1, first)
3521 call setbufline(a:state.capture_bufnr, 1001, lines)
3523 call setbufline(a:state.capture_bufnr, line_count + 1, lines)
3525 call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3526 if !a:state.pager && getwinvar(bufwinid(a:state.capture_bufnr), '&previewwindow')
3527 let winnr = bufwinnr(a:state.capture_bufnr)
3529 let old_winnr = winnr()
3530 exe 'noautocmd' winnr.'wincmd w'
3532 exe 'noautocmd' old_winnr.'wincmd w'
3539 function! s:RunExit(state, tmp, job, exit_status) abort
3540 let a:state.exit_status = a:exit_status
3541 if has_key(a:state, 'job')
3544 call s:RunFinished(a:state)
3547 function! s:RunClose(state, tmp, job, ...) abort
3549 call s:RunExit(a:state, a:tmp, a:job, a:1)
3551 let noeol = substitute(substitute(a:tmp.err, "\r$", '', ''), ".*\r", '', '') . a:tmp.out
3552 call writefile([noeol], a:state.file, 'ba')
3553 call remove(a:state, 'job')
3554 if has_key(a:state, 'capture_bufnr') && bufloaded(a:state.capture_bufnr)
3556 call setbufvar(a:state.capture_bufnr, '&modifiable', 1)
3557 call setbufline(a:state.capture_bufnr, a:tmp.line_count + 1, [noeol])
3558 call setbufvar(a:state.capture_bufnr, '&eol', 0)
3559 call setbufvar(a:state.capture_bufnr, '&modifiable', 0)
3561 call setbufvar(a:state.capture_bufnr, '&modified', 0)
3562 call setbufvar(a:state.capture_bufnr, '&buflisted', 0)
3563 if a:state.filetype !=# getbufvar(a:state.capture_bufnr, '&filetype', '')
3564 call setbufvar(a:state.capture_bufnr, '&filetype', a:state.filetype)
3567 if !has_key(a:state, 'exit_status')
3570 call s:RunFinished(a:state)
3573 function! s:RunSend(job, str) abort
3575 if type(a:job) == type(0)
3576 call chansend(a:job, a:str)
3578 call ch_sendraw(a:job, a:str)
3581 catch /^Vim\%((\a\+)\)\=:E90[06]:/
3586 function! s:RunCloseIn(job) abort
3588 if type(a:job) ==# type(0)
3589 call chanclose(a:job, 'stdin')
3591 call ch_close_in(a:job)
3594 catch /^Vim\%((\a\+)\)\=:E90[06]:/
3599 function! s:RunEcho(tmp) abort
3600 if !has_key(a:tmp, 'echo')
3603 let data = a:tmp.echo
3604 let a:tmp.echo = matchstr(data, "[\r\n]\\+$")
3606 let data = strpart(data, 0, len(data) - len(a:tmp.echo))
3608 echon substitute(data, "\r\\ze\n", '', 'g')
3611 function! s:RunTick(job) abort
3612 if type(a:job) == v:t_number
3613 return jobwait([a:job], 1)[0] == -1
3614 elseif type(a:job) == 8
3615 let running = ch_status(a:job) !~# '^closed$\|^fail$' || job_status(a:job) ==# 'run'
3621 if !exists('s:edit_jobs')
3622 let s:edit_jobs = {}
3624 function! s:RunWait(state, tmp, job, ...) abort
3625 if a:0 && filereadable(a:1)
3629 if a:tmp.no_more && &more
3633 while get(a:state, 'request', '') !=# 'edit' && s:RunTick(a:job)
3634 call s:RunEcho(a:tmp)
3635 if !get(a:tmp, 'closed_in')
3636 let peek = getchar(1)
3637 if peek != 0 && !(has('win32') && peek == 128)
3639 let c = type(c) == type(0) ? nr2char(c) : c
3640 if c ==# "\<C-D>" || c ==# "\<Esc>"
3641 let a:tmp.closed_in = 1
3642 let can_pedit = s:RunCloseIn(a:job) && exists('*setbufline')
3643 for winnr in range(1, winnr('$'))
3644 if getwinvar(winnr, '&previewwindow') && getbufvar(winbufnr(winnr), '&modified')
3649 if has_key(a:tmp, 'echo')
3650 call remove(a:tmp, 'echo')
3652 call writefile(['fugitive: aborting edit due to background operation.'], a:state.file . '.exit')
3653 exe (&splitbelow ? 'botright' : 'topleft') 'silent pedit ++ff=unix' s:fnameescape(a:state.file)
3654 let a:state.capture_bufnr = bufnr(a:state.file)
3655 call setbufvar(a:state.capture_bufnr, '&modified', 1)
3661 call s:RunSend(a:job, c)
3669 if !has_key(a:state, 'request') && has_key(a:state, 'job') && exists('*job_status') && job_status(a:job) ==# "dead"
3670 throw 'fugitive: close callback did not fire; this should never happen'
3672 call s:RunEcho(a:tmp)
3673 if has_key(a:tmp, 'echo')
3674 let a:tmp.echo = substitute(a:tmp.echo, "^\r\\=\n", '', '')
3677 let finished = !s:RunEdit(a:state, a:tmp, a:job)
3682 if !exists('finished')
3684 if a:state.pty && !get(a:tmp, 'closed_in')
3685 call s:RunSend(a:job, "\<C-C>")
3686 elseif type(a:job) == type(0)
3689 call job_stop(a:job)
3694 call fugitive#DidChange(a:state)
3700 if !exists('s:resume_queue')
3701 let s:resume_queue = []
3703 function! fugitive#Resume() abort
3704 while len(s:resume_queue)
3705 let enqueued = remove(s:resume_queue, 0)
3706 if enqueued[2] isnot# ''
3708 call call('s:RunWait', enqueued)
3714 function! s:RunBufDelete(bufnr) abort
3715 let state = s:TempState(+a:bufnr)
3716 if has_key(state, 'job')
3718 if type(state.job) == type(0)
3719 call jobstop(state.job)
3721 call job_stop(state.job)
3726 if has_key(s:edit_jobs, a:bufnr) |
3727 call add(s:resume_queue, remove(s:edit_jobs, a:bufnr))
3728 call feedkeys("\<C-\>\<C-N>:redraw!|call delete(" . string(s:resume_queue[-1][0].file . '.edit') .
3729 \ ")|call fugitive#Resume()|checktime\r", 'n')
3733 augroup fugitive_job
3735 autocmd BufDelete * call s:RunBufDelete(+expand('<abuf>'))
3737 \ for s:jobbuf in keys(s:edit_jobs) |
3738 \ call writefile(['Aborting edit due to Vim exit.'], s:edit_jobs[s:jobbuf][0].file . '.exit') |
3740 \ call call('s:RunWait', remove(s:edit_jobs, s:jobbuf)) |
3744 function! fugitive#CanPty() abort
3745 return get(g:, 'fugitive_pty_debug_override',
3746 \ has('unix') && !has('win32unix') && (has('patch-8.0.0744') || has('nvim')) && fugitive#GitVersion() !~# '\.windows\>')
3749 function! fugitive#PagerFor(argv, ...) abort
3753 elseif (args[0] ==# 'help' || get(args, 1, '') ==# '--help') && !s:HasOpt(args, '--web')
3756 if args[0] ==# 'config' && (s:HasOpt(args, '-e', '--edit') ||
3757 \ !s:HasOpt(args, '--list', '--get-all', '--get-regexp', '--get-urlmatch')) ||
3758 \ args[0] =~# '^\%(tag\|branch\)$' && (
3759 \ s:HasOpt(args, '--edit-description', '--unset-upstream', '-m', '-M', '--move', '-c', '-C', '--copy', '-d', '-D', '--delete') ||
3760 \ len(filter(args[1:-1], 'v:val =~# "^[^-]\\|^--set-upstream-to="')) &&
3761 \ !s:HasOpt(args, '--contains', '--no-contains', '--merged', '--no-merged', '--points-at'))
3764 let config = a:0 ? a:1 : fugitive#Config()
3765 let value = get(fugitive#ConfigGetAll('pager.' . args[0], config), 0, -1)
3766 if value =~# '^\%(true\|yes\|on\|1\)$'
3768 elseif value =~# '^\%(false\|no|off\|0\|\)$'
3770 elseif type(value) == type('')
3772 elseif args[0] =~# '^\%(branch\|config\|diff\|grep\|log\|range-diff\|shortlog\|show\|tag\|whatchanged\)$' ||
3773 \ (args[0] ==# 'stash' && get(args, 1, '') ==# 'show') ||
3774 \ (args[0] ==# 'reflog' && get(args, 1, '') !~# '^\%(expire\|delete\|exists\)$') ||
3775 \ (args[0] ==# 'am' && s:HasOpt(args, '--show-current-patch'))
3782 let s:disable_colors = []
3783 for s:colortype in ['advice', 'branch', 'diff', 'grep', 'interactive', 'pager', 'push', 'remote', 'showBranch', 'status', 'transport', 'ui']
3784 call extend(s:disable_colors, ['-c', 'color.' . s:colortype . '=false'])
3787 function! fugitive#Command(line1, line2, range, bang, mods, arg, ...) abort
3788 exe s:VersionCheck()
3789 let dir = call('s:Dir', a:000)
3793 let config = copy(fugitive#Config(dir))
3794 let curwin = a:arg =~# '^++curwin\>' || !a:line2
3795 let [args, after] = s:SplitExpandChain(substitute(a:arg, '^++curwin\>\s*', '', ''), s:Tree(dir))
3798 let explicit_pathspec_option = 0
3799 let did_expand_alias = 0
3801 if args[0] ==# '-c' && len(args) > 1
3802 call extend(flags, remove(args, 0, 1))
3803 elseif args[0] =~# '^-p$\|^--paginate$'
3805 call remove(args, 0)
3806 elseif args[0] =~# '^-P$\|^--no-pager$'
3808 call remove(args, 0)
3809 elseif args[0] =~# '^--\%([[:lower:]-]\+-pathspecs\)$'
3810 let explicit_pathspec_option = 1
3811 call add(flags, remove(args, 0))
3812 elseif args[0] =~# '^\%(--no-optional-locks\)$'
3813 call add(flags, remove(args, 0))
3814 elseif args[0] =~# '^-C$\|^--\%(exec-path=\|git-dir=\|work-tree=\|bare$\)'
3815 return 'echoerr ' . string('fugitive: ' . args[0] . ' is not supported')
3816 elseif did_expand_alias
3819 let alias = FugitiveConfigGet('alias.' . get(args, 0, ''), config)
3820 if get(args, 1, '') !=# '--help' && alias !~# '^$\|^!\|[\"'']' && !filereadable(s:VimExecPath() . '/git-' . args[0])
3821 \ && !(has('win32') && filereadable(s:VimExecPath() . '/git-' . args[0] . '.exe'))
3822 call remove(args, 0)
3823 call extend(args, split(alias, '\s\+'), 'keep')
3824 let did_expand_alias = 1
3830 if !explicit_pathspec_option
3831 call insert(flags, '--no-literal-pathspecs')
3833 let no_pager = pager is# 0
3835 call add(flags, '--no-pager')
3839 while i < len(flags) - 1
3840 if flags[i] ==# '-c'
3842 let config_name = tolower(matchstr(flags[i], '^[^=]\+'))
3843 if has_key(s:prepare_env, config_name) && flags[i] =~# '=.'
3844 let env[s:prepare_env[config_name]] = matchstr(flags[i], '=\zs.*')
3847 let config[config_name] = [matchstr(flags[i], '=\zs.*')]
3849 let config[config_name] = [1]
3854 let options = {'git': s:UserCommandList(), 'git_dir': s:GitDir(dir), 'flags': flags, 'curwin': curwin}
3855 if empty(args) && pager is# -1
3856 let cmd = s:StatusCommand(a:line1, a:line2, a:range, curwin ? 0 : a:line2, a:bang, a:mods, '', '', [], options)
3857 return (empty(cmd) ? 'exe' : cmd) . after
3859 let name = substitute(get(args, 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
3860 if pager is# -1 && name =~# '^\a\+$' && exists('*s:' . name . 'Subcommand') && get(args, 1, '') !=# '--help'
3862 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))
3863 if type(overrides) == type('')
3864 return 'exe ' . string(overrides) . after
3866 let args = [get(overrides, 'command', args[0])] + get(overrides, 'insert_args', []) + args[1:-1]
3868 return 'echoerr ' . string(v:exception)
3873 call extend(env, get(overrides, 'env', {}))
3874 call s:PrepareEnv(env, dir)
3876 let pager = fugitive#PagerFor(args, config)
3878 let wants_terminal = type(pager) ==# type('') ||
3879 \ (s:HasOpt(args, ['add', 'checkout', 'commit', 'reset', 'restore', 'stage', 'stash'], '-p', '--patch') ||
3880 \ s:HasOpt(args, ['add', 'clean', 'stage'], '-i', '--interactive')) && pager is# 0
3882 let mods = substitute(s:Mods(a:mods), '\<tab\>', '-tab', 'g')
3883 let assign = len(dir) ? "|call FugitiveDetect({'git_dir':" . string(options.git_dir) . '})' : ''
3884 let argv = s:UserCommandList(options) + args
3885 let term_opts = len(env) ? {'env': env} : {}
3887 call fugitive#Autowrite()
3888 return mods . (curwin ? 'enew' : 'new') . '|call termopen(' . string(argv) . ', ' . string(term_opts) . ')' . assign . '|startinsert' . after
3889 elseif exists('*term_start')
3890 call fugitive#Autowrite()
3892 let term_opts.curwin = 1
3894 return mods . 'call term_start(' . string(argv) . ', ' . string(term_opts) . ')' . assign . after
3898 \ 'git': options.git,
3901 \ 'git_dir': options.git_dir,
3902 \ 'cwd': s:UserCommandCwd(dir),
3903 \ 'filetype': 'git',
3904 \ 'mods': s:Mods(a:mods),
3905 \ 'file': s:Resolve(tempname())}
3909 if a:bang && pager isnot# 2
3910 let state.pager = pager
3912 let stream = exists('*setbufline')
3913 let do_edit = substitute(s:Mods(a:mods, 'Edge'), '\<tab\>', '-tab', 'g') . 'pedit!'
3915 let allow_pty = get(args, 0, '') is# 'shortlog'
3916 if pager is# 2 && a:bang && a:line2 >= 0
3917 let [do_edit, after_edit] = s:ReadPrepare(a:line1, a:line2, a:range, a:mods)
3918 elseif pager is# 2 && a:bang
3919 let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'pedit'
3921 let do_edit = s:Mods(a:mods, 'SpanOrigin') . 'split'
3923 let do_edit = s:Mods(a:mods) . 'edit'
3926 call extend(env, {'COLUMNS': '' . get(g:, 'fugitive_columns', 80)}, 'keep')
3929 call extend(env, {'COLUMNS': '' . (&columns - 1)}, 'keep')
3930 let state.pty = allow_pty && fugitive#CanPty()
3932 let args = s:AskPassArgs(dir) + args
3935 \ 'no_more': no_pager || get(overrides, 'no_more'),
3940 let env.FUGITIVE = state.file
3941 let editor = 'sh ' . s:TempScript(
3942 \ '[ -f "$FUGITIVE.exit" ] && cat "$FUGITIVE.exit" >&2 && exit 1',
3943 \ 'echo "$1" > "$FUGITIVE.edit"',
3944 \ 'printf "\033]51;fugitive:edit\007" >&2',
3945 \ 'while [ -f "$FUGITIVE.edit" -a ! -f "$FUGITIVE.exit" ]; do sleep 0.05 2>/dev/null || sleep 1; done',
3949 \ 'GIT_EDITOR': editor,
3950 \ 'GIT_SEQUENCE_EDITOR': editor,
3951 \ 'GIT_PAGER': 'cat',
3952 \ 'PAGER': 'cat'}, 'keep')
3953 if s:executable('col')
3954 let env.MANPAGER = 'col -b'
3956 if len($GPG_TTY) && !has_key(env, 'GPG_TTY')
3957 let env.GPG_TTY = ''
3958 let did_override_gpg_tty = 1
3961 call writefile(['fugitive: aborting edit due to background operation.'], state.file . '.exit')
3963 call writefile(['fugitive: aborting edit due to use of pager.'], state.file . '.exit')
3964 let after = '|' . do_edit . ' ' . s:fnameescape(state.file) . after_edit . after
3966 let env.GIT_MERGE_AUTOEDIT = '1'
3969 let args = s:disable_colors + flags + ['-c', 'advice.waitingForEditor=false'] + args
3970 let argv = s:UserCommandList({'git': options.git, 'git_dir': options.git_dir}) + args
3971 let [argv, jobopts] = s:JobOpts(argv, env)
3972 call fugitive#Autowrite()
3973 call writefile([], state.file, 'b')
3974 call s:RunSave(state)
3975 if has_key(tmp, 'echo')
3978 if exists('*ch_close_in')
3979 call extend(jobopts, {
3981 \ 'out_cb': function('s:RunReceive', [state, tmp, 'out']),
3982 \ 'err_cb': function('s:RunReceive', [state, tmp, 'err']),
3983 \ 'close_cb': function('s:RunClose', [state, tmp]),
3984 \ 'exit_cb': function('s:RunExit', [state, tmp]),
3989 let job = job_start(argv, jobopts)
3991 let job = jobstart(argv, extend(jobopts, {
3994 \ 'stdout_buffered': pager,
3995 \ 'stderr_buffered': pager,
3996 \ 'on_stdout': function('s:RunReceive', [state, tmp, 'out']),
3997 \ 'on_stderr': function('s:RunReceive', [state, tmp, 'err']),
3998 \ 'on_exit': function('s:RunClose', [state, tmp]),
4003 let tmp.closed_in = 1
4004 call s:RunCloseIn(job)
4007 exe 'silent' do_edit '++ff=unix' s:fnameescape(state.file)
4008 let state.capture_bufnr = bufnr(state.file)
4009 call setbufvar(state.capture_bufnr, '&modified', 1)
4010 return (after_edit . after)[1:-1]
4012 call add(s:resume_queue, [state, tmp, job])
4013 return 'call fugitive#Resume()|checktime' . after
4015 let pre = s:BuildEnvPrefix(env)
4017 if exists('+guioptions') && &guioptions =~# '!'
4018 let guioptions = &guioptions
4021 silent! execute '!' . escape(pre . s:shellesc(s:UserCommandList(options) + s:disable_colors + flags + ['--no-pager'] + args), '!#%') .
4022 \ (&shell =~# 'csh' ? ' >& ' . s:shellesc(state.file) : ' > ' . s:shellesc(state.file) . ' 2>&1')
4023 let state.exit_status = v:shell_error
4025 if exists('guioptions')
4026 let &guioptions = guioptions
4030 call s:RunSave(state)
4031 call s:RunFinished(state)
4032 return do_edit . ' ' . s:fnameescape(state.file) . after_edit .
4033 \ '|call fugitive#DidChange(fugitive#Result(' . string(state.file) . '))' . after
4035 return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git on Windows')
4036 elseif has('gui_running')
4037 return 'echoerr ' . string('fugitive: Vim 8 with job support required to use :Git in GVim')
4039 if !explicit_pathspec_option && get(options.flags, 0, '') ==# '--no-literal-pathspecs'
4040 call remove(options.flags, 0)
4042 if exists('l:did_override_gpg_tty')
4043 call remove(env, 'GPG_TTY')
4045 let cmd = s:BuildEnvPrefix(env) . s:shellesc(s:UserCommandList(options) + args)
4046 let after = '|call fugitive#DidChange(' . string(dir) . ')' . after
4047 if !wants_terminal && (no_pager || index(['add', 'clean', 'reset', 'restore', 'stage'], get(args, 0, '')) >= 0 || s:HasOpt(args, ['checkout'], '-q', '--quiet', '--no-progress'))
4048 let output = substitute(s:SystemError(cmd)[0], "\n$", '', '')
4051 if &more && no_pager
4055 echo substitute(output, "\n$", "", "")
4062 return 'checktime' . after
4064 return 'exe ' . string('noautocmd !' . escape(cmd, '!#%')) . after
4069 let s:exec_paths = {}
4070 function! s:ExecPath() abort
4071 let git = s:GitShellCmd()
4072 if !has_key(s:exec_paths, git)
4073 let path = get(s:JobExecute(s:GitCmd() + ['--exec-path'], {}, [], [], {}).stdout, 0, '')
4074 let s:exec_paths[git] = [path, FugitiveVimPath(path)]
4076 return s:exec_paths[git]
4079 function! s:VimExecPath() abort
4080 return s:ExecPath()[1]
4083 let s:subcommands_before_2_5 = [
4084 \ 'add', 'am', 'apply', 'archive', 'bisect', 'blame', 'branch', 'bundle',
4085 \ 'checkout', 'cherry', 'cherry-pick', 'citool', 'clean', 'clone', 'commit', 'config',
4086 \ 'describe', 'diff', 'difftool', 'fetch', 'format-patch', 'fsck',
4087 \ 'gc', 'grep', 'gui', 'help', 'init', 'instaweb', 'log',
4088 \ 'merge', 'mergetool', 'mv', 'notes', 'pull', 'push',
4089 \ 'rebase', 'reflog', 'remote', 'repack', 'replace', 'request-pull', 'reset', 'revert', 'rm',
4090 \ 'send-email', 'shortlog', 'show', 'show-branch', 'stash', 'stage', 'status', 'submodule',
4091 \ 'tag', 'whatchanged',
4093 let s:path_subcommands = {}
4094 function! s:CompletableSubcommands(dir) abort
4095 let c_exec_path = s:cpath(s:VimExecPath())
4096 if !has_key(s:path_subcommands, c_exec_path)
4097 if fugitive#GitVersion(2, 18)
4098 let [lines, exec_error] = s:LinesError([a:dir, '--list-cmds=list-mainporcelain,nohelpers,list-complete'])
4099 call filter(lines, 'v:val =~# "^\\S\\+$"')
4100 if !exec_error && len(lines)
4101 let s:path_subcommands[c_exec_path] = lines
4103 let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4104 \ ['maintenance', 'prune', 'range-diff', 'restore', 'sparse-checkout', 'switch', 'worktree']
4107 let s:path_subcommands[c_exec_path] = s:subcommands_before_2_5 +
4108 \ (fugitive#GitVersion(2, 5) ? ['worktree'] : [])
4111 let commands = copy(s:path_subcommands[c_exec_path])
4112 for path in split($PATH, has('win32') ? ';' : ':')
4113 if path !~# '^/\|^\a:[\\/]'
4116 let cpath = s:cpath(path)
4117 if !has_key(s:path_subcommands, cpath)
4118 let s:path_subcommands[cpath] = filter(map(s:GlobComplete(path.'/git-', '*', 1),'substitute(v:val,"\\.exe$","","")'), 'v:val !~# "--\\|/"')
4120 call extend(commands, s:path_subcommands[cpath])
4122 call extend(commands, keys(fugitive#ConfigGetRegexp('^alias\.\zs[^.]\+$', a:dir)))
4123 let configured = split(FugitiveConfigGet('completion.commands', a:dir), '\s\+')
4125 for command in configured
4126 if command =~# '^-.'
4127 let rejected[strpart(command, 1)] = 1
4130 call filter(configured, 'v:val !~# "^-"')
4131 let results = filter(sort(commands + configured), '!has_key(rejected, v:val)')
4133 return uniq(results)
4136 while i < len(results)
4137 if results[i] ==# results[i-1]
4138 call remove(results, i)
4147 function! fugitive#Complete(lead, ...) abort
4148 let dir = a:0 == 1 ? a:1 : a:0 >= 3 ? s:Dir(a:3) : s:Dir()
4149 let root = a:0 >= 4 ? a:4 : s:Tree(s:Dir())
4150 let pre = a:0 > 1 ? strpart(a:1, 0, a:2) : ''
4151 let subcmd = matchstr(pre, '\u\w*[! ] *\%(\%(++\S\+\|--\S\+-pathspecs\|-c\s\+\S\+\)\s\+\)*\zs[[:alnum:]][[:alnum:]-]*\ze ')
4152 if empty(subcmd) && a:lead =~# '^+'
4153 let results = ['++curwin']
4154 elseif empty(subcmd) && a:lead =~# '^-'
4155 let results = ['--literal-pathspecs', '--no-literal-pathspecs', '--glob-pathspecs', '--noglob-pathspecs', '--icase-pathspecs', '--no-optional-locks']
4156 elseif empty(subcmd)
4157 let results = s:CompletableSubcommands(dir)
4158 elseif a:0 ==# 2 && subcmd =~# '^\%(commit\|revert\|push\|fetch\|pull\|merge\|rebase\|bisect\)$'
4159 let cmdline = substitute(a:1, '\u\w*\([! ] *\)' . subcmd, 'G' . subcmd, '')
4160 let caps_subcmd = substitute(subcmd, '\%(^\|-\)\l', '\u&', 'g')
4161 return fugitive#{caps_subcmd}Complete(a:lead, cmdline, a:2 + len(cmdline) - len(a:1), dir, root)
4162 elseif pre =~# ' -- '
4163 return fugitive#CompletePath(a:lead, a:1, a:2, dir, root)
4164 elseif a:lead =~# '^-'
4165 let results = split(s:ChompDefault('', [dir, subcmd, '--git-completion-helper']), ' ')
4167 return fugitive#CompleteObject(a:lead, a:1, a:2, dir, root)
4169 return filter(results, 'strpart(v:val, 0, strlen(a:lead)) ==# a:lead')
4172 function! fugitive#CompleteForWorkingDir(A, L, P, ...) abort
4173 let path = a:0 ? a:1 : getcwd()
4174 return fugitive#Complete(a:A, a:L, a:P, FugitiveExtractGitDir(path), path)
4177 " Section: :Gcd, :Glcd
4179 function! fugitive#CdComplete(A, L, P) abort
4180 return filter(fugitive#CompletePath(a:A), 'v:val =~# "/$"')
4183 function! fugitive#Cd(path, ...) abort
4184 exe s:VersionCheck()
4185 let path = substitute(a:path, '^:/:\=\|^:(\%(top\|top,literal\|literal,top\|literal\))', '', '')
4186 if path !~# '^/\|^\a\+:\|^\.\.\=\%(/\|$\)'
4189 let path = (empty(s:Tree(dir)) ? dir : s:Tree(dir)) . '/' . path
4191 return (a:0 && a:1 ? 'lcd ' : 'cd ') . fnameescape(s:VimSlash(path))
4196 function! s:StatusCommand(line1, line2, range, count, bang, mods, reg, arg, args, ...) abort
4197 let dir = a:0 ? s:Dir(a:1) : s:Dir()
4200 let mods = s:Mods(a:mods, 'Edge')
4201 let file = fugitive#Find(':', dir)
4202 let arg = ' +setl\ foldmarker=<<<<<<<<,>>>>>>>>\|let\ w:fugitive_status=FugitiveGitDir() ' .
4203 \ s:fnameescape(file)
4204 for tabnr in [tabpagenr()] + (mods =~# '\<tab\>' ? range(1, tabpagenr('$')) : [])
4205 let bufs = tabpagebuflist(tabnr)
4206 for winnr in range(1, tabpagewinnr(tabnr, '$'))
4207 if s:cpath(file, fnamemodify(bufname(bufs[winnr-1]), ':p'))
4208 if tabnr == tabpagenr() && winnr == winnr()
4209 call s:ReloadStatus()
4211 call s:ExpireStatus(dir)
4212 exe tabnr . 'tabnext'
4213 exe winnr . 'wincmd w'
4215 let w:fugitive_status = dir
4222 return mods . 'edit' . (a:bang ? '!' : '') . arg
4224 return mods . 'pedit' . arg . '|wincmd P'
4226 return mods . 'keepalt split' . arg
4229 return 'echoerr ' . string(v:exception)
4234 function! s:StageJump(offset, section, ...) abort
4235 let line = search('^\%(' . a:section . '\)', 'nw')
4237 let line = search('^\%(' . a:1 . '\)', 'nw')
4242 for i in range(a:offset)
4243 call search(s:file_commit_pattern . '\|^$', 'W')
4244 if empty(getline('.')) && a:0 && getline(line('.') + 1) =~# '^\%(' . a:1 . '\)'
4245 call search(s:file_commit_pattern . '\|^$', 'W')
4247 if empty(getline('.'))
4251 call s:StageReveal()
4253 call s:StageReveal()
4260 function! s:StageSeek(info, fallback) abort
4262 if empty(info.heading)
4265 let line = search('^' . escape(info.heading, '^$.*[]~\') . ' (\d\++\=)$', 'wn')
4267 for section in get({'Staged': ['Unstaged', 'Untracked'], 'Unstaged': ['Untracked', 'Staged'], 'Untracked': ['Unstaged', 'Staged']}, info.section, [])
4268 let line = search('^' . section, 'wn')
4270 return line + (info.index > 0 ? 1 : 0)
4276 while len(getline(line))
4277 let filename = matchstr(getline(line), '^[A-Z?] \zs.*')
4279 \ ((info.filename[-1:-1] ==# '/' && filename[0 : len(info.filename) - 1] ==# info.filename) ||
4280 \ (filename[-1:-1] ==# '/' && filename ==# info.filename[0 : len(filename) - 1]) ||
4281 \ filename ==# info.filename)
4285 if getline(line+1) !~# '^@'
4286 exe s:StageInline('show', line)
4288 if getline(line+1) !~# '^@'
4291 let type = info.sigil ==# '-' ? '-' : '+'
4293 while offset < info.offset
4295 if getline(line) =~# '^@'
4296 let offset = +matchstr(getline(line), type . '\zs\d\+') - 1
4297 elseif getline(line) =~# '^[ ' . type . ']'
4299 elseif getline(line) !~# '^[ @\+-]'
4306 let commit = matchstr(getline(line), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\+')
4307 if len(commit) && commit ==# info.commit
4313 let i += getline(line) !~# '^[ @\+-]'
4316 return exists('backup') ? backup : line - 1
4319 function! s:DoAutocmdChanged(dir) abort
4320 let dir = a:dir is# -2 ? '' : FugitiveGitDir(a:dir)
4321 if empty(dir) || !exists('#User#FugitiveChanged') || exists('g:fugitive_event')
4325 let g:fugitive_event = dir
4326 if type(a:dir) == type({}) && has_key(a:dir, 'args') && has_key(a:dir, 'exit_status')
4327 let g:fugitive_result = a:dir
4329 exe s:DoAutocmd('User FugitiveChanged')
4331 unlet! g:fugitive_event g:fugitive_result
4332 " Force statusline reload with the buffer's Git dir
4333 if dir isnot# FugitiveGitDir()
4340 function! s:ReloadStatusBuffer() abort
4341 if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading'))
4344 let original_lnum = line('.')
4345 let info = s:StageInfo(original_lnum)
4346 exe fugitive#BufReadStatus(0)
4347 call setpos('.', [0, s:StageSeek(info, original_lnum), 1, 0])
4351 function! s:ReloadStatus() abort
4352 call s:ExpireStatus(-1)
4353 call s:ReloadStatusBuffer()
4354 exe s:DoAutocmdChanged(-1)
4358 let s:last_time = reltime()
4359 if !exists('s:last_times')
4360 let s:last_times = {}
4363 function! s:ExpireStatus(bufnr) abort
4364 if a:bufnr is# -2 || a:bufnr is# 0
4365 let s:head_cache = {}
4366 let s:last_time = reltime()
4369 let head_file = fugitive#Find('.git/HEAD', a:bufnr)
4370 if !empty(head_file)
4371 let s:last_times[s:Tree(a:bufnr) . '/'] = reltime()
4372 if has_key(s:head_cache, head_file)
4373 call remove(s:head_cache, head_file)
4379 function! s:ReloadWinStatus(...) abort
4380 if get(b:, 'fugitive_type', '') !=# 'index' || !empty(get(b:, 'fugitive_loading')) || &modified
4383 if !exists('b:fugitive_status.reltime')
4384 exe call('s:ReloadStatusBuffer', a:000)
4387 let t = b:fugitive_status.reltime
4388 if reltimestr(reltime(s:last_time, t)) =~# '-\|\d\{10\}\.' ||
4389 \ reltimestr(reltime(get(s:last_times, s:Tree() . '/', t), t)) =~# '-\|\d\{10\}\.'
4390 exe call('s:ReloadStatusBuffer', a:000)
4394 function! s:ReloadTabStatus() abort
4395 if !exists('g:fugitive_did_change_at')
4397 elseif exists('t:fugitive_reloaded_at')
4398 let time_ahead = reltime(g:fugitive_did_change_at, t:fugitive_reloaded_at)
4399 if reltimefloat(time_ahead) >= 0
4403 let t:fugitive_reloaded_at = reltime()
4405 while winnr <= winnr('$')
4406 if getbufvar(winbufnr(winnr), 'fugitive_type') ==# 'index'
4408 execute 'noautocmd' winnr.'wincmd w'
4409 let restorewinnr = 1
4412 call s:ReloadWinStatus()
4414 if exists('restorewinnr')
4424 function! fugitive#DidChange(...) abort
4425 call s:ExpireStatus(a:0 ? a:1 : -1)
4426 if a:0 > 1 ? a:2 : (!a:0 || a:1 isnot# 0)
4427 let g:fugitive_did_change_at = reltime()
4428 call s:ReloadTabStatus()
4430 call s:ReloadWinStatus()
4433 exe s:DoAutocmdChanged(a:0 ? a:1 : -1)
4437 function! fugitive#ReloadStatus(...) abort
4438 return call('fugitive#DidChange', a:000)
4441 function! fugitive#EfmDir(...) abort
4442 let dir = matchstr(a:0 ? a:1 : &errorformat, '\c,%\\&\%(git\|fugitive\)_\=dir=\zs\%(\\.\|[^,]\)*')
4443 let dir = substitute(dir, '%%', '%', 'g')
4444 let dir = substitute(dir, '\\\ze[\,]', '', 'g')
4448 augroup fugitive_status
4450 autocmd BufWritePost * call fugitive#DidChange(+expand('<abuf>'), 0)
4451 autocmd User FileChmodPost,FileUnlinkPost call fugitive#DidChange(+expand('<abuf>'), 0)
4452 autocmd ShellCmdPost,ShellFilterPost * nested call fugitive#DidChange(0)
4453 autocmd BufDelete * nested
4454 \ if getbufvar(+expand('<abuf>'), 'buftype') ==# 'terminal' |
4455 \ if !empty(FugitiveGitDir(+expand('<abuf>'))) |
4456 \ call fugitive#DidChange(+expand('<abuf>')) |
4458 \ call fugitive#DidChange(0) |
4461 autocmd QuickFixCmdPost make,lmake,[cl]file,[cl]getfile nested
4462 \ call fugitive#DidChange(fugitive#EfmDir())
4463 autocmd FocusGained *
4464 \ if get(g:, 'fugitive_focus_gained', !has('win32')) |
4465 \ call fugitive#DidChange(0) |
4467 autocmd BufEnter index,index.lock,fugitive://*//
4468 \ call s:ReloadWinStatus()
4470 \ call s:ReloadTabStatus()
4473 function! s:StatusSectionFile(heading, filename) abort
4474 return get(get(get(get(b:, 'fugitive_status', {}), 'files', {}), a:heading, {}), a:filename, {})
4477 function! s:StageInfo(...) abort
4478 let lnum = a:0 ? a:1 : line('.')
4479 let sigil = matchstr(getline(lnum), '^[ @\+-]')
4482 let [lnum, old_lnum, new_lnum] = s:HunkPosition(lnum)
4483 let offset = sigil ==# '-' ? old_lnum : new_lnum
4484 while getline(lnum) =~# '^[ @\+-]'
4488 let slnum = lnum + 1
4491 while len(getline(slnum - 1)) && empty(heading)
4493 let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4494 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4498 let text = matchstr(getline(lnum), '^[A-Z?] \zs.*')
4499 let file = s:StatusSectionFile(heading, text)
4500 let relative = get(file, 'relative', len(text) ? [text] : [])
4501 return {'section': matchstr(heading, '^\u\l\+'),
4502 \ 'heading': heading,
4506 \ 'relative': copy(relative),
4507 \ 'paths': map(copy(relative), 's:Tree() . "/" . v:val'),
4508 \ 'commit': matchstr(getline(lnum), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze '),
4509 \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze \|^\%(\x\x\x\)\@!\l\+\ze [0-9a-f]'),
4510 \ 'submodule': get(file, 'submodule', ''),
4514 function! s:Selection(arg1, ...) abort
4516 let arg1 = line('.')
4518 elseif a:arg1 ==# 'v'
4519 let arg1 = line("'<")
4520 let arg2 = line("'>")
4523 let arg2 = a:0 ? a:1 : 0
4527 let last = first - arg2 - 1
4533 while first <= line('$') && getline(first) =~# '^$\|^[A-Z][a-z]'
4536 if first > last || &filetype !=# 'fugitive'
4540 while getline(flnum) =~# '^[ @\+-]'
4543 let slnum = flnum + 1
4546 while empty(heading)
4548 let heading = matchstr(getline(slnum), '^\u\l\+.\{-\}\ze (\d\++\=)$')
4549 if empty(heading) && getline(slnum) !~# '^[ @\+-]'
4555 \ 'heading': heading,
4556 \ 'section': matchstr(heading, '^\u\l\+'),
4564 let line = getline(flnum)
4565 let lnum = first - (arg1 == flnum ? 0 : 1)
4566 let root = s:Tree() . '/'
4568 let heading = matchstr(line, '^\u\l\+\ze.\{-\}\ze (\d\++\=)$')
4570 let template.heading = heading
4571 let template.section = matchstr(heading, '^\u\l\+')
4572 let template.index = 0
4573 elseif line =~# '^[ @\+-]'
4574 let template.index -= 1
4575 if !results[-1].patch
4576 let results[-1].patch = lnum
4578 let results[-1].lnum = lnum
4579 elseif line =~# '^[A-Z?] '
4580 let text = matchstr(line, '^[A-Z?] \zs.*')
4581 let file = s:StatusSectionFile(template.heading, text)
4582 let relative = get(file, 'relative', len(text) ? [text] : [])
4583 call add(results, extend(deepcopy(template), {
4586 \ 'relative': copy(relative),
4587 \ 'paths': map(copy(relative), 'root . v:val'),
4588 \ 'status': matchstr(line, '^[A-Z?]'),
4590 elseif line =~# '^\x\x\x\+ '
4591 call add(results, extend({
4593 \ 'commit': matchstr(line, '^\x\x\x\+'),
4594 \ }, template, 'keep'))
4595 elseif line =~# '^\l\+ \x\x\x\+ '
4596 call add(results, extend({
4598 \ 'commit': matchstr(line, '^\l\+ \zs\x\x\x\+'),
4599 \ 'status': matchstr(line, '^\l\+'),
4600 \ }, template, 'keep'))
4603 let template.index += 1
4604 let line = getline(lnum)
4606 if len(results) && results[0].patch && arg2 == 0
4607 while getline(results[0].patch) =~# '^[ \+-]'
4608 let results[0].patch -= 1
4610 while getline(results[0].lnum + 1) =~# '^[ \+-]'
4611 let results[0].lnum += 1
4617 function! s:StageArgs(visual) abort
4620 for record in s:Selection(a:visual ? 'v' : 'n')
4621 if len(record.commit)
4622 call add(commits, record.commit)
4624 call extend(paths, record.paths)
4626 if s:cpath(s:Tree(), getcwd())
4627 call map(paths, 'fugitive#Path(v:val, "./")')
4629 return join(map(commits + paths, 's:fnameescape(v:val)'), ' ')
4632 function! s:Do(action, visual) abort
4633 let line = getline('.')
4635 if !a:visual && !v:count && line =~# '^[A-Z][a-z]'
4636 let header = matchstr(line, '^\S\+\ze:')
4637 if len(header) && exists('*s:Do' . a:action . header . 'Header')
4638 let reload = s:Do{a:action}{header}Header(matchstr(line, ': \zs.*')) > 0
4640 let section = matchstr(line, '^\S\+')
4641 if exists('*s:Do' . a:action . section . 'Heading')
4642 let reload = s:Do{a:action}{section}Heading(line) > 0
4645 return reload ? s:ReloadStatus() : ''
4647 let selection = s:Selection(a:visual ? 'v' : 'n')
4651 call filter(selection, 'v:val.section ==# selection[0].section')
4655 for record in selection
4656 if exists('*s:Do' . a:action . record.section)
4657 let status = s:Do{a:action}{record.section}(record)
4664 let reload = reload || (status > 0)
4667 execute record.lnum + 1
4671 return 'echoerr ' . string(v:exception)
4674 execute s:ReloadStatus()
4676 if exists('success')
4677 call s:StageReveal()
4683 function! s:StageReveal() abort
4685 let begin = line('.')
4686 if getline(begin) =~# '^@'
4688 while getline(end) =~# '^[ \+-]'
4691 elseif getline(begin) =~# '^commit '
4693 while end < line('$') && getline(end + 1) !~# '^commit '
4696 elseif getline(begin) =~# s:section_pattern
4698 while len(getline(end + 1))
4703 while line('.') > line('w0') + &scrolloff && end > line('w$')
4704 execute "normal! \<C-E>"
4709 let s:file_pattern = '^[A-Z?] .\|^diff --'
4710 let s:file_commit_pattern = s:file_pattern . '\|^\%(\l\{3,\} \)\=[0-9a-f]\{4,\} '
4711 let s:item_pattern = s:file_commit_pattern . '\|^@@'
4713 function! s:NextHunk(count) abort
4714 if &filetype ==# 'fugitive' && getline('.') =~# s:file_pattern
4715 exe s:StageInline('show')
4717 for i in range(a:count)
4718 if &filetype ==# 'fugitive'
4719 call search(s:file_pattern . '\|^@', 'W')
4720 if getline('.') =~# s:file_pattern
4721 exe s:StageInline('show')
4722 if getline(line('.') + 1) =~# '^@'
4727 call search('^@@', 'W')
4730 call s:StageReveal()
4734 function! s:PreviousHunk(count) abort
4736 for i in range(a:count)
4737 if &filetype ==# 'fugitive'
4738 if getline('.') =~# '^@' && getline(line('.') - 1) =~# s:file_pattern
4741 let lnum = search(s:file_pattern . '\|^@','Wbn')
4742 call s:StageInline('show', lnum)
4743 call search('^? .\|^@','Wb')
4745 call search('^@@', 'Wb')
4748 call s:StageReveal()
4752 function! s:NextFile(count) abort
4753 for i in range(a:count)
4754 exe s:StageInline('hide')
4755 if !search(s:file_pattern, 'W')
4759 exe s:StageInline('hide')
4763 function! s:PreviousFile(count) abort
4764 exe s:StageInline('hide')
4766 for i in range(a:count)
4767 if !search(s:file_pattern, 'Wb')
4770 exe s:StageInline('hide')
4775 function! s:NextItem(count) abort
4776 for i in range(a:count)
4777 if !search(s:item_pattern, 'W') && getline('.') !~# s:item_pattern
4778 call search('^commit ', 'W')
4781 call s:StageReveal()
4785 function! s:PreviousItem(count) abort
4787 for i in range(a:count)
4788 if !search(s:item_pattern, 'Wb') && getline('.') !~# s:item_pattern
4789 call search('^commit ', 'Wb')
4792 call s:StageReveal()
4796 let s:section_pattern = '^[A-Z][a-z][^:]*$'
4797 let s:section_commit_pattern = s:section_pattern . '\|^commit '
4799 function! s:NextSection(count) abort
4800 let orig = line('.')
4801 if getline('.') !~# '^commit '
4804 for i in range(a:count)
4805 if !search(s:section_commit_pattern, 'W')
4809 if getline('.') =~# s:section_commit_pattern
4810 call s:StageReveal()
4811 return getline('.') =~# s:section_pattern ? '+' : ':'
4817 function! s:PreviousSection(count) abort
4818 let orig = line('.')
4819 if getline('.') !~# '^commit '
4823 for i in range(a:count)
4824 if !search(s:section_commit_pattern . '\|\%^', 'bW')
4828 if getline('.') =~# s:section_commit_pattern || line('.') == 1
4829 call s:StageReveal()
4830 return getline('.') =~# s:section_pattern ? '+' : ':'
4836 function! s:NextSectionEnd(count) abort
4838 if empty(getline('.'))
4841 for i in range(a:count)
4842 if !search(s:section_commit_pattern, 'W')
4846 return search('^.', 'Wb')
4849 function! s:PreviousSectionEnd(count) abort
4851 for i in range(a:count)
4852 if search(s:section_commit_pattern, 'Wb') <= 1
4862 return search('^.', 'Wb')
4865 function! s:PatchSearchExpr(reverse) abort
4866 let line = getline('.')
4867 if col('.') ==# 1 && line =~# '^[+-]'
4868 if line =~# '^[+-]\{3\} '
4869 let pattern = '^[+-]\{3\} ' . substitute(escape(strpart(line, 4), '^$.*[]~\'), '^\w/', '\\w/', '') . '$'
4871 let pattern = '^[+-]\s*' . escape(substitute(strpart(line, 1), '^\s*\|\s*$', '', ''), '^$.*[]~\') . '\s*$'
4874 return '?' . escape(pattern, '/?') . "\<CR>"
4876 return '/' . escape(pattern, '/') . "\<CR>"
4879 return a:reverse ? '#' : '*'
4882 function! s:StageInlineGetDiff(diff_section, info) abort
4884 if a:info.status ==# 'U'
4885 let diff_header = 'diff --cc ' . s:Quote(a:info.relative[0])
4887 let diff_header = 'diff --git ' . s:Quote(a:info.relative[-1]) . ' ' . s:Quote(a:info.relative[0])
4889 let stdout = fugitive#Wait(a:diff_section).stdout
4890 let start = index(stdout, diff_header)
4894 let index = start + 1
4895 while get(stdout, index, '@@') !~# '^@@\|^diff '
4898 while get(stdout, index, '') =~# '^[@ \+-]'
4899 call add(diff, stdout[index])
4902 return [diff, start]
4905 function! s:StageInline(mode, ...) abort
4906 if &filetype !=# 'fugitive'
4909 let lnum1 = a:0 ? a:1 : line('.')
4910 let lnum = lnum1 + 1
4911 if a:0 > 1 && a:2 == 0 && lnum1 == 1
4912 let lnum = line('$') - 1
4913 elseif a:0 > 1 && a:2 == 0
4914 let info = s:StageInfo(lnum - 1)
4915 if empty(info.paths) && len(info.section)
4916 while len(getline(lnum))
4925 while lnum > 0 && getline(lnum) =~# '^[ @\+-]'
4928 let info = s:StageInfo(lnum)
4929 let diff_section = get(get(get(b:, 'fugitive_status', {}), 'diff', {}), info.section, {})
4930 if empty(diff_section)
4933 if getline(lnum + 1) =~# '^[ @\+-]'
4934 let lnum2 = lnum + 1
4935 while getline(lnum2 + 1) =~# '^[ @\+-]'
4938 if a:mode !=# 'show'
4939 setlocal modifiable noreadonly
4940 exe 'silent keepjumps ' . (lnum + 1) . ',' . lnum2 . 'delete _'
4941 call remove(b:fugitive_expanded[info.section], info.filename)
4942 setlocal nomodifiable readonly nomodified
4946 if info.status !~# '^[ADMRU]$' || a:mode ==# 'hide'
4949 let [diff, start] = s:StageInlineGetDiff(diff_section, info)
4951 setlocal modifiable noreadonly
4952 silent call append(lnum, diff)
4953 let b:fugitive_expanded[info.section][info.filename] = [start]
4954 setlocal nomodifiable readonly nomodified
4955 if foldclosed(lnum+1) > 0
4956 silent exe (lnum+1) . ',' . (lnum+len(diff)) . 'foldopen!'
4963 function! s:NextExpandedHunk(count) abort
4964 for i in range(a:count)
4965 call s:StageInline('show', line('.'), 1)
4966 call search(s:file_pattern . '\|^@','W')
4971 function! s:StageDiff(diff) abort
4972 let lnum = line('.')
4973 let info = s:StageInfo(lnum)
4974 let prefix = info.offset > 0 ? '+' . info.offset : ''
4975 if info.submodule =~# '^S'
4976 if info.section ==# 'Staged'
4977 return 'Git --paginate diff --no-ext-diff --submodule=log --cached -- ' . info.paths[0]
4978 elseif info.submodule =~# '^SC'
4979 return 'Git --paginate diff --no-ext-diff --submodule=log -- ' . info.paths[0]
4981 return 'Git --paginate diff --no-ext-diff --submodule=diff -- ' . info.paths[0]
4983 elseif empty(info.paths) && info.section ==# 'Staged'
4984 return 'Git --paginate diff --no-ext-diff --cached'
4985 elseif empty(info.paths)
4986 return 'Git --paginate diff --no-ext-diff'
4987 elseif len(info.paths) > 1
4988 execute 'Gedit' . prefix s:fnameescape(':0:' . info.paths[0])
4989 return a:diff . '! @:'.s:fnameescape(info.paths[1])
4990 elseif info.section ==# 'Staged' && info.sigil ==# '-'
4991 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4992 return a:diff . '! :0:%'
4993 elseif info.section ==# 'Staged'
4994 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4995 return a:diff . '! @:%'
4996 elseif info.sigil ==# '-'
4997 execute 'Gedit' prefix s:fnameescape(':0:'.info.paths[0])
4998 return a:diff . '! :(top)%'
5000 execute 'Gedit' prefix s:fnameescape(':(top)'.info.paths[0])
5005 function! s:StageDiffEdit() abort
5006 let info = s:StageInfo(line('.'))
5007 let arg = (empty(info.paths) ? s:Tree() : info.paths[0])
5008 if info.section ==# 'Staged'
5009 return 'Git --paginate diff --no-ext-diff --cached '.s:fnameescape(arg)
5010 elseif info.status ==# '?'
5011 call s:TreeChomp('add', '--intent-to-add', '--', arg)
5012 return s:ReloadStatus()
5014 return 'Git --paginate diff --no-ext-diff '.s:fnameescape(arg)
5018 function! s:StageApply(info, reverse, extra) abort
5019 if a:info.status ==# 'R'
5020 throw 'fugitive: patching renamed file not yet supported'
5022 let cmd = ['apply', '-p0', '--recount'] + a:extra
5024 let start = info.patch
5026 let lines = getline(start, end)
5027 if empty(filter(copy(lines), 'v:val =~# "^[+-]"'))
5030 while getline(end) =~# '^[-+\ ]'
5032 if getline(end) =~# '^[' . (a:reverse ? '+' : '-') . '\ ]'
5033 call add(lines, ' ' . getline(end)[1:-1])
5036 while start > 0 && getline(start) !~# '^@'
5038 if getline(start) =~# '^[' . (a:reverse ? '+' : '-') . ' ]'
5039 call insert(lines, ' ' . getline(start)[1:-1])
5040 elseif getline(start) =~# '^@'
5041 call insert(lines, getline(start))
5045 throw 'fugitive: could not find hunk'
5046 elseif getline(start) !~# '^@@ '
5047 throw 'fugitive: cannot apply conflict hunk'
5049 let i = b:fugitive_expanded[info.section][info.filename][0]
5051 let diff_lines = fugitive#Wait(b:fugitive_status.diff[info.section]).stdout
5052 while get(diff_lines, i, '@') !~# '^@'
5053 let line = diff_lines[i]
5054 if line ==# '--- /dev/null'
5055 call add(head, '--- ' . get(diff_lines, i + 1, '')[4:-1])
5056 elseif line !~# '^new file '
5057 call add(head, line)
5061 call extend(lines, head, 'keep')
5062 let temp = tempname()
5063 call writefile(lines, temp)
5065 call add(cmd, '--reverse')
5067 call extend(cmd, ['--', temp])
5068 let output = s:ChompStderr(cmd)
5072 call s:throw(output)
5075 function! s:StageDelete(lnum1, lnum2, count) abort
5079 let did_conflict_err = 0
5080 let reset_commit = matchstr(getline(a:lnum1), '^Un\w\+ \%(to\| from\) \zs\S\+')
5082 for info in s:Selection(a:lnum1, a:lnum2)
5083 if empty(info.paths)
5085 let reset_commit = info.commit . '^'
5089 let sub = get(s:StatusSectionFile(info.section, info.filename), 'submodule', '')
5090 if sub =~# '^S' && info.status ==# 'M'
5091 let undo = 'Git checkout ' . fugitive#RevParse('HEAD', FugitiveExtractGitDir(info.paths[0]))[0:10] . ' --'
5093 let err .= '|echoerr ' . string('fugitive: will not touch submodule ' . string(info.relative[0]))
5095 elseif info.status ==# 'D'
5096 let undo = 'GRemove'
5097 elseif info.paths[0] =~# '/$'
5098 let err .= '|echoerr ' . string('fugitive: will not delete directory ' . string(info.relative[0]))
5101 let undo = 'Gread ' . s:TreeChomp('hash-object', '-w', '--', info.paths[0])[0:10]
5104 call s:StageApply(info, 1, info.section ==# 'Staged' ? ['--index'] : [])
5106 if info.section ==# 'Staged'
5107 call s:TreeChomp('reset', '--', info.paths[0])
5109 call s:TreeChomp('submodule', 'update', '--', info.paths[0])
5110 elseif info.status ==# '?'
5111 call s:TreeChomp('clean', '-f', '--', info.paths[0])
5113 if get(s:StatusSectionFile('Staged', info.filename), 'status', '') ==# 'D'
5114 call delete(info.paths[0])
5116 call s:TreeChomp('checkout', '--ours', '--', info.paths[0])
5119 if get(s:StatusSectionFile('Unstaged', info.filename), 'status', '') ==# 'D'
5120 call delete(info.paths[0])
5122 call s:TreeChomp('checkout', '--theirs', '--', info.paths[0])
5124 elseif info.status =~# '[ADU]' &&
5125 \ get(s:StatusSectionFile(info.section ==# 'Staged' ? 'Unstaged' : 'Staged', info.filename), 'status', '') =~# '[AU]'
5126 if get(g:, 'fugitive_conflict_x', 0)
5127 call s:TreeChomp('checkout', info.section ==# 'Unstaged' ? '--ours' : '--theirs', '--', info.paths[0])
5129 if !did_conflict_err
5130 let err .= '|echoerr "Use 2X for --ours or 3X for --theirs"'
5131 let did_conflict_err = 1
5135 elseif info.status ==# 'U'
5136 call delete(info.paths[0])
5137 elseif info.status ==# 'A'
5138 call s:TreeChomp('rm', '-f', '--', info.paths[0])
5139 elseif info.section ==# 'Unstaged'
5140 call s:TreeChomp('checkout', '--', info.paths[0])
5142 call s:TreeChomp('checkout', '@', '--', info.paths[0])
5145 call add(restore, ':Gsplit ' . s:fnameescape(info.relative[0]) . '|' . undo)
5149 let err .= '|echoerr ' . string(v:exception)
5152 if len(reset_commit) && empty(err)
5153 call feedkeys(':Git reset ' . reset_commit)
5157 exe s:ReloadStatus()
5158 call s:StageReveal()
5159 return 'checktime|redraw|echomsg ' . string('To restore, ' . join(restore, '|')) . err
5162 function! s:StageIgnore(lnum1, lnum2, count) abort
5164 for info in s:Selection(a:lnum1, a:lnum2)
5165 call extend(paths, info.relative)
5167 call map(paths, '"/" . v:val')
5169 let dir = fugitive#Find('.git/info/')
5170 if !isdirectory(dir)
5177 exe 'Gsplit' (a:count ? '.gitignore' : '.git/info/exclude')
5178 let last = line('$')
5179 if last == 1 && empty(getline(1))
5180 call setline(last, paths)
5182 call append(last, paths)
5188 function! s:DoToggleHeadHeader(value) abort
5189 exe 'edit' fnameescape(fugitive#Find('.git/'))
5190 call search('\C^index$', 'wc')
5193 function! s:DoToggleHelpHeader(value) abort
5194 exe 'help fugitive-map'
5197 function! s:DoStagePushHeader(value) abort
5198 let stat = get(b:, 'fugitive_status', {})
5199 let remote = get(stat, 'push_remote', '')
5200 let branch = substitute(get(stat, 'push', ''), '^ref/heads/', '', '')
5201 if empty(remote) || empty(branch)
5204 call feedkeys(':Git push ' . remote . ' ' . branch)
5207 function! s:DoTogglePushHeader(value) abort
5208 return s:DoStagePushHeader(a:value)
5211 function! s:DoStageUnpushedHeading(heading) abort
5212 let stat = get(b:, 'fugitive_status', {})
5213 let remote = get(stat, 'push_remote', '')
5214 let push = get(stat, 'push', '')
5215 if empty(remote) || empty(push)
5218 call feedkeys(':Git push ' . remote . ' ' . '@:' . push)
5221 function! s:DoToggleUnpushedHeading(heading) abort
5222 return s:DoStageUnpushedHeading(a:heading)
5225 function! s:DoStageUnpushed(record) abort
5226 let stat = get(b:, 'fugitive_status', {})
5227 let remote = get(stat, 'push_remote', '')
5228 let push = get(stat, 'push', '')
5229 if empty(remote) || empty(push)
5232 call feedkeys(':Git push ' . remote . ' ' . a:record.commit . ':' . push)
5235 function! s:DoToggleUnpushed(record) abort
5236 return s:DoStageUnpushed(a:record)
5239 function! s:DoUnstageUnpulledHeading(heading) abort
5240 call feedkeys(':Git rebase')
5243 function! s:DoToggleUnpulledHeading(heading) abort
5244 call s:DoUnstageUnpulledHeading(a:heading)
5247 function! s:DoUnstageUnpulled(record) abort
5248 call feedkeys(':Git rebase ' . a:record.commit)
5251 function! s:DoToggleUnpulled(record) abort
5252 call s:DoUnstageUnpulled(a:record)
5255 function! s:DoUnstageUnpushed(record) abort
5256 call feedkeys(':Git -c sequence.editor=true rebase --interactive --autosquash ' . a:record.commit . '^')
5259 function! s:DoToggleStagedHeading(...) abort
5260 call s:TreeChomp('reset', '-q')
5264 function! s:DoUnstageStagedHeading(heading) abort
5265 return s:DoToggleStagedHeading(a:heading)
5268 function! s:DoToggleUnstagedHeading(...) abort
5269 call s:TreeChomp('add', '-u')
5273 function! s:DoStageUnstagedHeading(heading) abort
5274 return s:DoToggleUnstagedHeading(a:heading)
5277 function! s:DoToggleUntrackedHeading(...) abort
5278 call s:TreeChomp('add', '.')
5282 function! s:DoStageUntrackedHeading(heading) abort
5283 return s:DoToggleUntrackedHeading(a:heading)
5286 function! s:DoToggleStaged(record) abort
5288 return s:StageApply(a:record, 1, ['--cached'])
5290 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5295 function! s:DoUnstageStaged(record) abort
5296 return s:DoToggleStaged(a:record)
5299 function! s:DoToggleUnstaged(record) abort
5301 return s:StageApply(a:record, 0, ['--cached'])
5303 call s:TreeChomp(['add', '-A', '--'] + a:record.paths)
5308 function! s:DoStageUnstaged(record) abort
5309 return s:DoToggleUnstaged(a:record)
5312 function! s:DoUnstageUnstaged(record) abort
5313 if a:record.status ==# 'A'
5314 call s:TreeChomp(['reset', '-q', '--'] + a:record.paths)
5321 function! s:DoToggleUntracked(record) abort
5322 call s:TreeChomp(['add', '--'] + a:record.paths)
5326 function! s:DoStageUntracked(record) abort
5327 return s:DoToggleUntracked(a:record)
5330 function! s:StagePatch(lnum1, lnum2, ...) abort
5334 let patch_only = a:0 && a:1
5336 for lnum in range(a:lnum1,a:lnum2)
5337 let info = s:StageInfo(lnum)
5338 if empty(info.paths) && info.section ==# 'Staged'
5339 execute 'tab Git reset --patch'
5341 elseif empty(info.paths) && info.section ==# 'Unstaged'
5342 execute 'tab Git add --patch'
5344 elseif empty(info.paths) && info.section ==# 'Untracked'
5345 execute 'tab Git add --interactive'
5347 elseif !patch_only && info.section ==# 'Unpushed'
5348 if empty(info.commit)
5349 call s:DoStageUnpushedHeading(info)
5351 call s:DoStageUnpushed(info)
5354 elseif empty(info.paths)
5358 if info.section ==# 'Staged'
5359 let reset += info.relative
5360 elseif info.section ==# 'Untracked'
5361 let intend += info.paths
5362 elseif info.status !~# '^D'
5363 let add += info.relative
5368 call s:TreeChomp(['add', '--intent-to-add', '--'] + intend)
5371 execute "tab Git add --patch -- ".join(map(add,'fnameescape(v:val)'))
5374 execute "tab Git reset --patch -- ".join(map(reset,'fnameescape(v:val)'))
5377 return 'echoerr ' . string(v:exception)
5379 return s:ReloadStatus()
5382 " Section: :Git commit, :Git revert
5384 function! s:CommitInteractive(line1, line2, range, bang, mods, options, patch) abort
5385 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)
5386 let status = len(status) ? status . '|' : ''
5388 return status . 'if search("^Unstaged")|exe "normal >"|exe "+"|endif'
5390 return status . 'if search("^Untracked\\|^Unstaged")|exe "+"|endif'
5394 function! s:CommitSubcommand(line1, line2, range, bang, mods, options) abort
5395 let argv = copy(a:options.subcommand_args)
5397 while get(argv, i, '--') !=# '--'
5398 if argv[i] =~# '^-[apzsneiovq].'
5399 call insert(argv, argv[i][0:1])
5400 let argv[i+1] = '-' . argv[i+1][2:-1]
5405 if s:HasOpt(argv, '-i', '--interactive')
5406 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 0)
5407 elseif s:HasOpt(argv, '-p', '--patch')
5408 return s:CommitInteractive(a:line1, a:line2, a:range, a:bang, a:mods, a:options, 1)
5414 function! s:RevertSubcommand(line1, line2, range, bang, mods, options) abort
5415 return {'insert_args': ['--edit']}
5418 function! fugitive#CommitComplete(A, L, P, ...) abort
5419 let dir = a:0 ? a:1 : s:Dir()
5420 if a:A =~# '^--fixup=\|^--squash='
5421 let commits = s:LinesError([dir, 'log', '--pretty=format:%s', '@{upstream}..'])[0]
5422 let pre = matchstr(a:A, '^--\w*=''\=') . ':/^'
5424 call map(commits, 'pre . string(tr(v:val, "|\"^$*[]", "......."))[1:-1]')
5425 call filter(commits, 'strpart(v:val, 0, strlen(a:A)) ==# a:A')
5428 return s:FilterEscape(map(commits, 'pre . tr(v:val, "\\ !^$*?[]()''\"`&;<>|#", "....................")'), a:A)
5431 return s:CompleteSub('commit', a:A, a:L, a:P, function('fugitive#CompletePath'), a:000)
5436 function! fugitive#RevertComplete(A, L, P, ...) abort
5437 return s:CompleteSub('revert', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5440 " Section: :Git merge, :Git rebase, :Git pull
5442 function! fugitive#MergeComplete(A, L, P, ...) abort
5443 return s:CompleteSub('merge', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5446 function! fugitive#RebaseComplete(A, L, P, ...) abort
5447 return s:CompleteSub('rebase', a:A, a:L, a:P, function('s:CompleteRevision'), a:000)
5450 function! fugitive#PullComplete(A, L, P, ...) abort
5451 return s:CompleteSub('pull', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
5454 function! s:MergeSubcommand(line1, line2, range, bang, mods, options) abort
5455 if empty(a:options.subcommand_args) && (
5456 \ filereadable(fugitive#Find('.git/MERGE_MSG', a:options)) ||
5457 \ isdirectory(fugitive#Find('.git/rebase-apply', a:options)) ||
5458 \ !empty(s:TreeChomp([a:options.git_dir, 'diff-files', '--diff-filter=U'])))
5459 return 'echoerr ":Git merge for loading conflicts has been removed in favor of :Git mergetool"'
5464 function! s:RebaseSubcommand(line1, line2, range, bang, mods, options) abort
5465 let args = a:options.subcommand_args
5466 if s:HasOpt(args, '--autosquash') && !s:HasOpt(args, '-i', '--interactive')
5467 return {'env': {'GIT_SEQUENCE_EDITOR': 'true'}, 'insert_args': ['--interactive']}
5472 " Section: :Git bisect
5474 function! s:CompleteBisect(A, L, P, ...) abort
5475 let bisect_subcmd = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
5476 if empty(bisect_subcmd)
5477 let subcmds = ['start', 'bad', 'new', 'good', 'old', 'terms', 'skip', 'next', 'reset', 'replay', 'log', 'run']
5478 return s:FilterEscape(subcmds, a:A)
5480 let dir = a:0 ? a:1 : s:Dir()
5481 return fugitive#CompleteObject(a:A, dir)
5484 function! fugitive#BisectComplete(A, L, P, ...) abort
5485 return s:CompleteSub('bisect', a:A, a:L, a:P, function('s:CompleteBisect'), a:000)
5488 " Section: :Git difftool, :Git mergetool
5490 function! s:ToolItems(state, from, to, offsets, text, ...) abort
5492 for i in range(len(a:state.diff))
5493 let diff = a:state.diff[i]
5494 let path = (i == len(a:state.diff) - 1) ? a:to : a:from
5499 \ 'valid': a:0 ? a:1 : 1,
5500 \ 'filename': diff.filename . s:VimSlash(path),
5501 \ 'lnum': matchstr(get(a:offsets, i), '\d\+'),
5503 if len(get(diff, 'module', ''))
5504 let item.module = diff.module . path
5506 call add(items, item)
5508 if get(a:offsets, 0, '') isnot# 'none'
5509 let items[-1].context = {'diff': items[0:-2]}
5514 function! s:ToolToFrom(str) abort
5516 let str = a:str =~# '{.* => .*}' ? a:str : '{' . a:str . '}'
5517 return [substitute(str, '{.* => \(.*\)}', '\1', ''),
5518 \ substitute(str, '{\(.*\) => .*}', '\1', '')]
5520 return [a:str, a:str]
5524 function! s:ToolParse(state, line) abort
5525 if type(a:line) !=# type('') || a:state.mode ==# 'hunk' && a:line =~# '^[ +-]'
5527 elseif a:line =~# '^diff '
5528 let a:state.mode = 'diffhead'
5529 let a:state.from = ''
5531 elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- [^/]'
5532 let a:state.from = a:line[4:-1]
5533 let a:state.to = a:state.from
5534 elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ [^/]'
5535 let a:state.to = a:line[4:-1]
5536 if empty(get(a:state, 'from', ''))
5537 let a:state.from = a:state.to
5539 elseif a:line[0] ==# '@'
5540 let a:state.mode = 'hunk'
5541 if has_key(a:state, 'from')
5542 let offsets = split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' ')
5543 return s:ToolItems(a:state, a:state.from, a:state.to, offsets, matchstr(a:line, ' @@\+ \zs.*'))
5545 elseif a:line =~# '^\* Unmerged path .'
5546 let file = a:line[16:-1]
5547 return s:ToolItems(a:state, file, file, [], '')
5548 elseif a:line =~# '^[A-Z]\d*\t.\|^:.*\t.'
5549 " --raw, --name-status
5550 let [status; files] = split(a:line, "\t")
5551 return s:ToolItems(a:state, files[0], files[-1], [], a:state.name_only ? '' : status)
5552 elseif a:line =~# '^ \S.* |'
5554 let [_, to, changes; __] = matchlist(a:line, '^ \(.\{-\}\) \+|\zs \(.*\)$')
5555 let [to, from] = s:ToolToFrom(to)
5556 return s:ToolItems(a:state, from, to, [], changes)
5557 elseif a:line =~# '^ *\([0-9.]\+%\) .'
5559 let [_, changes, to; __] = matchlist(a:line, '^ *\([0-9.]\+%\) \(.*\)')
5560 return s:ToolItems(a:state, to, to, [], changes)
5561 elseif a:line =~# '^\(\d\+\|-\)\t\(\d\+\|-\)\t.'
5563 let [_, add, remove, to; __] = matchlist(a:line, '^\(\d\+\|-\)\t\(\d\+\|-\)\t\(.*\)')
5564 let [to, from] = s:ToolToFrom(to)
5565 return s:ToolItems(a:state, from, to, [], add ==# '-' ? 'Binary file' : '+' . add . ' -' . remove, add !=# '-')
5566 elseif a:line =~# '^\f\+:\d\+: \D'
5568 let [_, to, line, text; __] = matchlist(a:line, '^\(\f\+\):\(\d\+\):\s*\(.*\)$')
5569 return s:ToolItems(a:state, to, to, ['none', line], text)
5570 elseif a:state.mode !=# 'diffhead' && a:state.mode !=# 'hunk' && len(a:line) || a:line =~# '^git: \|^usage: \|^error: \|^fatal: '
5571 return [{'text': a:line}]
5576 function! s:ToolStream(line1, line2, range, bang, mods, options, args, state) abort
5578 let argv = copy(a:args)
5582 let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5583 if len(match) && len(match[2])
5584 call insert(argv, match[1])
5585 let argv[i+1] = '-' . match[2]
5589 if arg =~# '^-t$\|^--tool=\|^--tool-help$\|^--help$'
5591 elseif arg =~# '^-y$\|^--no-prompt$'
5593 call remove(argv, i)
5595 elseif arg ==# '--prompt'
5597 call remove(argv, i)
5599 elseif arg =~# '^--\%(no-\)\=\(symlinks\|trust-exit-code\|gui\)$'
5600 call remove(argv, i)
5607 call fugitive#Autowrite()
5608 let a:state.mode = 'init'
5609 let a:state.from = ''
5611 let exec = s:UserCommandList({'git': a:options.git, 'git_dir': a:options.git_dir}) + ['-c', 'diff.context=0']
5612 let exec += a:options.flags + ['--no-pager', 'diff', '--no-ext-diff', '--no-color', '--no-prefix'] + argv
5614 let title = ':Git ' . s:fnameescape(a:options.flags + [a:options.subcommand] + a:options.subcommand_args)
5615 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)
5619 let tabnr = tabpagenr() + 1
5620 for line in s:SystemList(exec)[0]
5621 for item in s:ToolParse(a:state, line)
5622 if len(get(item, 'filename', '')) && item.filename != filename
5623 call add(cmd, 'tabedit ' . s:fnameescape(item.filename))
5624 for i in reverse(range(len(get(get(item, 'context', {}), 'diff', []))))
5625 call add(cmd, (i ? 'rightbelow' : 'leftabove') . ' vertical Gdiffsplit! ' . s:fnameescape(item.context.diff[i].filename))
5627 call add(cmd, 'wincmd =')
5628 let filename = item.filename
5632 return join(cmd, '|') . (empty(cmd) ? '' : '|' . tabnr . 'tabnext')
5636 function! s:MergetoolSubcommand(line1, line2, range, bang, mods, options) abort
5637 let dir = a:options.git_dir
5641 let cmd = ['diff', '--diff-filter=U']
5642 let state = {'name_only': 0}
5643 let state.diff = [{'prefix': ':2:', 'module': ':2:'}, {'prefix': ':3:', 'module': ':3:'}, {'prefix': ':(top)'}]
5644 call map(state.diff, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5645 return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, ['--diff-filter=U'] + a:options.subcommand_args, state)
5648 function! s:DifftoolSubcommand(line1, line2, range, bang, mods, options) abort
5649 let dir = s:Dir(a:options)
5652 let argv = copy(a:options.subcommand_args)
5657 let state = {'name_only': 0}
5658 let merge_base_against = {}
5659 let dash = (index(argv, '--') > i ? ['--'] : [])
5661 let match = matchlist(argv[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
5662 if len(match) && len(match[2])
5663 call insert(argv, match[1])
5664 let argv[i+1] = '-' . match[2]
5668 if arg ==# '--cached'
5672 elseif arg ==# '--name-only'
5673 let state.name_only = 1
5674 let argv[0] = '--name-status'
5677 elseif arg !~# '^-\|^\.\.\=\%(/\|$\)'
5678 let parsed = s:LinesError(['rev-parse', '--revs-only', substitute(arg, ':.*', '', '')] + dash)[0]
5679 call map(parsed, '{"uninteresting": v:val =~# "^\\^", "prefix": substitute(v:val, "^\\^", "", "") . ":"}')
5680 let merge_base_against = {}
5681 if arg =~# '\.\.\.' && len(parsed) > 2
5682 let display = map(split(arg, '\.\.\.', 1), 'empty(v:val) ? "@" : v:val')
5683 if len(display) == 2
5684 let parsed[0].module = display[1] . ':'
5685 let parsed[1].module = display[0] . ':'
5687 let parsed[2].module = arg . ':'
5689 let merge_base_against = parsed[0]
5690 let parsed = [parsed[2]]
5692 elseif arg =~# '\.\.' && len(parsed) == 2
5693 let display = map(split(arg, '\.\.', 1), 'empty(v:val) ? "@" : v:val')
5694 if len(display) == 2
5695 let parsed[0].module = display[0] . ':'
5696 let parsed[1].module = display[1] . ':'
5698 elseif len(parsed) == 1
5699 let parsed[0].module = arg . ':'
5701 call extend(commits, parsed)
5705 if len(merge_base_against)
5706 call add(commits, merge_base_against)
5708 let commits = filter(copy(commits), 'v:val.uninteresting') + filter(commits, '!v:val.uninteresting')
5711 call add(commits, {'prefix': '@:', 'module': '@:'})
5713 call add(commits, {'prefix': ':0:', 'module': ':0:'})
5714 elseif len(commits) < 2
5715 call add(commits, {'prefix': ':(top)'})
5717 call insert(commits, {'prefix': ':0:', 'module': ':0:'})
5721 let commits = [commits[-1]] + repeat([commits[0]], len(commits) - 1)
5722 call reverse(commits)
5725 call add(commits, remove(commits, 0))
5727 call map(commits, 'extend(v:val, {"filename": fugitive#Find(v:val.prefix, dir)})')
5728 let state.diff = commits
5729 return s:ToolStream(a:line1, a:line2, a:range, a:bang, a:mods, a:options, argv, state)
5732 " Section: :Ggrep, :Glog
5734 if !exists('g:fugitive_summary_format')
5735 let g:fugitive_summary_format = '%s'
5738 function! fugitive#GrepComplete(A, L, P) abort
5739 return s:CompleteSub('grep', a:A, a:L, a:P)
5742 function! fugitive#LogComplete(A, L, P) abort
5743 return s:CompleteSub('log', a:A, a:L, a:P)
5746 function! s:GrepParseLine(options, quiet, dir, line) abort
5750 let entry = {'valid': 1}
5751 let match = matchlist(a:line, '^\(.\{-\}\):\([1-9]\d*\):\([1-9]\d*:\)\=\(.*\)$')
5752 if a:line =~# '^git: \|^usage: \|^error: \|^fatal: \|^BUG: '
5753 return {'text': a:line}
5755 let entry.module = match[1]
5756 let entry.lnum = +match[2]
5757 let entry.col = +match[3]
5758 let entry.text = match[4]
5760 let entry.module = matchstr(a:line, '\CBinary file \zs.*\ze matches$')
5761 if len(entry.module)
5762 let entry.text = 'Binary file'
5766 if empty(entry.module) && !a:options.line_number
5767 let match = matchlist(a:line, '^\(.\{-\}\):\(.*\)$')
5769 let entry.module = match[1]
5770 let entry.pattern = '\M^' . escape(match[2], '\.^$/') . '$'
5773 if empty(entry.module) && a:options.name_count && a:line =~# ':\d\+$'
5774 let entry.text = matchstr(a:line, '\d\+$')
5775 let entry.module = strpart(a:line, 0, len(a:line) - len(entry.text) - 1)
5777 if empty(entry.module) && a:options.name_only
5778 let entry.module = a:line
5780 if empty(entry.module)
5781 return {'text': a:line}
5783 if entry.module !~# ':'
5784 let entry.filename = s:PathJoin(a:options.prefix, entry.module)
5786 let entry.filename = fugitive#Find(matchstr(entry.module, '^[^:]*:') .
5787 \ substitute(matchstr(entry.module, ':\zs.*'), '/\=:', '/', 'g'), a:dir)
5792 let s:grep_combine_flags = '[aiIrhHEGPFnlLzocpWq]\{-\}'
5793 function! s:GrepOptions(args, dir) abort
5794 let options = {'name_only': 0, 'name_count': 0, 'line_number': 0}
5795 let tree = s:Tree(a:dir)
5796 let prefix = empty(tree) ? fugitive#Find(':0:', a:dir) :
5797 \ s:VimSlash(tree . '/')
5798 let options.prefix = prefix
5803 if arg =~# '^\%(-' . s:grep_combine_flags . 'c\|--count\)$'
5804 let options.name_count = 1
5806 if arg =~# '^\%(-' . s:grep_combine_flags . 'n\|--line-number\)$'
5807 let options.line_number = 1
5808 elseif arg =~# '^\%(--no-line-number\)$'
5809 let options.line_number = 0
5811 if arg =~# '^\%(-' . s:grep_combine_flags . '[lL]\|--files-with-matches\|--name-only\|--files-without-match\)$'
5812 let options.name_only = 1
5814 if arg ==# '--cached'
5815 let options.prefix = fugitive#Find(':0:', a:dir)
5816 elseif arg ==# '--no-cached'
5817 let options.prefix = prefix
5823 function! s:GrepCfile(result) abort
5824 let options = s:GrepOptions(a:result.args, a:result)
5825 let entry = s:GrepParseLine(options, 1, a:result, getline('.'))
5826 if get(entry, 'col')
5827 return [entry.filename, entry.lnum, "norm!" . entry.col . "|"]
5828 elseif has_key(entry, 'lnum')
5829 return [entry.filename, entry.lnum]
5830 elseif has_key(entry, 'pattern')
5831 return [entry.filename, '', 'silent /' . entry.pattern]
5832 elseif has_key(entry, 'filename')
5833 return [entry.filename]
5839 function! s:GrepSubcommand(line1, line2, range, bang, mods, options) abort
5840 let args = copy(a:options.subcommand_args)
5844 while i < len(args) && args[i] !=# '--'
5845 let partition = matchstr(args[i], '^-' . s:grep_combine_flags . '\ze[qzO]')
5846 if len(partition) > 1
5847 call insert(args, '-' . strpart(args[i], len(partition)), i+1)
5848 let args[i] = partition
5849 elseif args[i] =~# '^\%(-' . s:grep_combine_flags . '[eABC]\|--max-depth\|--context\|--after-context\|--before-context\|--threads\)$'
5851 elseif args[i] =~# '^\%(-O\|--open-files-in-pager\)$'
5853 call remove(args, i)
5855 elseif args[i] =~# '^\%(-O\|--open-files-in-pager=\)'
5857 elseif args[i] =~# '^-[qz].'
5858 let args[i] = '-' . args[i][2:-1]
5860 elseif args[i] =~# '^\%(-[qz]\|--quiet\)$'
5862 call remove(args, i)
5864 elseif args[i] =~# '^--no-quiet$'
5866 elseif args[i] =~# '^\%(--heading\)$'
5867 call remove(args, i)
5872 if handle < 0 ? !quiet : !handle
5875 call fugitive#Autowrite()
5876 let listnr = get(a:options, 'curwin') && a:line2 < 0 ? 0 : a:line2
5877 if s:HasOpt(args, '--no-line-number')
5880 let lc = fugitive#GitVersion(2, 19) ? ['-n', '--column'] : ['-n']
5882 let cmd = ['grep', '--no-color', '--full-name'] + lc
5883 let dir = s:Dir(a:options)
5884 let options = s:GrepOptions(lc + args, dir)
5886 exe listnr 'wincmd w'
5890 let title = (listnr < 0 ? ':Ggrep ' : ':Glgrep ') . s:fnameescape(args)
5891 call s:QuickfixCreate(listnr, {'title': title})
5892 let tempfile = tempname()
5894 \ 'git': a:options.git,
5895 \ 'flags': a:options.flags,
5896 \ 'args': cmd + args,
5897 \ 'git_dir': s:GitDir(a:options),
5898 \ 'cwd': s:UserCommandCwd(a:options),
5899 \ 'filetype': 'git',
5900 \ 'mods': s:Mods(a:mods),
5901 \ 'file': s:Resolve(tempfile)}
5902 let event = listnr < 0 ? 'grep-fugitive' : 'lgrep-fugitive'
5903 exe s:DoAutocmd('QuickFixCmdPre ' . event)
5912 let list = s:SystemList(s:UserCommandList(a:options) + cmd + args)[0]
5913 call writefile(list + [''], tempfile, 'b')
5914 call s:RunSave(state)
5915 call map(list, 's:GrepParseLine(options, ' . quiet . ', dir, v:val)')
5916 call s:QuickfixSet(listnr, list, 'a')
5917 let press_enter_shortfall = &cmdheight - len(list)
5918 if press_enter_shortfall > 0 && !quiet
5919 echo repeat("\n", press_enter_shortfall - 1)
5926 call s:RunFinished(state)
5927 exe s:DoAutocmd('QuickFixCmdPost ' . event)
5929 let bufnr = bufnr('')
5930 exe s:QuickfixOpen(listnr, a:mods)
5931 if bufnr != bufnr('') && !a:bang
5935 if !a:bang && !empty(list)
5936 return 'silent ' . (listnr < 0 ? 'c' : 'l').'first'
5942 function! fugitive#GrepCommand(line1, line2, range, bang, mods, arg) abort
5943 return fugitive#Command(a:line1, a:line2, a:range, a:bang, a:mods,
5944 \ "grep -O " . a:arg)
5947 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}'
5949 function! s:LogFlushQueue(state, dir) abort
5950 let queue = remove(a:state, 'queue')
5951 if a:state.child_found && get(a:state, 'ignore_commit')
5952 call remove(queue, 0)
5953 elseif len(queue) && len(a:state.target) && len(get(a:state, 'parents', []))
5954 let from = substitute(a:state.target, '^/', ':', '')
5956 let queue[0].context.diff = map(copy(a:state.parents), s:log_diff_context)
5958 if len(queue) && queue[-1] ==# {'text': ''}
5959 call remove(queue, -1)
5964 function! s:LogParse(state, dir, prefix, line) abort
5965 if a:state.mode ==# 'hunk' && a:line =~# '^[-+ ]'
5968 let list = matchlist(a:line, '^\%(fugitive \(.\{-\}\)\t\|commit \|From \)\=\(\x\{40,\}\)\%( \(.*\)\)\=$')
5970 let queue = s:LogFlushQueue(a:state, a:dir)
5971 let a:state.mode = 'commit'
5972 let a:state.base = a:prefix . list[2]
5974 let [a:state.base_module; a:state.parents] = split(list[1], ' ')
5976 let a:state.base_module = list[2]
5977 let a:state.parents = []
5979 let a:state.message = list[3]
5980 let a:state.from = ''
5983 let a:state.queue = [{
5985 \ 'context': context,
5986 \ 'filename': s:PathJoin(a:state.base, a:state.target),
5987 \ 'module': a:state.base_module . substitute(a:state.target, '^/', ':', ''),
5988 \ 'text': a:state.message}]
5989 let a:state.child_found = 0
5991 elseif type(a:line) == type(0)
5992 return s:LogFlushQueue(a:state, a:dir)
5993 elseif a:line =~# '^diff'
5994 let a:state.mode = 'diffhead'
5995 let a:state.from = ''
5997 elseif a:state.mode ==# 'diffhead' && a:line =~# '^--- \w/'
5998 let a:state.from = a:line[6:-1]
5999 let a:state.to = a:state.from
6000 elseif a:state.mode ==# 'diffhead' && a:line =~# '^+++ \w/'
6001 let a:state.to = a:line[6:-1]
6002 if empty(get(a:state, 'from', ''))
6003 let a:state.from = a:state.to
6005 elseif a:line =~# '^@@[^@]*+\d' && len(get(a:state, 'to', '')) && has_key(a:state, 'base')
6006 let a:state.mode = 'hunk'
6007 if empty(a:state.target) || a:state.target ==# '/' . a:state.to
6008 if !a:state.child_found && len(a:state.queue) && a:state.queue[-1] ==# {'text': ''}
6009 call remove(a:state.queue, -1)
6011 let a:state.child_found = 1
6012 let offsets = map(split(matchstr(a:line, '^@\+ \zs[-+0-9, ]\+\ze @'), ' '), '+matchstr(v:val, "\\d\\+")')
6014 if len(a:state.parents)
6015 let from = ":" . a:state.from
6016 let context.diff = map(copy(a:state.parents), s:log_diff_context)
6018 call add(a:state.queue, {
6020 \ 'context': context,
6021 \ 'filename': s:VimSlash(a:state.base . '/' . a:state.to),
6022 \ 'module': a:state.base_module . ':' . a:state.to,
6023 \ 'lnum': offsets[-1],
6024 \ 'text': a:state.message . matchstr(a:line, ' @@\+ .\+')})
6026 elseif a:state.follow &&
6027 \ a:line =~# '^ \%(mode change \d\|\%(create\|delete\) mode \d\|\%(rename\|copy\|rewrite\) .* (\d\+%)$\)'
6028 let rename = matchstr(a:line, '^ \%(copy\|rename\) \zs.* => .*\ze (\d\+%)$')
6030 let rename = rename =~# '{.* => .*}' ? rename : '{' . rename . '}'
6031 if a:state.target ==# simplify('/' . substitute(rename, '{.* => \(.*\)}', '\1', ''))
6032 let a:state.target = simplify('/' . substitute(rename, '{\(.*\) => .*}', '\1', ''))
6035 if !get(a:state, 'ignore_summary')
6036 call add(a:state.queue, {'text': a:line})
6038 elseif a:state.mode ==# 'commit' || a:state.mode ==# 'init'
6039 call add(a:state.queue, {'text': a:line})
6044 function! fugitive#LogCommand(line1, count, range, bang, mods, args, type) abort
6045 exe s:VersionCheck()
6048 let listnr = a:type =~# '^l' ? 0 : -1
6049 let [args, after] = s:SplitExpandChain('log ' . a:args, s:Tree(dir))
6050 call remove(args, 0)
6051 let split = index(args, '--')
6053 let paths = args[split : -1]
6054 let args = args[0 : split - 1]
6061 if a:line1 == 0 && a:count
6062 let path = fugitive#Path(bufname(a:count), '/', dir)
6063 let titlepre = ':0,' . a:count
6065 let path = fugitive#Path(@%, '/', dir)
6066 let titlepre = a:count == 0 ? ':0,' . bufnr('') : ':'
6073 let extra_paths = []
6074 let state = {'mode': 'init', 'child_found': 0, 'queue': [], 'follow': 0}
6075 if path =~# '^/\.git\%(/\|$\)\|^$'
6078 let range = "0," . (a:count ? a:count : bufnr(''))
6079 let extra_paths = ['.' . path]
6080 if (empty(paths) || paths ==# ['--']) && !s:HasOpt(args, '--no-follow')
6081 let state.follow = 1
6082 if !s:HasOpt(args, '--follow')
6083 call insert(extra_args, '--follow')
6085 if !s:HasOpt(args, '--summary')
6086 call insert(extra_args, '--summary')
6087 let state.ignore_summary = 1
6090 let state.ignore_commit = 1
6092 if !s:HasOpt(args, '--merges', '--no-merges')
6093 call insert(extra_args, '--no-merges')
6095 call add(args, '-L' . a:line1 . ',' . a:count . ':' . path[1:-1])
6096 let state.ignore_commit = 1
6098 if len(path) && empty(filter(copy(args), 'v:val =~# "^[^-]"'))
6099 let owner = s:Owner(@%, dir)
6101 call add(args, owner . (owner =~# '^\x\{40,}' ? '' : '^{}'))
6104 if empty(extra_paths)
6107 if s:HasOpt(args, '-g', '--walk-reflogs')
6108 let format = "%gd %P\t%H %gs"
6110 let format = "%h %P\t%H " . g:fugitive_summary_format
6112 let cmd = ['--no-pager']
6113 call extend(cmd, ['-c', 'diff.context=0', '-c', 'diff.noprefix=false', 'log'] +
6114 \ ['--no-color', '--no-ext-diff', '--pretty=format:fugitive ' . format] +
6115 \ args + extra_args + paths + extra_paths)
6116 let state.target = path
6117 let title = titlepre . (listnr < 0 ? 'Gclog ' : 'Gllog ') . s:fnameescape(args + paths)
6118 return s:QuickfixStream(listnr, 'log', title, s:UserCommandList(dir) + cmd, !a:bang, a:mods, s:function('s:LogParse'), state, dir, s:DirUrlPrefix(dir)) . after
6121 " Section: :Gedit, :Gpedit, :Gsplit, :Gvsplit, :Gtabedit, :Gread
6123 function! s:UsableWin(nr) abort
6124 return a:nr && !getwinvar(a:nr, '&previewwindow') && !getwinvar(a:nr, '&winfixwidth') &&
6125 \ !getwinvar(a:nr, '&winfixbuf') &&
6126 \ (empty(getwinvar(a:nr, 'fugitive_status')) || getbufvar(winbufnr(a:nr), 'fugitive_type') !=# 'index') &&
6127 \ index(['gitrebase', 'gitcommit'], getbufvar(winbufnr(a:nr), '&filetype')) < 0 &&
6128 \ index(['nofile','help','quickfix', 'terminal'], getbufvar(winbufnr(a:nr), '&buftype')) < 0
6131 function! s:ArgSplit(string) abort
6132 let string = a:string
6134 while string =~# '\S'
6135 let arg = matchstr(string, '^\s*\%(\\.\|\S\)\+')
6136 let string = strpart(string, len(arg))
6137 let arg = substitute(arg, '^\s\+', '', '')
6138 call add(args, substitute(arg, '\\\+[|" ]', '\=submatch(0)[len(submatch(0))/2 : -1]', 'g'))
6143 function! s:PlusEscape(string) abort
6144 return substitute(a:string, '\\*[|" ]', '\=repeat("\\", len(submatch(0))).submatch(0)', 'g')
6147 function! s:OpenParse(string, wants_cmd, wants_multiple) abort
6150 let args = s:ArgSplit(a:string)
6152 if args[0] =~# '^++'
6153 call add(opts, ' ' . s:PlusEscape(remove(args, 0)))
6154 elseif a:wants_cmd && args[0] ==# '+'
6155 call remove(args, 0)
6157 elseif a:wants_cmd && args[0] =~# '^+'
6158 call add(cmds, remove(args, 0)[1:-1])
6163 if !a:wants_multiple && empty(args)
6167 let wants_cmd = a:wants_cmd
6170 let [url, lnum] = s:OpenExpand(dir, arg, wants_cmd)
6172 call insert(cmds, lnum)
6178 let pre = join(opts, '')
6180 let pre .= ' +' . s:PlusEscape(join(map(cmds, '"exe ".string(v:val)'), '|'))
6182 let pre .= ' +' . s:PlusEscape(cmds[0])
6184 return [a:wants_multiple ? urls : urls[0], pre]
6187 function! s:OpenExpand(dir, file, wants_cmd) abort
6189 let result = fugitive#Result()
6190 if has_key(result, 'file')
6191 let efile = result.file
6193 throw 'fugitive: no previous command output'
6196 let efile = s:Expand(a:file)
6198 if efile =~# '^https\=://'
6199 let [url, lnum] = s:ResolveUrl(efile, a:dir)
6200 return [url, a:wants_cmd ? lnum : 0]
6202 let url = s:Generate(efile, a:dir)
6203 if a:wants_cmd && a:file[0] ==# '>' && efile[0] !=# '>' && get(b:, 'fugitive_type', '') isnot# 'tree' && &filetype !=# 'netrw'
6204 let line = line('.')
6205 if s:Slash(expand('%:p')) !=# s:Slash(url)
6206 let diffcmd = 'diff'
6207 let from = s:DirRev(@%)[1]
6208 let to = s:DirRev(url)[1]
6209 if empty(from) && empty(to)
6210 let diffcmd = 'diff-files'
6211 let args = ['--', expand('%:p'), url]
6213 let args = [from, '--', url]
6215 let args = [to, '--', expand('%:p')]
6218 let args = [from, to]
6220 let [res, exec_error] = s:LinesError([a:dir, diffcmd, '-U0'] + args)
6222 call filter(res, 'v:val =~# "^@@ "')
6223 call map(res, 'substitute(v:val, ''[-+]\d\+\zs '', ",1 ", "g")')
6224 call map(res, 'matchlist(v:val, ''^@@ -\(\d\+\),\(\d\+\) +\(\d\+\),\(\d\+\) @@'')[1:4]')
6225 if exists('reverse')
6226 call map(res, 'v:val[2:3] + v:val[0:1]')
6228 call filter(res, 'v:val[0] < '.line('.'))
6229 let hunk = get(res, -1, [0,0,0,0])
6230 if hunk[0] + hunk[1] > line('.')
6231 let line = hunk[2] + max([1 - hunk[3], 0])
6233 let line = hunk[2] + max([hunk[3], 1]) + line('.') - hunk[0] - max([hunk[1], 1])
6242 function! fugitive#DiffClose() abort
6243 let mywinnr = winnr()
6244 for winnr in [winnr('#')] + range(winnr('$'),1,-1)
6245 if winnr != mywinnr && getwinvar(winnr,'&diff')
6246 execute winnr.'wincmd w'
6256 function! s:BlurStatus() abort
6257 if (&previewwindow || getwinvar(winnr(), '&winfixbuf') is# 1 || exists('w:fugitive_status')) && get(b:, 'fugitive_type', '') ==# 'index'
6258 let winnrs = filter([winnr('#')] + range(1, winnr('$')), 's:UsableWin(v:val)')
6260 exe winnrs[0].'wincmd w'
6262 belowright new +setl\ bufhidden=delete
6265 call fugitive#DiffClose()
6270 let s:bang_edits = {'split': 'Git', 'vsplit': 'vertical Git', 'tabedit': 'tab Git', 'pedit': 'Git!'}
6271 function! fugitive#Open(cmd, bang, mods, arg, ...) abort
6272 exe s:VersionCheck()
6274 return 'echoerr ' . string(':G' . a:cmd . '! for temp buffer output has been replaced by :' . get(s:bang_edits, a:cmd, 'Git') . ' --paginate')
6278 let [file, pre] = s:OpenParse(a:arg, 1, 0)
6280 return 'echoerr ' . string(v:exception)
6282 let mods = s:Mods(a:mods)
6286 return mods . a:cmd . pre . ' ' . s:fnameescape(file)
6289 function! fugitive#DropCommand(line1, count, range, bang, mods, arg, ...) abort
6290 exe s:VersionCheck()
6292 let mods = s:Mods(a:mods)
6294 let [files, pre] = s:OpenParse(a:arg, 1, 1)
6296 return 'echoerr ' . string(v:exception)
6302 return mods . 'drop' . ' ' . s:fnameescape(files) . substitute(pre, '^ *+', '|', '')
6305 function! s:ReadPrepare(line1, count, range, mods) abort
6306 let mods = s:Mods(a:mods)
6309 let delete = 'silent 1,' . line('$') . 'delete_|'
6310 let after = line('$')
6312 let delete = 'silent ' . a:line1 . ',' . a:count . 'delete_|'
6317 let pre = after . 'foldopen!|'
6321 return [pre . 'keepalt ' . mods . after . 'read', '|' . delete . 'diffupdate' . (a:count < 0 ? '|' . line('.') : '')]
6324 function! fugitive#ReadCommand(line1, count, range, bang, mods, arg, ...) abort
6325 exe s:VersionCheck()
6326 let [read, post] = s:ReadPrepare(a:line1, a:count, a:range, a:mods)
6328 let [file, pre] = s:OpenParse(a:arg, 0, 0)
6330 return 'echoerr ' . string(v:exception)
6332 if file =~# '^fugitive:' && a:count is# 0
6333 return 'exe ' .string('keepalt ' . s:Mods(a:mods) . fugitive#FileReadCmd(file, 0, pre)) . '|diffupdate'
6335 return read . ' ' . pre . ' ' . s:fnameescape(file) . post
6338 function! fugitive#EditComplete(A, L, P) abort
6340 return map(s:FilterEscape(s:CompleteHeads(s:Dir()), a:A[1:-1]), "'>' . v:val")
6342 return fugitive#CompleteObject(a:A, a:L, a:P)
6346 function! fugitive#ReadComplete(A, L, P) abort
6347 return fugitive#EditComplete(a:A, a:L, a:P)
6350 " Section: :Gwrite, :Gwq
6352 function! fugitive#WriteCommand(line1, line2, range, bang, mods, arg, ...) abort
6353 exe s:VersionCheck()
6354 if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG')) && empty(a:arg)
6355 return (empty($GIT_INDEX_FILE) ? 'write|bdelete' : 'wq') . (a:bang ? '!' : '')
6356 elseif get(b:, 'fugitive_type', '') ==# 'index' && empty(a:arg)
6358 elseif &buftype ==# 'nowrite' && getline(4) =~# '^[+-]\{3\} '
6359 return 'echoerr ' . string('fugitive: :Gwrite from :Git diff has been removed in favor of :Git add --edit')
6361 let mytab = tabpagenr()
6362 let mybufnr = bufnr('')
6363 let args = s:ArgSplit(a:arg)
6365 if get(args, 0) =~# '^+'
6366 let after = '|' . remove(args, 0)[1:-1]
6369 let file = len(args) ? s:Generate(s:Expand(join(args, ' '))) : fugitive#Real(@%)
6371 return 'echoerr ' . string(v:exception)
6374 return 'echoerr '.string('fugitive: cannot determine file path')
6376 if file =~# '^fugitive:'
6377 return 'write' . (a:bang ? '! ' : ' ') . s:fnameescape(file)
6380 let always_permitted = s:cpath(fugitive#Real(@%), file) && empty(s:DirCommitFile(@%)[1])
6381 if !always_permitted && !a:bang && (len(s:TreeChomp('diff', '--name-status', 'HEAD', '--', file)) || len(s:TreeChomp('ls-files', '--others', '--', file)))
6382 let v:errmsg = 'fugitive: file has uncommitted changes (use ! to override)'
6383 return 'echoerr v:errmsg'
6386 for nr in range(1,bufnr('$'))
6387 if fnamemodify(bufname(nr),':p') ==# file
6392 if treebufnr > 0 && treebufnr != bufnr('')
6393 let temp = tempname()
6394 silent execute 'keepalt %write '.temp
6395 for tab in [mytab] + range(1,tabpagenr('$'))
6396 for winnr in range(1,tabpagewinnr(tab,'$'))
6397 if tabpagebuflist(tab)[winnr-1] == treebufnr
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 '.temp
6407 silent execute '1,'.last.'delete_'
6413 if exists('restorewinnr')
6416 execute 'tabnext '.mytab
6423 call writefile(readfile(temp,'b'),file,'b')
6426 execute 'write! '.s:fnameescape(file)
6429 let message = s:ChompStderr(['add'] + (a:bang ? ['--force'] : []) + ['--', file])
6431 let v:errmsg = 'fugitive: '.message
6432 return 'echoerr v:errmsg'
6434 if s:cpath(fugitive#Real(@%), file) && s:DirCommitFile(@%)[1] =~# '^\d$'
6438 let one = fugitive#Find(':1:'.file)
6439 let two = fugitive#Find(':2:'.file)
6440 let three = fugitive#Find(':3:'.file)
6441 for nr in range(1,bufnr('$'))
6442 let name = fnamemodify(bufname(nr), ':p')
6443 if bufloaded(nr) && !getbufvar(nr,'&modified') && (name ==# one || name ==# two || name ==# three)
6444 execute nr.'bdelete'
6449 let zero = fugitive#Find(':0:'.file)
6450 exe s:DoAutocmd('BufWritePost ' . s:fnameescape(zero))
6451 for tab in range(1,tabpagenr('$'))
6452 for winnr in range(1,tabpagewinnr(tab,'$'))
6453 let bufnr = tabpagebuflist(tab)[winnr-1]
6454 let bufname = fnamemodify(bufname(bufnr), ':p')
6455 if bufname ==# zero && bufnr != mybufnr
6456 execute 'tabnext '.tab
6458 execute winnr.'wincmd w'
6459 let restorewinnr = 1
6462 let lnum = line('.')
6463 let last = line('$')
6464 silent execute '$read '.s:fnameescape(file)
6465 silent execute '1,'.last.'delete_'
6470 if exists('restorewinnr')
6473 execute 'tabnext '.mytab
6479 call fugitive#DidChange()
6480 return 'checktime' . after
6483 function! fugitive#WqCommand(...) abort
6484 let bang = a:4 ? '!' : ''
6485 if s:cpath(expand('%:p'), fugitive#Find('.git/COMMIT_EDITMSG'))
6488 let result = call('fugitive#WriteCommand', a:000)
6489 if result =~# '^\%(write\|wq\|echoerr\)'
6490 return s:sub(result,'^write','wq')
6492 return result.'|quit'.bang
6496 " Section: :Git push, :Git fetch
6498 function! s:CompletePush(A, L, P, ...) abort
6499 let dir = a:0 ? a:1 : s:Dir()
6500 let remote = matchstr(a:L, '\u\w*[! ] *.\{-\}\s\@<=\zs[^-[:space:]]\S*\ze ')
6502 let matches = s:LinesError([dir, 'remote'])[0]
6504 let lead = matchstr(a:A, '^[^:]*:')
6505 let matches = s:LinesError([dir, 'ls-remote', remote])[0]
6506 call filter(matches, 'v:val =~# "\t" && v:val !~# "{"')
6507 call map(matches, 'lead . s:sub(v:val, "^.*\t", "")')
6509 let matches = s:CompleteHeads(dir)
6510 if a:A =~# '^[\''"]\=+'
6511 call map(matches, '"+" . v:val')
6514 return s:FilterEscape(matches, a:A)
6517 function! fugitive#PushComplete(A, L, P, ...) abort
6518 return s:CompleteSub('push', a:A, a:L, a:P, function('s:CompletePush'), a:000)
6521 function! fugitive#FetchComplete(A, L, P, ...) abort
6522 return s:CompleteSub('fetch', a:A, a:L, a:P, function('s:CompleteRemote'), a:000)
6525 function! s:PushSubcommand(...) abort
6526 return {'no_more': 1}
6529 function! s:FetchSubcommand(...) abort
6530 return {'no_more': 1}
6535 augroup fugitive_diff
6537 autocmd BufWinLeave * nested
6538 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 2 |
6539 \ call s:diffoff_all(s:Dir(+expand('<abuf>'))) |
6541 autocmd BufWinEnter * nested
6542 \ if s:can_diffoff(+expand('<abuf>')) && s:diff_window_count() == 1 |
6543 \ call s:diffoff() |
6547 function! s:can_diffoff(buf) abort
6548 return getwinvar(bufwinnr(a:buf), '&diff') &&
6549 \ !empty(getwinvar(bufwinnr(a:buf), 'fugitive_diff_restore'))
6552 function! fugitive#CanDiffoff(buf) abort
6553 return s:can_diffoff(bufnr(a:buf))
6556 function! s:DiffModifier(count, default) abort
6557 let fdc = matchstr(&diffopt, 'foldcolumn:\zs\d\+')
6558 if &diffopt =~# 'horizontal' && &diffopt !~# 'vertical'
6560 elseif &diffopt =~# 'vertical'
6562 elseif !get(g:, 'fugitive_diffsplit_directional_fit', a:default)
6564 elseif winwidth(0) <= a:count * ((&tw ? &tw : 80) + (empty(fdc) ? 2 : fdc))
6571 function! s:diff_window_count() abort
6573 for nr in range(1,winnr('$'))
6574 let c += getwinvar(nr,'&diff')
6579 function! s:diffthis() abort
6581 let w:fugitive_diff_restore = 1
6586 function! s:diffoff() abort
6587 unlet! w:fugitive_diff_restore
6591 function! s:diffoff_all(dir) abort
6592 let curwin = winnr()
6593 for nr in range(1,winnr('$'))
6594 if getwinvar(nr, '&diff') && !empty(getwinvar(nr, 'fugitive_diff_restore'))
6595 call setwinvar(nr, 'fugitive_diff_restore', '')
6598 if curwin != winnr()
6599 execute curwin.'wincmd w'
6604 function! s:IsConflicted() abort
6605 return len(@%) && !empty(s:ChompDefault('', ['ls-files', '--unmerged', '--', expand('%:p')]))
6608 function! fugitive#Diffsplit(autodir, keepfocus, mods, arg, ...) abort
6609 exe s:VersionCheck()
6610 let args = s:ArgSplit(a:arg)
6612 let autodir = a:autodir
6613 while get(args, 0, '') =~# '^++'
6614 if args[0] =~? '^++novertical$'
6617 return 'echoerr ' . string('fugitive: unknown option ' . args[0])
6619 call remove(args, 0)
6621 if get(args, 0) =~# '^+'
6622 let post = remove(args, 0)[1:-1]
6624 if exists(':DiffGitCached') && empty(args)
6625 return s:Mods(a:mods) . 'DiffGitCached' . (len(post) ? '|' . post : '')
6627 let commit = s:DirCommitFile(@%)[1]
6628 if a:mods =~# '\<\d*tab\>'
6629 let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
6630 let pre = matchstr(a:mods, '\<\d*tab\>') . 'edit'
6632 let mods = 'keepalt ' . a:mods
6635 let back = exists('*win_getid') ? 'call win_gotoid(' . win_getid() . ')' : 'wincmd p'
6636 if (empty(args) || args[0] =~# '^>\=:$') && a:keepfocus
6638 if commit =~# '^1\=$' && s:IsConflicted()
6639 let parents = [s:Relative(':2:'), s:Relative(':3:')]
6640 elseif empty(commit)
6641 let parents = [s:Relative(':0:')]
6642 elseif commit =~# '^\d\=$'
6643 let parents = [s:Relative('@:')]
6644 elseif commit =~# '^\x\x\+$'
6645 let parents = s:LinesError(['rev-parse', commit . '^@'])[0]
6646 call map(parents, 's:Relative(v:val . ":")')
6650 if exists('parents') && len(parents) > 1
6652 let mods = (autodir ? s:DiffModifier(len(parents) + 1, empty(args) || args[0] =~# '^>') : '') . s:Mods(mods, 'leftabove')
6654 if len(parents) > 1 && !&equalalways
6658 execute mods 'split' s:fnameescape(fugitive#Find(parents[0]))
6659 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6663 call s:Map('n', 'd2o', ':diffget '.nr2.'<Bar>diffupdate<CR>', '<silent>')
6664 let mods = substitute(mods, '\Cleftabove\|rightbelow\|aboveleft\|belowright', '\=submatch(0) =~# "f" ? "rightbelow" : "leftabove"', '')
6665 for i in range(len(parents)-1, 1, -1)
6666 execute mods 'split' s:fnameescape(fugitive#Find(parents[i]))
6667 call s:Map('n', 'dp', ':diffput '.nr.'<Bar>diffupdate<CR>', '<silent>')
6671 call s:Map('n', 'd' . (i + 2) . 'o', ':diffget '.nrx.'<Bar>diffupdate<CR>', '<silent>')
6676 let arg = join(args, ' ')
6681 let file = s:Relative()
6684 let file = len(commit) ? s:Relative() : s:Relative(s:IsConflicted() ? ':1:' : ':0:')
6685 elseif arg =~# '^:\d$'
6687 let file = s:Relative(arg . ':')
6688 elseif arg =~# '^[~^]\d*$'
6689 return 'echoerr ' . string('fugitive: change ' . arg . ' to !' . arg . ' to diff against ancestor')
6692 let file = arg =~# '^:/.' ? fugitive#RevParse(arg) . s:Relative(':') : s:Expand(arg)
6694 return 'echoerr ' . string(v:exception)
6697 if a:keepfocus || arg =~# '^>'
6698 let mods = s:Mods(a:mods, 'leftabove')
6700 let mods = s:Mods(a:mods)
6702 elseif exists('parents')
6703 let file = get(parents, -1, s:Relative(repeat('0', 40). ':'))
6704 let mods = s:Mods(a:mods, 'leftabove')
6706 let file = s:Relative()
6707 let mods = s:Mods(a:mods, 'rightbelow')
6708 elseif s:IsConflicted()
6709 let file = s:Relative(':1:')
6710 let mods = s:Mods(a:mods, 'leftabove')
6711 if get(g:, 'fugitive_legacy_commands', 1)
6712 let post = 'echohl WarningMsg|echo "Use :Gdiffsplit! for 3 way diff"|echohl NONE|' . post
6716 let file = s:Relative(':0:')
6717 let mods = s:Mods(a:mods, 'leftabove')
6719 let spec = s:Generate(file)
6720 if spec =~# '^fugitive:' && empty(s:DirCommitFile(spec)[2])
6721 let spec = s:VimSlash(spec . s:Relative('/'))
6724 let w:fugitive_diff_restore = 1
6725 let mods = (autodir ? s:DiffModifier(2, empty(args) || args[0] =~# '^>') : '') . mods
6726 if &diffopt =~# 'vertical'
6727 let diffopt = &diffopt
6728 set diffopt-=vertical
6730 execute mods 'diffsplit' s:fnameescape(spec)
6731 let w:fugitive_diff_restore = 1
6733 if getwinvar('#', '&diff')
6740 return 'echoerr ' . string(v:exception)
6742 if exists('l:equalalways')
6743 let &g:equalalways = equalalways
6745 if exists('diffopt')
6746 let &diffopt = diffopt
6751 " Section: :GMove, :GRemove
6753 function! s:Move(force, rename, destination) abort
6754 exe s:VersionCheck()
6757 if s:DirCommitFile(@%)[1] !~# '^0\=$' || empty(@%)
6758 return 'echoerr ' . string('fugitive: mv not supported for this buffer')
6761 let default_root = expand('%:p:s?[\/]$??:h') . '/'
6763 let default_root = s:Tree(dir) . '/'
6765 if a:destination =~# '^:/:\='
6766 let destination = s:Tree(dir) . s:Expand(substitute(a:destination, '^:/:\=', '', ''))
6767 elseif a:destination =~# '^:(top)'
6768 let destination = s:Expand(matchstr(a:destination, ')\zs.*'))
6769 if destination !~# '^/\|^\a\+:'
6770 let destination = s:Tree(dir) . '/' . destination
6772 let destination = s:Tree(dir) .
6773 elseif a:destination =~# '^:(\%(top,literal\|literal,top\))'
6774 let destination = s:Tree(dir) . matchstr(a:destination, ')\zs.*')
6775 elseif a:destination =~# '^:(literal)\.\.\=\%(/\|$\)'
6776 let destination = simplify(getcwd() . '/' . matchstr(a:destination, ')\zs.*'))
6777 elseif a:destination =~# '^:(literal)'
6778 let destination = simplify(default_root . matchstr(a:destination, ')\zs.*'))
6780 let destination = s:Expand(a:destination)
6781 if destination =~# '^\.\.\=\%(/\|$\)'
6782 let destination = simplify(getcwd() . '/' . destination)
6783 elseif destination !~# '^\a\+:\|^/'
6784 let destination = default_root . destination
6787 let destination = s:Slash(destination)
6791 let exec = fugitive#Execute(['mv'] + (a:force ? ['-f'] : []) + ['--', expand('%:p'), destination], dir)
6792 if exec.exit_status && exec.stderr !=# ['']
6793 return 'echoerr ' .string('fugitive: '.s:JoinChomp(exec.stderr))
6795 if isdirectory(destination)
6796 let destination = fnamemodify(s:sub(destination,'/$','').'/'.expand('%:t'),':.')
6798 let reload = '|call fugitive#DidChange(' . string(exec) . ')'
6799 if empty(s:DirCommitFile(@%)[1])
6800 if isdirectory(destination)
6801 return 'keepalt edit '.s:fnameescape(destination) . reload
6803 return 'keepalt saveas! '.s:fnameescape(destination) . reload
6806 return 'file '.s:fnameescape(fugitive#Find(':0:'.destination, dir)) . reload
6810 function! fugitive#RenameComplete(A,L,P) abort
6811 if a:A =~# '^[.:]\=/'
6812 return fugitive#CompletePath(a:A)
6814 let pre = s:Slash(fnamemodify(expand('%:p:s?[\/]$??'), ':h')) . '/'
6815 return map(fugitive#CompletePath(pre.a:A), 'strpart(v:val, len(pre))')
6819 function! fugitive#MoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6820 return s:Move(a:bang, 0, a:arg)
6823 function! fugitive#RenameCommand(line1, line2, range, bang, mods, arg, ...) abort
6824 return s:Move(a:bang, 1, a:arg)
6827 function! s:Remove(after, force) abort
6828 exe s:VersionCheck()
6831 if len(@%) && s:DirCommitFile(@%)[1] ==# ''
6833 elseif s:DirCommitFile(@%)[1] ==# '0'
6834 let cmd = ['rm','--cached']
6836 return 'echoerr ' . string('fugitive: rm not supported for this buffer')
6839 let cmd += ['--force']
6841 let message = s:ChompStderr(cmd + ['--', expand('%:p')], dir)
6843 let v:errmsg = 'fugitive: '.s:sub(message,'error:.*\zs\n\(.*-f.*',' (add ! to force)')
6844 return 'echoerr '.string(v:errmsg)
6846 return a:after . (a:force ? '!' : ''). '|call fugitive#DidChange(' . string(dir) . ')'
6850 function! fugitive#RemoveCommand(line1, line2, range, bang, mods, arg, ...) abort
6851 return s:Remove('edit', a:bang)
6854 function! fugitive#UnlinkCommand(line1, line2, range, bang, mods, arg, ...) abort
6855 return s:Remove('edit', a:bang)
6858 function! fugitive#DeleteCommand(line1, line2, range, bang, mods, arg, ...) abort
6859 return s:Remove('bdelete', a:bang)
6862 " Section: :Git blame
6864 function! s:Keywordprg() abort
6865 let args = ' --git-dir=' . escape(FugitiveGitPath(s:GitDir()), "\\\"' ")
6866 if has('gui_running') && !has('win32')
6867 return s:GitShellCmd() . ' --no-pager' . args . ' log -1'
6869 return s:GitShellCmd() . args . ' show'
6873 function! s:linechars(pattern) abort
6874 let chars = strlen(s:gsub(matchstr(getline('.'), a:pattern), '.', '.'))
6875 if &conceallevel > 1
6876 for col in range(1, chars)
6877 let chars -= synconcealed(line('.'), col)[0]
6883 function! s:BlameBufnr(...) abort
6884 let state = s:TempState(a:0 ? a:1 : bufnr(''))
6885 if get(state, 'filetype', '') ==# 'fugitiveblame'
6886 return get(state, 'origin_bufnr', -1)
6892 function! s:BlameCommitFileLnum(...) abort
6893 let line = a:0 ? a:1 : getline('.')
6894 let state = a:0 > 1 ? a:2 : s:TempState()
6895 if get(state, 'filetype', '') !=# 'fugitiveblame'
6898 let commit = matchstr(line, '^\^\=[?*]*\zs\x\+')
6899 if commit =~# '^0\+$'
6901 elseif has_key(state, 'blame_reverse_end')
6902 let commit = get(s:LinesError([state.git_dir, 'rev-list', '--ancestry-path', '--reverse', commit . '..' . state.blame_reverse_end])[0], 0, '')
6904 let lnum = +matchstr(line, ' \zs\d\+\ze \%((\| *\d\+)\)')
6905 let path = matchstr(line, '^\^\=[?*]*\x* \+\%(\d\+ \+\d\+ \+\)\=\zs.\{-\}\ze\s*\d\+ \%((\| *\d\+)\)')
6906 if empty(path) && lnum
6907 let path = get(state, 'blame_file', '')
6909 return [commit, path, lnum]
6912 function! s:BlameLeave() abort
6913 let state = s:TempState()
6914 let bufwinnr = exists('*win_id2win') ? win_id2win(get(state, 'origin_winid')) : 0
6916 let bufwinnr = bufwinnr(get(state, 'origin_bufnr', -1))
6918 if get(state, 'filetype', '') ==# 'fugitiveblame' && bufwinnr > 0
6919 let bufnr = bufnr('')
6920 exe bufwinnr . 'wincmd w'
6921 return bufnr . 'bdelete'
6926 function! s:BlameQuit() abort
6927 let cmd = s:BlameLeave()
6930 elseif len(s:DirCommitFile(@%)[1])
6931 return cmd . '|Gedit'
6937 function! fugitive#BlameComplete(A, L, P) abort
6938 return s:CompleteSub('blame', a:A, a:L, a:P)
6941 function! s:BlameSubcommand(line1, count, range, bang, mods, options) abort
6942 let dir = s:Dir(a:options)
6944 let flags = copy(a:options.subcommand_args)
6950 if a:line1 > 0 && a:count > 0 && a:range != 1
6951 call extend(ranges, ['-L', a:line1 . ',' . a:count])
6953 while i < len(flags)
6954 let match = matchlist(flags[i], '^\(-[a-zABDFH-KN-RT-Z]\)\ze\(.*\)')
6955 if len(match) && len(match[2])
6956 call insert(flags, match[1])
6957 let flags[i+1] = '-' . match[2]
6961 if arg =~# '^-p$\|^--\%(help\|porcelain\|line-porcelain\|incremental\)$'
6963 elseif arg ==# '--contents' && i + 1 < len(flags)
6964 call extend(commits, remove(flags, i, i+1))
6966 elseif arg ==# '-L' && i + 1 < len(flags)
6967 call extend(ranges, remove(flags, i, i+1))
6969 elseif arg =~# '^--contents='
6970 call add(commits, remove(flags, i))
6972 elseif arg =~# '^-L.'
6973 call add(ranges, remove(flags, i))
6975 elseif arg =~# '^-[GLS]$\|^--\%(date\|encoding\|contents\|ignore-rev\|ignore-revs-file\)$'
6979 echo s:ChompStderr([dir, 'blame', arg])
6984 if i + 1 < len(flags)
6985 call extend(files, remove(flags, i + 1, -1))
6987 call remove(flags, i)
6989 elseif arg !~# '^-' && (s:HasOpt(flags, '--not') || arg !~# '^\^')
6990 if index(flags, '--') >= 0
6991 call add(commits, remove(flags, i))
6994 if arg =~# '\.\.' && arg !~# '^\.\.\=\%(/\|$\)' && empty(commits)
6995 call add(commits, remove(flags, i))
6999 let dcf = s:DirCommitFile(fugitive#Find(arg, dir))
7000 if len(dcf[1]) && empty(dcf[2])
7001 call add(commits, remove(flags, i))
7006 call add(files, remove(flags, i))
7011 let file = substitute(get(files, 0, get(s:TempState(), 'blame_file', s:Relative('./', dir))), '^\.\%(/\|$\)', '', '')
7012 if empty(commits) && len(files) > 1
7013 call add(commits, remove(files, 1))
7017 let cmd = a:options.flags + ['--no-pager', '-c', 'blame.coloring=none', '-c', 'blame.blankBoundary=false', a:options.subcommand, '--show-number']
7018 call extend(cmd, filter(copy(flags), 'v:val !~# "\\v^%(-b|--%(no-)=color-.*|--progress)$"'))
7019 if a:count > 0 && empty(ranges)
7020 let cmd += ['-L', (a:line1 ? a:line1 : line('.')) . ',' . (a:line1 ? a:line1 : line('.'))]
7022 call extend(cmd, ranges)
7023 let tempname = tempname()
7024 let temp = tempname . (raw ? '' : '.fugitiveblame')
7027 elseif empty(files) && len(matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$'))
7028 let cmd += [matchstr(s:DirCommitFile(@%)[1], '^\x\x\+$')]
7029 elseif empty(files) && !s:HasOpt(flags, '--reverse')
7030 if &modified || !empty(s:DirCommitFile(@%)[1])
7031 let cmd += ['--contents', tempname . '.in']
7032 silent execute 'noautocmd keepalt %write ' . s:fnameescape(tempname . '.in')
7035 exe 'checktime ' . bufnr('')
7038 call fugitive#Autowrite()
7040 let basecmd = [{'git': a:options.git}, dir, '--literal-pathspecs'] + cmd + ['--'] + (len(files) ? files : [file])
7041 let [err, exec_error] = s:StdoutToFile(temp, basecmd)
7042 if exists('delete_in')
7043 call delete(tempname . '.in')
7048 let lines = split(err, "\n")
7050 let lines = readfile(temp)
7052 for i in range(len(lines))
7053 if lines[i] =~# '^error: \|^fatal: '
7061 if i != len(lines) - 1
7068 \ 'git': a:options.git,
7069 \ 'flags': a:options.flags,
7070 \ 'args': [a:options.subcommand] + a:options.subcommand_args,
7071 \ 'git_dir': s:GitDir(a:options),
7072 \ 'cwd': s:UserCommandCwd(a:options),
7073 \ 'filetype': (raw ? 'git' : 'fugitiveblame'),
7074 \ 'blame_options': a:options,
7075 \ 'blame_flags': flags,
7076 \ 'blame_file': file}
7077 if s:HasOpt(flags, '--reverse')
7078 let temp_state.blame_reverse_end = matchstr(get(commits, 0, ''), '\.\.\zs.*')
7080 if a:line1 == 0 && a:count == 1
7081 if get(a:options, 'curwin')
7088 return s:BlameCommit(s:Mods(a:mods) . edit, get(readfile(temp), 0, ''), temp_state)
7089 elseif (a:line1 == 0 || a:range == 1) && a:count > 0
7090 let edit = s:Mods(a:mods) . get(['edit', 'split', 'pedit', 'vsplit', 'tabedit', 'edit'], a:count - (a:line1 ? a:line1 : 1), 'split')
7091 return s:BlameCommit(edit, get(readfile(temp), 0, ''), temp_state)
7093 let temp = s:Resolve(temp)
7094 let temp_state.file = temp
7095 call s:RunSave(temp_state)
7096 if len(ranges + commits + files) || raw
7097 let reload = '|call fugitive#DidChange(fugitive#Result(' . string(temp_state.file) . '))'
7098 let mods = s:Mods(a:mods)
7100 exe 'silent keepalt' mods get(a:options, 'curwin') ? 'edit' : 'split' s:fnameescape(temp)
7101 elseif !&modified || a:bang || &bufhidden ==# 'hide' || (empty(&bufhidden) && &hidden)
7102 exe 'silent' mods 'edit' . (a:bang ? '! ' : ' ') . s:fnameescape(temp)
7104 return mods . 'edit ' . s:fnameescape(temp) . reload
7106 return reload[1 : -1]
7108 let tabmod = matchstr(a:mods, '\<\d*tab\>')
7109 let mods = substitute(a:mods, '\<\d*tab\>', '', 'g')
7111 silent execute tabmod . 'edit %'
7113 let temp_state.origin_bufnr = bufnr('')
7114 if exists('*win_getid')
7115 let temp_state.origin_winid = win_getid()
7118 for winnr in range(winnr('$'),1,-1)
7119 if getwinvar(winnr, '&scrollbind')
7121 call setwinvar(winnr, '&scrollbind', 0)
7122 elseif winnr != winnr() && getwinvar(winnr, '&foldenable')
7123 call setwinvar(winnr, '&foldenable', 0)
7124 call add(restore, 'call setwinvar(bufwinnr('.winbufnr(winnr).'),"&foldenable",1)')
7127 let win_blame_bufnr = s:BlameBufnr(winbufnr(winnr))
7128 if getwinvar(winnr, '&scrollbind') ? win_blame_bufnr == temp_state.origin_bufnr : win_blame_bufnr > 0
7129 execute winbufnr(winnr).'bdelete'
7132 let restore_winnr = get(temp_state, 'origin_winid', 'bufwinnr(' . temp_state.origin_bufnr . ')')
7134 call add(restore, 'call setwinvar(' . restore_winnr . ',"&scrollbind",0)')
7137 call add(restore, 'call setwinvar(' . restore_winnr . ',"&wrap",1)')
7140 call add(restore, 'call setwinvar(' . restore_winnr . ',"&foldenable",1)')
7142 setlocal scrollbind nowrap nofoldenable
7143 let top = line('w0') + &scrolloff
7144 let current = line('.')
7145 exe 'silent keepalt' (a:bang ? s:Mods(mods) . 'split' : s:Mods(mods, 'leftabove') . 'vsplit') s:fnameescape(temp)
7146 let w:fugitive_leave = join(restore, '|')
7150 setlocal nonumber scrollbind nowrap foldcolumn=0 nofoldenable winfixwidth
7151 if exists('&winfixbuf')
7154 if exists('+relativenumber')
7155 setlocal norelativenumber
7157 if exists('+signcolumn')
7158 setlocal signcolumn=no
7160 execute "vertical resize ".(s:linechars('.\{-\}\s\+\d\+\ze)')+1)
7163 exe s:DoAutocmdChanged(temp_state)
7168 return 'echoerr ' . string(v:exception)
7172 function! s:BlameCommit(cmd, ...) abort
7173 let line = a:0 ? a:1 : getline('.')
7174 let state = a:0 ? a:2 : s:TempState()
7175 let sigil = has_key(state, 'blame_reverse_end') ? '-' : '+'
7176 let mods = (s:BlameBufnr() < 0 ? '' : &splitbelow ? "botright " : "topleft ")
7177 let [commit, path, lnum] = s:BlameCommitFileLnum(line, state)
7178 if empty(commit) && len(path) && has_key(state, 'blame_reverse_end')
7179 let path = (len(state.blame_reverse_end) ? state.blame_reverse_end . ':' : ':(top)') . path
7180 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7182 if commit =~# '^0*$'
7183 return 'echoerr ' . string('fugitive: no commit')
7185 if line =~# '^\^' && !has_key(state, 'blame_reverse_end')
7186 let path = commit . ':' . path
7187 return fugitive#Open(mods . a:cmd, 0, '', '+' . lnum . ' ' . s:fnameescape(path), ['+' . lnum, path])
7189 let cmd = fugitive#Open(mods . a:cmd, 0, '', commit, [commit])
7190 if cmd =~# '^echoerr'
7194 if a:cmd ==# 'pedit' || empty(path)
7197 if search('^diff .* b/\M'.escape(path,'\').'$','W')
7199 let head = line('.')
7200 while search('^@@ \|^diff ') && getline('.') =~# '^@@ '
7201 let top = +matchstr(getline('.'),' ' . sigil .'\zs\d\+')
7202 let len = +matchstr(getline('.'),' ' . sigil . '\d\+,\zs\d\+')
7203 if lnum >= top && lnum <= top + len
7204 let offset = lnum - top
7212 while offset > 0 && line('.') < line('$')
7214 if getline('.') =~# '^[ ' . sigil . ']'
7227 function! s:BlameJump(suffix, ...) abort
7228 let suffix = a:suffix
7229 let [commit, path, lnum] = s:BlameCommitFileLnum()
7231 return 'echoerr ' . string('fugitive: could not determine filename for blame')
7233 if commit =~# '^0*$'
7237 let offset = line('.') - line('w0')
7238 let state = s:TempState()
7239 let flags = get(state, 'blame_flags', [])
7240 let blame_bufnr = s:BlameBufnr()
7242 let bufnr = bufnr('')
7243 let winnr = bufwinnr(blame_bufnr)
7245 exe winnr.'wincmd w'
7248 execute 'Gedit' s:fnameescape(commit . suffix . ':' . path)
7251 let my_bufnr = bufnr('')
7253 let blame_args = flags + [commit . suffix, '--', path]
7254 let result = s:BlameSubcommand(0, 0, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7256 let blame_args = flags
7257 let result = s:BlameSubcommand(-1, -1, 0, 0, '', extend({'subcommand_args': blame_args}, state.blame_options, 'keep'))
7259 if bufnr('') == my_bufnr
7264 let delta = line('.') - line('w0') - offset
7266 execute 'normal! '.delta."\<C-E>"
7268 execute 'normal! '.(-delta)."\<C-Y>"
7272 echo ':Git blame' s:fnameescape(blame_args)
7276 let s:hash_colors = {}
7278 function! fugitive#BlameSyntax() abort
7279 let conceal = has('conceal') ? ' conceal' : ''
7280 let flags = get(s:TempState(), 'blame_flags', [])
7281 syn spell notoplevel
7282 syn match FugitiveblameBlank "^\s\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalFile,FugitiveblameOriginalLineNumber skipwhite
7283 syn match FugitiveblameHash "\%(^\^\=[?*]*\)\@<=\<\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7284 if s:HasOpt(flags, '-b') || FugitiveConfigGet('blame.blankBoundary') =~# '^1$\|^true$'
7285 syn match FugitiveblameBoundaryIgnore "^\^[*?]*\x\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7287 syn match FugitiveblameBoundary "^\^"
7289 syn match FugitiveblameScoreDebug " *\d\+\s\+\d\+\s\@=" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile contained skipwhite
7290 syn region FugitiveblameAnnotation matchgroup=FugitiveblameDelimiter start="(" end="\%(\s\d\+\)\@<=)" contained keepend oneline
7291 syn match FugitiveblameTime "\<[0-9:/+-][0-9:/+ -]*[0-9:/+-]\%(\s\+\d\+)\)\@=" contained containedin=FugitiveblameAnnotation
7292 exec 'syn match FugitiveblameLineNumber "\s[[:digit:][:space:]]\{0,' . (len(line('$'))-1). '\}\d)\@=" contained containedin=FugitiveblameAnnotation' conceal
7293 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)
7294 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s(\)\@=" contained nextgroup=FugitiveblameAnnotation skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7295 exec 'syn match FugitiveblameOriginalLineNumber "\s*\d\+\%(\s\+\d\+)\)\@=" contained nextgroup=FugitiveblameShort skipwhite' (s:HasOpt(flags, '--show-number', '-n') ? '' : conceal)
7296 syn match FugitiveblameShort " \+\d\+)" contained contains=FugitiveblameLineNumber
7297 syn match FugitiveblameNotCommittedYet "(\@<=Not Committed Yet\>" contained containedin=FugitiveblameAnnotation
7298 hi def link FugitiveblameBoundary Keyword
7299 hi def link FugitiveblameHash Identifier
7300 hi def link FugitiveblameBoundaryIgnore Ignore
7301 hi def link FugitiveblameUncommitted Ignore
7302 hi def link FugitiveblameScoreDebug Debug
7303 hi def link FugitiveblameTime PreProc
7304 hi def link FugitiveblameLineNumber Number
7305 hi def link FugitiveblameOriginalFile String
7306 hi def link FugitiveblameOriginalLineNumber Float
7307 hi def link FugitiveblameShort FugitiveblameDelimiter
7308 hi def link FugitiveblameDelimiter Delimiter
7309 hi def link FugitiveblameNotCommittedYet Comment
7310 if !get(g:, 'fugitive_dynamic_colors', 1) && !s:HasOpt(flags, '--color-lines') || s:HasOpt(flags, '--no-color-lines')
7314 for x in split('01234567890abcdef', '\zs')
7315 exe 'syn match FugitiveblameHashGroup'.x '"\%(^\^\=[*?]*\)\@<='.x.'\x\{5,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameOriginalLineNumber,fugitiveblameOriginalFile skipwhite'
7317 for lnum in range(1, line('$'))
7318 let orig_hash = matchstr(getline(lnum), '^\^\=[*?]*\zs\x\{6\}')
7319 let hash = orig_hash
7320 let hash = substitute(hash, '\(\x\)\x', '\=submatch(1).printf("%x", 15-str2nr(submatch(1),16))', 'g')
7321 let hash = substitute(hash, '\(\x\x\)', '\=printf("%02x", str2nr(submatch(1),16)*3/4+32)', 'g')
7322 if hash ==# '' || orig_hash ==# '000000' || has_key(seen, hash)
7327 let [s, r, g, b; __] = map(matchlist(orig_hash, '\(\x\)\x\(\x\)\x\(\x\)\x'), 'str2nr(v:val,16)')
7328 let color = 16 + (r + 1) / 3 * 36 + (g + 1) / 3 * 6 + (b + 1) / 3
7334 let s:hash_colors[hash] = ' ctermfg='.color
7336 let s:hash_colors[hash] = ''
7338 let pattern = substitute(orig_hash, '^\(\x\)\x\(\x\)\x\(\x\)\x$', '\1\\x\2\\x\3\\x', '') . '*'
7339 exe 'syn match FugitiveblameHash'.hash.' "\%(^\^\=[*?]*\)\@<='.pattern.'" contained containedin=FugitiveblameHashGroup' . orig_hash[0]
7341 syn match FugitiveblameUncommitted "\%(^\^\=[?*]*\)\@<=\<0\{7,\}\>" nextgroup=FugitiveblameAnnotation,FugitiveblameScoreDebug,FugitiveblameOriginalLineNumber,FugitiveblameOriginalFile skipwhite
7342 call s:BlameRehighlight()
7345 function! s:BlameRehighlight() abort
7346 for [hash, cterm] in items(s:hash_colors)
7347 if !empty(cterm) || has('gui_running') || has('termguicolors') && &termguicolors
7348 exe 'hi FugitiveblameHash'.hash.' guifg=#' . hash . cterm
7350 exe 'hi link FugitiveblameHash'.hash.' Identifier'
7355 function! s:BlameMaps(is_ftplugin) abort
7356 let ft = a:is_ftplugin
7357 call s:MapGitOps(ft)
7358 call s:Map('n', '<F1>', ':help :Git_blame<CR>', '<silent>', ft)
7359 call s:Map('n', 'g?', ':help :Git_blame<CR>', '<silent>', ft)
7360 call s:Map('n', 'gq', ':exe <SID>BlameQuit()<CR>', '<silent>', ft)
7361 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7362 call s:Map('n', '<CR>', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7363 call s:Map('n', '-', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7364 call s:Map('n', 's', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7365 call s:Map('n', 'u', ':<C-U>exe <SID>BlameJump("")<CR>', '<silent>', ft)
7366 call s:Map('n', 'P', ':<C-U>if !v:count<Bar>echoerr "Use ~ (or provide a count)"<Bar>else<Bar>exe <SID>BlameJump("^".v:count1)<Bar>endif<CR>', '<silent>', ft)
7367 call s:Map('n', '~', ':<C-U>exe <SID>BlameJump("~".v:count1)<CR>', '<silent>', ft)
7368 call s:Map('n', 'i', ':<C-U>exe <SID>BlameCommit("exe <SID>BlameLeave()<Bar>edit")<CR>', '<silent>', ft)
7369 call s:Map('n', 'o', ':<C-U>exe <SID>BlameCommit("split")<CR>', '<silent>', ft)
7370 call s:Map('n', 'O', ':<C-U>exe <SID>BlameCommit("tabedit")<CR>', '<silent>', ft)
7371 call s:Map('n', 'p', ':<C-U>exe <SID>BlameCommit("pedit")<CR>', '<silent>', ft)
7372 exe s:Map('n', '.', ":<C-U> <C-R>=substitute(<SID>BlameCommitFileLnum()[0],'^$','@','')<CR><Home>", '', ft)
7373 exe s:Map('n', '(', "-", '', ft)
7374 exe s:Map('n', ')', "+", '', ft)
7375 call s:Map('n', 'A', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze [0-9:/+-][0-9:/+ -]* \\d\\+)')+1+v:count)<CR>", '<silent>', ft)
7376 call s:Map('n', 'C', ":<C-u>exe 'vertical resize '.(<SID>linechars('^\\S\\+')+1+v:count)<CR>", '<silent>', ft)
7377 call s:Map('n', 'D', ":<C-u>exe 'vertical resize '.(<SID>linechars('.\\{-\\}\\ze\\d\\ze\\s\\+\\d\\+)')+1-v:count)<CR>", '<silent>', ft)
7380 function! fugitive#BlameFileType() abort
7382 setlocal foldmethod=manual
7384 let &l:keywordprg = s:Keywordprg()
7386 let b:undo_ftplugin = 'setl keywordprg= foldmethod<'
7387 if exists('+concealcursor')
7388 setlocal concealcursor=nc conceallevel=2
7389 let b:undo_ftplugin .= ' concealcursor< conceallevel<'
7397 function! s:BlameCursorSync(bufnr, line) abort
7398 if a:line == line('.')
7401 if get(s:TempState(), 'origin_bufnr') == a:bufnr || get(s:TempState(a:bufnr), 'origin_bufnr') == bufnr('')
7405 let pos = getpos('.')
7407 call setpos('.', pos)
7412 augroup fugitive_blame
7414 autocmd ColorScheme,GUIEnter * call s:BlameRehighlight()
7415 autocmd BufWinLeave * execute getwinvar(+bufwinnr(+expand('<abuf>')), 'fugitive_leave')
7416 autocmd WinLeave * let s:cursor_for_blame = [bufnr(''), line('.')]
7417 autocmd WinEnter * if exists('s:cursor_for_blame') | call call('s:BlameCursorSync', s:cursor_for_blame) | endif
7422 function! s:BrowserOpen(url, mods, echo_copy) abort
7423 let [_, main, query, anchor; __] = matchlist(a:url, '^\([^#?]*\)\(?[^#]*\)\=\(#.*\)\=')
7424 let url = main . tr(query, ' ', '+') . anchor
7425 let url = substitute(url, '[ <>\|"]', '\="%".printf("%02X",char2nr(submatch(0)))', 'g')
7426 let mods = s:Mods(a:mods)
7431 return 'echo '.string(url)
7432 elseif exists(':Browse') == 2
7433 return 'echo '.string(url).'|' . mods . 'Browse '.url
7434 elseif exists(':OpenBrowser') == 2
7435 return 'echo '.string(url).'|' . mods . 'OpenBrowser '.url
7437 if !exists('g:loaded_netrw')
7438 runtime! autoload/netrw.vim
7440 if exists('*netrw#BrowseX')
7441 return 'echo '.string(url).'|' . mods . 'call netrw#BrowseX('.string(url).', 0)'
7442 elseif exists('*netrw#NetrwBrowseX')
7443 return 'echo '.string(url).'|' . mods . 'call netrw#NetrwBrowseX('.string(url).', 0)'
7444 elseif has('nvim-0.10')
7445 return mods . 'echo luaeval("({vim.ui.open(_A)})[2] or _A", ' . string(url) . ')'
7447 return 'echoerr ' . string('Netrw not found. Define your own :Browse to use :GBrowse')
7452 function! fugitive#BrowseCommand(line1, count, range, bang, mods, arg, ...) abort
7453 exe s:VersionCheck()
7457 if arg =~# '^++\%([Gg]it\)\=[Rr]emote='
7458 let remote = matchstr(arg, '^++\%([Gg]it\)\=[Rr]emote=\zs\S\+')
7459 let arg = matchstr(arg, '\s\zs\S.*')
7461 let validremote = '\.\%(git\)\=\|\.\=/.*\|\a[[:alnum:]_-]*\%(://.\{-\}\)\='
7465 let result = fugitive#Result()
7466 if filereadable(get(result, 'file', ''))
7467 let rev = s:fnameescape(result.file)
7469 return 'echoerr ' . string('fugitive: could not find prior :Git invocation')
7471 elseif !exists('l:remote')
7472 let remote = matchstr(arg, '\\\@<!\%(\\\\\)*[!@]\zs\%('.validremote.'\)$')
7473 let rev = strpart(arg, 0, len(arg) - len(remote) - (empty(remote) ? 0 : 1))
7477 let expanded = s:Expand(rev)
7478 if expanded =~? '^\a\a\+:[\/][\/]' && expanded !~? '^fugitive:'
7479 return s:BrowserOpen(s:Slash(expanded), a:mods, a:bang)
7481 if !exists('l:result')
7482 let result = s:TempState(empty(expanded) ? bufnr('') : expanded)
7484 if !get(result, 'origin_bufnr', 1) && filereadable(get(result, 'file', ''))
7485 for line in readfile(result.file, '', 4096)
7486 let rev = s:fnameescape(matchstr(line, '\<https\=://[^[:space:]<>]*[^[:space:]<>.,;:"''!?]'))
7488 return s:BrowserOpen(rev, a:mods, a:bang)
7491 return 'echoerr ' . string('fugitive: no URL found in output of :Git')
7493 if empty(remote) && expanded =~# '^[^-./:^~][^:^~]*$' && !empty(dir)
7494 let config = fugitive#Config(dir)
7495 if !empty(FugitiveConfigGet('remote.' . expanded . '.url', config))
7496 let remote = expanded
7501 let bufname = &buftype =~# '^\%(nofile\|terminal\)$' ? '' : s:BufName('%')
7502 let expanded = s:DirRev(bufname)[1]
7504 let expanded = fugitive#Path(bufname, ':(top)', dir)
7506 if a:count > 0 && has_key(result, 'origin_bufnr') && a:range != 2
7507 let blame = s:BlameCommitFileLnum(getline(a:count))
7509 let expanded = blame[0]
7513 let full = s:Generate(expanded, dir)
7516 let forbid_ref_as_commit = 0
7517 if full =~# '^fugitive:'
7518 let [dir, commit, path] = s:DirCommitFile(full)
7519 if commit =~# '^\d\=$'
7521 let type = path =~# '^/\=$' ? 'tree' : 'blob'
7523 let ref_match = matchlist(expanded, '^\(@{\@!\|[^:~^@]\+\)\(:\%(//\)\@!\|[~^@]\|$\)')
7524 let ref = get(ref_match, 1, '')
7525 let forbid_ref_as_commit = ref =~# '^@\=$' || ref_match[2] !~# '^:\=$'
7526 if empty(path) && !forbid_ref_as_commit
7529 let type = s:ChompDefault(empty(path) ? 'commit': 'blob',
7530 \ ['cat-file', '-t', commit . substitute(path, '^/', ':', '')], dir)
7533 let path = path[1:-1]
7534 elseif !empty(s:Tree(dir))
7535 let relevant_dir = FugitiveExtractGitDir(full)
7536 if !empty(relevant_dir)
7537 let dir = relevant_dir
7539 let path = fugitive#Path(full, '/', dir)[1:-1]
7540 if empty(path) || isdirectory(full)
7546 let path = '.git/' . full[strlen(dir)+1:-1]
7550 if path =~# '^\.git/'
7551 let ref = matchstr(path, '^.git/\zs\%(refs/[^/]\+/[^/].*\|\w*HEAD\)$')
7552 let type = empty(ref) ? 'root': 'ref'
7555 if empty(ref) || ref ==# 'HEAD' || ref ==# '@'
7556 let ref = fugitive#Head(-1, dir)
7558 if ref =~# '^\x\{40,\}$'
7560 elseif !empty(ref) && ref !~# '^refs/'
7561 let ref = FugitiveExecute(['rev-parse', '--symbolic-full-name', ref], dir).stdout[0]
7567 if !exists('l:config') || s:Dir(config) !=# dir
7568 let config = fugitive#Config(dir)
7571 if !empty(remote) && ref =~# '^refs/remotes/[^/]\+/[^/]\|^refs/heads/[^/]'
7572 let merge = matchstr(ref, '^refs/\%(heads/\|remotes/[^/]\+/\)\zs.\+')
7573 let ref = 'refs/heads/' . merge
7574 elseif ref =~# '^refs/remotes/[^/]\+/[^/]'
7575 let remote = matchstr(ref, '^refs/remotes/\zs[^/]\+')
7576 let merge = matchstr(ref, '^refs/remotes/[^/]\+/\zs.\+')
7577 let ref = 'refs/heads/' . merge
7578 elseif ref =~# '^refs/heads/[^/]'
7579 let merge = strpart(ref, 11)
7580 let r = FugitiveConfigGet('branch.' . merge . '.remote', config)
7581 let m = FugitiveConfigGet('branch.' . merge . '.merge', config)[11:-1]
7582 if r ==# '.' && !empty(m)
7583 let r2 = FugitiveConfigGet('branch.'.m.'.remote', config)
7586 let m = FugitiveConfigGet('branch.'.m.'.merge', config)[11:-1]
7593 let remote_ref = 'refs/remotes/' . remote . '/' . merge
7594 if FugitiveConfigGet('push.default', config) ==# 'upstream' ||
7595 \ !filereadable(FugitiveFind('.git/' . remote_ref, dir)) && empty(s:ChompDefault('', ['rev-parse', '--verify', remote_ref, '--'], dir))
7597 let ref = 'refs/heads/' . merge
7602 if empty(remote) || remote ==# '.'
7603 let remote = s:RemoteDefault(config)
7605 if empty(merge) || empty(remote)
7606 let provider_ref = ref
7608 let provider_ref = 'refs/remotes/' . remote . '/' . merge
7610 if forbid_ref_as_commit || a:count >= 0
7615 elseif type ==# 'ref' && ref =~# '^refs/\%(heads\|tags\)/[^/]'
7616 let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7619 let line1 = a:count > 0 && type ==# 'blob' ? a:line1 : 0
7620 let line2 = a:count > 0 && type ==# 'blob' ? a:count : 0
7621 if empty(commit) && type =~# '^\%(tree\|blob\)$'
7623 let commit = matchstr(ref, '^\Crefs/\%(heads\|tags\)/\zs.*')
7624 elseif len(provider_ref)
7625 let owner = s:Owner(@%, dir)
7626 let commit = s:ChompDefault('', ['merge-base', provider_ref, empty(owner) ? '@' : owner, '--'], dir)
7627 if line2 > 0 && empty(arg) && commit =~# '^\x\{40,\}$' && type ==# 'blob'
7628 let blame_list = tempname()
7629 call writefile([commit, ''], blame_list, 'b')
7630 let blame_cmd = ['-c', 'blame.coloring=none', 'blame', '-L', line1.','.line2, '-S', blame_list, '-s', '--show-number']
7631 if !&l:modified || has_key(result, 'origin_bufnr')
7632 let [blame, exec_error] = s:LinesError(blame_cmd + ['./' . path], dir)
7634 let blame_in = tempname()
7635 silent exe 'noautocmd keepalt %write' blame_in
7636 let [blame, exec_error] = s:LinesError(blame_cmd + ['--contents', blame_in, './' . path], dir)
7637 call delete(blame_in)
7639 call delete(blame_list)
7641 let blame_regex = '^\^\x\+\s\+\zs\d\+\ze\s'
7642 if get(blame, 0) =~# blame_regex && get(blame, -1) =~# blame_regex
7643 let line1 = +matchstr(blame[0], blame_regex)
7644 let line2 = +matchstr(blame[-1], blame_regex)
7646 throw "fugitive: can't browse to unpushed change"
7652 let commit = fugitive#RevParse(empty(ref) ? 'HEAD' : ref, dir)
7657 let remote_url = remote
7659 let remote_url = fugitive#RemoteUrl(remote, config)
7661 let raw = empty(remote_url) ? remote : remote_url
7662 let git_dir = s:GitDir(dir)
7665 \ 'git_dir': git_dir,
7666 \ 'repo': {'git_dir': git_dir},
7668 \ 'remote_name': remote,
7669 \ 'commit': s:UrlEncode(commit),
7670 \ 'path': substitute(s:UrlEncode(path), '%20', ' ', 'g'),
7676 if type ==# 'ref' && ref =~# '^refs/'
7677 let opts.path = '.git/' . s:UrlEncode(ref)
7679 elseif type ==# 'root'
7680 let opts.path ='.git/index'
7683 elseif type ==# 'tree' && !empty(path)
7684 let opts.path = s:sub(opts.path, '/\=$', '/')
7687 for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7688 let l:.url = call(Handler, [copy(opts)])
7689 if type(url) == type('') && url =~# '://'
7690 return s:BrowserOpen(url, a:mods, a:bang)
7694 if !empty(remote_url)
7695 return 'echoerr ' . string("fugitive: no GBrowse handler installed for '".remote_url."'")
7697 return 'echoerr ' . string("fugitive: could not find remote named '".remote."'")
7700 return 'echoerr ' . string(v:exception)
7704 function! s:RemoteRefToLocalRef(repo, remote_url, ref_path) abort
7705 let ref_path = substitute(a:ref_path, ':', '/', '')
7707 if ref_path =~# '^\x\{40,\}\%(/\|$\)'
7708 let rev = substitute(ref_path, '/', ':', '')
7709 elseif ref_path =~# '^[^:/^~]\+'
7710 let first_component = matchstr(ref_path, '^[^:/^~]\+')
7711 let lines = fugitive#Execute(['ls-remote', a:remote_url, first_component, first_component . '/*'], a:repo).stdout[0:-2]
7713 let full = matchstr(line, "\t\\zs.*")
7714 for candidate in [full, matchstr(full, '^refs/\w\+/\zs.*')]
7715 if candidate ==# first_component || strpart(ref_path . '/', 0, len(candidate) + 1) ==# candidate . '/'
7716 let rev = matchstr(line, '^\x\+') . substitute(strpart(ref_path, len(candidate)), '/', ':', '')
7724 let commitish = matchstr(rev, '^[^:^~]*')
7725 let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7726 if rev_parse.exit_status
7727 if fugitive#Execute(['fetch', remote_url, commitish], a:repo).exit_status
7730 let rev_parse = fugitive#Execute(['rev-parse', '--verify', commitish], a:repo)
7732 if rev_parse.exit_status
7735 return rev_parse.stdout[0] . matchstr(rev, ':.*')
7738 function! fugitive#ResolveUrl(target, ...) abort
7739 let repo = call('s:Dir', a:000)
7740 let origins = get(g:, 'fugitive_url_origins', {})
7741 let prefix = substitute(s:Slash(a:target), '#.*', '', '')
7742 while prefix =~# '://'
7743 let extracted = FugitiveExtractGitDir(expand(get(origins, prefix, '')))
7744 if !empty(extracted)
7745 let repo = s:Dir(extracted)
7748 let prefix = matchstr(prefix, '.*\ze/')
7750 let git_dir = s:GitDir(repo)
7751 for remote_name in keys(FugitiveConfigGetRegexp('^remote\.\zs.*\ze\.url$', repo))
7752 let remote_url = fugitive#RemoteUrl(remote_name, repo)
7753 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`']]
7754 let handler_opts = {
7755 \ 'git_dir': git_dir,
7756 \ 'repo': {'git_dir': git_dir},
7757 \ 'remote': remote_url,
7758 \ 'remote_name': remote_name,
7759 \ 'commit': '1`commit`',
7760 \ 'type': get(variant, 0),
7761 \ 'path': get(variant, 1) ? '1`path`' : '',
7762 \ 'line1': get(variant, 2),
7763 \ 'line2': get(variant, 3)}
7765 for l:.Handler in get(g:, 'fugitive_browse_handlers', [])
7766 let l:.url = call(Handler, [copy(handler_opts)])
7767 if type(url) == type('') && url =~# '://'
7771 if type(url) != type('') || url !~# '://'
7774 let keys = split(substitute(url, '\d`\(\w\+`\)\|.', '\1', 'g'), '`')
7775 let pattern = substitute(url, '\d`\w\+`\|[][^$.*\~]', '\=len(submatch(0)) == 1 ? "\\" . submatch(0) : "\\([^#?&;]\\{-\\}\\)"', 'g')
7776 let pattern = '^' . substitute(pattern, '^https\=:', 'https\\=:', '') . '$'
7777 let target = s:Slash(no_anchor ? substitute(a:target, '#.*', '', '') : a:target)
7778 let values = matchlist(s:Slash(a:target), pattern)[1:-1]
7783 for i in range(len(keys))
7784 let kvs[keys[i]] = values[i]
7786 if has_key(kvs, 'commit') && has_key(kvs, 'path')
7787 let ref_path = kvs.commit . '/' . kvs.path
7788 elseif has_key(kvs, 'commit') && variant[0] ==# 'tree'
7789 let ref_path = kvs.commit . '/'
7790 elseif has_key(kvs, 'commit')
7791 let ref_path = kvs.commit
7795 let rev = s:RemoteRefToLocalRef(repo, remote_url, fugitive#UrlDecode(ref_path))
7796 return [fugitive#Find(rev, repo), empty(rev) ? 0 : +get(kvs, 'line1')]
7802 function! s:ResolveUrl(target, ...) abort
7804 let [url, lnum] = call('fugitive#ResolveUrl', [a:target] + a:000)
7810 return [substitute(a:target, '#.*', '', ''), 0]
7815 let s:ref_header = '\%(Merge\|Rebase\|Upstream\|Pull\|Push\)'
7817 nnoremap <SID>: :<C-U><C-R>=v:count ? v:count : ''<CR>
7818 function! fugitive#MapCfile(...) abort
7819 exe 'cnoremap <buffer> <expr> <Plug><cfile>' (a:0 ? a:1 : 'fugitive#Cfile()')
7820 let b:undo_ftplugin = get(b:, 'undo_ftplugin', 'exe') . '|sil! exe "cunmap <buffer> <Plug><cfile>"'
7821 if !exists('g:fugitive_no_maps')
7822 call s:Map('n', 'gf', '<SID>:find <Plug><cfile><CR>', '<silent><unique>', 1)
7823 call s:Map('n', '<C-W>f', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7824 call s:Map('n', '<C-W><C-F>', '<SID>:sfind <Plug><cfile><CR>', '<silent><unique>', 1)
7825 call s:Map('n', '<C-W>gf', '<SID>:tabfind <Plug><cfile><CR>', '<silent><unique>', 1)
7826 call s:Map('c', '<C-R><C-F>', '<Plug><cfile>', '<unique>', 1)
7830 function! s:ContainingCommit() abort
7831 let commit = s:Owner(@%)
7832 return empty(commit) ? '@' : commit
7835 function! s:SquashArgument(...) abort
7836 if &filetype == 'fugitive'
7837 let commit = matchstr(getline('.'), '^\%(\%(\x\x\x\)\@!\l\+\s\+\)\=\zs[0-9a-f]\{4,\}\ze \|^' . s:ref_header . ': \zs\S\+')
7838 elseif has_key(s:temp_files, s:cpath(expand('%:p')))
7839 let commit = matchstr(getline('.'), '\S\@<!\x\{4,\}\S\@!')
7841 let commit = s:Owner(@%)
7843 return len(commit) && a:0 ? printf(a:1, commit) : commit
7846 function! s:RebaseArgument() abort
7847 return s:SquashArgument(' %s^')
7850 function! s:NavigateUp(count) abort
7851 let rev = substitute(s:DirRev(@%)[1], '^$', ':', 'g')
7855 let rev = matchstr(rev, '.*\ze/.\+', '')
7856 elseif rev =~# '.:.'
7857 let rev = matchstr(rev, '^.[^:]*:')
7870 function! s:ParseDiffHeader(str) abort
7871 let list = matchlist(a:str, '\Cdiff --git \("\=\w/.*\|/dev/null\) \("\=\w/.*\|/dev/null\)$')
7873 let list = matchlist(a:str, '\Cdiff --git \("\=[^/].*\|/dev/null\) \("\=[^/].*\|/dev/null\)$')
7875 return [fugitive#Unquote(get(list, 1, '')), fugitive#Unquote(get(list, 2, ''))]
7878 function! s:HunkPosition(lnum) abort
7879 let lnum = a:lnum + get({'@': 1, '\': -1}, getline(a:lnum)[0], 0)
7880 let offsets = {' ': -1, '+': 0, '-': 0}
7881 let sigil = getline(lnum)[0]
7882 let line_char = sigil
7883 while has_key(offsets, line_char)
7884 let offsets[line_char] += 1
7886 let line_char = getline(lnum)[0]
7888 let starts = matchlist(getline(lnum), '^@@\+[ 0-9,-]* -\(\d\+\)\%(,\d\+\)\= +\(\d\+\)[ ,]')
7893 \ sigil ==# '+' ? 0 : starts[1] + offsets[' '] + offsets['-'],
7894 \ sigil ==# '-' ? 0 : starts[2] + offsets[' '] + offsets['+']]
7897 function! s:MapMotion(lhs, rhs) abort
7899 \ s:Map('n', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7900 \ s:Map('o', a:lhs, ":<C-U>" . a:rhs . "<CR>", "<silent>"),
7901 \ s:Map('x', a:lhs, ":<C-U>exe 'normal! gv'<Bar>" . a:rhs . "<CR>", "<silent>")]
7902 call filter(maps, '!empty(v:val)')
7903 return join(maps, '|')
7906 function! s:MapGitOps(is_ftplugin) abort
7907 let ft = a:is_ftplugin
7911 exe s:Map('n', 'c<Space>', ':Git commit<Space>', '', ft)
7912 exe s:Map('n', 'c<CR>', ':Git commit<CR>', '', ft)
7913 exe s:Map('n', 'cv<Space>', ':tab Git commit -v<Space>', '', ft)
7914 exe s:Map('n', 'cv<CR>', ':tab Git commit -v<CR>', '', ft)
7915 exe s:Map('n', 'ca', ':<C-U>Git commit --amend<CR>', '<silent>', ft)
7916 exe s:Map('n', 'cc', ':<C-U>Git commit<CR>', '<silent>', ft)
7917 exe s:Map('n', 'ce', ':<C-U>Git commit --amend --no-edit<CR>', '<silent>', ft)
7918 exe s:Map('n', 'cw', ':<C-U>Git commit --amend --only<CR>', '<silent>', ft)
7919 exe s:Map('n', 'cva', ':<C-U>tab Git commit -v --amend<CR>', '<silent>', ft)
7920 exe s:Map('n', 'cvc', ':<C-U>tab Git commit -v<CR>', '<silent>', ft)
7921 exe s:Map('n', 'cRa', ':<C-U>Git commit --reset-author --amend<CR>', '<silent>', ft)
7922 exe s:Map('n', 'cRe', ':<C-U>Git commit --reset-author --amend --no-edit<CR>', '<silent>', ft)
7923 exe s:Map('n', 'cRw', ':<C-U>Git commit --reset-author --amend --only<CR>', '<silent>', ft)
7924 exe s:Map('n', 'cf', ':<C-U>Git commit --fixup=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7925 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)
7926 exe s:Map('n', 'cs', ':<C-U>Git commit --no-edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7927 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)
7928 exe s:Map('n', 'cA', ':<C-U>Git commit --edit --squash=<C-R>=<SID>SquashArgument()<CR>', '', ft)
7929 exe s:Map('n', 'c?', ':<C-U>help fugitive_c<CR>', '<silent>', ft)
7931 exe s:Map('n', 'cr<Space>', ':Git revert<Space>', '', ft)
7932 exe s:Map('n', 'cr<CR>', ':Git revert<CR>', '', ft)
7933 exe s:Map('n', 'crc', ':<C-U>Git revert <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7934 exe s:Map('n', 'crn', ':<C-U>Git revert --no-commit <C-R>=<SID>SquashArgument()<CR><CR>', '<silent>', ft)
7935 exe s:Map('n', 'cr?', ':<C-U>help fugitive_cr<CR>', '<silent>', ft)
7937 exe s:Map('n', 'cm<Space>', ':Git merge<Space>', '', ft)
7938 exe s:Map('n', 'cm<CR>', ':Git merge<CR>', '', ft)
7939 exe s:Map('n', 'cmt', ':Git mergetool', '', ft)
7940 exe s:Map('n', 'cm?', ':<C-U>help fugitive_cm<CR>', '<silent>', ft)
7942 exe s:Map('n', 'cz<Space>', ':Git stash<Space>', '', ft)
7943 exe s:Map('n', 'cz<CR>', ':Git stash<CR>', '', ft)
7944 exe s:Map('n', 'cza', ':<C-U>Git stash apply --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7945 exe s:Map('n', 'czA', ':<C-U>Git stash apply --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7946 exe s:Map('n', 'czp', ':<C-U>Git stash pop --quiet --index stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7947 exe s:Map('n', 'czP', ':<C-U>Git stash pop --quiet stash@{<C-R>=v:count<CR>}<CR>', '', ft)
7948 exe s:Map('n', 'czs', ':<C-U>Git stash push --staged<CR>', '', ft)
7949 exe s:Map('n', 'czv', ':<C-U>exe "Gedit" fugitive#RevParse("stash@{" . v:count . "}")<CR>', '<silent>', ft)
7950 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)
7951 exe s:Map('n', 'czz', ':<C-U>Git stash push <C-R>=v:count > 1 ? " --all" : v:count ? " --include-untracked" : ""<CR><CR>', '', ft)
7952 exe s:Map('n', 'cz?', ':<C-U>help fugitive_cz<CR>', '<silent>', ft)
7954 exe s:Map('n', 'co<Space>', ':Git checkout<Space>', '', ft)
7955 exe s:Map('n', 'co<CR>', ':Git checkout<CR>', '', ft)
7956 exe s:Map('n', 'coo', ':<C-U>Git checkout <C-R>=substitute(<SID>SquashArgument(),"^$",get(<SID>TempState(),"filetype","") ==# "git" ? expand("<cfile>") : "","")<CR> --<CR>', '', ft)
7957 exe s:Map('n', 'co?', ':<C-U>help fugitive_co<CR>', '<silent>', ft)
7959 exe s:Map('n', 'cb<Space>', ':Git branch<Space>', '', ft)
7960 exe s:Map('n', 'cb<CR>', ':Git branch<CR>', '', ft)
7961 exe s:Map('n', 'cb?', ':<C-U>help fugitive_cb<CR>', '<silent>', ft)
7963 exe s:Map('n', 'r<Space>', ':Git rebase<Space>', '', ft)
7964 exe s:Map('n', 'r<CR>', ':Git rebase<CR>', '', ft)
7965 exe s:Map('n', 'ri', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7966 exe s:Map('n', 'rf', ':<C-U>Git -c sequence.editor=true rebase --interactive --autosquash<C-R>=<SID>RebaseArgument()<CR><CR>', '<silent>', ft)
7967 exe s:Map('n', 'ru', ':<C-U>Git rebase --interactive @{upstream}<CR>', '<silent>', ft)
7968 exe s:Map('n', 'rp', ':<C-U>Git rebase --interactive @{push}<CR>', '<silent>', ft)
7969 exe s:Map('n', 'rw', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/reword/e<CR>', '<silent>', ft)
7970 exe s:Map('n', 'rm', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/edit/e<CR>', '<silent>', ft)
7971 exe s:Map('n', 'rd', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7972 exe s:Map('n', 'rk', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7973 exe s:Map('n', 'rx', ':<C-U>Git rebase --interactive<C-R>=<SID>RebaseArgument()<CR><Bar>s/^pick/drop/e<CR>', '<silent>', ft)
7974 exe s:Map('n', 'rr', ':<C-U>Git rebase --continue<CR>', '<silent>', ft)
7975 exe s:Map('n', 'rs', ':<C-U>Git rebase --skip<CR>', '<silent>', ft)
7976 exe s:Map('n', 're', ':<C-U>Git rebase --edit-todo<CR>', '<silent>', ft)
7977 exe s:Map('n', 'ra', ':<C-U>Git rebase --abort<CR>', '<silent>', ft)
7978 exe s:Map('n', 'r?', ':<C-U>help fugitive_r<CR>', '<silent>', ft)
7981 function! fugitive#MapJumps(...) abort
7983 if get(b:, 'fugitive_type', '') ==# 'blob'
7984 let blame_tail = '<C-R>=v:count ? " --reverse" : ""<CR><CR>'
7985 exe s:Map('n', '<2-LeftMouse>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7986 exe s:Map('n', '<CR>', ':<C-U>0,1Git ++curwin blame' . blame_tail, '<silent>')
7987 exe s:Map('n', 'o', ':<C-U>0,1Git blame' . blame_tail, '<silent>')
7988 exe s:Map('n', 'p', ':<C-U>0,1Git! blame' . blame_tail, '<silent>')
7989 if has('patch-7.4.1898')
7990 exe s:Map('n', 'gO', ':<C-U>vertical 0,1Git blame' . blame_tail, '<silent>')
7991 exe s:Map('n', 'O', ':<C-U>tab 0,1Git blame' . blame_tail, '<silent>')
7993 exe s:Map('n', 'gO', ':<C-U>0,4Git blame' . blame_tail, '<silent>')
7994 exe s:Map('n', 'O', ':<C-U>0,5Git blame' . blame_tail, '<silent>')
7997 call s:Map('n', 'D', ":echoerr 'fugitive: D has been removed in favor of dd'<CR>", '<silent><unique>')
7998 call s:Map('n', 'dd', ":<C-U>call fugitive#DiffClose()<Bar>Gdiffsplit!<CR>", '<silent>')
7999 call s:Map('n', 'dh', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
8000 call s:Map('n', 'ds', ":<C-U>call fugitive#DiffClose()<Bar>Ghdiffsplit!<CR>", '<silent>')
8001 call s:Map('n', 'dv', ":<C-U>call fugitive#DiffClose()<Bar>Gvdiffsplit!<CR>", '<silent>')
8002 call s:Map('n', 'd?', ":<C-U>help fugitive_d<CR>", '<silent>')
8005 call s:Map('n', '<2-LeftMouse>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
8006 call s:Map('n', '<CR>', ':<C-U>exe <SID>GF("edit")<CR>', '<silent>')
8007 call s:Map('n', 'o', ':<C-U>exe <SID>GF("split")<CR>', '<silent>')
8008 call s:Map('n', 'gO', ':<C-U>exe <SID>GF("vsplit")<CR>', '<silent>')
8009 call s:Map('n', 'O', ':<C-U>exe <SID>GF("tabedit")<CR>', '<silent>')
8010 call s:Map('n', 'p', ':<C-U>exe <SID>GF("pedit")<CR>', '<silent>')
8012 if !exists('g:fugitive_no_maps')
8013 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>')
8014 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>')
8016 call s:MapMotion('(', 'exe <SID>PreviousItem(v:count1)')
8017 call s:MapMotion(')', 'exe <SID>NextItem(v:count1)')
8018 call s:MapMotion('K', 'exe <SID>PreviousHunk(v:count1)')
8019 call s:MapMotion('J', 'exe <SID>NextHunk(v:count1)')
8020 call s:MapMotion('[c', 'exe <SID>PreviousHunk(v:count1)')
8021 call s:MapMotion(']c', 'exe <SID>NextHunk(v:count1)')
8022 call s:MapMotion('[/', 'exe <SID>PreviousFile(v:count1)')
8023 call s:MapMotion(']/', 'exe <SID>NextFile(v:count1)')
8024 call s:MapMotion('[m', 'exe <SID>PreviousFile(v:count1)')
8025 call s:MapMotion(']m', 'exe <SID>NextFile(v:count1)')
8026 call s:MapMotion('[[', 'exe <SID>PreviousSection(v:count1)')
8027 call s:MapMotion(']]', 'exe <SID>NextSection(v:count1)')
8028 call s:MapMotion('[]', 'exe <SID>PreviousSectionEnd(v:count1)')
8029 call s:MapMotion('][', 'exe <SID>NextSectionEnd(v:count1)')
8030 call s:Map('nxo', '*', '<SID>PatchSearchExpr(0)', '<expr>')
8031 call s:Map('nxo', '#', '<SID>PatchSearchExpr(1)', '<expr>')
8033 call s:Map('n', 'S', ':<C-U>echoerr "Use gO"<CR>', '<silent><unique>')
8034 call s:Map('n', 'dq', ":<C-U>call fugitive#DiffClose()<CR>", '<silent>')
8035 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>')
8036 call s:Map('n', 'P', ":<C-U>if !v:count<Bar>echoerr 'Use ~ (or provide a count)'<Bar>else<Bar>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'^'.v:count1.<SID>Relative(':'))<Bar>endif<CR>", '<silent>')
8037 call s:Map('n', '~', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit().'~'.v:count1.<SID>Relative(':'))<CR>", '<silent>')
8038 call s:Map('n', 'C', ":<C-U>exe 'Gedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8039 call s:Map('n', 'cp', ":<C-U>echoerr 'Use gC'<CR>", '<silent><unique>')
8040 call s:Map('n', 'gC', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8041 call s:Map('n', 'gc', ":<C-U>exe 'Gpedit ' . <SID>fnameescape(<SID>ContainingCommit())<CR>", '<silent>')
8042 call s:Map('n', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
8043 call s:Map('x', 'gi', ":<C-U>exe 'Gsplit' (v:count ? '.gitignore' : '.git/info/exclude')<CR>", '<silent>')
8045 call s:Map('n', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
8046 call s:Map('x', '.', ":<C-U> <C-R>=<SID>fnameescape(fugitive#Real(@%))<CR><Home>")
8047 call s:Map('n', 'g?', ":<C-U>help fugitive-map<CR>", '<silent>')
8048 call s:Map('n', '<F1>', ":<C-U>help fugitive-map<CR>", '<silent>')
8051 let old_browsex = maparg('<Plug>NetrwBrowseX', 'n')
8052 let new_browsex = substitute(old_browsex, '\Cnetrw#CheckIfRemote(\%(netrw#GX()\)\=)', '0', 'g')
8053 let new_browsex = substitute(new_browsex, 'netrw#GX()\|expand((exists("g:netrw_gx")? g:netrw_gx : ''<cfile>''))', 'fugitive#GX()', 'g')
8054 if new_browsex !=# old_browsex
8055 exe 'nnoremap <silent> <buffer> <Plug>NetrwBrowseX' new_browsex
8060 function! fugitive#GX() abort
8062 let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'git' ? s:cfile() : []
8063 if len(results) && len(results[0])
8064 return FugitiveReal(s:Generate(results[0]))
8068 return expand(get(g:, 'netrw_gx', expand('<cfile>')))
8071 function! s:CfilePorcelain(...) abort
8076 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8077 let info = s:StageInfo()
8078 let line = getline('.')
8079 if len(info.sigil) && len(info.section) && len(info.paths)
8080 if info.section ==# 'Unstaged' && info.sigil !=# '-'
8081 return [lead . info.relative[0], info.offset, 'normal!zv']
8082 elseif info.section ==# 'Staged' && info.sigil ==# '-'
8083 return ['@:' . info.relative[0], info.offset, 'normal!zv']
8085 return [':0:' . info.relative[0], info.offset, 'normal!zv']
8087 elseif len(info.paths)
8088 return [lead . info.relative[0]]
8089 elseif len(info.commit)
8090 return [info.commit]
8091 elseif line =~# '^' . s:ref_header . ': \|^Head: '
8092 return [matchstr(line, ' \zs.*')]
8098 function! fugitive#PorcelainCfile() abort
8099 let file = fugitive#Find(s:CfilePorcelain()[0])
8100 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8103 function! s:StatusCfile(...) abort
8108 let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
8109 if getline('.') =~# '^.\=\trenamed:.* -> '
8110 return [lead . matchstr(getline('.'),' -> \zs.*')]
8111 elseif getline('.') =~# '^.\=\t\(\k\| \)\+\p\?: *.'
8112 return [lead . matchstr(getline('.'),': *\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')]
8113 elseif getline('.') =~# '^.\=\t.'
8114 return [lead . matchstr(getline('.'),'\t\zs.*')]
8115 elseif getline('.') =~# ': needs merge$'
8116 return [lead . matchstr(getline('.'),'.*\ze: needs merge$')]
8117 elseif getline('.') =~# '^\%(. \)\=Not currently on any branch.$'
8119 elseif getline('.') =~# '^\%(. \)\=On branch '
8120 return ['refs/heads/'.getline('.')[12:]]
8121 elseif getline('.') =~# "^\\%(. \\)\=Your branch .*'"
8122 return [matchstr(getline('.'),"'\\zs\\S\\+\\ze'")]
8128 function! fugitive#MessageCfile() abort
8129 let file = fugitive#Find(get(s:StatusCfile(), 0, ''))
8130 return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
8133 function! s:BranchCfile(result) abort
8134 return matchstr(getline('.'), '^. \zs\S\+')
8137 let s:diff_header_pattern = '^diff --git \%("\=[abciow12]/.*\|/dev/null\) \%("\=[abciow12]/.*\|/dev/null\)$'
8138 function! s:cfile() abort
8139 let temp_state = s:TempState()
8140 let name = substitute(get(get(temp_state, 'args', []), 0, ''), '\%(^\|-\)\(\l\)', '\u\1', 'g')
8141 if exists('*s:' . name . 'Cfile')
8142 let cfile = s:{name}Cfile(temp_state)
8144 return type(cfile) == type('') ? [cfile] : cfile
8147 if empty(FugitiveGitDir())
8151 let myhash = s:DirRev(@%)[1]
8154 let myhash = fugitive#RevParse(myhash)
8159 if empty(myhash) && get(temp_state, 'filetype', '') ==# 'git'
8160 let lnum = line('.')
8162 if getline(lnum) =~# '^\%(commit\|tag\) \w'
8163 let myhash = matchstr(getline(lnum),'^\w\+ \zs\S\+')
8170 let showtree = (getline(1) =~# '^tree ' && getline(2) == "")
8172 let treebase = substitute(s:DirCommitFile(@%)[1], '^\d$', ':&', '') . ':' .
8173 \ s:Relative('') . (s:Relative('') =~# '^$\|/$' ? '' : '/')
8175 if getline('.') =~# '^\d\{6\} \l\{3,8\} \x\{40,\}\t'
8176 return [treebase . s:sub(matchstr(getline('.'),'\t\zs.*'),'/$','')]
8178 return [treebase . s:sub(getline('.'),'/$','')]
8185 if getline('.') =~# '^\d\{6\} \x\{40,\} \d\t'
8186 let ref = matchstr(getline('.'),'\x\{40,\}')
8187 let file = ':'.s:sub(matchstr(getline('.'),'\d\t.*'),'\t',':')
8191 if getline('.') =~# '^ref: '
8192 let ref = strpart(getline('.'),5)
8194 elseif getline('.') =~# '^\%([|/\\_ ]*\*[|/\\_ ]*\)\=commit \x\{40,\}\>'
8195 let ref = matchstr(getline('.'),'\x\{40,\}')
8198 elseif getline('.') =~# '^parent \x\{40,\}\>'
8199 let ref = matchstr(getline('.'),'\x\{40,\}')
8200 let line = line('.')
8202 while getline(line) =~# '^parent '
8208 elseif getline('.') =~# '^tree \x\{40,\}$'
8209 let ref = matchstr(getline('.'),'\x\{40,\}')
8210 if len(myhash) && fugitive#RevParse(myhash.':') ==# ref
8211 let ref = myhash.':'
8215 elseif getline('.') =~# '^object \x\{40,\}$' && getline(line('.')+1) =~ '^type \%(commit\|tree\|blob\)$'
8216 let ref = matchstr(getline('.'),'\x\{40,\}')
8217 let type = matchstr(getline(line('.')+1),'type \zs.*')
8219 elseif getline('.') =~# '^\l\{3,8\} '.myhash.'$'
8220 let ref = s:DirRev(@%)[1]
8222 elseif getline('.') =~# '^\l\{3,8\} \x\{40,\}\>'
8223 let ref = matchstr(getline('.'),'\x\{40,\}')
8224 echoerr "warning: unknown context ".matchstr(getline('.'),'^\l*')
8226 elseif getline('.') =~# '^[A-Z]\d*\t\S' && len(myhash)
8227 let files = split(getline('.'), "\t")[1:-1]
8228 let ref = 'b/' . files[-1]
8229 if getline('.') =~# '^D'
8230 let ref = 'a/' . files[0]
8231 elseif getline('.') !~# '^A'
8232 let dcmds = ['', 'Gdiffsplit! >' . myhash . '^:' . fnameescape(files[0])]
8235 elseif getline('.') =~# '^[+-]'
8236 let [header_lnum, old_lnum, new_lnum] = s:HunkPosition(line('.'))
8238 let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[1]
8239 let dcmds = [new_lnum, 'normal!zv']
8241 let ref = s:ParseDiffHeader(getline(search(s:diff_header_pattern, 'bnW')))[0]
8242 let dcmds = [old_lnum, 'normal!zv']
8244 let ref = fugitive#Unquote(matchstr(getline('.'), '\C[+-]\{3\} \zs"\=[abciow12]/.*'))
8247 elseif getline('.') =~# '^rename from '
8248 let ref = 'a/'.getline('.')[12:]
8249 elseif getline('.') =~# '^rename to '
8250 let ref = 'b/'.getline('.')[10:]
8252 elseif getline('.') =~# '^@@ -\d\+\%(,\d\+\)\= +\d\+'
8253 let diff = getline(search(s:diff_header_pattern, 'bcnW'))
8254 let offset = matchstr(getline('.'), '+\zs\d\+')
8256 let [dref, ref] = s:ParseDiffHeader(diff)
8257 let dcmd = 'Gdiffsplit! +'.offset
8259 elseif getline('.') =~# s:diff_header_pattern
8260 let [dref, ref] = s:ParseDiffHeader(getline('.'))
8261 let dcmd = 'Gdiffsplit!'
8263 elseif getline('.') =~# '^index ' && getline(line('.')-1) =~# s:diff_header_pattern
8264 let [dref, ref] = s:ParseDiffHeader(getline(line('.') - '.'))
8265 let dcmd = 'Gdiffsplit!'
8267 elseif line('$') == 1 && getline('.') =~ '^\x\{40,\}$'
8268 let ref = getline('.')
8270 elseif expand('<cword>') =~# '^\x\{7,\}\>'
8271 return [expand('<cword>')]
8286 let prefixes.a = myhash.'^:'
8287 let prefixes.b = myhash.':'
8289 let ref = substitute(ref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8291 let dref = substitute(dref, '^\(\w\)/', '\=get(prefixes, submatch(1), "@:")', '')
8294 if ref ==# '/dev/null'
8296 let ref = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
8300 return [ref, dcmd . ' >' . s:fnameescape(dref)] + dcmds
8302 return [ref] + dcmds
8310 function! s:GF(mode) abort
8312 let results = &filetype ==# 'fugitive' ? s:CfilePorcelain() : &filetype ==# 'gitcommit' ? s:StatusCfile() : s:cfile()
8314 return 'echoerr ' . string(v:exception)
8317 let cmd = 'G' . a:mode .
8318 \ (empty(results[1]) ? '' : ' +' . s:PlusEscape(results[1])) . ' ' .
8319 \ fnameescape(results[0])
8320 let tail = join(map(results[2:-1], '"|" . v:val'), '')
8321 if a:mode ==# 'pedit' && len(tail)
8322 return cmd . '|wincmd P|exe ' . string(tail[1:-1]) . '|wincmd p'
8326 elseif len(results) && len(results[0])
8327 return 'G' . a:mode . ' ' . s:fnameescape(results[0])
8333 function! fugitive#Cfile() abort
8335 let results = s:cfile()
8337 if !empty(s:TempState())
8338 let cfile = s:TempDotMap()
8340 return fnameescape(s:Generate(cfile))
8343 let cfile = expand('<cfile>')
8344 if &includeexpr =~# '\<v:fname\>'
8345 sandbox let cfile = eval(substitute(&includeexpr, '\C\<v:fname\>', '\=string(cfile)', 'g'))
8348 elseif len(results) > 1
8349 let pre = '+' . join(map(results[1:-1], 'escape(v:val, " ")'), '\|') . ' '
8351 return pre . fnameescape(s:Generate(results[0]))
8354 " Section: Statusline
8356 function! fugitive#Statusline(...) abort
8357 let dir = s:Dir(bufnr(''))
8362 let commit = s:DirCommitFile(@%)[1]
8364 let status .= ':' . commit[0:6]
8366 let status .= '('.fugitive#Head(7, dir).')'
8367 return '[Git'.status.']'
8370 function! fugitive#statusline(...) abort
8371 return fugitive#Statusline()
8376 function! fugitive#Foldtext() abort
8377 if &foldmethod !=# 'syntax'
8381 let line_foldstart = getline(v:foldstart)
8382 if line_foldstart =~# '^diff '
8383 let [add, remove] = [-1, -1]
8385 for lnum in range(v:foldstart, v:foldend)
8386 let line = getline(lnum)
8387 if filename ==# '' && line =~# '^[+-]\{3\} "\=[abciow12]/'
8388 let filename = fugitive#Unquote(line[4:-1])[2:-1]
8392 elseif line =~# '^-'
8394 elseif line =~# '^Binary '
8399 let filename = fugitive#Unquote(matchstr(line_foldstart, '^diff .\{-\} \zs"\=[abciow12]/\zs.*\ze "\=[abciow12]/'))[2:-1]
8402 let filename = line_foldstart[5:-1]
8405 return 'Binary: '.filename
8407 return '+-' . v:folddashes . ' ' . (add<10&&remove<100?' ':'') . add . '+ ' . (remove<10&&add<100?' ':'') . remove . '- ' . filename
8409 elseif line_foldstart =~# '^@@\+ .* @@'
8410 return '+-' . v:folddashes . ' ' . line_foldstart
8411 elseif &filetype ==# 'fugitive' && line_foldstart =~# '^[A-Z][a-z].* (\d\+)$'
8412 let c = +matchstr(line_foldstart, '(\zs\d\+\ze)$')
8413 return '+-' . v:folddashes . printf('%3d item', c) . (c == 1 ? ': ' : 's: ') . matchstr(line_foldstart, '.*\ze (\d\+)$')
8414 elseif &filetype ==# 'gitcommit' && line_foldstart =~# '^# .*:$'
8415 let lines = getline(v:foldstart, v:foldend)
8416 call filter(lines, 'v:val =~# "^#\t"')
8417 call map(lines, "s:sub(v:val, '^#\t%(modified: +|renamed: +)=', '')")
8418 call map(lines, "s:sub(v:val, '^([[:alpha:] ]+): +(.*)', '\\2 (\\1)')")
8419 return line_foldstart.' '.join(lines, ', ')
8424 function! fugitive#foldtext() abort
8425 return fugitive#Foldtext()