geet 0.1.8 → 0.1.9

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +31 -0
  5. data/README.md +0 -2
  6. data/bin/geet +50 -39
  7. data/geet.gemspec +1 -1
  8. data/lib/geet/commandline/commands.rb +14 -0
  9. data/lib/geet/{helpers/configuration_helper.rb → commandline/configuration.rb} +4 -12
  10. data/lib/geet/git/repository.rb +93 -44
  11. data/lib/geet/{git_hub → github}/abstract_issue.rb +13 -13
  12. data/lib/geet/github/account.rb +19 -0
  13. data/lib/geet/{git_hub/api_helper.rb → github/api_interface.rb} +21 -19
  14. data/lib/geet/github/collaborator.rb +17 -0
  15. data/lib/geet/{git_hub → github}/gist.rb +4 -4
  16. data/lib/geet/{git_hub → github}/issue.rb +10 -10
  17. data/lib/geet/github/label.rb +17 -0
  18. data/lib/geet/{git_hub → github}/milestone.rb +11 -11
  19. data/lib/geet/github/pr.rb +61 -0
  20. data/lib/geet/services/create_gist.rb +4 -4
  21. data/lib/geet/services/create_issue.rb +30 -25
  22. data/lib/geet/services/create_pr.rb +28 -27
  23. data/lib/geet/services/list_issues.rb +2 -2
  24. data/lib/geet/services/list_labels.rb +2 -2
  25. data/lib/geet/services/list_milestones.rb +10 -10
  26. data/lib/geet/services/list_prs.rb +2 -2
  27. data/lib/geet/services/merge_pr.rb +8 -7
  28. data/lib/geet/version.rb +1 -1
  29. data/spec/integration/create_gist_spec.rb +46 -0
  30. data/spec/integration/create_issue_spec.rb +66 -0
  31. data/spec/integration/create_pr_spec.rb +68 -0
  32. data/spec/integration/list_issues_spec.rb +52 -0
  33. data/spec/integration/list_labels_spec.rb +28 -0
  34. data/spec/integration/list_milestones_spec.rb +43 -0
  35. data/spec/integration/list_prs_spec.rb +52 -0
  36. data/spec/integration/merge_pr_spec.rb +30 -0
  37. data/spec/spec_helper.rb +121 -0
  38. data/spec/vcr_cassettes/create_gist_private.yml +80 -0
  39. data/spec/vcr_cassettes/create_gist_public.yml +80 -0
  40. data/spec/vcr_cassettes/create_issue.yml +534 -0
  41. data/spec/vcr_cassettes/create_issue_upstream.yml +235 -0
  42. data/spec/vcr_cassettes/create_pr.yml +693 -0
  43. data/spec/vcr_cassettes/create_pr_upstream.yml +313 -0
  44. data/spec/vcr_cassettes/list_issues.yml +82 -0
  45. data/spec/vcr_cassettes/list_issues_upstream.yml +84 -0
  46. data/spec/vcr_cassettes/list_labels.yml +78 -0
  47. data/spec/vcr_cassettes/list_milestones.yml +468 -0
  48. data/spec/vcr_cassettes/list_prs.yml +84 -0
  49. data/spec/vcr_cassettes/list_prs_upstream.yml +80 -0
  50. data/spec/vcr_cassettes/merge_pr.yml +156 -0
  51. metadata +36 -11
  52. data/lib/geet/git_hub/account.rb +0 -19
  53. data/lib/geet/git_hub/pr.rb +0 -57
  54. data/lib/geet/git_hub/remote_repository.rb +0 -65
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geet
4
+ module Github
5
+ class Account
6
+ def initialize(api_interface)
7
+ @api_interface = api_interface
8
+ end
9
+
10
+ def authenticated_user
11
+ api_path = '/user'
12
+
13
+ response = @api_interface.send_request(api_path)
14
+
15
+ response.fetch('login')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -6,27 +6,17 @@ require 'json'
6
6
  require 'shellwords'
7
7
 
8
8
  module Geet
9
- module GitHub
10
- class ApiHelper
11
- def initialize(api_token, user, repository_path, upstream)
9
+ module Github
10
+ class ApiInterface
11
+ API_AUTH_USER = '' # We don't need the login, as the API key uniquely identifies the user
12
+ API_BASE_URL = 'https://api.github.com'
13
+
14
+ def initialize(api_token, repository_path, upstream)
12
15
  @api_token = api_token
13
- @user = user
14
16
  @repository_path = repository_path
15
17
  @upstream = upstream
16
18
  end
17
19
 
18
- def api_base_link
19
- 'https://api.github.com'
20
- end
21
-
22
- def api_repo_link
23
- "#{api_base_link}/repos/#{@repository_path}"
24
- end
25
-
26
- def repo_link
27
- "https://github.com/#{@repository_path}"
28
- end
29
-
30
20
  def upstream?
31
21
  @upstream
32
22
  end
@@ -36,14 +26,20 @@ module Geet
36
26
  # Returns the parsed response, or an Array, in case of multipage.
37
27
  #
38
28
  # params:
29
+ # :api_path: api path, will be appended to the API URL.
30
+ # for root path, prepend a `/`:
31
+ # - use `/gists` for `https://api.github.com/gists`
32
+ # when owner/project/repos is included, don't prepend `/`:
33
+ # - use `issues` for `https://api.github.com/myowner/myproject/repos/issues`
39
34
  # :params: (Hash)
40
35
  # :data: (Hash) if present, will generate a POST request, otherwise, a GET
41
- # :multipage: set true for paged GitHub responses (eg. issues); it will make the method
36
+ # :multipage: set true for paged Github responses (eg. issues); it will make the method
42
37
  # return an array, with the concatenated (parsed) responses
43
38
  # :http_method: :get, :patch, :post and :put are accepted, but only :patch/:put are meaningful,
44
39
  # since the others are automatically inferred by :data.
45
40
  #
46
- def send_request(address, params: nil, data: nil, multipage: false, http_method: nil)
41
+ def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
42
+ address = api_url(api_path)
47
43
  # filled only on :multipage
48
44
  parsed_responses = []
49
45
 
@@ -69,6 +65,12 @@ module Geet
69
65
 
70
66
  private
71
67
 
68
+ def api_url(api_path)
69
+ url = API_BASE_URL
70
+ url += "/repos/#{@repository_path}/" if !api_path.start_with?('/')
71
+ url + api_path
72
+ end
73
+
72
74
  def send_http_request(address, params: nil, data: nil, http_method: nil)
73
75
  uri = encode_uri(address, params)
74
76
  http_class = find_http_class(http_method, data)
@@ -76,8 +78,8 @@ module Geet
76
78
  Net::HTTP.start(uri.host, use_ssl: true) do |http|
77
79
  request = http_class.new(uri)
78
80
 
81
+ request.basic_auth API_AUTH_USER, @api_token
79
82
  request.body = data.to_json if data
80
- request.basic_auth @user, @api_token
81
83
  request['Accept'] = 'application/vnd.github.v3+json'
82
84
 
83
85
  http.request(request)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Geet
6
+ module Github
7
+ class Collaborator
8
+ # Returns a flat list of names in string form.
9
+ def self.list(api_interface)
10
+ api_path = 'collaborators'
11
+ response = api_interface.send_request(api_path, multipage: true)
12
+
13
+ response.map { |user_entry| user_entry.fetch('login') }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,14 +3,14 @@
3
3
  require_relative 'abstract_issue'
4
4
 
5
5
  module Geet
6
- module GitHub
6
+ module Github
7
7
  class Gist
8
- def self.create(filename, content, api_helper, description: nil, publik: false)
9
- request_address = "#{api_helper.api_base_link}/gists"
8
+ def self.create(filename, content, api_interface, description: nil, publik: false)
9
+ api_path = "/gists"
10
10
 
11
11
  request_data = prepare_request_data(filename, content, description, publik)
12
12
 
13
- response = api_helper.send_request(request_address, data: request_data)
13
+ response = api_interface.send_request(api_path, data: request_data)
14
14
 
15
15
  id = response.fetch('id')
16
16
 
@@ -1,28 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Geet
4
- module GitHub
4
+ module Github
5
5
  # See AbstractIssue for the circular dependency issue notes.
6
6
  autoload :AbstractIssue, File.expand_path('abstract_issue', __dir__)
7
7
 
8
- class Issue < Geet::GitHub::AbstractIssue
9
- def self.create(title, description, api_helper)
10
- request_address = "#{api_helper.api_repo_link}/issues"
8
+ class Issue < Geet::Github::AbstractIssue
9
+ def self.create(title, description, api_interface)
10
+ api_path = 'issues'
11
11
  request_data = { title: title, body: description, base: 'master' }
12
12
 
13
- response = api_helper.send_request(request_address, data: request_data)
13
+ response = api_interface.send_request(api_path, data: request_data)
14
14
 
15
15
  issue_number, title, link = response.fetch_values('number', 'title', 'html_url')
16
16
 
17
- new(issue_number, api_helper, title, link)
17
+ new(issue_number, api_interface, title, link)
18
18
  end
19
19
 
20
20
  # See https://developer.github.com/v3/issues/#list-issues-for-a-repository
21
21
  #
22
- def self.list(api_helper)
23
- request_address = "#{api_helper.api_repo_link}/issues"
22
+ def self.list(api_interface)
23
+ api_path = 'issues'
24
24
 
25
- response = api_helper.send_request(request_address, multipage: true)
25
+ response = api_interface.send_request(api_path, multipage: true)
26
26
 
27
27
  response.each_with_object([]) do |issue_data, result|
28
28
  if !issue_data.key?('pull_request')
@@ -30,7 +30,7 @@ module Geet
30
30
  title = issue_data.fetch('title')
31
31
  link = issue_data.fetch('html_url')
32
32
 
33
- result << new(number, api_helper, title, link)
33
+ result << new(number, api_interface, title, link)
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Geet
6
+ module Github
7
+ class Label
8
+ # Returns a flat list of names in string form.
9
+ def self.list(api_interface)
10
+ api_path = 'labels'
11
+ response = api_interface.send_request(api_path, multipage: true)
12
+
13
+ response.map { |label_entry| label_entry['name'] }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,45 +3,45 @@
3
3
  require 'date'
4
4
 
5
5
  module Geet
6
- module GitHub
6
+ module Github
7
7
  class Milestone
8
8
  attr_reader :number, :title, :due_on
9
9
 
10
- def initialize(number, title, due_on, api_helper)
10
+ def initialize(number, title, due_on, api_interface)
11
11
  @number = number
12
12
  @title = title
13
13
  @due_on = due_on
14
14
 
15
- @api_helper = api_helper
15
+ @api_interface = api_interface
16
16
  end
17
17
 
18
18
  # See https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
19
19
  #
20
- def self.find(number, api_helper)
21
- request_address = "#{api_helper.api_repo_link}/milestones/#{number}"
20
+ def self.find(number, api_interface)
21
+ api_path = "milestones/#{number}"
22
22
 
23
- response = api_helper.send_request(request_address)
23
+ response = api_interface.send_request(api_path)
24
24
 
25
25
  number = response.fetch('number')
26
26
  title = response.fetch('title')
27
27
  due_on = parse_due_on(response.fetch('due_on'))
28
28
 
29
- new(number, title, due_on, api_helper)
29
+ new(number, title, due_on, api_interface)
30
30
  end
31
31
 
32
32
  # See https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
33
33
  #
34
- def self.list(api_helper)
35
- request_address = "#{api_helper.api_repo_link}/milestones"
34
+ def self.list(api_interface)
35
+ api_path = 'milestones'
36
36
 
37
- response = api_helper.send_request(request_address, multipage: true)
37
+ response = api_interface.send_request(api_path, multipage: true)
38
38
 
39
39
  response.map do |milestone_data|
40
40
  number = milestone_data.fetch('number')
41
41
  title = milestone_data.fetch('title')
42
42
  due_on = parse_due_on(milestone_data.fetch('due_on'))
43
43
 
44
- new(number, title, due_on, api_helper)
44
+ new(number, title, due_on, api_interface)
45
45
  end
46
46
  end
47
47
 
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Geet
4
+ module Github
5
+ # See AbstractIssue for the circular dependency issue notes.
6
+ autoload :AbstractIssue, File.expand_path('abstract_issue', __dir__)
7
+
8
+ class PR < AbstractIssue
9
+ # See https://developer.github.com/v3/pulls/#create-a-pull-request
10
+ #
11
+ def self.create(title, description, head, api_interface)
12
+ api_path = 'pulls'
13
+
14
+ if api_interface.upstream?
15
+ account = Geet::Github::Account.new(api_interface)
16
+ head = "#{account.authenticated_user}:#{head}"
17
+ end
18
+
19
+ request_data = { title: title, body: description, head: head, base: 'master' }
20
+
21
+ response = api_interface.send_request(api_path, data: request_data)
22
+
23
+ number, title, link = response.fetch_values('number', 'title', 'html_url')
24
+
25
+ new(number, api_interface, title, link)
26
+ end
27
+
28
+ # See https://developer.github.com/v3/pulls/#list-pull-requests
29
+ #
30
+ def self.list(api_interface, head: nil)
31
+ api_path = 'pulls'
32
+ request_params = { head: head } if head
33
+
34
+ response = api_interface.send_request(api_path, params: request_params, multipage: true)
35
+
36
+ response.map do |issue_data|
37
+ number = issue_data.fetch('number')
38
+ title = issue_data.fetch('title')
39
+ link = issue_data.fetch('html_url')
40
+
41
+ new(number, api_interface, title, link)
42
+ end
43
+ end
44
+
45
+ # See https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
46
+ #
47
+ def merge
48
+ api_path = "pulls/#{number}/merge"
49
+
50
+ @api_interface.send_request(api_path, http_method: :put)
51
+ end
52
+
53
+ def request_review(reviewers)
54
+ api_path = "pulls/#{number}/requested_reviewers"
55
+ request_data = { reviewers: reviewers }
56
+
57
+ @api_interface.send_request(api_path, data: request_data)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/os_helper.rb'
4
- require_relative '../git/repository.rb'
5
4
 
6
5
  module Geet
7
6
  module Services
@@ -13,16 +12,17 @@ module Geet
13
12
  # :publik: defaults to false
14
13
  # :no_browse defaults to false
15
14
  #
16
- def execute(repository, full_filename, description: nil, publik: false, no_browse: false)
15
+ def execute(repository, full_filename, description: nil, publik: false, no_browse: false, output: $stdout)
17
16
  content = IO.read(full_filename)
18
17
 
19
- puts 'Creating the gist...'
18
+ gist_access = publik ? 'public' : 'private'
19
+ output.puts "Creating a #{gist_access} gist..."
20
20
 
21
21
  filename = File.basename(full_filename)
22
22
  gist = repository.create_gist(filename, content, description: description, publik: publik)
23
23
 
24
24
  if no_browse
25
- puts "Gist address: #{gist.link}"
25
+ output.puts "Gist address: #{gist.link}"
26
26
  else
27
27
  os_open(gist.link)
28
28
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/os_helper.rb'
4
- require_relative '../git/repository.rb'
5
4
 
6
5
  module Geet
7
6
  module Services
@@ -14,26 +13,30 @@ module Geet
14
13
  # :assignee_patterns
15
14
  # :no_open_issue
16
15
  #
17
- def execute(repository, title, description, label_patterns: nil, milestone_pattern: nil, assignee_patterns: nil, no_open_issue: nil, **)
18
- labels_thread = select_labels(repository, label_patterns) if label_patterns
19
- milestone_thread = find_milestone(repository, milestone_pattern) if milestone_pattern
20
- assignees_thread = select_assignees(repository, assignee_patterns) if assignee_patterns
16
+ def execute(
17
+ repository, title, description,
18
+ label_patterns: nil, milestone_pattern: nil, assignee_patterns: nil, no_open_issue: nil,
19
+ output: $stdout, **
20
+ )
21
+ labels_thread = select_labels(repository, label_patterns, output) if label_patterns
22
+ milestone_thread = find_milestone(repository, milestone_pattern, output) if milestone_pattern
23
+ assignees_thread = select_assignees(repository, assignee_patterns, output) if assignee_patterns
21
24
 
22
25
  selected_labels = labels_thread&.join&.value
23
26
  assignees = assignees_thread&.join&.value
24
27
  milestone = milestone_thread&.join&.value
25
28
 
26
- puts 'Creating the issue...'
29
+ output.puts 'Creating the issue...'
27
30
 
28
31
  issue = repository.create_issue(title, description)
29
32
 
30
- add_labels_thread = add_labels(issue, selected_labels) if selected_labels
31
- set_milestone_thread = set_milestone(issue, milestone) if milestone
33
+ add_labels_thread = add_labels(issue, selected_labels, output) if selected_labels
34
+ set_milestone_thread = set_milestone(issue, milestone, output) if milestone
32
35
 
33
36
  if assignees
34
- assign_users_thread = assign_users(issue, assignees)
37
+ assign_users_thread = assign_users(issue, assignees, output)
35
38
  else
36
- assign_users_thread = assign_authenticated_user(repository, issue)
39
+ assign_users_thread = assign_authenticated_user(repository, issue, output)
37
40
  end
38
41
 
39
42
  add_labels_thread&.join
@@ -41,18 +44,20 @@ module Geet
41
44
  assign_users_thread.join
42
45
 
43
46
  if no_open_issue
44
- puts "Issue address: #{issue.link}"
47
+ output.puts "Issue address: #{issue.link}"
45
48
  else
46
49
  os_open(issue.link)
47
50
  end
51
+
52
+ issue
48
53
  end
49
54
 
50
55
  private
51
56
 
52
57
  # Internal actions
53
58
 
54
- def select_labels(repository, label_patterns)
55
- puts 'Finding labels...'
59
+ def select_labels(repository, label_patterns, output)
60
+ output.puts 'Finding labels...'
56
61
 
57
62
  Thread.new do
58
63
  all_labels = repository.labels
@@ -61,8 +66,8 @@ module Geet
61
66
  end
62
67
  end
63
68
 
64
- def find_milestone(repository, milestone_pattern)
65
- puts 'Finding milestone...'
69
+ def find_milestone(repository, milestone_pattern, output)
70
+ output.puts 'Finding milestone...'
66
71
 
67
72
  Thread.new do
68
73
  if milestone_pattern =~ /\A\d+\Z/
@@ -75,8 +80,8 @@ module Geet
75
80
  end
76
81
  end
77
82
 
78
- def select_assignees(repository, assignee_patterns)
79
- puts 'Finding collaborators...'
83
+ def select_assignees(repository, assignee_patterns, output)
84
+ output.puts 'Finding collaborators...'
80
85
 
81
86
  Thread.new do
82
87
  all_collaborators = repository.collaborators
@@ -85,32 +90,32 @@ module Geet
85
90
  end
86
91
  end
87
92
 
88
- def add_labels(issue, selected_labels)
89
- puts "Adding labels #{selected_labels.join(', ')}..."
93
+ def add_labels(issue, selected_labels, output)
94
+ output.puts "Adding labels #{selected_labels.join(', ')}..."
90
95
 
91
96
  Thread.new do
92
97
  issue.add_labels(selected_labels)
93
98
  end
94
99
  end
95
100
 
96
- def set_milestone(issue, milestone)
97
- puts "Setting milestone #{milestone.title}..."
101
+ def set_milestone(issue, milestone, output)
102
+ output.puts "Setting milestone #{milestone.title}..."
98
103
 
99
104
  Thread.new do
100
105
  issue.edit(milestone: milestone.number)
101
106
  end
102
107
  end
103
108
 
104
- def assign_users(issue, users)
105
- puts "Assigning users #{users.join(', ')}..."
109
+ def assign_users(issue, users, output)
110
+ output.puts "Assigning users #{users.join(', ')}..."
106
111
 
107
112
  Thread.new do
108
113
  issue.assign_users(users)
109
114
  end
110
115
  end
111
116
 
112
- def assign_authenticated_user(repository, issue)
113
- puts 'Assigning authenticated user...'
117
+ def assign_authenticated_user(repository, issue, output)
118
+ output.puts 'Assigning authenticated user...'
114
119
 
115
120
  Thread.new do
116
121
  issue.assign_users(repository.authenticated_user)