1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use std::{
    env,
    path::{Path, PathBuf},
    sync::Arc,
    thread,
};

use clap::Parser;
use eyre::{Context, Result};
use tracing::info;
use tracing_subscriber::EnvFilter;

use crate::{
    cli::Cli,
    git::{parse_repository_from_github, Git, Repository},
    repo_config::{read_repo_config, read_repo_config_from_path, RepoConfig},
};

pub fn setup() -> Result<Cli> {
    let cli = Cli::parse();

    if cli.verbose {
        std::env::set_var("RUST_LOG", "info");
    }

    setup_env()?;
    Ok(cli)
}

fn setup_env() -> Result<()> {
    color_eyre::install()?;

    if std::env::var("RUST_LIB_BACKTRACE").is_err() {
        std::env::set_var("RUST_LIB_BACKTRACE", "1");
    }

    tracing_subscriber::fmt()
        .without_time()
        .with_env_filter(EnvFilter::from_default_env())
        .init();

    Ok(())
}

pub fn get_repo_config(cli: &Cli) -> Result<(RepoConfig, Repository, String)> {
    let env_repo_config = env::var("REPO_CONFIG")
        .map(|p| Path::new(&p).to_path_buf())
        .map_err(|e| eyre::eyre!("Error getting repo config path: {}", e))
        .and_then(|p| read_repo_config_from_path(&p));
    let repo_from_env = env::var("REPO").map(|s| parse_repository_from_github(&s).unwrap());

    // The env variables are meant to help with development. I opted to not put them as cli
    // arguments as they would make --help more noisy.
    let (repo_config, repo, branch) = match (env_repo_config, repo_from_env) {
        (Ok(repo_config), Ok(repo)) => {
            let branch = cli.branch.clone().ok_or_else(|| {
                eyre::eyre!("Error: --branch must be given when using REPO env variable")
            })?;
            (repo_config, repo, branch)
        }
        (Ok(_), Err(_)) | (Err(_), Ok(_)) => {
            eyre::bail!("Error: both env variables REPO and REPO_CONFIG should be given at the same time or not at all")
        }
        (Err(_), Err(_)) => {
            let repo_path = get_repo_path()?;
            let (repo, current_branch) = get_git_info(&repo_path, cli)?;
            let repo_config = read_repo_config(&repo_path)?;
            (repo_config, repo, current_branch)
        }
    };

    info!(?repo_config, ?repo, "config");
    Ok((repo_config, repo, branch))
}

fn find_git_ancestor(mut dir: PathBuf) -> Option<PathBuf> {
    loop {
        let git_dir = dir.join(".git");
        if git_dir.is_dir() {
            return Some(dir);
        }

        if !dir.pop() {
            return None;
        }
    }
}

fn get_repo_path() -> Result<PathBuf> {
    env::var("REPO_PATH")
        .map(|p| Path::new(&p).to_path_buf())
        .or_else(|_| env::current_dir().wrap_err("Failed to get current directory"))
        .and_then(|path| find_git_ancestor(path).ok_or(eyre::eyre!("Not in git repository")))
        .map_err(|e| eyre::eyre!("Error getting repo path: {}", e))
}

fn get_git_info(repo_path: &Path, cli: &Cli) -> Result<(Repository, String)> {
    let git = Arc::new(Git::new(repo_path.to_path_buf()));
    let git1 = Arc::clone(&git);
    let handle1 = thread::spawn(move || git1.get_remote());
    let branch = match &cli.branch {
        Some(branch) => branch.clone(),
        None => {
            let git2 = Arc::clone(&git);
            let handle2 = thread::spawn(move || git2.get_branch());
            handle2.join().unwrap()?
        }
    };
    let repo = handle1.join().unwrap()?;
    Ok((repo, branch))
}