From 81deb6333aeca6e4a266346b0f02945f95dad4d5 Mon Sep 17 00:00:00 2001 From: Tim Pope Date: Sat, 28 Jul 2018 21:08:56 -0400 Subject: [PATCH] Improve worktree support --- autoload/fugitive.vim | 124 +++++++++++++++++++++++++++++++----------- plugin/fugitive.vim | 82 +++++++++++++++------------- 2 files changed, 138 insertions(+), 68 deletions(-) diff --git a/autoload/fugitive.vim b/autoload/fugitive.vim index e9061a4..7f10078 100644 --- a/autoload/fugitive.vim +++ b/autoload/fugitive.vim @@ -24,6 +24,21 @@ function! s:gsub(str,pat,rep) abort return substitute(a:str,'\v\C'.a:pat,a:rep,'g') endfunction +function! s:Uniq(list) abort + let i = 0 + let seen = {} + while i < len(a:list) + let str = string(a:list[i]) + if has_key(seen, str) + call remove(a:list, i) + else + let seen[str] = 1 + let i += 1 + endif + endwhile + return a:list +endfunction + function! s:winshell() abort return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash endfunction @@ -82,6 +97,15 @@ function! s:PlatformSlash(path) abort endif endfunction +function! s:cpath(path, ...) abort + if exists('+fileignorecase') && &fileignorecase + let path = s:PlatformSlash(tolower(a:path)) + else + let path = s:PlatformSlash(a:path) + endif + return a:0 ? path ==# s:cpath(a:1) : path +endfunction + let s:executables = {} function! s:executable(binary) abort @@ -119,6 +143,28 @@ function! fugitive#GitVersion(...) abort return s:git_versions[g:fugitive_git_executable] endfunction +let s:commondirs = {} +function! fugitive#CommonDir(dir) abort + if empty(a:dir) + return '' + endif + if !has_key(s:commondirs, a:dir) + if getfsize(a:dir . '/HEAD') < 10 + let s:commondirs[a:dir] = '' + elseif filereadable(a:dir . '/commondir') + let dir = get(readfile(a:dir . '/commondir', 1), 0, '') + if dir =~# '^/\|^\a:/' + let s:commondirs[a:dir] = dir + else + let s:commondirs[a:dir] = simplify(a:dir . '/' . dir) + endif + else + let s:commondirs[a:dir] = a:dir + endif + endif + return s:commondirs[a:dir] +endfunction + function! s:Tree(...) abort return FugitiveTreeForGitDir(a:0 ? a:1 : get(b:, 'git_dir', '')) endfunction @@ -292,7 +338,13 @@ function! s:repo_translate(spec, ...) dict abort elseif rev =~# '^/\=\.git$' && empty(tree) let f = dir elseif rev =~# '^/\=\.git/' - let f = dir . s:sub(rev, '^/=\.git', '') + let f = s:sub(rev, '^/=\.git', '') + let cdir = fugitive#CommonDir(dir) + if cdir !=# dir && (f =~# '^/\%(config\|info\|hooks\|objects\|refs\|worktrees\)' || !filereadable(f) && filereadable(cdir . f)) + let f = cdir . f + else + let f = dir . f + endif elseif empty(rev) || rev ==# '/.' return self.tree() elseif rev =~# '^\.\=/' @@ -309,14 +361,9 @@ function! s:repo_translate(spec, ...) dict abort let f = 'fugitive://' . dir . '//0/' . rev[1:-1] else if rev =~# 'HEAD\|^refs/' && rev !~# ':' - let refs = dir . '/refs/' - if filereadable(dir . '/commondir') - let refs = simplify(dir . '/' . get(readfile(dir . '/commondir', 1), 0, '')) . '/refs/' - endif - if filereadable(refs . '../' . rev) - let f = simplify(refs . '../' . rev) - elseif filereadable(refs . rev) - let f = refs . rev + let cdir = rev =~# '^refs/' ? fugitive#CommonDir(dir) : dir + if filereadable(cdir . '/' . rev) + let f = simplify(cdir . '/' . rev) endif endif if !exists('f') @@ -811,14 +858,6 @@ function! s:buffer_commit() dict abort return matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs\w*') endfunction -function! s:cpath(path) abort - if exists('+fileignorecase') && &fileignorecase - return s:PlatformSlash(tolower(a:path)) - else - return s:PlatformSlash(a:path) - endif -endfunction - function! s:buffer_relative(...) dict abort let rev = matchstr(self.spec(),'^fugitive:\%(//\)\=.\{-\}\%(//\|::\)\zs.*') if rev != '' @@ -884,27 +923,49 @@ call s:add_methods('buffer',['getvar','getline','repo','type','spec','name','com " Section: Completion +function! s:GlobComplete(lead, pattern) abort + if v:version >= 704 + let results = glob(a:lead . a:pattern, 0, 1) + else + let results = split(glob(a:lead . a:pattern), "\n") + endif + call map(results, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val') + call map(results, 'v:val[ strlen(a:lead) : -1 ]') + return results +endfunction + function! fugitive#PathComplete(base, ...) abort - let tree = FugitiveWorkTree(a:0 == 1 ? a:1 : get(b:, 'git_dir', '')) . '/' - let strip = '^:\=/' + let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '') + let tree = FugitiveTreeForGitDir(dir) . '/' + let strip = '^:\=/\%(\./\)\=' let base = substitute(a:base, strip, '', '') - let matches = split(glob(tree . s:gsub(base, '/', '*&').'*'), "\n") - call map(matches, 's:shellslash(v:val)') - call map(matches, 'v:val !~# "/$" && isdirectory(v:val) ? v:val."/" : v:val') - call map(matches, 'matchstr(a:base, strip) . v:val[ strlen(tree) : -1 ]') - call map(matches, 's:fnameescape(v:val)') + if base =~# '^\.git/' + let pattern = s:gsub(base[5:-1], '/', '*&').'*' + let matches = s:GlobComplete(dir . '/', pattern) + let cdir = fugitive#CommonDir(dir) + if len(cdir) && s:cpath(dir) !=# s:cpath(cdir) + call extend(matches, s:GlobComplete(cdir . '/', pattern)) + endif + call s:Uniq(matches) + call map(matches, "'.git/' . v:val") + else + let matches = s:GlobComplete(tree, s:gsub(base, '/', '*&').'*') + endif + call map(matches, 's:fnameescape(s:shellslash(matchstr(a:base, strip) . v:val))') return matches endfunction function! fugitive#Complete(base, ...) abort let dir = a:0 == 1 ? a:1 : get(b:, 'git_dir', '') - let tree = FugitiveWorkTree(a:0 == 1 ? a:1 : get(b:, 'git_dir', '')) . '/' + let tree = s:Tree(dir) . '/' if a:base =~# '^\.\=/' || a:base !~# ':' let results = [] - if a:base !~# '^\.\=/' - let heads = ["HEAD","ORIG_HEAD","FETCH_HEAD","MERGE_HEAD"] + if a:base =~# '^refs/' + let results += map(s:GlobComplete(fugitive#CommonDir(dir) . '/', a:base . '*'), 's:shellslash(v:val)') + elseif a:base !~# '^\.\=/' + let heads = ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD', 'refs/'] let heads += sort(split(s:TreeChomp(["rev-parse","--symbolic","--branches","--tags","--remotes"], dir),"\n")) - if filereadable(dir . '/refs/stash') + if filereadable(fugitive#CommonDir(dir) . '/refs/stash') let heads += ["stash"] let heads += sort(split(s:TreeChomp(["stash","list","--pretty=format:%gd"], dir),"\n")) endif @@ -912,7 +973,7 @@ function! fugitive#Complete(base, ...) abort let results += heads endif call map(results, 's:fnameescape(v:val)') - if !empty(s:Tree(dir)) + if !empty(tree) let results += fugitive#PathComplete(a:base, dir) endif return results @@ -2915,8 +2976,9 @@ function! s:Browse(bang,line1,count,...) abort else let expanded = s:Expand(rev) endif + let cdir = fugitive#CommonDir(b:git_dir) for dir in ['tags/', 'heads/', 'remotes/'] - if filereadable(b:git_dir . '/refs/' . dir . expanded) + if filereadable(cdir . '/refs/' . dir . expanded) let expanded = '/.git/refs/' . dir . expanded endif endfor @@ -3003,7 +3065,7 @@ function! s:Browse(bang,line1,count,...) abort let commit = readfile(b:git_dir . '/HEAD', '', 1)[0] let i = 0 while commit =~# '^ref: ' && i < 10 - let commit = readfile(b:git_dir . '/' . commit[5:-1], '', 1)[0] + let commit = readfile(cdir . '/' . commit[5:-1], '', 1)[0] let i -= 1 endwhile endif diff --git a/plugin/fugitive.vim b/plugin/fugitive.vim index 492dc72..d581674 100644 --- a/plugin/fugitive.vim +++ b/plugin/fugitive.vim @@ -28,45 +28,16 @@ function! FugitiveGitDir(...) abort endif endfunction -function! FugitiveIsGitDir(path) abort - let path = substitute(a:path, '[\/]$', '', '') . '/' - return getfsize(path.'HEAD') > 10 && ( - \ isdirectory(path.'objects') && isdirectory(path.'refs') || - \ getftype(path.'commondir') ==# 'file') +function! FugitiveCommonDir(...) abort + let dir = FugitiveGitDir(a:0 ? a:1 : -1) + if empty(dir) + return '' + endif + return fugitive#CommonDir(dir) endfunction -let s:worktree_for_dir = {} -let s:dir_for_worktree = {} function! FugitiveWorkTree(...) abort - let dir = substitute(s:shellslash(a:0 ? a:1 : get(b:, 'git_dir', '')), '/$', '', '') - if dir =~# '/\.git$' - return len(dir) ==# 5 ? '/' : dir[0:-6] - endif - if !has_key(s:worktree_for_dir, dir) - let s:worktree_for_dir[dir] = '' - let config_file = dir . '/config' - if filereadable(config_file) - let config = readfile(config_file,'',10) - call filter(config,'v:val =~# "^\\s*worktree *="') - if len(config) == 1 - let worktree = matchstr(config[0], '= *\zs.*') - endif - elseif filereadable(dir . '/gitdir') - let worktree = fnamemodify(readfile(dir . '/gitdir')[0], ':h') - if worktree ==# '.' - unlet! worktree - endif - endif - if exists('worktree') - let s:worktree_for_dir[dir] = worktree - let s:dir_for_worktree[s:worktree_for_dir[dir]] = dir - endif - endif - if s:worktree_for_dir[dir] =~# '^\.' - return simplify(dir . '/' . s:worktree_for_dir[dir]) - else - return s:worktree_for_dir[dir] - endif + return FugitiveTreeForGitDir(FugitiveGitDir(a:0 ? a:1 : -1)) endfunction function! FugitiveReal(...) abort @@ -132,8 +103,45 @@ function! FugitiveStatusline(...) abort return fugitive#Statusline() endfunction +function! FugitiveIsGitDir(path) abort + let path = substitute(a:path, '[\/]$', '', '') . '/' + return getfsize(path.'HEAD') > 10 && ( + \ isdirectory(path.'objects') && isdirectory(path.'refs') || + \ getftype(path.'commondir') ==# 'file') +endfunction + +let s:worktree_for_dir = {} +let s:dir_for_worktree = {} function! FugitiveTreeForGitDir(path) abort - return FugitiveWorkTree(a:path) + let dir = a:path + if dir =~# '/\.git$' + return len(dir) ==# 5 ? '/' : dir[0:-6] + endif + if !has_key(s:worktree_for_dir, dir) + let s:worktree_for_dir[dir] = '' + let config_file = dir . '/config' + if filereadable(config_file) + let config = readfile(config_file,'',10) + call filter(config,'v:val =~# "^\\s*worktree *="') + if len(config) == 1 + let worktree = matchstr(config[0], '= *\zs.*') + endif + elseif filereadable(dir . '/gitdir') + let worktree = fnamemodify(readfile(dir . '/gitdir')[0], ':h') + if worktree ==# '.' + unlet! worktree + endif + endif + if exists('worktree') + let s:worktree_for_dir[dir] = worktree + let s:dir_for_worktree[s:worktree_for_dir[dir]] = dir + endif + endif + if s:worktree_for_dir[dir] =~# '^\.' + return simplify(dir . '/' . s:worktree_for_dir[dir]) + else + return s:worktree_for_dir[dir] + endif endfunction function! FugitiveExtractGitDir(path) abort