Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Git::URL parse and clone_to methods
Signed-off-by: James Couball <jcouball@yahoo.com>
- Loading branch information
Showing
4 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'addressable/uri' | ||
|
||
module Git | ||
# Methods for parsing a Git URL | ||
# | ||
# Any URL that can be passed to `git clone` can be parsed by this class. | ||
# | ||
# @see https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a GIT URLs | ||
# @see https://github.com/sporkmonger/addressable Addresable::URI | ||
# | ||
# @api public | ||
# | ||
class URL | ||
GIT_ALTERNATIVE_SSH_SYNTAX = %r{ | ||
^ | ||
(?:(?<user>[^@/]+)@)? # user or nil | ||
(?<host>[^:/]+) # host is required | ||
:(?!/) # : serparator is required, but must not be followed by / | ||
(?<path>.*?) # path is required | ||
$ | ||
}x.freeze | ||
|
||
# Parse a Git URL and return an Addressable::URI object | ||
# | ||
# The URI returned can be converted back to a string with 'to_s'. This is | ||
# guaranteed to return the same URL string that was parsed. | ||
# | ||
# @example | ||
# uri = Git::URL.parse('https://github.com/ruby-git/ruby-git.git') | ||
# #=> #<Addressable::URI:0x44c URI:https://github.com/ruby-git/ruby-git.git> | ||
# uri.scheme #=> "https" | ||
# uri.host #=> "github.com" | ||
# uri.path #=> "/ruby-git/ruby-git.git" | ||
# | ||
# Git::URL.parse('/Users/James/projects/ruby-git') | ||
# #=> #<Addressable::URI:0x438 URI:/Users/James/projects/ruby-git> | ||
# | ||
# @param url [String] the Git URL to parse | ||
# | ||
# @return [Addressable::URI] the parsed URI | ||
# | ||
def self.parse(url) | ||
if !url.start_with?('file:') && (m = GIT_ALTERNATIVE_SSH_SYNTAX.match(url)) | ||
GitAltURI.new(user: m[:user], host: m[:host], path: m[:path]) | ||
else | ||
Addressable::URI.parse(url) | ||
end | ||
end | ||
|
||
# The name `git clone` would use for the repository directory for the given URL | ||
# | ||
# @example | ||
# Git::URL.clone_to('https://github.com/ruby-git/ruby-git.git') #=> 'ruby-git' | ||
# | ||
# @param url [String] the Git URL containing the repository directory | ||
# | ||
# @return [String] the name of the repository directory | ||
# | ||
def self.clone_to(url) | ||
uri = parse(url) | ||
path_parts = uri.path.split('/') | ||
path_parts.pop if path_parts.last == '.git' | ||
|
||
path_parts.last.sub(/\.git$/, '') | ||
end | ||
end | ||
|
||
# The URI for git's alternative scp-like syntax | ||
# | ||
# This class is necessary to ensure that #to_s returns the same string | ||
# that was passed to the initializer. | ||
# | ||
# @api public | ||
# | ||
class GitAltURI < Addressable::URI | ||
# Create a new GitAltURI object | ||
# | ||
# @example | ||
# uri = Git::GitAltURI.new(user: 'james', host: 'github.com', path: 'james/ruby-git') | ||
# uri.to_s #=> 'james@github.com/james/ruby-git' | ||
# | ||
# @param user [String, nil] the user from the URL or nil | ||
# @param host [String] the host from the URL | ||
# @param path [String] the path from the URL | ||
# | ||
def initialize(user:, host:, path:) | ||
super(scheme: 'git-alt', user: user, host: host, path: path) | ||
end | ||
|
||
# Convert the URI to a String | ||
# | ||
# @example | ||
# uri = Git::GitAltURI.new(user: 'james', host: 'github.com', path: 'james/ruby-git') | ||
# uri.to_s #=> 'james@github.com/james/ruby-git' | ||
# | ||
# @return [String] the URI as a String | ||
# | ||
def to_s | ||
if user | ||
"#{user}@#{host}:#{path[1..-1]}" | ||
else | ||
"#{host}:#{path[1..-1]}" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
require 'test/unit' | ||
|
||
GIT_URLS = [ | ||
{ | ||
url: 'ssh://host.xz/path/to/repo.git/', | ||
expected_attributes: { scheme: 'ssh', host: 'host.xz', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'ssh://host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'ssh', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'ssh:///path/to/repo.git/', | ||
expected_attributes: { scheme: 'ssh', host: '', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'user@host.xz:path/to/repo.git/', | ||
expected_attributes: { scheme: 'git-alt', user: 'user', host: 'host.xz', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'host.xz:path/to/repo.git/', | ||
expected_attributes: { scheme: 'git-alt', host: 'host.xz', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'git://host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'git', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'git://user@host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'git', user: 'user', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'https://host.xz/path/to/repo.git/', | ||
expected_attributes: { scheme: 'https', host: 'host.xz', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'https://host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'https', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'ftps://host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'ftps', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'ftps://host.xz:4443/path/to/repo.git/', | ||
expected_attributes: { scheme: 'ftps', host: 'host.xz', port: 4443, path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'file:./relative-path/to/repo.git/', | ||
expected_attributes: { scheme: 'file', path: './relative-path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'file:///path/to/repo.git/', | ||
expected_attributes: { scheme: 'file', host: '', path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'file:///path/to/repo.git', | ||
expected_attributes: { scheme: 'file', host: '', path: '/path/to/repo.git' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: 'file://host.xz/path/to/repo.git', | ||
expected_attributes: { scheme: 'file', host: 'host.xz', path: '/path/to/repo.git' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: '/path/to/repo.git/', | ||
expected_attributes: { path: '/path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: '/path/to/bare-repo/.git', | ||
expected_attributes: { path: '/path/to/bare-repo/.git' }, | ||
expected_clone_to: 'bare-repo' | ||
}, | ||
{ | ||
url: 'relative-path/to/repo.git/', | ||
expected_attributes: { path: 'relative-path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: './relative-path/to/repo.git/', | ||
expected_attributes: { path: './relative-path/to/repo.git/' }, | ||
expected_clone_to: 'repo' | ||
}, | ||
{ | ||
url: '../ruby-git/.git', | ||
expected_attributes: { path: '../ruby-git/.git' }, | ||
expected_clone_to: 'ruby-git' | ||
} | ||
].freeze | ||
|
||
# Tests for the Git::URL class | ||
# | ||
class TestURL < Test::Unit::TestCase | ||
def test_parse | ||
GIT_URLS.each do |url_data| | ||
url = url_data[:url] | ||
expected_attributes = url_data[:expected_attributes] | ||
actual_attributes = Git::URL.parse(url).to_hash.delete_if {| key, value | value.nil? } | ||
assert_equal(expected_attributes, actual_attributes, "Failed to parse URL '#{url}' correctly") | ||
end | ||
end | ||
|
||
def test_clone_to | ||
GIT_URLS.each do |url_data| | ||
url = url_data[:url] | ||
expected_clone_to = url_data[:expected_clone_to] | ||
actual_repo_name = Git::URL.clone_to(url) | ||
assert_equal( | ||
expected_clone_to, actual_repo_name, | ||
"Failed to determine the repository directory for URL '#{url}' correctly" | ||
) | ||
end | ||
end | ||
|
||
def test_to_s | ||
GIT_URLS.each do |url_data| | ||
url = url_data[:url] | ||
to_s = Git::URL.parse(url).to_s | ||
assert_equal(url, to_s, "Parsed URI#to_s does not return the original URL '#{url}' correctly") | ||
end | ||
end | ||
end |