Skip to content

Commit e26646c

Browse files
committed
Refactor bounds definition.
1 parent a05c97c commit e26646c

22 files changed

+305
-266
lines changed

Diff for: Cargo.lock

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ xz2 = "0.1.7"
3333
chrono = "0.4.22"
3434
colored = "2"
3535
regex = "1.10.1"
36+
toml = "0.8.10"
3637

3738
[dev-dependencies]
3839
quickcheck = "1"

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+
Commits { start: String, end: String },
65+
/// Search between two dates.
66+
Dates { start: GitDate, end: GitDate },
67+
}
68+
69+
impl Bounds {
70+
pub fn from_args(args: &Opts) -> anyhow::Result<Bounds> {
71+
let (start, end) = translate_tags(&args)?;
72+
let today = today();
73+
let check_in_future = |which, date: &NaiveDate| -> anyhow::Result<()> {
74+
if date > &today {
75+
bail!(
76+
"{which} date should be on or before current date, \
77+
got {which} date request: {date} and current date is {today}"
78+
);
79+
}
80+
Ok(())
81+
};
82+
let bounds = match (start, end) {
83+
// Neither --start or --end specified.
84+
(None, None) => Bounds::SearchNightlyBackwards {
85+
end: installed_nightly_or_latest()?,
86+
},
87+
88+
// --start or --end is a commit
89+
(Some(Bound::Commit(start)), Some(Bound::Commit(end))) => {
90+
Bounds::Commits { start, end }
91+
}
92+
(Some(Bound::Commit(start)), None) => Bounds::Commits {
93+
start,
94+
end: "origin/master".to_string(),
95+
},
96+
(None, Some(Bound::Commit(end))) => Bounds::Commits {
97+
start: EPOCH_COMMIT.to_string(),
98+
end,
99+
},
100+
101+
// --start or --end is a date
102+
(Some(Bound::Date(start)), Some(Bound::Date(end))) => {
103+
check_in_future("start", &start)?;
104+
check_in_future("end", &end)?;
105+
Bounds::Dates { start, end }
106+
}
107+
(Some(Bound::Date(start)), None) => {
108+
check_in_future("start", &start)?;
109+
Bounds::Dates {
110+
start,
111+
end: find_latest_nightly()?,
112+
}
113+
}
114+
(None, Some(Bound::Date(end))) => {
115+
check_in_future("end", &end)?;
116+
if args.by_commit {
117+
bail!("--by-commit with an end date requires --start to be specified");
118+
}
119+
Bounds::SearchNightlyBackwards { end }
120+
}
121+
122+
// Mixed not supported.
123+
(Some(Bound::Commit(_)), Some(Bound::Date(_)))
124+
| (Some(Bound::Date(_)), Some(Bound::Commit(_))) => bail!(
125+
"cannot take different types of bounds for start/end, \
126+
got start: {:?} and end {:?}",
127+
args.start,
128+
args.end
129+
),
130+
};
131+
if let Bounds::Dates { start, end } = &bounds {
132+
if end < start {
133+
bail!("end should be after start, got start: {start} and end {end}");
134+
}
135+
if args.by_commit {
136+
eprintln!("finding commit range that corresponds to dates specified");
137+
let bounds = Bounds::Commits {
138+
start: date_to_sha(&start)?,
139+
end: date_to_sha(&end)?,
140+
};
141+
return Ok(bounds);
142+
}
143+
}
144+
Ok(bounds)
145+
}
146+
}
147+
148+
/// Translates a tag-like bound (such as `1.62.0`) to a `Bound::Date` so that
149+
/// bisecting works for versions older than 167 days.
150+
fn translate_tags(args: &Opts) -> anyhow::Result<(Option<Bound>, Option<Bound>)> {
151+
let is_tag = |bound: &Option<Bound>| -> bool {
152+
match bound {
153+
Some(Bound::Commit(commit)) => commit.contains('.'),
154+
None | Some(Bound::Date(_)) => false,
155+
}
156+
};
157+
let is_datelike = |bound: &Option<Bound>| -> bool {
158+
matches!(bound, None | Some(Bound::Date(_))) || is_tag(bound)
159+
};
160+
if !(is_datelike(&args.start) && is_datelike(&args.end)) {
161+
// If the user specified an actual commit for one bound, then don't
162+
// even try to convert the other bound to a date.
163+
return Ok((args.start.clone(), args.end.clone()));
164+
}
165+
let fixup = |which: &str, bound: &Option<Bound>| -> anyhow::Result<Option<Bound>> {
166+
if is_tag(bound) {
167+
if let Some(Bound::Commit(tag)) = bound {
168+
let date = args
169+
.access
170+
.repo()
171+
.bound_to_date(Bound::Commit(tag.clone()))?;
172+
eprintln!(
173+
"translating --{which}={tag} to {date}",
174+
date = date.format(YYYY_MM_DD)
175+
);
176+
return Ok(Some(Bound::Date(date)));
177+
}
178+
}
179+
Ok(bound.clone())
180+
};
181+
Ok((fixup("start", &args.start)?, fixup("end", &args.end)?))
182+
}
183+
184+
/// Returns the commit SHA of the nightly associated with the given date.
185+
fn date_to_sha(date: &NaiveDate) -> anyhow::Result<String> {
186+
let date_str = date.format(YYYY_MM_DD);
187+
let url = format!("{NIGHTLY_SERVER}/{date_str}/channel-rust-nightly-git-commit-hash.txt");
188+
189+
eprintln!("fetching {url}");
190+
let client = Client::new();
191+
let name = format!("nightly manifest {date_str}");
192+
let mut response = download_progress(&client, &name, &url)?;
193+
let mut commit = String::new();
194+
response.read_to_string(&mut commit)?;
195+
196+
eprintln!("converted {date_str} to {commit}");
197+
198+
Ok(commit)
199+
}
200+
201+
/// Returns the date of the nightly toolchain currently installed. If no
202+
/// nightly is found, then it goes to the network to determine the date of the
203+
/// latest nightly.
204+
fn installed_nightly_or_latest() -> anyhow::Result<GitDate> {
205+
if let Some(date) = Toolchain::default_nightly() {
206+
return Ok(date);
207+
}
208+
find_latest_nightly()
209+
}
210+
211+
/// Returns the date of the latest nightly (fetched from the network).
212+
fn find_latest_nightly() -> anyhow::Result<GitDate> {
213+
let url = format!("{NIGHTLY_SERVER}/channel-rust-nightly.toml");
214+
eprintln!("fetching {url}");
215+
let client = Client::new();
216+
let mut response = download_progress(&client, "nightly manifest", &url)?;
217+
let mut manifest = String::new();
218+
response.read_to_string(&mut manifest)?;
219+
let manifest: toml::Value = toml::from_str(&manifest)?;
220+
let date = manifest["date"].as_str().expect("date is a string");
221+
let date = NaiveDate::parse_from_str(date, "%Y-%m-%d")?;
222+
eprintln!("determined the latest nightly is {date}");
223+
Ok(date)
224+
}

0 commit comments

Comments
 (0)