1use std::collections::HashSet;
2
3use git2::{Oid, Repository, Sort};
4use psect_core::{
5 Bernoulli, RegressionProbabilities, TestOutcomeDistributions, next_revision_to_test,
6 regression::Revision,
7};
8
9use crate::{error::Error, state::State};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct GitRevision(pub Oid);
13
14impl PartialOrd for GitRevision {
15 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
16 self.0.as_bytes().partial_cmp(other.0.as_bytes())
17 }
18}
19
20impl Revision for GitRevision {}
21
22pub fn build(repo: &Repository, state: &State) -> Result<Vec<GitRevision>, Error> {
25 let mut walk = repo.revwalk()?;
26 for sha in &state.new_revisions {
27 walk.push(repo.revparse_single(sha)?.id())?;
28 }
29 for sha in &state.old_revisions {
32 let commit = repo.find_commit(repo.revparse_single(sha)?.id())?;
33 for parent in commit.parents() {
34 walk.hide(parent.id())?;
35 }
36 }
37 walk.set_sorting(Sort::TOPOLOGICAL | Sort::REVERSE)?;
38 walk.map(|r| r.map(GitRevision).map_err(Error::from))
39 .collect()
40}
41
42pub fn build_distributions(state: &State) -> TestOutcomeDistributions<bool> {
43 TestOutcomeDistributions {
44 old: Box::new(Bernoulli {
45 prior: state
46 .priors
47 .old_pass_rate
48 .expect("old_pass_rate set by `git psect old`"),
49 }),
50 new: Box::new(Bernoulli {
51 prior: state
52 .priors
53 .new_pass_rate
54 .expect("new_pass_rate set by `git psect new`"),
55 }),
56 }
57}
58
59pub fn reconstruct<'a>(
60 repo: &Repository,
61 state: &State,
62 candidates: &'a Vec<GitRevision>,
63 distributions: &TestOutcomeDistributions<bool>,
64) -> Result<RegressionProbabilities<'a, GitRevision>, Error> {
65 let known_old: HashSet<GitRevision> = state
66 .old_revisions
67 .iter()
68 .map(|sha| {
69 repo.revparse_single(sha)
70 .map(|o| GitRevision(o.id()))
71 .map_err(Error::from)
72 })
73 .collect::<Result<_, _>>()?;
74
75 let mut ps = RegressionProbabilities::initialize(candidates, &known_old);
76 for sample in &state.samples {
77 let oid = repo.revparse_single(&sample.revision)?.id();
78 let idx = candidates.iter().position(|r| r.0 == oid).ok_or_else(|| {
79 Error::Validation(format!(
80 "sample {} not found in candidate list",
81 &sample.revision[..10]
82 ))
83 })?;
84 ps.update_with_sample(distributions, idx, sample.outcome);
85 }
86 Ok(ps)
87}
88
89pub fn checkout(repo: &Repository, oid: Oid) -> Result<(), Error> {
90 let commit = repo.find_commit(oid)?;
91 let tree = commit.tree()?;
92 repo.checkout_tree(
93 tree.as_object(),
94 Some(&mut git2::build::CheckoutBuilder::default()),
95 )?;
96 repo.set_head_detached(oid)?;
97 Ok(())
98}
99
100pub fn checkout_next<'a>(
102 repo: &Repository,
103 distributions: &TestOutcomeDistributions<bool>,
104 ps: &RegressionProbabilities<'a, GitRevision>,
105) -> Result<String, Error> {
106 let next_oid = next_revision_to_test(ps, distributions).0;
107 checkout(repo, next_oid)?;
108 Ok(next_oid.to_string())
109}