geet 0.3.14 → 0.4.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/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/README.md +4 -8
- data/bin/geet +23 -8
- data/geet.gemspec +1 -1
- data/lib/geet/commandline/commands.rb +4 -0
- data/lib/geet/commandline/configuration.rb +26 -1
- data/lib/geet/git/repository.rb +21 -3
- data/lib/geet/github/api_interface.rb +2 -0
- data/lib/geet/github/branch.rb +1 -1
- data/lib/geet/github/issue.rb +2 -2
- data/lib/geet/github/label.rb +2 -2
- data/lib/geet/github/milestone.rb +29 -2
- data/lib/geet/github/pr.rb +1 -2
- data/lib/geet/github/remote_repository.rb +37 -0
- data/lib/geet/github/user.rb +2 -2
- data/lib/geet/gitlab/api_interface.rb +2 -0
- data/lib/geet/gitlab/label.rb +2 -2
- data/lib/geet/gitlab/milestone.rb +1 -1
- data/lib/geet/gitlab/user.rb +1 -1
- data/lib/geet/helpers/os_helper.rb +4 -3
- data/lib/geet/services/add_upstream_repo.rb +37 -0
- data/lib/geet/services/close_milestones.rb +46 -0
- data/lib/geet/services/comment_pr.rb +2 -2
- data/lib/geet/services/create_issue.rb +5 -3
- data/lib/geet/services/create_milestone.rb +24 -0
- data/lib/geet/services/create_pr.rb +10 -6
- data/lib/geet/services/list_issues.rb +4 -1
- data/lib/geet/services/merge_pr.rb +55 -4
- data/lib/geet/services/open_repo.rb +50 -0
- data/lib/geet/shared/selection.rb +3 -0
- data/lib/geet/utils/attributes_selection_manager.rb +12 -3
- data/lib/geet/utils/git_client.rb +122 -29
- data/lib/geet/version.rb +1 -1
- data/spec/integration/comment_pr_spec.rb +1 -1
- data/spec/integration/create_issue_spec.rb +4 -4
- data/spec/integration/create_label_spec.rb +5 -5
- data/spec/integration/create_milestone_spec.rb +34 -0
- data/spec/integration/create_pr_spec.rb +13 -8
- data/spec/integration/list_issues_spec.rb +6 -6
- data/spec/integration/list_labels_spec.rb +4 -4
- data/spec/integration/list_milestones_spec.rb +4 -4
- data/spec/integration/list_prs_spec.rb +3 -3
- data/spec/integration/merge_pr_spec.rb +33 -4
- data/spec/integration/open_pr_spec.rb +1 -1
- data/spec/integration/open_repo_spec.rb +46 -0
- data/spec/vcr_cassettes/create_issue.yml +1 -1
- data/spec/vcr_cassettes/create_issue_upstream.yml +1 -1
- data/spec/vcr_cassettes/github_com/create_milestone.yml +82 -0
- metadata +11 -4
@@ -10,6 +10,8 @@ module Geet
|
|
10
10
|
class ApiInterface
|
11
11
|
API_BASE_URL = 'https://gitlab.com/api/v4'
|
12
12
|
|
13
|
+
attr_reader :repository_path
|
14
|
+
|
13
15
|
# repo_path: "path/namespace"; required for the current GitLab operations.
|
14
16
|
# upstream: boolean; required for the current GitLab operations.
|
15
17
|
#
|
data/lib/geet/gitlab/label.rb
CHANGED
@@ -11,7 +11,7 @@ module Geet
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Returns a flat list of names in string form.
|
14
|
-
def self.list(api_interface)
|
14
|
+
def self.list(api_interface, **)
|
15
15
|
api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/labels"
|
16
16
|
response = api_interface.send_request(api_path, multipage: true)
|
17
17
|
|
@@ -24,7 +24,7 @@ module Geet
|
|
24
24
|
end
|
25
25
|
|
26
26
|
# See https://docs.gitlab.com/ee/api/labels.html#create-a-new-label
|
27
|
-
def self.create(name, color, api_interface)
|
27
|
+
def self.create(name, color, api_interface, **)
|
28
28
|
api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/labels"
|
29
29
|
request_data = { name: name, color: "##{color}" }
|
30
30
|
|
@@ -17,7 +17,7 @@ module Geet
|
|
17
17
|
|
18
18
|
# See https://docs.gitlab.com/ee/api/milestones.html#list-project-milestones
|
19
19
|
#
|
20
|
-
def self.list(api_interface)
|
20
|
+
def self.list(api_interface, **)
|
21
21
|
api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/milestones"
|
22
22
|
|
23
23
|
response = api_interface.send_request(api_path, multipage: true)
|
data/lib/geet/gitlab/user.rb
CHANGED
@@ -13,7 +13,7 @@ module Geet
|
|
13
13
|
|
14
14
|
# Returns an array of User instances
|
15
15
|
#
|
16
|
-
def self.list_collaborators(api_interface)
|
16
|
+
def self.list_collaborators(api_interface, **)
|
17
17
|
api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/members"
|
18
18
|
|
19
19
|
response = api_interface.send_request(api_path, multipage: true)
|
@@ -26,14 +26,15 @@ module Geet
|
|
26
26
|
# interactive: set when required; in this case, a different API will be used (`system()`
|
27
27
|
# instead of `popen3`).
|
28
28
|
# silent_stderr: don't print the stderr output
|
29
|
+
# allow_error: don't raise error on failure
|
29
30
|
#
|
30
|
-
def execute_command(command, description: nil, interactive: false, silent_stderr: false)
|
31
|
+
def execute_command(command, description: nil, interactive: false, silent_stderr: false, allow_error: false)
|
31
32
|
description_message = " on #{description}" if description
|
32
33
|
|
33
34
|
if interactive
|
34
35
|
system(command)
|
35
36
|
|
36
|
-
if !$CHILD_STATUS.success?
|
37
|
+
if !$CHILD_STATUS.success? && !allow_error
|
37
38
|
raise "Error#{description_message} (exit status: #{$CHILD_STATUS.exitstatus})"
|
38
39
|
end
|
39
40
|
else
|
@@ -43,7 +44,7 @@ module Geet
|
|
43
44
|
|
44
45
|
puts stderr_content if stderr_content != '' && !silent_stderr
|
45
46
|
|
46
|
-
if !wait_thread.value.success?
|
47
|
+
if !wait_thread.value.success? && !allow_error
|
47
48
|
error_message = stderr_content.lines.first&.strip || "Error running command #{command.inspect}"
|
48
49
|
raise "Error#{description_message}: #{error_message}"
|
49
50
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geet
|
4
|
+
module Services
|
5
|
+
# Add the upstream repository to the current repository (configuration).
|
6
|
+
#
|
7
|
+
class AddUpstreamRepo
|
8
|
+
DEFAULT_GIT_CLIENT = Utils::GitClient.new
|
9
|
+
|
10
|
+
def initialize(repository, out: $stdout, git_client: DEFAULT_GIT_CLIENT)
|
11
|
+
@repository = repository
|
12
|
+
@out = out
|
13
|
+
@git_client = git_client
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute
|
17
|
+
raise "Upstream remote already existing!" if @git_client.remote_defined?(Utils::GitClient::UPSTREAM_NAME)
|
18
|
+
|
19
|
+
parent_path = @repository.remote.parent_path
|
20
|
+
|
21
|
+
parent_url = compose_parent_url(parent_path)
|
22
|
+
|
23
|
+
@git_client.add_remote(Utils::GitClient::UPSTREAM_NAME, parent_url)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Use the same protocol as the main repository.
|
29
|
+
#
|
30
|
+
def compose_parent_url(parent_path)
|
31
|
+
protocol, domain, separator, _, suffix = @git_client.remote_components
|
32
|
+
|
33
|
+
"#{protocol}#{domain}#{separator}#{parent_path}#{suffix}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../shared/selection'
|
4
|
+
|
5
|
+
module Geet
|
6
|
+
module Services
|
7
|
+
class CloseMilestones
|
8
|
+
include Geet::Shared::Selection
|
9
|
+
|
10
|
+
def initialize(repository, out: $stdout)
|
11
|
+
@repository = repository
|
12
|
+
@out = out
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(numbers: nil)
|
16
|
+
numbers = find_and_select_milestone_numbers(numbers)
|
17
|
+
|
18
|
+
close_milestone_threads = close_milestones(numbers)
|
19
|
+
|
20
|
+
close_milestone_threads.each(&:join)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_and_select_milestone_numbers(numbers)
|
26
|
+
selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
|
27
|
+
|
28
|
+
selection_manager.add_attribute(:milestones, 'milestone', numbers, SELECTION_MULTIPLE, name_method: :title)
|
29
|
+
|
30
|
+
milestones = selection_manager.select_attributes[0]
|
31
|
+
|
32
|
+
milestones.map(&:number)
|
33
|
+
end
|
34
|
+
|
35
|
+
def close_milestones(numbers)
|
36
|
+
@out.puts "Closing milestones #{numbers.join(', ')}..."
|
37
|
+
|
38
|
+
numbers.map do |number|
|
39
|
+
Thread.new do
|
40
|
+
@repository.close_milestone(number)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end # CloseMilestones
|
45
|
+
end # Services
|
46
|
+
end # Geet
|
@@ -19,11 +19,11 @@ module Geet
|
|
19
19
|
@git_client = git_client
|
20
20
|
end
|
21
21
|
|
22
|
-
def execute(comment)
|
22
|
+
def execute(comment, no_open_pr: nil)
|
23
23
|
merge_owner, merge_head = find_merge_head
|
24
24
|
pr = checked_find_branch_pr(merge_owner, merge_head)
|
25
25
|
pr.comment(comment)
|
26
|
-
open_file_with_default_application(pr.link)
|
26
|
+
open_file_with_default_application(pr.link) unless no_open_pr
|
27
27
|
pr
|
28
28
|
end
|
29
29
|
end
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
require_relative 'abstract_create_issue'
|
4
4
|
require_relative '../shared/repo_permissions'
|
5
|
+
require_relative '../shared/selection'
|
5
6
|
|
6
7
|
module Geet
|
7
8
|
module Services
|
8
9
|
class CreateIssue < AbstractCreateIssue
|
9
10
|
include Geet::Shared::RepoPermissions
|
11
|
+
include Geet::Shared::Selection
|
10
12
|
|
11
13
|
# options:
|
12
14
|
# :labels
|
@@ -56,9 +58,9 @@ module Geet
|
|
56
58
|
def find_and_select_attributes(labels, milestone, assignees)
|
57
59
|
selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
|
58
60
|
|
59
|
-
selection_manager.add_attribute(:labels, 'label', labels,
|
60
|
-
selection_manager.add_attribute(:milestones, 'milestone', milestone,
|
61
|
-
selection_manager.add_attribute(:collaborators, 'assignee', assignees,
|
61
|
+
selection_manager.add_attribute(:labels, 'label', labels, SELECTION_MULTIPLE, name_method: :name) if labels
|
62
|
+
selection_manager.add_attribute(:milestones, 'milestone', milestone, SELECTION_SINGLE, name_method: :title) if milestone
|
63
|
+
selection_manager.add_attribute(:collaborators, 'assignee', assignees, SELECTION_MULTIPLE, name_method: :username) if assignees
|
62
64
|
|
63
65
|
selection_manager.select_attributes
|
64
66
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geet
|
4
|
+
module Services
|
5
|
+
class CreateMilestone
|
6
|
+
def initialize(repository, out: $stdout)
|
7
|
+
@repository = repository
|
8
|
+
@out = out
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(title)
|
12
|
+
create_milestone(title)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def create_milestone(title)
|
18
|
+
@out.puts 'Creating milestone...'
|
19
|
+
|
20
|
+
@repository.create_milestone(title)
|
21
|
+
end
|
22
|
+
end # class CreateMilestone
|
23
|
+
end # module Services
|
24
|
+
end # module Geet
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
require_relative 'abstract_create_issue'
|
4
4
|
require_relative '../shared/repo_permissions'
|
5
|
+
require_relative '../shared/selection'
|
5
6
|
|
6
7
|
module Geet
|
7
8
|
module Services
|
8
9
|
class CreatePr < AbstractCreateIssue
|
9
10
|
include Geet::Shared::RepoPermissions
|
11
|
+
include Geet::Shared::Selection
|
10
12
|
|
11
13
|
DEFAULT_GIT_CLIENT = Geet::Utils::GitClient.new
|
12
14
|
|
@@ -37,7 +39,7 @@ module Geet
|
|
37
39
|
|
38
40
|
sync_with_upstream_branch if automated_mode
|
39
41
|
|
40
|
-
pr = create_pr(title, description, base)
|
42
|
+
pr = create_pr(title, description, base: base)
|
41
43
|
|
42
44
|
if user_has_write_permissions
|
43
45
|
edit_pr(pr, selected_labels, selected_milestone, selected_reviewers)
|
@@ -66,11 +68,11 @@ module Geet
|
|
66
68
|
def find_and_select_attributes(labels, milestone, reviewers)
|
67
69
|
selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
|
68
70
|
|
69
|
-
selection_manager.add_attribute(:labels, 'label', labels,
|
70
|
-
selection_manager.add_attribute(:milestones, 'milestone', milestone,
|
71
|
+
selection_manager.add_attribute(:labels, 'label', labels, SELECTION_MULTIPLE, name_method: :name) if labels
|
72
|
+
selection_manager.add_attribute(:milestones, 'milestone', milestone, SELECTION_SINGLE, name_method: :title) if milestone
|
71
73
|
|
72
74
|
if reviewers
|
73
|
-
selection_manager.add_attribute(:collaborators, 'reviewer', reviewers,
|
75
|
+
selection_manager.add_attribute(:collaborators, 'reviewer', reviewers, SELECTION_MULTIPLE, name_method: :username) do |all_reviewers|
|
74
76
|
authenticated_user = @repository.authenticated_user
|
75
77
|
all_reviewers.delete_if { |reviewer| reviewer.username == authenticated_user.username }
|
76
78
|
end
|
@@ -93,10 +95,12 @@ module Geet
|
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
96
|
-
def create_pr(title, description, base)
|
98
|
+
def create_pr(title, description, base: nil)
|
97
99
|
@out.puts 'Creating PR...'
|
98
100
|
|
99
|
-
|
101
|
+
base ||= @git_client.main_branch
|
102
|
+
|
103
|
+
@repository.create_pr(title, description, @git_client.current_branch, base)
|
100
104
|
end
|
101
105
|
|
102
106
|
def edit_pr(pr, labels, milestone, reviewers)
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../utils/attributes_selection_manager'
|
4
|
+
require_relative '../shared/selection'
|
4
5
|
|
5
6
|
module Geet
|
6
7
|
module Services
|
7
8
|
class ListIssues
|
9
|
+
include Geet::Shared::Selection
|
10
|
+
|
8
11
|
def initialize(repository, out: $stdout)
|
9
12
|
@repository = repository
|
10
13
|
@out = out
|
@@ -25,7 +28,7 @@ module Geet
|
|
25
28
|
def find_and_select_attributes(assignee)
|
26
29
|
selection_manager = Geet::Utils::AttributesSelectionManager.new(@repository, out: @out)
|
27
30
|
|
28
|
-
selection_manager.add_attribute(:collaborators, 'assignee', assignee,
|
31
|
+
selection_manager.add_attribute(:collaborators, 'assignee', assignee, SELECTION_SINGLE, name_method: :username)
|
29
32
|
|
30
33
|
selection_manager.select_attributes[0]
|
31
34
|
end
|
@@ -12,6 +12,7 @@ module Geet
|
|
12
12
|
#
|
13
13
|
class MergePr
|
14
14
|
include Geet::Helpers::ServicesWorkflowHelper
|
15
|
+
include Geet::Shared
|
15
16
|
|
16
17
|
DEFAULT_GIT_CLIENT = Geet::Utils::GitClient.new
|
17
18
|
|
@@ -24,8 +25,30 @@ module Geet
|
|
24
25
|
def execute(delete_branch: false)
|
25
26
|
merge_owner, merge_head = find_merge_head
|
26
27
|
pr = checked_find_branch_pr(merge_owner, merge_head)
|
28
|
+
|
27
29
|
merge_pr(pr)
|
28
|
-
|
30
|
+
|
31
|
+
if delete_branch
|
32
|
+
branch = @git_client.current_branch
|
33
|
+
|
34
|
+
delete_remote_branch(branch)
|
35
|
+
end
|
36
|
+
|
37
|
+
fetch_repository
|
38
|
+
|
39
|
+
if upstream_branch_gone?
|
40
|
+
pr_branch = @git_client.current_branch
|
41
|
+
main_branch = @git_client.main_branch
|
42
|
+
|
43
|
+
# The rebase could also be placed after the branch deletion. There are pros/cons;
|
44
|
+
# currently, it's not important.
|
45
|
+
#
|
46
|
+
checkout_branch(main_branch)
|
47
|
+
rebase
|
48
|
+
|
49
|
+
delete_local_branch(pr_branch)
|
50
|
+
end
|
51
|
+
|
29
52
|
pr
|
30
53
|
end
|
31
54
|
|
@@ -37,10 +60,38 @@ module Geet
|
|
37
60
|
pr.merge
|
38
61
|
end
|
39
62
|
|
40
|
-
def
|
41
|
-
@out.puts "Deleting branch #{
|
63
|
+
def delete_remote_branch(branch)
|
64
|
+
@out.puts "Deleting remote branch #{branch}..."
|
65
|
+
|
66
|
+
@repository.delete_branch(branch)
|
67
|
+
end
|
68
|
+
|
69
|
+
def fetch_repository
|
70
|
+
@out.puts "Fetching repository..."
|
71
|
+
|
72
|
+
@git_client.fetch
|
73
|
+
end
|
74
|
+
|
75
|
+
def upstream_branch_gone?
|
76
|
+
@git_client.upstream_branch_gone?
|
77
|
+
end
|
78
|
+
|
79
|
+
def checkout_branch(branch)
|
80
|
+
@out.puts "Checking out #{branch}..."
|
81
|
+
|
82
|
+
@git_client.checkout(branch)
|
83
|
+
end
|
84
|
+
|
85
|
+
def rebase
|
86
|
+
@out.puts "Rebasing..."
|
87
|
+
|
88
|
+
@git_client.rebase
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete_local_branch(branch)
|
92
|
+
@out.puts "Deleting local branch #{branch}..."
|
42
93
|
|
43
|
-
@
|
94
|
+
@git_client.delete_branch(branch)
|
44
95
|
end
|
45
96
|
end
|
46
97
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../helpers/os_helper'
|
4
|
+
|
5
|
+
module Geet
|
6
|
+
module Services
|
7
|
+
# Open in the browser the current repository.
|
8
|
+
#
|
9
|
+
class OpenRepo
|
10
|
+
include Helpers::OsHelper
|
11
|
+
|
12
|
+
DEFAULT_GIT_CLIENT = Utils::GitClient.new
|
13
|
+
|
14
|
+
def initialize(repository, out: $stdout, git_client: DEFAULT_GIT_CLIENT)
|
15
|
+
@repository = repository
|
16
|
+
@out = out
|
17
|
+
@git_client = git_client
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute(upstream: false)
|
21
|
+
remote_options = upstream ? {name: Utils::GitClient::UPSTREAM_NAME} : {}
|
22
|
+
|
23
|
+
repo_url = @git_client.remote(**remote_options)
|
24
|
+
repo_url = convert_repo_url_to_http_protocol(repo_url)
|
25
|
+
|
26
|
+
open_file_with_default_application(repo_url)
|
27
|
+
|
28
|
+
repo_url
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# The repository URL may be in any of the git/http protocols.
|
34
|
+
#
|
35
|
+
def convert_repo_url_to_http_protocol(repo_url)
|
36
|
+
case repo_url
|
37
|
+
when /https:/
|
38
|
+
when /git@/
|
39
|
+
else
|
40
|
+
# Minimal error, due to match guaranteed by GitClient#remote.
|
41
|
+
raise
|
42
|
+
end
|
43
|
+
|
44
|
+
domain, _, path = repo_url.match(Utils::GitClient::REMOTE_URL_REGEX)[2..4]
|
45
|
+
|
46
|
+
"https://#{domain}/#{path}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|