#![cfg(feature = "git")]
#![allow(missing_docs)]
use std::path::{Path, PathBuf};
use std::process::Command;
use futures::StreamExt;
use rtb_forge::git::{ChangeKind, Repo};
#[tokio::test]
async fn w1_walk_head_yields_commits_newest_first() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let mut walk = repo.walk("HEAD").expect("walk HEAD");
let mut messages: Vec<String> = Vec::new();
while let Some(commit) = walk.next().await {
let commit = commit.expect("walk item");
messages.push(commit.summary);
}
assert_eq!(
messages,
vec!["third".to_string(), "second".to_string(), "initial".to_string()],
"walk should return newest-first"
);
}
#[tokio::test]
async fn w2_walk_range_honours_exclusion() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let mut walk = repo.walk("HEAD~2..HEAD").expect("walk range");
let mut messages: Vec<String> = Vec::new();
while let Some(commit) = walk.next().await {
messages.push(commit.expect("walk item").summary);
}
assert_eq!(messages, vec!["third".to_string(), "second".to_string()]);
}
#[tokio::test]
async fn w3_walk_bad_revspec_returns_revspec_not_found() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let err = repo.walk("does-not-exist").expect_err("bad revspec must fail");
assert!(
matches!(err, rtb_forge::git::RepoError::RevspecNotFound { ref revspec } if revspec == "does-not-exist"),
"got {err:?}"
);
}
#[tokio::test]
async fn d1_diff_between_consecutive_commits() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let diff = repo.diff("HEAD~2", "HEAD~1").await.expect("diff");
let mut paths: Vec<(PathBuf, ChangeKind)> =
diff.changes.iter().map(|c| (c.path.clone(), c.kind.clone())).collect();
paths.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(
paths,
vec![
(PathBuf::from("LICENSE"), ChangeKind::Added),
(PathBuf::from("README.md"), ChangeKind::Modified),
],
);
}
#[tokio::test]
async fn d2_diff_captures_deletions() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let diff = repo.diff("HEAD~1", "HEAD").await.expect("diff");
let mut paths: Vec<(PathBuf, ChangeKind)> =
diff.changes.iter().map(|c| (c.path.clone(), c.kind.clone())).collect();
paths.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(
paths,
vec![
(PathBuf::from("CHANGELOG.md"), ChangeKind::Added),
(PathBuf::from("README.md"), ChangeKind::Deleted),
],
);
}
#[tokio::test]
async fn d3_diff_bad_revspec_returns_revspec_not_found() {
let fixture = build_three_commit_repo("alice");
let repo = Repo::open(fixture.path()).await.expect("open fixture");
let err = repo.diff("HEAD", "no-such-rev").await.expect_err("bad revspec must fail");
assert!(
matches!(err, rtb_forge::git::RepoError::RevspecNotFound { ref revspec } if revspec == "no-such-rev"),
"got {err:?}"
);
}
fn build_three_commit_repo(author: &str) -> tempfile::TempDir {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path();
git_init(path);
write(path, "README.md", "v1\n");
git_add_all(path);
git_commit(path, author, "initial");
write(path, "README.md", "v2\n");
write(path, "LICENSE", "MIT\nokay\n");
git_add_all(path);
git_commit(path, author, "second");
std::fs::remove_file(path.join("README.md")).expect("remove README");
write(path, "CHANGELOG.md", "v0.1\n");
git_add_all(path);
git_commit(path, author, "third");
dir
}
fn git_init(path: &Path) {
let status = Command::new("git")
.arg("init")
.arg("-b")
.arg("main")
.current_dir(path)
.output()
.expect("git init");
assert!(status.status.success(), "git init: {}", String::from_utf8_lossy(&status.stderr));
}
fn git_add_all(path: &Path) {
let status =
Command::new("git").arg("add").arg("-A").current_dir(path).output().expect("git add");
assert!(status.status.success(), "git add: {}", String::from_utf8_lossy(&status.stderr));
}
fn git_commit(path: &Path, author: &str, message: &str) {
let status = Command::new("git")
.arg("commit")
.arg("--allow-empty")
.arg("-m")
.arg(message)
.env("GIT_AUTHOR_NAME", author)
.env("GIT_AUTHOR_EMAIL", format!("{author}@example.test"))
.env("GIT_COMMITTER_NAME", author)
.env("GIT_COMMITTER_EMAIL", format!("{author}@example.test"))
.env("GIT_AUTHOR_DATE", "2026-05-14T00:00:00Z")
.env("GIT_COMMITTER_DATE", "2026-05-14T00:00:00Z")
.current_dir(path)
.output()
.expect("git commit");
assert!(status.status.success(), "git commit: {}", String::from_utf8_lossy(&status.stderr));
}
fn write(path: &Path, file: &str, contents: &str) {
std::fs::write(path.join(file), contents).expect("write fixture file");
}