1use std::collections::HashSet;
18use std::pin::pin;
19use std::sync::Arc;
20
21use futures::TryStreamExt as _;
22use thiserror::Error;
23
24use crate::backend::BackendError;
25use crate::backend::CommitId;
26use crate::commit::Commit;
27use crate::repo::Repo;
28use crate::revset::ResolvedRevsetExpression;
29use crate::revset::Revset;
30use crate::revset::RevsetEvaluationError;
31use crate::revset::RevsetExpression;
32use crate::revset::RevsetStreamExt as _;
33
34#[derive(Error, Debug)]
36pub enum BisectionError {
37 #[error("Failed to read data from the backend involved in bisection")]
39 BackendError(#[from] BackendError),
40 #[error("Failed to evaluate a revset involved in bisection")]
42 RevsetEvaluationError(#[from] RevsetEvaluationError),
43}
44
45#[derive(Debug)]
48pub enum Evaluation {
49 Good,
51 Bad,
53 Skip,
55 Abort,
57}
58
59impl Evaluation {
60 pub fn invert(self) -> Self {
64 use Evaluation::*;
65 match self {
66 Good => Bad,
67 Bad => Good,
68 Skip => Skip,
69 Abort => Abort,
70 }
71 }
72}
73
74pub struct Bisector<'repo> {
76 repo: &'repo dyn Repo,
77 input_range: Arc<ResolvedRevsetExpression>,
78 good_commits: HashSet<CommitId>,
79 bad_commits: HashSet<CommitId>,
80 skipped_commits: HashSet<CommitId>,
81 aborted: bool,
82}
83
84#[derive(Debug, PartialEq, Eq, Clone)]
86pub enum BisectionResult {
87 Found(Vec<Commit>),
90 Indeterminate,
93 Abort,
95}
96
97#[derive(Debug, PartialEq, Eq, Clone)]
99pub enum NextStep {
100 Evaluate(Commit),
102 Done(BisectionResult),
104}
105
106impl<'repo> Bisector<'repo> {
107 pub async fn new(
110 repo: &'repo dyn Repo,
111 input_range: Arc<ResolvedRevsetExpression>,
112 ) -> Result<Self, BisectionError> {
113 let bad_commits = input_range
114 .heads()
115 .evaluate(repo)?
116 .stream()
117 .try_collect()
118 .await?;
119 Ok(Self {
120 repo,
121 input_range,
122 bad_commits,
123 good_commits: HashSet::new(),
124 skipped_commits: HashSet::new(),
125 aborted: false,
126 })
127 }
128
129 pub fn mark_good(&mut self, id: CommitId) {
131 assert!(!self.bad_commits.contains(&id));
132 assert!(!self.skipped_commits.contains(&id));
133 assert!(!self.aborted);
134 self.good_commits.insert(id);
135 }
136
137 pub fn mark_bad(&mut self, id: CommitId) {
139 assert!(!self.good_commits.contains(&id));
140 assert!(!self.skipped_commits.contains(&id));
141 assert!(!self.aborted);
142 self.bad_commits.insert(id);
143 }
144
145 pub fn mark_skipped(&mut self, id: CommitId) {
147 assert!(!self.good_commits.contains(&id));
148 assert!(!self.bad_commits.contains(&id));
149 assert!(!self.aborted);
150 self.skipped_commits.insert(id);
151 }
152
153 pub fn mark_abort(&mut self, id: CommitId) {
155 assert!(!self.good_commits.contains(&id));
160 assert!(!self.bad_commits.contains(&id));
161 assert!(!self.skipped_commits.contains(&id));
162 self.aborted = true;
163 }
164
165 pub fn mark(&mut self, id: CommitId, evaluation: Evaluation) {
168 match evaluation {
169 Evaluation::Good => self.mark_good(id),
170 Evaluation::Bad => self.mark_bad(id),
171 Evaluation::Skip => self.mark_skipped(id),
172 Evaluation::Abort => self.mark_abort(id),
173 }
174 }
175
176 pub fn good_commits(&self) -> &HashSet<CommitId> {
178 &self.good_commits
179 }
180
181 pub fn bad_commits(&self) -> &HashSet<CommitId> {
183 &self.bad_commits
184 }
185
186 pub fn skipped_commits(&self) -> &HashSet<CommitId> {
188 &self.skipped_commits
189 }
190
191 fn candidates(&self) -> Arc<ResolvedRevsetExpression> {
192 let good_expr = RevsetExpression::commits(self.good_commits.iter().cloned().collect());
193 let bad_expr = RevsetExpression::commits(self.bad_commits.iter().cloned().collect());
194 let skipped_expr =
195 RevsetExpression::commits(self.skipped_commits.iter().cloned().collect());
196
197 self.input_range
198 .intersection(&good_expr.heads().range(&bad_expr.roots()))
199 .minus(&bad_expr)
200 .minus(&skipped_expr)
201 }
202
203 pub async fn remaining_revset(&self) -> Result<Box<dyn Revset + 'repo>, BisectionError> {
207 Ok(self.candidates().evaluate(self.repo)?)
208 }
209
210 pub async fn next_step(&mut self) -> Result<NextStep, BisectionError> {
213 if self.aborted {
214 return Ok(NextStep::Done(BisectionResult::Abort));
215 }
216 let to_evaluate_expr = self.candidates().bisect().latest(1);
221 let to_evaluate_set = to_evaluate_expr.evaluate(self.repo)?;
222 if let Some(commit_id) = pin!(to_evaluate_set.stream()).try_next().await? {
223 let commit = self.repo.store().get_commit_async(&commit_id).await?;
224 Ok(NextStep::Evaluate(commit))
225 } else {
226 let bad_expr = RevsetExpression::commits(self.bad_commits.iter().cloned().collect());
227 let bad_roots = bad_expr.roots().evaluate(self.repo)?;
228 let bad_commits: Vec<_> = bad_roots
229 .stream()
230 .commits(self.repo.store())
231 .try_collect()
232 .await?;
233 if bad_commits.is_empty() {
234 Ok(NextStep::Done(BisectionResult::Indeterminate))
235 } else {
236 Ok(NextStep::Done(BisectionResult::Found(bad_commits)))
237 }
238 }
239 }
240}