tests/file_history_test.rs
Ref: Size: 5.6 KiB History
//! The file-history view lists exactly the commits that touched a path.
//!
//! Like GitHub's "History" button on a blob: open a file, click History, see
//! the line of commits that changed it. A commit that touched a *different*
//! file must not appear, and a commit that *deleted* the file must.
mod common;
use common::ServerHarness;
#[test]
fn history_lists_only_commits_that_touched_the_file() {
let harness = ServerHarness::new("file-history");
let repo = harness.work_repo();
let first = repo.commit_file("tracked.rs", "v1\n", "add tracked.rs");
let _other = repo.commit_file("other.rs", "x\n", "touch a different file");
let second = repo.commit_file("tracked.rs", "v2\n", "change tracked.rs");
harness.push_head();
let name = harness.repo_name();
let body = harness
.get_ok(&format!("/{name}/history/main/tracked.rs"))
.body;
// Both commits that touched tracked.rs appear, newest first.
let first_pos = body.find(&first[..8]).expect("first commit short id shown");
let second_pos = body
.find(&second[..8])
.expect("second commit short id shown");
assert!(
second_pos < first_pos,
"history is not newest-first:\n{body}"
);
// The commit that only touched other.rs is absent.
assert!(
!body.contains(&_other[..8]),
"history lists a commit that did not touch the file:\n{body}"
);
assert!(
body.contains("change tracked.rs") && body.contains("add tracked.rs"),
"history is missing commit summaries:\n{body}"
);
}
#[test]
fn history_view_is_linked_from_the_blob_page() {
let harness = ServerHarness::new("file-history-link");
let repo = harness.work_repo();
repo.commit_file("tracked.rs", "v1\n", "add tracked.rs");
harness.push_head();
let name = harness.repo_name();
let blob = harness
.get_ok(&format!("/{name}/blob/main/tracked.rs"))
.body;
assert!(
blob.contains(&format!("/{name}/history/main/tracked.rs")),
"the blob page has no History link:\n{blob}"
);
}
#[test]
fn history_for_a_path_that_was_never_tracked_is_empty() {
let harness = ServerHarness::new("file-history-missing");
harness.work_repo().commit_file("tracked.rs", "v1\n", "add");
harness.push_head();
let name = harness.repo_name();
let body = harness
.get_ok(&format!("/{name}/history/main/nope.rs"))
.body;
assert!(
body.contains("No commits touched this file."),
"a never-tracked path should say so, not 404 or list commits:\n{body}"
);
}
#[test]
fn a_commit_that_deletes_the_file_appears_in_its_history() {
let harness = ServerHarness::new("file-history-deletion");
let repo = harness.work_repo();
let added = repo.commit_file("doomed.rs", "v1\n", "add doomed.rs");
// Delete the file in a second commit.
repo.git(&["rm", "doomed.rs"]);
repo.git(&["commit", "-m", "delete doomed.rs"]);
harness.push_head();
let name = harness.repo_name();
let body = harness
.get_ok(&format!("/{name}/history/main/doomed.rs"))
.body;
// Both the adding commit and the deleting commit appear.
assert!(
body.contains(&added[..8]),
"the commit that added the file is missing from its history:\n{body}"
);
assert!(
body.contains("delete doomed.rs"),
"the commit that deleted the file is missing from its history:\n{body}"
);
}
/// A clean non-fast-forward merge whose file entry matches a non-first parent
/// must be suppressed, the way `git log -- <path>` suppresses it. The old code
/// compared only parent 0 and listed the merge alongside the source-branch
/// commit.
#[test]
fn a_clean_merge_matching_any_parent_is_suppressed() {
let harness = ServerHarness::new("file-history-merge");
let repo = harness.work_repo();
// A: add f.rs = v1 on main.
repo.commit_file("f.rs", "v1\n", "add f");
// Branch from main, change f.rs = v2 (the source-branch commit).
repo.git(&["checkout", "-b", "topic"]);
let topic_commit = repo.commit_file("f.rs", "v2\n", "change f on topic");
// Back on main: touch a *different* file so f.rs stays v1, then merge.
repo.git(&["checkout", "main"]);
repo.commit_file("other.txt", "x\n", "touch other on main");
repo.git(&["merge", "--no-ff", "topic", "-m", "merge topic"]);
harness.push_head();
let name = harness.repo_name();
let body = harness.get_ok(&format!("/{name}/history/main/f.rs")).body;
let merge_oid = repo.git(&["rev-parse", "HEAD"]).trim().to_string();
assert!(
!body.contains(&merge_oid[..8]),
"the merge commit (TREESAME to a parent) should be suppressed:\n{body}"
);
assert!(
body.contains(&topic_commit[..8]),
"the source-branch commit that changed the file should appear:\n{body}"
);
}
/// A mode-only change (chmod +x) keeps the same blob oid, so an oid-only
/// comparison omits it. `git log -- <path>` includes it, and so must we.
#[test]
fn a_mode_only_change_appears_in_the_history() {
let harness = ServerHarness::new("file-history-mode");
let repo = harness.work_repo();
repo.commit_file("s.sh", "echo hi\n", "add script");
repo.git(&["update-index", "--chmod=+x", "s.sh"]);
repo.git(&["commit", "-m", "chmod +x"]);
harness.push_head();
let name = harness.repo_name();
let body = harness.get_ok(&format!("/{name}/history/main/s.sh")).body;
let chmod_oid = repo.git(&["rev-parse", "HEAD"]).trim().to_string();
assert!(
body.contains(&chmod_oid[..8]),
"a mode-only change (same blob oid) must appear in file history:\n{body}"
);
}