loca 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d72e91059bc88a4090bd9eb2c6c8f1e45c13704
4
+ data.tar.gz: e8fda9734eaf1fc2446fdb85490e792db395d6ee
5
+ SHA512:
6
+ metadata.gz: 71eb60e85d247ce2ee9e14905c685283632262618815a1dccf80af8039a449302f0945af2b40f144358e84ecad73129d72f1adac0183bbd48f6bd4439da611b3
7
+ data.tar.gz: 129bf8eb437ae3a064b5f9a77e284738eb961f8f6a55ba4c9390b6eba4a91623a73b8664fe200394dd414c083ca6b1e0d16ae348dba6686656889555455258ec
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'loca'
4
+
5
+ Loca::CLI.start(ARGV)
@@ -0,0 +1,14 @@
1
+ # Require all runtime dependencies first
2
+ require 'thor'
3
+ require 'colorize'
4
+ require 'mixlib/shellout'
5
+ require 'addressable/uri'
6
+
7
+ # Start by requiring files with no dependencies on other files, then files with
8
+ # only external gem runtime dependencies (specified above), then files with
9
+ # dependencies on other files within the loca project itself
10
+ require 'loca/version'
11
+ require 'loca/error'
12
+ require 'loca/url'
13
+ require 'loca/git'
14
+ require 'loca/cli'
@@ -0,0 +1,47 @@
1
+ module Loca
2
+ class CLI < Thor
3
+ include Thor::Actions # https://github.com/erikhuda/thor/wiki/Actions
4
+
5
+ map %w(--version -v) => :__print_version
6
+
7
+ desc '--version, -v', 'print the version'
8
+ def __print_version
9
+ puts Loca::VERSION
10
+ end
11
+
12
+ desc 'c URL', 'Check out a pull request locally'
13
+ method_option :delete, aliases: '-d', desc: 'Delete the branch instead of creating it'
14
+ def c(pasted_url)
15
+ return d(pasted_url) if options[:delete]
16
+
17
+ git = Loca::Git.new(pasted_url)
18
+ branch_name = git.branch_name
19
+
20
+ if git.first_time_creating? || yes?("WARN: Branch '#{branch_name}' "\
21
+ ' already exists. Overwrite? (n)', :yellow)
22
+ git.fetch
23
+ git.checkout
24
+ say "Checked out #{branch_name}!", :green
25
+ else
26
+ fail Loca::Error::GitAborted, 'Git checkout aborted!'
27
+ end
28
+ end
29
+
30
+ desc 'd URL', 'Delete the local branch for that URL'
31
+ def d(pasted_url)
32
+ git = Loca::Git.new(pasted_url)
33
+ branch_name = git.branch_name
34
+
35
+ git.delete
36
+ say "Deleted #{branch_name}!", :green
37
+ end
38
+
39
+ private # rubocop:disable Lint/UselessAccessModifier
40
+
41
+ no_commands do # Thor primitive(s) that we want to stub in RSpec
42
+ def yes?(*args)
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ # Using http://ablogaboutcode.com/2011/01/03/using-custom-error-messages-for-cleaner-code/
2
+ # and http://www.simonewebdesign.it/how-to-set-default-message-exception/
3
+ module Loca
4
+ module Error
5
+ class Base < StandardError
6
+ def initialize(message)
7
+ # To get the message in red
8
+ $stderr.puts message.red
9
+ @message = message
10
+ end
11
+ end
12
+
13
+ InvalidURL = Class.new(Base)
14
+
15
+ GitStdErrDetected = Class.new(Base)
16
+ UnstashedFilesFound = Class.new(Base)
17
+ OnlyOneBranch = Class.new(Base)
18
+ RemoteNotSet = Class.new(Base)
19
+ GitAborted = Class.new(Base)
20
+ end
21
+ end
@@ -0,0 +1,117 @@
1
+ module Loca
2
+ class Git
3
+ include Thor::Actions
4
+
5
+ attr_reader :branch_name
6
+
7
+ def initialize(url, remote = nil)
8
+ @url = Loca::URL.new(url)
9
+ @branch_name = @url.branch_name
10
+
11
+ ensure_git_repo
12
+ ensure_no_unstashed_files
13
+ @remote_name = remote || extract_remote_name
14
+ end
15
+
16
+ def delete
17
+ # Cannot delete a branch you are currently on:
18
+ checkout_another_branch if @branch_name == current_branch
19
+ git "branch -D #{@branch_name}"
20
+ end
21
+
22
+ def checkout
23
+ git "checkout #{@branch_name}", false # prints to stderr for some reason
24
+ end
25
+
26
+ def fetch
27
+ # To avoid fatal error: Refusing to fetch into current branch
28
+ delete unless first_time_creating?
29
+ # Performs `git fetch upstream pull/PR_NUMBER/head:BRANCH_NAME`
30
+ git "fetch #{@remote_name} pull/#{@url.pr_num}/head:#{@branch_name}", false # shellout has stderr for some reason
31
+ end
32
+
33
+ def first_time_creating? # Keep this a public method so we can prompt the user for overwrite
34
+ branches.include?(@branch_name) ? false : true
35
+ end
36
+
37
+ # Example
38
+ #
39
+ # git_match_http?("https://github.com/smoll/loca.git", "https://github.com/smoll/loca/pull/1")
40
+ # => true
41
+ def git_match_http?(git, http)
42
+ format = lambda do |uri| # Strip off uri scheme & trailing '.git'
43
+ uri.sub('https://', '')
44
+ .sub('http://', '')
45
+ .sub('git://', '')
46
+ .sub(/.git$/, '')
47
+ end
48
+ format.call(http).start_with?(format.call(git))
49
+ end
50
+
51
+ private
52
+
53
+ def git(cmd, fail_on_stderr = true)
54
+ shellout = Mixlib::ShellOut.new "git #{cmd}"
55
+ shellout.run_command
56
+ return shellout.stdout if shellout.stderr.empty?
57
+ if fail_on_stderr
58
+ fail Loca::Error::GitStdErrDetected, "#{shellout.stderr.strip}"
59
+ else
60
+ $stderr.puts shellout.stderr.strip.yellow
61
+ end
62
+ shellout.stdout.strip
63
+ end
64
+
65
+ def branches
66
+ git("for-each-ref refs/heads/ --format='%(refname:short)'").split("\n")
67
+ end
68
+
69
+ def current_branch
70
+ git('rev-parse --abbrev-ref HEAD').strip
71
+ end
72
+
73
+ def ensure_git_repo
74
+ git 'rev-parse'
75
+ end
76
+
77
+ def ensure_no_unstashed_files
78
+ val = git 'status --porcelain'
79
+ fail Loca::Error::UnstashedFilesFound, 'Commit or stash your files before continuing!' unless val.empty?
80
+ end
81
+
82
+ def checkout_another_branch
83
+ another = branches.find { |branch| branch != current_branch }
84
+ fail Loca::Error::OnlyOneBranch, 'No other branch to checkout!' unless another
85
+ git "checkout #{another}", false # prints to stderr for some reason
86
+ end
87
+
88
+ def remote_mapping
89
+ names = git('remote show -n').split("\n")
90
+ mapping = {}
91
+ names.each do |name|
92
+ mapping[name] = git("config --get remote.#{name}.url").strip
93
+ end
94
+ mapping
95
+ end
96
+
97
+ def extract_remote_name
98
+ match = remote_mapping.find { |_name, url| git_match_http?(url, @url.to_s) }
99
+ unless match
100
+ if yes?("Remote #{(@url)} not set. Would you like to set it as 'auto_loca_remote'?")
101
+ set_remote
102
+ else
103
+ fail Loca::Error::RemoteNotSet, "You must set the repo (#{@url}) as a remote "\
104
+ "(see `git remote -v'). All remotes: #{remote_mapping}"
105
+ end
106
+ end
107
+ match.first
108
+ end
109
+
110
+ def set_remote
111
+ uri = Addressable::URI.parse(@url)
112
+ uri.path = uri.path.split('/')[0..2].join('/')
113
+ remote_url = "#{uri}.git"
114
+ git "remote add upstream #{remote_url}"
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,37 @@
1
+ module Loca
2
+ class URL
3
+ attr_reader :branch_name
4
+ attr_reader :pr_num
5
+
6
+ def initialize(url)
7
+ @url = url
8
+ ensure_well_formed
9
+
10
+ @branch_name = extract_branch_name
11
+ @pr_num = extract_pr_num
12
+ end
13
+
14
+ def to_s
15
+ @url
16
+ end
17
+
18
+ def ensure_well_formed
19
+ # TODO: add more checks via Addressable::URI
20
+ segments = URI(@url).path.split('/')
21
+ int = Integer(segments[-1]) rescue false # replace with coercible gem?
22
+ pull = segments[-2] == 'pull'
23
+
24
+ fail Loca::Error::InvalidURL, "Doesn't appear to be a well-formed URL: #{@url}" unless int && pull
25
+ end
26
+
27
+ private
28
+
29
+ def extract_branch_name
30
+ "PULL_#{extract_pr_num}"
31
+ end
32
+
33
+ def extract_pr_num
34
+ URI(@url).path.split('/').last
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Loca
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,43 @@
1
+ describe 'Checking out a GitHub PR locally' do
2
+ let(:pr_url) { 'https://github.com/octocat/Spoon-Knife/pull/4865' }
3
+ let(:expected_branch_name) { 'PULL_4865' }
4
+
5
+ before(:each) do
6
+ clone_test_repo
7
+ cd_to_cloned_dir
8
+ end
9
+
10
+ after(:each) { teardown }
11
+
12
+ context "the repo exists as the 'upstream' remote" do
13
+ before(:each) { set_upstream }
14
+
15
+ it 'checks out then deletes' do
16
+ shellout! "loca c #{pr_url}"
17
+ expect(current_branch.strip).to eq expected_branch_name
18
+ shellout! "loca c #{pr_url} -d"
19
+ expect(current_branch.strip).to_not eq expected_branch_name
20
+ end
21
+
22
+ it 'checks out then prompts to overwrite' do
23
+ shellout! "loca c #{pr_url}"
24
+ expect(current_branch.strip).to eq expected_branch_name
25
+ shellout! "loca c #{pr_url}", input: 'yes'
26
+ expect(current_branch.strip).to eq expected_branch_name
27
+ end
28
+ end
29
+
30
+ context 'the repo does not exist as a remote' do
31
+ it 'fails to checkout' do
32
+ expect { shellout! "loca c #{pr_url}" }.to raise_error
33
+ expect(current_branch.strip).to_not eq expected_branch_name
34
+ end
35
+ end
36
+
37
+ context 'the wrong URL is supplied' do
38
+ it 'fails to checkout' do
39
+ expect { shellout! 'loca c https://github.com/octocat/Spoon-Knife/wrong/4865' }.to raise_error
40
+ expect(current_branch.strip).to_not eq expected_branch_name
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,69 @@
1
+ describe Loca::CLI do
2
+ let(:invalid_url) { 'https://github.com/fakez/fakezz/pull/1' }
3
+ let(:url) { 'https://github.com/smoll/loca/pull/1' }
4
+ subject { described_class.new }
5
+
6
+ before do # ensure we don't shell out to git
7
+ allow_any_instance_of(Loca::Git).to receive(:fetch)
8
+ allow_any_instance_of(Loca::Git).to receive(:checkout)
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
28
+
29
+ it 'raises an error when the branch exists and the user says no to overwrite' do
30
+ expect_any_instance_of(Loca::Git).to receive(:branch_name).and_return 'PULL_1'
31
+ expect_any_instance_of(Loca::Git).to receive(:first_time_creating?).and_return false
32
+ silence(:stdout) { allow_any_instance_of(described_class).to receive(:yes?).and_return false }
33
+
34
+ expect { silence(:stderr) { described_class.start(%W(c #{url})) } }.to raise_error
35
+ end
36
+
37
+ it 'raises an error when an invalid repo is supplied' do
38
+ expect { silence(:stderr) { described_class.start(%W(c #{invalid_url})) } }.to raise_error
39
+ end
40
+
41
+ it 'deletes when the delete flag is appended' do
42
+ expect_any_instance_of(Loca::Git).to receive(:branch_name).and_return 'PULL_1'
43
+ expect_any_instance_of(Loca::Git).to receive(:delete)
44
+
45
+ output = capture(:stdout) { described_class.start(%W(c #{url} -d)) }.strip
46
+ expect(output).to eq 'Deleted PULL_1!'
47
+ end
48
+ end
49
+
50
+ describe 'd' do
51
+ it 'deletes' do
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
63
+
64
+ describe '--version' do
65
+ it 'returns the correct version' do
66
+ expect(capture(:stdout) { described_class.start(%w(-v)) }.strip).to eq(Loca::VERSION)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,152 @@
1
+ describe Loca::Git do
2
+ let(:pid) { 1 } # Pull request ID
3
+ let(:url) { "https://github.com/smoll/loca/pull/#{pid}" }
4
+ let(:remote_name) { 'loca-fake-remote' }
5
+ let(:remote_url) { 'https://github.com/smoll/loca.git' }
6
+ subject { described_class.new(url, remote_name) }
7
+
8
+ let(:expected_branch_name) { "PULL_#{pid}" }
9
+ let(:branches_without_expected) { ['master'] }
10
+ let(:branches_with_expected) { branches_without_expected << expected_branch_name }
11
+
12
+ describe '#git' do # private method that shells out to git
13
+ it 'raises an error when an invalid git command is supplied' do
14
+ expect { capture(:stderr) { subject.send(:git, 'not-a-git-command') } }.to raise_error
15
+ end
16
+
17
+ it 'prints to stderr when an invalid git command is supplied, but we do not want to fail on stderr' do
18
+ output = capture(:stderr) { subject.send(:git, 'not-a-git-command', false) }
19
+ expect(output).to include "git: 'not-a-git-command' is not a git command"
20
+ end
21
+ end
22
+
23
+ describe '#branches' do # private method
24
+ it 'returns all local branches' do
25
+ cmd = "for-each-ref refs/heads/ --format='%(refname:short)'"
26
+ expect(subject).to receive(:git).with(cmd).and_return 'a_branch'
27
+ expect(subject.send(:branches)).to eq ['a_branch']
28
+ end
29
+ end
30
+
31
+ describe '#remote_mapping' do # private method
32
+ it "returns all of the repo's remotes" do
33
+ expect(subject).to receive(:git).with('remote show -n').and_return remote_name
34
+ expect(subject).to receive(:git).with("config --get remote.#{remote_name}.url").and_return remote_url
35
+ expect(subject.send(:remote_mapping)).to eq(remote_name => remote_url)
36
+ end
37
+ end
38
+
39
+ describe '#extract_remote_name' do # private method
40
+ it 'returns the local repo remote name corresponding to the Pull Request URL' do
41
+ expect(subject).to receive(:remote_mapping).and_return(remote_name => remote_url) # investigate this
42
+
43
+ expect(subject.send(:extract_remote_name)).to eq remote_name
44
+ end
45
+ end
46
+
47
+ describe '#current_branch' do # private method
48
+ it 'returns the current branch' do
49
+ expect(subject).to receive(:git).with('rev-parse --abbrev-ref HEAD').and_return "branch_name\n"
50
+ expect(subject.send(:current_branch)).to eq 'branch_name'
51
+ end
52
+ end
53
+
54
+ describe '#checkout' do # private method
55
+ it 'checks out the expected branch' do
56
+ expect(subject).to receive(:git).with("checkout #{expected_branch_name}", false).once
57
+ subject.send(:checkout)
58
+ end
59
+ end
60
+
61
+ describe '#checkout_another_branch' do # private method
62
+ it 'checks out a branch that is not the current one' do
63
+ allow(subject).to receive(:branches).and_return %w(branch1 branch2)
64
+ allow(subject).to receive(:current_branch).and_return 'branch1'
65
+ expect(subject).to receive(:git).with('checkout branch2', false).once
66
+ silence(:stdout) { subject.send(:checkout_another_branch) }
67
+ end
68
+
69
+ it 'raises an error when there is no other branch to check out' do
70
+ allow(subject).to receive(:branches).and_return ['branch1']
71
+ allow(subject).to receive(:current_branch).and_return 'branch1'
72
+
73
+ expect { silence(:stderr) { subject.send(:checkout_another_branch) } }.to raise_error
74
+ end
75
+ end
76
+
77
+ describe '#fetch' do
78
+ before do
79
+ allow(subject).to receive(:git) # ensure we don't actually shell out to `git`
80
+ end
81
+
82
+ it 'deletes before fetching when the branch already exists' do
83
+ allow(subject).to receive(:branches).and_return branches_with_expected
84
+ expect(subject).to receive(:delete).once
85
+
86
+ subject.fetch
87
+ end
88
+
89
+ it 'fetches the remote pull request' do
90
+ allow(subject).to receive(:branches).and_return branches_without_expected
91
+
92
+ # Investigate why this git command prints to $stderr, i.e. we must pass false as the 2nd arg:
93
+ expect(subject).to receive(:git).with("fetch #{remote_name} pull/#{pid}/head:#{expected_branch_name}", false)
94
+
95
+ subject.fetch
96
+ end
97
+ end
98
+
99
+ describe '#delete' do # should attempt to delete a branch with name 'PULL_1'
100
+ it 'raises an error when the branch does not exist' do
101
+ allow(subject).to receive(:branches).and_return branches_without_expected
102
+ expect { silence(:stderr) { subject.delete } }.to raise_error
103
+ end
104
+
105
+ it 'deletes the branch when it exists' do
106
+ allow(subject).to receive(:branches).and_return branches_with_expected
107
+ allow(subject).to receive(:current_branch).and_return('master')
108
+ expect(subject).to receive(:git).with("branch -D #{expected_branch_name}").once
109
+
110
+ subject.delete
111
+ end
112
+
113
+ it 'checks out another branch if attempting to delete current branch' do
114
+ allow(subject).to receive(:branches).and_return branches_with_expected
115
+ allow(subject).to receive(:current_branch).and_return expected_branch_name
116
+
117
+ expect(subject).to receive(:checkout_another_branch).once
118
+ expect(subject).to receive(:git).with("branch -D #{expected_branch_name}").once
119
+
120
+ subject.delete
121
+ end
122
+ end
123
+
124
+ describe '#first_time_creating?' do
125
+ it 'returns false when the branch already exists' do
126
+ allow(subject).to receive(:branches).and_return branches_with_expected
127
+ expect(subject.first_time_creating?).to eq false
128
+ end
129
+
130
+ it 'returns true when the branch does not exist' do
131
+ allow(subject).to receive(:branches).and_return branches_without_expected
132
+ expect(subject.first_time_creating?).to eq true
133
+ end
134
+ end
135
+
136
+ describe '#git_match_http?' do
137
+ it 'returns true when matching urls are supplied' do
138
+ git_url = 'https://github.com/smoll/loca.git'
139
+ expect(subject.git_match_http?(git_url, url)).to eq true
140
+ end
141
+
142
+ it 'returns true when different URI schemes but matching urls are supplied' do
143
+ git_url = 'git://github.com/smoll/loca.git'
144
+ expect(subject.git_match_http?(git_url, url)).to eq true
145
+ end
146
+
147
+ it 'returns false when non-matching urls are supplied' do
148
+ git_url = 'git://github.com/someoneelse/loca.git'
149
+ expect(subject.git_match_http?(git_url, url)).to eq false
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,47 @@
1
+ describe Loca::URL do
2
+ let(:pid) { 1 } # Pull request ID
3
+ let(:url) { "https://github.com/smoll/loca/pull/#{pid}" }
4
+ subject { described_class.new(url) }
5
+
6
+ let(:expected_branch_name) { "PULL_#{pid}" }
7
+ let(:expected_pr_num) { pid.to_s }
8
+
9
+ describe '#initialize' do
10
+ it 'does not raise an error for a valid URL' do
11
+ expect { Loca::URL.new(url) }.to_not raise_error
12
+ end
13
+ # it 'does not raise an error for a GitHub PR URL with additional segments' do
14
+ # expect { Loca::URL.new("#{url}/something/something") }.to_not raise_error # TODO: technical improvement
15
+ # end
16
+ it 'raises an error for a non-URL' do
17
+ expect { silence(:stderr) { Loca::URL.new('not a URL') } }.to raise_error
18
+ end
19
+ it 'raises an error for a non-GitHub URL' do
20
+ expect { silence(:stderr) { Loca::URL.new('http://bad-url-here.com') } }.to raise_error
21
+ end
22
+ it 'raises an error for a GitHub non-PR URL' do
23
+ expect { silence(:stderr) { Loca::URL.new('https://github.com/smoll/loca/something/something') } }.to raise_error
24
+ end
25
+ it 'raises an error for a GitHub URL without an integer in the PID location' do
26
+ expect { silence(:stderr) { Loca::URL.new('https://github.com/smoll/loca/pull/NaN') } }.to raise_error
27
+ end
28
+ end
29
+
30
+ describe '#to_s' do
31
+ it 'returns the url passed to the constructor' do
32
+ expect(subject.to_s).to eq url
33
+ end
34
+ end
35
+
36
+ describe '#extract_branch_name' do # private method
37
+ it 'returns the expected branch name' do
38
+ expect(subject.send(:extract_branch_name)).to eq expected_branch_name
39
+ end
40
+ end
41
+
42
+ describe '#extract_pr_num' do # private method
43
+ it 'returns the expected branch name' do
44
+ expect(subject.send(:extract_pr_num)).to eq "#{pid}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,84 @@
1
+ # Using https://raw.githubusercontent.com/bbatsov/rubocop/master/spec/spec_helper.rb as a guide
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+ require 'codeclimate-test-reporter'
5
+
6
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
+ SimpleCov::Formatter::HTMLFormatter,
8
+ Coveralls::SimpleCov::Formatter,
9
+ CodeClimate::TestReporter::Formatter
10
+ ]
11
+ SimpleCov.start
12
+
13
+ require 'bundler/setup'
14
+ Bundler.setup # From http://stackoverflow.com/a/4402193
15
+
16
+ require 'loca'
17
+
18
+ # Requires supporting files with custom matchers and macros, etc,
19
+ # in ./support/ and its subdirectories.
20
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
21
+
22
+ RSpec.configure do |config|
23
+ config.include Features::GitHubHelper
24
+
25
+ config.order = :random
26
+
27
+ config.expect_with :rspec do |expectations|
28
+ expectations.syntax = :expect # Disable `should`
29
+ end
30
+
31
+ config.mock_with :rspec do |mocks|
32
+ mocks.syntax = :expect # Disable `should_receive` and `stub`
33
+ mocks.verify_partial_doubles = true # Avoid La La Land,
34
+ # read http://wegowise.github.io/blog/2014/09/03/rspec-verifying-doubles/
35
+ end
36
+
37
+ config.before do
38
+ ARGV.replace []
39
+ 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
+ end
@@ -0,0 +1,55 @@
1
+ require 'mixlib/shellout'
2
+ require 'fileutils'
3
+
4
+ module Features
5
+ module GitHubHelper
6
+ def teardown
7
+ return_to_original_wd
8
+ FileUtils.rm_rf absolute_path
9
+ end
10
+
11
+ def cd_to_cloned_dir
12
+ @original_wd = Dir.pwd
13
+ Dir.chdir(absolute_path)
14
+ end
15
+
16
+ def return_to_original_wd
17
+ Dir.chdir(@original_wd)
18
+ end
19
+
20
+ def clone_test_repo
21
+ shellout! "git clone https://github.com/smoll/Spoon-Knife ./#{rel_path}"
22
+ end
23
+
24
+ def set_upstream
25
+ shellout! 'git remote add upstream https://github.com/octocat/Spoon-Knife.git'
26
+ end
27
+
28
+ def current_branch
29
+ shellout! 'git rev-parse --abbrev-ref HEAD'
30
+ end
31
+
32
+ def shellout!(cmd, opts = {})
33
+ opts = {
34
+ input: nil
35
+ }.merge(opts)
36
+ sh = opts[:input].nil? ? Mixlib::ShellOut.new(cmd.to_s) : Mixlib::ShellOut.new(cmd.to_s, input: opts[:input].to_s)
37
+ sh.run_command
38
+
39
+ sh.error! if opts[:input].nil? # NOTE: CLI exits non-zero if waiting on user input
40
+ sh.stdout
41
+ end
42
+
43
+ private
44
+
45
+ def rel_path
46
+ absolute_path.split(Dir.pwd.to_s)[1].sub('/', '')
47
+ end
48
+
49
+ def absolute_path
50
+ path = File.expand_path('../../../tmp/cloned', __FILE__)
51
+ FileUtils.mkdir_p path
52
+ path
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,224 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loca
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - smoll
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: mixlib-shellout
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.0.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: addressable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.7
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.3.7
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.4'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 10.4.2
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '10.4'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 10.4.2
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.29.0
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 0.29.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 3.2.0
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 3.2.0
131
+ - !ruby/object:Gem::Dependency
132
+ name: coveralls
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 0.7.9
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: 0.7.9
145
+ - !ruby/object:Gem::Dependency
146
+ name: codeclimate-test-reporter
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: 0.4.6
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: 0.4.6
159
+ - !ruby/object:Gem::Dependency
160
+ name: simplecov
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: 0.9.1
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: 0.9.1
173
+ description:
174
+ email:
175
+ - mollah@gmail.com
176
+ executables:
177
+ - loca
178
+ extensions: []
179
+ extra_rdoc_files: []
180
+ files:
181
+ - bin/loca
182
+ - lib/loca.rb
183
+ - lib/loca/cli.rb
184
+ - lib/loca/error.rb
185
+ - lib/loca/git.rb
186
+ - lib/loca/url.rb
187
+ - lib/loca/version.rb
188
+ - spec/e2e/github_spec.rb
189
+ - spec/loca/cli_spec.rb
190
+ - spec/loca/git_spec.rb
191
+ - spec/loca/url_spec.rb
192
+ - spec/spec_helper.rb
193
+ - spec/support/features/github_helper.rb
194
+ homepage: https://github.com/smoll/loca
195
+ licenses:
196
+ - MIT
197
+ metadata: {}
198
+ post_install_message:
199
+ rdoc_options: []
200
+ require_paths:
201
+ - lib
202
+ required_ruby_version: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ required_rubygems_version: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: '0'
212
+ requirements: []
213
+ rubyforge_project:
214
+ rubygems_version: 2.4.6
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: CLI for checking out GitHub Pull Requests locally
218
+ test_files:
219
+ - spec/e2e/github_spec.rb
220
+ - spec/loca/cli_spec.rb
221
+ - spec/loca/git_spec.rb
222
+ - spec/loca/url_spec.rb
223
+ - spec/spec_helper.rb
224
+ - spec/support/features/github_helper.rb