From e3ce014bfa941f75130e862d86cb032b64ab873e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 1 Dec 2017 00:23:52 +0000 Subject: [PATCH] Add support for Visual Studio Team Services and Team Foundation Server (#93) * Parse remotes as URLs or SCP-style paths Instead of trying to strip on ':' and '/' to simplify a URL, actually switch based on whether the remote path is a URL ("scheme://host:port/path") or an SCP-style path (user@host:path) and parse them separately. This allows us to handle custom ports in HTTP and HTTPS, but ignore custom ports in an SSH url and HTTP remotes, instead of always upgrading them to HTTPS. * Introduce tests for Visual Studio Team Services Add tests for Visual Studio Team Services (VSTS) and Team Foundation Server (TFS). VSTS suggests remote paths in two formats: HTTPS URLs or SSH URLs (including port number). TFS is an on-premises product which - when running as an HTTP endpoint - defaults to port 8080. * Branch selection in Visual Studio Team Services VSTS and TFS URLs end in '/_git/RepositoryName` (with 0 or more leading folders of hierarchy in front of that.) Detect these from the `_git` in the penultimate folder of the path. Append branch information to VSTS and TFS URLs. Since VSTS and TFS use a query string to select a branch, instead of including it in the server path, the `providerBranchRef` for other services was changed to include the leading `/`, and now the `openurl` and `providerBranchRef` are simply concatenated, to avoid an incorrect trailing `/` for the VSTS and TFS branch URLs. * Issues for Visual Studio Team Services Provide issue support for VSTS and TFS, modifying the URL from the `_git` endpoint to the `_workitems` endpoint and appending the `id` query string. * README: add VSTS and TFS support --- README.md | 4 ++- git-open | 56 +++++++++++++++++++++++++++++---------- test/git-open.bats | 65 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dec489d..ad9aebe 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ git open [remote-name] [branch-name] git open --issue ``` -(`git open` works with these [hosted repo providers](#supported-remote-repositories), `git open --issue` currently only works with GitHub) +(`git open` works with these [hosted repo providers](#supported-remote-repositories), `git open --issue` currently only works with GitHub, Visual Studio Team Services and Team Foundation Server) ### Examples @@ -100,6 +100,8 @@ git-open can automatically guess the corresponding repository page for remotes - GitLab custom hosted (see below) - bitbucket.org - Atlassian Bitbucket Server (formerly _Atlassian Stash_) +- Visual Studio Team Services +- Team Foundation Server (on-premises) ### GitLab support diff --git a/git-open b/git-open index 9e3b304..e6f8463 100755 --- a/git-open +++ b/git-open @@ -62,17 +62,37 @@ fi # ftp[s]://host.xz[:port]/path/to/repo.git/ # [user@]host.xz:path/to/repo.git/ - scp-like but is an alternative to ssh. -# Trim "/" and ".git" from the end of the url -giturl=${giturl%/} giturl=${giturl%.git} +# 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#*@} -# Trim before last '@' and protocol (*://) from beginning -uri=${giturl##*@} uri=${uri##*://} + # Split on first '/ to get server name and path + domain=${uri%%/*} + urlpath=${uri#*/} -# If there isn't a protocol, we can assume it's using the scp syntax which uses ':' to seperate the path. -[[ $giturl =~ :// ]] && pathsep='/' || pathsep=':' + # 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##*@} -# Seperate the domain and the urlpath on the first {pathsep}. This also removes the gitport from the domain. -domain=${uri%%[:$pathsep]*} urlpath=${uri#*$pathsep} + # Split on first ':' to get server name and path + domain=${uri%%:*} + urlpath=${uri#*:} +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" @@ -93,30 +113,40 @@ IFS='/' pathargs=($urlpath) if (( is_issue )); then # For issues, take the numbers and preprend 'issues/' - providerBranchRef="issues/${branch//[^0-9]/}" + providerBranchRef="/issues/${branch//[^0-9]/}" else # Make # and % characters url friendly # github.com/paulirish/git-open/pull/24 branch=${branch//%/%25} branch=${branch//#/%23} - providerBranchRef="tree/$branch" + providerBranchRef="/tree/$branch" fi if [[ "$domain" == 'bitbucket.org' ]]; then # Bitbucket, see https://github.com/paulirish/git-open/issues/80 for why ?at is needed. - providerBranchRef="src?at=$branch" + providerBranchRef="/src?at=$branch" elif [[ ${pathargs[0]} == 'scm' ]]; then # Bitbucket server, which starts with 'scm' # Replace the first element, 'scm', with 'projects'. Keep the first argument, the string 'repos', and finally the rest of the arguments. pathargs=('projects' ${pathargs[1]} 'repos' "${pathargs[@]:2}") IFS='/' urlpath="${pathargs[*]}" - providerBranchRef="browse?at=$branch" + providerBranchRef="/browse?at=$branch" +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" + providerBranchRef="?id=${branch//[^0-9]/}" + else + # Keep project and repository name, append branch selector. + providerBranchRef="?version=GB$branch" + fi fi openurl="$protocol://$domain/$urlpath" # simplify URL for master if [[ $branch != "master" ]]; then - openurl="$openurl/$providerBranchRef" + openurl="$openurl$providerBranchRef" fi # get current open browser command diff --git a/test/git-open.bats b/test/git-open.bats index 3401349..c3abd6c 100755 --- a/test/git-open.bats +++ b/test/git-open.bats @@ -144,6 +144,24 @@ setup() { assert_output "https://github.com/paulirish/git-open" } +@test "basic: https url can contain port" { + git remote set-url origin "https://github.com:99/user/repo.git" + run ../git-open + assert_output "https://github.com:99/user/repo" +} + +@test "basic: ssh url has port removed from http url" { + git remote set-url origin "ssh://github.com:22/user/repo.git" + run ../git-open + assert_output "https://github.com/user/repo" +} + +@test "basic: http url scheme is preserved" { + git remote set-url origin "http://github.com/user/repo.git" + run ../git-open + assert_output "http://github.com/user/repo" +} + ## ## Bitbucket @@ -299,10 +317,53 @@ setup() { git remote set-url origin "https://git.example.com:7000/XXX/YYY.git" run ../git-open - assert_output "https://git.example.com/XXX/YYY" - refute_output --partial ":7000" + assert_output "https://git.example.com:7000/XXX/YYY" } +## +## Visual Studio Team Services +## + +@test "vsts: https url" { + git remote set-url origin "https://gitopen.visualstudio.com/Project/_git/Repository" + run ../git-open + assert_output --partial "https://gitopen.visualstudio.com/Project/_git/Repository" +} + +@test "vsts: ssh url" { + git remote add vsts_ssh "ssh://gitopen@gitopen.visualstudio.com:22/Project/_git/Repository" + run ../git-open "vsts_ssh" + assert_output "https://gitopen.visualstudio.com/Project/_git/Repository" +} + +@test "vsts: on-premises tfs http url" { + git remote set-url origin "http://tfs.example.com:8080/Project/_git/Repository" + run ../git-open + assert_output --partial "http://tfs.example.com:8080/Project/_git/Repository" +} + +@test "vsts: branch" { + git remote set-url origin "ssh://gitopen@gitopen.visualstudio.com:22/_git/Repository" + git checkout -B "mybranch" + run ../git-open + assert_output "https://gitopen.visualstudio.com/_git/Repository?version=GBmybranch" +} + +@test "vsts: on-premises tfs branch" { + git remote set-url origin "http://tfs.example.com:8080/Project/Folder/_git/Repository" + git checkout -B "mybranch" + run ../git-open + assert_output "http://tfs.example.com:8080/Project/Folder/_git/Repository?version=GBmybranch" +} + +@test "vsts: issue" { + git remote set-url origin "http://tfs.example.com:8080/Project/Folder/_git/Repository" + git checkout -B "bugfix-36" + run ../git-open "--issue" + assert_output "http://tfs.example.com:8080/Project/Folder/_workitems?id=36" +} + + teardown() { cd .. rm -rf "$foldername"