git_assist/command/bisect/
mod.rs1mod config;
2
3use std::{
4 collections::HashSet,
5 os::unix::process::ExitStatusExt,
6 path::PathBuf,
7 process::{Command, ExitStatus},
8};
9
10use git2::{Oid, Repository as GitRepository};
11
12use crate::{
13 git::commits_in_range,
14 host::{GitHost, GitPullRequest, GitRepositoryUrl},
15};
16
17pub use config::SkipPullRequestsConfigBuilder;
18
19pub struct SkipPullRequestsConfig {
20 pub repository: GitRepositoryUrl,
22
23 pub directory: PathBuf,
25
26 pub good: String,
28
29 pub bad: String,
31
32 pub dry_run: bool,
34}
35
36pub async fn skip_pull_requests(
37 host: &dyn GitHost,
38 config: &SkipPullRequestsConfig,
39) -> anyhow::Result<ExitStatus> {
40 eprintln!("Opening git repository ...");
41 let repository = GitRepository::open(&config.directory)?;
42
43 let range_commit_ids: HashSet<Oid> = {
44 let range = (
45 repository.revparse_single(&config.good)?.id(),
46 repository.revparse_single(&config.bad)?.id(),
47 );
48 commits_in_range(&repository, range)?
49 .into_iter()
50 .map(|commit| commit.id())
51 .collect()
52 };
53
54 eprintln!("Requesting pull requests ...");
55 let pull_requests = host.merged_pull_requests(&config.repository).await?;
56
57 eprintln!("Filtering pull requests ...");
58 let pull_requests: Vec<GitPullRequest> = pull_requests
59 .into_iter()
60 .filter(|pull_request| {
61 let Ok(base_obj) = repository.revparse_single(&pull_request.base_sha) else {
62 return false;
63 };
64 let Ok(merge_obj) = repository.revparse_single(&pull_request.merge_sha) else {
65 return false;
66 };
67
68 range_commit_ids.contains(&base_obj.id()) || range_commit_ids.contains(&merge_obj.id())
69 })
70 .collect();
71
72 if !config.dry_run && std::env::current_dir()? != config.directory {
73 eprintln!("Entering repository directory ...");
74 std::env::set_current_dir(&config.directory)?;
75 }
76
77 for pull_request in pull_requests {
78 let program = "git";
79 let mut command = Command::new(program);
80 let mut args = vec!["bisect".to_owned(), "skip".to_owned()];
81
82 args.push(format!(
83 "{start}..{end}^",
84 start = pull_request.base_sha,
85 end = pull_request.merge_sha,
86 ));
87
88 let formatted_args = args.join(" ");
89
90 command.args(args);
91
92 println!(
93 "# Pull request #{number}: {title:?}",
94 number = pull_request.identifier,
95 title = pull_request.title
96 );
97
98 if config.dry_run {
99 println!("{program} {formatted_args}");
100 } else {
101 command.spawn().and_then(|mut child| child.wait())?;
102 }
103 }
104
105 Ok(ExitStatus::from_raw(0))
106}