Skip to main content

git_assist/command/bisect/
config.rs

1use std::{path::PathBuf, str::FromStr};
2
3use git2::{Remote as GitRemote, Repository as GitRepository};
4use inquire::{Select, Text};
5
6use crate::host::GitRepositoryUrl;
7
8use super::SkipPullRequestsConfig;
9
10enum RepositoryUrlChoice<'a> {
11    Remote(&'a GitRemote<'a>),
12    Custom,
13}
14
15impl std::fmt::Display for RepositoryUrlChoice<'_> {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            RepositoryUrlChoice::Remote(remote) => {
19                let url = remote.url().unwrap_or_default();
20
21                write!(f, "{url}")
22            }
23            RepositoryUrlChoice::Custom => {
24                write!(f, "Custom url ...")
25            }
26        }
27    }
28}
29
30/// Builder for creating `SkipPullRequestsConfig` from command-line arguments and user input.
31pub struct SkipPullRequestsConfigBuilder {
32    pub remote_url: Option<String>,
33    pub directory: Option<String>,
34    pub good: Option<String>,
35    pub bad: Option<String>,
36    pub dry_run: bool,
37}
38
39impl Default for SkipPullRequestsConfigBuilder {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl SkipPullRequestsConfigBuilder {
46    pub fn new() -> Self {
47        Self {
48            remote_url: None,
49            directory: None,
50            good: None,
51            bad: None,
52            dry_run: false,
53        }
54    }
55
56    pub fn remote_url(mut self, remote_url: Option<String>) -> Self {
57        self.remote_url = remote_url;
58        self
59    }
60
61    pub fn directory(mut self, directory: Option<String>) -> Self {
62        self.directory = directory;
63        self
64    }
65
66    pub fn good(mut self, good: Option<String>) -> Self {
67        self.good = good;
68        self
69    }
70
71    pub fn bad(mut self, bad: Option<String>) -> Self {
72        self.bad = bad;
73        self
74    }
75
76    pub fn dry_run(mut self, dry_run: bool) -> Self {
77        self.dry_run = dry_run;
78        self
79    }
80
81    pub fn build(self) -> anyhow::Result<SkipPullRequestsConfig> {
82        let directory = match self.directory {
83            Some(directory) => PathBuf::from(shellexpand::tilde(&directory).as_ref()),
84            None => std::env::current_dir()?,
85        };
86
87        let repository_handle = GitRepository::open(&directory)?;
88
89        let remotes: Vec<GitRemote<'_>> = repository_handle
90            .remotes()?
91            .into_iter()
92            .filter_map(|name| {
93                name.and_then(|name| match repository_handle.find_remote(name) {
94                    Ok(remote) => Some(remote),
95                    Err(err) => {
96                        eprintln!("Warning: Failed to find remote '{name}': {err}");
97                        None
98                    }
99                })
100            })
101            .collect();
102
103        let url = match self.remote_url {
104            Some(repository_url) => repository_url,
105            None => {
106                let mut choices: Vec<_> = remotes.iter().map(RepositoryUrlChoice::Remote).collect();
107                choices.push(RepositoryUrlChoice::Custom);
108
109                let choice = Select::new("Remote url:", choices).prompt()?;
110
111                match choice {
112                    RepositoryUrlChoice::Remote(remote) => remote
113                        .url()
114                        .ok_or_else(|| anyhow::anyhow!("Remote has no URL configured"))?
115                        .to_owned(),
116                    RepositoryUrlChoice::Custom => Text::new("Remote url:").prompt()?,
117                }
118            }
119        };
120
121        let repository = GitRepositoryUrl::from_str(&url)?;
122
123        let good: String = match self.good {
124            Some(good) => good,
125            None => Text::new("Known good commit:").prompt()?,
126        }
127        .trim()
128        .to_owned();
129
130        let bad: String = match self.bad {
131            Some(bad) => bad,
132            None => Text::new("Known bad commit:").prompt()?,
133        }
134        .trim()
135        .to_owned();
136
137        let dry_run = self.dry_run;
138
139        Ok(SkipPullRequestsConfig {
140            repository,
141            directory,
142            good,
143            bad,
144            dry_run,
145        })
146    }
147}