heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Commit builder for creating new check-ins.

use crate::error::{FossilError, Result};
use crate::repo::Repository;
use std::fs;
use std::path::Path;

/// Builder for creating commits.
pub struct CommitBuilder<'a> {
    repo: &'a Repository,
    message: Option<String>,
    author: Option<String>,
    parent: Option<String>,
    branch: Option<String>,
    files: Vec<(String, Vec<u8>)>,
    is_initial: bool,
}

impl<'a> CommitBuilder<'a> {
    pub(crate) fn new(repo: &'a Repository) -> Self {
        Self {
            repo,
            message: None,
            author: None,
            parent: None,
            branch: None,
            files: Vec::new(),
            is_initial: false,
        }
    }

    /// Set the commit message.
    pub fn message(mut self, msg: &str) -> Self {
        self.message = Some(msg.to_string());
        self
    }

    /// Set the author/user.
    pub fn author(mut self, user: &str) -> Self {
        self.author = Some(user.to_string());
        self
    }

    /// Set the parent commit hash.
    pub fn parent(mut self, hash: &str) -> Self {
        self.parent = Some(hash.to_string());
        self
    }

    /// Set or create a branch for this commit.
    pub fn branch(mut self, name: &str) -> Self {
        self.branch = Some(name.to_string());
        self
    }

    /// Add a file with content.
    pub fn file(mut self, path: &str, content: &[u8]) -> Self {
        self.files.push((path.to_string(), content.to_vec()));
        self
    }

    /// Add multiple files at once.
    pub fn files(mut self, files: &[(&str, &[u8])]) -> Self {
        for (path, content) in files {
            self.files.push((path.to_string(), content.to_vec()));
        }
        self
    }

    /// Add a file by reading from disk.
    pub fn file_from_path<P: AsRef<Path>>(mut self, repo_path: &str, disk_path: P) -> Result<Self> {
        let content = fs::read(disk_path.as_ref())?;
        self.files.push((repo_path.to_string(), content));
        Ok(self)
    }

    /// Mark this as an initial commit (no parent, creates trunk).
    pub fn initial(mut self) -> Self {
        self.is_initial = true;
        self.branch = Some("trunk".to_string());
        self
    }

    /// Execute the commit and return the hash.
    pub fn execute(self) -> Result<String> {
        let message = self.message.ok_or_else(|| {
            FossilError::InvalidArtifact("commit message is required".to_string())
        })?;
        let author = self
            .author
            .ok_or_else(|| FossilError::InvalidArtifact("commit author is required".to_string()))?;

        let files_refs: Vec<(&str, &[u8])> = self
            .files
            .iter()
            .map(|(p, c)| (p.as_str(), c.as_slice()))
            .collect();

        self.repo.commit_internal(
            &files_refs,
            &message,
            &author,
            self.parent.as_deref(),
            self.branch.as_deref(),
        )
    }
}