paulirish.git-open/git-open
Sandro 67032ca07f
Use absolute PATH to powershell.exe
This is guaranteed to be always at this location and is always available if Interopt is enabled.

With this it is no longer required to have powershell.exe in PATH which significantly slows down tab completion due to the very slow fs access times on WSL2. Before git-open would silently fail if powershell.exe was not available.
2023-05-09 17:05:03 +02:00

278 lines
8.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Use git-sh-setup, similar to git-rebase
# https://www.kernel.org/pub/software/scm/git/docs/git-sh-setup.html
# https://github.com/git/git/blob/master/git-rebase.sh
# shellcheck disable=SC2034
OPTIONS_STUCKLONG=t
# shellcheck disable=SC2034
OPTIONS_KEEPDASHDASH=
# shellcheck disable=SC2034
OPTIONS_SPEC="\
git open [options]
git open [remote] [branch]
--
Opens the GitHub page for a repo/branch in your browser.
https://github.com/paulirish/git-open/
Available options are
c,commit! open current commit
i,issue! open issues page
s,suffix= append this suffix
p,print! just print the url
"
# https://github.com/koalaman/shellcheck/wiki/SC1090
# shellcheck source=/dev/null
SUBDIRECTORY_OK='Yes' . "$(git --exec-path)/git-sh-setup"
# Defaults
is_commit=0
is_issue=0
protocol="https"
print_only=0
suffix_flag=""
while test $# != 0; do
case "$1" in
--commit) is_commit=1;;
--issue) is_issue=1;;
--suffix=*) suffix_flag="$1";;
--print) print_only=1;;
--) shift; break ;;
esac
shift
done
# parse suffix from suffix=value
IFS='=' read -ra suffix_flag <<< "$suffix_flag"
function join_by { local IFS="$1"; shift; echo "$*"; }
suffix=$(join_by "=" "${suffix_flag[@]:1}")
# are we in a git repo?
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
echo "Not a git repository." 1>&2
exit 1
fi
# choose remote. priority to: provided argument, default in config, detected tracked remote, 'origin'
branch=${2:-$(git symbolic-ref -q --short HEAD)}
upstream_branch_full_name=$(git config "branch.$branch.merge")
upstream_branch=${upstream_branch_full_name#"refs/heads/"}
tracked_remote=$(git config "branch.$branch.remote")
default_remote=$(git config open.default.remote)
remote=${1:-$default_remote}
remote=${remote:-$tracked_remote}
remote=${remote:-"origin"}
giturl=$(git ls-remote --get-url "$remote")
if [[ -z "$giturl" ]]; then
echo "Git remote is not set for $remote" 1>&2
exit 1
fi
ssh_config=${ssh_config:-"$HOME/.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="$(echo "${ssh_array[0]}" | tr '[:lower:]' '[:upper:]')"
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
# Trim URL scheme and possible username
gitprotocol=${giturl%%://*}
uri=${giturl#*://}
uri=${uri#*@}
# Split on first '/ to get server name and path
domain=${uri%%/*}
urlpath=${uri#*/}
# Remove port number from non-http/https protocols (ie, ssh)
if [[ $gitprotocol != 'https' && $gitprotocol != 'http' ]]; then
domain=${domain%:*}
fi
else
# Trim possible username from SSH path
uri=${giturl##*@}
# 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 [[ -n "$domain_resolv" ]]; then
domain="$domain_resolv"
fi
fi
fi
# Trim "/" from beginning of URL; "/" and ".git" from end of URL
urlpath=${urlpath#/} urlpath=${urlpath%/} urlpath=${urlpath%.git}
# If the URL is provided as "http", preserve that
if [[ $gitprotocol == 'http' ]]; then
protocol='http'
fi
# Allow config options to replace the server or the protocol
openurl="$protocol://$domain"
function getConfig() {
config=$(git config --get-urlmatch "open.$1" "$openurl")
echo "${config:-${!1}}"
}
domain=$(getConfig "domain")
protocol=$(getConfig "protocol")
forge=$(getConfig "forge")
# Remote ref to open
remote_ref=${upstream_branch:-${branch:-$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD)}}
# Split arguments on '/'
IFS='/' read -r -a pathargs <<<"$urlpath"
if (( is_issue )); then
# For issues, take the numbers and preprend 'issues/'
[[ $remote_ref =~ [0-9]+ ]]
providerBranchRef="/issues/${BASH_REMATCH[0]}"
else
# Make # and % characters url friendly
# github.com/paulirish/git-open/pull/24
remote_ref=${remote_ref//%/%25} remote_ref=${remote_ref//#/%23}
if [[ $forge == 'gitea' ]]; then
providerBranchRef="/src/branch/$remote_ref"
else
providerBranchRef="/tree/$remote_ref"
fi
fi
if [[ "$domain" == 'bitbucket.org' ]]; then
providerBranchRef="/src/$remote_ref"
elif [[ "${#pathargs[@]}" -ge 3 && ${pathargs[${#pathargs[@]} - 3]} == 'scm' ]]; then
# Bitbucket server always has /scm/ as the third to last segment in the url path, e.g. /scm/ppp/test-repo.git
# Anything before the 'scm' is part of the server's root context
# If there are other context parts, add them, up to (but not including) the found 'scm'
pathPref=("${pathargs[*]:0:${#pathargs[@]} - 3}")
# Replace the 'scm' element, with 'projects'. Keep the first argument, the string 'repos', and finally the rest of the arguments.
# shellcheck disable=SC2206
pathargs=(${pathPref[@]} 'projects' ${pathargs[${#pathargs[@]} - 2]} 'repos' "${pathargs[@]:${#pathargs[@]} - 1}")
IFS='/' urlpath="${pathargs[*]}"
providerBranchRef="/browse?at=$remote_ref"
elif [[ "${#pathargs[@]}" -ge '2' && ${pathargs[${#pathargs[@]} - 2]} == '_git' ]]; then
# Visual Studio Team Services and Team Foundation Server always have /_git/ as the second to last segment in the url path
if (( is_issue )); then
# Switch to workitems, provide work item id if specified
urlpath="${urlpath%%/_git/*}/_workitems"
# Handle case for the default repository url
urlpath="${urlpath#_git\/*}"
providerBranchRef="?id=${remote_ref//[^0-9]/}"
else
# Keep project and repository name, append branch selector.
providerBranchRef="?version=GB$remote_ref"
fi
elif [[ "$domain" =~ amazonaws\.com$ ]]; then
# AWS Code Commit
if (( is_issue )); then
echo "Issue feature does not supported on AWS Code Commit." 1>&2
exit 1
fi
# Take region name from domain.
region=${domain#*.}
region=${region%%.*}
# Replace domain.
# Ex. git-codecommit.us-east-1.amazonaws.com -> us-east-1.console.aws.amazon.com
domain="${region}.console.aws.amazon.com"
# Replace URL path.
# Ex. v1/repos/example -> codecommit/home?region=us-east-1#/repository/example/browse/
urlpath="codecommit/home?region=${region}#/repository/${urlpath##*/}/browse/"
# Replace branch ref.
# Ex. /tree/foobar -> foobar/--/
providerBranchRef="${providerBranchRef##*/}/--/"
fi
openurl="$protocol://$domain/$urlpath"
if (( is_commit )); then
sha=$(git rev-parse HEAD)
openurl="$openurl/commit/$sha"
elif [[ $remote_ref != "master" ]]; then
# simplify URL for master
openurl="$openurl$providerBranchRef"
fi
if [ "$suffix" ]; then
openurl="$openurl/$suffix"
fi
# get current open browser command
case $( uname -s ) in
Darwin) open='open';;
MINGW*) open='start';;
MSYS*) open='start';;
CYGWIN*) open='cygstart';;
*) # Try to detect WSL (Windows Subsystem for Linux)
if uname -r | grep -q -i Microsoft; then
open='/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile Start'
else
open='xdg-open'
fi;;
esac
if (( print_only )); then
BROWSER="echo"
fi
# open it in a browser
${BROWSER:-$open} "$openurl"