geet 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
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)