geet 0.3.3 → 0.3.4

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