commit fd83fcaf90023f2bd4b0193eee237b117b37c2be (tree)
parent 1e3786734bbb0c3a9783ae33f294d5658262af3d
Author: Tim Pope <code@tpope.net>
Date: Tue, 28 Aug 2018 00:35:06 -0400
Support expansion flags
Diffstat:
2 files changed, 69 insertions(+), 27 deletions(-)
diff --git a/autoload/fugitive.vim b/autoload/fugitive.vim
@@ -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
- 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
+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
- return a:path[2:-1]
+ return a:path
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 = {}
diff --git a/doc/fugitive.txt b/doc/fugitive.txt
@@ -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*