geet 0.3.3 → 0.3.4

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: a9069b26e04387727f7ae73ba34dbf83375f3c65
4
- data.tar.gz: 8647d63bb9fa5e471e07dfd04c4da24ce60aa4e1
3
+ metadata.gz: 8b30416607b1d594f4f4d38dc61668a8bb4d2b33
4
+ data.tar.gz: c76019c10cc053dc38d7fdc257dbfa0f07e76f48
5
5
  SHA512:
6
- metadata.gz: 852bc60cf052d3bde605a7851a32054bc60e7aabaff31a793f5ba3d4f2ad1aadb44a3dc00a406b015a095d6bc4103ad04f6030b19439292d62fa3e5df08c8af5
7
- data.tar.gz: 76fd3f5cd74b34fed1a527c65827f093d9c75ba2e2c6c7f8012bad4d851858ec48408f2887d03a6edcb1082ce067a5ce7c31b88f5058918f49bb2cda57fe1c1b
6
+ metadata.gz: 8be0ea5f9f1be8f4307cced46bb846b84b5f74ecfd42f93d89266c57f4cb466913f07d9b849dece1ff4ff4214714d8663fb7316032fd47e10762c5669a528aca
7
+ data.tar.gz: a57c1f035b94aa97a7393da8e818f176683fd682542a2a80d5cf39203442cceefe13eb2ffef872e07d8f315e2e461511d4ef8104531dc9bb6f7dcc95bebbaa87
data/bin/geet CHANGED
@@ -2,13 +2,13 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'simple_scripting/configuration'
5
- require_relative '../lib/geet/commandline/configuration.rb'
6
- require_relative '../lib/geet/commandline/commands.rb'
7
- require_relative '../lib/geet/commandline/editor.rb'
8
- require_relative '../lib/geet/git/repository.rb'
9
- require_relative '../lib/geet/helpers/summary_helper.rb'
10
- require_relative '../lib/geet/shared/constants.rb'
11
- require_relative '../lib/geet/utils/git_client.rb'
5
+ require_relative '../lib/geet/commandline/configuration'
6
+ require_relative '../lib/geet/commandline/commands'
7
+ require_relative '../lib/geet/commandline/editor'
8
+ require_relative '../lib/geet/git/repository'
9
+ require_relative '../lib/geet/helpers/summary_helper'
10
+ require_relative '../lib/geet/shared/selection'
11
+ require_relative '../lib/geet/utils/git_client'
12
12
  Dir[File.join(__dir__, '../lib/geet/services/*.rb')].each { |filename| require filename }
13
13
 
14
14
  class GeetLauncher
@@ -84,7 +84,7 @@ class GeetLauncher
84
84
 
85
85
  def default_to_manual_selection(options, *params)
86
86
  params.each do |param|
87
- options[param] ||= Shared::Constants::MANUAL_LIST_SELECTION_FLAG
87
+ options[param] ||= Shared::Selection::MANUAL_LIST_SELECTION_FLAG
88
88
  end
89
89
 
90
90
  options
data/geet.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.required_ruby_version = '>= 2.3.0'
12
12
  s.authors = ['Saverio Miroddi']
13
- s.date = '2018-02-03'
13
+ s.date = '2018-02-05'
14
14
  s.email = ['saverio.pub2@gmail.com']
15
15
  s.homepage = 'https://github.com/saveriomiroddi/geet'
16
16
  s.summary = 'Commandline interface for performing SCM (eg. GitHub) operations (eg. PR creation).'
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'tempfile'
4
4
 
5
- require_relative '../helpers/os_helper.rb'
5
+ require_relative '../helpers/os_helper'
6
6
 
7
7
  module Geet
8
8
  module Commandline
@@ -3,6 +3,7 @@
3
3
  require 'uri'
4
4
  require 'net/http'
5
5
  require 'json'
6
+ require_relative '../shared/http_error'
6
7
 
7
8
  module Geet
8
9
  module Github
@@ -53,8 +54,8 @@ module Geet
53
54
  parsed_response = JSON.parse(response.body) if response.body
54
55
 
55
56
  if error?(response)
56
- formatted_error = decode_and_format_error(parsed_response)
57
- raise(formatted_error)
57
+ error_message = decode_and_format_error(parsed_response)
58
+ raise Geet::Shared::HttpError.new(error_message, response.code)
58
59
  end
59
60
 
60
61
  return parsed_response if !multipage
@@ -1,12 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../shared/repo_permissions'
4
+ require_relative '../shared/http_error'
5
+
3
6
  module Geet
4
7
  module Github
5
8
  class User
9
+ include Geet::Shared::RepoPermissions
10
+
6
11
  attr_reader :username
7
12
 
8
- def initialize(username)
13
+ def initialize(username, api_interface)
9
14
  @username = username
15
+ @api_interface = api_interface
16
+ end
17
+
18
+ # See #repo_permission.
19
+ #
20
+ def has_permission?(permission)
21
+ user_permission = self.class.repo_permission(@api_interface)
22
+
23
+ permission_greater_or_equal_to?(user_permission, permission)
24
+ end
25
+
26
+ # See https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
27
+ #
28
+ def is_collaborator?
29
+ api_path = "collaborators/#{@username}"
30
+
31
+ begin
32
+ @api_interface.send_request(api_path)
33
+
34
+ # 204: user is a collaborator.
35
+ true
36
+ rescue Geet::Shared::HttpError => error
37
+ # 404: not a collaborator.
38
+ # Although the documentation mentions only 404, 403 is a valid response as well; it seems
39
+ # that 404 is given on private repositories, while 403 on public ones ("Must have push
40
+ # access to view repository collaborators.").
41
+ #
42
+ (error.code == 404 || error.code == 403) ? false : raise
43
+ end
10
44
  end
11
45
 
12
46
  # See https://developer.github.com/v3/users/#get-the-authenticated-user
@@ -16,7 +50,7 @@ module Geet
16
50
 
17
51
  response = api_interface.send_request(api_path)
18
52
 
19
- new(response.fetch('login'))
53
+ new(response.fetch('login'), api_interface)
20
54
  end
21
55
 
22
56
  # Returns an array of User instances
@@ -25,7 +59,32 @@ module Geet
25
59
  api_path = 'collaborators'
26
60
  response = api_interface.send_request(api_path, multipage: true)
27
61
 
28
- response.map { |user_entry| new(user_entry.fetch('login')) }
62
+ response.map { |user_entry| new(user_entry.fetch('login'), api_interface) }
63
+ end
64
+
65
+ # See https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level
66
+ #
67
+ def self.repo_permission(api_interface)
68
+ username = authenticated(api_interface).username
69
+ api_path = "collaborators/#{username}/permission"
70
+
71
+ response = api_interface.send_request(api_path)
72
+
73
+ permission = response.fetch('permission')
74
+
75
+ check_permission!(permission)
76
+
77
+ permission
78
+ end
79
+
80
+ class << self
81
+ private
82
+
83
+ # Future-proofing.
84
+ #
85
+ def check_permission!(permission)
86
+ raise "Unexpected permission #{permission.inspect}!" if !self::ALL_PERMISSIONS.include?(permission)
87
+ end
29
88
  end
30
89
  end
31
90
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../helpers/os_helper.rb'
4
- require_relative '../github/api_interface.rb'
5
- require_relative '../github/gist.rb'
3
+ require_relative '../helpers/os_helper'
4
+ require_relative '../github/api_interface'
5
+ require_relative '../github/gist'
6
6
 
7
7
  module Geet
8
8
  module Services
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'abstract_create_issue'
4
+ require_relative '../shared/repo_permissions'
4
5
 
5
6
  module Geet
6
7
  module Services
7
8
  class CreateIssue < AbstractCreateIssue
9
+ include Geet::Shared::RepoPermissions
10
+
8
11
  # options:
9
12
  # :labels
10
13
  # :milestone: number or description pattern.
@@ -16,11 +19,23 @@ module Geet
16
19
  labels: nil, milestone: nil, assignees: nil, no_open_issue: nil,
17
20
  **
18
21
  )
19
- selected_labels, selected_milestone, selected_assignees = find_and_select_attributes(labels, milestone, assignees)
22
+ # Inefficient (in worst case, triples the pre issue creation waiting time: #is_collaborator?,
23
+ # #has_permissions?, and the attributes batch), but not trivial to speed up. Not difficult
24
+ # either, but currently not worth spending time.
25
+ #
26
+ # Theoretically, #is_collaborator? could be skipped, but this is cleaner.
27
+ user_has_write_permissions = @repository.authenticated_user.is_collaborator? &&
28
+ @repository.authenticated_user.has_permission?(PERMISSION_WRITE)
29
+
30
+ if user_has_write_permissions
31
+ selected_labels, selected_milestone, selected_assignees = find_and_select_attributes(labels, milestone, assignees)
32
+ end
20
33
 
21
34
  issue = create_issue(title, description)
22
35
 
23
- edit_issue(issue, selected_labels, selected_milestone, selected_assignees)
36
+ if user_has_write_permissions
37
+ edit_issue(issue, selected_labels, selected_milestone, selected_assignees)
38
+ end
24
39
 
25
40
  if no_open_issue
26
41
  @out.puts "Issue address: #{issue.link}"
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'abstract_create_issue'
4
+ require_relative '../shared/repo_permissions'
4
5
 
5
6
  module Geet
6
7
  module Services
7
8
  class CreatePr < AbstractCreateIssue
9
+ include Geet::Shared::RepoPermissions
10
+
8
11
  DEFAULT_GIT_CLIENT = Geet::Utils::GitClient.new
9
12
 
10
13
  def initialize(repository, out: $stdout, git_client: DEFAULT_GIT_CLIENT)
@@ -24,13 +27,21 @@ module Geet
24
27
  )
25
28
  ensure_clean_tree if automated_mode
26
29
 
27
- selected_labels, selected_milestone, selected_reviewers = find_and_select_attributes(labels, milestone, reviewers)
30
+ # See CreateIssue#execute for notes about performance.
31
+ user_has_write_permissions = @repository.authenticated_user.is_collaborator? &&
32
+ @repository.authenticated_user.has_permission?(PERMISSION_WRITE)
33
+
34
+ if user_has_write_permissions
35
+ selected_labels, selected_milestone, selected_reviewers = find_and_select_attributes(labels, milestone, reviewers)
36
+ end
28
37
 
29
38
  sync_with_upstream_branch if automated_mode
30
39
 
31
40
  pr = create_pr(title, description)
32
41
 
33
- edit_pr(pr, selected_labels, selected_milestone, selected_reviewers)
42
+ if user_has_write_permissions
43
+ edit_pr(pr, selected_labels, selected_milestone, selected_reviewers)
44
+ end
34
45
 
35
46
  if no_open_pr
36
47
  @out.puts "PR address: #{pr.link}"
@@ -3,7 +3,7 @@
3
3
  module Geet
4
4
  module Services
5
5
  class ListMilestones
6
- def initialize(repository, out: $output)
6
+ def initialize(repository, out: $stdout)
7
7
  @repository = repository
8
8
  @out = out
9
9
  end
@@ -0,0 +1,13 @@
1
+ module Geet
2
+ module Shared
3
+ class HttpError < RuntimeError
4
+ # Integer.
5
+ attr_reader :code
6
+
7
+ def initialize(message, code)
8
+ super(message)
9
+ @code = code.to_i
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geet
4
+ module Shared
5
+ module RepoPermissions
6
+ PERMISSION_ADMIN = 'admin'
7
+ PERMISSION_WRITE = 'write'
8
+ PERMISSION_READ = 'read'
9
+ PERMISSION_NONE = 'none'
10
+
11
+ ALL_PERMISSIONS = [
12
+ PERMISSION_ADMIN,
13
+ PERMISSION_WRITE,
14
+ PERMISSION_READ,
15
+ PERMISSION_NONE,
16
+ ]
17
+
18
+ # Not worth creating a Permission class at this stage.
19
+ #
20
+ def permission_greater_or_equal_to?(subject_permission, object_permission)
21
+ ALL_PERMISSIONS.index(subject_permission) <= ALL_PERMISSIONS.index(object_permission)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Geet
4
4
  module Shared
5
- module Constants
5
+ module Selection
6
6
  MANUAL_LIST_SELECTION_FLAG = '-'.freeze
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'manual_list_selection'
4
4
  require_relative 'string_matching_selection'
5
- require_relative '../shared/constants'
5
+ require_relative '../shared/selection'
6
6
 
7
7
  module Geet
8
8
  module Utils
@@ -14,7 +14,7 @@ module Geet
14
14
  # multiple attributes are required (typically, three).
15
15
  #
16
16
  class AttributesSelectionManager
17
- include Geet::Shared::Constants
17
+ include Geet::Shared::Selection
18
18
 
19
19
  # Workaround for VCR not supporting multithreading; see https://github.com/vcr/vcr/issues/200.
20
20
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'shellwords'
4
- require_relative '../helpers/os_helper.rb'
4
+ require_relative '../helpers/os_helper'
5
5
 
6
6
  module Geet
7
7
  module Utils
@@ -153,7 +153,7 @@ module Geet
153
153
  private
154
154
 
155
155
  def gitdir_option
156
- "--git-dir #{@location.shellescape}/.git" if @location
156
+ "-C #{@location.shellescape}" if @location
157
157
  end
158
158
  end
159
159
  end
data/lib/geet/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geet
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.4'
5
5
  end
@@ -7,12 +7,12 @@ require_relative '../../lib/geet/services/create_issue'
7
7
 
8
8
  describe Geet::Services::CreateIssue do
9
9
  let(:git_client) { Geet::Utils::GitClient.new }
10
- let(:repository) { Geet::Git::Repository.new(git_client: git_client) }
10
+ let(:repository) { Geet::Git::Repository.new(warnings: false, git_client: git_client) }
11
11
  let(:upstream_repository) { Geet::Git::Repository.new(upstream: true, git_client: git_client) }
12
12
 
13
13
  context 'with labels, assignees and milestones' do
14
14
  it 'should create an issue' do
15
- allow(git_client).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
15
+ allow(git_client).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo_f')
16
16
 
17
17
  expected_output = <<~STR
18
18
  Finding labels...
@@ -21,8 +21,8 @@ describe Geet::Services::CreateIssue do
21
21
  Creating the issue...
22
22
  Adding labels bug, invalid...
23
23
  Setting milestone 0.0.1...
24
- Assigning users donald-ts, donald-fr...
25
- Issue address: https://github.com/donaldduck/testrepo/issues/1
24
+ Assigning users donaldduck, donald-fr...
25
+ Issue address: https://github.com/donaldduck/testrepo_f/issues/2
26
26
  STR
27
27
 
28
28
  actual_output = StringIO.new
@@ -30,40 +30,44 @@ describe Geet::Services::CreateIssue do
30
30
  actual_created_issue = VCR.use_cassette('create_issue') do
31
31
  described_class.new(repository, out: actual_output).execute(
32
32
  'Title', 'Description',
33
- labels: 'bug,invalid', milestone: '0.0.1', assignees: 'donald-ts,donald-fr',
33
+ labels: 'bug,invalid', milestone: '0.0.1', assignees: 'donaldduck,donald-fr',
34
34
  no_open_issue: true
35
35
  )
36
36
  end
37
37
 
38
38
  expect(actual_output.string).to eql(expected_output)
39
39
 
40
- expect(actual_created_issue.number).to eql(1)
40
+ expect(actual_created_issue.number).to eql(2)
41
41
  expect(actual_created_issue.title).to eql('Title')
42
- expect(actual_created_issue.link).to eql('https://github.com/donaldduck/testrepo/issues/1')
42
+ expect(actual_created_issue.link).to eql('https://github.com/donaldduck/testrepo_f/issues/2')
43
43
  end
44
44
  end
45
45
 
46
- it 'should create an upstream issue' do
47
- allow(git_client).to receive(:current_branch).and_return('mybranch')
48
- allow(git_client).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
49
- allow(git_client).to receive(:remote).with('upstream').and_return('git@github.com:donald-fr/testrepo_u')
46
+ context 'without write permissions' do
47
+ context 'without labels, assignees and milestones' do
48
+ it 'should create an upstream issue' do
49
+ allow(git_client).to receive(:current_branch).and_return('mybranch')
50
+ allow(git_client).to receive(:remote).with('origin').and_return('git@github.com:donaldduck/testrepo')
51
+ allow(git_client).to receive(:remote).with('upstream').and_return('git@github.com:momcorp/therepo')
50
52
 
51
- expected_output = <<~STR
52
- Creating the issue...
53
- Issue address: https://github.com/donald-fr/testrepo_u/issues/7
54
- STR
53
+ expected_output = <<~STR
54
+ Creating the issue...
55
+ Issue address: https://github.com/momcorp/therepo/issues/42
56
+ STR
55
57
 
56
- actual_output = StringIO.new
58
+ actual_output = StringIO.new
57
59
 
58
- actual_created_issue = VCR.use_cassette('create_issue_upstream') do
59
- create_options = { no_open_issue: true, out: actual_output }
60
- described_class.new(upstream_repository, out: actual_output).execute('Title', 'Description', create_options)
61
- end
60
+ actual_created_issue = VCR.use_cassette('create_issue_upstream') do
61
+ create_options = { no_open_issue: true, out: actual_output }
62
+ described_class.new(upstream_repository, out: actual_output).execute('Title', 'Description', create_options)
63
+ end
62
64
 
63
- expect(actual_output.string).to eql(expected_output)
65
+ expect(actual_output.string).to eql(expected_output)
64
66
 
65
- expect(actual_created_issue.number).to eql(7)
66
- expect(actual_created_issue.title).to eql('Title')
67
- expect(actual_created_issue.link).to eql('https://github.com/donald-fr/testrepo_u/issues/7')
67
+ expect(actual_created_issue.number).to eql(42)
68
+ expect(actual_created_issue.title).to eql('Title')
69
+ expect(actual_created_issue.link).to eql('https://github.com/momcorp/therepo/issues/42')
70
+ end
71
+ end
68
72
  end
69
73
  end