Better support for ssh config

* Respect User and Port in addition to Hostname.
* Support globs and negations in addition to literal host matching.
* Parse global config file in addition to user config file.
* Handle Include declarations.

References: https://github.com/tpope/vim-rhubarb/issues/65
This commit is contained in:
Tim Pope
2021-07-17 15:55:57 -04:00
parent 58516a13c6
commit de6495ae84

View File

@@ -711,32 +711,83 @@ function! s:Remote(dir) abort
return remote =~# '^\.\=$' ? 'origin' : remote return remote =~# '^\.\=$' ? 'origin' : remote
endfunction endfunction
unlet! s:ssh_aliases function! s:SshParseHost(value) abort
function! fugitive#SshHostAlias(...) abort let patterns = []
if !exists('s:ssh_aliases') let negates = []
let s:ssh_aliases = {} for host in split(a:value, '\s\+')
if filereadable(expand('~/.ssh/config')) let pattern = substitute(host, '[\\^$.*~?]', '\=submatch(0) == "*" ? ".*" : submatch(0) == "?" ? "." : "\\" . submatch(0)', 'g')
let hosts = [] if pattern[0] ==# '!'
for line in readfile(expand('~/.ssh/config')) call add(negates, '\&\%(^' . pattern[1 : -1] . '$\)\@!')
let key = matchstr(line, '^\s*\zs\w\+\ze\s') else
let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S') call add(patterns, pattern)
if key ==? 'host'
let hosts = split(value, '\s\+')
elseif key ==? 'hostname'
for host in hosts
if !has_key(s:ssh_aliases, host)
let s:ssh_aliases[host] = tolower(value)
endif
endfor
endif
endfor
endif endif
endfor
return '^\%(' . join(patterns, '\|') . '\)$' . join(negates, '')
endfunction
function! s:SshParseConfig(into, root, file, ...) abort
if !filereadable(a:file)
return a:into
endif endif
if a:0 let host = a:0 ? a:1 : '^\%(.*\)$'
return get(s:ssh_aliases, a:1, a:1) for line in readfile(a:file)
else let key = tolower(matchstr(line, '^\s*\zs\w\+\ze\s'))
return s:ssh_aliases let value = matchstr(line, '^\s*\w\+\s\+\zs.*\S')
if key ==# 'match'
let host = value ==# 'all' ? '^\%(.*\)$' : ''
elseif key ==# 'host'
let host = s:SshParseHost(value)
elseif key ==# 'include'
call s:SshParseInclude(a:into, a:root, host, value)
elseif len(key) && len(host)
call extend(a:into, {key: []}, 'keep')
call add(a:into[key], [host, value])
endif
endfor
return a:into
endfunction
function! s:SshParseInclude(into, root, host, value) abort
for glob in split(a:value)
if glob !~# '^/'
let glob = a:root . glob
endif
for file in split(glob(glob), "\n")
call s:SshParseConfig(a:into, a:root, file, a:host)
endfor
endfor
endfunction
unlet! s:ssh_config
function! fugitive#SshConfig(host, ...) abort
if !exists('s:ssh_config')
let s:ssh_config = {}
for file in [expand("~/.ssh/config"), "/etc/ssh/ssh_config"]
call s:SshParseConfig(s:ssh_config, substitute(file, '\w*$', '', ''), file)
endfor
endif endif
let host_config = {}
for key in a:0 ? a:1 : keys(s:ssh_config)
for [host_pattern, value] in get(s:ssh_config, key, [])
if a:host =~# host_pattern
let host_config[key] = value
break
endif
endfor
endfor
return host_config
endfunction
function! fugitive#SshHostAlias(authority) abort
let [_, user, host, port; __] = matchlist(a:authority, '^\%(\([^/@]\+\)@\)\=\(.\{-\}\)\%(:\(\d\+\)\)\=$')
let c = fugitive#SshConfig(host, ['user', 'hostname', 'port'])
if empty(user)
let user = get(c, 'user', '')
endif
if empty(port)
let port = get(c, 'port', '')
endif
return (len(user) ? user . '@' : '') . get(c, 'hostname', host) . (port =~# '^\%(22\)\=$' ? '' : ':' . port)
endfunction endfunction
let s:redirects = {} let s:redirects = {}
@@ -752,10 +803,23 @@ function! fugitive#ResolveRemote(remote) abort
if len(s:redirects[a:remote]) if len(s:redirects[a:remote])
return s:redirects[a:remote] return s:redirects[a:remote]
endif endif
elseif a:remote =~# '^ssh://'
let authority = matchstr(a:remote, '[^/?#]*', 6)
return 'ssh://' . fugitive#SshHostAlias(authority) . strpart(a:remote, 6 + len(authority))
endif
let scp_authority = matchstr(a:remote, '^[^:/]\+\ze:\%(//\)\@!')
if empty(scp_authority)
return a:remote
endif
let path = strpart(a:remote, len(scp_authority) + 1)
let alias = fugitive#SshHostAlias(scp_authority)
if alias !~# ':'
return alias . ':' . path
elseif path =~# '^/'
return 'ssh://' . alias . path
else
return a:remote
endif endif
return substitute(a:remote,
\ '^ssh://\%([^@:/]\+@\)\=\zs[^/:]\+\|^\%([^@:/]\+@\)\=\zs[^/:]\+\ze:/\@!',
\ '\=fugitive#SshHostAlias(submatch(0))', '')
endfunction endfunction
function! fugitive#RemoteUrl(...) abort function! fugitive#RemoteUrl(...) abort