Skip to main content

termesh_git/
diff.rs

1use std::path::PathBuf;
2
3use termesh_core::{GitContextDiff, GitDiffTarget, GitFileDiff, GitResult};
4
5pub fn bounded_diff(
6    path: PathBuf,
7    target: GitDiffTarget,
8    bytes: &[u8],
9    limit: usize,
10) -> GitResult<GitFileDiff> {
11    let (text, truncated) = bounded_text(bytes, limit);
12    Ok(GitFileDiff { path, target, text, truncated })
13}
14
15pub fn bounded_context_diff(
16    index: &[u8],
17    worktree: &[u8],
18    limit: usize,
19) -> GitResult<GitContextDiff> {
20    let (index, index_truncated) = bounded_text(index, limit);
21    let (worktree, worktree_truncated) = bounded_text(worktree, limit);
22    Ok(GitContextDiff { index, worktree, index_truncated, worktree_truncated })
23}
24
25fn bounded_text(bytes: &[u8], limit: usize) -> (String, bool) {
26    let text = String::from_utf8_lossy(bytes);
27    let truncated = bytes.len() > limit || text.len() > limit;
28    if !truncated {
29        return (text.into_owned(), false);
30    }
31
32    let mut end = limit.min(text.len());
33    while end > 0 && !text.is_char_boundary(end) {
34        end -= 1;
35    }
36    (text[..end].to_owned(), true)
37}
38
39#[cfg(test)]
40mod tests {
41    use termesh_core::GitDiffTarget;
42
43    use super::{bounded_context_diff, bounded_diff};
44
45    #[test]
46    fn bounded_diff_keeps_complete_utf8_and_marks_truncation() {
47        // Cutting directly at byte five would split the final beta. This catches a
48        // byte-slice implementation that can create invalid Rust strings.
49        let diff =
50            bounded_diff("src/lib.rs".into(), GitDiffTarget::Worktree, "+αβγ\n".as_bytes(), 5)
51                .unwrap();
52        assert!(diff.truncated);
53        assert!(diff.text.is_char_boundary(diff.text.len()));
54        assert_eq!(diff.text, "+αβ");
55    }
56
57    #[test]
58    fn context_diff_bounds_each_side_independently() {
59        let diff = bounded_context_diff(b"staged-contents", b"worktree-contents", 7).unwrap();
60        assert!(diff.index_truncated);
61        assert!(diff.worktree_truncated);
62        assert_eq!(diff.index, "staged-");
63        assert_eq!(diff.worktree, "worktre");
64    }
65
66    #[test]
67    fn invalid_process_bytes_are_replaced_not_rejected() {
68        let diff =
69            bounded_diff("binary-ish".into(), GitDiffTarget::Index, b"ok\xfftail", 64).unwrap();
70        assert_eq!(diff.text, "ok�tail");
71        assert!(!diff.truncated);
72    }
73}