commit d3468dd09be59ac726c112b7a52ce80a27005e69 (tree)
parent d06080ae3526ba4e6e008ed56188745f22442d12
Author: Tim Pope <code@tpope.net>
Date: Tue, 25 Dec 2018 14:07:17 -0500
Prototype new :Gstatus
Diffstat:
3 files changed, 282 insertions(+), 97 deletions(-)
diff --git a/autoload/fugitive.vim b/autoload/fugitive.vim
@@ -1368,43 +1368,158 @@ function! s:ReplaceCmd(cmd, ...) abort
endtry
endfunction
+function! s:QueryLog(refspec) abort
+ let lines = split(system(FugitivePrepare('log', '-n', '256', '--format=%h%x09%s', a:refspec, '--')), "\n")
+ call map(lines, 'split(v:val, "\t")')
+ call map(lines, '{"type": "Log", "commit": v:val[0], "subject": v:val[-1]}')
+ return lines
+endfunction
+
+function! s:FormatLog(dict) abort
+ return a:dict.commit . ' ' . a:dict.subject
+endfunction
+
+function! s:FormatFile(dict) abort
+ return a:dict.status . ' ' . a:dict.filename
+endfunction
+
+function! s:Format(val) abort
+ if type(a:val) == type({})
+ return s:Format{a:val.type}(a:val)
+ elseif type(a:val) == type([])
+ return map(copy(a:val), 's:Format(v:val)')
+ else
+ return '' . a:val
+ endif
+endfunction
+
+function! s:AddHeader(key, value) abort
+ if empty(a:value)
+ return
+ endif
+ let before = 1
+ while !empty(getline(before))
+ let before += 1
+ endwhile
+ call append(before - 1, [a:key . ':' . (len(a:value) ? ' ' . a:value : '')])
+ if before == 1 && line('$') == 2
+ silent 2delete _
+ endif
+endfunction
+
+function! s:AddSection(label, lines, ...) abort
+ let note = a:0 ? a:1 : ''
+ if empty(a:lines) && empty(note)
+ return
+ endif
+ call append(line('$'), ['', a:label . (len(note) ? ': ' . note : ' (' . len(a:lines) . ')')] + s:Format(a:lines))
+endfunction
+
function! fugitive#BufReadStatus() abort
let amatch = s:Slash(expand('%:p'))
- if !exists('b:fugitive_display_format')
- let b:fugitive_display_format = filereadable(expand('%').'.lock')
- endif
- let b:fugitive_display_format = b:fugitive_display_format % 2
let b:fugitive_type = 'index'
try
silent doautocmd BufReadPre
let cmd = [fnamemodify(amatch, ':h')]
- setlocal noro ma nomodeline
+ setlocal noro ma nomodeline buftype=nowrite
if s:cpath(fnamemodify($GIT_INDEX_FILE !=# '' ? $GIT_INDEX_FILE : b:git_dir . '/index', ':p')) !=# s:cpath(amatch)
let cmd += ['-c', 'GIT_INDEX_FILE=' . amatch]
endif
- if b:fugitive_display_format
- let cmd += ['ls-files', '--stage']
+ let cmd += ['status', '--porcelain', '-bz']
+ let output = split(system(fugitive#Prepare(cmd)), "\1")
+ if v:shell_error
+ throw 'fugitive: ' . join(output, ' ')
+ endif
+
+ let head = matchstr(output[0], '^## \zs\S\+\ze\%($\| \[\)')
+ let pull = ''
+ if head =~# '\.\.\.'
+ let [head, pull] = split(head, '\.\.\.')
+ let branch = head
+ elseif head ==# 'HEAD' || empty(head)
+ let head = FugitiveHead(11)
+ let branch = ''
else
- let cmd += [
- \ '-c', 'status.displayCommentPrefix=true',
- \ '-c', 'color.status=false',
- \ '-c', 'status.short=false',
- \ 'status']
+ let branch = head
endif
- call s:ReplaceCmd(call('fugitive#Prepare', cmd), 1)
- silent doautocmd BufReadPost
- if b:fugitive_display_format
- if &filetype !=# 'git'
- set filetype=git
+
+ let [staged, unstaged, untracked] = [[], [], []]
+ let i = 0
+ while i < len(output)
+ let line = output[i]
+ let file = line[3:-1]
+ let files = file
+ let i += 1
+ if line[0:1] =~# '[RC]'
+ let files = output[i] . ' -> ' . file
+ let i += 1
endif
- set nospell
- else
- if &filetype !=# 'gitcommit'
- set filetype=gitcommit
+ if line[0] ==# '?'
+ call add(untracked, {'type': 'File', 'status': '?', 'filename': file})
+ continue
+ endif
+ if line[0] !~# '[ ?!#]'
+ call add(staged, {'type': 'File', 'status': line[0], 'filename': (line[0] =~# '[RC]' ? files : file)})
+ endif
+ if line[1] !~# '[ ?!#]'
+ call add(unstaged, {'type': 'File', 'status': line[1], 'filename': (line[1] =~# '[RC]' ? files : file)})
endif
- set foldtext=fugitive#Foldtext()
+ endwhile
+
+ let config = fugitive#Config()
+
+ if len(pull)
+ let rebase = fugitive#Config('branch.' . branch . '.rebase', config)
+ if empty(rebase)
+ let rebase = fugitive#Config('pull.rebase', config)
+ endif
+ if rebase =~# '^\%(true\|yes\|on\|1\|interactive\)$'
+ let pull_type = 'Rebase'
+ elseif rebase =~# '^\%(false\|no|off\|0\|\)$'
+ let pull_type = 'Merge'
+ else
+ let pull_type = 'Upstream'
+ endif
+ endif
+
+ let push_remote = fugitive#Config('branch.' . branch . '.pushRemote', config)
+ if empty(push_remote)
+ let push_remote = fugitive#Config('remote.pushDefault', config)
endif
- setlocal readonly nomodifiable nomodified noswapfile
+ let push = len(push_remote) && len(branch) ? push_remote . '/' . branch : ''
+ if empty(push)
+ let push = pull
+ endif
+
+ if len(pull)
+ let unpulled = s:QueryLog(head . '..' . pull)
+ else
+ let unpulled = []
+ endif
+ if len(push)
+ let unpushed = s:QueryLog(push . '..' . head)
+ else
+ let unpushed = []
+ endif
+
+ silent keepjumps %delete_
+
+ call s:AddHeader('Head', head)
+ call s:AddHeader(pull_type, pull)
+ if push !=# pull
+ call s:AddHeader('Push', push)
+ endif
+ call s:AddSection('Untracked', untracked)
+ call s:AddSection('Unstaged', unstaged)
+ call s:AddSection('Staged', staged)
+ call s:AddSection('Unpushed to ' . push, unpushed)
+ call s:AddSection('Unpulled from ' . pull, unpulled)
+
+ set nomodified readonly noswapfile
+ silent doautocmd BufReadPost
+ set foldtext=fugitive#Foldtext()
+ set filetype=fugitive
+ setlocal nomodifiable
if &bufhidden ==# ''
setlocal bufhidden=delete
endif
@@ -1420,8 +1535,6 @@ function! fugitive#BufReadStatus() abort
exe "xnoremap <buffer> <silent>" nowait "s :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
exe "nnoremap <buffer> <silent>" nowait "u :<C-U>silent execute <SID>StageToggle(line('.'),line('.')+v:count1-1)<CR>"
exe "xnoremap <buffer> <silent>" nowait "u :<C-U>silent execute <SID>StageToggle(line(\"'<\"),line(\"'>\"))<CR>"
- nnoremap <buffer> <silent> a :<C-U>let b:fugitive_display_format += 1<Bar>exe fugitive#BufReadStatus()<CR>
- nnoremap <buffer> <silent> i :<C-U>let b:fugitive_display_format -= 1<Bar>exe fugitive#BufReadStatus()<CR>
nnoremap <buffer> <silent> C :<C-U>Gcommit<CR>:echohl WarningMsg<Bar>echo ':Gstatus C is deprecated in favor of cc'<Bar>echohl NONE<CR>
nnoremap <buffer> <silent> ca :<C-U>Gcommit --amend<CR>
nnoremap <buffer> <silent> cc :<C-U>Gcommit<CR>
@@ -1772,7 +1885,7 @@ function! s:Status(bang, count, mods) abort
return ''
endfunction
-function! fugitive#ReloadStatus() abort
+function! fugitive#ReloadStatus(...) abort
if exists('s:reloading_status')
return
endif
@@ -1789,7 +1902,9 @@ function! fugitive#ReloadStatus() abort
endif
try
if !&modified
+ let pos = getpos('.')
call fugitive#BufReadStatus()
+ call setpos('.', pos)
endif
finally
if exists('restorewinnr')
@@ -1805,39 +1920,30 @@ function! fugitive#ReloadStatus() abort
endtry
endfunction
-function! fugitive#reload_status() abort
- return fugitive#ReloadStatus()
+function! s:StageInfo(...) abort
+ let lnum = a:0 ? a:1 : line('.')
+ let slnum = lnum
+ let section = ''
+ while len(getline(slnum - 1)) && empty(section)
+ let slnum -= 1
+ let section = matchstr(getline(slnum), '^\u\l\+\ze.* (\d\+)$')
+ endwhile
+ return {'section': section,
+ \ 'heading': getline(slnum),
+ \ 'filename': matchstr(getline(lnum), '^[A-Z?] \zs.*'),
+ \ 'commit': matchstr(getline(lnum), '^[0-9a-f]\{4,\}\ze '),
+ \ 'status': matchstr(getline(lnum), '^[A-Z?]\ze '),
+ \ 'index': lnum - slnum}
endfunction
function! s:StageFileSection(lnum) abort
- let filename = matchstr(getline(a:lnum),'^.\=\t\zs.\{-\}\ze\%( ([^()[:digit:]]\+)\)\=$')
- let lnum = a:lnum
- if has('multi_byte_encoding')
- let colon = '\%(:\|\%uff1a\)'
- else
- let colon = ':'
- endif
- while lnum && getline(lnum) !~# colon.'$'
- let lnum -= 1
- endwhile
- if !lnum
- return ['', '']
- elseif (getline(lnum+1) =~# '^.\= .*\<git \%(reset\|rm --cached\) ' && getline(lnum+2) ==# '#') || getline(lnum) =~# '^\%(. \)\=Changes to be committed:$'
- return [matchstr(filename, colon.' *\zs.*'), 'staged']
- elseif (getline(lnum+1) =~# '^.\= .*\<git add ' && getline(lnum+2) ==# '#' && getline(lnum+3) !~# colon.' ') || getline(lnum) =~# '^\(. \)\=Untracked files:$'
- return [filename, 'untracked']
- elseif getline(lnum+2) =~# '^.\= .*\<git checkout ' || getline(lnum) =~# '\%(. \)\=Changes not staged for commit:$'
- return [matchstr(filename, colon.' *\zs.*'), 'unstaged']
- elseif getline(lnum+2) =~# '^.\= .*\<git \%(add\|rm\)' || getline(lnum) =~# '\%(. \)\=Unmerged paths:$'
- return [matchstr(filename, colon.' *\zs.*'), 'unmerged']
- else
- return ['', 'unknown']
- endif
+ let info = s:StageInfo(a:lnum)
+ return [info.filename, info.section]
endfunction
function! s:StageNext(count) abort
for i in range(a:count)
- call search('^.\=\t.*','W')
+ call search('^[A-Z?] .\|^[0-9a-f]\{4,\} ','W')
endfor
return '.'
endfunction
@@ -1847,7 +1953,7 @@ function! s:StagePrevious(count) abort
return 'CtrlP '.fnameescape(s:Tree())
else
for i in range(a:count)
- call search('^.\=\t.*','Wbe')
+ call search('^[A-Z?] .\|^[0-9a-f]\{4,\} ','Wbe')
endfor
return '.'
endif
@@ -1865,35 +1971,45 @@ function! s:StageReloadSeek(target,lnum1,lnum2) abort
silent! edit!
1
redraw
- call search('^.\=\t\%([[:alpha:] ]\+: *\|.*\%uff1a *\)\=\V'.target[0].'\%( ([^()[:digit:]]\+)\)\=\$','W')
+ let lnum = 0
+ while lnum < line('$')
+ let lnum += 1
+ let file = getline(lnum)[2:-1]
+ if (target[0][-1:-1] ==# '/' && file[0 : len(target[0]) - 1] ==# target[0]) ||
+ \ (file[-1:-1] ==# '/' && file ==# target[0][0 : len(file) - 1]) ||
+ \ file ==# target[0]
+ exe lnum
+ return ''
+ endif
+ endwhile
endfunction
function! s:StageUndo() abort
- let [filename, section] = s:StageFileSection(line('.'))
- if empty(filename)
+ let info = s:StageInfo(line('.'))
+ if empty(info.filename)
return ''
endif
- let hash = s:TreeChomp('hash-object', '-w', './' . filename)
+ let hash = s:TreeChomp('hash-object', '-w', './' . info.filename)
if !empty(hash)
- if section ==# 'untracked'
- call s:TreeChomp('clean', '-f', './' . filename)
- elseif section ==# 'unmerged'
- call s:TreeChomp('rm', './' . filename)
- elseif section ==# 'unstaged'
- call s:TreeChomp('checkout', './' . filename)
+ if info.status ==# 'U'
+ call s:TreeChomp('rm', './' . info.filename)
+ elseif info.section ==# 'Untracked'
+ call s:TreeChomp('clean', '-f', './' . info.filename)
+ elseif info.section ==# 'Unstaged'
+ call s:TreeChomp('checkout', './' . info.filename)
else
- call s:TreeChomp('checkout', 'HEAD^{}', './' . filename)
+ call s:TreeChomp('checkout', 'HEAD^{}', './' . info.filename)
endif
- call s:StageReloadSeek([filename, ''], line('.'), line('.'))
+ call s:StageReloadSeek([info.filename, ''], line('.'), line('.'))
let @" = hash
return 'checktime|redraw|echomsg ' .
- \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.filename)
+ \ string('To restore, :Git cat-file blob '.hash[0:6].' > '.info.filename)
endif
endfunction
function! s:StageDiff(diff) abort
let [filename, section] = s:StageFileSection(line('.'))
- if filename ==# '' && section ==# 'staged'
+ if filename ==# '' && section ==# 'Staged'
return 'Git! diff --no-ext-diff --cached'
elseif filename ==# ''
return 'Git! diff --no-ext-diff'
@@ -1901,7 +2017,7 @@ function! s:StageDiff(diff) abort
let [old, new] = split(filename,' -> ')
execute 'Gedit '.s:fnameescape(':0:'.new)
return a:diff.' HEAD:'.s:fnameescape(old)
- elseif section ==# 'staged'
+ elseif section ==# 'Staged'
execute 'Gedit '.s:fnameescape(':0:'.filename)
return a:diff.' -'
else
@@ -1913,18 +2029,18 @@ endfunction
function! s:StageDiffEdit() abort
let [filename, section] = s:StageFileSection(line('.'))
let arg = (filename ==# '' ? '.' : filename)
- if section ==# 'staged'
+ if section ==# 'Staged'
return 'Git! diff --no-ext-diff --cached '.s:shellesc(arg)
- elseif section ==# 'untracked'
+ elseif section ==# 'Untracked'
call s:TreeChomp('add', '--intent-to-add', './' . arg)
if arg ==# '.'
silent! edit!
1
- if !search('^.*:\n.*\n.\= .*"git checkout \|^\%(# \)=Changes not staged for commit:$','W')
+ if !search('^Unstaged','W')
call search(':$','W')
endif
else
- call s:StageReloadSeek([filename, 'staged'], line('.'), line('.'))
+ call s:StageReloadSeek([filename, 'Staged'], line('.'), line('.'))
endif
return ''
else
@@ -1939,49 +2055,61 @@ function! s:StageToggle(lnum1,lnum2) abort
try
let output = ''
for lnum in range(a:lnum1,a:lnum2)
- let [filename, section] = s:StageFileSection(lnum)
- if getline('.') =~# ':$'
- if section ==# 'staged'
+ let info = s:StageInfo(lnum)
+ if empty(info.filename)
+ if info.section ==# 'Staged'
call s:TreeChomp('reset','-q')
silent! edit!
1
- if !search('^.*:\n.\= .*"git add .*\n#\n\|^\%(. \)\=Untracked files:$','W')
- call search(':$','W')
+ if !search('^Untracked','W')
+ call search('^Unstaged','W')
endif
return ''
- elseif section ==# 'unstaged'
+ elseif info.section ==# 'Unstaged'
call s:TreeChomp('add','-u')
silent! edit!
1
- if !search('^.*:\n\.\= .*"git add .*\n#\n\|^\%( \)=Untracked files:$','W')
- call search(':$','W')
+ if !search('^Untracked','W')
+ call search('^Staged','W')
endif
return ''
- else
+ elseif info.section ==# 'Unpushed' && len(info.commit)
+ let remote = matchstr(info.heading, 'to \zs[^/]\+\ze/')
+ if empty(remote)
+ let remote = '.'
+ endif
+ let branch = matchstr(info.heading, 'to \%([^/]\+/\)\=\zs\S\+')
+ call feedkeys(':Gpush ' . remote . ' ' . info.commit . ':' . branch)
+ return ''
+ elseif info.section ==# 'Unpulled'
+ call feedkeys(':Grebase ' . info.commit)
+ return ''
+ elseif info.section ==# 'Untracked'
call s:TreeChomp('add', '.')
silent! edit!
1
- call search(':$','W')
+ call search('^Unstaged\|^Staged','W')
return ''
endif
endif
- if filename ==# ''
+ let filename = info.filename
+ if empty(filename)
continue
endif
execute lnum
- if section ==# 'staged'
+ if info.section ==# 'Staged'
let files_to_unstage = split(filename, ' -> ')
let filename = files_to_unstage[-1]
let cmd = ['reset', '-q'] + map(copy(files_to_unstage), '"./" . v:val')
- elseif getline(lnum) =~# '^.\=\tdeleted:'
+ elseif getline(lnum) =~# '^D'
let cmd = ['rm', './' . filename]
- elseif getline(lnum) =~# '^.\=\tmodified:'
+ elseif getline(lnum) =~# '^M'
let cmd = ['add', './' . filename]
else
let cmd = ['add','-A', './' . filename]
endif
if !exists('target')
- let target = [filename, section ==# 'staged' ? '' : 'staged']
+ let target = [filename, info.section ==# 'Staged' ? '' : 'Staged']
endif
let output .= call('s:TreeChomp', cmd)."\n"
endfor
@@ -2001,11 +2129,11 @@ function! s:StagePatch(lnum1,lnum2) abort
for lnum in range(a:lnum1,a:lnum2)
let [filename, section] = s:StageFileSection(lnum)
- if getline('.') =~# ':$' && section ==# 'staged'
+ if empty(filename) && section ==# 'Staged'
return 'Git reset --patch'
- elseif getline('.') =~# ':$' && section ==# 'unstaged'
+ elseif empty(filename) && section ==# 'Unstaged'
return 'Git add --patch'
- elseif getline('.') =~# ':$' && section ==# 'untracked'
+ elseif empty(filename) && section ==# 'Untracked'
return 'Git add -N .'
elseif filename ==# ''
continue
@@ -2016,7 +2144,7 @@ function! s:StagePatch(lnum1,lnum2) abort
execute lnum
if filename =~ ' -> '
let reset += [split(filename,' -> ')[1]]
- elseif section ==# 'staged'
+ elseif section ==# 'Staged'
let reset += [filename]
elseif getline(lnum) !~# '^.\=\tdeleted:'
let add += [filename]
@@ -2033,7 +2161,7 @@ function! s:StagePatch(lnum1,lnum2) abort
silent! edit!
1
redraw
- call search('^.\=\t\%([[:alpha:] ]\+: *\)\=\V'.first_filename.'\%( ([^()[:digit:]]\+)\)\=\$','W')
+ call search('^[A-Z?] \V'.first_filename.'\$','W')
endif
catch /^fugitive:/
return 'echoerr v:errmsg'
@@ -3531,7 +3659,11 @@ function! s:ContainingCommit() abort
endfunction
function! s:SquashArgument() abort
- return s:Owner(@%)
+ if &filetype == 'fugitive'
+ return matchstr(getline('.'), '^[0-9a-f]\{4,\}\ze ')
+ else
+ return s:Owner(@%)
+ endif
endfunction
function! s:NavigateUp(count) abort
@@ -3590,7 +3722,26 @@ function! fugitive#MapJumps(...) abort
endfunction
function! s:StatusCfile(...) abort
- let pre = ''
+ let tree = FugitiveTreeForGitDir(b:git_dir)
+ let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
+ let line = getline('.')
+ if line =~# '^\S '
+ return lead . line[2:-1]
+ elseif line =~# '^[0-9a-f]\{4,\}\s'
+ return matchstr(line, '^\S\+')
+ elseif line =~# '^\%(Head\|Merge\|Rebase\|Upstream\|Pull\|Push\): '
+ return matchstr(line, ' \zs.*')
+ else
+ return ''
+ endif
+endfunction
+
+function! fugitive#StatusCfile() abort
+ let file = s:Generate(s:StatusCfile())
+ return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
+endfunction
+
+function! s:MessageCfile(...) abort
let tree = FugitiveTreeForGitDir(b:git_dir)
let lead = s:cpath(tree, getcwd()) ? './' : tree . '/'
if getline('.') =~# '^.\=\trenamed:.* -> '
@@ -3612,8 +3763,8 @@ function! s:StatusCfile(...) abort
endif
endfunction
-function! fugitive#StatusCfile() abort
- let file = s:Generate(s:StatusCfile())
+function! fugitive#MessageCfile() abort
+ let file = s:Generate(s:MessageCfile())
return empty(file) ? fugitive#Cfile() : s:fnameescape(file)
endfunction
@@ -3773,7 +3924,7 @@ endfunction
function! s:GF(mode) abort
try
- let results = &filetype ==# 'gitcommit' ? [s:StatusCfile()] : s:cfile()
+ let results = &filetype ==# 'fugitive' ? [s:StatusCfile()] : &filetype ==# 'gitcommit' ? s:MessageCfile() : s:cfile()
catch /^fugitive:/
return 'echoerr v:errmsg'
endtry
diff --git a/plugin/fugitive.vim b/plugin/fugitive.vim
@@ -257,6 +257,10 @@ augroup fugitive
\ endif
autocmd FileType gitcommit
\ if exists('b:git_dir') |
+ \ call fugitive#MapCfile('fugitive#MessageCfile()') |
+ \ endif
+ autocmd FileType fugitive
+ \ if exists('b:git_dir') |
\ call fugitive#MapCfile('fugitive#StatusCfile()') |
\ endif
autocmd FileType gitrebase
diff --git a/syntax/fugitive.vim b/syntax/fugitive.vim
@@ -0,0 +1,30 @@
+if exists("b:current_syntax")
+ finish
+endif
+
+syn sync fromstart
+syn spell notoplevel
+
+syn include @fugitiveDiff syntax/diff.vim
+
+syn match fugitiveHeader /^[A-Z][a-z][^:]*:/ nextgroup=fugitiveHash,fugitiveSymbolicRef skipwhite
+
+syn region fugitiveSection start=/^\%(.*(\d\+)$\)\@=/ contains=fugitiveHeading end=/^$\@=/
+syn match fugitiveHeading /^[A-Z][a-z][^:]*\ze (\d\+)$/ contains=fugitivePreposition contained nextgroup=fugitiveCount skipwhite
+syn match fugitiveCount /(\d\+)/hs=s+1,he=e-1 contained
+syn match fugitivePreposition /\<\%([io]nto\|from\|to\)\>/ transparent contained nextgroup=fugitiveHash,fugitiveSymbolicRef skipwhite
+
+syn match fugitiveModifier /^[MADRCU?]\{1,2} / contained containedin=fugitiveSection
+syn match FugitiveSymbolicRef /\.\@!\%(\.\.\@!\|[^[:space:][:cntrl:]\:.]\)\+\.\@<!/ contained
+syn match fugitiveHash /^\x\{4,\}\>/ contained containedin=fugitiveSection
+
+syn region fugitiveHunk start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) \|@@\|$\)\@=/ contains=@fugitiveDiff containedin=fugitiveSection fold
+
+hi link fugitiveModifier Type
+hi link fugitiveHeader Label
+hi link fugitiveHeading PreProc
+hi link fugitiveHash Identifier
+hi link fugitiveSymbolicRef Function
+hi link fugitiveCount Number
+
+let b:current_syntax = "fugitive"