Skip to main content

REST API ๋ฐ Ruby๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํฌ๋ฆฝํŒ…

Octokit.rb SDK๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ REST API์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

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.rb ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด octokit ๋ณด์„์„ ์„ค์น˜ํ•˜๊ณ  ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” Ruby์˜ ๊ทœ์น™์— ๋”ฐ๋ผ import ๋ฌธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์„ค์น˜ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Octokit.rb README์˜ ์„ค์น˜ ์„น์…˜์„ ์ฐธ์กฐํ•˜์„ธ์š”.

์ธ์Šคํ„ด์Šคํ™” ๋ฐ ์ธ์ฆ

๊ฒฝ๊ณ 

์ธ์ฆ ์ž๊ฒฉ ์ฆ๋ช…์„ ์•”ํ˜ธ์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ž๊ฒฉ ์ฆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋น„๋ฐ€๋กœ ์ €์žฅํ•˜๊ณ  GitHub Actions๋ฅผ ํ†ตํ•ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Actions์—์„œ ๋น„๋ฐ€ ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋˜ํ•œ ์ž๊ฒฉ ์ฆ๋ช…์„ Codespaces ๋น„๋ฐ€๋กœ ์ €์žฅํ•˜๊ณ  Codespaces์—์„œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Codespaces์— ๋Œ€ํ•œ ๊ณ„์ •๋ณ„ ๋น„๋ฐ€ ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์ด(๊ฐ€) ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๋‹ค๋ฅธ CLI ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๊ฒฉ ์ฆ๋ช…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

personal access token์„(๋ฅผ) ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ

๊ฐœ์ธ์šฉ์œผ๋กœ GitHub REST API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ personal access token๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. personal access token์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ฐœ์ธ์šฉ ์•ก์„ธ์Šค ํ† ํฐ ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋จผ์ € octokit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ personal access token์„(๋ฅผ) access_token ์˜ต์…˜์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ Octokit ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” YOUR-TOKEN์„(๋ฅผ) personal access token์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.

Ruby
require 'octokit'

octokit = Octokit::Client.new(access_token: 'YOUR-TOKEN')

GitHub App(์œผ)๋กœ ์ธ์ฆ

์กฐ์ง ๋˜๋Š” ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋ฅผ ๋Œ€์‹ ํ•˜์—ฌ API๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ GitHub์—์„œ GitHub App์„(๋ฅผ) ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. GitHub Apps์— ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ REST ์ฐธ์กฐ ์„ค๋ช…์„œ์— ํ•„์š”ํ•œ GitHub App ํ† ํฐ ์œ ํ˜•์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub ์•ฑ ๋“ฑ๋ก ๋ฐ GitHub ์•ฑ์„ ์‚ฌ์šฉํ•œ ์ธ์ฆ ์ •๋ณด์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

octokit์ด(๊ฐ€) ํ•„์š”ํ•œ ๋Œ€์‹  GitHub App์˜ ์ •๋ณด๋ฅผ ์˜ต์…˜์œผ๋กœ ์ „๋‹ฌํ•˜์—ฌ Octokit::Client ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” APP_ID์„(๋ฅผ) ์•ฑ์˜ ID๋กœ, PRIVATE_KEY์„(๋ฅผ) ์•ฑ์˜ ํ”„๋ผ์ด๋น— ํ‚ค๋กœ, ๊ทธ๋ฆฌ๊ณ  INSTALLATION_ID์„(๋ฅผ) ๋Œ€์‹  ์ธ์ฆํ•˜๋ ค๋Š” ์•ฑ ์„ค์น˜์˜ ID๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์•ฑ ID๋ฅผ ์ฐพ๊ณ  ์•ฑ์˜ ์„ค์ • ํŽ˜์ด์ง€ ์„ค์ •์—์„œ ํ”„๋ผ์ด๋น— ํ‚ค๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub ์•ฑ์— ๋Œ€ํ•œ ํ”„๋ผ์ด๋น— ํ‚ค ๊ด€๋ฆฌ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”. GET /users/{username}/installation, GET /repos/{owner}/{repo}/installation, GET /orgs/{org}/installation ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์น˜ ID๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Apps์— ๋Œ€ํ•œ REST API ์—”๋“œํฌ์ธํŠธ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

Ruby
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_TOKEN์„ ๋น„๋ฐ€์˜ ์ด๋ฆ„์œผ๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ๋น„๋ฐ€์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ GitHub Actions์—์„œ ๋น„๋ฐ€ ์‚ฌ์šฉ์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

๋˜ํ•œ run ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌGitHub Actions ์›Œํฌํ”Œ๋กœ์—์„œ Ruby ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ GITHUB_TOKEN์˜ ๊ฐ’์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ENV['VARIABLE_NAME']๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์›Œํฌํ”Œ๋กœ ๋‹จ๊ณ„์—์„œ TOKEN์ด๋ผ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GITHUB_TOKEN์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

- name: Run script
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    ruby .github/actions-scripts/use-the-api.rb

์›Œํฌํ”Œ๋กœ๊ฐ€ ์‹คํ–‰ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋Š” ENV['TOKEN']์„ ์ธ์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Ruby
require 'octokit'

octokit = Octokit::Client.new(access_token: ENV['TOKEN'])

์ธ์ฆ ์—†๋Š” ์ธ์Šคํ„ด์Šคํ™”

ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ์ด ๋‚ฎ๊ณ  ์ผ๋ถ€ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด๋„ ์ธ์ฆ ์—†์ด REST API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธ์ฆํ•˜์ง€ ์•Š๊ณ  ์ธ์Šคํ„ด์Šค Octokit์„(๋ฅผ) ๋งŒ๋“ค๋ ค๋ฉด access_token ์˜ต์…˜์„ ์ „๋‹ฌํ•˜์ง€ ๋งˆ์„ธ์š”.

Ruby
require 'octokit'

octokit = Octokit::Client.new

์š”์ฒญ ์ˆ˜ํ–‰

Octokit์€ ์š”์ฒญ์„ ๋งŒ๋“œ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ HTTP ๋™์‚ฌ์™€ ๊ฒฝ๋กœ๋ฅผ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. IDE์—์„œ ์ž๋™ ์™„์„ฑ ๋ฐ ์ž…๋ ฅ์„ ์ด์šฉํ•˜๋ ค๋Š” ๊ฒฝ์šฐ rest ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ์—”๋“œํฌ์ธํŠธ์˜ ๊ฒฝ์šฐ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

request ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋งŒ๋“ค๋ ค๋ฉด HTTP ๋ฉ”์„œ๋“œ์™€ ๊ฒฝ๋กœ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ํ•ด์‹œ์˜ ๋ณธ๋ฌธ, ์ฟผ๋ฆฌ, ๊ฒฝ๋กœ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํŒจ์Šคํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด /repos/{owner}/{repo}/issues์— GET์„ ์š”์ฒญํ•˜๊ณ  owner, repo, per_page ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.

Ruby
octokit.request("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 2)

request ๋ฉ”์„œ๋“œ๋Š” ์ž๋™์œผ๋กœ Accept: application/vnd.github+json ๋จธ๋ฆฌ๊ธ€์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ํ—ค๋” ๋˜๋Š” ๋‹ค๋ฅธ Accept ํ—ค๋”๋ฅผ ์ „๋‹ฌํ•˜๋ ค๋ฉด ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋˜๋Š” ํ•ด์‹œ์— headers ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. headers ์˜ต์…˜์˜ ๊ฐ’์€ ํ—ค๋” ์ด๋ฆ„์„ ํ‚ค๋กœ, ํ—ค๋” ๊ฐ’์„ ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ•ด์‹œ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด text/plain์˜ ๊ฐ’๊ณผ ํ•จ๊ป˜ content-type ํ—ค๋”๋ฅผ ๋ณด๋‚ด๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

Ruby
octokit.request("POST /markdown/raw", text: "Hello **world**", headers: { "content-type" => "text/plain" })

rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ

๋ชจ๋“  REST API ์—”๋“œํฌ์ธํŠธ์—๋Š” Octokit์— ์—ฐ๊ฒฐ๋œ rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฉ”์„œ๋“œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํŽธ์˜๋ฅผ ์œ„ํ•ด IDE์—์„œ ์ž๋™ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ํ•ด์‹œ๋กœ ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Ruby
octokit.rest.issues.list_for_repo(owner: "github", repo: "docs", per_page: 2)

ํŽ˜์ด์ง€ ๋งค๊ธด ์š”์ฒญ ๋งŒ๋“ค๊ธฐ

์—”๋“œํฌ์ธํŠธ์˜ ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง€๊ณ  ๋‘˜ ์ด์ƒ์˜ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋ฅผ ํŽ˜์น˜ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. paginate๋Š” ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ์˜ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ฌ๋ฆฌ ๋ช‡ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฒฐ๊ณผ๋ฅผ ๊ฐœ์ฒด์˜ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์›์‹œ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐœ์ฒด์ธ ๊ฒฝ์šฐ์—๋„ paginate๋Š” ํ•ญ์ƒ ํ•ญ๋ชฉ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” github/docs ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ๋ชจ๋“  ์ด์Šˆ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์— 100๊ฐœ์˜ ์ด์Šˆ๋ฅผ ์š”์ฒญํ•˜์ง€๋งŒ ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Ruby
issue_data = octokit.paginate("GET /repos/{owner}/{repo}/issues", owner: "github", repo: "docs", per_page: 100)

์ด paginate ๋ฉ”์„œ๋“œ๋Š” ๊ฒฐ๊ณผ์˜ ๊ฐ ํŽ˜์ด์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์„ ํƒ์  ๋ธ”๋ก์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‘๋‹ต์—์„œ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ์ œ๋ชฉ์— "test"๊ฐ€ ํฌํ•จ๋œ ์ด์Šˆ๊ฐ€ ๋ฐ˜ํ™˜๋  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์† ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ ์ด์Šˆ ์ œ๋ชฉ๊ณผ ์ž‘์„ฑ์ž๋งŒ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

Ruby
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"๊ฐ€ ํฌํ•จ๋œ ์ด์Šˆ์— ๋„๋‹ฌํ•˜๋ฉด ์Šคํฌ๋ฆฝํŠธ๋Š” ๋ฐ˜๋ณต์„ ์ค‘์ง€ํ•˜๊ณ  ์ฒ˜๋ฆฌ๋œ ๊ฐ ๊ฐœ์ฒด์˜ ์ด์Šˆ ์ œ๋ชฉ ๋ฐ ์ด์Šˆ ์ž‘์„ฑ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๊ธฐ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ๊ฐ€์žฅ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

Ruby
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

rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ์—์„œ๋„ paginate ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. rest ์—”๋“œํฌ์ธํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•˜๊ณ  ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

Ruby
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๋Š” ์‘๋‹ต(response.status) ๋ฐ ์‘๋‹ต ํ—ค๋”(response.headers)์˜ HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ์˜ค๋ฅ˜๋ฅผ throwํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด try/catch ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Ruby
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 ์‘๋‹ต์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ๋ณ„ํ‘œ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Œ์„ ๋‚˜ํƒ€๋‚ด๊ณ  ๋‹ค๋ฅธ ๋ชจ๋“  ์˜ค๋ฅ˜ ์ฝ”๋“œ๋Š” ์˜ค๋ฅ˜๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

Ruby
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"์ด ๋ฉ๋‹ˆ๋‹ค. ์‘๋‹ต ํ—ค๋”์—๋Š” ํ˜„์žฌ ํŠธ๋ž˜ํ”ฝ๋ฅ  ์ œํ•œ ์ฐฝ์ด ์žฌ์„ค์ •๋˜๋Š” ์‹œ๊ฐ„์„ UTC Epoch ์ดˆ ๋‹จ์œ„๋กœ ์•Œ๋ ค์ฃผ๋Š” x-ratelimit-reset ํ—ค๋”๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. x-ratelimit-reset์— ์ง€์ •๋œ ์‹œ๊ฐ„ ํ›„์— ์š”์ฒญ์„ ๋‹ค์‹œ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Ruby
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 ์†์„ฑ์€ ์ƒ๋žต๋ฉ๋‹ˆ๋‹ค.

Ruby
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 ๊ฐœ์ฒด์—๋Š” ๋ฐ์ดํ„ฐ, ์ƒํƒœ, URL ๋ฐ ํ—ค๋”๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

Ruby
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๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. personal access token ๋Œ€์‹  GitHub App์„(๋ฅผ) ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆํ•˜๋ ค๋Š” ๊ฒฝ์šฐ Octokit ๋Œ€์‹  App์„ ๊ฐ€์ ธ์˜ค๊ณ  ์ธ์Šคํ„ด์Šคํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” ์ด ๊ฐ€์ด๋“œ์˜ GitHub App์—์„œ ์ธ์ฆ์„ ์ฐธ์กฐํ•˜์„ธ์š”.

get_changed_files ํ•จ์ˆ˜๋Š” ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์— ๋Œ€ํ•ด ๋ณ€๊ฒฝ๋œ ๋ชจ๋“  ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. comment_if_data_files_changed ํ•จ์ˆ˜๋Š” get_changed_files ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์ด ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ์ด ํŒŒ์ผ ๊ฒฝ๋กœ์— /data/๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ ํ•จ์ˆ˜๋Š” ๋Œ์–ด์˜ค๊ธฐ ์š”์ฒญ์— ๋Œ€ํ•ด ์„ค๋ช…์„ ๋‹ฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Ruby
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 ์„ค๋ช…์„œ์„ ์ฐธ์กฐํ•˜์„ธ์š”.