geet 0.3.11 → 0.3.16

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.byebug_history +5 -0
  3. data/.travis.yml +7 -0
  4. data/README.md +2 -0
  5. data/bin/geet +17 -0
  6. data/extra/anonymize_vcr_data +58 -0
  7. data/geet.gemspec +1 -1
  8. data/lib/geet/commandline/commands.rb +4 -0
  9. data/lib/geet/commandline/configuration.rb +23 -0
  10. data/lib/geet/git/repository.rb +10 -2
  11. data/lib/geet/github/abstract_issue.rb +9 -0
  12. data/lib/geet/github/milestone.rb +27 -0
  13. data/lib/geet/github/pr.rb +2 -2
  14. data/lib/geet/gitlab/pr.rb +16 -1
  15. data/lib/geet/helpers/services_workflow_helper.rb +33 -0
  16. data/lib/geet/services/close_milestones.rb +46 -0
  17. data/lib/geet/services/comment_pr.rb +31 -0
  18. data/lib/geet/services/create_issue.rb +5 -3
  19. data/lib/geet/services/create_milestone.rb +24 -0
  20. data/lib/geet/services/create_pr.rb +5 -3
  21. data/lib/geet/services/list_issues.rb +4 -1
  22. data/lib/geet/services/merge_pr.rb +57 -17
  23. data/lib/geet/services/open_pr.rb +30 -0
  24. data/lib/geet/shared/branches.rb +9 -0
  25. data/lib/geet/shared/selection.rb +3 -0
  26. data/lib/geet/utils/attributes_selection_manager.rb +5 -3
  27. data/lib/geet/utils/git_client.rb +65 -11
  28. data/lib/geet/version.rb +1 -1
  29. data/spec/integration/comment_pr_spec.rb +44 -0
  30. data/spec/integration/create_milestone_spec.rb +34 -0
  31. data/spec/integration/merge_pr_spec.rb +47 -16
  32. data/spec/integration/open_pr_spec.rb +44 -0
  33. data/spec/vcr_cassettes/create_gist_private.yml +1 -1
  34. data/spec/vcr_cassettes/create_gist_public.yml +1 -1
  35. data/spec/vcr_cassettes/create_issue.yml +13 -13
  36. data/spec/vcr_cassettes/create_issue_upstream.yml +2 -2
  37. data/spec/vcr_cassettes/github_com/comment_pr.yml +161 -0
  38. data/spec/vcr_cassettes/github_com/create_label.yml +1 -1
  39. data/spec/vcr_cassettes/github_com/create_label_upstream.yml +1 -1
  40. data/spec/vcr_cassettes/github_com/create_label_with_random_color.yml +1 -1
  41. data/spec/vcr_cassettes/github_com/create_milestone.yml +82 -0
  42. data/spec/vcr_cassettes/github_com/create_pr.yml +16 -16
  43. data/spec/vcr_cassettes/github_com/create_pr_in_auto_mode_create_upstream.yml +7 -7
  44. data/spec/vcr_cassettes/github_com/create_pr_in_auto_mode_with_push.yml +7 -7
  45. data/spec/vcr_cassettes/github_com/create_pr_upstream.yml +8 -8
  46. data/spec/vcr_cassettes/github_com/create_pr_upstream_without_write_permissions.yml +3 -3
  47. data/spec/vcr_cassettes/github_com/list_issues.yml +5 -5
  48. data/spec/vcr_cassettes/github_com/list_issues_upstream.yml +6 -6
  49. data/spec/vcr_cassettes/github_com/list_issues_with_assignee.yml +4 -4
  50. data/spec/vcr_cassettes/github_com/list_labels.yml +1 -1
  51. data/spec/vcr_cassettes/github_com/list_labels_upstream.yml +1 -1
  52. data/spec/vcr_cassettes/github_com/list_milestones.yml +50 -50
  53. data/spec/vcr_cassettes/github_com/merge_pr.yml +35 -34
  54. data/spec/vcr_cassettes/github_com/merge_pr_with_branch_deletion.yml +48 -45
  55. data/spec/vcr_cassettes/github_com/open_pr.yml +81 -0
  56. data/spec/vcr_cassettes/gitlab_com/create_label.yml +1 -1
  57. data/spec/vcr_cassettes/gitlab_com/list_issues.yml +4 -4
  58. data/spec/vcr_cassettes/gitlab_com/list_issues_with_assignee.yml +8 -8
  59. data/spec/vcr_cassettes/gitlab_com/list_labels.yml +1 -1
  60. data/spec/vcr_cassettes/gitlab_com/list_milestones.yml +9 -9
  61. data/spec/vcr_cassettes/gitlab_com/merge_pr.yml +24 -24
  62. data/spec/vcr_cassettes/list_milestones_upstream.yml +21 -21
  63. data/spec/vcr_cassettes/list_prs.yml +10 -10
  64. data/spec/vcr_cassettes/list_prs_upstream.yml +10 -10
  65. metadata +17 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3028d791ef73b12a9f62d87bb5bf8816732458f83ff516710fcb05995a7e454d
4
- data.tar.gz: bc08e3337f41536a1b52064fc4b6d54cda680206e71eae53d1a85a4dc2f1579c
3
+ metadata.gz: 15603a9a3a61fd8b6c3a377bd8fb1f482c121078b153e0cb8f3bdf12609d01f2
4
+ data.tar.gz: cde2b52ebcc17f2af2e64466c5565a65fad846e946747d91dfd734e5ce108e2c
5
5
  SHA512:
6
- metadata.gz: cf3fbd2a20215ad7d41a5016bea9f35eeff72c5db9a811c1d5f8b77b427c22fd4b3ff7ed3314befe228e821e546c44dedb1c9ca48016d965990a5194ee4452ad
7
- data.tar.gz: 131fd6221ab18b4aa41d8fb9bfbed26b4bb41e10f876d6341a9958f6e958755956ac540739424cb5758b3a51db427ee41a1a38fb6dbec4fd748392b3de707de8
6
+ metadata.gz: 5ea5d57a2f25a0f83910afe316194d335f11d04b4228324ab577845a74b62331705b3650b654cdb951e69c6fa76438441e1fa1caa3bf431e456acefc39f4457d
7
+ data.tar.gz: 06c954789d0e39cb723daa45de62c190817773cad1c29d1719524452c98731ded5f0cce8f72450f677c54688c38636b21538eca04226dafb45b00d16ab548806
@@ -0,0 +1,5 @@
1
+ n
2
+ s
3
+ n
4
+ s
5
+ owner
@@ -1,9 +1,16 @@
1
+ dist: bionic
1
2
  language: ruby
2
3
  rvm:
3
4
  - 2.3
4
5
  - 2.4
5
6
  - 2.5
6
7
  - 2.6
8
+ - 2.7
9
+ - ruby-head
10
+ matrix:
11
+ fast_finish: true
12
+ allow_failures:
13
+ - rvm: ruby-head
7
14
  # API tokens are always required, but not used in testing, since no requests are actually made.
8
15
  env:
9
16
  - GITHUB_API_TOKEN=phony GITLAB_API_TOKEN=phony
data/README.md CHANGED
@@ -24,8 +24,10 @@ The functionalities currently supported are:
24
24
  - list PRs
25
25
  - merge PR
26
26
  - Github:
27
+ - comment PR
27
28
  - create gist
28
29
  - create issue
30
+ - create milestone
29
31
  - create PR
30
32
 
31
33
  ## Samples
data/bin/geet CHANGED
@@ -46,8 +46,23 @@ class GeetLauncher
46
46
  Services::ListIssues.new(repository).execute(options)
47
47
  when LABEL_LIST_COMMAND
48
48
  Services::ListLabels.new(repository).execute
49
+ when MILESTONE_CLOSE_COMMAND
50
+ # Don't support user selection. This requires extra complexity, specifically, matching by number
51
+ # while displaying the titles (see AttributesSelectionManager).
52
+ #
53
+ options = {numbers: Shared::Selection::MANUAL_LIST_SELECTION_FLAG}
54
+
55
+ Services::CloseMilestones.new(repository).execute(options)
56
+ when MILESTONE_CREATE_COMMAND
57
+ title = options.delete(:title)
58
+
59
+ Services::CreateMilestone.new(repository).execute(title)
49
60
  when MILESTONE_LIST_COMMAND
50
61
  Services::ListMilestones.new(repository).execute
62
+ when PR_COMMENT_COMMAND
63
+ comment = options.delete(:comment)
64
+
65
+ Services::CommentPr.new(repository).execute(comment, options)
51
66
  when PR_CREATE_COMMAND
52
67
  summary = options[:summary] || edit_pr_summary(base: options[:base])
53
68
  title, description = split_summary(summary)
@@ -59,6 +74,8 @@ class GeetLauncher
59
74
  Services::ListPrs.new(repository).execute
60
75
  when PR_MERGE_COMMAND
61
76
  Services::MergePr.new(repository).execute(options)
77
+ when PR_OPEN_COMMAND
78
+ Services::OpenPr.new(repository).execute(options)
62
79
  else
63
80
  raise "Internal error - Unrecognized command #{command.inspect}"
64
81
  end
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+
3
+ set -o errexit
4
+
5
+ # We match ids with a length of 6 or more chars for simplicity - if we a generic `\d+` match is
6
+ # performed, there are false positives (e.g. PR numbers).
7
+ #
8
+ # Note that since this is an associative array, there is not guaranteed ordering.
9
+ #
10
+ declare -A PATTERNS=(
11
+ ['Saverio']='Donald'
12
+ ['saverio']='donald'
13
+ ['Miroddi']='Duck'
14
+ ['miroddi']='duck'
15
+ ['("\w*id"):\d{6,}']='$1:123456'
16
+ ['\b(\w*id)=\d{6,}']='$1=123456'
17
+ ['u\/\d{6,}\b']='u\/123456'
18
+ ['(gravatar.com\/avatar\/)[0-9a-f]{16}']='${1}0123456789abcdef'
19
+ )
20
+
21
+ TEST_SUITES_LOCATION="$(git rev-parse --show-toplevel)/spec"
22
+
23
+ if [[ "$1" == "-h" || "$1" == "--help" ]]; then
24
+ cat <<HELP
25
+ Usage: $(basename "$0") [<files...>|<dirs...>]
26
+
27
+ Anonymizes the private data in all the files in the project test suites subdirectory ('$TEST_SUITES_LOCATION').
28
+
29
+ Before applying the changes, all the project files are added to the staging area.
30
+
31
+ Private data patterns:
32
+
33
+ HELP
34
+
35
+ for key in ${!PATTERNS[@]}; do
36
+ echo "- $key => ${PATTERNS[$key]}"
37
+ done
38
+
39
+ cat <<'HELP'
40
+ HELP
41
+
42
+ exit 0
43
+ fi
44
+
45
+ if [[ ! -d "$TEST_SUITES_LOCATION" ]]; then
46
+ echo "The expected TEST_SUITES_LOCATION '$TEST_SUITES_LOCATION' doesn't exist!"
47
+ exit 1
48
+ fi
49
+
50
+ git add -A :/
51
+
52
+ for pattern_from in ${!PATTERNS[@]}; do
53
+ pattern_to="${PATTERNS[$pattern_from]}"
54
+
55
+ grep -lP "$pattern_from" -r "$TEST_SUITES_LOCATION" | xargs -I {} perl -i -pe "s/$pattern_from/$pattern_to/g" "{}"
56
+ done
57
+
58
+ git difftool --extcmd='vim -d -c "windo set wrap" $5'
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.required_ruby_version = '>= 2.3.0'
12
12
  s.authors = ['Saverio Miroddi']
13
- s.date = '2019-03-20'
13
+ s.date = '2020-11-16'
14
14
  s.email = ['saverio.pub2@gmail.com']
15
15
  s.homepage = 'https://github.com/saveriomiroddi/geet'
16
16
  s.summary = 'Commandline interface for performing SCM host operations, eg. create a PR on GitHub'
@@ -8,10 +8,14 @@ module Geet
8
8
  LABEL_CREATE_COMMAND = 'label.create'
9
9
  ISSUE_LIST_COMMAND = 'issue.list'
10
10
  LABEL_LIST_COMMAND = 'label.list'
11
+ MILESTONE_CLOSE_COMMAND = 'milestone.close'
12
+ MILESTONE_CREATE_COMMAND = 'milestone.create'
11
13
  MILESTONE_LIST_COMMAND = 'milestone.list'
14
+ PR_COMMENT_COMMAND = 'pr.comment'
12
15
  PR_CREATE_COMMAND = 'pr.create'
13
16
  PR_LIST_COMMAND = 'pr.list'
14
17
  PR_MERGE_COMMAND = 'pr.merge'
18
+ PR_OPEN_COMMAND = 'pr.open'
15
19
  end
16
20
  end
17
21
  end
@@ -45,10 +45,25 @@ module Geet
45
45
  ['-u', '--upstream', 'List on the upstream repository'],
46
46
  ].freeze
47
47
 
48
+ MILESTONE_CLOSE_OPTIONS = [
49
+ long_help: 'Close milestones.'
50
+ ]
51
+
52
+ MILESTONE_CREATE_OPTIONS = [
53
+ 'title',
54
+ long_help: 'Create a milestone.'
55
+ ]
56
+
48
57
  MILESTONE_LIST_OPTIONS = [
49
58
  ['-u', '--upstream', 'List on the upstream repository'],
50
59
  ].freeze
51
60
 
61
+ PR_COMMENT_OPTIONS = [
62
+ ['-n', '--no-open-pr', "Don't open the PR link in the browser after creation"],
63
+ 'comment',
64
+ long_help: 'Add a comment to the PR for the current branch.'
65
+ ]
66
+
52
67
  PR_CREATE_OPTIONS = [
53
68
  ['-A', '--automated-mode', "Automate the branch operations (see long help)"],
54
69
  ['-n', '--no-open-pr', "Don't open the PR link in the browser after creation"],
@@ -79,6 +94,10 @@ module Geet
79
94
  long_help: 'Merge the PR for the current branch'
80
95
  ]
81
96
 
97
+ PR_OPEN_OPTIONS = [
98
+ long_help: 'Open in the browser the PR for the current branch'
99
+ ]
100
+
82
101
  # Commands decoding table
83
102
 
84
103
  COMMANDS_DECODING_TABLE = {
@@ -94,12 +113,16 @@ module Geet
94
113
  'list' => LABEL_LIST_OPTIONS,
95
114
  },
96
115
  'milestone' => {
116
+ 'close' => MILESTONE_CLOSE_OPTIONS,
117
+ 'create' => MILESTONE_CREATE_OPTIONS,
97
118
  'list' => MILESTONE_LIST_OPTIONS,
98
119
  },
99
120
  'pr' => {
121
+ 'comment' => PR_COMMENT_OPTIONS,
100
122
  'create' => PR_CREATE_OPTIONS,
101
123
  'list' => PR_LIST_OPTIONS,
102
124
  'merge' => PR_MERGE_OPTIONS,
125
+ 'open' => PR_OPEN_OPTIONS,
103
126
  },
104
127
  }
105
128
 
@@ -58,6 +58,10 @@ module Geet
58
58
  attempt_provider_call(:Issue, :list, api_interface, assignee: assignee, milestone: milestone)
59
59
  end
60
60
 
61
+ def create_milestone(title)
62
+ attempt_provider_call(:Milestone, :create, title, api_interface)
63
+ end
64
+
61
65
  def milestone(number)
62
66
  attempt_provider_call(:Milestone, :find, number, api_interface)
63
67
  end
@@ -66,6 +70,10 @@ module Geet
66
70
  attempt_provider_call(:Milestone, :list, api_interface)
67
71
  end
68
72
 
73
+ def close_milestone(number)
74
+ attempt_provider_call(:Milestone, :close, number, api_interface)
75
+ end
76
+
69
77
  def create_pr(title, description, head, base: nil)
70
78
  confirm(LOCAL_ACTION_ON_UPSTREAM_REPOSITORY_MESSAGE) if local_action_on_upstream_repository? && @warnings
71
79
  confirm(ACTION_ON_PROTECTED_REPOSITORY_MESSAGE) if action_on_protected_repository? && @warnings
@@ -73,8 +81,8 @@ module Geet
73
81
  attempt_provider_call(:PR, :create, title, description, head, api_interface, base: base)
74
82
  end
75
83
 
76
- def prs(head: nil, milestone: nil)
77
- attempt_provider_call(:PR, :list, api_interface, head: head, milestone: milestone)
84
+ def prs(owner: nil, head: nil, milestone: nil)
85
+ attempt_provider_call(:PR, :list, api_interface, owner: owner, head: head, milestone: milestone)
78
86
  end
79
87
 
80
88
  # REMOTE FUNCTIONALITIES (ACCOUNT)
@@ -61,6 +61,15 @@ module Geet
61
61
  @api_interface.send_request(api_path, data: request_data)
62
62
  end
63
63
 
64
+ # See https://developer.github.com/v3/issues/comments/#create-a-comment
65
+ #
66
+ def comment(comment)
67
+ api_path = "issues/#{@number}/comments"
68
+ request_data = { body: comment }
69
+
70
+ @api_interface.send_request(api_path, data: request_data)
71
+ end
72
+
64
73
  # See https://developer.github.com/v3/issues/#edit-an-issue
65
74
  #
66
75
  def edit(milestone:)
@@ -7,6 +7,8 @@ module Geet
7
7
  class Milestone
8
8
  attr_reader :number, :title, :due_on
9
9
 
10
+ STATE_CLOSED = 'closed'
11
+
10
12
  class << self
11
13
  private
12
14
 
@@ -21,6 +23,20 @@ module Geet
21
23
  @api_interface = api_interface
22
24
  end
23
25
 
26
+ # See https://developer.github.com/v3/issues/milestones/#create-a-milestone
27
+ def self.create(title, api_interface)
28
+ api_path = 'milestones'
29
+ request_data = { title: title }
30
+
31
+ response = api_interface.send_request(api_path, data: request_data)
32
+
33
+ number = response.fetch('number')
34
+ title = response.fetch('title')
35
+ due_on = nil
36
+
37
+ new(number, title, due_on, api_interface)
38
+ end
39
+
24
40
  # See https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
25
41
  #
26
42
  def self.find(number, api_interface)
@@ -50,6 +66,17 @@ module Geet
50
66
  new(number, title, due_on, api_interface)
51
67
  end
52
68
  end
69
+
70
+ # See https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#update-a-milestone
71
+ #
72
+ # This is a convenience method; the underlying operation is a generic update.
73
+ #
74
+ def self.close(number, api_interface)
75
+ api_path = "milestones/#{number}"
76
+ request_data = { state: STATE_CLOSED }
77
+
78
+ api_interface.send_request(api_path, data: request_data)
79
+ end
53
80
  end
54
81
  end
55
82
  end
@@ -28,12 +28,12 @@ module Geet
28
28
 
29
29
  # See https://developer.github.com/v3/pulls/#list-pull-requests
30
30
  #
31
- def self.list(api_interface, milestone: nil, assignee: nil, head: nil)
31
+ def self.list(api_interface, milestone: nil, assignee: nil, owner: nil, head: nil)
32
32
  check_list_params!(milestone, assignee, head)
33
33
 
34
34
  if head
35
35
  api_path = 'pulls'
36
- request_params = { head: head }
36
+ request_params = { head: "#{owner}:#{head}" }
37
37
 
38
38
  response = api_interface.send_request(api_path, params: request_params, multipage: true)
39
39
 
@@ -12,11 +12,16 @@ module Geet
12
12
  @link = link
13
13
  end
14
14
 
15
+ # owner: required only for API compatibility. it's not required; if passed, it's only checked
16
+ # against the API path to make sure it's correct.
17
+ #
15
18
  # See https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests
16
19
  #
17
- def self.list(api_interface, milestone: nil, assignee: nil, head: nil)
20
+ def self.list(api_interface, milestone: nil, assignee: nil, owner: nil, head: nil)
18
21
  api_path = "projects/#{api_interface.path_with_namespace(encoded: true)}/merge_requests"
19
22
 
23
+ check_list_owner!(api_interface, owner) if owner
24
+
20
25
  request_params = {}
21
26
  request_params[:assignee_id] = assignee.id if assignee
22
27
  request_params[:milestone] = milestone.title if milestone
@@ -40,6 +45,16 @@ module Geet
40
45
 
41
46
  @api_interface.send_request(api_path, http_method: :put)
42
47
  end
48
+
49
+ class << self
50
+ private
51
+
52
+ def check_list_owner!(api_interface, owner)
53
+ if !api_interface.path_with_namespace.start_with?("#{owner}/")
54
+ raise "Mismatch owner/API path!: #{owner}<>#{api_interface.path_with_namespace}"
55
+ end
56
+ end
57
+ end
43
58
  end # PR
44
59
  end # Gitlab
45
60
  end # Geet
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'open3'
5
+ require 'shellwords'
6
+
7
+ module Geet
8
+ module Helpers
9
+ # Helper for services common workflow, for example, find the merge head.
10
+ #
11
+ module ServicesWorkflowHelper
12
+ # Requires: @git_client
13
+ #
14
+ def find_merge_head
15
+ [@git_client.owner, @git_client.current_branch]
16
+ end
17
+
18
+ # Expect to find only one.
19
+ #
20
+ # Requires: @out, @repository.
21
+ #
22
+ def checked_find_branch_pr(owner, head)
23
+ @out.puts "Finding PR with head (#{owner}:#{head})..."
24
+
25
+ prs = @repository.prs(owner: owner, head: head)
26
+
27
+ raise "Expected to find only one PR for the current branch; found: #{prs.size}" if prs.size != 1
28
+
29
+ prs[0]
30
+ end
31
+ end
32
+ end
33
+ 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