octokitted 0.0.5 → 0.0.7
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/README.md +8 -0
- data/lib/octokitted/common/issue.rb +77 -0
- data/lib/octokitted/git_plugin.rb +12 -0
- data/lib/octokitted.rb +82 -7
- data/lib/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 930bcddc8ee154391c8deb09e7a2c0e483e49d6cb3091adeb70239d0388d3e5d
|
4
|
+
data.tar.gz: b64e1ba7b33e4c2eeb73975825bf5fa7cd2eaf37f63383b01427e4adddbd7e4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2738d3e5a2bb9bb665a8ed84f76ecbe4594b753e46b27a49d504587fbceae86f4a5905bd704368373bf4868551fe5a014716555e274f22fcad95b8d29eb107c
|
7
|
+
data.tar.gz: 486cf3f74dc41658968876d79d11383c1d8bfe7577f822c4fe17660555e96f4fa5480f180acd86bd140cbd8af7c972ad4068bb74436a8d2127238ba56141afb9
|
data/README.md
CHANGED
@@ -3,3 +3,11 @@
|
|
3
3
|
[](https://github.com/GrantBirki/octokitted/actions/workflows/test.yml) [](https://github.com/GrantBirki/octokitted/actions/workflows/lint.yml) [](https://github.com/GrantBirki/octokitted/actions/workflows/codeql-analysis.yml)
|
4
4
|
|
5
5
|
A self-hydrating version of Octokit for usage in CI systems - like GitHub Actions!
|
6
|
+
|
7
|
+
> **kit** or **kitted** (_verb_)
|
8
|
+
>
|
9
|
+
> Defintion: provide someone or something with the appropriate clothing or equipment.
|
10
|
+
>
|
11
|
+
> "we were all kitted out in life jackets", "our octokit client was kitted out for CI usage"
|
12
|
+
|
13
|
+

|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "contracts"
|
4
|
+
|
5
|
+
class Issue
|
6
|
+
include Contracts::Core
|
7
|
+
include Contracts::Builtin
|
8
|
+
|
9
|
+
# A helper class for common operations on GitHub Issues
|
10
|
+
def initialize(octokitted)
|
11
|
+
@octokit = octokitted.octokit
|
12
|
+
@log = octokitted.log
|
13
|
+
@octokitted = octokitted
|
14
|
+
end
|
15
|
+
|
16
|
+
# Adds a set of labels to an issue or pull request
|
17
|
+
# :param labels: The labels to add to the issue (Array of strings)
|
18
|
+
# :param issue_number: The issue number to add labels to
|
19
|
+
Contract KeywordArgs[labels: ArrayOf[String], issue_number: Maybe[Numeric]] => Any
|
20
|
+
def add_labels(labels:, issue_number: nil)
|
21
|
+
issue_number = construct_issue_numer(issue_number)
|
22
|
+
@log.debug("adding labels: #{labels} to issue: #{issue_number}")
|
23
|
+
|
24
|
+
@octokit.add_labels_to_an_issue(@octokitted.org_and_repo, issue_number, labels)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Removes a set of labels from an issue or pull request
|
28
|
+
# If the label does not exist, the exception is caught and logged
|
29
|
+
# :param labels: The labels to remove from the issue (Array of strings)
|
30
|
+
# :param issue_number: The issue number to remove labels from
|
31
|
+
Contract KeywordArgs[labels: ArrayOf[String], issue_number: Maybe[Numeric]] => Any
|
32
|
+
def remove_labels(labels:, issue_number: nil)
|
33
|
+
issue_number = construct_issue_numer(issue_number)
|
34
|
+
@log.debug("removing labels: #{labels} from issue: #{issue_number}")
|
35
|
+
|
36
|
+
labels.each do |label|
|
37
|
+
@octokit.remove_label(@octokitted.org_and_repo, issue_number, label)
|
38
|
+
rescue Octokit::NotFound
|
39
|
+
@log.warn("label: #{label} not found on issue: #{issue_number}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds a comment to an issue or pull request
|
44
|
+
# :param comment: The comment to add to the issue (String)
|
45
|
+
# :param issue_number: The issue number to add the comment to
|
46
|
+
Contract KeywordArgs[comment: String, issue_number: Maybe[Numeric]] => Any
|
47
|
+
def add_comment(comment:, issue_number: nil)
|
48
|
+
issue_number = construct_issue_numer(issue_number)
|
49
|
+
@log.debug("adding comment: #{comment} to issue: #{issue_number}")
|
50
|
+
|
51
|
+
@octokit.add_comment(@octokitted.org_and_repo, issue_number, comment)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Closes an issue
|
55
|
+
# :param issue_number: The issue number to close
|
56
|
+
Contract KeywordArgs[issue_number: Maybe[Numeric], options: Maybe[Hash]] => Any
|
57
|
+
def close(issue_number: nil, options: {})
|
58
|
+
issue_number = construct_issue_numer(issue_number)
|
59
|
+
@log.debug("closing issue: #{issue_number}")
|
60
|
+
|
61
|
+
@octokit.close_issue(@octokitted.org_and_repo, issue_number, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Helper method to construct the issue number from the auto-hydrated issue_number if it exists
|
67
|
+
# :param issue_number: The issue number to use if not nil
|
68
|
+
# :return: The issue number to use
|
69
|
+
# Note: If the issue_number is nil, we trye use the auto-hydrated issue_number...
|
70
|
+
# ... if the issue_number is not nil, we use that
|
71
|
+
Contract Maybe[Numeric] => Numeric
|
72
|
+
def construct_issue_numer(issue_number)
|
73
|
+
return @octokitted.issue_number if issue_number.nil?
|
74
|
+
|
75
|
+
return issue_number
|
76
|
+
end
|
77
|
+
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "git"
|
4
|
+
require "contracts"
|
4
5
|
|
5
6
|
class GitPlugin
|
6
7
|
attr_reader :login
|
7
8
|
|
9
|
+
include Contracts::Core
|
10
|
+
include Contracts::Builtin
|
11
|
+
|
8
12
|
# Initialize the class
|
13
|
+
Contract KeywordArgs[logger: Any, login: Maybe[String], token: Maybe[String]] => Any
|
9
14
|
def initialize(logger:, login:, token:)
|
10
15
|
@log = logger
|
11
16
|
@login = login
|
@@ -14,25 +19,32 @@ class GitPlugin
|
|
14
19
|
|
15
20
|
# Removes / cleans up all repos that this class has cloned
|
16
21
|
# :param cloned_repos: An array of paths to cloned repos to remove
|
22
|
+
# :return: true to indicate success
|
23
|
+
Contract ArrayOf[String] => true
|
17
24
|
def remove_all_clones!(cloned_repos)
|
18
25
|
@log.debug("removing all cloned repos")
|
19
26
|
cloned_repos.each do |path|
|
20
27
|
@log.debug("removing cloned repo: #{path}")
|
21
28
|
FileUtils.rm_r(path)
|
22
29
|
end
|
30
|
+
true
|
23
31
|
end
|
24
32
|
|
25
33
|
# Removes a single cloned repo
|
26
34
|
# :param path: The path to the cloned repo to remove (String)
|
35
|
+
# :return: true to indicate success
|
36
|
+
Contract String => true
|
27
37
|
def remove_clone!(path)
|
28
38
|
@log.debug("removing cloned repo: #{path}")
|
29
39
|
FileUtils.rm_r(path)
|
40
|
+
return true
|
30
41
|
end
|
31
42
|
|
32
43
|
# Clone a repository
|
33
44
|
# :param path: The relative path to clone the repo to - (default: ".")
|
34
45
|
# :param options: The options to pass to the Git.clone method (default: {} - https://rubydoc.info/gems/git/Git#clone-class_method)
|
35
46
|
# :return: Hash of the Git Object, and the path to the cloned repo
|
47
|
+
Contract KeywordArgs[org: String, repo: String, path: Maybe[String], options: Maybe[Hash]] => Hash
|
36
48
|
def clone(org:, repo:, path: ".", options: {})
|
37
49
|
@log.debug("cloning #{org}/#{repo}")
|
38
50
|
git_object = Git.clone("https://#{@token}@github.com/#{org}/#{repo}.git", repo, path:, log: @log, **options)
|
data/lib/octokitted.rb
CHANGED
@@ -2,33 +2,59 @@
|
|
2
2
|
|
3
3
|
require "octokit"
|
4
4
|
require "logger"
|
5
|
+
require "contracts"
|
5
6
|
|
6
7
|
require_relative "octokitted/git_plugin"
|
8
|
+
require_relative "octokitted/common/issue"
|
7
9
|
|
8
10
|
class Octokitted
|
9
11
|
# A Octokitted class to interact with the GitHub API
|
10
|
-
attr_reader :login,
|
12
|
+
attr_reader :login,
|
13
|
+
:org,
|
14
|
+
:repo,
|
15
|
+
:org_and_repo,
|
16
|
+
:octokit,
|
17
|
+
:cloned_repos,
|
18
|
+
:log,
|
19
|
+
:github_event,
|
20
|
+
:sha,
|
21
|
+
:issue_number,
|
22
|
+
:issue
|
23
|
+
|
24
|
+
include Contracts::Core
|
25
|
+
include Contracts::Builtin
|
11
26
|
|
12
27
|
# Initialize the class
|
28
|
+
# :param event_path: The path to the GitHub event data (defaults to the GITHUB_EVENT_PATH env var)
|
13
29
|
# :param login: The login to use for GitHubAPI interactions (defaults to the owner of the token)
|
14
30
|
# :param org: The org to use with the Octokitted class
|
15
31
|
# :param repo: The repo to interact with with the Octokitted class
|
32
|
+
# :param issue_number: The issue/pull_request number to interact with with the Octokitted class
|
16
33
|
# :param token: The token to use to authenticate with the GitHub API
|
17
34
|
# :param logger: The logger to use for logging
|
18
|
-
|
35
|
+
#
|
36
|
+
# Note: If you do not provide an org, repo, token, or issue_number, Octokitted will attempt to self-hydrate...
|
37
|
+
# ... these values from the environment and the GitHub event data when you call `.new` on the class
|
38
|
+
def initialize(event_path: nil, login: nil, org: nil, repo: nil, issue_number: nil, token: nil, logger: nil)
|
39
|
+
@log = logger || setup_logger
|
19
40
|
@cloned_repos = []
|
41
|
+
@event_path = event_path || ENV.fetch("GITHUB_EVENT_PATH", nil)
|
42
|
+
@sha = ENV.fetch("GITHUB_SHA", nil)
|
20
43
|
org_and_repo_hash = fetch_org_and_repo
|
21
44
|
@login = login
|
22
45
|
@org = org || org_and_repo_hash[:org]
|
23
46
|
@repo = repo || org_and_repo_hash[:repo]
|
24
47
|
@token = token || fetch_token
|
25
48
|
@octokit = setup_octokit_client
|
26
|
-
@log = logger || setup_logger
|
27
49
|
@org_and_repo = org_and_repo_hash[:org_and_repo]
|
50
|
+
@github_event = fetch_github_event(@event_path)
|
51
|
+
@issue_number = issue_number || fetch_issue_number(@github_event)
|
28
52
|
@login = @octokit.login if @login.nil? # reset the login to the owner of the token if not provided
|
29
53
|
|
30
54
|
# setup the git plugin
|
31
55
|
@git = GitPlugin.new(logger: @log, login: @login, token: @token)
|
56
|
+
# setup the common Issue plugin
|
57
|
+
@issue = Issue.new(self)
|
32
58
|
|
33
59
|
@log.debug("Octokitted initialized")
|
34
60
|
@log.debug("login: #{@octokit.login}")
|
@@ -38,8 +64,9 @@ class Octokitted
|
|
38
64
|
|
39
65
|
# Setter method for the repo instance variable
|
40
66
|
# :param repo: The repo to set
|
41
|
-
# :return:
|
67
|
+
# :return: it does not return as it is a setter method
|
42
68
|
# Example: gh.repo = "test"
|
69
|
+
Contract String => Any
|
43
70
|
def repo=(repo)
|
44
71
|
@repo = repo
|
45
72
|
@org_and_repo = "#{@org}/#{@repo}"
|
@@ -48,8 +75,9 @@ class Octokitted
|
|
48
75
|
|
49
76
|
# Setter method for the org instance variable
|
50
77
|
# :param org: The org to set
|
51
|
-
# :return:
|
78
|
+
# :return: it does not return as it is a setter method
|
52
79
|
# Example: gh.org = "test"
|
80
|
+
Contract String => Any
|
53
81
|
def org=(org)
|
54
82
|
@org = org
|
55
83
|
@org_and_repo = "#{@org}/#{@repo}"
|
@@ -60,12 +88,17 @@ class Octokitted
|
|
60
88
|
# :param path: The relative path to clone the repo to - (default: ".")
|
61
89
|
# :param options: The options to pass (default: {} - https://rubydoc.info/gems/git/Git#clone-class_method)
|
62
90
|
# :return: The Git object to operate with
|
91
|
+
Contract Maybe[String], Maybe[Hash] => Git::Base
|
63
92
|
def clone(path: ".", options: {})
|
64
93
|
result = @git.clone(org: @org, repo: @repo, path:, options:)
|
65
94
|
@cloned_repos << result[:path]
|
66
95
|
return result[:git_object]
|
67
96
|
end
|
68
97
|
|
98
|
+
# Remove a cloned repository
|
99
|
+
# :param path: The relative path to the cloned repo to remove (String)
|
100
|
+
# :return: true to indicate success
|
101
|
+
Contract String => true
|
69
102
|
def remove_clone!(path)
|
70
103
|
valid = false
|
71
104
|
|
@@ -82,11 +115,16 @@ class Octokitted
|
|
82
115
|
|
83
116
|
@git.remove_clone!(path)
|
84
117
|
@cloned_repos.delete(path)
|
118
|
+
true
|
85
119
|
end
|
86
120
|
|
121
|
+
# Remove all cloned repositories that have been cloned with this instance of Octokitted
|
122
|
+
# :return: true to indicate success
|
123
|
+
Contract None => true
|
87
124
|
def remove_all_clones!
|
88
125
|
@git.remove_all_clones!(@cloned_repos)
|
89
126
|
@cloned_repos = []
|
127
|
+
true
|
90
128
|
end
|
91
129
|
|
92
130
|
private
|
@@ -99,6 +137,7 @@ class Octokitted
|
|
99
137
|
|
100
138
|
# Fetch the org and repo from the environment
|
101
139
|
# :return: A hash containing the org and repo, and the org and repo separately
|
140
|
+
Contract None => HashOf[Symbol, String]
|
102
141
|
def fetch_org_and_repo
|
103
142
|
org_and_repo = ENV.fetch("GITHUB_REPOSITORY", nil)
|
104
143
|
org = nil
|
@@ -110,8 +149,41 @@ class Octokitted
|
|
110
149
|
return { org_and_repo:, org:, repo: }
|
111
150
|
end
|
112
151
|
|
152
|
+
# A helper method that attempts to self-hydrate context from the GitHub event data
|
153
|
+
# In Actions, the GITHUB_EVENT_PATH env var is set to a file containing the GitHub json event data
|
154
|
+
# If it exists, we try to load it into this class
|
155
|
+
# :param event_path: The path to the GitHub event data (defaults to the GITHUB_EVENT_PATH env var)
|
156
|
+
# :return: A Hash of the GitHub event data or nil if not found
|
157
|
+
Contract Maybe[String] => Maybe[Hash]
|
158
|
+
def fetch_github_event(event_path)
|
159
|
+
unless event_path
|
160
|
+
@log.warn("GITHUB_EVENT_PATH env var not found")
|
161
|
+
return nil
|
162
|
+
end
|
163
|
+
|
164
|
+
@log.info("GitHub Event data auto-hydrated")
|
165
|
+
return JSON.parse(File.read(event_path), symbolize_names: true)
|
166
|
+
end
|
167
|
+
|
168
|
+
# A helper method that attempts to self-hydrate the issue_number from the GitHub event data
|
169
|
+
# :param github_event: The GitHub event data (Hash)
|
170
|
+
# :return: The issue_number or nil if not found
|
171
|
+
Contract Maybe[Hash] => Maybe[Numeric]
|
172
|
+
def fetch_issue_number(github_event)
|
173
|
+
if github_event.nil?
|
174
|
+
@log.debug("GitHub event data not found - issue_number not auto-hydrated")
|
175
|
+
return nil
|
176
|
+
end
|
177
|
+
|
178
|
+
issue_number = (github_event[:issue] || github_event[:pull_request] || github_event)[:number]
|
179
|
+
|
180
|
+
@log.info("issue_number auto-hydrated - issue_number: #{issue_number}")
|
181
|
+
return issue_number
|
182
|
+
end
|
183
|
+
|
113
184
|
# fetch the GitHub token from the environment
|
114
|
-
# :return: The GitHub token
|
185
|
+
# :return: The GitHub token or nil if not found
|
186
|
+
Contract None => Maybe[String]
|
115
187
|
def fetch_token
|
116
188
|
# first try to use the OCTOKIT_ACCESS_TOKEN env var if it exists
|
117
189
|
token = ENV.fetch("OCTOKIT_ACCESS_TOKEN", nil) # if running in actions
|
@@ -121,7 +193,10 @@ class Octokitted
|
|
121
193
|
token = ENV.fetch("GITHUB_TOKEN", nil) # if running in actions
|
122
194
|
return token unless token.nil?
|
123
195
|
|
124
|
-
|
196
|
+
# if we get here, we don't have a token - this is okay because we can still do some things...
|
197
|
+
# ... without a token, rate limiting can be an issue
|
198
|
+
@log.warn("No GitHub token found")
|
199
|
+
return nil
|
125
200
|
end
|
126
201
|
|
127
202
|
# Setup an Octokit client
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: octokitted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Grant Birkinbine
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- LICENSE
|
79
79
|
- README.md
|
80
80
|
- lib/octokitted.rb
|
81
|
+
- lib/octokitted/common/issue.rb
|
81
82
|
- lib/octokitted/git_plugin.rb
|
82
83
|
- lib/version.rb
|
83
84
|
- octokitted.gemspec
|