Files
branchgraph/tests/fixture/mod.rs
Jean-Gabriel Gill-Couture e03a92c570 Remove dead code, merge duplicated logic, match docs to the code
- 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.
2026-10-09 12:24:24 -04:00

223 lines
7.6 KiB
Rust

#![allow(dead_code)]
//! A fixture clone built with the git CLI: an `origin` bare repository, a
//! clone with remote, local, merged, conflicting and worktree branches.
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
pub struct Fixture {
_dir: tempfile::TempDir,
pub clone: PathBuf,
}
static CLOCK: AtomicU64 = AtomicU64::new(1_790_000_000);
/// Runs git in `dir` with a fixed identity and a clock that moves one minute
/// per call, so commit order is deterministic.
pub fn git(dir: &Path, args: &[&str]) -> String {
let t = CLOCK.fetch_add(60, Ordering::SeqCst);
let date = format!("@{t} +0000");
let out = Command::new("git")
.args(args)
.current_dir(dir)
.env("GIT_AUTHOR_NAME", "alice")
.env("GIT_AUTHOR_EMAIL", "alice@example.com")
.env("GIT_COMMITTER_NAME", "alice")
.env("GIT_COMMITTER_EMAIL", "alice@example.com")
.env("GIT_AUTHOR_DATE", &date)
.env("GIT_COMMITTER_DATE", &date)
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_NOSYSTEM", "1")
.output()
.expect("git runs");
assert!(
out.status.success() || args.first() == Some(&"merge-tree"),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout)
.expect("utf8")
.trim()
.to_owned()
}
/// `git merge-tree` exit code is 1 on conflict; returns the conflicting paths.
pub fn merge_tree_conflicts(dir: &Path, a: &str, b: &str) -> Vec<String> {
let out = git(
dir,
&[
"merge-tree",
"--write-tree",
"--name-only",
"--no-messages",
a,
b,
],
);
let mut paths: Vec<String> = out.lines().skip(1).map(str::to_owned).collect();
paths.sort();
paths.dedup();
paths
}
fn write(dir: &Path, file: &str, body: &str) {
let p = dir.join(file);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, body).unwrap();
}
pub fn commit(dir: &Path, file: &str, body: &str, msg: &str) {
write(dir, file, body);
git(dir, &["add", "-A"]);
git(dir, &["commit", "-q", "-m", msg]);
}
impl Fixture {
pub fn new() -> Fixture {
let dir = tempfile::tempdir().unwrap();
let origin = dir.path().join("origin.git");
let clone = dir.path().join("clone");
git(
dir.path(),
&[
"init",
"-q",
"--bare",
"-b",
"main",
origin.to_str().unwrap(),
],
);
git(
dir.path(),
&[
"clone",
"-q",
origin.to_str().unwrap(),
clone.to_str().unwrap(),
],
);
let c = clone.as_path();
git(c, &["symbolic-ref", "HEAD", "refs/heads/main"]);
commit(c, "app.txt", "one\ntwo\nthree\n", "m1");
commit(c, "Cargo.lock", "lock v1\n", "m2");
git(c, &["push", "-q", "-u", "origin", "main"]);
// Old fork, pushed, two commits.
git(c, &["checkout", "-q", "-b", "feat/old"]);
commit(c, "old.txt", "a\n", "old 1");
commit(c, "old.txt", "a\nb\n", "old 2");
git(c, &["push", "-q", "-u", "origin", "feat/old"]);
// Main moves on: changes line two and the lock file.
git(c, &["checkout", "-q", "main"]);
commit(c, "app.txt", "one\nTWO main\nthree\n", "m3");
commit(c, "Cargo.lock", "lock v2 main\n", "m4");
git(c, &["push", "-q", "origin", "main"]);
// Conflicts with main on app.txt, forked before m3.
git(c, &["checkout", "-q", "-b", "feat/conflict", "HEAD~2"]);
commit(c, "app.txt", "one\nTWO branch\nthree\n", "conflict 1");
git(c, &["push", "-q", "-u", "origin", "feat/conflict"]);
// Conflicts with main only on the lock file.
git(c, &["checkout", "-q", "-b", "deps/lock", "main~1"]);
commit(c, "Cargo.lock", "lock v2 deps\n", "lock bump");
git(c, &["push", "-q", "-u", "origin", "deps/lock"]);
// Merged into main with a merge commit: nothing ahead.
git(c, &["checkout", "-q", "-b", "feat/merged", "main"]);
commit(c, "merged.txt", "m\n", "merged 1");
git(c, &["push", "-q", "-u", "origin", "feat/merged"]);
git(c, &["checkout", "-q", "main"]);
git(
c,
&[
"merge",
"-q",
"--no-ff",
"-m",
"merge feat/merged",
"feat/merged",
],
);
git(c, &["push", "-q", "origin", "main"]);
// Long lane: forty commits, so some fold.
git(c, &["checkout", "-q", "-b", "feat/long", "main"]);
for i in 0..40 {
commit(c, "long.txt", &format!("{i}\n"), &format!("long {i}"));
}
git(c, &["push", "-q", "-u", "origin", "feat/long"]);
// Pushed, then one more local commit: one unpushed.
git(c, &["checkout", "-q", "-b", "feat/unpushed", "main"]);
commit(c, "u.txt", "1\n", "unpushed 1");
git(c, &["push", "-q", "-u", "origin", "feat/unpushed"]);
commit(c, "u.txt", "1\n2\n", "unpushed 2");
// Local only, checked out in a second worktree.
git(c, &["checkout", "-q", "-b", "agent/x", "main"]);
commit(c, "agent.txt", "x\n", "agent 1");
commit(c, "agent.txt", "x\ny\n", "agent 2");
git(c, &["checkout", "-q", "main"]);
let wt = dir.path().join("wt-agent");
git(
c,
&["worktree", "add", "-q", wt.to_str().unwrap(), "agent/x"],
);
// Forks where feat/old forks, then merges main in: it must still
// attach to main in the rail.
git(c, &["checkout", "-q", "-b", "feat/synced", "feat/old~2"]);
commit(c, "synced.txt", "s\n", "synced 1");
git(c, &["merge", "-q", "--no-edit", "main"]);
commit(c, "synced.txt", "s\nt\n", "synced 2");
git(c, &["push", "-q", "-u", "origin", "feat/synced"]);
git(c, &["checkout", "-q", "main"]);
// Renames a file and edits it: the stat must show one renamed file.
git(c, &["checkout", "-q", "-b", "feat/rename", "main"]);
git(c, &["mv", "app.txt", "application.txt"]);
commit(
c,
"application.txt",
"one\nTWO main\nthree\nfour\n",
"rename app",
);
git(c, &["push", "-q", "-u", "origin", "feat/rename"]);
git(c, &["checkout", "-q", "main"]);
// Cut from origin/main, so it tracks main: the usual agent branch.
git(
c,
&["checkout", "-q", "-b", "agent/from-main", "origin/main"],
);
commit(c, "from.txt", "f\n", "from main 1");
git(c, &["checkout", "-q", "main"]);
// No history in common with main, like gh-pages.
git(c, &["checkout", "-q", "--orphan", "gh-pages"]);
git(c, &["rm", "-rq", "--cached", "."]);
commit(c, "index.html", "site\n", "site");
git(c, &["push", "-q", "-u", "origin", "gh-pages"]);
git(c, &["checkout", "-q", "-f", "main"]);
git(c, &["clean", "-fdq"]);
git(c, &["remote", "set-head", "origin", "main"]);
Fixture { _dir: dir, clone }
}
}
/// The user's checkout as git shows it (HEAD, status, index): what
/// branchgraph's own writes must never change.
pub fn checkout_state(c: &Path) -> (String, String, Vec<u8>) {
(
git(c, &["rev-parse", "HEAD"]),
git(c, &["status", "--porcelain"]),
std::fs::read(c.join(".git/index")).unwrap(),
)
}