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    pub fn bad_commit(&mut self, c: impl Into<String>) -> &mut Self {
58        if let BisectAction::Start { bad, .. } = &mut self.action {
59            *bad = Some(c.into());
60        }
61        self
62    }
63
64    /// Add a known-`good` commit (for `start`).
65    pub fn good_commit(&mut self, c: impl Into<String>) -> &mut Self {
66        if let BisectAction::Start { good, .. } = &mut self.action {
67            good.push(c.into());
68        }
69        self
70    }
71
72    /// `bisect good` with optional revs.
73    #[must_use]
74    pub fn good(revs: Vec<String>) -> Self {
75        Self {
76            executor: CommandExecutor::default(),
77            action: BisectAction::Good(revs),
78        }
79    }
80
81    /// `bisect bad` with optional rev.
82    #[must_use]
83    pub fn bad(rev: Option<String>) -> Self {
84        Self {
85            executor: CommandExecutor::default(),
86            action: BisectAction::Bad(rev),
87        }
88    }
89
90    /// `bisect skip`.
91    #[must_use]
92    pub fn skip(revs: Vec<String>) -> Self {
93        Self {
94            executor: CommandExecutor::default(),
95            action: BisectAction::Skip(revs),
96        }
97    }
98
99    /// `bisect reset`.
100    #[must_use]
101    pub fn reset(commit: Option<String>) -> Self {
102        Self {
103            executor: CommandExecutor::default(),
104            action: BisectAction::Reset(commit),
105        }
106    }
107
108    /// `bisect log`.
109    #[must_use]
110    pub fn log() -> Self {
111        Self {
112            executor: CommandExecutor::default(),
113            action: BisectAction::Log,
114        }
115    }
116
117    /// `bisect replay <log>`.
118    pub fn replay(path: impl Into<PathBuf>) -> Self {
119        Self {
120            executor: CommandExecutor::default(),
121            action: BisectAction::Replay(path.into()),
122        }
123    }
124
125    /// `bisect run <cmd> [args…]`.
126    pub fn run<I, S>(command: I) -> Self
127    where
128        I: IntoIterator<Item = S>,
129        S: Into<String>,
130    {
131        Self {
132            executor: CommandExecutor::default(),
133            action: BisectAction::Run(command.into_iter().map(Into::into).collect()),
134        }
135    }
136}
137
138#[async_trait]
139impl GitCommand for BisectCommand {
140    type Output = CommandOutput;
141    fn get_executor(&self) -> &CommandExecutor {
142        &self.executor
143    }
144    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
145        &mut self.executor
146    }
147    fn build_command_args(&self) -> Vec<String> {
148        let mut args = vec!["bisect".to_string()];
149        match &self.action {
150            BisectAction::Start { bad, good } => {
151                args.push("start".into());
152                if let Some(b) = bad {
153                    args.push(b.clone());
154                }
155                args.extend(good.iter().cloned());
156            }
157            BisectAction::Good(revs) => {
158                args.push("good".into());
159                args.extend(revs.iter().cloned());
160            }
161            BisectAction::Bad(rev) => {
162                args.push("bad".into());
163                if let Some(r) = rev {
164                    args.push(r.clone());
165                }
166            }
167            BisectAction::Skip(revs) => {
168                args.push("skip".into());
169                args.extend(revs.iter().cloned());
170            }
171            BisectAction::Reset(c) => {
172                args.push("reset".into());
173                if let Some(c) = c {
174                    args.push(c.clone());
175                }
176            }
177            BisectAction::Log => args.push("log".into()),
178            BisectAction::Replay(p) => {
179                args.push("replay".into());
180                args.push(p.display().to_string());
181            }
182            BisectAction::Run(cmd) => {
183                args.push("run".into());
184                args.extend(cmd.iter().cloned());
185            }
186        }
187        args
188    }
189    async fn execute(&self) -> Result<CommandOutput> {
190        self.execute_raw().await
191    }
192}