geet 0.1.12 → 0.2.0
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 +4 -4
- data/.rubocop.yml +32 -6
- data/.rubocop_todo.yml +24 -1
- data/.travis.yml +7 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +18 -0
- data/README.md +45 -11
- data/Rakefile +5 -0
- data/bin/geet +6 -9
- data/extra/issue_editing.png +0 -0
- data/extra/pr_editing.png +0 -0
- data/geet.gemspec +2 -2
- data/lib/geet/commandline/commands.rb +3 -1
- data/lib/geet/commandline/configuration.rb +37 -31
- data/lib/geet/commandline/editor.rb +66 -0
- data/lib/geet/git/repository.rb +5 -1
- data/lib/geet/github/abstract_issue.rb +7 -3
- data/lib/geet/github/api_interface.rb +9 -5
- data/lib/geet/github/branch.rb +15 -0
- data/lib/geet/github/gist.rb +1 -1
- data/lib/geet/github/issue.rb +1 -13
- data/lib/geet/github/label.rb +1 -1
- data/lib/geet/gitlab/api_interface.rb +9 -5
- data/lib/geet/helpers/os_helper.rb +8 -1
- data/lib/geet/resources/edit_summary_template.md +7 -0
- data/lib/geet/services/create_gist.rb +1 -1
- data/lib/geet/services/create_issue.rb +1 -1
- data/lib/geet/services/create_pr.rb +5 -2
- data/lib/geet/services/merge_pr.rb +8 -1
- data/lib/geet/version.rb +1 -1
- data/spec/integration/create_gist_spec.rb +5 -3
- data/spec/integration/create_issue_spec.rb +6 -3
- data/spec/integration/create_label_spec.rb +6 -4
- data/spec/integration/create_pr_spec.rb +4 -2
- data/spec/integration/list_issues_spec.rb +5 -3
- data/spec/integration/list_labels_spec.rb +4 -2
- data/spec/integration/list_milestones_spec.rb +3 -1
- data/spec/integration/list_prs_spec.rb +4 -2
- data/spec/integration/merge_pr_spec.rb +26 -1
- data/spec/spec_helper.rb +4 -2
- data/spec/vcr_cassettes/merge_pr_with_branch_deletion.yml +222 -0
- metadata +11 -3
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
require_relative '../helpers/os_helper.rb'
|
6
|
+
|
7
|
+
module Geet
|
8
|
+
module Commandline
|
9
|
+
class Editor
|
10
|
+
# Liberally ripp..., ahem, inspired from git.
|
11
|
+
SUMMARY_TEMPLATE = File.expand_path('../resources/edit_summary_template.md', __dir__)
|
12
|
+
SUMMARY_TEMPLATE_SEPARATOR = '------------------------ >8 ------------------------'
|
13
|
+
|
14
|
+
include Geet::Helpers::OsHelper
|
15
|
+
|
16
|
+
# Edits a summary in the default editor, providing the SUMMARY_TEMPLATE.
|
17
|
+
#
|
18
|
+
# A summary is a composition with a title and an optional description;
|
19
|
+
# if the description is not found, a blank string is returned.
|
20
|
+
#
|
21
|
+
def edit_summary
|
22
|
+
raw_summary = edit_content_in_default_editor(IO.read(SUMMARY_TEMPLATE))
|
23
|
+
|
24
|
+
split_raw_summary(raw_summary)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# MAIN STEPS #######################################################################
|
30
|
+
|
31
|
+
# The gem `tty-editor` does this, although it requires in turn other 7/8 gems.
|
32
|
+
# Interestingly, the API `TTY::Editor.open(content: 'text')` is not very useful,
|
33
|
+
# as it doesn't return the filename (!).
|
34
|
+
#
|
35
|
+
def edit_content_in_default_editor(content)
|
36
|
+
tempfile = Tempfile.open(['geet_editor', '.md']) { |file| file << content }.path
|
37
|
+
|
38
|
+
execute_command('editing', system_editor, tempfile)
|
39
|
+
|
40
|
+
content = IO.read(tempfile)
|
41
|
+
|
42
|
+
File.unlink(tempfile)
|
43
|
+
|
44
|
+
content
|
45
|
+
end
|
46
|
+
|
47
|
+
def split_raw_summary(raw_summary)
|
48
|
+
raw_summary, _ = raw_summary.strip.split(SUMMARY_TEMPLATE_SEPARATOR, 2)
|
49
|
+
|
50
|
+
raise "Missing title!" if raw_summary.empty?
|
51
|
+
|
52
|
+
title, description = raw_summary.split(/\r|\n/, 2)
|
53
|
+
|
54
|
+
# The title may have a residual newline char; the description may not be present,
|
55
|
+
# or have multiple blank lines.
|
56
|
+
[title.strip, description.to_s.strip]
|
57
|
+
end
|
58
|
+
|
59
|
+
# HELPERS ##########################################################################
|
60
|
+
|
61
|
+
def system_editor
|
62
|
+
ENV['EDITOR'] || ENV['VISUAL'] || 'vi'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/geet/git/repository.rb
CHANGED
@@ -47,6 +47,10 @@ module Geet
|
|
47
47
|
attempt_provider_call(:Label, :create, name, color, api_interface)
|
48
48
|
end
|
49
49
|
|
50
|
+
def delete_branch(name)
|
51
|
+
attempt_provider_call(:Branch, :delete, name, api_interface)
|
52
|
+
end
|
53
|
+
|
50
54
|
def abstract_issues(milestone: nil)
|
51
55
|
attempt_provider_call(:AbstractIssue, :list, api_interface, milestone: milestone)
|
52
56
|
end
|
@@ -139,7 +143,7 @@ module Geet
|
|
139
143
|
if Kernel.const_defined?(full_class_name)
|
140
144
|
klass = Kernel.const_get(full_class_name)
|
141
145
|
|
142
|
-
if !
|
146
|
+
if !klass.respond_to?(meth)
|
143
147
|
raise "The functionality invoked (#{class_name} #{meth}) is not currently supported!"
|
144
148
|
end
|
145
149
|
|
@@ -22,21 +22,25 @@ module Geet
|
|
22
22
|
|
23
23
|
# See https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
24
24
|
#
|
25
|
-
def self.list(api_interface, milestone: nil)
|
25
|
+
def self.list(api_interface, only_issues: false, milestone: nil)
|
26
26
|
api_path = 'issues'
|
27
27
|
request_params = { milestone: milestone } if milestone
|
28
28
|
|
29
29
|
response = api_interface.send_request(api_path, params: request_params, multipage: true)
|
30
30
|
|
31
|
-
response.map do |issue_data|
|
31
|
+
abstract_issues_list = response.map do |issue_data|
|
32
32
|
number = issue_data.fetch('number')
|
33
33
|
title = issue_data.fetch('title')
|
34
34
|
link = issue_data.fetch('html_url')
|
35
35
|
|
36
36
|
klazz = issue_data.key?('pull_request') ? PR : Issue
|
37
37
|
|
38
|
-
klazz
|
38
|
+
if !only_issues || klazz == Issue
|
39
|
+
klazz.new(number, api_interface, title, link)
|
40
|
+
end
|
39
41
|
end
|
42
|
+
|
43
|
+
abstract_issues_list.compact
|
40
44
|
end
|
41
45
|
|
42
46
|
# params:
|
@@ -24,6 +24,7 @@ module Geet
|
|
24
24
|
# Send a request.
|
25
25
|
#
|
26
26
|
# Returns the parsed response, or an Array, in case of multipage.
|
27
|
+
# Where no body is present in the response, nil is returned.
|
27
28
|
#
|
28
29
|
# params:
|
29
30
|
# :api_path: api path, will be appended to the API URL.
|
@@ -35,8 +36,9 @@ module Geet
|
|
35
36
|
# :data: (Hash) if present, will generate a POST request, otherwise, a GET
|
36
37
|
# :multipage: set true for paged Github responses (eg. issues); it will make the method
|
37
38
|
# return an array, with the concatenated (parsed) responses
|
38
|
-
# :http_method: :get, :patch, :post
|
39
|
-
#
|
39
|
+
# :http_method: symbol format of the method (:get, :patch, :post, :put and :delete)
|
40
|
+
# :get and :post are automatically inferred by the present of :data; the other
|
41
|
+
# cases must be specified.
|
40
42
|
#
|
41
43
|
def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
|
42
44
|
address = api_url(api_path)
|
@@ -46,7 +48,7 @@ module Geet
|
|
46
48
|
loop do
|
47
49
|
response = send_http_request(address, params: params, data: data, http_method: http_method)
|
48
50
|
|
49
|
-
parsed_response = JSON.parse(response.body)
|
51
|
+
parsed_response = JSON.parse(response.body) if response.body
|
50
52
|
|
51
53
|
if error?(response)
|
52
54
|
formatted_error = decode_and_format_error(parsed_response)
|
@@ -133,12 +135,14 @@ module Geet
|
|
133
135
|
case http_method
|
134
136
|
when :get
|
135
137
|
Net::HTTP::Get
|
138
|
+
when :delete
|
139
|
+
Net::HTTP::Delete
|
136
140
|
when :patch
|
137
141
|
Net::HTTP::Patch
|
138
|
-
when :put
|
139
|
-
Net::HTTP::Put
|
140
142
|
when :post
|
141
143
|
Net::HTTP::Post
|
144
|
+
when :put
|
145
|
+
Net::HTTP::Put
|
142
146
|
else
|
143
147
|
raise "Unsupported HTTP method: #{http_method.inspect}"
|
144
148
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geet
|
4
|
+
module Github
|
5
|
+
class Branch
|
6
|
+
# See https://developer.github.com/v3/git/refs/#delete-a-reference
|
7
|
+
#
|
8
|
+
def self.delete(name, api_interface)
|
9
|
+
api_path = "git/refs/heads/#{name}"
|
10
|
+
|
11
|
+
api_interface.send_request(api_path, http_method: :delete)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/geet/github/gist.rb
CHANGED
data/lib/geet/github/issue.rb
CHANGED
@@ -20,19 +20,7 @@ module Geet
|
|
20
20
|
# See https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
21
21
|
#
|
22
22
|
def self.list(api_interface)
|
23
|
-
|
24
|
-
|
25
|
-
response = api_interface.send_request(api_path, multipage: true)
|
26
|
-
|
27
|
-
response.each_with_object([]) do |issue_data, result|
|
28
|
-
if !issue_data.key?('pull_request')
|
29
|
-
number = issue_data.fetch('number')
|
30
|
-
title = issue_data.fetch('title')
|
31
|
-
link = issue_data.fetch('html_url')
|
32
|
-
|
33
|
-
result << new(number, api_interface, title, link)
|
34
|
-
end
|
35
|
-
end
|
23
|
+
super(api_interface, only_issues: true)
|
36
24
|
end
|
37
25
|
end
|
38
26
|
end
|
data/lib/geet/github/label.rb
CHANGED
@@ -28,7 +28,7 @@ module Geet
|
|
28
28
|
# See https://developer.github.com/v3/issues/labels/#create-a-label
|
29
29
|
def self.create(name, color, api_interface)
|
30
30
|
api_path = 'labels'
|
31
|
-
request_data = {name: name, color: color }
|
31
|
+
request_data = { name: name, color: color }
|
32
32
|
|
33
33
|
api_interface.send_request(api_path, data: request_data)
|
34
34
|
|
@@ -28,6 +28,7 @@ module Geet
|
|
28
28
|
# Send a request.
|
29
29
|
#
|
30
30
|
# Returns the parsed response, or an Array, in case of multipage.
|
31
|
+
# Where no body is present in the response, nil is returned.
|
31
32
|
#
|
32
33
|
# params:
|
33
34
|
# :api_path: api path, will be appended to the API URL.
|
@@ -39,8 +40,9 @@ module Geet
|
|
39
40
|
# :data: (Hash) if present, will generate a POST request, otherwise, a GET
|
40
41
|
# :multipage: set true for paged Github responses (eg. issues); it will make the method
|
41
42
|
# return an array, with the concatenated (parsed) responses
|
42
|
-
# :http_method: :get, :patch, :post
|
43
|
-
#
|
43
|
+
# :http_method: symbol format of the method (:get, :patch, :post, :put and :delete)
|
44
|
+
# :get and :post are automatically inferred by the present of :data; the other
|
45
|
+
# cases must be specified.
|
44
46
|
#
|
45
47
|
def send_request(api_path, params: nil, data: nil, multipage: false, http_method: nil)
|
46
48
|
address = api_url(api_path)
|
@@ -50,7 +52,7 @@ module Geet
|
|
50
52
|
loop do
|
51
53
|
response = send_http_request(address, params: params, data: data, http_method: http_method)
|
52
54
|
|
53
|
-
parsed_response = JSON.parse(response.body)
|
55
|
+
parsed_response = JSON.parse(response.body) if response.body
|
54
56
|
|
55
57
|
if error?(response)
|
56
58
|
formatted_error = decode_and_format_error(parsed_response)
|
@@ -126,12 +128,14 @@ module Geet
|
|
126
128
|
case http_method
|
127
129
|
when :get
|
128
130
|
Net::HTTP::Get
|
131
|
+
when :delete
|
132
|
+
Net::HTTP::Delete
|
129
133
|
when :patch
|
130
134
|
Net::HTTP::Patch
|
131
|
-
when :put
|
132
|
-
Net::HTTP::Put
|
133
135
|
when :post
|
134
136
|
Net::HTTP::Post
|
137
|
+
when :put
|
138
|
+
Net::HTTP::Put
|
135
139
|
else
|
136
140
|
raise "Unsupported HTTP method: #{http_method.inspect}"
|
137
141
|
end
|
@@ -1,17 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'English'
|
3
4
|
require 'shellwords'
|
4
5
|
|
5
6
|
module Geet
|
6
7
|
module Helpers
|
7
8
|
module OsHelper
|
8
|
-
def
|
9
|
+
def open_file_with_default_application(file_or_url)
|
9
10
|
if `uname`.strip == 'Darwin'
|
10
11
|
exec "open #{file_or_url.shellescape}"
|
11
12
|
else
|
12
13
|
exec "xdg-open #{file_or_url.shellescape}"
|
13
14
|
end
|
14
15
|
end
|
16
|
+
|
17
|
+
def execute_command(description, *command_tokens)
|
18
|
+
system(*command_tokens.map(&:shellescape))
|
19
|
+
|
20
|
+
raise "Error during #{description} (exit status: #{$CHILD_STATUS.exitstatus})" if !$CHILD_STATUS.success?
|
21
|
+
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
------------------------ >8 ------------------------
|
4
|
+
Please enter above, the title and description for your changes.
|
5
|
+
The first line is the title, and it's mandatory; lines between
|
6
|
+
the second and the separator above, if present, are the description;
|
7
|
+
the separator and any line below are ignored.
|
@@ -12,7 +12,10 @@ module Geet
|
|
12
12
|
# :reviewer_patterns
|
13
13
|
# :no_open_pr
|
14
14
|
#
|
15
|
-
def execute(
|
15
|
+
def execute(
|
16
|
+
repository, title, description, label_patterns: nil, milestone_pattern: nil, reviewer_patterns: nil,
|
17
|
+
no_open_pr: nil, output: $stdout, **
|
18
|
+
)
|
16
19
|
labels_thread = select_labels(repository, label_patterns, output) if label_patterns
|
17
20
|
milestone_thread = find_milestone(repository, milestone_pattern, output) if milestone_pattern
|
18
21
|
reviewers_thread = select_reviewers(repository, reviewer_patterns, output) if reviewer_patterns
|
@@ -36,7 +39,7 @@ module Geet
|
|
36
39
|
if no_open_pr
|
37
40
|
output.puts "PR address: #{pr.link}"
|
38
41
|
else
|
39
|
-
|
42
|
+
open_file_with_default_application(pr.link)
|
40
43
|
end
|
41
44
|
|
42
45
|
pr
|
@@ -3,10 +3,11 @@
|
|
3
3
|
module Geet
|
4
4
|
module Services
|
5
5
|
class MergePr
|
6
|
-
def execute(repository, output: $stdout)
|
6
|
+
def execute(repository, delete_branch: false, output: $stdout)
|
7
7
|
merge_head = find_merge_head(repository)
|
8
8
|
pr = checked_find_branch_pr(repository, merge_head, output)
|
9
9
|
merge_pr(pr, output)
|
10
|
+
do_delete_branch(repository, output) if delete_branch
|
10
11
|
pr
|
11
12
|
end
|
12
13
|
|
@@ -32,6 +33,12 @@ module Geet
|
|
32
33
|
|
33
34
|
pr.merge
|
34
35
|
end
|
36
|
+
|
37
|
+
def do_delete_branch(repository, output)
|
38
|
+
output.puts "Deleting branch #{repository.current_branch}..."
|
39
|
+
|
40
|
+
repository.delete_branch(repository.current_branch)
|
41
|
+
end
|
35
42
|
end
|
36
43
|
end
|
37
44
|
end
|
data/lib/geet/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'tempfile'
|
3
5
|
|
@@ -6,7 +8,7 @@ require_relative '../../lib/geet/services/create_gist'
|
|
6
8
|
|
7
9
|
describe Geet::Services::CreateGist do
|
8
10
|
let(:repository) { Geet::Git::Repository.new }
|
9
|
-
let(:tempfile) { Tempfile.open('geet_gist') { |file| file <<
|
11
|
+
let(:tempfile) { Tempfile.open('geet_gist') { |file| file << 'testcontent' } }
|
10
12
|
|
11
13
|
it 'should create a public gist' do
|
12
14
|
expected_output = <<~STR
|
@@ -16,7 +18,7 @@ describe Geet::Services::CreateGist do
|
|
16
18
|
|
17
19
|
actual_output = StringIO.new
|
18
20
|
|
19
|
-
VCR.use_cassette(
|
21
|
+
VCR.use_cassette('create_gist_public') do
|
20
22
|
described_class.new.execute(
|
21
23
|
repository, tempfile.path,
|
22
24
|
description: 'testdescription', publik: true, no_browse: true, output: actual_output
|
@@ -34,7 +36,7 @@ describe Geet::Services::CreateGist do
|
|
34
36
|
|
35
37
|
actual_output = StringIO.new
|
36
38
|
|
37
|
-
VCR.use_cassette(
|
39
|
+
VCR.use_cassette('create_gist_private') do
|
38
40
|
described_class.new.execute(
|
39
41
|
repository, tempfile.path,
|
40
42
|
description: 'testdescription', no_browse: true, output: actual_output
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
require_relative '../../lib/geet/git/repository'
|
@@ -24,7 +26,7 @@ describe Geet::Services::CreateIssue do
|
|
24
26
|
|
25
27
|
actual_output = StringIO.new
|
26
28
|
|
27
|
-
actual_created_issue = VCR.use_cassette(
|
29
|
+
actual_created_issue = VCR.use_cassette('create_issue') do
|
28
30
|
described_class.new.execute(
|
29
31
|
repository, 'Title', 'Description',
|
30
32
|
label_patterns: 'bug,invalid', milestone_pattern: '0.0.1', assignee_patterns: 'nald-ts,nald-fr',
|
@@ -53,8 +55,9 @@ describe Geet::Services::CreateIssue do
|
|
53
55
|
|
54
56
|
actual_output = StringIO.new
|
55
57
|
|
56
|
-
actual_created_issue = VCR.use_cassette(
|
57
|
-
|
58
|
+
actual_created_issue = VCR.use_cassette('create_issue_upstream') do
|
59
|
+
create_options = { no_open_issue: true, output: actual_output }
|
60
|
+
described_class.new.execute(upstream_repository, 'Title', 'Description', create_options)
|
58
61
|
end
|
59
62
|
|
60
63
|
expect(actual_output.string).to eql(expected_output)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
require_relative '../../lib/geet/git/repository'
|
@@ -17,7 +19,7 @@ describe Geet::Services::CreateLabel do
|
|
17
19
|
|
18
20
|
actual_output = StringIO.new
|
19
21
|
|
20
|
-
actual_created_label = VCR.use_cassette(
|
22
|
+
actual_created_label = VCR.use_cassette('create_label') do
|
21
23
|
described_class.new.execute(repository, 'my_label', color: 'c64c64', output: actual_output)
|
22
24
|
end
|
23
25
|
|
@@ -34,16 +36,16 @@ describe Geet::Services::CreateLabel do
|
|
34
36
|
|
35
37
|
expected_output_template = <<~STR
|
36
38
|
Creating label...
|
37
|
-
Created with color
|
39
|
+
Created with color #%<color>s
|
38
40
|
STR
|
39
41
|
|
40
42
|
actual_output = StringIO.new
|
41
43
|
|
42
|
-
actual_created_label = VCR.use_cassette(
|
44
|
+
actual_created_label = VCR.use_cassette('create_label_with_random_color') do
|
43
45
|
described_class.new.execute(repository, 'my_label', output: actual_output)
|
44
46
|
end
|
45
47
|
|
46
|
-
expected_output = expected_output_template
|
48
|
+
expected_output = format(expected_output_template, color: actual_created_label.color)
|
47
49
|
|
48
50
|
expect(actual_output.string).to eql(expected_output)
|
49
51
|
|