heddle-refs 0.3.1

An AI-native version control system
Documentation
// SPDX-License-Identifier: Apache-2.0
use objects::{
    error::HeddleError,
    object::{ChangeId, MarkerName, ThreadName},
};
use tempfile::TempDir;

use super::*;

fn create_ref_manager() -> (TempDir, RefManager) {
    let temp_dir = TempDir::new().unwrap();
    let heddle_dir = temp_dir.path().join(".heddle");
    std::fs::create_dir_all(&heddle_dir).unwrap();
    let refs = RefManager::new(&heddle_dir);
    refs.init().unwrap();
    (temp_dir, refs)
}

#[test]
fn test_get_thread_from_packed_refs() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.set_thread(&ThreadName::new("cold-branch"), &id)
        .unwrap();
    refs.pack_refs().unwrap();
    let loose = refs.root.join("refs/threads/cold-branch");
    assert!(!loose.exists(), "pack_refs should delete loose file");
    assert_eq!(
        refs.get_thread(&ThreadName::new("cold-branch")).unwrap(),
        Some(id)
    );
}
#[test]
fn test_loose_overrides_packed_refs() {
    let (_temp, refs) = create_ref_manager();
    let id1 = ChangeId::generate();
    let id2 = ChangeId::generate();
    refs.set_thread(&ThreadName::new("main"), &id1).unwrap();
    refs.pack_refs().unwrap();
    refs.set_thread(&ThreadName::new("main"), &id2).unwrap();
    assert_eq!(
        refs.get_thread(&ThreadName::new("main")).unwrap(),
        Some(id2)
    );
}
#[test]
fn test_pack_refs_consolidates_loose() {
    let (_temp, refs) = create_ref_manager();
    let ids: Vec<ChangeId> = (0..5).map(|_| ChangeId::generate()).collect();
    for (i, id) in ids.iter().enumerate() {
        refs.set_thread(&ThreadName::new(format!("branch-{}", i)), id)
            .unwrap();
    }
    refs.pack_refs().unwrap();
    let packed_path = refs.packed_refs_path();
    assert!(packed_path.exists(), "packed-refs file should exist");
    for (i, id) in ids.iter().enumerate() {
        assert_eq!(
            refs.get_thread(&ThreadName::new(format!("branch-{}", i)))
                .unwrap(),
            Some(*id)
        );
    }
}
#[test]
fn test_list_threads_includes_packed() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.set_thread(&ThreadName::new("packed-branch"), &id)
        .unwrap();
    refs.pack_refs().unwrap();
    let threads = refs.list_threads().unwrap();
    assert!(threads.contains(&ThreadName::new("packed-branch")));
}
#[test]
fn test_delete_thread_removes_from_packed() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.set_thread(&ThreadName::new("to-delete"), &id).unwrap();
    refs.pack_refs().unwrap();
    refs.delete_thread(&ThreadName::new("to-delete")).unwrap();
    assert_eq!(
        refs.get_thread(&ThreadName::new("to-delete")).unwrap(),
        None
    );
}
#[test]
fn test_packed_refs_format() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.set_thread(&ThreadName::new("format-test"), &id)
        .unwrap();
    refs.pack_refs().unwrap();
    let packed_path = refs.packed_refs_path();
    let contents = std::fs::read_to_string(&packed_path).unwrap();
    assert!(contents.contains("refs/threads/format-test"));
    assert!(contents.contains(&id.to_string_full()));
}
#[test]
fn test_markers_in_packed_refs() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.create_marker(&MarkerName::new("v1.0.0"), &id).unwrap();
    refs.pack_refs().unwrap();
    let loose = refs.root.join("refs/markers/v1.0.0");
    assert!(!loose.exists(), "pack_refs should delete loose marker file");
    assert_eq!(
        refs.get_marker(&MarkerName::new("v1.0.0")).unwrap(),
        Some(id)
    );
}
#[test]
fn test_delete_thread_cas_removes_packed_entry() {
    let (_temp, refs) = create_ref_manager();
    let id = ChangeId::generate();
    refs.set_thread(&ThreadName::new("packed-thread"), &id)
        .unwrap();
    refs.pack_refs().unwrap();
    refs.delete_thread_cas(&ThreadName::new("packed-thread"), RefExpectation::Value(id))
        .unwrap();
    assert_eq!(
        refs.get_thread(&ThreadName::new("packed-thread")).unwrap(),
        None
    );
}
#[test]
fn test_delete_thread_cas_packed_conflict() {
    let (_temp, refs) = create_ref_manager();
    let id1 = ChangeId::generate();
    let id2 = ChangeId::generate();
    refs.set_thread(&ThreadName::new("packed-thread"), &id1)
        .unwrap();
    refs.pack_refs().unwrap();
    let result = refs.delete_thread_cas(
        &ThreadName::new("packed-thread"),
        RefExpectation::Value(id2),
    );
    assert!(matches!(result, Err(HeddleError::Conflict(_))));
    assert_eq!(
        refs.get_thread(&ThreadName::new("packed-thread")).unwrap(),
        Some(id1)
    );
}

#[test]
fn test_ref_summary_index_reports_packed_entries_and_loose_overrides() {
    let (_temp, refs) = create_ref_manager();
    let packed_thread = ChangeId::generate();
    let packed_marker = ChangeId::generate();
    let loose_override = ChangeId::generate();

    refs.set_thread(&ThreadName::new("release"), &packed_thread)
        .unwrap();
    refs.create_marker(&MarkerName::new("v1.0.0"), &packed_marker)
        .unwrap();
    refs.pack_refs().unwrap();

    let packed_summary = refs.inspect_ref_summary_index().unwrap();
    assert!(packed_summary.present);
    assert!(packed_summary.valid);
    assert_eq!(packed_summary.threads, 1);
    assert_eq!(packed_summary.markers, 1);
    assert_eq!(packed_summary.packed_threads, 1);
    assert_eq!(packed_summary.packed_markers, 1);

    refs.set_thread(&ThreadName::new("release"), &loose_override)
        .unwrap();
    let override_summary = refs.inspect_ref_summary_index().unwrap();
    assert!(override_summary.present);
    assert!(override_summary.valid);
    assert_eq!(override_summary.threads, 1);
    assert_eq!(override_summary.packed_threads, 1);
    assert_eq!(
        refs.list_threads().unwrap(),
        vec![ThreadName::new("release")]
    );
    assert_eq!(
        refs.get_thread(&ThreadName::new("release")).unwrap(),
        Some(loose_override)
    );
}