geet 0.3.14 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -0
  4. data/README.md +4 -8
  5. data/bin/geet +23 -8
  6. data/geet.gemspec +1 -1
  7. data/lib/geet/commandline/commands.rb +4 -0
  8. data/lib/geet/commandline/configuration.rb +26 -1
  9. data/lib/geet/git/repository.rb +21 -3
  10. data/lib/geet/github/api_interface.rb +2 -0
  11. data/lib/geet/github/branch.rb +1 -1
  12. data/lib/geet/github/issue.rb +2 -2
  13. data/lib/geet/github/label.rb +2 -2
  14. data/lib/geet/github/milestone.rb +29 -2
  15. data/lib/geet/github/pr.rb +1 -2
  16. data/lib/geet/github/remote_repository.rb +37 -0
  17. data/lib/geet/github/user.rb +2 -2
  18. data/lib/geet/gitlab/api_interface.rb +2 -0
  19. data/lib/geet/gitlab/label.rb +2 -2
  20. data/lib/geet/gitlab/milestone.rb +1 -1
  21. data/lib/geet/gitlab/user.rb +1 -1
  22. data/lib/geet/helpers/os_helper.rb +4 -3
  23. data/lib/geet/services/add_upstream_repo.rb +37 -0
  24. data/lib/geet/services/close_milestones.rb +46 -0
  25. data/lib/geet/services/comment_pr.rb +2 -2
  26. data/lib/geet/services/create_issue.rb +5 -3
  27. data/lib/geet/services/create_milestone.rb +24 -0
  28. data/lib/geet/services/create_pr.rb +10 -6
  29. data/lib/geet/services/list_issues.rb +4 -1
  30. data/lib/geet/services/merge_pr.rb +55 -4
  31. data/lib/geet/services/open_repo.rb +50 -0
  32. data/lib/geet/shared/selection.rb +3 -0
  33. data/lib/geet/utils/attributes_selection_manager.rb +12 -3
  34. data/lib/geet/utils/git_client.rb +122 -29
  35. data/lib/geet/version.rb +1 -1
  36. data/spec/integration/comment_pr_spec.rb +1 -1
  37. data/spec/integration/create_issue_spec.rb +4 -4
  38. data/spec/integration/create_label_spec.rb +5 -5
  39. data/spec/integration/create_milestone_spec.rb +34 -0
  40. data/spec/integration/create_pr_spec.rb +13 -8
  41. data/spec/integration/list_issues_spec.rb +6 -6
  42. data/spec/integration/list_labels_spec.rb +4 -4
  43. data/spec/integration/list_milestones_spec.rb +4 -4
  44. data/spec/integration/list_prs_spec.rb +3 -3
  45. data/spec/integration/merge_pr_spec.rb +33 -4
  46. data/spec/integration/open_pr_spec.rb +1 -1
  47. data/spec/integration/open_repo_spec.rb +46 -0
  48. data/spec/vcr_cassettes/create_issue.yml +1 -1
  49. data/spec/vcr_cassettes/create_issue_upstream.yml +1 -1
  50. data/spec/vcr_cassettes/github_com/create_milestone.yml +82 -0
  51. 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
  #
@@ -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)
@@ -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, :multiple, name_method: :name) if labels
60
- selection_manager.add_attribute(:milestones, 'milestone', milestone, :single, name_method: :title) if milestone
61
- selection_manager.add_attribute(:collaborators, 'assignee', assignees, :multiple, name_method: :username) if 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, :multiple, name_method: :name) if labels
70
- selection_manager.add_attribute(:milestones, 'milestone', milestone, :single, name_method: :title) if 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, :multiple, name_method: :username) do |all_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
- @repository.create_pr(title, description, @git_client.current_branch, base: base)
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, :single, name_method: :username)
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
- do_delete_branch if delete_branch
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 do_delete_branch
41
- @out.puts "Deleting branch #{@git_client.current_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
- @repository.delete_branch(@git_client.current_branch)
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
@@ -4,6 +4,9 @@ module Geet
4
4
  module Shared
5
5
  module Selection
6
6
  MANUAL_LIST_SELECTION_FLAG = '-'.freeze
7
+
8
+ SELECTION_SINGLE = :single
9
+ SELECTION_MULTIPLE = :multiple
7
10
  end
8
11
  end
9
12
  end