From b11bb0813ea55f26544b653782fb3afec5503bf0 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sat, 2 Jan 2021 22:16:17 +0100 Subject: [PATCH 01/19] Add first implementation of a mercurial resolver --- src/config.cr | 1 + src/resolvers/hg.cr | 463 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 464 insertions(+) create mode 100644 src/resolvers/hg.cr diff --git a/src/config.cr b/src/config.cr index e41d070e..a1cbfd9a 100644 --- a/src/config.cr +++ b/src/config.cr @@ -12,6 +12,7 @@ module Shards VERSION_REFERENCE = /^v?\d+[-.][-.a-zA-Z\d]+$/ VERSION_TAG = /^v(\d+[-.][-.a-zA-Z\d]+)$/ VERSION_AT_GIT_COMMIT = /^(\d+[-.][-.a-zA-Z\d]+)\+git\.commit\.([0-9a-f]+)$/ + VERSION_AT_HG_COMMIT = /^(\d+[-.][-.a-zA-Z\d]+)\+hg\.commit\.([0-9a-f]+)$/ def self.cache_path @@cache_path ||= find_or_create_cache_path diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr new file mode 100644 index 00000000..8e35e94d --- /dev/null +++ b/src/resolvers/hg.cr @@ -0,0 +1,463 @@ +require "uri" +require "./resolver" +require "../versions" +require "../logger" +require "../helpers" + +module Shards + abstract struct HgRef < Ref + def full_info + to_s + end + end + + struct HgBranchRef < HgRef + def initialize(@branch : String) + end + + def to_hg_ref(simple = false) + simple ? @branch : "branch(\"#{@branch}\") and head()" + end + + def to_s(io) + io << "branch " << @branch + end + + def to_yaml(yaml) + yaml.scalar "branch" + yaml.scalar @branch + end + end + + struct HgBookmarkRef < HgRef + def initialize(@bookmark : String) + end + + def to_hg_ref(simple = false) + simple ? @bookmark : "bookmark(\"#{@bookmark}\")" + end + + def to_s(io) + io << "bookmark " << @bookmark + end + + def to_yaml(yaml) + yaml.scalar "bookmark" + yaml.scalar @bookmark + end + end + + struct HgTagRef < HgRef + def initialize(@tag : String) + end + + def to_hg_ref(simple = false) + simple ? @tag : "tag(\"#{@tag}\")" + end + + def to_s(io) + io << "tag " << @tag + end + + def to_yaml(yaml) + yaml.scalar "tag" + yaml.scalar @tag + end + end + + struct HgCommitRef < HgRef + getter commit : String + + def initialize(@commit : String) + end + + def =~(other : HgCommitRef) + commit.starts_with?(other.commit) || other.commit.starts_with?(commit) + end + + def to_hg_ref(simple = false) + @commit + end + + def to_s(io) + io << "commit " << @commit[0...7] + end + + def full_info + "commit #{@commit}" + end + + def to_yaml(yaml) + yaml.scalar "commit" + yaml.scalar @commit + end + end + + struct HgCurrentRef < HgRef + def to_hg_ref(simple = false) + "." + end + + def to_s(io) + io << "current" + end + + def to_yaml(yaml) + raise NotImplementedError.new("HgCurrentRef is for internal use only") + end + end + + class HgResolver < Resolver + @@has_hg_command : Bool? + @@hg_version : String? + + @origin_url : String? + + def self.key + "hg" + end + + def self.normalize_key_source(key : String, source : String) : {String, String} + case key + when "hg" + {"hg", source} + else + raise "Unknown resolver #{key}" + end + end + + protected def self.has_hg_command? + if @@has_hg_command.nil? + @@has_hg_command = (Process.run("hg", ["--version"]).success? rescue false) + end + @@has_hg_command + end + + protected def self.hg_version + @@hg_version ||= `hg --version`[/\(version\s+([^)]*)\)/, 1] + end + + def read_spec(version : Version) : String? + update_local_cache + ref = hg_ref(version) + + if file_exists?(ref, SPEC_FILENAME) + capture("hg cat -r #{Process.quote(ref.to_hg_ref)} #{Process.quote(SPEC_FILENAME)}") + else + Log.debug { "Missing \"#{SPEC_FILENAME}\" for #{name.inspect} at #{ref}" } + nil + end + end + + private def spec_at_ref(ref : HgRef) : Spec? + update_local_cache + begin + if file_exists?(ref, SPEC_FILENAME) + spec_yaml = capture("hg cat -r #{Process.quote(ref.to_hg_ref)} #{Process.quote(SPEC_FILENAME)}") + Spec.from_yaml(spec_yaml) + end + rescue Error + nil + end + end + + private def spec?(version) + spec(version) + rescue Error + end + + def available_releases : Array(Version) + update_local_cache + versions_from_tags + end + + def latest_version_for_ref(ref : HgRef?) : Version + update_local_cache + ref ||= HgCurrentRef.new + begin + commit = commit_sha1_at(ref) + rescue Error + raise Error.new "Could not find #{ref.full_info} for shard #{name.inspect} in the repository #{source}" + end + + if spec = spec_at_ref(ref) + Version.new "#{spec.version.value}+hg.commit.#{commit}" + else + raise Error.new "No #{SPEC_FILENAME} was found for shard #{name.inspect} at commit #{commit}" + end + end + + def matches_ref?(ref : HgRef, version : Version) + case ref + when HgCommitRef + ref =~ hg_ref(version) + when HgBranchRef, HgBookmarkRef, HgCurrentRef + # TODO: check if version is the branch + version.has_metadata? + else + # TODO: check branch and tags + true + end + end + + protected def versions_from_tags + capture("hg tags --template '{tag}\\n'") + .split('\n') + .sort! + .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } + end + + def matches?(commit) + if branch = dependency["branch"]? + !capture("hg log -r 'branch(#{tag}) and descendants(#{commit})' --template '{branch}\\n'").strip.empty? + elsif bookmark = dependency["bookmark"]? + !capture("hg log -r 'bookmark(#{bookmark}) and descendants(#{commit})' --template '{bookmarks}\\n'").strip.empty? + elsif tag = dependency["tag"]? + !capture("hg log -r 'tag(#{tag}) and descendants(#{commit})' --template '{tags}\n'").strip.empty? + else + !capture("hg log -r #{commit}").strip.empty? + end + end + + def install_sources(version : Version, install_path : String) + update_local_cache + ref = hg_ref(version) + + FileUtils.rm_r(install_path) if File.exists?(install_path) + Dir.mkdir_p(install_path) + run "hg clone --quiet -u #{Process.quote(ref.to_hg_ref(true))} -- #{Process.quote(local_path)} #{Process.quote(install_path)}" + end + + def commit_sha1_at(ref : HgRef) + capture("hg log -r #{Process.quote(ref.to_hg_ref)} --template '{node}'").strip + end + + def local_path + @local_path ||= begin + uri = parse_uri(hg_url) + + path = uri.path + path = Path[path] + # E.g. turns "c:\local\path" into "c\local\path". Or just drops the leading slash. + if (anchor = path.anchor) + path = Path[path.drive.to_s.rchop(":"), path.relative_to(anchor)] + end + + if host = uri.host + File.join(Shards.cache_path, host, path) + else + File.join(Shards.cache_path, path) + end + end + end + + def hg_url + source.strip + end + + def parse_requirement(params : Hash(String, String)) : Requirement + params.each do |key, value| + case key + when "branch" + return HgBranchRef.new value + when "bookmark" + return HgBookmarkRef.new value + when "tag" + return HgTagRef.new value + when "commit" + return HgCommitRef.new value + else + end + end + + super + end + + record HgVersion, value : String, commit : String? = nil + + private def parse_hg_version(version : Version) : HgVersion + case version.value + when VERSION_REFERENCE + HgVersion.new version.value + when VERSION_AT_HG_COMMIT + HgVersion.new $1, $2 + else + raise Error.new("Invalid version for hg resolver: #{version}") + end + end + + private def hg_ref(version : Version) : HgRef + hg_version = parse_hg_version(version) + if commit = hg_version.commit + HgCommitRef.new commit + else + HgTagRef.new "v#{hg_version.value}" + end + end + + private def update_local_cache + if cloned_repository? && origin_changed? + delete_repository + @updated_cache = false + end + + return if Shards.local? || @updated_cache + Log.info { "Fetching #{hg_url}" } + + if cloned_repository? + # repositories cloned with shards v0.8.0 won't fetch any new remote + # refs; we must delete them and clone again! + if valid_repository? + fetch_repository + else + delete_repository + mirror_repository + end + else + mirror_repository + end + + @updated_cache = true + end + + private def mirror_repository + hg_retry(err: "Failed to clone #{hg_url}") do + # We checkout the working directory so that "." is meaningful. + # + # An alternative would be to use the `@` bookmark, but only as long + # as nothing new is committed. + run_in_current_folder "hg clone --quiet -- #{Process.quote(hg_url)} #{Process.quote(local_path)}" + end + end + + private def fetch_repository + hg_retry(err: "Failed to update #{hg_url}") do + run "hg pull" + end + end + + private def hg_retry(err = "Failed to update repository") + retries = 0 + loop do + yield + break + rescue Error + retries += 1 + next if retries < 3 + raise Error.new(err) + end + end + + private def delete_repository + Log.debug { "rm -rf #{Process.quote(local_path)}'" } + Shards::Helpers.rm_rf(local_path) + @origin_url = nil + end + + private def cloned_repository? + Dir.exists?(local_path) + end + + private def valid_repository? + File.each_line(File.join(local_path, ".hg", "dirstate")) do |line| + return true if line =~ /mirror\s*=\s*true/ + end + false + end + + private def origin_url + @origin_url ||= capture("hg paths default").strip + end + + # Returns whether origin URLs have differing hosts and/or paths. + protected def origin_changed? + return false if origin_url == hg_url + return true if origin_url.nil? || hg_url.nil? + + origin_parsed = parse_uri(origin_url) + hg_parsed = parse_uri(hg_url) + + (origin_parsed.host != hg_parsed.host) || (origin_parsed.path != hg_parsed.path) + end + + # Parses a URI string, with additional support for ssh+git URI schemes. + private def parse_uri(raw_uri) + # Need to check for file URIs early, otherwise generic parsing will fail on a colon. + if (path = raw_uri.lchop?("file://")) + return URI.new(scheme: "file", path: path) + end + + # Try normal URI parsing first + uri = URI.parse(raw_uri) + return uri if uri.absolute? && !uri.opaque? + + # Otherwise, assume and attempt to parse the scp-style ssh URIs + host, _, path = raw_uri.partition(':') + + if host.includes?('@') + user, _, host = host.partition('@') + end + + # Normalize leading slash, matching URI parsing + unless path.starts_with?('/') + path = '/' + path + end + + URI.new(scheme: "ssh", host: host, path: path, user: user) + end + + private def file_exists?(ref : HgRef, path) + files = capture("hg files -r #{Process.quote(ref.to_hg_ref)} -- #{Process.quote(path)}") + !files.strip.empty? + end + + private def capture(command, path = local_path) + run(command, capture: true, path: path).not_nil! + end + + private def run(command, path = local_path, capture = false) + if Shards.local? && !Dir.exists?(path) + dependency_name = File.basename(path) + raise Error.new("Missing repository cache for #{dependency_name.inspect}. Please run without --local to fetch it.") + end + Dir.cd(path) do + run_in_current_folder(command, capture) + end + end + + private def run_in_current_folder(command, capture = false) + unless HgResolver.has_hg_command? + raise Error.new("Error missing hg command line tool. Please install Mercurial first!") + end + + Log.debug { command } + + output = capture ? IO::Memory.new : Process::Redirect::Close + error = IO::Memory.new + status = Process.run(command, shell: true, output: output, error: error) + + if status.success? + output.to_s if capture + else + str = error.to_s + if str.starts_with?("abort: ") && (idx = str.index('\n')) + message = str[7...idx] + else + message = str + end + raise Error.new("Failed #{command} (#{message}). Maybe a commit, branch, bookmark or file doesn't exist?") + end + end + + def report_version(version : Version) : String + hg_version = parse_hg_version(version) + if commit = hg_version.commit + "#{hg_version.value} at #{commit[0...7]}" + else + version.value + end + end + + register_resolver "hg", HgResolver + end +end From c1a6c2dca8da5de217cdefdbd8e0c2ca78d5227e Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sat, 2 Jan 2021 22:21:48 +0100 Subject: [PATCH 02/19] Add specs for hg resolver --- spec/support/factories.cr | 86 +++++++++++++++++++ spec/support/requirement.cr | 4 + spec/unit/hg_resolver_spec.cr | 153 ++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 spec/unit/hg_resolver_spec.cr diff --git a/spec/support/factories.cr b/spec/support/factories.cr index af800bcb..c15da9fe 100644 --- a/spec/support/factories.cr +++ b/spec/support/factories.cr @@ -80,6 +80,78 @@ def checkout_git_branch(project, branch) end end +def create_hg_repository(project, *versions) + Dir.cd(tmp_path) do + run "hg init #{Process.quote(project)}" + end + + Dir.mkdir(File.join(hg_path(project), "src")) + File.write(File.join(hg_path(project), "src", "#{project}.cr"), "module #{project.capitalize}\nend") + + Dir.cd(hg_path(project)) do + run "hg add #{Process.quote("src/#{project}.cr")}" + end + + versions.each { |version| create_hg_release project, version } +end + +def create_fork_hg_repository(project, upstream) + Dir.cd(tmp_path) do + run "hg clone #{Process.quote(hg_url(upstream))} #{Process.quote(project)}" + end +end + +def create_hg_version_commit(project, version, shard : Bool | NamedTuple = true) + Dir.cd(hg_path(project)) do + if shard + contents = shard.is_a?(NamedTuple) ? shard : nil + create_shard project, version, contents + end + Dir.cd(hg_path(project)) do + name = shard[:name]? if shard.is_a?(NamedTuple) + name ||= project + File.touch "src/#{name}.cr" + run "hg add #{Process.quote("src/#{name}.cr")}" + end + create_hg_commit project, "release: v#{version}" + end +end + +def create_hg_release(project, version, shard : Bool | NamedTuple = true) + create_hg_version_commit(project, version, shard) + create_hg_tag(project, "v#{version}") +end + +def create_hg_tag(project, version) + Dir.cd(hg_path(project)) do + run "hg tag #{Process.quote(version)}" + end +end + +def create_hg_commit(project, message = "new commit") + Dir.cd(hg_path(project)) do + run "hg commit -A -m #{Process.quote(message)}" + end +end + +def checkout_new_hg_bookmark(project, branch) + Dir.cd(hg_path(project)) do + run "hg bookmark #{Process.quote(branch)}" + end +end + +def checkout_new_hg_branch(project, branch) + Dir.cd(hg_path(project)) do + run "hg branch #{Process.quote(branch)}" + end +end + +def checkout_hg_rev(project, rev) + Dir.cd(hg_path(project)) do + run "hg update -C #{Process.quote(rev)}" + end +end + def create_shard(project, version, contents : NamedTuple? = nil) spec = {name: project, version: version, crystal: Shards.crystal_version} spec = spec.merge(contents) if contents @@ -116,6 +188,20 @@ def git_path(project) File.join(tmp_path, project.to_s) end +def hg_commits(project, rev = ".") + Dir.cd(hg_path(project)) do + run("hg log --template='{node}\n' -r #{Process.quote(rev)}").strip.split('\n') + end +end + +def hg_url(project) + "file://#{Path[hg_path(project)].to_posix}" +end + +def hg_path(project) + File.join(tmp_path, project.to_s) +end + def rel_path(project) "../../spec/.repositories/#{project}" end diff --git a/spec/support/requirement.cr b/spec/support/requirement.cr index 7a7a8223..24594575 100644 --- a/spec/support/requirement.cr +++ b/spec/support/requirement.cr @@ -6,6 +6,10 @@ def commit(sha1) Shards::GitCommitRef.new(sha1) end +def hg_branch(name) + Shards::HgBranchRef.new(name) +end + def version(version) Shards::Version.new(version) end diff --git a/spec/unit/hg_resolver_spec.cr b/spec/unit/hg_resolver_spec.cr new file mode 100644 index 00000000..66b99821 --- /dev/null +++ b/spec/unit/hg_resolver_spec.cr @@ -0,0 +1,153 @@ +require "./spec_helper" + +private def resolver(name) + Shards::HgResolver.new(name, hg_url(name)) +end + +module Shards + # Allow overriding `source` for the specs + class HgResolver + def source=(@source) + end + end + + describe HgResolver do + before_each do + create_hg_repository "empty" + create_hg_commit "empty", "initial release" + + create_hg_repository "unreleased" + create_hg_version_commit "unreleased", "0.1.0" + checkout_new_hg_branch "unreleased", "branch" + create_hg_commit "unreleased", "testing" + checkout_hg_rev "unreleased", "default" + + create_hg_repository "library", "0.0.1", "0.1.0", "0.1.1", "0.1.2", "0.2.0" + + # Create a version tag not prefixed by 'v' which should be ignored + create_hg_tag "library", "99.9.9" + end + + it "available releases" do + resolver("empty").available_releases.should be_empty + resolver("library").available_releases.should eq(versions ["0.0.1", "0.1.0", "0.1.1", "0.1.2", "0.2.0"]) + end + + it "latest version for ref" do + expect_raises(Shards::Error, "No shard.yml was found for shard \"empty\" at commit #{hg_commits(:empty)[0]}") do + resolver("empty").latest_version_for_ref(hg_branch "default") + end + expect_raises(Shards::Error, "No shard.yml was found for shard \"empty\" at commit #{hg_commits(:empty)[0]}") do + resolver("empty").latest_version_for_ref(nil) + end + resolver("unreleased").latest_version_for_ref(hg_branch "default").should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}") + resolver("unreleased").latest_version_for_ref(hg_branch "branch").should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased, "branch")[0]}") + resolver("unreleased").latest_version_for_ref(nil).should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}") + resolver("library").latest_version_for_ref(hg_branch "default").should eq(version "0.2.0+hg.commit.#{hg_commits(:library)[0]}") + resolver("library").latest_version_for_ref(nil).should eq(version "0.2.0+hg.commit.#{hg_commits(:library)[0]}") + expect_raises(Shards::Error, "Could not find branch foo for shard \"library\" in the repository #{hg_url(:library)}") do + resolver("library").latest_version_for_ref(hg_branch "foo") + end + end + + it "versions for" do + expect_raises(Shards::Error, "No shard.yml was found for shard \"empty\" at commit #{hg_commits(:empty)[0]}") do + resolver("empty").versions_for(Any) + end + resolver("library").versions_for(Any).should eq(versions ["0.0.1", "0.1.0", "0.1.1", "0.1.2", "0.2.0"]) + resolver("library").versions_for(VersionReq.new "~> 0.1.0").should eq(versions ["0.1.0", "0.1.1", "0.1.2"]) + resolver("library").versions_for(hg_branch "default").should eq(versions ["0.2.0+hg.commit.#{hg_commits(:library)[0]}"]) + resolver("unreleased").versions_for(hg_branch "default").should eq(versions ["0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}"]) + resolver("unreleased").versions_for(Any).should eq(versions ["0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}"]) + end + + it "read spec for release" do + spec = resolver("library").spec(version "0.1.1") + spec.original_version.should eq(version "0.1.1") + spec.version.should eq(version "0.1.1") + end + + it "read spec for commit" do + version = version("0.2.0+hg.commit.#{hg_commits(:library)[0]}") + spec = resolver("library").spec(version) + spec.original_version.should eq(version "0.2.0") + spec.version.should eq(version) + end + + it "install" do + library = resolver("library") + + library.install_sources(version("0.1.2"), install_path("library")) + File.exists?(install_path("library", "src/library.cr")).should be_true + File.exists?(install_path("library", "shard.yml")).should be_true + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.1.2") + + library.install_sources(version("0.2.0"), install_path("library")) + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.2.0") + end + + it "install commit" do + library = resolver("library") + version = version "0.2.0+hg.commit.#{hg_commits(:library)[0]}" + library.install_sources(version, install_path("library")) + Spec.from_file(install_path("library", "shard.yml")).version.should eq(version "0.2.0") + end + + it "origin changed" do + library = HgResolver.new("library", hg_url("library")) + library.install_sources(version("0.1.2"), install_path("library")) + + # Change the origin in the cache repo to https://foss.heptapod.net/foo/bar + hgrc_path = File.join(library.local_path, ".hg", "hgrc") + hgrc = File.read(hgrc_path) + hgrc = hgrc.gsub(/(default\s*=\s*)([^\r\n]*)/, "\\1https://foss.heptapod.net/foo/bar") + File.write(hgrc_path, hgrc) + # + # All of these alternatives should not trigger origin as changed + same_origins = [ + "https://foss.heptapod.net/foo/bar", + "https://foss.heptapod.net:1234/foo/bar", + "http://foss.heptapod.net/foo/bar", + "ssh://foss.heptapod.net/foo/bar", + "hg://foss.heptapod.net/foo/bar", + "rsync://foss.heptapod.net/foo/bar", + "hg@foss.heptapod.net:foo/bar", + "bob@foss.heptapod.net:foo/bar", + "foss.heptapod.net:foo/bar", + ] + + same_origins.each do |origin| + library.source = origin + library.origin_changed?.should be_false + end + + # These alternatives should all trigger origin as changed + changed_origins = [ + "https://foss.heptapod.net/foo/bar2", + "https://foss.heptapod.net/foos/bar", + "https://hghubz.com/foo/bar", + "file:///foss.heptapod.net/foo/bar", + "hg@foss.heptapod.net:foo/bar2", + "hg@foss.heptapod2.net.com:foo/bar", + "", + ] + + changed_origins.each do |origin| + library.source = origin + library.origin_changed?.should be_true + end + end + + it "renders report version" do + resolver("library").report_version(version "1.2.3").should eq("1.2.3") + resolver("library").report_version(version "1.2.3+hg.commit.654875c9dbfa8d72fba70d65fd548d51ffb85aff").should eq("1.2.3 at 654875c") + end + + it "#matches_ref" do + resolver = HgResolver.new("", "") + resolver.matches_ref?(HgCommitRef.new("1234567890abcdef"), Shards::Version.new("0.1.0.+hg.commit.1234567")).should be_true + resolver.matches_ref?(HgCommitRef.new("1234567890abcdef"), Shards::Version.new("0.1.0.+hg.commit.1234567890abcdef")).should be_true + resolver.matches_ref?(HgCommitRef.new("1234567"), Shards::Version.new("0.1.0.+hg.commit.1234567890abcdef")).should be_true + end + end +end From 976260c9219af479c5b7bc3c8da2be0d90cb3a9e Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sat, 2 Jan 2021 22:40:34 +0100 Subject: [PATCH 03/19] Add some specs for mercurial bookmarks --- spec/support/factories.cr | 1 + spec/support/requirement.cr | 4 ++++ spec/unit/hg_resolver_spec.cr | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/spec/support/factories.cr b/spec/support/factories.cr index c15da9fe..2766d014 100644 --- a/spec/support/factories.cr +++ b/spec/support/factories.cr @@ -130,6 +130,7 @@ end def create_hg_commit(project, message = "new commit") Dir.cd(hg_path(project)) do + File.write("src/#{project}.cr", "# #{message}", mode: "a") run "hg commit -A -m #{Process.quote(message)}" end end diff --git a/spec/support/requirement.cr b/spec/support/requirement.cr index 24594575..e308dbde 100644 --- a/spec/support/requirement.cr +++ b/spec/support/requirement.cr @@ -6,6 +6,10 @@ def commit(sha1) Shards::GitCommitRef.new(sha1) end +def hg_bookmark(name) + Shards::HgBookmarkRef.new(name) +end + def hg_branch(name) Shards::HgBranchRef.new(name) end diff --git a/spec/unit/hg_resolver_spec.cr b/spec/unit/hg_resolver_spec.cr index 66b99821..97df5ef5 100644 --- a/spec/unit/hg_resolver_spec.cr +++ b/spec/unit/hg_resolver_spec.cr @@ -22,6 +22,12 @@ module Shards create_hg_commit "unreleased", "testing" checkout_hg_rev "unreleased", "default" + create_hg_repository "unreleased-bm" + create_hg_version_commit "unreleased-bm", "0.1.0" + checkout_new_hg_bookmark "unreleased-bm", "branch" + create_hg_commit "unreleased-bm", "testing" + checkout_hg_rev "unreleased-bm", "default" + create_hg_repository "library", "0.0.1", "0.1.0", "0.1.1", "0.1.2", "0.2.0" # Create a version tag not prefixed by 'v' which should be ignored @@ -43,6 +49,9 @@ module Shards resolver("unreleased").latest_version_for_ref(hg_branch "default").should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}") resolver("unreleased").latest_version_for_ref(hg_branch "branch").should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased, "branch")[0]}") resolver("unreleased").latest_version_for_ref(nil).should eq(version "0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}") + resolver("unreleased-bm").latest_version_for_ref(hg_branch "default").should eq(version "0.1.0+hg.commit.#{hg_commits("unreleased-bm")[0]}") + resolver("unreleased-bm").latest_version_for_ref(hg_bookmark "branch").should eq(version "0.1.0+hg.commit.#{hg_commits("unreleased-bm", "branch")[0]}") + resolver("unreleased-bm").latest_version_for_ref(nil).should eq(version "0.1.0+hg.commit.#{hg_commits("unreleased-bm")[0]}") resolver("library").latest_version_for_ref(hg_branch "default").should eq(version "0.2.0+hg.commit.#{hg_commits(:library)[0]}") resolver("library").latest_version_for_ref(nil).should eq(version "0.2.0+hg.commit.#{hg_commits(:library)[0]}") expect_raises(Shards::Error, "Could not find branch foo for shard \"library\" in the repository #{hg_url(:library)}") do @@ -59,6 +68,8 @@ module Shards resolver("library").versions_for(hg_branch "default").should eq(versions ["0.2.0+hg.commit.#{hg_commits(:library)[0]}"]) resolver("unreleased").versions_for(hg_branch "default").should eq(versions ["0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}"]) resolver("unreleased").versions_for(Any).should eq(versions ["0.1.0+hg.commit.#{hg_commits(:unreleased)[0]}"]) + resolver("unreleased-bm").versions_for(hg_branch "default").should eq(versions ["0.1.0+hg.commit.#{hg_commits("unreleased-bm")[0]}"]) + resolver("unreleased-bm").versions_for(Any).should eq(versions ["0.1.0+hg.commit.#{hg_commits("unreleased-bm")[0]}"]) end it "read spec for release" do From 8b4fd1a260872819aa4d245f49639805adcf85e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sun, 3 Jan 2021 15:50:13 +0100 Subject: [PATCH 04/19] [CI] Install mercurial --- .circleci/config.yml | 10 ++++++++-- .travis.yml | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ac1bc4e..be0d5ae2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,6 +40,9 @@ jobs: docker: - image: crystallang/crystal:latest steps: + - run: + name: Install mercurial + command: apt-get update && apt-get install mercurial -y - shards-make-test test-on-osx: @@ -49,14 +52,17 @@ jobs: - with-brew-cache: steps: - run: - name: Install Crystal - command: brew install crystal + name: Install Crystal and Mercurial + command: brew install crystal mercurial - shards-make-test test-on-nightly: docker: - image: crystallang/crystal:nightly steps: + - run: + name: Install mercurial + command: apt-get update && apt-get install mercurial -y - shards-make-test workflows: diff --git a/.travis.yml b/.travis.yml index 45f5a148..039a7409 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,6 @@ before_script: | git config --global column.ui always script: + - apt-get update && apt-get install mercurial -y - make test - crystal tool format --check src spec From d32e82a6302103eac1aa1215593432a9e1920a12 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sun, 3 Jan 2021 16:11:06 +0100 Subject: [PATCH 05/19] Forward error message in `hg_retry` --- src/resolvers/hg.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 8e35e94d..513c1414 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -341,10 +341,10 @@ module Shards loop do yield break - rescue Error + rescue ex : Error retries += 1 next if retries < 3 - raise Error.new(err) + raise Error.new("#{err}: #{ex}") end end From 736b656deebcc378b387f69a6723c4331a2be094 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sun, 3 Jan 2021 16:24:56 +0100 Subject: [PATCH 06/19] Ensure parent directory exists before hg clone --- src/resolvers/hg.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 513c1414..1a73a091 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -321,12 +321,14 @@ module Shards end private def mirror_repository + path = local_path + Dir.mkdir_p(File.dirname(path)) hg_retry(err: "Failed to clone #{hg_url}") do # We checkout the working directory so that "." is meaningful. # # An alternative would be to use the `@` bookmark, but only as long # as nothing new is committed. - run_in_current_folder "hg clone --quiet -- #{Process.quote(hg_url)} #{Process.quote(local_path)}" + run_in_current_folder "hg clone --quiet -- #{Process.quote(hg_url)} #{Process.quote(path)}" end end From 16da02b480f8653f62816f1bb54baf657fa0817d Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sun, 3 Jan 2021 18:31:21 +0100 Subject: [PATCH 07/19] Remove install path before installing sources from hg --- src/resolvers/hg.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 1a73a091..128f4253 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -322,7 +322,8 @@ module Shards private def mirror_repository path = local_path - Dir.mkdir_p(File.dirname(path)) + FileUtils.rm_r(path) if File.exists?(path) + Dir.mkdir_p(path) hg_retry(err: "Failed to clone #{hg_url}") do # We checkout the working directory so that "." is meaningful. # From 2cb5a69d2325642c3e9c01d82a0490728561c955 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 10:28:29 +0100 Subject: [PATCH 08/19] [CI] Fix installation of mercurial on travis-ci --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 039a7409..6dc9bbd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ language: crystal os: - linux +before_install: + - sudo apt-get update + - sudo apt-get -y install mercurial + before_script: | make git config --global user.email "you@example.com" @@ -10,6 +14,5 @@ before_script: | git config --global column.ui always script: - - apt-get update && apt-get install mercurial -y - make test - crystal tool format --check src spec From bd79e579931629d8c1c75136beca1b4194ce3001 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 11:14:56 +0100 Subject: [PATCH 09/19] [CI] Install mercurial in github workflow --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af1061cc..233940fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,15 @@ jobs: git config --global column.ui always git config --global core.autocrlf false + - name: Install Python + uses: actions/setup-python@v2 + + - name: Upgrade pip + run: python -m pip install --upgrade pip + + - name: Install Mercurial + run: pip install mercurial + - name: Install Crystal uses: oprypin/install-crystal@v1 with: From d9e2a7b99f57a79a36d1ce5aec55dc21e1c1af91 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 12:29:40 +0100 Subject: [PATCH 10/19] Quote shell arguments correctly --- spec/support/factories.cr | 2 +- src/resolvers/hg.cr | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/support/factories.cr b/spec/support/factories.cr index 2766d014..d32b48b0 100644 --- a/spec/support/factories.cr +++ b/spec/support/factories.cr @@ -191,7 +191,7 @@ end def hg_commits(project, rev = ".") Dir.cd(hg_path(project)) do - run("hg log --template='{node}\n' -r #{Process.quote(rev)}").strip.split('\n') + run("hg log --template=#{Process.quote("{node}\n")} -r #{Process.quote(rev)}").strip.split('\n') end end diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 128f4253..2997babb 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -201,7 +201,7 @@ module Shards end protected def versions_from_tags - capture("hg tags --template '{tag}\\n'") + capture("hg tags --template #{Process.quote("{tag}\n")}") .split('\n') .sort! .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } @@ -209,14 +209,15 @@ module Shards def matches?(commit) if branch = dependency["branch"]? - !capture("hg log -r 'branch(#{tag}) and descendants(#{commit})' --template '{branch}\\n'").strip.empty? + rev = "branch(\"#{tag}\") and descendants(#{commit})" elsif bookmark = dependency["bookmark"]? - !capture("hg log -r 'bookmark(#{bookmark}) and descendants(#{commit})' --template '{bookmarks}\\n'").strip.empty? + rev = "bookmark(\"#{bookmark}\") and descendants(#{commit})" elsif tag = dependency["tag"]? - !capture("hg log -r 'tag(#{tag}) and descendants(#{commit})' --template '{tags}\n'").strip.empty? + rev = "tag(\"tag\") and descendants(#{commit})" else - !capture("hg log -r #{commit}").strip.empty? + rev = commit end + !capture("hg log -r #{Process.quote(rev)}").strip.empty? end def install_sources(version : Version, install_path : String) @@ -229,7 +230,7 @@ module Shards end def commit_sha1_at(ref : HgRef) - capture("hg log -r #{Process.quote(ref.to_hg_ref)} --template '{node}'").strip + capture("hg log -r #{Process.quote(ref.to_hg_ref)} --template #{Process.quote("{node}\n")}").strip end def local_path @@ -352,7 +353,7 @@ module Shards end private def delete_repository - Log.debug { "rm -rf #{Process.quote(local_path)}'" } + Log.debug { "rm -rf #{Process.quote(local_path)}" } Shards::Helpers.rm_rf(local_path) @origin_url = nil end From dbac5f9860a55cb425d5bd544070ebcd5c266aa3 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 11:45:28 +0100 Subject: [PATCH 11/19] Remove a `file://` prefix from urls on Windows --- src/resolvers/hg.cr | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 2997babb..93b6adbf 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -325,12 +325,18 @@ module Shards path = local_path FileUtils.rm_r(path) if File.exists?(path) Dir.mkdir_p(path) - hg_retry(err: "Failed to clone #{hg_url}") do + + source = hg_url + # Remove a "file://" from the beginning, otherwise the path might be invalid + # on Windows. + source = source[7..] if source.starts_with?("file://") + + hg_retry(err: "Failed to clone #{source}") do # We checkout the working directory so that "." is meaningful. # # An alternative would be to use the `@` bookmark, but only as long # as nothing new is committed. - run_in_current_folder "hg clone --quiet -- #{Process.quote(hg_url)} #{Process.quote(path)}" + run_in_current_folder "hg clone --quiet -- #{Process.quote(source)} #{Process.quote(path)}" end end From 16099f841ea7a7921d16301e5f2028b574c53277 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 12:14:45 +0100 Subject: [PATCH 12/19] Set explicit username for hg commit and hg tag --- spec/support/factories.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/factories.cr b/spec/support/factories.cr index d32b48b0..7cad5926 100644 --- a/spec/support/factories.cr +++ b/spec/support/factories.cr @@ -124,14 +124,14 @@ end def create_hg_tag(project, version) Dir.cd(hg_path(project)) do - run "hg tag #{Process.quote(version)}" + run "hg tag -u #{Process.quote("Your Name ")} #{Process.quote(version)}" end end def create_hg_commit(project, message = "new commit") Dir.cd(hg_path(project)) do File.write("src/#{project}.cr", "# #{message}", mode: "a") - run "hg commit -A -m #{Process.quote(message)}" + run "hg commit -u #{Process.quote("Your Name ")} -A -m #{Process.quote(message)}" end end From a4a2e92009635a1ede8555e10758836f7ebfc2c8 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 19:33:32 +0100 Subject: [PATCH 13/19] Only check for existence of `.hg/dirstate` for identifying hg repos --- src/resolvers/hg.cr | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 93b6adbf..86185a35 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -369,10 +369,7 @@ module Shards end private def valid_repository? - File.each_line(File.join(local_path, ".hg", "dirstate")) do |line| - return true if line =~ /mirror\s*=\s*true/ - end - false + File.exists?(File.join(local_path, ".hg", "dirstate")) end private def origin_url From 3266b2b2d07281a3ee0d4232ab6cc7a6a9f3c7ba Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 19:34:18 +0100 Subject: [PATCH 14/19] Improve code of HgResolver --- src/resolvers/hg.cr | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 86185a35..8abb75bc 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -202,7 +202,7 @@ module Shards protected def versions_from_tags capture("hg tags --template #{Process.quote("{tag}\n")}") - .split('\n') + .lines .sort! .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } end @@ -267,7 +267,6 @@ module Shards return HgTagRef.new value when "commit" return HgCommitRef.new value - else end end @@ -329,7 +328,7 @@ module Shards source = hg_url # Remove a "file://" from the beginning, otherwise the path might be invalid # on Windows. - source = source[7..] if source.starts_with?("file://") + source = source.lchop("file://") hg_retry(err: "Failed to clone #{source}") do # We checkout the working directory so that "." is meaningful. @@ -349,8 +348,7 @@ module Shards private def hg_retry(err = "Failed to update repository") retries = 0 loop do - yield - break + return yield rescue ex : Error retries += 1 next if retries < 3 From 7afdb5038268e692396891e9a3149dba0715329e Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Mon, 4 Jan 2021 20:25:12 +0100 Subject: [PATCH 15/19] Add `to_hg_revset` and `to_hg_ref` methods --- src/resolvers/hg.cr | 46 ++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 8abb75bc..bfa829ee 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -15,8 +15,12 @@ module Shards def initialize(@branch : String) end - def to_hg_ref(simple = false) - simple ? @branch : "branch(\"#{@branch}\") and head()" + def to_hg_ref + @branch + end + + def to_hg_revset + "branch(\"#{@branch}\") and head()" end def to_s(io) @@ -33,8 +37,12 @@ module Shards def initialize(@bookmark : String) end - def to_hg_ref(simple = false) - simple ? @bookmark : "bookmark(\"#{@bookmark}\")" + def to_hg_ref + @bookmark + end + + def to_hg_revset + "bookmark(\"#{@bookmark}\")" end def to_s(io) @@ -51,8 +59,12 @@ module Shards def initialize(@tag : String) end - def to_hg_ref(simple = false) - simple ? @tag : "tag(\"#{@tag}\")" + def to_hg_ref + @tag + end + + def to_hg_revset + "tag(\"#{@tag}\")" end def to_s(io) @@ -75,7 +87,11 @@ module Shards commit.starts_with?(other.commit) || other.commit.starts_with?(commit) end - def to_hg_ref(simple = false) + def to_hg_ref + @commit + end + + def to_hg_revset @commit end @@ -94,7 +110,11 @@ module Shards end struct HgCurrentRef < HgRef - def to_hg_ref(simple = false) + def to_hg_revset + "." + end + + def to_hg_ref "." end @@ -142,7 +162,7 @@ module Shards ref = hg_ref(version) if file_exists?(ref, SPEC_FILENAME) - capture("hg cat -r #{Process.quote(ref.to_hg_ref)} #{Process.quote(SPEC_FILENAME)}") + capture("hg cat -r #{Process.quote(ref.to_hg_revset)} #{Process.quote(SPEC_FILENAME)}") else Log.debug { "Missing \"#{SPEC_FILENAME}\" for #{name.inspect} at #{ref}" } nil @@ -153,7 +173,7 @@ module Shards update_local_cache begin if file_exists?(ref, SPEC_FILENAME) - spec_yaml = capture("hg cat -r #{Process.quote(ref.to_hg_ref)} #{Process.quote(SPEC_FILENAME)}") + spec_yaml = capture("hg cat -r #{Process.quote(ref.to_hg_revset)} #{Process.quote(SPEC_FILENAME)}") Spec.from_yaml(spec_yaml) end rescue Error @@ -226,11 +246,11 @@ module Shards FileUtils.rm_r(install_path) if File.exists?(install_path) Dir.mkdir_p(install_path) - run "hg clone --quiet -u #{Process.quote(ref.to_hg_ref(true))} -- #{Process.quote(local_path)} #{Process.quote(install_path)}" + run "hg clone --quiet -u #{Process.quote(ref.to_hg_ref)} -- #{Process.quote(local_path)} #{Process.quote(install_path)}" end def commit_sha1_at(ref : HgRef) - capture("hg log -r #{Process.quote(ref.to_hg_ref)} --template #{Process.quote("{node}\n")}").strip + capture("hg log -r #{Process.quote(ref.to_hg_revset)} --template #{Process.quote("{node}\n")}").strip end def local_path @@ -412,7 +432,7 @@ module Shards end private def file_exists?(ref : HgRef, path) - files = capture("hg files -r #{Process.quote(ref.to_hg_ref)} -- #{Process.quote(path)}") + files = capture("hg files -r #{Process.quote(ref.to_hg_revset)} -- #{Process.quote(path)}") !files.strip.empty? end From 1453032e815c566a94d8568e267dd5b0efffac8a Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Tue, 5 Jan 2021 21:27:23 +0100 Subject: [PATCH 16/19] Remove unused method `HgResolver#matches?` --- src/resolvers/hg.cr | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index bfa829ee..4528307d 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -227,19 +227,6 @@ module Shards .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } end - def matches?(commit) - if branch = dependency["branch"]? - rev = "branch(\"#{tag}\") and descendants(#{commit})" - elsif bookmark = dependency["bookmark"]? - rev = "bookmark(\"#{bookmark}\") and descendants(#{commit})" - elsif tag = dependency["tag"]? - rev = "tag(\"tag\") and descendants(#{commit})" - else - rev = commit - end - !capture("hg log -r #{Process.quote(rev)}").strip.empty? - end - def install_sources(version : Version, install_path : String) update_local_cache ref = hg_ref(version) From fff4ccc531c8a26eadfcee36e4700cb890ca9c5a Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Tue, 5 Jan 2021 23:22:37 +0100 Subject: [PATCH 17/19] HgResolver#file_exists? does not use exceptions if the file does not exist Exceptions should only be used on real errors, not on expected fails. --- src/resolvers/hg.cr | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 4528307d..8887db80 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -419,25 +419,24 @@ module Shards end private def file_exists?(ref : HgRef, path) - files = capture("hg files -r #{Process.quote(ref.to_hg_revset)} -- #{Process.quote(path)}") - !files.strip.empty? + run("hg files -r #{Process.quote(ref.to_hg_revset)} -- #{Process.quote(path)}", raise_on_fail: false) end private def capture(command, path = local_path) - run(command, capture: true, path: path).not_nil! + run(command, capture: true, path: path).as(String) end - private def run(command, path = local_path, capture = false) + private def run(command, path = local_path, capture = false, raise_on_fail = true) if Shards.local? && !Dir.exists?(path) dependency_name = File.basename(path) raise Error.new("Missing repository cache for #{dependency_name.inspect}. Please run without --local to fetch it.") end Dir.cd(path) do - run_in_current_folder(command, capture) + run_in_current_folder(command, capture, raise_on_fail: raise_on_fail) end end - private def run_in_current_folder(command, capture = false) + private def run_in_current_folder(command, capture = false, raise_on_fail = true) unless HgResolver.has_hg_command? raise Error.new("Error missing hg command line tool. Please install Mercurial first!") end @@ -449,8 +448,12 @@ module Shards status = Process.run(command, shell: true, output: output, error: error) if status.success? - output.to_s if capture - else + if capture + output.to_s + else + true + end + elsif raise_on_fail str = error.to_s if str.starts_with?("abort: ") && (idx = str.index('\n')) message = str[7...idx] From 2e31e09d4f05bb62419aeefc1718100bafafd8c5 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Wed, 6 Jan 2021 15:40:14 +0100 Subject: [PATCH 18/19] Add documentation for hg to SPEC.md --- SPEC.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/SPEC.md b/SPEC.md index a39de50c..1ac9b0fe 100644 --- a/SPEC.md +++ b/SPEC.md @@ -244,17 +244,36 @@ installations, you must use the generic `git` resolver. Example: `gitlab: thelonlyghost/minitest.cr` +#### hg + +A Mercurial repository URL (String). + +The URL may be any protocol supported by Mercurial, which includes SSH and HTTPS. + +The Merurial repository will be cloned, the list of versions (and associated +`shard.yml`) will be extracted from Mercurial tags (e.g., `v1.2.3`). + +One of the other attributes (`version`, `tag`, `branch`, `bookmark` or `commit`) is +required. When missing, Shards will install the `@` bookmark or `tip`. + +Example: `hg: https://hg.example.org/crystal-library` + + #### commit -Install a Git dependency at the specified commit (String). +Install a Git or Mercurial dependency at the specified commit (String). #### tag -Install a Git dependency at the specified tag (String). +Install a Git or Mercurial dependency at the specified tag (String). #### branch -Install a Git dependency at the specified branch (String). +Install a Git dependency at the specified branch or a Mercurial dependency at the specified named branch (String). + +#### bookmark + +Install a Mercurial dependency at the specified bookmark (String). ### development_dependencies From c6e5df6616ee25d5f38bd0259ffad010f6715d72 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Wed, 18 Aug 2021 21:01:37 +0200 Subject: [PATCH 19/19] Fix specs for `HgResolver.normalize_key_source` --- spec/unit/hg_resolver_spec.cr | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/spec/unit/hg_resolver_spec.cr b/spec/unit/hg_resolver_spec.cr index 3ca5d8f9..0063f815 100644 --- a/spec/unit/hg_resolver_spec.cr +++ b/spec/unit/hg_resolver_spec.cr @@ -35,28 +35,11 @@ module Shards end it "normalizes github bitbucket gitlab sources" do - # deal with case insensitive paths - GitResolver.normalize_key_source("github", "repo/path").should eq({"git", "https://github.com/repo/path.git"}) - GitResolver.normalize_key_source("github", "rEpo/pAth").should eq({"git", "https://github.com/repo/path.git"}) - GitResolver.normalize_key_source("github", "REPO/PATH").should eq({"git", "https://github.com/repo/path.git"}) - GitResolver.normalize_key_source("bitbucket", "repo/path").should eq({"git", "https://bitbucket.com/repo/path.git"}) - GitResolver.normalize_key_source("bitbucket", "rEpo/pAth").should eq({"git", "https://bitbucket.com/repo/path.git"}) - GitResolver.normalize_key_source("bitbucket", "REPO/PATH").should eq({"git", "https://bitbucket.com/repo/path.git"}) - GitResolver.normalize_key_source("gitlab", "repo/path").should eq({"git", "https://gitlab.com/repo/path.git"}) - GitResolver.normalize_key_source("gitlab", "rEpo/pAth").should eq({"git", "https://gitlab.com/repo/path.git"}) - GitResolver.normalize_key_source("gitlab", "REPO/PATH").should eq({"git", "https://gitlab.com/repo/path.git"}) - - # normalise full git paths - GitResolver.normalize_key_source("git", "HTTPS://User:Pass@Github.com/Repo/Path.git?Shallow=true")[1].should eq "https://User:Pass@github.com/repo/path.git?Shallow=true" - GitResolver.normalize_key_source("git", "HTTPS://User:Pass@Bitbucket.com/Repo/Path.Git?Shallow=true")[1].should eq "https://User:Pass@bitbucket.com/repo/path.git?Shallow=true" - GitResolver.normalize_key_source("git", "HTTPS://User:Pass@Gitlab.com/Repo/Path?Shallow=true")[1].should eq "https://User:Pass@gitlab.com/repo/path.git?Shallow=true" - GitResolver.normalize_key_source("git", "HTTPS://User:Pass@www.Github.com/Repo/Path?Shallow=true")[1].should eq "https://User:Pass@github.com/repo/path.git?Shallow=true" - # don't normalise other domains - GitResolver.normalize_key_source("git", "HTTPs://mygitserver.com/Repo.git").should eq({"git", "HTTPs://mygitserver.com/Repo.git"}) + HgResolver.normalize_key_source("hg", "HTTPs://myhgserver.com/Repo").should eq({"hg", "HTTPs://myhgserver.com/Repo"}) # don't change protocol from ssh - GitResolver.normalize_key_source("git", "ssh://git@github.com/Repo/Path?Shallow=true").should eq({"git", "ssh://git@github.com/Repo/Path?Shallow=true"}) + HgResolver.normalize_key_source("hg", "ssh://hg@myhgserver.com/Repo").should eq({"hg", "ssh://hg@myhgserver.com/Repo"}) end it "available releases" do