关于 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 自述文件的安装部分。
实例化和身份验证
警告:将您的身份验证凭据视为密码。
为了确保凭据安全,您可以将凭据存储为机密,并通过 GitHub Actions 运行脚本。有关更多信息,请参阅 "在 GitHub Actions 中使用机密."
您还可以将凭据存储为 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 文档。