Refactor!

In the original version (written in Python), based on force variable __init__ of GitRepository maybe used either for loading a repository or creating a new one. I don't like that approach. I do believe in the separation of concerns.

    def __init__(self, path, force=False):
        self.worktree = path
        self.gitdir = os.path.join(path, ".git")

        if not (force or os.path.isdir(self.gitdir)):
            raise Exception("Not a Git repository %s" % path)

        # Read configuration file in .git/config
        self.conf = configparser.ConfigParser()
        cf = repo_file(self, "config")

        if cf and os.path.exists(cf):
            self.conf.read([cf])
        elif not force:
            raise Exception("Configuration file missing")

        if not force:
            vers = int(self.conf.get("core", "repositoryformatversion"))
            if vers != 0:
                raise Exception("Unsupported repositoryformatversion %s" % vers)

Currently GitRepository is doing too much, doing (at least, at this time) more than one job. Managing the repository and dealing with directories at a low level. So let's moe directory management to a new module named DirectoryManager:

// src/lib.rs
pub mod directory_manager;

pub use directory_manager::DirectoryManager;

Add DirectoryManager struct in this new module:

use std::path::{Path, PathBuf};

#[derive(Debug)]
pub struct DirectoryManager {
    pub work_tree: PathBuf, // Where we add our project's files
    pub dot_git_path: PathBuf, // Where git stores its local files
}

Add an impl block for this struct:

impl DirectoryManager {
    // To create a new object
    pub fn new<T: Into<PathBuf>>(base_path: T) -> Self {
        let base_path: PathBuf = base_path.into();
        Self {
            dot_git_path: base_path.join(".git"),
            work_tree: base_path,
        }
    }

    // To return config file path. Instead of having a raw function like rep_path, let's have a more meaningful function
    pub fn config_file(&self) -> PathBuf {
        self.dot_git_path.join("config")
    }
}

Move tests from repository.rs to this module:

#[cfg(test)]
mod tests {
    const PROJECT_DIR: &'static str = "~/home/projects/test";
    use std::path::Path;

    use crate::DirectoryManager;

    #[test]
    fn should_return_correct_git_path() {
        let dir_manager = DirectoryManager::new(PROJECT_DIR);
        assert_eq!(
            dir_manager.dot_git_path,
            Path::new("~/home/projects/test/.git")
        );
    }

    #[test]
    fn should_return_correct_config_file_path() {
        let dir_manager = DirectoryManager::new(PROJECT_DIR);
        assert_eq!(
            dir_manager.config_file(),
            Path::new("~/home/projects/test/.git/config")
        );
    }
}

Change Repository to have a DirectoryManager

#[derive(Debug)]
pub struct GitRepository {
    config: GitConfig,
    directory_manager: DirectoryManager,
}

Instead of having a Repository::new function that does magic with a force flag (What does force mean? force to what?) let's have two separate functions load and create. Names explain the goal. load is used to load an existing repository, create creates an new one.

impl GitRepository {
    /// Load an existing repository.
    pub fn load(base_path: &Path) -> Result<Self, String> {
        let directory_manager = DirectoryManager::new(base_path);
        let config_file = directory_manager.config_file();

        let mut config_file = File::open(config_file).map_err(|e| e.to_string())?;
        let mut config_string = String::new();
        config_file
            .read_to_string(&mut config_string)
            .map_err(|e| e.to_string())?;

        let config: GitConfig = config_string.parse()?;

        let version = config.repository_format_version()?;
        if version != 0 {
            return Err(format!(
                "Repository format version {} not supported",
                version
            ));
        }

        Ok(Self {
            config,
            directory_manager,
        })
    }

    /// Create a new repository
    pub fn create(_base_path: &Path) -> Self {
        todo!()
    }
}

Last updated