mod support;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use serde_json::Value;
use support::git_repo::GitRepo;
use support::pointbreak;
#[test]
fn revision_list_runs_at_top_level() {
let repo = modified_repo();
pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]);
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout);
assert_eq!(json["schema"], "pointbreak.review-revision-list");
}
#[test]
fn revision_list_object_filter_resolves_a_short_id() {
let repo = modified_repo();
pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]);
let path = repo.path().to_str().unwrap();
let listed = parse_json(&pointbreak(["revision", "list", "--repo", path]).stdout);
let object_id = listed["entries"][0]["objectId"]
.as_str()
.unwrap()
.to_owned();
let digest = object_id.rsplit_once("sha256:").unwrap().1;
let prefixed_short = format!("obj:{}", &digest[..8]);
let filtered = pointbreak([
"revision",
"list",
"--repo",
path,
"--object",
&prefixed_short,
]);
assert!(
filtered.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&filtered.stderr)
);
let json = parse_json(&filtered.stdout);
assert_eq!(json["revisionCount"], 1);
assert_eq!(json["entries"][0]["objectId"], object_id);
}
#[test]
fn revision_list_emits_v1_json_with_freshness_metadata() {
let repo = modified_repo();
let capture =
parse_json(&pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]).stdout);
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout);
assert_eq!(json["schema"], "pointbreak.review-revision-list");
assert_eq!(json["version"], 1);
assert!(
json["eventSetHash"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
assert_eq!(json["eventCount"], 2);
assert_eq!(json["revisionCount"], 1);
let entry = &json["entries"][0];
assert_eq!(entry["revisionId"], capture["revision"]["id"]);
assert!(!entry["capturedAt"].as_str().unwrap().is_empty());
assert!(entry["revisionId"].as_str().unwrap().starts_with("rev:"));
assert!(entry.get("summary").is_none());
assert!(entry["objectId"].as_str().unwrap().starts_with("obj:"));
assert!(entry["source"].is_object());
assert!(entry["base"].is_object());
assert!(entry["target"].is_object());
assert!(
entry["objectArtifactContentHash"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
}
#[test]
fn revision_list_does_not_expose_storage_paths() {
let repo = modified_repo();
pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]);
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
let stdout = String::from_utf8_lossy(&output.stdout);
let json = parse_json(&output.stdout);
assert!(!stdout.contains(".pointbreak/data/events"));
assert!(!stdout.contains("artifacts/"));
assert!(json.get("statePath").is_none());
assert!(json["entries"][0].get("payloadHash").is_none());
assert!(json["entries"][0].get("eventId").is_none());
}
#[test]
fn revision_list_json_pretty_prints() {
let repo = modified_repo();
pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]);
let output = pointbreak([
"revision",
"list",
"--repo",
repo.path().to_str().unwrap(),
"--format",
"json-pretty",
]);
assert!(String::from_utf8_lossy(&output.stdout).starts_with("{\n"));
}
#[test]
fn revision_list_returns_multiple_entries_in_capture_order() {
let repo = modified_repo();
let first =
parse_json(&pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]).stdout);
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
let second =
parse_json(&pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]).stdout);
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
let json = parse_json(&output.stdout);
let entries = json["entries"].as_array().unwrap();
assert_ne!(first["revision"]["id"], second["revision"]["id"]);
assert_eq!(json["revisionCount"], 2);
assert_eq!(entries.len(), 2);
let ids: Vec<&str> = entries
.iter()
.map(|entry| entry["revisionId"].as_str().unwrap())
.collect();
assert!(ids.contains(&first["revision"]["id"].as_str().unwrap()));
assert!(ids.contains(&second["revision"]["id"].as_str().unwrap()));
assert!(
entries[0]["capturedAt"].as_str().unwrap() <= entries[1]["capturedAt"].as_str().unwrap()
);
}
#[test]
fn revision_list_succeeds_without_events() {
let repo = GitRepo::new();
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
let json = parse_json(&output.stdout);
assert!(output.status.success());
assert_eq!(json["eventCount"], 0);
assert_eq!(json["revisionCount"], 0);
assert!(json["entries"].as_array().unwrap().is_empty());
}
#[test]
fn revision_list_reads_capture_from_the_shared_store_after_seed_worktree_removed() {
let fixture = CloneWorktreeFixture::new();
fs::write(fixture.seed.join("README.md"), "changed in seed\n").unwrap();
let capture =
parse_json(&pointbreak(["capture", "--repo", fixture.seed.to_str().unwrap()]).stdout);
run_git_os(
fixture.main.path(),
[
OsString::from("worktree"),
OsString::from("remove"),
OsString::from("--force"),
fixture.seed.as_os_str().to_owned(),
],
);
let reader = fixture.add_worktree("reader");
assert!(!reader.join(".pointbreak/data/events").exists());
let output = pointbreak(["revision", "list", "--repo", reader.to_str().unwrap()]);
assert!(
output.status.success(),
"list stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
let json = parse_json(stdout.as_bytes());
assert_eq!(json["eventCount"], 2);
assert_eq!(json["revisionCount"], 1);
assert_eq!(json["entries"][0]["revisionId"], capture["revision"]["id"]);
assert!(json["diagnostics"].as_array().unwrap().is_empty());
assert!(!stdout.contains(".git"));
assert!(!stdout.contains(".pointbreak/data"));
}
#[test]
fn revision_list_omits_ambient_ambiguous_current_diagnostic_from_shared_store() {
let fixture = CloneWorktreeFixture::new();
fs::write(fixture.seed.join("README.md"), "changed once\n").unwrap();
let first =
parse_json(&pointbreak(["capture", "--repo", fixture.seed.to_str().unwrap()]).stdout);
fs::write(fixture.seed.join("README.md"), "changed twice\n").unwrap();
let second =
parse_json(&pointbreak(["capture", "--repo", fixture.seed.to_str().unwrap()]).stdout);
let reader = fixture.add_worktree("reader");
let output = pointbreak(["revision", "list", "--repo", reader.to_str().unwrap()]);
assert!(
output.status.success(),
"list stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout);
let ids = json["entries"]
.as_array()
.unwrap()
.iter()
.map(|entry| entry["revisionId"].as_str().unwrap())
.collect::<Vec<_>>();
assert_ne!(first["revision"]["id"], second["revision"]["id"]);
assert_eq!(json["eventCount"], 4);
assert_eq!(json["revisionCount"], 2);
assert!(ids.contains(&first["revision"]["id"].as_str().unwrap()));
assert!(ids.contains(&second["revision"]["id"].as_str().unwrap()));
assert!(
!json["diagnostics"]
.as_array()
.unwrap()
.iter()
.any(|diagnostic| {
diagnostic["code"].as_str() == Some("ambiguous_current_revision")
}),
"routine Revision list should not emit ambient current ambiguity diagnostics"
);
}
#[test]
fn unit_list_renders_commit_range_source_without_paths() {
let repo = support::committed_repo();
pointbreak([
"capture",
"--repo",
repo.path().to_str().unwrap(),
"--base",
"HEAD~1",
]);
let output = pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json = parse_json(&output.stdout);
let entry = &json["entries"][0];
assert_eq!(entry["source"]["kind"], "git_commit_range");
assert_eq!(entry["source"]["mode"], "base_tree_to_target_tree");
assert_eq!(entry["base"]["kind"], "git_commit");
assert_eq!(entry["target"]["kind"], "git_commit");
assert!(
!stdout.contains("worktreeRoot"),
"range capture unit list must not expose a worktree path"
);
}
#[test]
fn unit_list_shows_unreachable_revisions_by_default_and_filters_explicitly() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.git(["checkout", "-b", "feature"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("feature work");
let orphan = parse_json(
&pointbreak([
"capture",
"--repo",
repo.path().to_str().unwrap(),
"--base",
"main",
])
.stdout,
);
let orphan_id = orphan["revision"]["id"].as_str().unwrap().to_owned();
repo.git(["checkout", "main"]);
repo.git(["branch", "-D", "feature"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
let floating =
parse_json(&pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]).stdout);
let floating_id = floating["revision"]["id"].as_str().unwrap().to_owned();
assert_ne!(orphan_id, floating_id);
let default_ids = unit_list_ids(&repo, &[]);
assert!(default_ids.contains(&floating_id));
assert!(default_ids.contains(&orphan_id));
let all_ids = unit_list_ids(&repo, &["--all"]);
assert_eq!(all_ids, default_ids);
assert_eq!(
unit_list_ids(&repo, &["--unreachable"]),
vec![orphan_id.clone()]
);
assert_eq!(
unit_list_ids(&repo, &["--orphans"]),
vec![orphan_id.clone()]
);
assert_eq!(
unit_list_ids(&repo, &["--unreachable", "--all"]),
vec![orphan_id]
);
}
#[test]
fn unit_list_keeps_revision_after_capture_target_is_amended() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.git(["checkout", "-b", "feature"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("captured change");
let captured = parse_json(
&pointbreak([
"capture",
"--repo",
repo.path().to_str().unwrap(),
"--base",
"main",
])
.stdout,
);
let revision_id = captured["revision"]["id"].as_str().unwrap().to_owned();
let captured_target = captured["revision"]["target"]["commitOid"]
.as_str()
.unwrap()
.to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
repo.git(["add", "--all"]);
repo.git(["commit", "--amend", "--no-edit"]);
let amended_target = repo.git(["rev-parse", "HEAD"]).stdout.trim().to_owned();
assert_ne!(captured_target, amended_target);
assert!(
repo.git([
"for-each-ref",
&format!("--contains={captured_target}"),
"--format=%(refname)",
])
.stdout
.trim()
.is_empty(),
"the captured target must be unreachable from live refs"
);
let listed = parse_json(
&pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]).stdout,
);
let entry = listed["entries"]
.as_array()
.unwrap()
.iter()
.find(|entry| entry["revisionId"] == revision_id)
.unwrap_or_else(|| panic!("amended capture target removed the revision: {listed}"));
assert_eq!(
entry["mergeStatus"], "unreachable",
"an amended-away capture reads unreachable, never orphaned"
);
let shown = pointbreak([
"revision",
"show",
"--repo",
repo.path().to_str().unwrap(),
&revision_id,
]);
assert!(
shown.status.success(),
"direct revision read must still succeed: {}",
String::from_utf8_lossy(&shown.stderr)
);
assert_eq!(parse_json(&shown.stdout)["revision"]["id"], revision_id);
}
#[test]
fn unit_list_keeps_revision_after_capture_target_is_pruned() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.git(["checkout", "-b", "feature"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("captured change");
let captured = parse_json(
&pointbreak([
"capture",
"--repo",
repo.path().to_str().unwrap(),
"--base",
"main",
])
.stdout,
);
let revision_id = captured["revision"]["id"].as_str().unwrap().to_owned();
let captured_target = captured["revision"]["target"]["commitOid"]
.as_str()
.unwrap()
.to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
repo.git(["add", "--all"]);
repo.git(["commit", "--amend", "--no-edit"]);
repo.git(["reflog", "expire", "--expire=now", "--all"]);
repo.git(["prune", "--expire=now"]);
let gone = std::process::Command::new("git")
.args(["cat-file", "-e", &captured_target])
.current_dir(repo.path())
.status()
.unwrap();
assert!(!gone.success(), "the captured target object must be pruned");
assert_eq!(unit_list_merge_status(&repo, &revision_id), "unreachable");
assert!(unit_list_ids(&repo, &["--unreachable"]).contains(&revision_id));
}
#[test]
fn unit_list_attaches_merge_status_and_accepts_integration_and_worktree_flags() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("second");
let repo_arg = repo.path().to_str().unwrap();
let range = parse_json(&pointbreak(["capture", "--repo", repo_arg, "--base", "HEAD~1"]).stdout);
let range_id = range["revision"]["id"].as_str().unwrap().to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
let worktree = parse_json(&pointbreak(["capture", "--repo", repo_arg]).stdout);
let worktree_id = worktree["revision"]["id"].as_str().unwrap().to_owned();
let default = parse_json(&pointbreak(["revision", "list", "--repo", repo_arg]).stdout);
let status_for = |id: &str| -> String {
default["entries"]
.as_array()
.unwrap()
.iter()
.find(|entry| entry["revisionId"] == id)
.unwrap()["mergeStatus"]
.as_str()
.unwrap()
.to_owned()
};
assert_eq!(status_for(&range_id), "merged");
assert_eq!(status_for(&worktree_id), "unknown");
let scoped = pointbreak([
"revision",
"list",
"--repo",
repo_arg,
"--integration-ref",
"refs/heads/main",
"--worktree",
repo_arg,
]);
assert!(
scoped.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&scoped.stderr)
);
let scoped_ids: Vec<String> = parse_json(&scoped.stdout)["entries"]
.as_array()
.unwrap()
.iter()
.map(|entry| entry["revisionId"].as_str().unwrap().to_owned())
.collect();
assert!(scoped_ids.contains(&worktree_id));
}
fn unit_list_merge_status(repo: &GitRepo, revision_id: &str) -> String {
let listed = parse_json(
&pointbreak(["revision", "list", "--repo", repo.path().to_str().unwrap()]).stdout,
);
listed["entries"]
.as_array()
.unwrap()
.iter()
.find(|entry| entry["revisionId"] == revision_id)
.unwrap_or_else(|| panic!("revision {revision_id} not listed: {listed}"))["mergeStatus"]
.as_str()
.unwrap()
.to_owned()
}
#[test]
fn unit_list_reads_merged_for_landed_capture_with_deleted_source_branch() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
let repo_arg = repo.path().to_str().unwrap();
repo.git(["checkout", "-b", "feature"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("change");
let captured =
parse_json(&pointbreak(["capture", "--repo", repo_arg, "--base", "main"]).stdout);
let revision_id = captured["revision"]["id"].as_str().unwrap().to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
repo.commit_all("follow-up");
repo.git(["checkout", "main"]);
repo.git(["merge", "--ff-only", "feature"]);
let record = pointbreak([
"association",
"record",
"--repo",
repo_arg,
"--track",
"agent:codex",
"--revision",
&revision_id,
"--commit",
"HEAD",
]);
assert!(
record.status.success(),
"association record failed: {}",
String::from_utf8_lossy(&record.stderr)
);
repo.git(["branch", "-D", "feature"]);
assert_eq!(unit_list_merge_status(&repo, &revision_id), "merged");
repo.git(["checkout", "-b", "unmerged"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 4 }\n");
repo.commit_all("unmerged change");
let open = parse_json(&pointbreak(["capture", "--repo", repo_arg, "--base", "main"]).stdout);
let open_id = open["revision"]["id"].as_str().unwrap().to_owned();
assert_eq!(unit_list_merge_status(&repo, &open_id), "open");
}
#[test]
fn unit_list_keeps_broad_merge_status_without_a_detectable_default_branch() {
let repo = support::committed_repo();
repo.git(["branch", "-m", "main", "trunk"]);
let repo_arg = repo.path().to_str().unwrap();
let range = parse_json(&pointbreak(["capture", "--repo", repo_arg, "--base", "HEAD~1"]).stdout);
let range_id = range["revision"]["id"].as_str().unwrap().to_owned();
assert_eq!(unit_list_merge_status(&repo, &range_id), "open");
}
#[test]
fn unit_list_reads_side_branch_only_landing_as_open_and_never_unreachable() {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
let repo_arg = repo.path().to_str().unwrap();
repo.git(["checkout", "-b", "develop"]);
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo.commit_all("change");
let captured =
parse_json(&pointbreak(["capture", "--repo", repo_arg, "--base", "main"]).stdout);
let revision_id = captured["revision"]["id"].as_str().unwrap().to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
repo.commit_all("develop advance");
repo.git(["checkout", "main"]);
assert_eq!(unit_list_merge_status(&repo, &revision_id), "open");
assert!(!unit_list_ids(&repo, &["--unreachable"]).contains(&revision_id));
}
fn unit_list_ids(repo: &GitRepo, extra: &[&str]) -> Vec<String> {
let mut args: Vec<String> = vec![
"revision".to_owned(),
"list".to_owned(),
"--repo".to_owned(),
repo.path().to_str().unwrap().to_owned(),
];
args.extend(extra.iter().map(|flag| (*flag).to_owned()));
let output = pointbreak(args);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
parse_json(&output.stdout)["entries"]
.as_array()
.unwrap()
.iter()
.map(|entry| entry["revisionId"].as_str().unwrap().to_owned())
.collect()
}
#[test]
fn revision_list_filter_by_is_superseded() {
let repo = modified_repo();
let path = repo.path().to_str().unwrap();
let first = parse_json(&pointbreak(["capture", "--repo", path]).stdout);
let predecessor = first["revision"]["id"].as_str().unwrap().to_owned();
repo.write("src/lib.rs", "pub fn value() -> u32 { 3 }\n");
pointbreak(["capture", "--repo", path, "--supersedes", &predecessor]);
let json = parse_json(
&pointbreak([
"revision",
"list",
"--repo",
path,
"--filter",
"is:superseded",
])
.stdout,
);
let ids: Vec<&str> = json["entries"]
.as_array()
.unwrap()
.iter()
.map(|e| e["revisionId"].as_str().unwrap())
.collect();
assert_eq!(
ids,
vec![predecessor.as_str()],
"only the superseded revision matches"
);
assert_eq!(json["revisionCount"], 1);
}
#[test]
fn revision_list_filter_by_tag_key() {
let repo = modified_repo();
let path = repo.path().to_str().unwrap();
pointbreak(["capture", "--repo", path]);
pointbreak([
"observation",
"add",
"--repo",
path,
"--track",
"agent:codex",
"--title",
"Landed",
"--tag",
"state-change:landed",
]);
let json = parse_json(
&pointbreak([
"revision",
"list",
"--repo",
path,
"--filter",
"tag:state-change",
])
.stdout,
);
assert_eq!(json["revisionCount"], 1);
}
#[test]
fn revision_list_filter_rejects_type_qualifier_on_revision_surface() {
let repo = modified_repo();
let path = repo.path().to_str().unwrap();
pointbreak(["capture", "--repo", path]);
let out = pointbreak([
"revision",
"list",
"--repo",
path,
"--filter",
"type:observation",
]);
assert!(
!out.status.success(),
"type: is unsupported on the revision surface"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("type"),
"the message names the qualifier: {stderr}"
);
}
#[test]
fn revision_list_flagless_output_unchanged() {
let repo = modified_repo();
let path = repo.path().to_str().unwrap();
pointbreak(["capture", "--repo", path]);
pointbreak([
"observation",
"add",
"--repo",
path,
"--track",
"agent:codex",
"--title",
"Noise",
"--tag",
"state-change:landed",
]);
let out = pointbreak(["revision", "list", "--repo", path]);
let json = parse_json(&out.stdout);
assert_eq!(json["schema"], "pointbreak.review-revision-list");
let entry = &json["entries"][0];
assert!(
entry.get("overview").is_none(),
"flagless path builds no overview (zero new cost)"
);
assert!(
entry.get("attention").is_none(),
"no filter-derived fields leak into the flagless doc"
);
}
#[test]
fn tag_shared_convention_holds_across_observation_list_and_revision_filter() {
let repo = modified_repo();
let path = repo.path().to_str().unwrap();
pointbreak(["capture", "--repo", path]);
pointbreak([
"observation",
"add",
"--repo",
path,
"--track",
"agent:codex",
"--title",
"Landed",
"--tag",
"state-change:landed",
]);
let obs = parse_json(
&pointbreak([
"observation",
"list",
"--repo",
path,
"--tag",
"state-change:landed",
])
.stdout,
);
assert!(
obs["observations"]
.as_array()
.unwrap()
.iter()
.any(|o| o["tags"]
.as_array()
.unwrap()
.iter()
.any(|t| t == "state-change:landed")),
"exact whole-string --tag still matches"
);
let partial = parse_json(
&pointbreak([
"observation",
"list",
"--repo",
path,
"--tag",
"state-change",
])
.stdout,
);
assert!(
partial["observations"].as_array().unwrap().is_empty(),
"--tag stays exact whole-string (never the key facet)"
);
let rev = parse_json(
&pointbreak([
"revision",
"list",
"--repo",
path,
"--filter",
"tag:state-change",
])
.stdout,
);
assert_eq!(rev["revisionCount"], 1);
}
fn parse_json(bytes: &[u8]) -> Value {
serde_json::from_slice(bytes).expect("parse CLI JSON")
}
fn modified_repo() -> GitRepo {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
repo
}
struct CloneWorktreeFixture {
main: GitRepo,
_worktree_parent: tempfile::TempDir,
seed: PathBuf,
}
impl CloneWorktreeFixture {
fn new() -> Self {
let main = GitRepo::new();
main.write("README.md", "base\n");
main.commit_all("base");
let worktree_parent = tempfile::tempdir().expect("create worktree parent");
let seed = worktree_parent.path().join("seed");
add_worktree(main.path(), &seed, "seed");
Self {
main,
_worktree_parent: worktree_parent,
seed,
}
}
fn add_worktree(&self, branch: &str) -> PathBuf {
let path = self._worktree_parent.path().join(branch);
add_worktree(self.main.path(), &path, branch);
path
}
}
fn add_worktree(repo: &Path, path: &Path, branch: &str) {
run_git_os(
repo,
[
OsString::from("worktree"),
OsString::from("add"),
OsString::from("-b"),
OsString::from(branch),
path.as_os_str().to_owned(),
],
);
}
fn run_git_os<I>(cwd: &Path, args: I)
where
I: IntoIterator<Item = OsString>,
{
let output = Command::new("git")
.args(args)
.current_dir(cwd)
.output()
.unwrap_or_else(|error| panic!("run git in {}: {error}", cwd.display()));
assert!(
output.status.success(),
"git failed in {}\nstdout:\n{}\nstderr:\n{}",
cwd.display(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn text_revision_list_digest_is_one_line_per_revision() {
let repo = modified_repo();
pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]);
let output = pointbreak([
"revision",
"list",
"--repo",
repo.path().to_str().unwrap(),
"--format",
"text",
]);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
!stdout.contains("\"schema\""),
"text lane is not JSON: {stdout}"
);
assert!(stdout.contains("1 revision"), "count headline: {stdout}");
assert!(stdout.contains("rev:"), "short revision id: {stdout}");
assert!(stdout.contains("worktree"), "target endpoint: {stdout}");
assert_eq!(
stdout.trim_end().lines().count(),
2,
"header plus one line per revision: {stdout}"
);
}
#[test]
fn text_revision_list_digest_reports_an_empty_store() {
let repo = modified_repo();
let output = pointbreak([
"revision",
"list",
"--repo",
repo.path().to_str().unwrap(),
"--format",
"text",
]);
assert!(
output.status.success(),
"stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("no revisions"), "empty line: {stdout}");
assert!(
!stdout.contains("\"schema\""),
"text lane is not JSON: {stdout}"
);
}