git_assist/command/bisect/
config.rs1use 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
30pub 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}