Adds support for sshconfig expansion

See `man ssh_config` for documentation for sshconfig.
Only works on SCP-like remote URL's [user@]hostalias:path/to/repo.git
Parses through ~/.ssh/config, looking for a Host entry matching
hostalias (bash-like pattern matching supported) and expanding it to the
corresponding HostName entry, if applicable.
This commit is contained in:
August Valera 2017-12-01 14:41:02 -08:00
parent 7449bfa7e1
commit 8909a2b753
2 changed files with 222 additions and 1 deletions

View file

@ -56,12 +56,51 @@ if [[ -z "$giturl" ]]; then
exit 1
fi
ssh_config=${ssh_config:-~/.ssh/config}
# Resolves an ssh alias defined in ssh_config to it's corresponding hostname
# echos out result, should be used within subshell $( ssh_resolve $host )
# echos out nothing if alias could not be resolved
function ssh_resolve() {
domain="$1"
ssh_found=true
# Filter to only ssh_config lines that start with "Host" or "HostName"
resolved=$(while read -r ssh_line; do
# Split each line by spaces, of the form:
# Host alias [alias...]
# Host regex
# HostName resolved.domain.com
read -r -a ssh_array <<<"${ssh_line}"
ssh_optcode="${ssh_array[0]}"
if [[ ${ssh_optcode^^} == HOST ]]; then
# Host
ssh_found=false
# Iterate through aliases looking for a match
for ssh_index in $(seq 1 $((${#ssh_array[@]} - 1))); do
ssh_host=${ssh_array[$ssh_index]}
# shellcheck disable=SC2053
if [[ $domain == $ssh_host ]]; then
# Found a match, next HostName entry will be returned while matched
ssh_found=true
break
fi
done
elif $ssh_found && [[ ${ssh_optcode^^} == HOSTNAME ]]; then
# HostName, but only if ssh_found is true (the last Host entry matched)
# Replace all instances of %h with the Host alias
echo "${ssh_array[1]//%h/$domain}"
fi
done < <(grep -iE "^\\s*Host(Name)?\\s+" "$ssh_config"))
# Take only the last resolved hostname (multiple are overridden)
tail -1 <<<"$resolved"
}
# From git-fetch(5), native protocols:
# ssh://[user@]host.xz[:port]/path/to/repo.git/
# git://host.xz[:port]/path/to/repo.git/
# http[s]://host.xz[:port]/path/to/repo.git/
# ftp[s]://host.xz[:port]/path/to/repo.git/
# [user@]host.xz:path/to/repo.git/ - scp-like but is an alternative to ssh.
# [user@]hostalias:path/to/repo.git/ - handles host aliases defined in ssh_config(5)
# Determine whether this is a url (https, ssh, git+ssh...) or an scp-style path
if [[ "$giturl" =~ ^[a-z\+]+://.* ]]; then
@ -85,6 +124,14 @@ else
# Split on first ':' to get server name and path
domain=${uri%%:*}
urlpath=${uri#*:}
# Resolve sshconfig aliases
if [[ -e "$ssh_config" ]]; then
domain_resolv=$(ssh_resolve "$domain")
if [[ ! -z "$domain_resolv" ]]; then
domain="$domain_resolv"
fi
fi
fi
# Trim "/" from beginning of URL; "/" and ".git" from end of URL
@ -171,7 +218,9 @@ case $( uname -s ) in
esac
# Allow printing the url if BROWSER=echo
if [[ $BROWSER != "echo" ]]; then
if [[ $BROWSER == "echo" ]]; then
openopt=''
else
exec &>/dev/null
fi

View file

@ -174,6 +174,113 @@ setup() {
assert_output "http://github.com/user/repo"
}
##
## SSH config
##
@test "sshconfig: basic" {
create_ssh_sandbox
# Basic
git remote set-url origin "basic:user/repo.git"
run ../git-open
assert_output --partial "https://basic.com/user/repo"
# With git user
git remote set-url origin "git@nouser:user/repo.git"
run ../git-open
assert_output "https://no.user/user/repo"
}
@test "sshconfig: no action on no match" {
create_ssh_sandbox
git remote set-url origin "git@nomatch:user/repo.git"
run ../git-open
assert_output "https://nomatch/user/repo"
# No match due to improper casing
}
@test "sshconfig: check case sensitivity" {
create_ssh_sandbox
# Host and HostName keywords should be case insensitive
# But output URL will be case sensitive
git remote set-url origin "malformed:user/repo.git"
run ../git-open
assert_output "https://MaL.FoRmEd/user/repo"
# SSH aliases (hosts) are case sensitive, this should not match
git remote set-url origin "git@MALFORMED:user/repo.git"
run ../git-open
refute_output "https://MaL.FoRmEd/user/repo"
}
@test "sshconfig: multitarget host" {
create_ssh_sandbox
for i in $(seq 1 3); do
git remote set-url origin "multi$i:user/repo.git"
run ../git-open
assert_output "https://multi.com/user/repo"
done
}
@test "sshconfig: host substitution in hostname" {
create_ssh_sandbox
for i in $(seq 1 3); do
git remote set-url origin "sub$i:user/repo.git"
run ../git-open
assert_output "https://sub$i.multi.com/user/repo"
done
}
@test "sshconfig: host wildcard * matches zero or more chars" {
create_ssh_sandbox
# Normal *
for str in "" "-prod" "-dev"; do
git remote set-url origin "zero$str:user/repo.git"
run ../git-open
assert_output "https://zero.com/user/repo"
done
# * with substitution
for str in "" "-prod" "-dev"; do
git remote set-url origin "subzero$str:user/repo.git"
run ../git-open
assert_output "https://subzero$str.zero/user/repo"
done
}
@test "sshconfig: host wildcard ? matches exactly one char" {
create_ssh_sandbox
# Normal ?
for i in $(seq 1 3); do
git remote set-url origin "one$i:user/repo.git"
run ../git-open
assert_output "https://one.com/user/repo"
done
# Refute invalid match on ?
for str in "" "-test"; do
git remote set-url origin "one:user/repo.git"
run ../git-open
refute_output "https://one$str.com/user/repo"
done
# ? with substitution
for i in $(seq 1 3); do
git remote set-url origin "subone$i:user/repo.git"
run ../git-open
assert_output "https://subone$i.one/user/repo"
done
# Refute invalid match on ? with substitution
for str in "" "-test"; do
git remote set-url origin "subone$str:user/repo.git"
run ../git-open
refute_output "https://subone$str.one/user/repo"
done
# Refute invalid match on ? with substitution
}
@test "sshconfig: overriding host rules" {
create_ssh_sandbox
git remote set-url origin "zero-override:user/repo.git"
run ../git-open
assert_output "https://override.zero.com/user/repo"
}
##
## Bitbucket
@ -393,6 +500,9 @@ setup() {
teardown() {
cd ..
rm -rf "$foldername"
rm -rf "$ssh_config"
refute [ -e "$ssh_config" ]
unset ssh_config
}
# helper to create a test git sandbox that won't dirty the real repo
@ -418,3 +528,65 @@ function create_git_sandbox() {
git add readme.txt
git commit -m "add file" -q
}
# helper to create test SSH config file
function create_ssh_sandbox() {
export ssh_config=$(mktemp)
refute [ -z "$ssh_config" ]
# Populate ssh config with test data
echo "$ssh_testdata" >$ssh_config
assert [ -e "$ssh_config" ]
}
# Test SSH config data
ssh_testdata="
# Autogenerated test sshconfig for paulirish/git-open BATS tests
# It is safe to delete this file, a new one will be generated each test
Host basic
HostName basic.com
User git
Host nomatch
User git
Host nouser
HostName no.user
host malformed
hOsTnAmE MaL.FoRmEd
User other
# Multiple targets
Host multi1 multi2 multi3
HostName multi.com
User git
Host sub1 sub2 sub3
HostName %h.multi.com
User git
# Wildcard * matching (zero or more characters)
Host zero*
HostName zero.com
User git
Host subzero*
HostName %h.zero
User git
# Wildcard ? matching (exactly one character)
Host one?
HostName one.com
User git
Host subone?
HostName %h.one
User git
# Overrides rule zero*
Host zero-override
HostName override.zero.com
User git
"