- Drop fields and functions nothing reads: job and proc tips, fetch time, PR url/author (and their GraphQL fields), spine summaries, diff::commit, unread data attributes, unused CSS and tokens. - One home each for state wording (naming main as the repo does), lane lookup, unseen, outdated, main's tip, the stop wait and the token header. - Narrow visibility; rename render::View to Filter; queue ranking moves to the CLI, its only user. - Token test covers every route; shared checkout snapshot in the fixture. - Client: errors outside Run/Send are toasts, history only after a good load, a drag stays in one file, the agent marker is a ring. - design.md, the skill and AGENTS.md describe what is built.
122 lines
4.0 KiB
Rust
122 lines
4.0 KiB
Rust
mod fixture;
|
|
|
|
use branchgraph::diff::{self, Kind};
|
|
use fixture::{commit, git};
|
|
|
|
fn as_git(lines: &[diff::Line]) -> Vec<String> {
|
|
lines
|
|
.iter()
|
|
.map(|l| match l.kind {
|
|
Kind::Hunk => l.text.clone(),
|
|
Kind::Context => format!(" {}", l.text),
|
|
Kind::Added => format!("+{}", l.text),
|
|
Kind::Removed => format!("-{}", l.text),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// The hunk lines of `git show`, with the function context after `@@` cut.
|
|
fn git_show(d: &std::path::Path, rev: &str) -> Vec<String> {
|
|
git(d, &["show", "--format=", "-U3", rev])
|
|
.lines()
|
|
.skip_while(|l| !l.starts_with("@@"))
|
|
.map(|l| match l.strip_prefix("@@") {
|
|
Some(rest) => format!("@@{} @@", rest.split(" @@").next().unwrap()),
|
|
None => l.to_owned(),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn hunks_match_git_show() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let d = dir.path();
|
|
git(d, &["init", "-q"]);
|
|
let before: String = (1..=40).map(|i| format!("line {i}\n")).collect();
|
|
commit(d, "a.txt", &before, "base");
|
|
let after = before
|
|
.replace("line 2\n", "line two\n")
|
|
.replace("line 6\n", "")
|
|
.replace("line 30\n", "line 30\nnew\n");
|
|
commit(d, "a.txt", &after, "edit");
|
|
commit(d, "b.txt", "added\n", "add b");
|
|
|
|
let repo = gix::open(d).unwrap();
|
|
let id = |rev: &str| gix::ObjectId::from_hex(git(d, &["rev-parse", rev]).as_bytes()).unwrap();
|
|
|
|
let edit = diff::range(&repo, id("HEAD~2"), id("HEAD~1")).unwrap();
|
|
assert_eq!(edit.files.len(), 1);
|
|
let f = &edit.files[0];
|
|
assert_eq!((f.path.as_str(), f.stat), ("a.txt", Some((2, 2))));
|
|
assert_eq!(as_git(f.patch.as_ref().unwrap()), git_show(d, "HEAD~1"));
|
|
|
|
let add = diff::range(&repo, id("HEAD~1"), id("HEAD")).unwrap();
|
|
assert_eq!(
|
|
as_git(add.files[0].patch.as_ref().unwrap()),
|
|
git_show(d, "HEAD")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn diff_stops_at_the_cap() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let d = dir.path();
|
|
git(d, &["init", "-q"]);
|
|
commit(d, "seed", "x\n", "seed");
|
|
let big: String = (0..diff::CAP / 20)
|
|
.map(|i| format!("generated line {i:06}\n"))
|
|
.collect();
|
|
std::fs::write(d.join("small.txt"), "small\n").unwrap();
|
|
commit(d, "big.txt", &big, "big");
|
|
|
|
let repo = gix::open(d).unwrap();
|
|
let id = |rev: &str| gix::ObjectId::from_hex(git(d, &["rev-parse", rev]).as_bytes()).unwrap();
|
|
let out = diff::range(&repo, id("HEAD~1"), id("HEAD")).unwrap();
|
|
assert!(out.truncated);
|
|
let got: Vec<_> = out
|
|
.files
|
|
.iter()
|
|
.map(|f| (f.path.as_str(), f.stat, f.patch.is_some()))
|
|
.collect();
|
|
let lines = (diff::CAP / 20) as u32;
|
|
assert_eq!(
|
|
got,
|
|
[
|
|
("big.txt", Some((lines, 0)), false),
|
|
("small.txt", None, false)
|
|
],
|
|
"the file that crosses the cap keeps its counts; later files are listed only"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn range_gives_every_file_and_one_large_file_loads_alone() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let d = dir.path();
|
|
git(d, &["init", "-q"]);
|
|
commit(d, "seed", "x\n", "seed");
|
|
let base = git(d, &["rev-parse", "HEAD"]);
|
|
for i in 0..5 {
|
|
std::fs::write(d.join(format!("f{i}.txt")), format!("line {i}\n")).unwrap();
|
|
}
|
|
let big: String = (0..diff::CAP / 20)
|
|
.map(|i| format!("generated line {i:06}\n"))
|
|
.collect();
|
|
commit(d, "zz_big.txt", &big, "many files");
|
|
let repo = gix::open(d).unwrap();
|
|
let id = |h: &str| gix::ObjectId::from_hex(h.as_bytes()).unwrap();
|
|
let (from, to) = (id(&base), id(&git(d, &["rev-parse", "HEAD"])));
|
|
|
|
let all = diff::range(&repo, from, to).unwrap();
|
|
let small: Vec<bool> = all
|
|
.files
|
|
.iter()
|
|
.filter(|f| f.path.starts_with('f'))
|
|
.map(|f| f.patch.is_some())
|
|
.collect();
|
|
assert_eq!(small, [true; 5], "every small file comes with its lines");
|
|
|
|
let alone = diff::file(&repo, from, to, "zz_big.txt").unwrap().unwrap();
|
|
assert!(alone.patch.is_some(), "asked for by name, it loads");
|
|
}
|