loca 0.0.1 → 0.1.0
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.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8d346e967391656b7e5270109caa97349ebd2e7
|
4
|
+
data.tar.gz: 8d24248adeaba1b73089eac6abb8d4a0deb1ba65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a14f15ea2617100bc2a952d8b5e7a2348fa1b03c55e4071023f096ee12cf358ff374fd2f5024d6956955eca84e7b7ba6defbad2d9a544ee3409169ea016776f5
|
7
|
+
data.tar.gz: 33e0b60efc31955531b15c68d33b2184fbaa98e048828bfae87139f2349751ca6d59eacaccb2baaab9577fbeb6ca5d420d42cdde167de7a33bcc2536e6425927
|
data/bin/loca
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
# Based on https://github.com/colszowka/simplecov/issues/234
|
4
|
+
# and http://stackoverflow.com/a/20505441/3456726
|
5
|
+
if ENV["COVERAGE"]
|
6
|
+
require "simplecov"
|
4
7
|
|
5
|
-
|
8
|
+
# As described in the issue, every process must have an unique name:
|
9
|
+
SimpleCov.command_name "loca #{ARGV.join(' ')} (Aruba)"
|
10
|
+
|
11
|
+
# When running with aruba simplecov was using /tmp/aruba as the root folder.
|
12
|
+
# This is to force using the project folder
|
13
|
+
SimpleCov.root(File.join(File.expand_path(File.dirname(__FILE__)), ".."))
|
14
|
+
|
15
|
+
SimpleCov.start do
|
16
|
+
filters.clear
|
17
|
+
|
18
|
+
# Because simplecov filters everything outside of the SimpleCov.root
|
19
|
+
# This should be added, cf.
|
20
|
+
# https://github.com/colszowka/simplecov#default-root-filter-and-coverage-for-things-outside-of-it
|
21
|
+
add_filter do |src|
|
22
|
+
!(src.filename =~ /^#{SimpleCov.root}/) unless src.filename =~ /loca/
|
23
|
+
end
|
24
|
+
|
25
|
+
# Ignoring test folders and tmp for Aruba
|
26
|
+
add_filter "/spec/"
|
27
|
+
add_filter "/test/"
|
28
|
+
add_filter "/features/"
|
29
|
+
add_filter "/tmp/"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require "loca"
|
34
|
+
Loca::CLI.new(ARGV.dup).execute!
|
data/lib/loca.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
1
|
# Require all runtime dependencies first
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
require "addressable/uri"
|
3
|
+
require "colorize"
|
4
|
+
require "mixlib/shellout"
|
5
|
+
require "require_all"
|
6
6
|
|
7
7
|
# Start by requiring files with no dependencies on other files, then files with
|
8
8
|
# only external gem runtime dependencies (specified above), then files with
|
9
9
|
# dependencies on other files within the loca project itself
|
10
|
-
|
11
|
-
require 'loca/error'
|
12
|
-
require 'loca/url'
|
13
|
-
require 'loca/git'
|
14
|
-
require 'loca/cli'
|
10
|
+
require_rel "./loca"
|
data/lib/loca/cli.rb
CHANGED
@@ -1,47 +1,73 @@
|
|
1
|
-
|
2
|
-
class CLI < Thor
|
3
|
-
include Thor::Actions # https://github.com/erikhuda/thor/wiki/Actions
|
4
|
-
|
5
|
-
map %w(--version -v) => :__print_version
|
1
|
+
require "optparse"
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module Loca
|
4
|
+
class CLI
|
5
|
+
def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
|
6
|
+
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
10
7
|
end
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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!'
|
9
|
+
def execute!
|
10
|
+
begin
|
11
|
+
parse_opts
|
12
|
+
parse_cmd
|
13
|
+
rescue Loca::Error::Standard, OptionParser::InvalidOption => e # colorize if so
|
14
|
+
@stderr.puts e.message.red
|
15
|
+
@stderr.puts e.backtrace if @options[:backtrace]
|
16
|
+
@stdout.puts parser # add if-user-error validation here
|
17
|
+
@kernel.exit(1)
|
27
18
|
end
|
19
|
+
@kernel.exit(0)
|
28
20
|
end
|
29
21
|
|
30
|
-
|
31
|
-
def d(pasted_url)
|
32
|
-
git = Loca::Git.new(pasted_url)
|
33
|
-
branch_name = git.branch_name
|
22
|
+
private
|
34
23
|
|
35
|
-
|
36
|
-
|
24
|
+
def parse_opts
|
25
|
+
parser.parse! @argv
|
37
26
|
end
|
38
27
|
|
39
|
-
|
28
|
+
def parse_cmd
|
29
|
+
fail Loca::Error::InvalidURL, "Need to pass in a single URL! Args: #{@argv}" unless @argv.count == 1
|
30
|
+
@parsed_url = Loca::URL::Parser.new(@argv[0]).parse
|
31
|
+
|
32
|
+
return delete if @options[:delete]
|
33
|
+
create
|
34
|
+
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
def parser # rubocop:disable Metrics/MethodLength
|
37
|
+
return @parser if @parser
|
38
|
+
@options = { delete: false, backtrace: false }
|
39
|
+
@parser = OptionParser.new do |opts|
|
40
|
+
opts.banner = "Usage: loca <url> [options]"
|
41
|
+
opts.on("-b", "--backtrace", "Display full stacktraces on error") do
|
42
|
+
@options[:backtrace] = true
|
43
|
+
end
|
44
|
+
opts.on("-d", "--delete", "Deletes the checked out branch") do
|
45
|
+
@options[:delete] = true
|
46
|
+
end
|
47
|
+
opts.on_tail("-h", "--help", "Displays Help") do
|
48
|
+
@stdout.puts opts
|
49
|
+
@kernel.exit(0)
|
50
|
+
end
|
51
|
+
opts.on_tail("-v", "--version", "Display Version") do
|
52
|
+
@stdout.puts Loca::VERSION
|
53
|
+
@kernel.exit(0)
|
54
|
+
end
|
44
55
|
end
|
45
56
|
end
|
57
|
+
|
58
|
+
def delete
|
59
|
+
Loca::Git::BranchDeleter.new(@parsed_url[:branch_name]).delete
|
60
|
+
@stdout.puts "Deleted branch #{@parsed_url[:branch_name]}".green
|
61
|
+
end
|
62
|
+
|
63
|
+
def create
|
64
|
+
Loca::Git::BranchCreator.new(
|
65
|
+
@parsed_url[:pull][:num],
|
66
|
+
@parsed_url[:branch_name],
|
67
|
+
@parsed_url[:remote][:name],
|
68
|
+
@parsed_url[:remote][:url]
|
69
|
+
).create
|
70
|
+
@stdout.puts "Created and checked out branch #{@parsed_url[:branch_name]}".green
|
71
|
+
end
|
46
72
|
end
|
47
73
|
end
|
data/lib/loca/error.rb
CHANGED
@@ -2,20 +2,13 @@
|
|
2
2
|
# and http://www.simonewebdesign.it/how-to-set-default-message-exception/
|
3
3
|
module Loca
|
4
4
|
module Error
|
5
|
-
class
|
6
|
-
def initialize(message)
|
7
|
-
# To get the message in red
|
8
|
-
$stderr.puts message.red
|
9
|
-
@message = message
|
10
|
-
end
|
11
|
-
end
|
5
|
+
class Standard < StandardError; end
|
12
6
|
|
13
|
-
InvalidURL = Class.new(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
GitAborted = Class.new(Base)
|
7
|
+
InvalidURL = Class.new(Standard)
|
8
|
+
GitStdErrDetected = Class.new(Standard)
|
9
|
+
UnstashedFilesFound = Class.new(Standard)
|
10
|
+
OnlyOneBranch = Class.new(Standard)
|
11
|
+
RemoteNotSet = Class.new(Standard)
|
12
|
+
GitAborted = Class.new(Standard)
|
20
13
|
end
|
21
14
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Loca
|
2
|
+
module Git
|
3
|
+
class BranchCreator
|
4
|
+
include Common
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
def initialize(pull_num, branch_name, remote_name, remote_url)
|
8
|
+
@pull_num, @branch_name, @remote_name, @remote_url = pull_num, branch_name, remote_name, remote_url
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
run_git_checkers
|
13
|
+
add_remote
|
14
|
+
# To avoid fatal error: Refusing to fetch into current branch
|
15
|
+
delete unless first_time_creating?
|
16
|
+
fetch
|
17
|
+
checkout
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch
|
21
|
+
# Performs `git fetch upstream pull/PR_NUMBER/head:BRANCH_NAME`
|
22
|
+
git "fetch #{@remote_name} pull/#{@pull_num}/head:#{@branch_name}" # shellout has stderr for some reason
|
23
|
+
end
|
24
|
+
|
25
|
+
def checkout
|
26
|
+
git "checkout #{@branch_name}" # prints to stderr for some reason
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def delete
|
32
|
+
BranchDeleter.new(@branch_name).delete
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_time_creating?
|
36
|
+
branches.include?(@branch_name) ? false : true
|
37
|
+
end
|
38
|
+
|
39
|
+
def remote_mapping
|
40
|
+
names = git("remote show -n").split("\n")
|
41
|
+
mapping = {}
|
42
|
+
names.each do |name|
|
43
|
+
mapping[name] = git("config --get remote.#{name}.url").strip
|
44
|
+
end
|
45
|
+
mapping
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_remote
|
49
|
+
match = remote_mapping.find { |_name, url| git_urls_match?(url, @remote_url) }
|
50
|
+
return @remote_name = match.first if match # avoid creating duplicate remotes
|
51
|
+
git "remote add #{@remote_name} #{@remote_url}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Loca
|
2
|
+
module Git
|
3
|
+
class BranchDeleter
|
4
|
+
include Common
|
5
|
+
include Utils
|
6
|
+
|
7
|
+
def initialize(branch)
|
8
|
+
@branch = branch
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete
|
12
|
+
run_git_checkers
|
13
|
+
# Cannot delete a branch you are currently on:
|
14
|
+
checkout_another_branch if @branch == current_branch
|
15
|
+
git "branch -D #{@branch}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def checkout_another_branch
|
21
|
+
another = "master" if branches.include?("master") # prefer to checkout master branch if it exists
|
22
|
+
another = branches.find { |branch| branch != current_branch } unless another
|
23
|
+
fail Loca::Error::OnlyOneBranch, "No other branch to checkout!" unless another
|
24
|
+
git "checkout #{another}" # prints to stderr for some reason
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_branch
|
28
|
+
git("rev-parse --abbrev-ref HEAD").strip
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Loca
|
2
|
+
module Git
|
3
|
+
module Common
|
4
|
+
def run_git_checkers
|
5
|
+
ensure_git_repo
|
6
|
+
ensure_no_unstashed_files
|
7
|
+
end
|
8
|
+
|
9
|
+
def git(cmd)
|
10
|
+
shellout = Mixlib::ShellOut.new "git #{cmd}"
|
11
|
+
shellout.run_command
|
12
|
+
shellout.error!
|
13
|
+
shellout.stdout
|
14
|
+
end
|
15
|
+
|
16
|
+
def branches
|
17
|
+
git("for-each-ref refs/heads/ --format='%(refname:short)'").split("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Example
|
21
|
+
#
|
22
|
+
# git_urls_match?("https://github.com/smoll/loca.git", "https://github.com/smoll/loca/") # note trailing slash
|
23
|
+
# => true
|
24
|
+
def git_urls_match?(git, http)
|
25
|
+
git_uri = Addressable::URI.parse git
|
26
|
+
http_uri = Addressable::URI.parse http
|
27
|
+
http_uri.host == git_uri.host && http_uri.path.chomp("/").chomp(".git") == git_uri.path.chomp("/").chomp(".git")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ensure_git_repo
|
33
|
+
git "rev-parse"
|
34
|
+
end
|
35
|
+
|
36
|
+
def ensure_no_unstashed_files
|
37
|
+
val = git "status --porcelain"
|
38
|
+
fail Loca::Error::UnstashedFilesFound, "Commit or stash your files before continuing!" if non_empty_string?(val)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Loca
|
2
|
+
module URL
|
3
|
+
class Parser
|
4
|
+
def initialize(url)
|
5
|
+
@url = url
|
6
|
+
@uri = Addressable::URI.parse(@url)
|
7
|
+
@segments = @uri.path.split("/").reject(&:empty?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@url
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
validate
|
16
|
+
parse_attrs
|
17
|
+
set_other_attrs
|
18
|
+
all_attrs
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_attrs
|
22
|
+
{
|
23
|
+
branch_name: @branch_name,
|
24
|
+
pull: {
|
25
|
+
num: @pull_num,
|
26
|
+
url: @pull_url
|
27
|
+
},
|
28
|
+
remote: {
|
29
|
+
name: @remote_name,
|
30
|
+
url: @remote_url
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate
|
38
|
+
Validator.new(@url).validate
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_attrs
|
42
|
+
@username = @segments[0]
|
43
|
+
@repo_name = @segments[1]
|
44
|
+
@pull_num = @segments[3]
|
45
|
+
|
46
|
+
pull_uri = @uri
|
47
|
+
pull_uri.path = @segments[0..3].join("/")
|
48
|
+
@pull_url = pull_uri.to_s
|
49
|
+
|
50
|
+
remote_uri = @uri
|
51
|
+
remote_uri.path = @segments[0..1].join("/") + ".git"
|
52
|
+
@remote_url = remote_uri.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def set_other_attrs
|
56
|
+
@remote_name = "loca_r_#{@username}" # i.e. loca_r_USERNAME
|
57
|
+
@branch_name = "PULL_#{@pull_num}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Loca
|
2
|
+
module URL
|
3
|
+
class Validator
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def initialize(url)
|
7
|
+
@url = url
|
8
|
+
@uri = Addressable::URI.parse(@url)
|
9
|
+
@segments = @uri.path.split("/").reject(&:empty?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate
|
13
|
+
return if valid_host? && valid_user_and_repo_segments? && valid_pull_segment? && valid_pull_number?
|
14
|
+
fail Loca::Error::InvalidURL, <<-MSG
|
15
|
+
Not a GitHub PR URL: #{@url}
|
16
|
+
Examples of valid URLs:
|
17
|
+
- https://github.com/smoll/loca/pull/1337
|
18
|
+
- https://github.com/smoll/loca/pull/1337/commits
|
19
|
+
- https://github.com/smoll/loca/pull/1337/files
|
20
|
+
MSG
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def valid_host?
|
26
|
+
@uri.host == "github.com"
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid_user_and_repo_segments?
|
30
|
+
non_empty_string?(@segments[0]) && non_empty_string?(@segments[1])
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_pull_segment?
|
34
|
+
@segments[2] == "pull"
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_pull_number?
|
38
|
+
Integer(@segments[3])
|
39
|
+
true
|
40
|
+
rescue ArgumentError
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|