Skip to content

Commit d9b9580

Browse files
authored
Merge pull request #109 from 4U6U57/master
Support domain expansion from sshconfig
2 parents 7449bfa + 775361b commit d9b9580

File tree

2 files changed

+224
-2
lines changed

2 files changed

+224
-2
lines changed

git-open

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,51 @@ if [[ -z "$giturl" ]]; then
5656
exit 1
5757
fi
5858

59+
ssh_config=${ssh_config:-"$HOME/.ssh/config"}
60+
# Resolves an ssh alias defined in ssh_config to it's corresponding hostname
61+
# echos out result, should be used within subshell $( ssh_resolve $host )
62+
# echos out nothing if alias could not be resolved
63+
function ssh_resolve() {
64+
domain="$1"
65+
ssh_found=true
66+
# Filter to only ssh_config lines that start with "Host" or "HostName"
67+
resolved=$(while read -r ssh_line; do
68+
# Split each line by spaces, of the form:
69+
# Host alias [alias...]
70+
# Host regex
71+
# HostName resolved.domain.com
72+
read -r -a ssh_array <<<"${ssh_line}"
73+
ssh_optcode="${ssh_array[0]}"
74+
if [[ ${ssh_optcode^^} == HOST ]]; then
75+
# Host
76+
ssh_found=false
77+
# Iterate through aliases looking for a match
78+
for ssh_index in $(seq 1 $((${#ssh_array[@]} - 1))); do
79+
ssh_host=${ssh_array[$ssh_index]}
80+
# shellcheck disable=SC2053
81+
if [[ $domain == $ssh_host ]]; then
82+
# Found a match, next HostName entry will be returned while matched
83+
ssh_found=true
84+
break
85+
fi
86+
done
87+
elif $ssh_found && [[ ${ssh_optcode^^} == HOSTNAME ]]; then
88+
# HostName, but only if ssh_found is true (the last Host entry matched)
89+
# Replace all instances of %h with the Host alias
90+
echo "${ssh_array[1]//%h/$domain}"
91+
fi
92+
done < <(grep -iE "^\\s*Host(Name)?\\s+" "$ssh_config"))
93+
# Take only the last resolved hostname (multiple are overridden)
94+
tail -1 <<<"$resolved"
95+
}
96+
5997
# From git-fetch(5), native protocols:
6098
# ssh://[user@]host.xz[:port]/path/to/repo.git/
6199
# git://host.xz[:port]/path/to/repo.git/
62100
# http[s]://host.xz[:port]/path/to/repo.git/
63101
# ftp[s]://host.xz[:port]/path/to/repo.git/
64102
# [user@]host.xz:path/to/repo.git/ - scp-like but is an alternative to ssh.
103+
# [user@]hostalias:path/to/repo.git/ - handles host aliases defined in ssh_config(5)
65104

66105
# Determine whether this is a url (https, ssh, git+ssh...) or an scp-style path
67106
if [[ "$giturl" =~ ^[a-z\+]+://.* ]]; then
@@ -85,6 +124,14 @@ else
85124
# Split on first ':' to get server name and path
86125
domain=${uri%%:*}
87126
urlpath=${uri#*:}
127+
128+
# Resolve sshconfig aliases
129+
if [[ -e "$ssh_config" ]]; then
130+
domain_resolv=$(ssh_resolve "$domain")
131+
if [[ ! -z "$domain_resolv" ]]; then
132+
domain="$domain_resolv"
133+
fi
134+
fi
88135
fi
89136

90137
# Trim "/" from beginning of URL; "/" and ".git" from end of URL
@@ -110,7 +157,7 @@ protocol=$(getConfig "protocol")
110157
branch=${2:-$(git symbolic-ref -q --short HEAD)}
111158

112159
# Split arguments on '/'
113-
IFS='/' pathargs=($urlpath)
160+
IFS='/' read -r -a pathargs <<<"$urlpath"
114161

115162
if (( is_issue )); then
116163
# For issues, take the numbers and preprend 'issues/'
@@ -133,6 +180,7 @@ elif [[ "${#pathargs[@]}" -ge 3 && ${pathargs[${#pathargs[@]} - 3]} == 'scm' ]];
133180
pathPref=("${pathargs[*]:0:${#pathargs[@]} - 3}")
134181

135182
# Replace the 'scm' element, with 'projects'. Keep the first argument, the string 'repos', and finally the rest of the arguments.
183+
# shellcheck disable=SC2206
136184
pathargs=(${pathPref[@]} 'projects' ${pathargs[${#pathargs[@]} - 2]} 'repos' "${pathargs[@]:${#pathargs[@]} - 1}")
137185
IFS='/' urlpath="${pathargs[*]}"
138186
providerBranchRef="/browse?at=$branch"
@@ -171,7 +219,9 @@ case $( uname -s ) in
171219
esac
172220

173221
# Allow printing the url if BROWSER=echo
174-
if [[ $BROWSER != "echo" ]]; then
222+
if [[ $BROWSER == "echo" ]]; then
223+
openopt=''
224+
else
175225
exec &>/dev/null
176226
fi
177227

test/git-open.bats

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,113 @@ setup() {
174174
assert_output "http://github.com/user/repo"
175175
}
176176

177+
##
178+
## SSH config
179+
##
180+
181+
@test "sshconfig: basic" {
182+
create_ssh_sandbox
183+
# Basic
184+
git remote set-url origin "basic:user/repo.git"
185+
run ../git-open
186+
assert_output --partial "https://basic.com/user/repo"
187+
# With git user
188+
git remote set-url origin "git@nouser:user/repo.git"
189+
run ../git-open
190+
assert_output "https://no.user/user/repo"
191+
}
192+
193+
@test "sshconfig: no action on no match" {
194+
create_ssh_sandbox
195+
git remote set-url origin "git@nomatch:user/repo.git"
196+
run ../git-open
197+
assert_output "https://nomatch/user/repo"
198+
# No match due to improper casing
199+
}
200+
201+
@test "sshconfig: check case sensitivity" {
202+
create_ssh_sandbox
203+
# Host and HostName keywords should be case insensitive
204+
# But output URL will be case sensitive
205+
git remote set-url origin "malformed:user/repo.git"
206+
run ../git-open
207+
assert_output "https://MaL.FoRmEd/user/repo"
208+
# SSH aliases (hosts) are case sensitive, this should not match
209+
git remote set-url origin "git@MALFORMED:user/repo.git"
210+
run ../git-open
211+
refute_output "https://MaL.FoRmEd/user/repo"
212+
}
213+
214+
@test "sshconfig: multitarget host" {
215+
create_ssh_sandbox
216+
for i in $(seq 1 3); do
217+
git remote set-url origin "multi$i:user/repo.git"
218+
run ../git-open
219+
assert_output "https://multi.com/user/repo"
220+
done
221+
}
222+
223+
@test "sshconfig: host substitution in hostname" {
224+
create_ssh_sandbox
225+
for i in $(seq 1 3); do
226+
git remote set-url origin "sub$i:user/repo.git"
227+
run ../git-open
228+
assert_output "https://sub$i.multi.com/user/repo"
229+
done
230+
}
231+
232+
@test "sshconfig: host wildcard * matches zero or more chars" {
233+
create_ssh_sandbox
234+
# Normal *
235+
for str in "" "-prod" "-dev"; do
236+
git remote set-url origin "zero$str:user/repo.git"
237+
run ../git-open
238+
assert_output "https://zero.com/user/repo"
239+
done
240+
# * with substitution
241+
for str in "" "-prod" "-dev"; do
242+
git remote set-url origin "subzero$str:user/repo.git"
243+
run ../git-open
244+
assert_output "https://subzero$str.zero/user/repo"
245+
done
246+
}
247+
248+
@test "sshconfig: host wildcard ? matches exactly one char" {
249+
create_ssh_sandbox
250+
# Normal ?
251+
for i in $(seq 1 3); do
252+
git remote set-url origin "one$i:user/repo.git"
253+
run ../git-open
254+
assert_output "https://one.com/user/repo"
255+
done
256+
# Refute invalid match on ?
257+
for str in "" "-test"; do
258+
git remote set-url origin "one:user/repo.git"
259+
run ../git-open
260+
refute_output "https://one$str.com/user/repo"
261+
done
262+
263+
# ? with substitution
264+
for i in $(seq 1 3); do
265+
git remote set-url origin "subone$i:user/repo.git"
266+
run ../git-open
267+
assert_output "https://subone$i.one/user/repo"
268+
done
269+
# Refute invalid match on ? with substitution
270+
for str in "" "-test"; do
271+
git remote set-url origin "subone$str:user/repo.git"
272+
run ../git-open
273+
refute_output "https://subone$str.one/user/repo"
274+
done
275+
# Refute invalid match on ? with substitution
276+
}
277+
278+
@test "sshconfig: overriding host rules" {
279+
create_ssh_sandbox
280+
git remote set-url origin "zero-override:user/repo.git"
281+
run ../git-open
282+
assert_output "https://override.zero.com/user/repo"
283+
}
177284

178285
##
179286
## Bitbucket
@@ -393,6 +500,9 @@ setup() {
393500
teardown() {
394501
cd ..
395502
rm -rf "$foldername"
503+
rm -rf "$ssh_config"
504+
refute [ -e "$ssh_config" ]
505+
unset ssh_config
396506
}
397507

398508
# helper to create a test git sandbox that won't dirty the real repo
@@ -418,3 +528,65 @@ function create_git_sandbox() {
418528
git add readme.txt
419529
git commit -m "add file" -q
420530
}
531+
532+
# helper to create test SSH config file
533+
function create_ssh_sandbox() {
534+
export ssh_config=$(mktemp)
535+
refute [ -z "$ssh_config" ]
536+
537+
# Populate ssh config with test data
538+
echo "$ssh_testdata" >$ssh_config
539+
assert [ -e "$ssh_config" ]
540+
}
541+
542+
# Test SSH config data
543+
ssh_testdata="
544+
# Autogenerated test sshconfig for paulirish/git-open BATS tests
545+
# It is safe to delete this file, a new one will be generated each test
546+
547+
Host basic
548+
HostName basic.com
549+
User git
550+
551+
Host nomatch
552+
User git
553+
554+
Host nouser
555+
HostName no.user
556+
557+
host malformed
558+
hOsTnAmE MaL.FoRmEd
559+
User other
560+
561+
# Multiple targets
562+
Host multi1 multi2 multi3
563+
HostName multi.com
564+
User git
565+
566+
Host sub1 sub2 sub3
567+
HostName %h.multi.com
568+
User git
569+
570+
# Wildcard * matching (zero or more characters)
571+
Host zero*
572+
HostName zero.com
573+
User git
574+
575+
Host subzero*
576+
HostName %h.zero
577+
User git
578+
579+
# Wildcard ? matching (exactly one character)
580+
Host one?
581+
HostName one.com
582+
User git
583+
584+
Host subone?
585+
HostName %h.one
586+
User git
587+
588+
# Overrides rule zero*
589+
Host zero-override
590+
HostName override.zero.com
591+
User git
592+
"

0 commit comments

Comments
 (0)