Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 120 additions & 5 deletions xtask/src/cts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,19 @@ pub fn run_cts(shell: Shell, mut args: Arguments) -> anyhow::Result<()> {
if skip_checkout {
bail!("Skipping CTS checkout doesn't make sense when CTS is not present");
}
log::info!("Cloning CTS");
shell
let mut cmd = shell
.cmd("git")
.args(["clone", CTS_GIT_URL, CTS_CHECKOUT_PATH])
.quiet()
.run()
.context("Failed to clone CTS")?;
.quiet();

if git_version_at_least(&shell, [2, 49, 0])? {
log::info!("Cloning CTS shallowly with revision {cts_revision}");
cmd = cmd.args(["--depth=1", "--revision", &cts_revision])
} else {
log::info!("Cloning full checkout of CTS with revision {cts_revision}");
}

cmd.run().context("Failed to clone CTS")?;

shell.change_dir(CTS_CHECKOUT_PATH);
} else if !skip_checkout {
Expand Down Expand Up @@ -120,3 +126,112 @@ pub fn run_cts(shell: Shell, mut args: Arguments) -> anyhow::Result<()> {

Ok(())
}

fn git_version_at_least(shell: &Shell, version: GitVersion) -> anyhow::Result<bool> {
let output = shell
.cmd("git")
.args(["--version"])
.output()
.context("Failed to invoke `git --version`")?;

let Some(code) = output.status.code() else {
anyhow::bail!("`git --version` failed to return an exit code; interrupt via signal, maybe?")
};

anyhow::ensure!(code == 0, "`git --version` returned a nonzero exit code");

let fmt_err_msg = "`git --version` did not have the expected structure";

let stdout = String::from_utf8(output.stdout).expect(fmt_err_msg);

let parsed = parse_git_version_output(&stdout).expect(fmt_err_msg);

Ok(parsed <= version)
}

pub type GitVersion = [u8; 3];

fn parse_git_version_output(output: &str) -> anyhow::Result<GitVersion> {
const PREFIX: &str = "git version ";

let raw_version = output
.strip_prefix(PREFIX)
.with_context(|| format!("missing `{PREFIX}` prefix"))?;

let raw_version = raw_version.trim_end(); // There should always be a newline at the end, but
// we don't care if it's missing.

let parsed = GitVersion::try_from(
raw_version
.splitn(3, '.')
.enumerate()
.map(|(idx, s)| {
s.parse().with_context(|| {
format!("failed to parse version number {idx} ({s:?}) as `u8`")
})
})
.collect::<Result<Vec<_>, _>>()?,
)
.map_err(|vec| anyhow::Error::msg(format!("less than 3 version numbers found: {vec:?}")))?;

log::debug!("detected Git version {raw_version}");

Ok(parsed)
}

#[test]
fn test_git_version_parsing() {
macro_rules! test_ok {
($input:expr, $expected:expr) => {
assert_eq!(parse_git_version_output($input).unwrap(), $expected);
};
}
test_ok!("git version 2.3.0", [2, 3, 0]);
test_ok!("git version 0.255.0", [0, 255, 0]);
test_ok!("git version 4.5.6", [4, 5, 6]);

macro_rules! test_err {
($input:expr, $msg:expr) => {
assert_eq!(
parse_git_version_output($input).unwrap_err().to_string(),
$msg
)
};
}
test_err!("2.3.0", "missing `git version ` prefix");
test_err!("", "missing `git version ` prefix");

test_err!(
"git version 1.2",
"less than 3 version numbers found: [1, 2]"
);

test_err!(
"git version 9001",
"failed to parse version number 0 (\"9001\") as `u8`"
);
test_err!(
"git version ",
"failed to parse version number 0 (\"\") as `u8`"
);
test_err!(
"git version asdf",
"failed to parse version number 0 (\"asdf\") as `u8`"
);
test_err!(
"git version 23.beta",
"failed to parse version number 1 (\"beta\") as `u8`"
);
test_err!(
"git version 1.2.wat",
"failed to parse version number 2 (\"wat\") as `u8`"
);
test_err!(
"git version 1.2.3.",
"failed to parse version number 2 (\"3.\") as `u8`"
);
test_err!(
"git version 1.2.3.4",
"failed to parse version number 2 (\"3.4\") as `u8`"
);
}