Skip to content

Commit 308ce51

Browse files
authored
Merge pull request #2463 from guantw/fix/gitcode-files-response-unified
fix(review): load GitCode PR files as one bounded response
2 parents a10728f + 5101de3 commit 308ce51

1 file changed

Lines changed: 195 additions & 60 deletions

File tree

src/crates/services/services-integrations/src/review_platform.rs

Lines changed: 195 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,62 +1844,63 @@ async fn gitcode_review_target_parts(
18441844
);
18451845
let initial_detail =
18461846
send_bounded_json(gitcode_request(client.clone(), &base, ctx.token.as_deref())).await?;
1847-
let token = ctx.token.clone();
18481847
let files_url = format!("{}/files", base);
1849-
let files = fetch_bounded_paginated_array(
1850-
|page| {
1851-
let page = page.to_string();
1852-
gitcode_request(client.clone(), &files_url, token.as_deref())
1853-
.query(&[("per_page", "100"), ("page", &page)])
1854-
},
1855-
github_next_page,
1856-
MAX_REVIEW_TARGET_LIST_ITEMS,
1857-
)
1848+
let files_response = send_bounded_gitcode_files_response(gitcode_request(
1849+
client.clone(),
1850+
&files_url,
1851+
ctx.token.as_deref(),
1852+
))
18581853
.await?;
1854+
let files = files_response
1855+
.value
1856+
.as_array()
1857+
.ok_or_else(|| {
1858+
ReviewPlatformError::Parse(
1859+
"GitCode pull request files response was not an array".to_string(),
1860+
)
1861+
})?
1862+
.iter()
1863+
.take(MAX_REVIEW_TARGET_LIST_ITEMS)
1864+
.map(gitcode_file_from_value)
1865+
.collect::<Vec<_>>();
18591866
let confirmed_detail =
18601867
send_bounded_json(gitcode_request(client, &base, ctx.token.as_deref())).await?;
18611868
let initial_pull_request = gitcode_pull_request_from_value(&initial_detail);
1862-
let confirmed_pull_request = gitcode_pull_request_from_value(&confirmed_detail);
1869+
let mut confirmed_pull_request = gitcode_pull_request_from_value(&confirmed_detail);
18631870
ensure_pull_request_revisions_stable(&initial_pull_request, &confirmed_pull_request)?;
1864-
Ok((
1865-
confirmed_pull_request,
1866-
array_items(&files)
1867-
.iter()
1868-
.map(gitcode_file_from_value)
1869-
.collect(),
1870-
))
1871+
apply_gitcode_review_target_file_stats(&mut confirmed_pull_request, &files);
1872+
Ok((confirmed_pull_request, files))
18711873
}
18721874

18731875
async fn gitcode_review_file_parts(
18741876
ctx: &ProviderContext,
18751877
pull_request_id: &str,
18761878
file_path: &str,
1877-
file_page_hint: Option<u32>,
1879+
_file_page_hint: Option<u32>,
18781880
) -> Result<(ReviewPlatformPullRequest, Vec<ReviewPlatformFile>), ReviewPlatformError> {
18791881
let client = http_client()?;
18801882
let base = format!(
18811883
"{}/repos/{}/{}/pulls/{}",
18821884
ctx.api_base_url, ctx.remote.owner, ctx.remote.repository_name, pull_request_id
18831885
);
1884-
let token = ctx.token.clone();
18851886
let files_url = format!("{}/files", base);
1886-
let file = fetch_bounded_paginated_file(
1887-
|page| {
1888-
let page = page.to_string();
1889-
gitcode_request(client.clone(), &files_url, token.as_deref())
1890-
.query(&[("per_page", "100"), ("page", &page)])
1891-
},
1892-
github_next_page,
1893-
file_page_hint.unwrap_or(1),
1894-
if file_page_hint.is_some() {
1895-
100
1896-
} else {
1897-
MAX_REVIEW_TARGET_LIST_ITEMS
1898-
},
1899-
file_path,
1900-
gitcode_file_from_value,
1901-
)
1902-
.await?;
1887+
let files = send_bounded_gitcode_files_response(gitcode_request(
1888+
client.clone(),
1889+
&files_url,
1890+
ctx.token.as_deref(),
1891+
))
1892+
.await?
1893+
.value;
1894+
let file = files
1895+
.as_array()
1896+
.ok_or_else(|| {
1897+
ReviewPlatformError::Parse(
1898+
"GitCode pull request files response was not an array".to_string(),
1899+
)
1900+
})?
1901+
.iter()
1902+
.map(gitcode_file_from_value)
1903+
.find(|file| file.path == file_path || file.old_path.as_deref() == Some(file_path));
19031904
let detail = send_bounded_json(gitcode_request(client, &base, ctx.token.as_deref())).await?;
19041905
Ok((
19051906
gitcode_pull_request_from_value(&detail),
@@ -2851,7 +2852,7 @@ async fn gitcode_pull_request_detail_page(
28512852

28522853
match section {
28532854
ReviewPlatformDetailSection::Overview => {
2854-
if let Ok(response) = send_json_response(gitcode_request(
2855+
if let Ok(response) = send_bounded_gitcode_files_response(gitcode_request(
28552856
client.clone(),
28562857
&format!("{}/files", base),
28572858
ctx.token.as_deref(),
@@ -2866,22 +2867,21 @@ async fn gitcode_pull_request_detail_page(
28662867
ci = slice_page(ci, pagination);
28672868
}
28682869
ReviewPlatformDetailSection::Files => {
2869-
if let Ok(response) = fetch_array_page(
2870-
gitcode_request(
2871-
client.clone(),
2872-
&format!("{}/files", base),
2873-
ctx.token.as_deref(),
2874-
),
2875-
pagination,
2876-
)
2870+
if let Ok(response) = send_bounded_gitcode_files_response(gitcode_request(
2871+
client.clone(),
2872+
&format!("{}/files", base),
2873+
ctx.token.as_deref(),
2874+
))
28772875
.await
28782876
{
2879-
apply_gitcode_pull_request_change_stats(&mut pull_request, &response);
2880-
section_pagination = pagination_from_response(&response, pagination);
2881-
files = array_items(&response.value)
2882-
.iter()
2883-
.map(gitcode_file_from_value)
2884-
.collect();
2877+
if let Some(values) = response.value.as_array() {
2878+
apply_gitcode_pull_request_change_stats(&mut pull_request, &response);
2879+
section_pagination = gitcode_files_pagination(pagination, values.len());
2880+
files = slice_page(
2881+
values.iter().map(gitcode_file_from_value).collect(),
2882+
pagination,
2883+
);
2884+
}
28852885
}
28862886
}
28872887
ReviewPlatformDetailSection::Commits => {
@@ -3003,7 +3003,7 @@ impl ReviewProvider for GitcodeProvider {
30033003
let detail =
30043004
send_json(gitcode_request(client.clone(), &base, ctx.token.as_deref())).await?;
30053005
let files_url = format!("{}/files", base);
3006-
let files_response = send_json_response(gitcode_request(
3006+
let files_response = send_bounded_gitcode_files_response(gitcode_request(
30073007
client.clone(),
30083008
&files_url,
30093009
ctx.token.as_deref(),
@@ -3242,6 +3242,15 @@ fn review_http_error(error: ReviewHttpError) -> ReviewPlatformError {
32423242
}
32433243
}
32443244

3245+
fn gitcode_files_http_error(error: ReviewHttpError) -> ReviewPlatformError {
3246+
match error {
3247+
ReviewHttpError::ResponseTooLarge { limit_bytes } => ReviewPlatformError::Api(format!(
3248+
"GitCode pull request files response exceeded the {limit_bytes}-byte limit"
3249+
)),
3250+
error => review_http_error(error),
3251+
}
3252+
}
3253+
32453254
async fn send_json(request: ReviewHttpRequest) -> Result<Value, ReviewPlatformError> {
32463255
send_review_json(request).await.map_err(review_http_error)
32473256
}
@@ -3269,6 +3278,14 @@ async fn send_bounded_json_response(
32693278
.map_err(review_http_error)
32703279
}
32713280

3281+
async fn send_bounded_gitcode_files_response(
3282+
request: ReviewHttpRequest,
3283+
) -> Result<JsonResponse, ReviewPlatformError> {
3284+
send_review_json_response_bounded(request, MAX_REVIEW_TARGET_RESPONSE_BYTES)
3285+
.await
3286+
.map_err(gitcode_files_http_error)
3287+
}
3288+
32723289
async fn send_bounded_text(
32733290
request: ReviewHttpRequest,
32743291
max_bytes: usize,
@@ -3432,6 +3449,17 @@ fn pagination_from_total(
34323449
}
34333450
}
34343451

3452+
fn gitcode_files_pagination(
3453+
pagination: PullRequestPagination,
3454+
available_files: usize,
3455+
) -> ReviewPlatformPagination {
3456+
let mut result = pagination_from_total(pagination, available_files);
3457+
if available_files >= GITCODE_PULL_REQUEST_FILES_RESPONSE_LIMIT {
3458+
result.total = None;
3459+
}
3460+
result
3461+
}
3462+
34353463
fn slice_page<T>(items: Vec<T>, pagination: PullRequestPagination) -> Vec<T> {
34363464
let start = pagination
34373465
.page
@@ -3536,7 +3564,8 @@ async fn enrich_gitcode_pull_request_change_stats(
35363564
let token = ctx.token.clone();
35373565
async move {
35383566
if let Ok(response) =
3539-
send_json_response(gitcode_request(client, &url, token.as_deref())).await
3567+
send_bounded_gitcode_files_response(gitcode_request(client, &url, token.as_deref()))
3568+
.await
35403569
{
35413570
apply_gitcode_pull_request_change_stats(&mut pull_request, &response);
35423571
}
@@ -6646,17 +6675,37 @@ fn gitcode_file_from_value(value: &Value) -> ReviewPlatformFile {
66466675
value_string(value, "filename"),
66476676
value_string(value, "new_path"),
66486677
]),
6649-
old_path: value
6650-
.get("previous_filename")
6651-
.and_then(Value::as_str)
6652-
.map(str::to_string),
6653-
status: file_status(&value_string(value, "status")),
6678+
old_path: optional_string(value, "old_path")
6679+
.or_else(|| optional_string(value, "previous_filename")),
6680+
status: gitcode_file_status(value),
66546681
additions: value_i64(value, "additions") as i32,
66556682
deletions: value_i64(value, "deletions") as i32,
6656-
patch: optional_string(value, "patch").or_else(|| optional_string(value, "diff")),
6683+
patch: gitcode_patch_from_value(value),
6684+
}
6685+
}
6686+
6687+
fn gitcode_file_status(value: &Value) -> ReviewFileStatus {
6688+
if value_bool(value, "new_file") {
6689+
ReviewFileStatus::Added
6690+
} else if value_bool(value, "deleted_file") {
6691+
ReviewFileStatus::Deleted
6692+
} else if value_bool(value, "renamed_file") {
6693+
ReviewFileStatus::Renamed
6694+
} else {
6695+
file_status(&value_string(value, "status"))
66576696
}
66586697
}
66596698

6699+
fn gitcode_patch_from_value(value: &Value) -> Option<String> {
6700+
optional_string(value, "patch")
6701+
.or_else(|| {
6702+
value
6703+
.get("patch")
6704+
.and_then(|patch| optional_string(patch, "diff"))
6705+
})
6706+
.or_else(|| optional_string(value, "diff"))
6707+
}
6708+
66606709
fn gitlab_files(value: &Value) -> Vec<ReviewPlatformFile> {
66616710
value
66626711
.get("changes")
@@ -7119,6 +7168,15 @@ fn apply_files_stats(pull_request: &mut ReviewPlatformPullRequest, files: &[Revi
71197168
pull_request.deletions = deletions;
71207169
}
71217170

7171+
fn apply_gitcode_review_target_file_stats(
7172+
pull_request: &mut ReviewPlatformPullRequest,
7173+
files: &[ReviewPlatformFile],
7174+
) {
7175+
apply_files_stats(pull_request, files);
7176+
pull_request.changed_files = i32::try_from(files.len()).unwrap_or(i32::MAX);
7177+
pull_request.changed_file_count_known = files.len() < MAX_REVIEW_TARGET_LIST_ITEMS;
7178+
}
7179+
71227180
async fn fetch_bounded_paginated_array<F>(
71237181
mut build_request: F,
71247182
next_page: fn(&ReviewHttpHeaders, u32) -> Option<u32>,
@@ -8245,6 +8303,83 @@ mod tests {
82458303
assert_eq!(pull_request.deletions, 22);
82468304
}
82478305

8306+
#[test]
8307+
fn gitcode_file_maps_nested_patch_and_change_flags() {
8308+
let file = gitcode_file_from_value(&json!({
8309+
"filename": "src/new.rs",
8310+
"old_path": "src/old.rs",
8311+
"status": null,
8312+
"new_file": false,
8313+
"renamed_file": true,
8314+
"deleted_file": false,
8315+
"additions": 1,
8316+
"deletions": 1,
8317+
"patch": { "diff": "@@ -1 +1 @@\n-old\n+new" }
8318+
}));
8319+
8320+
assert_eq!(file.path, "src/new.rs");
8321+
assert_eq!(file.old_path.as_deref(), Some("src/old.rs"));
8322+
assert_eq!(file.status, ReviewFileStatus::Renamed);
8323+
assert_eq!(file.patch.as_deref(), Some("@@ -1 +1 @@\n-old\n+new"));
8324+
assert!(file_has_complete_patch(&file));
8325+
}
8326+
8327+
#[test]
8328+
fn gitcode_capped_files_pagination_does_not_claim_exact_total() {
8329+
let pagination = gitcode_files_pagination(
8330+
PullRequestPagination {
8331+
page: 60,
8332+
per_page: 50,
8333+
},
8334+
GITCODE_PULL_REQUEST_FILES_RESPONSE_LIMIT,
8335+
);
8336+
8337+
assert_eq!(pagination.total, None);
8338+
assert!(!pagination.has_next);
8339+
}
8340+
8341+
#[test]
8342+
fn gitcode_review_target_stats_use_the_same_thousand_file_budget() {
8343+
let mut pull_request = gitcode_pull_request_from_value(&json!({
8344+
"number": 5,
8345+
"title": "large change",
8346+
"state": "open",
8347+
"added_lines": 9_999,
8348+
"removed_lines": 8_888,
8349+
"changes_count": "2500"
8350+
}));
8351+
let files = vec![
8352+
ReviewPlatformFile {
8353+
path: "src/file.rs".to_string(),
8354+
old_path: None,
8355+
status: ReviewFileStatus::Modified,
8356+
additions: 1,
8357+
deletions: 2,
8358+
patch: Some("@@ -1 +1 @@\n-old\n+new".to_string()),
8359+
};
8360+
MAX_REVIEW_TARGET_LIST_ITEMS
8361+
];
8362+
8363+
apply_gitcode_review_target_file_stats(&mut pull_request, &files);
8364+
8365+
assert_eq!(pull_request.changed_files, 1_000);
8366+
assert!(!pull_request.changed_file_count_known);
8367+
assert_eq!(pull_request.additions, 1_000);
8368+
assert_eq!(pull_request.deletions, 2_000);
8369+
}
8370+
8371+
#[test]
8372+
fn gitcode_files_response_too_large_reports_explicit_reason() {
8373+
let error = gitcode_files_http_error(ReviewHttpError::ResponseTooLarge {
8374+
limit_bytes: MAX_REVIEW_TARGET_RESPONSE_BYTES,
8375+
});
8376+
8377+
assert_eq!(
8378+
error.to_string(),
8379+
"Provider API failed: GitCode pull request files response exceeded the 4194304-byte limit"
8380+
);
8381+
}
8382+
82488383
#[test]
82498384
fn gitcode_file_response_overrides_incomplete_list_stats() {
82508385
let mut pull_request = gitcode_pull_request_from_value(&json!({

0 commit comments

Comments
 (0)