Class: GemContribute::HostAdapters::GitHubAdapter
- Inherits:
-
GemContribute::HostAdapter
- Object
- GemContribute::HostAdapter
- GemContribute::HostAdapters::GitHubAdapter
- Defined in:
- lib/gem_contribute/host_adapters/github_adapter.rb
Overview
GitHub adapter. Implements the ‘HostAdapter` interface for github.com. Public read methods work anonymously; auth-required methods raise `AuthRequired` without a cached token (ADR-0001 / ADR-0004).
‘token` is optional. When present it’s sent as ‘Authorization: Bearer …` to lift the rate limit and unlock fork / comment / etc.
Class length: this adapter wraps a growing surface area of GitHub endpoints. The 150-line metric isn’t a useful constraint here — we’d just split into arbitrary sub-modules — so it’s disabled below with this rationale. rubocop:disable Metrics/ClassLength
Defined Under Namespace
Classes: RateLimit
Constant Summary collapse
- API_BASE =
"https://api.github.com"- ACCEPT =
"application/vnd.github+json"- API_VERSION =
"2022-11-28"- MAX_REDIRECTS =
3- FORK_READINESS_RETRIES =
GitHub’s POST /forks returns 202 immediately; the fork resource may 404 for a few seconds while propagation finishes. Bound the wait at 12 × 5s = 60s.
12- FORK_READINESS_INTERVAL =
5
Instance Attribute Summary collapse
-
#rate_limit ⇒ Object
readonly
Returns the value of attribute rate_limit.
Instance Method Summary collapse
-
#clone_url(owner, repo) ⇒ Object
Pure URL templating — no auth, no network.
-
#comment(project, issue:, body:) ⇒ Object
POST /repos/:owner/:repo/issues/:n/comments.
- #community_profile(project) ⇒ Object
- #file_contents(project, path) ⇒ Object
-
#fork(project) ⇒ Object
Idempotent, blocking fork.
-
#initialize(cache: Cache.new, http: Net::HTTP, token: nil, sleeper: ->(s) { Kernel.sleep(s) }) ⇒ GitHubAdapter
constructor
A new instance of GitHubAdapter.
-
#issue(project, number) ⇒ Hash
A single issue’s full payload (uncached — submit only).
-
#issue_comments(project, number) ⇒ Object
GET /repos/:owner/:repo/issues/:n/comments.
-
#issues(project, labels: nil) ⇒ Array<Hash>
Open issues filtered to the given labels (if any).
-
#pull_request_url(upstream, head_owner:, head_branch:, title:, body:) ⇒ Object
Builds GitHub’s pre-filled compare URL.
- #repo_url(owner, repo) ⇒ Object
-
#search_issues(query) ⇒ Object
GET /search/issues.
-
#viewer_login ⇒ Object
GET /user.
Constructor Details
#initialize(cache: Cache.new, http: Net::HTTP, token: nil, sleeper: ->(s) { Kernel.sleep(s) }) ⇒ GitHubAdapter
Returns a new instance of GitHubAdapter.
37 38 39 40 41 42 43 44 45 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 37 def initialize(cache: Cache.new, http: Net::HTTP, token: nil, sleeper: ->(s) { Kernel.sleep(s) }) super() @cache = cache @http = http @token = token @sleeper = sleeper @rate_limit = nil end |
Instance Attribute Details
#rate_limit ⇒ Object (readonly)
Returns the value of attribute rate_limit.
35 36 37 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 35 def rate_limit @rate_limit end |
Instance Method Details
#clone_url(owner, repo) ⇒ Object
Pure URL templating — no auth, no network. Used by Operations to construct the ‘upstream` remote and by CLI verbs for summary output.
166 167 168 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 166 def clone_url(owner, repo) "https://github.com/#{owner}/#{repo}.git" end |
#comment(project, issue:, body:) ⇒ Object
POST /repos/:owner/:repo/issues/:n/comments. Returns the created comment payload (id, body, html_url, …). Used by ‘fix` to post the “working on this” announcement.
119 120 121 122 123 124 125 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 119 def comment(project, issue:, body:) raise AuthRequired, "github.com" unless @token ensure_known_host!(project) post_json("/repos/#{project.owner}/#{project.repo}/issues/#{issue}/comments", { "body" => body }) end |
#community_profile(project) ⇒ Object
71 72 73 74 75 76 77 78 79 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 71 def community_profile(project) ensure_known_host!(project) cache_key = "#{project.owner}/#{project.repo}" cached = @cache.fetch("repos", cache_key) return cached if cached body = get_json("/repos/#{project.owner}/#{project.repo}/community/profile") @cache.write("repos", cache_key, body) end |
#file_contents(project, path) ⇒ Object
81 82 83 84 85 86 87 88 89 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 81 def file_contents(project, path) ensure_known_host!(project) cache_key = "#{project.owner}/#{project.repo}:#{path}" cached = @cache.fetch("files", cache_key) return cached if cached body = get_json("/repos/#{project.owner}/#{project.repo}/contents/#{path}") @cache.write("files", cache_key, body) end |
#fork(project) ⇒ Object
Idempotent, blocking fork. If the viewer already owns a fork at the same name, returns it as ‘reused: true` without a POST. Otherwise POSTs to /repos/:owner/:repo/forks and polls until the fork is reachable. Returns a `HostAdapter::ForkResult`.
95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 95 def fork(project) raise AuthRequired, "github.com" unless @token ensure_known_host!(project) viewer = viewer_login return existing_fork_result(viewer, project) if fork_exists?(viewer, project.repo) body = post_json("/repos/#{project.owner}/#{project.repo}/forks") wait_until_fork_ready(viewer, project.repo) new_fork_result(viewer, project, body) end |
#issue(project, number) ⇒ Hash
Returns a single issue’s full payload (uncached — submit only).
48 49 50 51 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 48 def issue(project, number) ensure_known_host!(project) get_json("/repos/#{project.owner}/#{project.repo}/issues/#{number}") end |
#issue_comments(project, number) ⇒ Object
GET /repos/:owner/:repo/issues/:n/comments. Returns an array of comment payloads. Uncached (callers may want fresh data, e.g. to check for an idempotency marker).
130 131 132 133 134 135 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 130 def issue_comments(project, number) raise AuthRequired, "github.com" unless @token ensure_known_host!(project) get_json("/repos/#{project.owner}/#{project.repo}/issues/#{number}/comments") end |
#issues(project, labels: nil) ⇒ Array<Hash>
Returns open issues filtered to the given labels (if any).
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 54 def issues(project, labels: nil) ensure_known_host!(project) cache_key = issue_cache_key(project, labels) cached = @cache.fetch("issues", cache_key) return cached if cached params = { state: "open", per_page: 50 } params[:labels] = Array(labels).join(",") if labels && !Array(labels).empty? body = get_json("/repos/#{project.owner}/#{project.repo}/issues", params) # GitHub's /issues endpoint mixes pull requests in. PRs have a # `pull_request` key; filter those out so callers see issues only. only_issues = body.reject { |i| i.key?("pull_request") } @cache.write("issues", cache_key, only_issues) end |
#pull_request_url(upstream, head_owner:, head_branch:, title:, body:) ⇒ Object
Builds GitHub’s pre-filled compare URL. The browser-based PR flow (ADR-0011) means the user reviews the title/body before submitting, so this method just templates — it doesn’t post.
155 156 157 158 159 160 161 162 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 155 def pull_request_url(upstream, head_owner:, head_branch:, title:, body:) ensure_known_host!(upstream) same_repo = head_owner == upstream.owner head = same_repo ? head_branch : "#{head_owner}:#{head_branch}" params = { "expand" => "1", "title" => title, "body" => body } "https://github.com/#{upstream.owner}/#{upstream.repo}/compare/#{head}?" \ "#{URI.encode_www_form(params)}" end |
#repo_url(owner, repo) ⇒ Object
170 171 172 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 170 def repo_url(owner, repo) "https://github.com/#{owner}/#{repo}" end |
#search_issues(query) ⇒ Object
GET /search/issues. Wraps GitHub’s issue search; works without auth (subject to the 60/hr anonymous rate limit). Returns an array of issue payloads (the search response’s ‘items` key). Cached under the `issues` namespace using the query as the key. Used to find issues already claimed via the gem-contribute marker.
142 143 144 145 146 147 148 149 150 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 142 def search_issues(query) cache_key = "search:#{query}" cached = @cache.fetch("issues", cache_key) return cached if cached raw = get_json("/search/issues", q: query) items = raw.fetch("items", []) @cache.write("issues", cache_key, items) end |
#viewer_login ⇒ Object
GET /user. Used by ‘auth status` and internally by `fork`. Returns the authenticated user’s login string (e.g. “cdhagmann”).
109 110 111 112 113 114 |
# File 'lib/gem_contribute/host_adapters/github_adapter.rb', line 109 def viewer_login raise AuthRequired, "github.com" unless @token body = get_json("/user") body.fetch("login") end |