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