Skip to main content

git_spawn/command/
bisect.rs

1//! `git bisect` — find the commit that introduced a bug via binary search.
2
3use crate::command::{CommandExecutor, CommandOutput, GitCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::path::PathBuf;
7
8/// Actions supported by `git bisect`.
9#[derive(Debug, Clone)]
10pub enum BisectAction {
11    /// `git bisect start [<bad>] [<good>…]`.
12    Start {
13        /// Initial known-bad commit.
14        bad: Option<String>,
15        /// Known-good commits.
16        good: Vec<String>,
17    },
18    /// `git bisect good [<rev>…]`.
19    Good(Vec<String>),
20    /// `git bisect bad [<rev>]`.
21    Bad(Option<String>),
22    /// `git bisect skip [<rev>…]`.
23    Skip(Vec<String>),
24    /// `git bisect reset [<commit>]`.
25    Reset(Option<String>),
26    /// `git bisect log`.
27    Log,
28    /// `git bisect replay <log>`.
29    Replay(PathBuf),
30    /// `git bisect run <cmd> [<args>…]`.
31    Run(Vec<String>),
32}
33
34/// Builder for `git bisect`.
35#[derive(Debug, Clone)]
36pub struct BisectCommand {
37    /// Shared executor.
38    pub executor: CommandExecutor,
39    /// Action.
40    pub action: BisectAction,
41}
42
43impl BisectCommand {
44    /// `bisect start`.
45    #[must_use]
46    pub fn start() -> Self {
47        Self {
48            executor: CommandExecutor::default(),
49            action: BisectAction::Start {
50                bad: None,
51                good: vec![],
52            },
53        }
54    }
55
56    /// Set initial `bad` commit.
57    #[must_use]
58    pub fn bad_commit(mut self, c: impl Into<String>) -> Self {
59        if let BisectAction::Start { bad, .. } = &mut self.action {
60            *bad = Some(c.into());
61        }
62        self
63    }
64
65    /// Add a known-`good` commit (for `start`).
66    #[must_use]
67    pub fn good_commit(mut self, c: impl Into<String>) -> Self {
68        if let BisectAction::Start { good, .. } = &mut self.action {
69            good.push(c.into());
70        }
71        self
72    }
73
74    /// `bisect good` with optional revs.
75    #[must_use]
76    pub fn good(revs: Vec<String>) -> Self {
77        Self {
78            executor: CommandExecutor::default(),
79            action: BisectAction::Good(revs),
80        }
81    }
82
83    /// `bisect bad` with optional rev.
84    #[must_use]
85    pub fn bad(rev: Option<String>) -> Self {
86        Self {
87            executor: CommandExecutor::default(),
88            action: BisectAction::Bad(rev),
89        }
90    }
91
92    /// `bisect skip`.
93    #[must_use]
94    pub fn skip(revs: Vec<String>) -> Self {
95        Self {
96            executor: CommandExecutor::default(),
97            action: BisectAction::Skip(revs),
98        }
99    }
100
101    /// `bisect reset`.
102    #[must_use]
103    pub fn reset(commit: Option<String>) -> Self {
104        Self {
105            executor: CommandExecutor::default(),
106            action: BisectAction::Reset(commit),
107        }
108    }
109
110    /// `bisect log`.
111    #[must_use]
112    pub fn log() -> Self {
113        Self {
114            executor: CommandExecutor::default(),
115            action: BisectAction::Log,
116        }
117    }
118
119    /// `bisect replay <log>`.
120    pub fn replay(path: impl Into<PathBuf>) -> Self {
121        Self {
122            executor: CommandExecutor::default(),
123            action: BisectAction::Replay(path.into()),
124        }
125    }
126
127    /// `bisect run <cmd> [args…]`.
128    pub fn run<I, S>(command: I) -> Self
129    where
130        I: IntoIterator<Item = S>,
131        S: Into<String>,
132    {
133        Self {
134            executor: CommandExecutor::default(),
135            action: BisectAction::Run(command.into_iter().map(Into::into).collect()),
136        }
137    }
138}
139
140#[async_trait]
141impl GitCommand for BisectCommand {
142    type Output = CommandOutput;
143    fn get_executor(&self) -> &CommandExecutor {
144        &self.executor
145    }
146    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
147        &mut self.executor
148    }
149    fn build_command_args(&self) -> Vec<String> {
150        let mut args = vec!["bisect".to_string()];
151        match &self.action {
152            BisectAction::Start { bad, good } => {
153                args.push("start".into());
154                if let Some(b) = bad {
155                    args.push(b.clone());
156                }
157                args.extend(good.iter().cloned());
158            }
159            BisectAction::Good(revs) => {
160                args.push("good".into());
161                args.extend(revs.iter().cloned());
162            }
163            BisectAction::Bad(rev) => {
164                args.push("bad".into());
165                if let Some(r) = rev {
166                    args.push(r.clone());
167                }
168            }
169            BisectAction::Skip(revs) => {
170                args.push("skip".into());
171                args.extend(revs.iter().cloned());
172            }
173            BisectAction::Reset(c) => {
174                args.push("reset".into());
175                if let Some(c) = c {
176                    args.push(c.clone());
177                }
178            }
179            BisectAction::Log => args.push("log".into()),
180            BisectAction::Replay(p) => {
181                args.push("replay".into());
182                args.push(p.display().to_string());
183            }
184            BisectAction::Run(cmd) => {
185                args.push("run".into());
186                args.extend(cmd.iter().cloned());
187            }
188        }
189        args
190    }
191    async fn execute(&self) -> Result<CommandOutput> {
192        self.execute_raw().await
193    }
194}