(&self, request: WorkspaceGitDiffRequest)
| 585 | } |
| 586 | |
| 587 | async fn diff(&self, request: WorkspaceGitDiffRequest) -> Result<String> { |
| 588 | // Two-layered defence against a misbehaving gitserver: |
| 589 | // |
| 590 | // * **Hard memory cap** = `max_diff_bytes * 4` (floor 64 KiB). The |
| 591 | // request streams the body and aborts once this is exceeded, so a |
| 592 | // server returning a 1 GiB diff never gets fully buffered. We |
| 593 | // allow 4× slack over the soft cap so legitimate-but-large diffs |
| 594 | // reach the parser and can be display-truncated below. |
| 595 | // * **Soft display cap** = `max_diff_bytes`. Applied after JSON |
| 596 | // decode: the diff text we hand back to the tool is shortened to |
| 597 | // this many bytes (UTF-8-safe) so callers see a useful preview |
| 598 | // without the model context bloating. |
| 599 | const DIFF_HARD_CAP_FLOOR: u64 = 64 * 1024; |
| 600 | let hard_cap = self |
| 601 | .max_diff_bytes |
| 602 | .saturating_mul(4) |
| 603 | .max(DIFF_HARD_CAP_FLOOR); |
| 604 | |
| 605 | let bytes = self |
| 606 | .post_streamed( |
| 607 | "diff", |
| 608 | &DiffReq { |
| 609 | target: request.target.as_deref(), |
| 610 | }, |
| 611 | hard_cap, |
| 612 | ) |
| 613 | .await?; |
| 614 | let resp: DiffResp = serde_json::from_slice(&bytes) |
| 615 | .map_err(|e| anyhow!("remote git 'diff' response body decode error: {}", e))?; |
| 616 | |
| 617 | if (resp.diff.len() as u64) > self.max_diff_bytes { |
| 618 | tracing::debug!( |
| 619 | "remote git diff body {} bytes exceeds max_diff_bytes {} — \ |
| 620 | client-side display truncation", |
| 621 | resp.diff.len(), |
| 622 | self.max_diff_bytes |
| 623 | ); |
| 624 | let cap = self.max_diff_bytes as usize; |
| 625 | let mut trimmed = resp.diff; |
| 626 | trimmed.truncate(safe_utf8_truncate(&trimmed, cap)); |
| 627 | trimmed.push_str("\n... [truncated by client max_diff_bytes]\n"); |
| 628 | return Ok(trimmed); |
| 629 | } |
| 630 | if resp.truncated { |
| 631 | return Ok(format!("{}\n... [truncated by gitserver]\n", resp.diff)); |
| 632 | } |
| 633 | Ok(resp.diff) |
| 634 | } |
| 635 | |
| 636 | async fn list_remotes(&self) -> Result<Vec<WorkspaceGitRemote>> { |
| 637 | let resp: RemotesResp = self.post_json("remotes", &EmptyReq).await?; |
no test coverage detected