mod common;
use common::TestRepo;
fn stack() -> TestRepo {
let repo = TestRepo::new();
repo.stack().args(["new", "feature/a"]).assert().success();
repo.commit_file("foo.txt", "alpha\n", "add foo");
repo.stack().args(["new", "feature/b"]).assert().success();
repo.commit_file("bar.txt", "beta\n", "add bar");
repo
}
#[test]
fn absorb_dry_run_routes_hunks_to_owning_commits() {
let repo = stack();
let head = repo.git(["rev-parse", "HEAD"]);
repo.write("foo.txt", "alpha fixed\n");
repo.write("bar.txt", "beta fixed\n");
repo.git(["add", "foo.txt", "bar.txt"]);
repo.stack()
.args(["absorb", "--dry-run"])
.assert()
.success()
.stdout(predicates::str::contains("foo.txt:1 -> feature/a"))
.stdout(predicates::str::contains("add foo"))
.stdout(predicates::str::contains("bar.txt:1 -> feature/b"))
.stdout(predicates::str::contains("add bar"));
assert_eq!(repo.git(["rev-parse", "HEAD"]), head);
}
#[test]
fn absorb_dry_run_leaves_trunk_owned_and_added_lines_unabsorbed() {
let repo = stack();
repo.write("README.md", "# changed\n");
repo.write("foo.txt", "alpha\nbrand new\n");
repo.git(["add", "README.md", "foo.txt"]);
repo.stack()
.args(["absorb", "--dry-run"])
.assert()
.success()
.stdout(predicates::str::contains("unabsorbed (left in place)"))
.stdout(predicates::str::contains(
"README.md:1 owned by a commit outside the stack",
))
.stdout(predicates::str::contains(
"foo.txt:1 added lines - no commit to attribute",
));
}
#[test]
fn absorb_without_staged_changes_reports_nothing() {
let repo = stack();
repo.stack()
.args(["absorb", "--dry-run"])
.assert()
.failure()
.stderr(predicates::str::contains("no staged changes to absorb"));
}
#[test]
fn absorb_include_unstaged_attributes_without_staging() {
let repo = stack();
repo.write("foo.txt", "alpha fixed\n");
repo.stack()
.args(["absorb", "--dry-run"])
.assert()
.failure()
.stderr(predicates::str::contains("no staged changes"));
repo.stack()
.args(["absorb", "--dry-run", "--include-unstaged"])
.assert()
.success()
.stdout(predicates::str::contains("foo.txt:1 -> feature/a"));
}
#[test]
fn absorb_respects_include_unstaged_config() {
let repo = stack();
repo.git(["config", "stk.absorbIncludeUnstaged", "true"]);
repo.write("foo.txt", "alpha fixed\n");
repo.stack()
.args(["absorb", "--dry-run"])
.assert()
.success()
.stdout(predicates::str::contains("foo.txt:1 -> feature/a"));
}
#[test]
fn absorb_folds_a_fix_into_its_owning_commit() {
let repo = stack();
repo.write("foo.txt", "alpha fixed\n");
repo.git(["add", "foo.txt"]);
repo.stack()
.args(["absorb"])
.assert()
.success()
.stdout(predicates::str::contains("absorbed 1 hunk into 1 commit"));
assert_eq!(repo.git(["show", "feature/a:foo.txt"]), "alpha fixed");
assert_eq!(repo.git(["rev-list", "--count", "main..feature/a"]), "1");
assert!(repo.git(["status", "--porcelain"]).is_empty());
}
#[test]
fn absorb_folds_into_multiple_commits_across_branches() {
let repo = stack();
repo.write("foo.txt", "alpha fixed\n");
repo.write("bar.txt", "beta fixed\n");
repo.git(["add", "foo.txt", "bar.txt"]);
repo.stack()
.args(["absorb"])
.assert()
.success()
.stdout(predicates::str::contains("absorbed 2 hunks into 2 commits"));
assert_eq!(repo.git(["show", "feature/a:foo.txt"]), "alpha fixed");
assert_eq!(repo.git(["show", "feature/b:bar.txt"]), "beta fixed");
assert!(repo.git(["status", "--porcelain"]).is_empty());
}
#[test]
fn absorb_leaves_unattributable_changes_in_place() {
let repo = stack();
repo.write("foo.txt", "alpha fixed\n"); repo.write("README.md", "# changed\n"); repo.git(["add", "foo.txt", "README.md"]);
repo.stack()
.args(["absorb"])
.assert()
.success()
.stdout(predicates::str::contains("absorbed 1 hunk into 1 commit"))
.stdout(predicates::str::contains(
"README.md:1 owned by a commit outside the stack",
));
assert_eq!(repo.git(["show", "feature/a:foo.txt"]), "alpha fixed");
assert_eq!(repo.git(["show", "HEAD:README.md"]), "# test repo");
assert_eq!(
std::fs::read_to_string(repo.path().join("README.md")).expect("README"),
"# changed\n"
);
}
#[test]
fn absorb_requires_running_from_a_leaf() {
let repo = stack();
repo.git(["switch", "feature/a"]);
repo.write("foo.txt", "alpha fixed\n");
repo.git(["add", "foo.txt"]);
repo.stack()
.args(["absorb"])
.assert()
.failure()
.stderr(predicates::str::contains("run `git stk absorb`"))
.stderr(predicates::str::contains("feature/b"));
}