Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mercurial command server #459

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b11bb08
Add first implementation of a mercurial resolver
f-fr Jan 2, 2021
c1a6c2d
Add specs for hg resolver
f-fr Jan 2, 2021
976260c
Add some specs for mercurial bookmarks
f-fr Jan 2, 2021
efdeda7
HgResolver uses the Mercurial command server
f-fr Jan 3, 2021
3da7e52
Specs for HgResolver use the command server
f-fr Jan 3, 2021
8b4fd1a
[CI] Install mercurial
straight-shoota Jan 3, 2021
d32e82a
Forward error message in `hg_retry`
f-fr Jan 3, 2021
736b656
Ensure parent directory exists before hg clone
f-fr Jan 3, 2021
b585358
Merge hg-resolver
f-fr Jan 3, 2021
fa97263
Ensure target path exists when installing sources from hg
f-fr Jan 3, 2021
16da02b
Remove install path before installing sources from hg
f-fr Jan 3, 2021
2f93b0b
Merge hg-resolver
f-fr Jan 3, 2021
2cb5a69
[CI] Fix installation of mercurial on travis-ci
f-fr Jan 4, 2021
bd79e57
[CI] Install mercurial in github workflow
f-fr Jan 4, 2021
06b9255
Call `Process` with full quoted command line
f-fr Jan 4, 2021
16099f8
Set explicit username for hg commit and hg tag
f-fr Jan 4, 2021
d9e2a7b
Quote shell arguments correctly
f-fr Jan 4, 2021
dbac5f9
Remove a `file://` prefix from urls on Windows
f-fr Jan 4, 2021
be1dc7c
Merge hg-resolver
f-fr Jan 4, 2021
a4a2e92
Only check for existence of `.hg/dirstate` for identifying hg repos
f-fr Jan 4, 2021
3266b2b
Improve code of HgResolver
f-fr Jan 4, 2021
7afdb50
Add `to_hg_revset` and `to_hg_ref` methods
f-fr Jan 4, 2021
1453032
Remove unused method `HgResolver#matches?`
f-fr Jan 5, 2021
fff4ccc
HgResolver#file_exists? does not use exceptions if the file does not …
f-fr Jan 5, 2021
f0de09e
Merge hg-resolver
f-fr Jan 6, 2021
2e31e09
Add documentation for hg to SPEC.md
f-fr Jan 6, 2021
8e8fffe
Merge master
f-fr Jun 30, 2021
a1aae57
Merge hg-resolver
f-fr Jun 30, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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:
Expand All @@ -60,14 +63,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:
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
25 changes: 22 additions & 3 deletions docs/shard.yml.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,21 @@ Extends the _git_ resolver, and acts exactly like it.
+
*Example:* _bitbucket: tom/library_

*hg*::

A Mercurial repository URL (string).
+
The URL may be [any protocol](https://www.mercurial-scm.org/repo/hg/help/clone)
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_

*version*::
A version requirement (string).
+
Expand All @@ -342,13 +357,17 @@ the _~>_ operator has a special meaning, best shown by example:
--

*branch*::
Install the specified branch of a git dependency (string).
Install the specified branch of a git dependency or the named branch
of a mercurial dependency (string).

*commit*::
Install the specified commit of a git dependency (string).
Install the specified commit of a git or mercurial dependency (string).

*tag*::
Install the specified tag of a git dependency (string).
Install the specified tag of a git or mercurial dependency (string).

*bookmark*::
Install the specified bookmark of a mercurial dependency (string).

== Example:

Expand Down
87 changes: 87 additions & 0 deletions spec/support/factories.cr
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,79 @@ def checkout_git_branch(project, branch)
end
end

def create_hg_repository(project, *versions)
Dir.cd(tmp_path) do
Shards::HgResolver.hg "init", 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
Shards::HgResolver.hg "add", "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
Shards::HgResolver.hg "clone", hg_url(upstream), 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"
Shards::HgResolver.hg "add", "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
Shards::HgResolver.hg "tag", "-u", "Your Name <[email protected]>", 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")
Shards::HgResolver.hg "commit", "-u", "Your Name <[email protected]>", "-A", "-m", message
end
end

def checkout_new_hg_bookmark(project, branch)
Dir.cd(hg_path(project)) do
Shards::HgResolver.hg "bookmark", branch
end
end

def checkout_new_hg_branch(project, branch)
Dir.cd(hg_path(project)) do
Shards::HgResolver.hg "branch", branch
end
end

def checkout_hg_rev(project, rev)
Dir.cd(hg_path(project)) do
Shards::HgResolver.hg "update", "-C", 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
Expand Down Expand Up @@ -119,6 +192,20 @@ def git_path(project)
File.join(tmp_path, project.to_s)
end

def hg_commits(project, rev = ".")
Dir.cd(hg_path(project)) do
Shards::HgResolver.hg("log", "--template", "{node}\n", "-r", rev).as(String).strip.lines
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
Expand Down
8 changes: 8 additions & 0 deletions spec/support/requirement.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ 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

def version(version)
Shards::Version.new(version)
end
Expand Down
189 changes: 189 additions & 0 deletions spec/unit/hg_resolver_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
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 "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
create_hg_tag "library", "99.9.9"
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:[email protected]/Repo/Path.git?Shallow=true")[1].should eq "https://User:[email protected]/repo/path.git?Shallow=true"
GitResolver.normalize_key_source("git", "HTTPS://User:[email protected]/Repo/Path.Git?Shallow=true")[1].should eq "https://User:[email protected]/repo/path.git?Shallow=true"
GitResolver.normalize_key_source("git", "HTTPS://User:[email protected]/Repo/Path?Shallow=true")[1].should eq "https://User:[email protected]/repo/path.git?Shallow=true"
GitResolver.normalize_key_source("git", "HTTPS://User:[email protected]/Repo/Path?Shallow=true")[1].should eq "https://User:[email protected]/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"})

# don't change protocol from ssh
GitResolver.normalize_key_source("git", "ssh://[email protected]/Repo/Path?Shallow=true").should eq({"git", "ssh://[email protected]/Repo/Path?Shallow=true"})
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("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
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]}"])
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
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",
"[email protected]:foo/bar",
"[email protected]: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",
"[email protected]:foo/bar2",
"[email protected]: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
Loading