a73x

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}"
    );
}