• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dcdpr / jp / 25502684181

07 May 2026 02:38PM UTC coverage: 66.242% (+0.06%) from 66.18%
25502684181

Pull #622

github

web-flow
Merge 33cbd718f into 4d6432dd3
Pull Request #622: chore(tools): Add `start_line`/`end_line` paging to diff tools

91 of 98 new or added lines in 4 files covered. (92.86%)

3 existing lines in 1 file now uncovered.

25753 of 38877 relevant lines covered (66.24%)

202.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.63
/.config/jp/tools/src/git/diff_commit.rs
1
use std::fmt::Write;
2

3
use camino::{Utf8Path, Utf8PathBuf};
4
use serde_json::{Map, Value};
5

6
use super::diff_filter::{
7
    add_slice_markers, grep_diff, slice_diff, truncate_diff, validate_line_range,
8
};
9
use crate::util::{
10
    OneOrMany, ToolResult, error,
11
    runner::{DuctProcessRunner, ProcessRunner},
12
};
13

14
/// Maximum lines of diff output before truncation kicks in.
15
const MAX_LINES: usize = 500;
16

17
pub(crate) async fn git_diff_commit(
4✔
18
    root: Utf8PathBuf,
4✔
19
    revision: String,
4✔
20
    paths: OneOrMany<String>,
4✔
21
    pattern: Option<String>,
4✔
22
    context: Option<usize>,
4✔
23
    start_line: Option<usize>,
4✔
24
    end_line: Option<usize>,
4✔
25
    options: &Map<String, Value>,
4✔
26
) -> ToolResult {
4✔
27
    let env = super::env_from_options(options);
4✔
28
    let paths = paths.iter().map(AsRef::as_ref).collect::<Vec<_>>();
4✔
29

30
    if let Err(msg) = validate_line_range(start_line, end_line) {
4✔
NEW
31
        return error(msg);
×
32
    }
4✔
33

34
    git_diff_commit_impl(
4✔
35
        &root,
4✔
36
        &revision,
4✔
37
        &paths,
4✔
38
        pattern.as_deref(),
4✔
39
        context,
4✔
40
        start_line,
4✔
41
        end_line,
4✔
42
        &DuctProcessRunner,
4✔
43
        &env,
4✔
44
    )
45
}
4✔
46

47
fn git_diff_commit_impl<R: ProcessRunner>(
12✔
48
    root: &Utf8Path,
12✔
49
    revision: &str,
12✔
50
    paths: &[&str],
12✔
51
    pattern: Option<&str>,
12✔
52
    context: Option<usize>,
12✔
53
    start_line: Option<usize>,
12✔
54
    end_line: Option<usize>,
12✔
55
    runner: &R,
12✔
56
    env: &[(&str, &str)],
12✔
57
) -> ToolResult {
12✔
58
    // `git show <rev> --format= -- <paths>` gives us just the diff for
59
    // specific files, with an empty format to suppress the commit header.
60
    let mut args: Vec<&str> = vec!["show", "--format=", revision, "--"];
12✔
61
    args.extend(paths);
12✔
62

63
    let output = runner.run_with_env("git", &args, root, env)?;
12✔
64

65
    if !output.status.is_success() {
12✔
66
        return error(format!("git show failed: {}", output.stderr.trim()));
1✔
67
    }
11✔
68

69
    let diff = output.stdout.trim_start().to_string();
11✔
70

71
    if diff.is_empty() {
11✔
72
        return Ok("No diff found for the specified revision and paths.".into());
2✔
73
    }
9✔
74

75
    let total_lines = diff.lines().count();
9✔
76
    if let Some(s) = start_line
9✔
77
        && s > total_lines
3✔
78
    {
79
        return error(format!(
1✔
80
            "`start_line` is greater than the number of diff output lines ({total_lines})."
81
        ));
82
    }
8✔
83

84
    let has_range = start_line.is_some() || end_line.is_some();
8✔
85

86
    // Apply slice first if a range was requested. An explicit range bypasses
87
    // the truncation cap — the user is paginating and owns their window size.
88
    let working = if has_range {
8✔
89
        slice_diff(&diff, start_line, end_line)
2✔
90
    } else {
91
        diff
6✔
92
    };
93

94
    // Then either grep (slice-then-grep), pass through (range-only), or
95
    // fall back to the default truncation cap.
96
    let (mut content, note): (String, Option<String>) = if let Some(pat) = pattern {
8✔
97
        let (c, n) = grep_diff(&working, pat, context.unwrap_or(3))?;
3✔
98
        (c.into_owned(), n)
3✔
99
    } else if has_range {
5✔
100
        (working, None)
1✔
101
    } else {
102
        let (c, n) = truncate_diff(&working, MAX_LINES);
4✔
103
        (c.into_owned(), n)
4✔
104
    };
105

106
    // Slice markers are added last so they survive grep filtering.
107
    if has_range {
8✔
108
        add_slice_markers(&mut content, start_line, end_line);
2✔
109
    }
6✔
110

111
    let mut result = String::new();
8✔
112
    write!(result, "```diff\n{}\n```", content.trim_end())?;
8✔
113
    if let Some(note) = note {
8✔
114
        writeln!(result, "\n\n{note}\n")?;
4✔
115
    }
4✔
116
    Ok(result.into())
8✔
117
}
12✔
118

119
#[cfg(test)]
120
#[path = "diff_commit_tests.rs"]
121
mod tests;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc