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 +4 -4
- data/bin/geet +8 -8
- data/geet.gemspec +1 -1
- data/lib/geet/commandline/editor.rb +1 -1
- data/lib/geet/github/api_interface.rb +3 -2
- data/lib/geet/github/user.rb +62 -3
- data/lib/geet/services/create_gist.rb +3 -3
- data/lib/geet/services/create_issue.rb +17 -2
- data/lib/geet/services/create_pr.rb +13 -2
- data/lib/geet/services/list_milestones.rb +1 -1
- data/lib/geet/shared/http_error.rb +13 -0
- data/lib/geet/shared/repo_permissions.rb +25 -0
- data/lib/geet/shared/{constants.rb → selection.rb} +1 -1
- data/lib/geet/utils/attributes_selection_manager.rb +2 -2
- data/lib/geet/utils/git_client.rb +2 -2
- data/lib/geet/version.rb +1 -1
- data/spec/integration/create_issue_spec.rb +28 -24
- data/spec/integration/create_pr_spec.rb +67 -30
- data/spec/vcr_cassettes/create_issue.yml +446 -70
- data/spec/vcr_cassettes/create_issue_upstream.yml +158 -11
- data/spec/vcr_cassettes/create_pr.yml +474 -103
- data/spec/vcr_cassettes/create_pr_in_auto_mode_create_upstream.yml +407 -31
- data/spec/vcr_cassettes/create_pr_in_auto_mode_with_push.yml +408 -32
- data/spec/vcr_cassettes/create_pr_upstream.yml +415 -39
- data/spec/vcr_cassettes/create_pr_upstream_without_write_permissions.yml +305 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b30416607b1d594f4f4d38dc61668a8bb4d2b33
|
4
|
+
data.tar.gz: c76019c10cc053dc38d7fdc257dbfa0f07e76f48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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/
|
11
|
-
require_relative '../lib/geet/utils/git_client
|
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::
|
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-
|
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).'
|
@@ -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
|
-
|
57
|
-
raise(
|
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
|
data/lib/geet/github/user.rb
CHANGED
@@ -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
|
4
|
-
require_relative '../github/api_interface
|
5
|
-
require_relative '../github/gist
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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}"
|
@@ -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
|
require_relative 'manual_list_selection'
|
4
4
|
require_relative 'string_matching_selection'
|
5
|
-
require_relative '../shared/
|
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::
|
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
|
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
|
-
"
|
156
|
+
"-C #{@location.shellescape}" if @location
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
data/lib/geet/version.rb
CHANGED
@@ -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/
|
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
|
25
|
-
Issue address: https://github.com/donaldduck/
|
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: '
|
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(
|
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/
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
expected_output = <<~STR
|
54
|
+
Creating the issue...
|
55
|
+
Issue address: https://github.com/momcorp/therepo/issues/42
|
56
|
+
STR
|
55
57
|
|
56
|
-
|
58
|
+
actual_output = StringIO.new
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
65
|
+
expect(actual_output.string).to eql(expected_output)
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|