Skip to content

Commit 3f16a92

Browse files
authored
Merge pull request #325 from ehuss/bounds-rework
Refactor bounds definition.
2 parents ee3e071 + b6dafd2 commit 3f16a92

21 files changed

+312
-270
lines changed

Diff for: src/bounds.rs

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
//! Definitions of bisection bounds.
2+
3+
use crate::toolchains::{
4+
download_progress, parse_to_naive_date, Toolchain, NIGHTLY_SERVER, YYYY_MM_DD,
5+
};
6+
use crate::GitDate;
7+
use crate::Opts;
8+
use crate::{today, EPOCH_COMMIT};
9+
use anyhow::bail;
10+
use chrono::NaiveDate;
11+
use reqwest::blocking::Client;
12+
use std::io::Read;
13+
use std::str::FromStr;
14+
15+
/// A bisection boundary.
16+
#[derive(Clone, Debug)]
17+
pub enum Bound {
18+
Commit(String),
19+
Date(GitDate),
20+
}
21+
22+
impl FromStr for Bound {
23+
type Err = std::convert::Infallible;
24+
fn from_str(s: &str) -> Result<Self, Self::Err> {
25+
parse_to_naive_date(s)
26+
.map(Self::Date)
27+
.or_else(|_| Ok(Self::Commit(s.to_string())))
28+
}
29+
}
30+
31+
impl Bound {
32+
/// Returns the SHA of this boundary.
33+
///
34+
/// For nightlies, this will fetch from the network.
35+
pub fn sha(&self) -> anyhow::Result<String> {
36+
match self {
37+
Bound::Commit(commit) => Ok(commit.clone()),
38+
Bound::Date(date) => {
39+
let date_str = date.format(YYYY_MM_DD);
40+
let url =
41+
format!("{NIGHTLY_SERVER}/{date_str}/channel-rust-nightly-git-commit-hash.txt");
42+
43+
eprintln!("fetching {url}");
44+
let client = Client::new();
45+
let name = format!("nightly manifest {date_str}");
46+
let mut response = download_progress(&client, &name, &url)?;
47+
let mut commit = String::new();
48+
response.read_to_string(&mut commit)?;
49+
50+
eprintln!("converted {date_str} to {commit}");
51+
52+
Ok(commit)
53+
}
54+
}
55+
}
56+
}
57+
58+
/// The starting bisection bounds.
59+
pub enum Bounds {
60+
/// Indicates to search backwards from the given date to find the start
61+
/// date where the regression does not occur.
62+
SearchNightlyBackwards { end: GitDate },
63+
/// Search between two commits.
64+
///
65+
/// `start` and `end` must be SHA1 hashes of the commit object.
66+
Commits { start: String, end: String },
67+
/// Search between two dates.
68+
Dates { start: GitDate, end: GitDate },
69+
}
70+
71+
impl Bounds {
72+
pub fn from_args(args: &Opts) -> anyhow::Result<Bounds> {
73+
let (start, end) = translate_tags(&args)?;
74+
let today = today();
75+
let check_in_future = |which, date: &NaiveDate| -> anyhow::Result<()> {
76+
if date > &today {
77+
bail!(
78+
"{which} date should be on or before current date, \
79+
got {which} date request: {date} and current date is {today}"
80+
);
81+
}
82+
Ok(())
83+
};
84+
let bounds = match (start, end) {
85+
// Neither --start or --end specified.
86+
(None, None) => Bounds::SearchNightlyBackwards {
87+
end: installed_nightly_or_latest()?,
88+
},
89+
90+
// --start or --end is a commit
91+
(Some(Bound::Commit(start)), Some(Bound::Commit(end))) => {
92+
Bounds::Commits { start, end }
93+
}
94+
(Some(Bound::Commit(start)), None) => Bounds::Commits {
95+
start,
96+
end: args.access.repo().commit("origin/master")?.sha,
97+
},
98+
(None, Some(Bound::Commit(end))) => Bounds::Commits {
99+
start: EPOCH_COMMIT.to_string(),
100+
end,
101+
},
102+
103+
// --start or --end is a date
104+
(Some(Bound::Date(start)), Some(Bound::Date(end))) => {
105+
check_in_future("start", &start)?;
106+
check_in_future("end", &end)?;
107+
Bounds::Dates { start, end }
108+
}
109+
(Some(Bound::Date(start)), None) => {
110+
check_in_future("start", &start)?;
111+
Bounds::Dates {
112+
start,
113+
end: find_latest_nightly()?,
114+
}
115+
}
116+
(None, Some(Bound::Date(end))) => {
117+
check_in_future("end", &end)?;
118+
if args.by_commit {
119+
bail!("--by-commit with an end date requires --start to be specified");
120+
}
121+
Bounds::SearchNightlyBackwards { end }
122+
}
123+
124+
// Mixed not supported.
125+
(Some(Bound::Commit(_)), Some(Bound::Date(_)))
126+
| (Some(Bound::Date(_)), Some(Bound::Commit(_))) => bail!(
127+
"cannot take different types of bounds for start/end, \
128+
got start: {:?} and end {:?}",
129+
args.start,
130+
args.end
131+
),
132+
};
133+
if let Bounds::Dates { start, end } = &bounds {
134+
if end < start {
135+
bail!("end should be after start, got start: {start} and end {end}");
136+
}
137+
if args.by_commit {
138+
eprintln!("finding commit range that corresponds to dates specified");
139+
let bounds = Bounds::Commits {
140+
start: date_to_sha(&start)?,
141+
end: date_to_sha(&end)?,
142+
};
143+
return Ok(bounds);
144+
}
145+
}
146+
Ok(bounds)
147+
}
148+
}
149+
150+
/// Translates a tag-like bound (such as `1.62.0`) to a `Bound::Date` so that
151+
/// bisecting works for versions older than 167 days.
152+
fn translate_tags(args: &Opts) -> anyhow::Result<(Option<Bound>, Option<Bound>)> {
153+
let is_tag = |bound: &Option<Bound>| -> bool {
154+
match bound {
155+
Some(Bound::Commit(commit)) => commit.contains('.'),
156+
None | Some(Bound::Date(_)) => false,
157+
}
158+
};
159+
let is_datelike = |bound: &Option<Bound>| -> bool {
160+
matches!(bound, None | Some(Bound::Date(_))) || is_tag(bound)
161+
};
162+
if !(is_datelike(&args.start) && is_datelike(&args.end)) {
163+
// If the user specified an actual commit for one bound, then don't
164+
// even try to convert the other bound to a date.
165+
return Ok((args.start.clone(), args.end.clone()));
166+
}
167+
let fixup = |which: &str, bound: &Option<Bound>| -> anyhow::Result<Option<Bound>> {
168+
if is_tag(bound) {
169+
if let Some(Bound::Commit(tag)) = bound {
170+
let date = args
171+
.access
172+
.repo()
173+
.bound_to_date(Bound::Commit(tag.clone()))?;
174+
eprintln!(
175+
"translating --{which}={tag} to {date}",
176+
date = date.format(YYYY_MM_DD)
177+
);
178+
return Ok(Some(Bound::Date(date)));
179+
}
180+
}
181+
Ok(bound.clone())
182+
};
183+
Ok((fixup("start", &args.start)?, fixup("end", &args.end)?))
184+
}
185+
186+
/// Returns the commit SHA of the nightly associated with the given date.
187+
fn date_to_sha(date: &NaiveDate) -> anyhow::Result<String> {
188+
let date_str = date.format(YYYY_MM_DD);
189+
let url = format!("{NIGHTLY_SERVER}/{date_str}/channel-rust-nightly-git-commit-hash.txt");
190+
191+
eprintln!("fetching {url}");
192+
let client = Client::new();
193+
let name = format!("nightly manifest {date_str}");
194+
let mut response = download_progress(&client, &name, &url)?;
195+
let mut commit = String::new();
196+
response.read_to_string(&mut commit)?;
197+
198+
eprintln!("converted {date_str} to {commit}");
199+
200+
Ok(commit)
201+
}
202+
203+
/// Returns the date of the nightly toolchain currently installed. If no
204+
/// nightly is found, then it goes to the network to determine the date of the
205+
/// latest nightly.
206+
fn installed_nightly_or_latest() -> anyhow::Result<GitDate> {
207+
if let Some(date) = Toolchain::default_nightly() {
208+
return Ok(date);
209+
}
210+
find_latest_nightly()
211+
}
212+
213+
/// Returns the date of the latest nightly (fetched from the network).
214+
fn find_latest_nightly() -> anyhow::Result<GitDate> {
215+
let url = format!("{NIGHTLY_SERVER}/channel-rust-nightly-date.txt");
216+
eprintln!("fetching {url}");
217+
let client = Client::new();
218+
let mut response = download_progress(&client, "nightly date", &url)?;
219+
let mut body = String::new();
220+
response.read_to_string(&mut body)?;
221+
let date = NaiveDate::parse_from_str(&body, "%Y-%m-%d")?;
222+
eprintln!("determined the latest nightly is {date}");
223+
Ok(date)
224+
}

Diff for: src/github.rs

+9
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ impl CommitsQuery<'_> {
148148
.url();
149149

150150
let response: Response = client.get(&url).send()?;
151+
let status = response.status();
152+
if !status.is_success() {
153+
bail!(
154+
"error: url <{}> response {}: {}",
155+
url,
156+
status,
157+
response.text().unwrap_or_else(|_| format!("<empty>"))
158+
);
159+
}
151160

152161
let action = parse_paged_elems(response, |elem: GithubCommitElem| {
153162
let found_last = elem.sha == self.earliest_sha;

0 commit comments

Comments
 (0)