heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Filesystem operations example demonstrating the fs() builder API.
//!
//! This example shows how to use high-level filesystem operations on repository contents:
//! - Copy files and directories
//! - Move/rename files and directories
//! - Delete files and directories
//! - Change permissions (chmod)
//! - Create symlinks
//! - Find files with patterns and ignore lists

use heroforge_core::{Permissions, Repository, Result};
use std::fs;

fn main() -> Result<()> {
    // Create a temporary directory for the example
    let tmp_dir = std::env::temp_dir().join("heroforge_fs_example");
    let _ = fs::remove_dir_all(&tmp_dir);
    fs::create_dir_all(&tmp_dir)?;
    let repo_path = tmp_dir.join("project.forge");

    println!("=== Filesystem Operations Example ===\n");

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

    // Create initial commit with some files
    let init_hash = repo
        .commit_builder()
        .message("Initial structure")
        .author("developer")
        .initial()
        .file("README.md", b"# My Project\n\nA sample project.\n")
        .file(
            "src/main.rs",
            b"fn main() {\n    println!(\"Hello!\");\n}\n",
        )
        .file("src/lib.rs", b"pub mod utils;\npub mod config;\n")
        .file("src/utils.rs", b"pub fn helper() {}\n")
        .file("src/config.rs", b"pub const VERSION: &str = \"1.0\";\n")
        .file("tests/test_main.rs", b"#[test]\nfn it_works() {}\n")
        .file("docs/guide.md", b"# User Guide\n")
        .file("docs/api.md", b"# API Reference\n")
        .file(".gitignore", b"target/\n*.log\n")
        .file("scripts/build.sh", b"#!/bin/bash\ncargo build\n")
        .file("scripts/test.sh", b"#!/bin/bash\ncargo test\n")
        .execute()?;

    println!("Initial commit: {}\n", &init_hash[..16]);

    // List files before operations
    println!("=== Files before operations ===");
    let files = repo.files().on_trunk().list()?;
    for f in &files {
        println!("  {}", f.name);
    }
    println!();

    // =========================================================================
    // Copy Operations
    // =========================================================================
    println!("=== Copy Operations ===");

    // Copy a single file
    let hash = repo
        .fs()
        .modify()
        .message("Copy README to docs")
        .author("developer")
        .copy_file("README.md", "docs/README.md")
        .execute()?;
    println!("Copied README.md -> docs/README.md ({})", &hash[..16]);

    // Copy entire directory
    let hash = repo
        .fs()
        .modify()
        .message("Backup src directory")
        .author("developer")
        .copy_dir("src", "src_backup")
        .execute()?;
    println!("Copied src/ -> src_backup/ ({})", &hash[..16]);

    // Verify copy - need to look at the new commit
    let backup_files = repo.files().at_commit(&hash).find("src_backup/**")?;
    println!("  Files in src_backup/:");
    for f in &backup_files {
        println!("    {}", f.name);
    }
    println!();

    // =========================================================================
    // Move/Rename Operations
    // =========================================================================
    println!("=== Move/Rename Operations ===");

    // Rename a file
    let hash = repo
        .fs()
        .modify()
        .message("Rename utils.rs to helpers.rs")
        .author("developer")
        .rename("src/utils.rs", "src/helpers.rs")
        .execute()?;
    println!("Renamed src/utils.rs -> src/helpers.rs ({})", &hash[..16]);

    // Move directory
    let hash = repo
        .fs()
        .modify()
        .message("Move scripts to tools directory")
        .author("developer")
        .move_dir("scripts", "tools")
        .execute()?;
    println!("Moved scripts/ -> tools/ ({})", &hash[..16]);

    // Verify move - need to look at the new commit
    let tools_files = repo.files().at_commit(&hash).find("tools/**")?;
    println!("  Files in tools/:");
    for f in &tools_files {
        println!("    {}", f.name);
    }
    println!();

    // =========================================================================
    // Delete Operations
    // =========================================================================
    println!("=== Delete Operations ===");

    // Delete a single file (won't fail if doesn't exist)
    let hash = repo
        .fs()
        .modify()
        .message("Remove backup directory")
        .author("developer")
        .delete_dir("src_backup")
        .execute()?;
    println!("Deleted src_backup/ ({})", &hash[..16]);

    // Delete files matching a pattern
    let hash = repo
        .fs()
        .modify()
        .message("Remove all markdown in docs except guide")
        .author("developer")
        .delete_file("docs/api.md")
        .execute()?;
    println!("Deleted docs/api.md ({})", &hash[..16]);

    // Delete non-existent file (should succeed silently)
    let hash = repo
        .fs()
        .modify()
        .message("Try to delete non-existent file")
        .author("developer")
        .delete_file("does/not/exist.txt")
        .execute()?;
    println!("Attempted delete of non-existent file ({})\n", &hash[..16]);

    // =========================================================================
    // Chmod Operations
    // =========================================================================
    println!("=== Chmod Operations ===");

    // Make scripts executable
    let hash = repo
        .fs()
        .modify()
        .message("Make build script executable")
        .author("developer")
        .make_executable("tools/build.sh")
        .make_executable("tools/test.sh")
        .execute()?;
    println!("Made tools/*.sh executable ({})", &hash[..16]);

    // Set specific permissions
    let hash = repo
        .fs()
        .modify()
        .message("Set config as read-only")
        .author("developer")
        .chmod("src/config.rs", 0o444)
        .execute()?;
    println!("Set src/config.rs to 444 (read-only) ({})", &hash[..16]);

    // Using Permissions struct
    let perms = Permissions::from_octal(0o640);
    println!(
        "  Permissions 640 = {} (octal: {:o})",
        perms.to_string_repr(),
        perms.to_octal()
    );
    println!();

    // =========================================================================
    // Symlink Operations
    // =========================================================================
    println!("=== Symlink Operations ===");

    // Create symlinks
    let hash = repo
        .fs()
        .modify()
        .message("Create symlinks")
        .author("developer")
        .symlink("GUIDE.md", "docs/guide.md")
        .symlink("build", "tools/build.sh")
        .execute()?;
    println!("Created symlinks ({})", &hash[..16]);

    // List symlinks from the new commit
    let files = repo.files().at_commit(&hash).list()?;
    println!("  Symlinks in repository:");
    for f in &files {
        if let Ok(content) = repo.files().at_commit(&hash).read(&f.name) {
            if let Ok(text) = String::from_utf8(content) {
                if text.starts_with("link ") {
                    let target = text[5..].trim();
                    println!("    {} -> {}", f.name, target);
                }
            }
        }
    }
    println!();

    // =========================================================================
    // Combined Operations
    // =========================================================================
    println!("=== Combined Operations (atomic) ===");

    // Multiple operations in a single commit
    let hash = repo
        .fs()
        .modify()
        .message("Reorganize project structure")
        .author("developer")
        .copy_file("README.md", "README.bak")
        .write_str("README.md", "# Updated Project\n\nNew content!\n")
        .move_file("docs/guide.md", "docs/manual.md")
        .touch("docs/.keep")
        .delete_file(".gitignore")
        .execute()?;
    println!(
        "Performed multiple operations atomically ({})\n",
        &hash[..16]
    );

    // =========================================================================
    // Preview Operations
    // =========================================================================
    println!("=== Preview Operations ===");

    let preview = repo
        .fs()
        .modify()
        .message("Preview these changes")
        .author("developer")
        .copy_file("README.md", "README2.md")
        .delete_dir("tools")
        .chmod("src/main.rs", 0o755)
        .preview()?;

    println!("Preview of pending operations:");
    println!("  Base commit: {}", &preview.base_commit[..16]);
    println!("  Base file count: {}", preview.base_file_count);
    println!("  Operations:");
    for desc in preview.describe() {
        println!("    {}", desc);
    }
    println!();

    // =========================================================================
    // Final State
    // =========================================================================
    println!("=== Final Repository State (at latest commit) ===");
    let files = repo.files().at_commit(&hash).list()?;
    for f in &files {
        println!("  {}", f.name);
    }
    println!();

    // Check some utility functions
    println!("=== Utility Functions ===");
    println!("  exists('README.md'): {}", repo.fs().exists("README.md")?);
    println!(
        "  exists('nonexistent.txt'): {}",
        repo.fs().exists("nonexistent.txt")?
    );
    println!("  is_dir('src'): {}", repo.fs().is_dir("src")?);
    println!("  is_dir('README.md'): {}", repo.fs().is_dir("README.md")?);

    if let Some(stat) = repo.fs().stat("README.md")? {
        println!("  stat('README.md'):");
        println!("    path: {}", stat.path);
        println!("    hash: {}", &stat.hash[..16]);
        println!("    size: {:?} bytes", stat.size);
    }

    let total_size = repo.fs().du("**/*.rs")?;
    println!("  Total size of *.rs files: {} bytes", total_size);

    let rs_count = repo.fs().count("**/*.rs")?;
    println!("  Number of *.rs files: {}", rs_count);

    // Cleanup
    let _ = fs::remove_dir_all(&tmp_dir);
    println!("\n=== Example completed successfully! ===");

    Ok(())
}