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 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