feat: basic pr diff rendering

This commit is contained in:
2026-05-24 16:44:10 +01:00
parent 1843622540
commit b3e041a257
23 changed files with 903 additions and 353 deletions

View File

@@ -78,8 +78,8 @@ impl query::QueryFn for FetchFileContent {
fn key(&self) -> query::Key {
match &self.reff {
| Some(reff) => format!("repo/fetch/{}/{}/{}", self.repo_slug, self.path, reff).into(),
| None => format!("repo/fetch/{}/{}", self.repo_slug, self.path).into(),
| Some(reff) => format!("repo/fetch/{}/{}/{}", self.repo_slug, self.path, reff).into(),
| None => format!("repo/fetch/{}/{}", self.repo_slug, self.path).into(),
}
}
@@ -94,11 +94,11 @@ impl query::QueryFn for FetchFileContent {
}
let path = match &self.reff {
| Some(reff) => format!(
"/repos/{}/contents/{}?ref={}",
self.repo_slug, self.path, reff
),
| None => format!("/repos/{}/contents/{}", self.repo_slug, self.path),
| Some(reff) => format!(
"/repos/{}/contents/{}?ref={}",
self.repo_slug, self.path, reff
),
| None => format!("/repos/{}/contents/{}", self.repo_slug, self.path),
};
let res = c
@@ -117,9 +117,18 @@ pub struct FetchFileDiff {
pub head: FileRef,
}
#[derive(Debug, thiserror::Error)]
pub enum FetchFileDiffError {
#[error("api error when fetching file diff: {0:?}")]
ApiError(#[from] api::Error),
#[error("invalid utf8 content or unsupported file type")]
InvalidTextContent,
}
impl query::QueryFn for FetchFileDiff {
type Data = util::diff::ContentDiff;
type Error = api::Error;
type Data = Arc<util::diff::ContentDiff>;
type Error = FetchFileDiffError;
type Context = api::QueryContext;
fn key(&self) -> query::Key {
@@ -134,35 +143,108 @@ impl query::QueryFn for FetchFileDiff {
async fn fetch_content(
r: &FileRef,
c: &<FetchFileDiff as query::QueryFn>::Context,
) -> Result<Option<bytes::Bytes>, api::Error> {
let path = match &r.reff {
| Some(reff) => format!("/repos/{}/contents/{}?ref={}", r.repo_slug, r.path, reff),
| None => format!("/repos/{}/contents/{}", r.repo_slug, r.path),
) -> Result<bytes::Bytes, FetchFileDiffError> {
#[cfg(debug_assertions)]
let bytes = if c.should_use_fixtures {
super::mock::fetch_file_content(&r.repo_slug, &r.path, r.reff.as_deref())?
} else {
let path = match &r.reff {
| Some(reff) => {
format!("/repos/{}/contents/{}?ref={}", r.repo_slug, r.path, reff)
}
| None => format!("/repos/{}/contents/{}", r.repo_slug, r.path),
};
let res = c
.github_request(Method::GET, &path)?
.header("Accept", "application/vnd.github.raw+json")
.send()
.await
.map_err(api::Error::HttpError)?;
api::raw_content(res).await?
};
let res = c
.github_request(Method::GET, &path)?
.header("Accept", "application/vnd.github.raw+json")
.send()
.await?;
#[cfg(not(debug_assertions))]
let bytes = {
let path = match &r.reff {
| Some(reff) => {
format!("/repos/{}/contents/{}?ref={}", r.repo_slug, r.path, reff)
}
| None => format!("/repos/{}/contents/{}", r.repo_slug, r.path),
};
res.headers().get("Content-Type");
let res = c
.github_request(Method::GET, &path)?
.header("Accept", "application/vnd.github.raw+json")
.send()
.await
.map_err(api::Error::HttpError)?;
let bytes = api::raw_content(res).await?;
let file::ContentType::Text = file::classify_content(&bytes) else {
return Ok(None);
api::raw_content(res).await?
};
Ok(Some(bytes))
match file::classify_content(&bytes) {
| file::ContentType::Text => Ok(bytes),
| _ => Err(FetchFileDiffError::InvalidTextContent),
}
}
let (old, new) = tokio::join!(fetch_content(&self.base, c), fetch_content(&self.head, c),);
let (old, new) =
tokio::try_join!(fetch_content(&self.base, c), fetch_content(&self.head, c),)?;
match (old, new) {
| (Ok(Some(old)), Ok(Some(new))) => Ok(util::diff::diff_content(old, new)),
| _ => Err(api::Error::MalformedResponse(
"failed to fetch content".to_string(),
)),
}
util::diff::diff_content(old, new)
.map(|diff| Arc::new(diff))
.ok_or(FetchFileDiffError::InvalidTextContent)
}
}
#[cfg(test)]
mod tests {
use crate::query::QueryFn;
use super::*;
#[tokio::test]
async fn fetch_file_diff_uses_repo_file_content_fixtures() {
let pull_request =
super::super::mock::fetch_pull_request(&api::issues::Id::from("PR_kwDONovem84"))
.expect("pull request fixture should parse");
let diff = FetchFileDiff {
base: FileRef {
repo_slug: pull_request.base_repo_slug.clone(),
path: Arc::from("src/query.rs"),
reff: Some(pull_request.base_ref.clone()),
},
head: FileRef {
repo_slug: pull_request.head_repo_slug.clone(),
path: Arc::from("src/query.rs"),
reff: Some(pull_request.head_ref.clone()),
},
}
.run(&api::QueryContext {
http: reqwest::Client::new(),
auth: None,
github: api::GithubCredentials {
base_url: "",
client_id: "",
},
should_use_fixtures: true,
})
.await
.expect("fetch file diff should succeed from fixtures");
assert!(diff.len() > 0);
assert!(
(0..diff.len())
.filter_map(|i| diff.get(i).old_content.as_deref())
.any(|line| { line.contains("pub struct CachedSelection") })
);
assert!(
(0..diff.len())
.filter_map(|i| diff.get(i).new_content.as_deref())
.any(|line| { line.contains("pub struct CachedQueryState") })
);
}
}