geet 0.3.13 → 0.3.18

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -0
  4. data/README.md +5 -8
  5. data/bin/geet +26 -7
  6. data/extra/anonymize_vcr_data +58 -0
  7. data/geet.gemspec +1 -1
  8. data/lib/geet/commandline/commands.rb +5 -0
  9. data/lib/geet/commandline/configuration.rb +31 -0
  10. data/lib/geet/git/repository.rb +19 -1
  11. data/lib/geet/github/abstract_issue.rb +9 -0
  12. data/lib/geet/github/api_interface.rb +2 -0
  13. data/lib/geet/github/branch.rb +1 -1
  14. data/lib/geet/github/issue.rb +1 -1
  15. data/lib/geet/github/label.rb +2 -2
  16. data/lib/geet/github/milestone.rb +29 -2
  17. data/lib/geet/github/remote_repository.rb +37 -0
  18. data/lib/geet/github/user.rb +2 -2
  19. data/lib/geet/gitlab/api_interface.rb +2 -0
  20. data/lib/geet/gitlab/label.rb +2 -2
  21. data/lib/geet/gitlab/milestone.rb +1 -1
  22. data/lib/geet/gitlab/user.rb +1 -1
  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 +31 -0
  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 +5 -3
  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/branches.rb +9 -0
  33. data/lib/geet/shared/selection.rb +3 -0
  34. data/lib/geet/utils/attributes_selection_manager.rb +12 -3
  35. data/lib/geet/utils/git_client.rb +101 -28
  36. data/lib/geet/version.rb +1 -1
  37. data/spec/integration/comment_pr_spec.rb +44 -0
  38. data/spec/integration/create_issue_spec.rb +4 -4
  39. data/spec/integration/create_label_spec.rb +5 -5
  40. data/spec/integration/create_milestone_spec.rb +34 -0
  41. data/spec/integration/create_pr_spec.rb +8 -8
  42. data/spec/integration/list_issues_spec.rb +6 -6
  43. data/spec/integration/list_labels_spec.rb +4 -4
  44. data/spec/integration/list_milestones_spec.rb +4 -4
  45. data/spec/integration/list_prs_spec.rb +3 -3
  46. data/spec/integration/merge_pr_spec.rb +29 -4
  47. data/spec/integration/open_pr_spec.rb +1 -1
  48. data/spec/integration/open_repo_spec.rb +46 -0
  49. data/spec/vcr_cassettes/create_gist_private.yml +1 -1
  50. data/spec/vcr_cassettes/create_gist_public.yml +1 -1
  51. data/spec/vcr_cassettes/create_issue.yml +13 -13
  52. data/spec/vcr_cassettes/create_issue_upstream.yml +2 -2
  53. data/spec/vcr_cassettes/github_com/comment_pr.yml +161 -0
  54. data/spec/vcr_cassettes/github_com/create_label.yml +1 -1
  55. data/spec/vcr_cassettes/github_com/create_label_upstream.yml +1 -1
  56. data/spec/vcr_cassettes/github_com/create_label_with_random_color.yml +1 -1
  57. data/spec/vcr_cassettes/github_com/create_milestone.yml +82 -0
  58. data/spec/vcr_cassettes/github_com/create_pr.yml +16 -16
  59. data/spec/vcr_cassettes/github_com/create_pr_in_auto_mode_create_upstream.yml +7 -7
  60. data/spec/vcr_cassettes/github_com/create_pr_in_auto_mode_with_push.yml +7 -7
  61. data/spec/vcr_cassettes/github_com/create_pr_upstream.yml +8 -8
  62. data/spec/vcr_cassettes/github_com/create_pr_upstream_without_write_permissions.yml +3 -3
  63. data/spec/vcr_cassettes/github_com/list_issues.yml +5 -5
  64. data/spec/vcr_cassettes/github_com/list_issues_upstream.yml +6 -6
  65. data/spec/vcr_cassettes/github_com/list_issues_with_assignee.yml +4 -4
  66. data/spec/vcr_cassettes/github_com/list_labels.yml +1 -1
  67. data/spec/vcr_cassettes/github_com/list_labels_upstream.yml +1 -1
  68. data/spec/vcr_cassettes/github_com/list_milestones.yml +50 -50
  69. data/spec/vcr_cassettes/github_com/merge_pr.yml +2 -2
  70. data/spec/vcr_cassettes/github_com/merge_pr_with_branch_deletion.yml +2 -2
  71. data/spec/vcr_cassettes/github_com/open_pr.yml +2 -2
  72. data/spec/vcr_cassettes/gitlab_com/create_label.yml +1 -1
  73. data/spec/vcr_cassettes/gitlab_com/list_issues.yml +4 -4
  74. data/spec/vcr_cassettes/gitlab_com/list_issues_with_assignee.yml +8 -8
  75. data/spec/vcr_cassettes/gitlab_com/list_labels.yml +1 -1
  76. data/spec/vcr_cassettes/gitlab_com/list_milestones.yml +9 -9
  77. data/spec/vcr_cassettes/gitlab_com/merge_pr.yml +5 -5
  78. data/spec/vcr_cassettes/list_milestones_upstream.yml +21 -21
  79. data/spec/vcr_cassettes/list_prs.yml +10 -10
  80. data/spec/vcr_cassettes/list_prs_upstream.yml +10 -10
  81. metadata +16 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92ec0750a0b9b19fcac605a9dbf4b5a746ac3149eb3685f85ccadae8a7500a94
4
- data.tar.gz: 5b56daef325b4a667d2b5aef5b28f5f5f1cdbe2bea6195d6ead8c58173a4580f
3
+ metadata.gz: 8b37d7ac48684dcd431b13c0e00cb9f999764ae714dec3c8d046102469f7f62f
4
+ data.tar.gz: 43a045941aff66c212314ae0de2c5c439434c75e89240ce2c59750f5759dcc5a
5
5
  SHA512:
6
- metadata.gz: de248a32d080a7804b3c2abcb22ad00b6269f2e2d16c30efd5a8b522414e3392e92c563b8798fb59b81d5de6cefeb1b53772ea5cddf13e61d0f94e5d9e25ff27
7
- data.tar.gz: 2c6d236e604a29525d73f9a3f356808575b0ddec25bc197436be5badf12063feb6ee2938d8e47e48778814ab912bcb348101c41bd41771ca104044a48ce1fe1a
6
+ metadata.gz: fe4aa67774fef6b5943ae4f6eb2950ec4d4c2b7f004aa963438e00904d80d01c3a73db8671567c718b6fc9b235a2e9d2002744ec891bce3ff4af1144ffa01d28
7
+ data.tar.gz: 14c3efa61fa206f3f3f70432c3683d394f9e4e4e09f6cefad757d96d11b88110559f393d22d296acef963d72d7117c668125724da79d8f1f6cbda9e35dfc367a
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  /.ruby-version
2
2
  /.ruby-gemset
3
3
  /Gemfile.lock
4
+ /test_repos
data/.travis.yml CHANGED
@@ -1,9 +1,17 @@
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
+ - 3.0
10
+ - ruby-head
11
+ matrix:
12
+ fast_finish: true
13
+ allow_failures:
14
+ - rvm: ruby-head
7
15
  # API tokens are always required, but not used in testing, since no requests are actually made.
8
16
  env:
9
17
  - GITHUB_API_TOKEN=phony GITLAB_API_TOKEN=phony
data/README.md CHANGED
@@ -18,15 +18,12 @@ The functionalities currently supported are:
18
18
 
19
19
  - Github/Gitlab:
20
20
  - create label
21
- - list issues
22
- - list labels
23
- - list milestones
24
- - list PRs
25
- - merge PR
21
+ - list issues, labels, milestones, MR/PRs
22
+ - merge MR/PR
23
+ - open repository
26
24
  - Github:
27
- - create gist
28
- - create issue
29
- - create PR
25
+ - comment PR
26
+ - create gist, issue, milestone, PR
30
27
 
31
28
  ## Samples
32
29
 
data/bin/geet CHANGED
@@ -28,39 +28,58 @@ class GeetLauncher
28
28
  filename = options.delete(:filename)
29
29
  options[:publik] = options.delete(:public) if options.key?(:public)
30
30
 
31
- Services::CreateGist.new.execute(filename, options)
31
+ Services::CreateGist.new.execute(filename, **options)
32
32
  when ISSUE_CREATE_COMMAND
33
33
  summary = options[:summary] || Commandline::Editor.new.edit_content(help: SUMMARY_TEMPLATE)
34
34
  title, description = split_summary(summary)
35
35
 
36
36
  options = default_to_manual_selection(options, :labels, :milestone, :assignees)
37
37
 
38
- Services::CreateIssue.new(repository).execute(title, description, options)
38
+ Services::CreateIssue.new(repository).execute(title, description, **options)
39
39
  when LABEL_CREATE_COMMAND
40
40
  name = options.delete(:name)
41
41
 
42
- Services::CreateLabel.new(repository).execute(name, options)
42
+ Services::CreateLabel.new(repository).execute(name, **options)
43
43
  when ISSUE_LIST_COMMAND
44
44
  options = default_to_manual_selection(options, :assignee)
45
45
 
46
- Services::ListIssues.new(repository).execute(options)
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)
54
69
 
55
70
  options = default_to_manual_selection(options, :labels, :milestone, :reviewers)
56
71
 
57
- Services::CreatePr.new(repository).execute(title, description, options)
72
+ Services::CreatePr.new(repository).execute(title, description, **options)
58
73
  when PR_LIST_COMMAND
59
74
  Services::ListPrs.new(repository).execute
60
75
  when PR_MERGE_COMMAND
61
- Services::MergePr.new(repository).execute(options)
76
+ Services::MergePr.new(repository).execute(**options)
62
77
  when PR_OPEN_COMMAND
63
- Services::OpenPr.new(repository).execute(options)
78
+ Services::OpenPr.new(repository).execute(**options)
79
+ when REPO_ADD_UPSTREAM_COMMAND
80
+ Services::AddUpstreamRepo.new(repository).execute
81
+ when REPO_OPEN_COMMAND
82
+ Services::OpenRepo.new(repository).execute(**options)
64
83
  else
65
84
  raise "Internal error - Unrecognized command #{command.inspect}"
66
85
  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'
data/geet.gemspec CHANGED
@@ -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-08-28'
13
+ s.date = '2021-05-28'
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,11 +8,16 @@ 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'
15
18
  PR_OPEN_COMMAND = 'pr.open'
19
+ REPO_ADD_UPSTREAM_COMMAND = 'repo.add_upstream'
20
+ REPO_OPEN_COMMAND = 'repo.open'
16
21
  end
17
22
  end
18
23
  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"],
@@ -83,6 +98,15 @@ module Geet
83
98
  long_help: 'Open in the browser the PR for the current branch'
84
99
  ]
85
100
 
101
+ REPO_ADD_UPSTREAM_OPTIONS = [
102
+ long_help: 'Add the upstream repository to the current repository (configuration).'
103
+ ]
104
+
105
+ REPO_OPEN_OPTIONS = [
106
+ ['-u', '--upstream', 'Open the upstream repository'],
107
+ long_help: 'Open the current repository in the browser'
108
+ ]
109
+
86
110
  # Commands decoding table
87
111
 
88
112
  COMMANDS_DECODING_TABLE = {
@@ -98,14 +122,21 @@ module Geet
98
122
  'list' => LABEL_LIST_OPTIONS,
99
123
  },
100
124
  'milestone' => {
125
+ 'close' => MILESTONE_CLOSE_OPTIONS,
126
+ 'create' => MILESTONE_CREATE_OPTIONS,
101
127
  'list' => MILESTONE_LIST_OPTIONS,
102
128
  },
103
129
  'pr' => {
130
+ 'comment' => PR_COMMENT_OPTIONS,
104
131
  'create' => PR_CREATE_OPTIONS,
105
132
  'list' => PR_LIST_OPTIONS,
106
133
  'merge' => PR_MERGE_OPTIONS,
107
134
  'open' => PR_OPEN_OPTIONS,
108
135
  },
136
+ 'repo' => {
137
+ 'add_upstream' => REPO_ADD_UPSTREAM_OPTIONS,
138
+ 'open' => REPO_OPEN_OPTIONS,
139
+ },
109
140
  }
110
141
 
111
142
  # Public interface
@@ -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
@@ -77,6 +85,12 @@ module Geet
77
85
  attempt_provider_call(:PR, :list, api_interface, owner: owner, head: head, milestone: milestone)
78
86
  end
79
87
 
88
+ # Returns the RemoteRepository instance.
89
+ #
90
+ def remote
91
+ attempt_provider_call(:RemoteRepository, :find, api_interface)
92
+ end
93
+
80
94
  # REMOTE FUNCTIONALITIES (ACCOUNT)
81
95
 
82
96
  def authenticated_user
@@ -115,7 +129,11 @@ module Geet
115
129
  raise "The functionality invoked (#{class_name}.#{meth}) is not currently supported!"
116
130
  end
117
131
 
118
- klass.send(meth, *args)
132
+ # Can't use ruby2_keywords, because the method definitions use named keyword arguments.
133
+ #
134
+ kwargs = args.last.is_a?(Hash) ? args.pop : {}
135
+
136
+ klass.send(meth, *args, **kwargs)
119
137
  else
120
138
  raise "The class referenced (#{full_class_name}) is not currently supported!"
121
139
  end
@@ -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:)
@@ -11,6 +11,8 @@ module Geet
11
11
  API_AUTH_USER = '' # We don't need the login, as the API key uniquely identifies the user
12
12
  API_BASE_URL = 'https://api.github.com'
13
13
 
14
+ attr_reader :repository_path
15
+
14
16
  # repo_path: optional for operations that don't require a repository, eg. gist creation.
15
17
  # upstream: boolean; makes sense only when :repo_path is set.
16
18
  #
@@ -5,7 +5,7 @@ module Geet
5
5
  class Branch
6
6
  # See https://developer.github.com/v3/git/refs/#delete-a-reference
7
7
  #
8
- def self.delete(name, api_interface)
8
+ def self.delete(name, api_interface, **)
9
9
  api_path = "git/refs/heads/#{name}"
10
10
 
11
11
  api_interface.send_request(api_path, http_method: :delete)
@@ -6,7 +6,7 @@ module Geet
6
6
  autoload :AbstractIssue, File.expand_path('abstract_issue', __dir__)
7
7
 
8
8
  class Issue < Geet::Github::AbstractIssue
9
- def self.create(title, description, api_interface)
9
+ def self.create(title, description, api_interface, **)
10
10
  api_path = 'issues'
11
11
  request_data = { title: title, body: description, base: 'master' }
12
12
 
@@ -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 = '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://developer.github.com/v3/issues/labels/#create-a-label
27
- def self.create(name, color, api_interface)
27
+ def self.create(name, color, api_interface, **)
28
28
  api_path = 'labels'
29
29
  request_data = { name: name, color: color }
30
30
 
@@ -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,9 +23,23 @@ 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
- def self.find(number, api_interface)
42
+ def self.find(number, api_interface, **)
27
43
  api_path = "milestones/#{number}"
28
44
 
29
45
  response = api_interface.send_request(api_path)
@@ -37,7 +53,7 @@ module Geet
37
53
 
38
54
  # See https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
39
55
  #
40
- def self.list(api_interface)
56
+ def self.list(api_interface, **)
41
57
  api_path = 'milestones'
42
58
 
43
59
  response = api_interface.send_request(api_path, multipage: true)
@@ -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