Class: Ace::Review::Molecules::GhPrFetcher
- Inherits:
-
Object
- Object
- Ace::Review::Molecules::GhPrFetcher
- Defined in:
- lib/ace/review/molecules/gh_pr_fetcher.rb
Overview
Fetch PR diff and metadata via gh CLI
Class Method Summary collapse
-
.fetch_diff(pr_identifier, options = {}) ⇒ Hash
Fetch PR diff content.
- .fetch_file_inventory(metadata, options = {}) ⇒ Object
-
.fetch_metadata(pr_identifier, options = {}) ⇒ Hash
Fetch PR metadata (state, draft status, title, etc.).
-
.fetch_pr(pr_identifier, options = {}) ⇒ Hash
Fetch a diff bracketed by stable PR metadata.
Class Method Details
.fetch_diff(pr_identifier, options = {}) ⇒ Hash
Fetch PR diff content
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/ace/review/molecules/gh_pr_fetcher.rb', line 20 def self.fetch_diff(pr_identifier, = {}) # Parse identifier to get gh CLI format using ace-git parsed = Ace::Git::Github::PrIdentifier.parse(pr_identifier) gh_format = parsed.gh_format # Default timeout for PR diff operations timeout = [:timeout] || 30 # Fetch diff with retry logic result = Ace::Review::Atoms::RetryWithBackoff.execute() do Ace::Git::Github::CliExecutor.execute("pr", ["diff", *parsed.cli_target_args], timeout: timeout) end if result[:success] { success: true, diff: result[:stdout], identifier: gh_format, parsed: parsed.to_h } else handle_fetch_error(result, pr_identifier) end rescue Ace::Review::Errors::DiffTooLargeError # Fall back to local git diff when GitHub API rejects large diffs fetch_local_diff_fallback(pr_identifier, ) rescue Ace::Review::Errors::GhCliNotInstalledError, Ace::Review::Errors::GhAuthenticationError, Ace::Git::ProviderCliMissingError, Ace::Git::ProviderAuthenticationError # Re-raise authentication and installation errors raise rescue => e { success: false, error: "Failed to fetch PR diff: #{e.}" } end |
.fetch_file_inventory(metadata, options = {}) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/ace/review/molecules/gh_pr_fetcher.rb', line 140 def self.fetch_file_inventory(, = {}) match = ["url"].to_s.match(%r{\Ahttps://github\.com/([^/]+/[^/]+)/pull/(\d+)\z}) return {success: false, error: "Cannot identify GitHub repository for PR file inventory"} unless match endpoint = "repos/#{match[1]}/pulls/#{match[2]}/files?per_page=100" result = Ace::Review::Atoms::RetryWithBackoff.execute() do Ace::Git::Github::CliExecutor.execute("api", [endpoint, "--paginate", "--jq", "[.[].filename]"], timeout: [:timeout] || 30) end return {success: false, error: "Failed to fetch PR file inventory: #{result[:stderr]}"} unless result[:success] pages = result[:stdout].lines.map { |line| JSON.parse(line) } unless pages.any? && pages.all? { |page| page.is_a?(Array) } return {success: false, error: "Invalid PR file inventory response"} end files = pages.flatten(1) unless files.all? { |file| file.is_a?(String) } return {success: false, error: "Invalid PR file inventory entry"} end {success: true, files: files.map { |file| {"path" => file} }} rescue JSON::ParserError => e {success: false, error: "Invalid PR file inventory JSON: #{e.}"} rescue Ace::Review::Errors::GhNetworkError => e {success: false, error: "Failed to fetch PR file inventory: #{e.}"} end |
.fetch_metadata(pr_identifier, options = {}) ⇒ Hash
Fetch PR metadata (state, draft status, title, etc.)
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/ace/review/molecules/gh_pr_fetcher.rb', line 63 def self.(pr_identifier, = {}) # Parse identifier using ace-git parsed = Ace::Git::Github::PrIdentifier.parse(pr_identifier) gh_format = parsed.gh_format # Default timeout for PR operations timeout = [:timeout] || 30 # Fetch metadata as JSON fields = "number,state,isDraft,title,body,author,headRefName,headRefOid,baseRefName,baseRefOid,url,changedFiles" result = Ace::Review::Atoms::RetryWithBackoff.execute() do Ace::Git::Github::CliExecutor.execute("pr", ["view", *parsed.cli_target_args, "--json", fields], timeout: timeout) end if result[:success] = JSON.parse(result[:stdout]) { success: true, metadata: , identifier: gh_format, parsed: parsed.to_h } else handle_fetch_error(result, pr_identifier) end rescue JSON::ParserError => e { success: false, error: "Failed to parse PR metadata: #{e.}" } rescue Ace::Review::Errors::GhCliNotInstalledError, Ace::Review::Errors::GhAuthenticationError, Ace::Git::ProviderCliMissingError, Ace::Git::ProviderAuthenticationError raise rescue => e { success: false, error: "Failed to fetch PR metadata: #{e.}" } end |
.fetch_pr(pr_identifier, options = {}) ⇒ Hash
Fetch a diff bracketed by stable PR metadata. The diff endpoint uses the mutable PR ref, so a head/base change during retrieval must not be attributed to the later SHA pair.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/ace/review/molecules/gh_pr_fetcher.rb', line 111 def self.fetch_pr(pr_identifier, = {}) 2.times do before = (pr_identifier, ) return before unless before[:success] diff_result = fetch_diff(pr_identifier, ) return diff_result unless diff_result[:success] inventory = fetch_file_inventory(before[:metadata], ) return inventory unless inventory[:success] after = (pr_identifier, ) return after unless after[:success] if %w[headRefOid baseRefOid].all? { |key| before[:metadata][key] == after[:metadata][key] } = after[:metadata].merge("files" => inventory[:files]) return { success: true, diff: diff_result[:diff], metadata: , identifier: diff_result[:identifier], parsed: diff_result[:parsed] } end end {success: false, error: "PR head/base changed while fetching diff; retry review on the current SHA"} end |