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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d72e91059bc88a4090bd9eb2c6c8f1e45c13704
4
- data.tar.gz: e8fda9734eaf1fc2446fdb85490e792db395d6ee
3
+ metadata.gz: f8d346e967391656b7e5270109caa97349ebd2e7
4
+ data.tar.gz: 8d24248adeaba1b73089eac6abb8d4a0deb1ba65
5
5
  SHA512:
6
- metadata.gz: 71eb60e85d247ce2ee9e14905c685283632262618815a1dccf80af8039a449302f0945af2b40f144358e84ecad73129d72f1adac0183bbd48f6bd4439da611b3
7
- data.tar.gz: 129bf8eb437ae3a064b5f9a77e284738eb961f8f6a55ba4c9390b6eba4a91623a73b8664fe200394dd414c083ca6b1e0d16ae348dba6686656889555455258ec
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
- require 'loca'
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
- Loca::CLI.start(ARGV)
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!
@@ -1,14 +1,10 @@
1
1
  # Require all runtime dependencies first
2
- require 'thor'
3
- require 'colorize'
4
- require 'mixlib/shellout'
5
- require 'addressable/uri'
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
- require 'loca/version'
11
- require 'loca/error'
12
- require 'loca/url'
13
- require 'loca/git'
14
- require 'loca/cli'
10
+ require_rel "./loca"
@@ -1,47 +1,73 @@
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
1
+ require "optparse"
6
2
 
7
- desc '--version, -v', 'print the version'
8
- def __print_version
9
- puts Loca::VERSION
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
- 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!'
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
- 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
22
+ private
34
23
 
35
- git.delete
36
- say "Deleted #{branch_name}!", :green
24
+ def parse_opts
25
+ parser.parse! @argv
37
26
  end
38
27
 
39
- private # rubocop:disable Lint/UselessAccessModifier
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
- no_commands do # Thor primitive(s) that we want to stub in RSpec
42
- def yes?(*args)
43
- super
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
@@ -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 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
5
+ class Standard < StandardError; end
12
6
 
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)
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