关于 Octokit.rb
如果您想使用 Ruby 编写脚本以与 GitHub REST API 交互,GitHub 建议您使用 Octokit.rb SDK。Octokit.rb 由 GitHub 维护。该 SDK 实现了最佳实践,使您能够更轻松地通过 Ruby 与 REST API 交互。Octokit.rb 适用于所有现代浏览器、Node.rb 和 Deno。有关 Octokit.rb 的更多信息,请参阅Octokit.rb 自述文件。
先决条件
本指南假设您熟悉 Ruby 和 GitHub REST API。有关 REST API 的更多信息,请参阅“REST API 入门”。
您必须安装并导入 octokit
gem 才能使用 Octokit.rb 库。本指南根据 Ruby 的约定使用导入语句。有关不同安装方法的更多信息,请参阅Octokit.rb 自述文件的“安装”部分。
实例化和身份验证
您还可以将凭据存储为 Codespaces 密钥,并在 Codespaces 中运行脚本。有关更多信息,请参阅“管理 GitHub Codespaces 的帐户特定密钥”。
如果这些选项不可用,请考虑使用其他 CLI 服务来安全地存储您的凭据。
使用个人访问令牌进行身份验证
如果您想将 GitHub REST API 用于个人用途,可以创建个人访问令牌。有关创建个人访问令牌的更多信息,请参阅“管理个人访问令牌”。
首先,需要 octokit
库。然后,通过将您的个人访问令牌作为 access_token
选项传递来创建 Octokit
的实例。在以下示例中,将 YOUR-TOKEN
替换为您的个人访问令牌。
require 'octokit' octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
require 'octokit'
octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')
使用 GitHub App 进行身份验证
如果您想代表组织或其他用户使用 API,GitHub 建议您使用 GitHub App。如果某个端点可用于 GitHub Apps,则该端点的 REST 参考文档将指示需要哪种类型的 GitHub App 令牌。有关更多信息,请参阅“注册 GitHub App”和“关于使用 GitHub App 进行身份验证”。
无需要求 octokit
,而是通过将 GitHub App 的信息作为选项传递来创建 Octokit::Client
的实例。在以下示例中,将 APP_ID
替换为应用程序的 ID,将 PRIVATE_KEY
替换为应用程序的私钥,并将 INSTALLATION_ID
替换为您要代表其进行身份验证的应用程序安装的 ID。您可以在应用程序的设置页面上找到应用程序的 ID 并生成私钥。有关更多信息,请参阅“管理 GitHub Apps 的私钥”。您可以使用 GET /users/{username}/installation
、GET /repos/{owner}/{repo}/installation
或 GET /orgs/{org}/installation
端点获取安装 ID。有关更多信息,请参阅“GitHub Apps 的 REST API 端点”。
require 'octokit' app = Octokit::Client.new( client_id: APP_ID, client_secret: PRIVATE_KEY, installation_id: INSTALLATION_ID ) octokit = Octokit::Client.new(bearer_token: app.create_app_installation.access_token)
require 'octokit'
app = Octokit::Client.new(
client_id: APP_ID,
client_secret: PRIVATE_KEY,
installation_id: INSTALLATION_ID
)
octokit = Octokit::Client.new(bearer_token: app.create_app_installation.access_token)
在 GitHub Actions 中进行身份验证
如果您想在 GitHub Actions 工作流中使用 API,GitHub 建议您使用内置的 GITHUB_TOKEN
进行身份验证,而不是创建令牌。您可以使用 permissions
密钥授予 GITHUB_TOKEN
权限。有关 GITHUB_TOKEN
的更多信息,请参阅“自动令牌身份验证”。
如果您的工作流需要访问工作流存储库外部的资源,则无法使用 GITHUB_TOKEN
。在这种情况下,请将您的凭据存储为密钥,并将以下示例中的 GITHUB_TOKEN
替换为密钥的名称。有关密钥的更多信息,请参阅“在 GitHub Actions 中使用密钥”。
如果您使用 run
关键字在 GitHub Actions 工作流中执行 Ruby 脚本,则可以将 GITHUB_TOKEN
的值存储为环境变量。您的脚本可以将环境变量访问为 ENV['VARIABLE_NAME']
。
例如,此工作流步骤将 GITHUB_TOKEN
存储在名为 TOKEN
的环境变量中
- name: Run script
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ruby .github/actions-scripts/use-the-api.rb
工作流运行的脚本使用 ENV['TOKEN']
进行身份验证
require 'octokit' octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
require 'octokit'
octokit = Octokit::Client.new(access_token: ENV['TOKEN'])
无需身份验证进行实例化
您可以使用 REST API 而无需身份验证,尽管您的速率限制较低,并且无法使用某些端点。要创建 Octokit
的实例而无需身份验证,请不要传递 access_token
选项。
require 'octokit' octokit = Octokit::Client.new
require 'octokit'
octokit = Octokit::Client.new
发出请求
Octokit 支持多种发出请求的方式。如果您知道端点的 HTTP 方法和路径,可以使用 request
方法发出请求。如果您想利用 IDE 和输入的自动完成功能,可以使用 rest
方法。对于分页端点,可以使用 paginate
方法请求多页数据。
使用 request
方法发出请求
要使用 request
方法发出请求,请将 HTTP 方法和路径作为第一个参数传递。将任何正文、查询或路径参数以哈希的形式作为第二个参数传递。例如,要向 /repos/{owner}/{repo}/issues
发出 GET
请求并传递 owner
、repo
和 per_page
参数
octokit.request("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
octokit.request("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
request
方法会自动传递 Accept: application/vnd.github+json
标头。要传递其他标头或不同的 Accept
标头,请将 headers
选项添加到作为第二个参数传递的哈希中。headers
选项的值是一个哈希,其中标头名称作为键,标头值作为值。例如,要发送 content-type
标头,其值为 text/plain
octokit.request("POST /markdown/raw", text: "Hello **world**", headers: { "content-type" => "text/plain" })
octokit.request("POST /markdown/raw", text: "Hello **world**", headers: { "content-type" => "text/plain" })
使用 rest
端点方法发出请求
每个 REST API 端点在 Octokit 中都有一个关联的 rest
端点方法。为了方便起见,这些方法通常会在您的 IDE 中自动完成。您可以将任何参数作为哈希传递给该方法。
octokit.rest.issues.list_for_repo(owner: "github", repo: "docs", per_page: 2)
octokit.rest.issues.list_for_repo(owner: "github", repo: "docs", per_page: 2)
发出分页请求
如果端点已分页,并且您想获取超过一页的结果,则可以使用 paginate
方法。paginate
将获取下一页的结果,直到到达最后一页,然后将所有结果作为数组返回。一些端点将分页结果作为对象中的数组返回,而不是将分页结果作为数组返回。即使原始结果是对象,paginate
始终会返回项目数组。
例如,以下示例获取 github/docs
存储库中的所有问题。虽然它一次请求 100 个问题,但该函数只有在到达数据最后一页时才会返回。
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
paginate
方法接受一个可选的代码块,您可以使用它来处理每一页的结果。这使您可以仅从响应中收集所需的数据。例如,以下示例继续获取结果,直到返回标题中包含“test”的问题为止。对于返回的数据页,仅存储问题标题和作者。
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) do |response, done| response.data.map do |issue| if issue.title.include?("test") done.call end { title: issue.title, author: issue.user.login } end end
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) do |response, done|
response.data.map do |issue|
if issue.title.include?("test")
done.call
end
{ title: issue.title, author: issue.user.login }
end
end
您可以使用 octokit.paginate.iterator()
逐页迭代,而不是一次获取所有结果。例如,以下示例一次获取一页结果,并在获取下一页之前处理该页面的每个对象。一旦遇到标题中包含“test”的问题,脚本就会停止迭代并返回已处理的每个对象的标题和作者。对于获取分页数据,迭代器是最节省内存的方法。
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) issue_data = [] break_loop = false iterator.each do |data| break if break_loop data.each do |issue| if issue.title.include?("test") break_loop = true break else issue_data << { title: issue.title, author: issue.user.login } end end end
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
issue_data = []
break_loop = false
iterator.each do |data|
break if break_loop
data.each do |issue|
if issue.title.include?("test")
break_loop = true
break
else
issue_data << { title: issue.title, author: issue.user.login }
end
end
end
您也可以将 paginate
方法与 rest
端点方法一起使用。将 rest
端点方法作为第一个参数,并将任何参数作为第二个参数传递。
iterator = octokit.paginate.iterator(octokit.rest.issues.list_for_repo, owner: "github", repo: "docs", per_page: 100)
iterator = octokit.paginate.iterator(octokit.rest.issues.list_for_repo, owner: "github", repo: "docs", per_page: 100)
有关分页的更多信息,请参阅“在 REST API 中使用分页”。
捕获错误
捕获所有错误
有时,GitHub REST API 会返回错误。例如,如果您的访问令牌已过期或您省略了必需的参数,则会收到错误。Octokit.rb 在遇到除 400 Bad Request
、401 Unauthorized
、403 Forbidden
、404 Not Found
和 422 Unprocessable Entity
之外的错误时会自动重试请求。如果即使在重试后仍发生 API 错误,Octokit.rb 会抛出一个错误,其中包含响应的 HTTP 状态代码 (response.status
) 和响应头 (response.headers
)。您应该在代码中处理这些错误。例如,您可以使用 try/catch 块来捕获错误
begin files_changed = [] iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: "github", repo: "docs", pull_number: 22809, per_page: 100) iterator.each do | data | files_changed.concat(data.map { | file_data | file_data.filename }) end rescue Octokit::Error => error if error.response puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}" end puts error end
begin
files_changed = []
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: "github", repo: "docs", pull_number: 22809, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
处理预期错误代码
有时,GitHub 使用 4xx 状态代码来指示非错误响应。如果您使用的端点执行此操作,则可以为特定错误添加其他处理。例如,GET /user/starred/{owner}/{repo}
端点如果存储库未加星标,则将返回 404
。以下示例使用 404
响应来指示存储库未加星标;所有其他错误代码都将视为错误。
begin octokit.request("GET /user/starred/{owner}/{repo}", owner: "github", repo: "docs") puts "The repository is starred by me" rescue Octokit::NotFound => error puts "The repository is not starred by me" rescue Octokit::Error => error puts "An error occurred while checking if the repository is starred: #{error&.response&.data&.message}" end
begin
octokit.request("GET /user/starred/{owner}/{repo}", owner: "github", repo: "docs")
puts "The repository is starred by me"
rescue Octokit::NotFound => error
puts "The repository is not starred by me"
rescue Octokit::Error => error
puts "An error occurred while checking if the repository is starred: #{error&.response&.data&.message}"
end
处理速率限制错误
如果您收到速率限制错误,您可能需要等待后重试请求。当您受到速率限制时,GitHub 会返回 403 Forbidden
错误,并且 x-ratelimit-remaining
响应头值将为 "0"
。响应头将包含 x-ratelimit-reset
头,它告诉您当前速率限制窗口重置的时间(以 UTC 纪元秒为单位)。您可以在 x-ratelimit-reset
指定的时间后重试请求。
def request_retry(route, parameters) begin response = octokit.request(route, parameters) return response rescue Octokit::RateLimitExceeded => error reset_time_epoch_seconds = error.response.headers['x-ratelimit-reset'].to_i current_time_epoch_seconds = Time.now.to_i seconds_to_wait = reset_time_epoch_seconds - current_time_epoch_seconds puts "You have exceeded your rate limit. Retrying in #{seconds_to_wait} seconds." sleep(seconds_to_wait) retry rescue Octokit::Error => error puts error end end response = request_retry("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
def request_retry(route, parameters)
begin
response = octokit.request(route, parameters)
return response
rescue Octokit::RateLimitExceeded => error
reset_time_epoch_seconds = error.response.headers['x-ratelimit-reset'].to_i
current_time_epoch_seconds = Time.now.to_i
seconds_to_wait = reset_time_epoch_seconds - current_time_epoch_seconds
puts "You have exceeded your rate limit. Retrying in #{seconds_to_wait} seconds."
sleep(seconds_to_wait)
retry
rescue Octokit::Error => error
puts error
end
end
response = request_retry("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)
使用响应
如果请求成功,则 request
方法将返回一个响应对象。响应对象包含 data
(端点返回的响应正文)、status
(HTTP 响应代码)、url
(请求的 URL)和 headers
(包含响应头的哈希表)。除非另有说明,否则响应正文采用 JSON 格式。某些端点不返回响应正文;在这种情况下,将省略 data
属性。
response = octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", owner: "github", repo: "docs", issue_number: 11901) puts "The status of the response is: #{response.status}" puts "The request URL was: #{response.url}" puts "The x-ratelimit-remaining response header is: #{response.headers['x-ratelimit-remaining']}" puts "The issue title is: #{response.data['title']}"
response = octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", owner: "github", repo: "docs", issue_number: 11901)
puts "The status of the response is: #{response.status}"
puts "The request URL was: #{response.url}"
puts "The x-ratelimit-remaining response header is: #{response.headers['x-ratelimit-remaining']}"
puts "The issue title is: #{response.data['title']}"
类似地,paginate
方法返回一个响应对象。如果 request
成功,则 response
对象包含 data、status、url 和 headers。
response = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100) puts "#{response.data.length} issues were returned" puts "The title of the first issue is: #{response.data[0]['title']}"
response = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)
puts "#{response.data.length} issues were returned"
puts "The title of the first issue is: #{response.data[0]['title']}"
示例脚本
这是一个使用 Octokit.rb 的完整示例脚本。该脚本导入 Octokit
并创建一个新的 Octokit
实例。如果您想使用 GitHub App 而不是个人访问令牌进行身份验证,则应导入并实例化 App
而不是 Octokit
。有关更多信息,请参阅本指南中的“使用 GitHub App 进行身份验证”。
get_changed_files
函数获取拉取请求的所有已更改文件。comment_if_data_files_changed
函数调用 get_changed_files
函数。如果拉取请求更改的任何文件的文件路径中包含 /data/
,则该函数将对拉取请求进行评论。
require "octokit" octokit = Octokit::Client.new(access_token: "YOUR-TOKEN") def get_changed_files(octokit, owner, repo, pull_number) files_changed = [] begin iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: owner, repo: repo, pull_number: pull_number, per_page: 100) iterator.each do | data | files_changed.concat(data.map { | file_data | file_data.filename }) end rescue Octokit::Error => error if error.response puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}" end puts error end files_changed end def comment_if_data_files_changed(octokit, owner, repo, pull_number) changed_files = get_changed_files(octokit, owner, repo, pull_number) if changed_files.any ? { | file_name | /\/data\//i.match ? (file_name) } begin comment = octokit.create_pull_request_review_comment(owner, repo, pull_number, "It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.") comment.html_url rescue Octokit::Error => error if error.response puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}" end puts error end end end # Example usage owner = "github" repo = "docs" pull_number = 22809 comment_url = comment_if_data_files_changed(octokit, owner, repo, pull_number) puts "A comment was added to the pull request: #{comment_url}"
require "octokit"
octokit = Octokit::Client.new(access_token: "YOUR-TOKEN")
def get_changed_files(octokit, owner, repo, pull_number)
files_changed = []
begin
iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", owner: owner, repo: repo, pull_number: pull_number, per_page: 100)
iterator.each do | data |
files_changed.concat(data.map {
| file_data | file_data.filename
})
end
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
files_changed
end
def comment_if_data_files_changed(octokit, owner, repo, pull_number)
changed_files = get_changed_files(octokit, owner, repo, pull_number)
if changed_files.any ? {
| file_name | /\/data\//i.match ? (file_name)
}
begin
comment = octokit.create_pull_request_review_comment(owner, repo, pull_number, "It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.")
comment.html_url
rescue Octokit::Error => error
if error.response
puts "Error! Status: #{error.response.status}. Message: #{error.response.data.message}"
end
puts error
end
end
end
# Example usage
owner = "github"
repo = "docs"
pull_number = 22809
comment_url = comment_if_data_files_changed(octokit, owner, repo, pull_number)
puts "A comment was added to the pull request: #{comment_url}"
注意
这只是一个基本示例。在实践中,您可能需要使用错误处理和条件检查来处理各种情况。
后续步骤
要了解有关使用 GitHub REST API 和 Octokit.rb 的更多信息,请探索以下资源
- 要了解有关 Octokit.rb 的更多信息,请参阅Octokit.rb 文档。
- 要查找有关 GitHub 可用 REST API 端点(包括其请求和响应结构)的详细信息,请参阅GitHub REST API 文档。