octokitted 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![test](https://github.com/GrantBirki/octokitted/actions/workflows/test.yml/badge.svg)](https://github.com/GrantBirki/octokitted/actions/workflows/test.yml) [![lint](https://github.com/GrantBirki/octokitted/actions/workflows/lint.yml/badge.svg)](https://github.com/GrantBirki/octokitted/actions/workflows/lint.yml) [![CodeQL](https://github.com/GrantBirki/octokitted/actions/workflows/codeql-analysis.yml/badge.svg)](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
|
+
![octokitted](./docs/assets/dalle.png)
|
@@ -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
|