loca 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/loca +31 -2
- data/lib/loca.rb +5 -9
- data/lib/loca/cli.rb +59 -33
- data/lib/loca/error.rb +7 -14
- data/lib/loca/git/branch_creator.rb +55 -0
- data/lib/loca/git/branch_deleter.rb +32 -0
- data/lib/loca/git/common.rb +42 -0
- data/lib/loca/url/parser.rb +61 -0
- data/lib/loca/url/validator.rb +45 -0
- data/lib/loca/utils.rb +12 -0
- data/lib/loca/version.rb +1 -1
- data/spec/loca/cli_spec.rb +33 -55
- data/spec/loca/git/branch_creator_spec.rb +18 -0
- data/spec/loca/git/common_spec.rb +46 -0
- data/spec/loca/url/parser_spec.rb +88 -0
- data/spec/loca/url/validator_spec.rb +26 -0
- data/spec/loca/utils_spec.rb +40 -0
- data/spec/spec_helper.rb +13 -59
- data/spec/support/matchers/terminate.rb +37 -0
- metadata +63 -41
- data/lib/loca/git.rb +0 -117
- data/lib/loca/url.rb +0 -37
- data/spec/e2e/github_spec.rb +0 -43
- data/spec/loca/git_spec.rb +0 -152
- data/spec/loca/url_spec.rb +0 -47
- data/spec/support/features/github_helper.rb +0 -55
data/lib/loca/utils.rb
ADDED
data/lib/loca/version.rb
CHANGED
data/spec/loca/cli_spec.rb
CHANGED
@@ -1,69 +1,47 @@
|
|
1
1
|
describe Loca::CLI do
|
2
|
-
let(:
|
3
|
-
let(:
|
4
|
-
|
2
|
+
let(:cli) { described_class.new(argv, stdin, stdout, stderr) }
|
3
|
+
let(:stdin) { StringIO.new } # from http://stackoverflow.com/a/16507814/3456726
|
4
|
+
let(:stdout) { StringIO.new }
|
5
|
+
let(:stderr) { StringIO.new }
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
describe 'c' do
|
12
|
-
it 'fetches and checks out the branch locally' do
|
13
|
-
expect_any_instance_of(Loca::Git).to receive(:branch_name).and_return 'PULL_1'
|
14
|
-
expect_any_instance_of(Loca::Git).to receive(:first_time_creating?).and_return true
|
15
|
-
|
16
|
-
output = capture(:stdout) { described_class.start(%W(c #{url})) }.strip
|
17
|
-
expect(output).to end_with 'Checked out PULL_1!'
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'fetches and overwrites existing branch if the user says yes to overwrite' do
|
21
|
-
expect_any_instance_of(Loca::Git).to receive(:branch_name).and_return 'PULL_1'
|
22
|
-
expect_any_instance_of(Loca::Git).to receive(:first_time_creating?).and_return false
|
23
|
-
silence(:stdout) { allow_any_instance_of(described_class).to receive(:yes?).and_return true }
|
24
|
-
|
25
|
-
output = capture(:stdout) { described_class.start(%W(c #{url})) }.strip
|
26
|
-
expect(output).to end_with 'Checked out PULL_1!'
|
27
|
-
end
|
7
|
+
describe "#execute!" do
|
8
|
+
context "-v" do
|
9
|
+
let(:argv) { ["-v"] }
|
28
10
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
11
|
+
it "returns the version" do
|
12
|
+
expect do
|
13
|
+
cli.execute!
|
14
|
+
expect(stdout.string).to eq Loca::VERSION
|
15
|
+
end.to raise_error(SystemExit) # because execute! calls `exit 0` (or 1, etc.)
|
16
|
+
# from http://stackoverflow.com/a/28047771/3456726
|
17
|
+
end
|
33
18
|
|
34
|
-
|
19
|
+
it "exits cleanly" do
|
20
|
+
expect { cli.execute! }.to terminate.with_code 0
|
21
|
+
end
|
35
22
|
end
|
36
23
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
24
|
+
context "-h" do
|
25
|
+
let(:argv) { ["-h"] }
|
40
26
|
|
41
|
-
|
42
|
-
|
43
|
-
|
27
|
+
it "returns the version" do
|
28
|
+
expect do
|
29
|
+
cli.execute!
|
30
|
+
expect(stdout.string).to include "Usage: loca <url> [options]"
|
31
|
+
end.to raise_error(SystemExit)
|
32
|
+
end
|
44
33
|
|
45
|
-
|
46
|
-
|
34
|
+
it "exits cleanly" do
|
35
|
+
expect { cli.execute! }.to terminate.with_code 0
|
36
|
+
end
|
47
37
|
end
|
48
|
-
end
|
49
38
|
|
50
|
-
|
51
|
-
|
52
|
-
expect_any_instance_of(Loca::Git).to receive(:branch_name).and_return 'PULL_1'
|
53
|
-
expect_any_instance_of(Loca::Git).to receive(:delete)
|
54
|
-
|
55
|
-
output = capture(:stdout) { described_class.start(%W(d #{url})) }.strip
|
56
|
-
expect(output).to eq 'Deleted PULL_1!'
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'raises an error when an invalid repo is supplied' do
|
60
|
-
expect { silence(:stderr) { described_class.start(%W(d #{invalid_url})) } }.to raise_error
|
61
|
-
end
|
62
|
-
end
|
39
|
+
context "invalid option" do
|
40
|
+
let(:argv) { ["--invalid-option"] }
|
63
41
|
|
64
|
-
|
65
|
-
|
66
|
-
|
42
|
+
it "exits 1" do
|
43
|
+
expect { cli.execute! }.to terminate.with_code 1
|
44
|
+
end
|
67
45
|
end
|
68
46
|
end
|
69
47
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
describe Loca::Git::BranchCreator do
|
2
|
+
subject { described_class.new("1337", "PULL_1337", "loca_r_smoll", "https://github.com/smoll/loca") }
|
3
|
+
|
4
|
+
# This is probably better off being functionally tested
|
5
|
+
# Otherwise, we end up stubbing out specific `git` commands which is very brittle
|
6
|
+
# See https://sethvargo.com/unit-and-functional-testing-git-with-rspec/
|
7
|
+
|
8
|
+
describe "#add_remote" do
|
9
|
+
it "uses and avoids duplicating a preexisting remote" do
|
10
|
+
expect(subject).to receive(:git).with("remote show -n").and_return "origin\n"
|
11
|
+
expect(subject).to receive(:git)
|
12
|
+
.with("config --get remote.origin.url")
|
13
|
+
.and_return "https://github.com/smoll/loca\n"
|
14
|
+
|
15
|
+
subject.send :add_remote
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
describe Loca::Git::Common do
|
2
|
+
let(:dummy_class) { Class.new { include Loca::Git::Common } }
|
3
|
+
subject { dummy_class.new }
|
4
|
+
|
5
|
+
describe "#git" do
|
6
|
+
context "nonexistent command" do
|
7
|
+
let(:command) { "fakegitcmd" }
|
8
|
+
|
9
|
+
it "raises an error" do
|
10
|
+
expect { subject.git(command) }.to raise_error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#git_urls_match?" do
|
16
|
+
# These should all equate with one another:
|
17
|
+
let(:remote_url) { "https://github.com/smoll/loca" }
|
18
|
+
let(:remote_url_with_git) { "https://github.com/smoll/loca.git" }
|
19
|
+
let(:remote_url_with_slash) { "https://github.com/smoll/loca/" }
|
20
|
+
|
21
|
+
it "matches when both have trailing '.git'" do
|
22
|
+
result = subject.git_urls_match?(remote_url_with_git, remote_url_with_git)
|
23
|
+
expect(result).to eq true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "matches when both lack trailing '.git'" do
|
27
|
+
result = subject.git_urls_match?(remote_url, remote_url)
|
28
|
+
expect(result).to eq true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "matches when only one has trailing '.git'" do
|
32
|
+
result = subject.git_urls_match?(remote_url_with_git, remote_url)
|
33
|
+
expect(result).to eq true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "matches when one has a trailing slash" do
|
37
|
+
result = subject.git_urls_match?(remote_url_with_git, remote_url_with_slash)
|
38
|
+
expect(result).to eq true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns false when they do not match" do
|
42
|
+
result = subject.git_urls_match?(remote_url, "https://github.com/smoll/locaz")
|
43
|
+
expect(result).to eq false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
describe Loca::URL::Parser do
|
2
|
+
subject { described_class.new(url) }
|
3
|
+
|
4
|
+
describe "#to_s" do
|
5
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337/" }
|
6
|
+
it "returns the URL passed to the constructor" do
|
7
|
+
expect(subject.to_s).to eq url
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#parse" do
|
12
|
+
let(:parsed) { subject.parse }
|
13
|
+
|
14
|
+
context "valid commits url" do
|
15
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337/commits" }
|
16
|
+
|
17
|
+
it "returns a hash" do
|
18
|
+
expect(parsed).to be_a Hash
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns the correct pull num" do
|
22
|
+
expect(parsed[:pull][:num].to_s).to eq "1337"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the correct pull url" do
|
26
|
+
expect(parsed[:pull][:url]).to eq "https://github.com/smoll/loca/pull/1337"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns the correct remote url" do
|
30
|
+
expect(parsed[:remote][:url]).to eq "https://github.com/smoll/loca.git"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns the expected branch name" do
|
34
|
+
expect(parsed[:branch_name]).to eq "PULL_1337"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "valid files url" do
|
39
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337/files" }
|
40
|
+
|
41
|
+
it "returns the correct remote url" do
|
42
|
+
expect(parsed[:remote][:url]).to eq "https://github.com/smoll/loca.git"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns the expected branch name" do
|
46
|
+
expect(parsed[:branch_name]).to eq "PULL_1337"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "valid pull url" do
|
51
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337" }
|
52
|
+
|
53
|
+
it "returns the correct remote url" do
|
54
|
+
expect(parsed[:remote][:url]).to eq "https://github.com/smoll/loca.git"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns the expected branch name" do
|
58
|
+
expect(parsed[:branch_name]).to eq "PULL_1337"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "valid pull url with trailing slash" do
|
63
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337/" }
|
64
|
+
|
65
|
+
it "returns the correct remote url" do
|
66
|
+
expect(parsed[:remote][:url]).to eq "https://github.com/smoll/loca.git"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns the expected branch name" do
|
70
|
+
expect(parsed[:branch_name]).to eq "PULL_1337"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "invalid host" do
|
75
|
+
let(:url) { "https://badhub.com/smoll/loca/pull/1337" }
|
76
|
+
it "raises an error" do
|
77
|
+
expect { parsed }.to raise_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "invalid url" do
|
82
|
+
let(:url) { "badhub.com" }
|
83
|
+
it "raises an error" do
|
84
|
+
expect { parsed }.to raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
describe Loca::URL::Validator do
|
2
|
+
subject { described_class.new(url) }
|
3
|
+
|
4
|
+
describe "#validate" do
|
5
|
+
context "good url" do
|
6
|
+
let(:url) { "https://github.com/smoll/loca/pull/1337/commits" }
|
7
|
+
it "does not raise an error" do
|
8
|
+
expect { subject.validate }.not_to raise_error
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "bad url" do
|
13
|
+
let(:url) { "bad-url.com" }
|
14
|
+
it "raises an error" do
|
15
|
+
expect { subject.validate }.to raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "invalid pull number" do
|
20
|
+
let(:url) { "https://github.com/smoll/loca/pull/xyz/commits" }
|
21
|
+
it "raises an error" do
|
22
|
+
expect { subject.validate }.to raise_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe Loca::Utils do
|
2
|
+
describe "#non_empty_string?" do
|
3
|
+
subject { Loca::Utils.non_empty_string?(param) }
|
4
|
+
|
5
|
+
context "nonempty string passed" do
|
6
|
+
let(:param) { "hi" }
|
7
|
+
it { is_expected.to eq true }
|
8
|
+
end
|
9
|
+
|
10
|
+
context "nonempty text after newline passed" do
|
11
|
+
let(:param) { "\nOK" }
|
12
|
+
it { is_expected.to eq true }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "whitespace string passed" do
|
16
|
+
let(:param) { " " }
|
17
|
+
it { is_expected.to eq false }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "newline string passed" do
|
21
|
+
let(:param) { "\n" }
|
22
|
+
it { is_expected.to eq false }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "integer passed" do
|
26
|
+
let(:param) { 1 }
|
27
|
+
it { is_expected.to eq false }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "float passed" do
|
31
|
+
let(:param) { 1.0 }
|
32
|
+
it { is_expected.to eq false }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "nil passed" do
|
36
|
+
let(:param) { nil }
|
37
|
+
it { is_expected.to eq false }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,27 +1,25 @@
|
|
1
1
|
# Using https://raw.githubusercontent.com/bbatsov/rubocop/master/spec/spec_helper.rb as a guide
|
2
|
-
require
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
]
|
11
|
-
SimpleCov.start
|
12
|
-
|
13
|
-
require
|
2
|
+
require "simplecov"
|
3
|
+
require "codeclimate-test-reporter"
|
4
|
+
|
5
|
+
# Sending to Coveralls requires no set up, is done by rake task
|
6
|
+
# Sending to Code Climate is set up here, but pushed in a rake task
|
7
|
+
# ref: https://github.com/codeclimate/ruby-test-reporter#using-with-parallel_tests
|
8
|
+
# ref: https://coveralls.zendesk.com/hc/en-us/articles/201769485-Ruby-Rails
|
9
|
+
SimpleCov.add_filter "vendor"
|
10
|
+
SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter]
|
11
|
+
SimpleCov.start CodeClimate::TestReporter.configuration.profile
|
12
|
+
|
13
|
+
require "bundler/setup"
|
14
14
|
Bundler.setup # From http://stackoverflow.com/a/4402193
|
15
15
|
|
16
|
-
require
|
16
|
+
require "loca"
|
17
17
|
|
18
18
|
# Requires supporting files with custom matchers and macros, etc,
|
19
19
|
# in ./support/ and its subdirectories.
|
20
20
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
21
21
|
|
22
22
|
RSpec.configure do |config|
|
23
|
-
config.include Features::GitHubHelper
|
24
|
-
|
25
23
|
config.order = :random
|
26
24
|
|
27
25
|
config.expect_with :rspec do |expectations|
|
@@ -37,48 +35,4 @@ RSpec.configure do |config|
|
|
37
35
|
config.before do
|
38
36
|
ARGV.replace []
|
39
37
|
end
|
40
|
-
|
41
|
-
# From https://raw.githubusercontent.com/erikhuda/thor/master/spec/helper.rb
|
42
|
-
# Captures the output for analysis later
|
43
|
-
#
|
44
|
-
# @example Capture `$stderr`
|
45
|
-
#
|
46
|
-
# output = capture(:stderr) { $stderr.puts "this is captured" }
|
47
|
-
#
|
48
|
-
# @param [Symbol] stream `:stdout` or `:stderr`
|
49
|
-
# @yield The block to capture stdout/stderr for.
|
50
|
-
# @return [String] The contents of $stdout or $stderr
|
51
|
-
# rubocop:disable Lint/Eval
|
52
|
-
def capture(stream)
|
53
|
-
begin
|
54
|
-
stream = stream.to_s
|
55
|
-
eval "$#{stream} = StringIO.new"
|
56
|
-
yield
|
57
|
-
result = eval("$#{stream}").string
|
58
|
-
ensure
|
59
|
-
eval("$#{stream} = #{stream.upcase}")
|
60
|
-
end
|
61
|
-
|
62
|
-
result
|
63
|
-
end
|
64
|
-
# rubocop:enable Lint/Eval
|
65
|
-
|
66
|
-
# This code was adapted from Ruby on Rails, available under MIT-LICENSE
|
67
|
-
# Copyright (c) 2004-2013 David Heinemeier Hansson
|
68
|
-
def silence_warnings
|
69
|
-
old_verbose, $VERBOSE = $VERBOSE, nil
|
70
|
-
yield
|
71
|
-
ensure
|
72
|
-
$VERBOSE = old_verbose
|
73
|
-
end
|
74
|
-
|
75
|
-
# Silences the output stream
|
76
|
-
#
|
77
|
-
# @example Silence `$stdout`
|
78
|
-
#
|
79
|
-
# silence(:stdout) { $stdout.puts "hi" }
|
80
|
-
#
|
81
|
-
# @param [IO] stream The stream to use such as $stderr or $stdout
|
82
|
-
# @return [nil]
|
83
|
-
alias :silence :capture # rubocop:disable Style/Alias
|
84
38
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec::Matchers.define :terminate do |_code|
|
2
|
+
actual = nil
|
3
|
+
|
4
|
+
def supports_block_expectations?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
match do |block|
|
9
|
+
begin
|
10
|
+
block.call
|
11
|
+
rescue SystemExit => e
|
12
|
+
actual = e.status
|
13
|
+
end
|
14
|
+
actual && actual == status_code
|
15
|
+
end
|
16
|
+
|
17
|
+
chain :with_code do |status_code|
|
18
|
+
@status_code = status_code
|
19
|
+
end
|
20
|
+
|
21
|
+
failure_message do |_block|
|
22
|
+
"expected block to call exit(#{status_code}) but exit" +
|
23
|
+
(actual.nil? ? " not called" : "(#{actual}) was called")
|
24
|
+
end
|
25
|
+
|
26
|
+
failure_message_when_negated do |_block|
|
27
|
+
"expected block not to call exit(#{status_code})"
|
28
|
+
end
|
29
|
+
|
30
|
+
description do
|
31
|
+
"expect block to call exit(#{status_code})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def status_code
|
35
|
+
@status_code ||= 0
|
36
|
+
end
|
37
|
+
end
|