Support expansion flags

This commit is contained in:
Tim Pope 2018-08-28 00:35:06 -04:00
parent 1e3786734b
commit fd83fcaf90
2 changed files with 69 additions and 27 deletions

View File

@ -53,11 +53,12 @@ function! s:shellesc(arg) abort
endif
endfunction
let s:fnameescape = " \t\n*?[{`$\\%#'\"|!<"
function! s:fnameescape(file) abort
if exists('*fnameescape')
return fnameescape(a:file)
else
return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
return escape(a:file, s:fnameescape)
endif
endfunction
@ -641,18 +642,13 @@ function! s:Generate(rev, ...) abort
return fugitive#Route(object, dir)
endfunction
function! s:RemoveDot(path, ...) abort
if a:path !~# '^\./'
return a:path
function! s:DotRelative(path) abort
let cwd = getcwd()
let path = substitute(a:path, '^[~$]\i*', '\=expand(submatch(0))', '')
if s:cpath(cwd . '/', (path . '/')[0 : len(cwd)])
return '.' . strpart(path, len(cwd))
endif
let dir = a:0 ? a:1 : get(b:, 'git_dir', '')
let cdir = fugitive#CommonDir(dir)
if len(filter(['', '/tags', '/heads', '/remotes'], 'getftime(cdir . "/refs" . v:val . a:path[1:-1]) >= 0')) ||
\ a:path =~# 'HEAD$' && filereadable(dir . a:path[1:-1]) ||
\ a:path =~# '^\./refs/' && filereadable(cdir . a:path[1:-1])
return a:path
endif
return a:path[2:-1]
endfunction
function! fugitive#Object(...) abort
@ -681,6 +677,44 @@ function! fugitive#Object(...) abort
endif
endfunction
let s:var = '\%(%\|#<\=\d\+\|##\=\)'
let s:flag = '\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)'
let s:expand = '\%(\(' . s:var . '\)\(' . s:flag . '*\)\(:S\)\=\)'
function! s:BufName(var) abort
if a:var ==# '%'
return bufname(get(b:, 'fugitive_blamed_bufnr', ''))
elseif a:var =~# '^#\d*$'
let nr = getbufvar(+a:var[1:-1], 'fugitive_blamed_bufnr', '')
return bufname(nr ? nr : +a:var[1:-1])
else
return expand(a:var)
endif
endfunction
function! s:ExpandVar(other, var, flags, esc) abort
if a:other =~# '^\'
return a:other[1:-1]
elseif a:other =~# '^!'
let buffer = s:BufName(len(a:other) > 1 ? '#'. a:other[1:-1] : '%')
let owner = s:Owner(buffer)
return len(owner) ? owner : '@'
endif
let flags = a:flags
let file = s:DotRelative(fugitive#Real(s:BufName(a:var)))
while len(flags)
let flag = matchstr(flags, s:flag)
let flags = strpart(flags, len(flag))
if flag ==# ':.'
let file = s:DotRelative(file)
else
let file = fnamemodify(file, flag)
endif
endwhile
let file = s:Slash(file)
return (len(a:esc) ? shellescape(file) : file)
endfunction
function! s:Expand(rev) abort
if a:rev =~# '^:[0-3]$'
let file = a:rev . s:Relative(':')
@ -688,19 +722,26 @@ function! s:Expand(rev) abort
let file = 'HEAD^{}' . a:rev[1:-1] . s:Relative(':')
elseif a:rev =~# '^@{'
let file = 'HEAD' . a:rev. s:Relative(':')
elseif a:rev =~# '^[~^]/\@!'
elseif a:rev =~# '^\^[0-9~^{]\|^\~[0-9~^]'
let commit = substitute(s:DirCommitFile(@%)[1], '^\d\=$', 'HEAD', '')
let file = commit . a:rev . s:Relative(':')
else
let file = a:rev
endif
return s:sub(substitute(file,
\ '\([%#]\)$\|\\\([[:punct:]]\)','\=len(submatch(2)) ? submatch(2) : fugitive#Path(expand(submatch(1)))','g'),
\ '\.\@<=/$','')
return substitute(file,
\ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
\ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),"")', 'g')
endfunction
function! fugitive#Expand(object) abort
return substitute(a:object,
\ '\(\\[' . s:fnameescape . ']\|^\\[>+-]\|!\d*\)\|' . s:expand,
\ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
endfunction
function! s:ShellExpand(cmd) abort
return substitute(a:cmd, '\\\@<![%#]:\@!', '\=s:RemoveDot(fugitive#Path(expand(submatch(0)), "./"))', 'g')
return substitute(a:cmd, '\(\\[!#%]\|!\d*\)\|' . s:expand,
\ '\=s:ExpandVar(submatch(1),submatch(2),submatch(3),submatch(5))', 'g')
endfunction
let s:trees = {}

View File

@ -312,14 +312,14 @@ a Show the current tag, commit, or tree in an alternate
SPECIFYING OBJECTS *fugitive-object* *fugitive-revision*
Fugitive objects are either work tree files or Git revisions as defined in the
"SPECIFYING REVISIONS" section in the git-rev-parse man page, with a few
convenience notations thrown in for good measure. For commands that accept an
"SPECIFYING REVISIONS" section in the git-rev-parse man page, with expansions
inspired by |cmdline-special| layered on top. For commands that accept an
optional object, the default is the file in the index for work tree files and
the work tree file for everything else. Example objects follow.
Object Meaning ~
HEAD .git/HEAD
refs/heads/x .git/refs/heads/x
refs/heads/x .git/refs/heads/x (in "common dir" if present)
@ The commit referenced by @ aka HEAD
master^ The parent of the commit referenced by master
master: The tree referenced by master
@ -327,15 +327,16 @@ master: The tree referenced by master
Makefile The file named Makefile in the work tree
@^:Makefile The file named Makefile in the parent of HEAD
:Makefile The file named Makefile in the index (writable)
@:% The current file in HEAD
- The current file in HEAD
-^ The current file in the previous commit
-~3 The current file 3 commits ago
: .git/index (Same as |:Gstatus|)
@~2:% The current file in the grandparent of HEAD
:% The current file in the index
:1:% The current file's common ancestor during a conflict
:2:% The current file in the target branch during a conflict
:3:% The current file in the merged branch during a conflict
:2:# The alternate file in the target branch during a conflict
:3:#5 The file from buffer #5 in the merged branch during a conflict
! The commit owning the current file
!:Makefile The file named Makefile in the commit owning the current file
!3^2 The second parent of the commit owning buffer #3
.git/config The repo config file
: Same as |:Gstatus|
STATUSLINE *fugitive-statusline*