heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Test writing 10 versions of a file using builder pattern and verifying with CLI
//!
//! This test:
//! 1. Creates a new Heroforge repository using heroforge
//! 2. Writes 10 versions of a file with commits using builder pattern
//! 3. Reads each version using heroforge
//! 4. Verifies with heroforge CLI

use heroforge_core::{Repository, Result};
use std::fs;
use std::path::Path;
use std::process::Command;

fn run_fossil(args: &[&str], cwd: &Path) -> std::result::Result<String, String> {
    let output = Command::new("heroforge")
        .args(args)
        .current_dir(cwd)
        .output()
        .map_err(|e| format!("Failed to run heroforge: {}", e))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(format!("Heroforge command failed: {}", stderr));
    }

    Ok(String::from_utf8_lossy(&output.stdout).to_string())
}

fn main() -> Result<()> {
    let test_dir = Path::new("/tmp/heroforge_write_test");
    let repo_file = test_dir.join("test.forge");

    // Clean up any previous test
    if test_dir.exists() {
        fs::remove_dir_all(test_dir).expect("Failed to clean up test directory");
    }
    fs::create_dir_all(test_dir).expect("Failed to create test directory");

    println!("=== Creating Repository with heroforge ===\n");

    // Initialize repository using pure Rust
    let repo = Repository::init(&repo_file)?;
    println!("Created repository: {}", repo_file.display());

    // Create initial empty check-in using builder pattern
    let initial_hash = repo
        .commit_builder()
        .message("initial empty check-in")
        .author("testuser")
        .initial()
        .execute()?;
    println!("Initial check-in: {}", &initial_hash[..16]);

    // Store version info
    let mut version_hashes: Vec<String> = Vec::new();
    let mut version_contents: Vec<String> = Vec::new();

    println!("\n=== Writing 10 Versions with heroforge ===\n");

    let mut parent_hash = initial_hash;

    for i in 1..=10 {
        let content = format!(
            "Version {} of test file\n\
             Created at iteration {}\n\
             Some data: {}\n\
             More lines to make it interesting:\n\
             - Line A for version {}\n\
             - Line B for version {}\n\
             - Line C for version {}\n",
            i,
            i,
            i * 100,
            i,
            i,
            i
        );

        version_contents.push(content.clone());

        // Commit using builder pattern
        let commit_hash = repo
            .commit_builder()
            .message(&format!("Commit version {}", i))
            .author("testuser")
            .parent(&parent_hash)
            .branch("trunk")
            .file("testfile.txt", content.as_bytes())
            .execute()?;

        version_hashes.push(commit_hash.clone());
        println!("Version {}: committed as [{}]", i, &commit_hash[..16]);

        parent_hash = commit_hash;
    }

    println!("\n=== Reading Versions via heroforge ===\n");

    let mut rs_match_count = 0;

    for (i, hash) in version_hashes.iter().enumerate() {
        let version_num = i + 1;
        let expected_content = &version_contents[i];

        // Use builder pattern to read file at specific commit
        match repo.files().at_commit(hash).read_string("testfile.txt") {
            Ok(content_str) => {
                if content_str.trim() == expected_content.trim() {
                    println!("Version {}: heroforge read OK", version_num);
                    rs_match_count += 1;
                } else {
                    println!("Version {}: heroforge MISMATCH", version_num);
                    println!(
                        "  Expected: {:?}...",
                        &expected_content[..50.min(expected_content.len())]
                    );
                    println!(
                        "  Got:      {:?}...",
                        &content_str[..50.min(content_str.len())]
                    );
                }
            }
            Err(e) => {
                println!("Version {}: heroforge read ERROR: {}", version_num, e);
            }
        }
    }

    println!("\n=== Verifying with Heroforge CLI ===\n");

    // Create a work directory and open the repo there
    let work_dir = test_dir.join("work");
    fs::create_dir_all(&work_dir).expect("Failed to create work directory");

    match run_fossil(&["open", repo_file.to_str().unwrap()], &work_dir) {
        Ok(_) => println!("Heroforge CLI opened repository successfully"),
        Err(e) => {
            println!("Heroforge CLI failed to open: {}", e);
            println!("\nNote: The repository was created with heroforge.");
            println!("If heroforge CLI fails, the format may need adjustment.");
        }
    }

    // Try to read timeline
    match run_fossil(&["timeline", "-n", "15", "-t", "ci"], &work_dir) {
        Ok(timeline) => {
            println!("\nFossil CLI timeline:");
            for line in timeline.lines().take(15) {
                println!("  {}", line);
            }
        }
        Err(e) => println!("Could not get timeline: {}", e),
    }

    // Compare file contents using -R flag directly
    println!("\n=== CLI Content Verification ===\n");

    let mut cli_match_count = 0;
    let repo_path = repo_file.to_str().unwrap();

    for (i, hash) in version_hashes.iter().enumerate() {
        let version_num = i + 1;
        let expected_content = &version_contents[i];

        // Use short hash prefix for CLI with -R flag
        let short_hash = &hash[..16];

        match run_fossil(
            &["cat", "testfile.txt", "-r", short_hash, "-R", repo_path],
            test_dir,
        ) {
            Ok(cli_content) => {
                if cli_content.trim() == expected_content.trim() {
                    println!("Version {}: CLI verification OK", version_num);
                    cli_match_count += 1;
                } else {
                    println!("Version {}: CLI MISMATCH", version_num);
                }
            }
            Err(e) => {
                println!("Version {}: CLI read failed: {}", version_num, e);
            }
        }
    }

    println!("\n=== Summary ===\n");
    println!("heroforge read: {}/10 versions matched", rs_match_count);
    println!(
        "heroforge CLI read: {}/10 versions matched",
        cli_match_count
    );

    if rs_match_count == 10 && cli_match_count == 10 {
        println!("\nSUCCESS: All versions match in both heroforge and heroforge CLI!");
    } else if rs_match_count == 10 {
        println!("\nPARTIAL: heroforge works, CLI may need format adjustments");
    } else {
        println!("\nFAILED: Some versions could not be read");
    }

    println!("\nVersion hash mapping:");
    for (i, hash) in version_hashes.iter().enumerate() {
        println!("  Version {}: {}", i + 1, &hash[..32]);
    }

    Ok(())
}