git-repo-manager 0.7.12

Manage multiple git repositories. You configure the git repositories in a file, the program does the rest!
Documentation
#![feature(io_error_more)]
#![feature(const_option_ext)]
#![forbid(unsafe_code)]

use std::path::Path;

pub mod auth;
pub mod config;
pub mod output;
pub mod path;
pub mod provider;
pub mod repo;
pub mod table;
pub mod tree;
pub mod worktree;

/// Find all git repositories under root, recursively
///
/// The bool in the return value specifies whether there is a repository
/// in root itself.
#[allow(clippy::type_complexity)]
fn find_repos(root: &Path) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)>, String> {
    let mut repos: Vec<repo::Repo> = Vec::new();
    let mut repo_in_root = false;
    let mut warnings = Vec::new();

    for path in tree::find_repo_paths(root)? {
        let is_worktree = repo::RepoHandle::detect_worktree(&path);
        if path == root {
            repo_in_root = true;
        }

        match repo::RepoHandle::open(&path, is_worktree) {
            Err(error) => {
                warnings.push(format!(
                    "Error opening repo {}{}: {}",
                    path.display(),
                    match is_worktree {
                        true => " as worktree",
                        false => "",
                    },
                    error
                ));
                continue;
            }
            Ok(repo) => {
                let remotes = match repo.remotes() {
                    Ok(remote) => remote,
                    Err(error) => {
                        warnings.push(format!(
                            "{}: Error getting remotes: {}",
                            &path::path_as_string(&path),
                            error
                        ));
                        continue;
                    }
                };

                let mut results: Vec<repo::Remote> = Vec::new();
                for remote_name in remotes.iter() {
                    match repo.find_remote(remote_name)? {
                        Some(remote) => {
                            let name = remote.name();
                            let url = remote.url();
                            let remote_type = match repo::detect_remote_type(&url) {
                                Some(t) => t,
                                None => {
                                    warnings.push(format!(
                                        "{}: Could not detect remote type of \"{}\"",
                                        &path::path_as_string(&path),
                                        &url
                                    ));
                                    continue;
                                }
                            };

                            results.push(repo::Remote {
                                name,
                                url,
                                remote_type,
                            });
                        }
                        None => {
                            warnings.push(format!(
                                "{}: Remote {} not found",
                                &path::path_as_string(&path),
                                remote_name
                            ));
                            continue;
                        }
                    };
                }
                let remotes = results;

                let (namespace, name) = if path == root {
                    (
                        None,
                        match &root.parent() {
                            Some(parent) => {
                                path::path_as_string(path.strip_prefix(parent).unwrap())
                            }
                            None => {
                                warnings.push(String::from("Getting name of the search root failed. Do you have a git repository in \"/\"?"));
                                continue;
                            }
                        },
                    )
                } else {
                    let name = path.strip_prefix(root).unwrap();
                    let namespace = name.parent().unwrap();
                    (
                        if namespace != Path::new("") {
                            Some(path::path_as_string(namespace).to_string())
                        } else {
                            None
                        },
                        path::path_as_string(name),
                    )
                };

                repos.push(repo::Repo {
                    name,
                    namespace,
                    remotes: Some(remotes),
                    worktree_setup: is_worktree,
                });
            }
        }
    }
    Ok(Some((repos, warnings, repo_in_root)))
}

pub fn find_in_tree(path: &Path) -> Result<(tree::Tree, Vec<String>), String> {
    let mut warnings = Vec::new();

    let (repos, repo_in_root): (Vec<repo::Repo>, bool) = match find_repos(path)? {
        Some((vec, mut repo_warnings, repo_in_root)) => {
            warnings.append(&mut repo_warnings);
            (vec, repo_in_root)
        }
        None => (Vec::new(), false),
    };

    let mut root = path.to_path_buf();
    if repo_in_root {
        root = match root.parent() {
            Some(root) => root.to_path_buf(),
            None => {
                return Err(String::from(
                    "Cannot detect root directory. Are you working in /?",
                ));
            }
        }
    }

    Ok((
        tree::Tree {
            root: root.into_os_string().into_string().unwrap(),
            repos,
        },
        warnings,
    ))
}