git-repo-manager 0.10.0

Manage multiple git repositories. You configure the git repositories in a file, the program does the rest!
Documentation
use std::fmt;

use super::{BranchName, RemoteName, RepoChanges, WorktreeName, config, path, repo};

use camino::Utf8PathBuf as PathBuf;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error(transparent)]
    Libgit(#[from] git2::Error),
    #[error(transparent)]
    Repo(#[from] repo::Error),
    #[error(transparent)]
    Config(#[from] config::Error),
    #[error(transparent)]
    InvalidWorktreeName(#[from] WorktreeValidationError),
    #[error("Remote \"{name}\" not found")]
    RemoteNotFound { name: RemoteName },
    #[error("Cannot push to non-pushable remote \"{name}\"")]
    RemoteNotPushable { name: RemoteName },
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("Current directory does not contain a worktree setup")]
    NotAWorktreeSetup,
    #[error("Worktree {name} already exists")]
    WorktreeAlreadyExists { name: WorktreeName },
    #[error("Branch \"{0}\" not found")]
    BranchNotFound(BranchName),
    #[error("Worktree name is not valid utf-8")]
    WorktreeNameNotUtf8,
    #[error("Worktree name is empty")]
    WorktreeNameEmpty,
    #[error(transparent)]
    Path(#[from] path::Error),
    #[error("Could not determine base directory from \"{git_dir}\"")]
    InvalidBaseDirectory { git_dir: PathBuf },
}

#[derive(Debug, Error)]
#[error("invalid worktree name \"{name}\": {reason}")]
pub struct WorktreeValidationError {
    pub(super) name: String,
    pub(super) reason: WorktreeValidationErrorReason,
}

#[derive(Debug)]
pub enum WorktreeValidationErrorReason {
    SlashAtStartOrEnd,
    ConsecutiveSlashes,
    ContainsWhitespace,
}

impl fmt::Display for WorktreeValidationErrorReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                Self::SlashAtStartOrEnd => "Cannot start or end with a slash",
                Self::ConsecutiveSlashes => "Cannot contain two consecutive slashes",
                Self::ContainsWhitespace => "Cannot contain whitespace",
            }
        )
    }
}

#[derive(Debug, Error)]
pub enum WorktreeRemoveError {
    #[error(transparent)]
    RepoError(repo::Error),
    #[error("Worktree at {0} does not exist")]
    DoesNotExist(PathBuf),
    #[error(
        "Branch \"{branch_name}\" is checked out in worktree \"{worktree_name}\", this does not look correct"
    )]
    BranchNameMismatch {
        worktree_name: WorktreeName,
        branch_name: BranchName,
    },
    #[error("Branch {0} not found")]
    BranchNotFound(BranchName),
    #[error("Changes found in worktree: {0}")]
    Changes(RepoChanges),
    #[error("Branch {branch_name} is not merged into any persistent branches")]
    NotMerged { branch_name: BranchName },
    #[error("Branch {branch_name} is not in line with remote branch")]
    NotInSyncWithRemote { branch_name: BranchName },
    #[error("Removing {path} failed: {error}")]
    RemoveError {
        path: PathBuf,
        error: std::io::Error,
    },
    #[error("Error getting directory entry {path}: {error}")]
    ReadDirectoryError {
        path: PathBuf,
        error: std::io::Error,
    },
}

impl From<repo::Error> for WorktreeRemoveError {
    fn from(value: repo::Error) -> Self {
        Self::RepoError(value)
    }
}

#[derive(Debug, Error)]
pub enum WorktreeConversionError {
    #[error(transparent)]
    RepoError(repo::Error),
    #[error("Changes found in worktree: {0}")]
    Changes(RepoChanges),
    #[error("Ignored files found")]
    Ignored,
    #[error("{}", .0)]
    RenameError(String),
    #[error(transparent)]
    Path(#[from] path::Error),
    #[error("Opening directory failed: {0}")]
    OpenDirectoryError(std::io::Error),
    #[error("Removing {path} failed: {error}")]
    RemoveError {
        path: PathBuf,
        error: std::io::Error,
    },
    #[error("Error getting directory entry: {0}")]
    ReadDirectoryError(std::io::Error),
}

impl From<repo::Error> for WorktreeConversionError {
    fn from(value: repo::Error) -> Self {
        Self::RepoError(value)
    }
}

#[derive(Debug, Error)]
pub enum CleanupWorktreeError {
    #[error(transparent)]
    RepoError(#[from] Error),
    #[error(transparent)]
    RemoveError(#[from] WorktreeRemoveError),
    #[error("Could not get default branch: {0}")]
    DefaultBranch(repo::Error),
    #[error("Branch name error: {0}")]
    BranchName(repo::Error),
    #[error("Branch \"{branch_name}\" not found")]
    BranchNotFound { branch_name: BranchName },
}

pub struct CleanupWorktreeWarning {
    pub worktree_name: WorktreeName,
    pub reason: CleanupWorktreeWarningReason,
}

pub enum CleanupWorktreeWarningReason {
    UncommittedChanges(RepoChanges),
    NotMerged { branch_name: BranchName },
    NoDirectory,
}