From 6f9ccf054b8af59243e50b24d2c8b36e22ab3ac4 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sun, 14 Feb 2021 11:57:45 +0000 Subject: Initial commit --- src/nixpkgs.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/nixpkgs.rs (limited to 'src/nixpkgs.rs') diff --git a/src/nixpkgs.rs b/src/nixpkgs.rs new file mode 100644 index 0000000..7e622c2 --- /dev/null +++ b/src/nixpkgs.rs @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later WITH GPL-3.0-linking-exception +// SPDX-FileCopyrightText: 2021 Alyssa Ross + +use std::collections::BTreeSet; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::fmt::{self, Display, Formatter}; +use std::os::unix::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::ExitStatus; + +use async_std::io; +use async_std::process::{Command, Stdio}; + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + ExitFailure(ExitStatus), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use Error::*; + match self { + Io(e) => write!(f, "git: {}", e), + ExitFailure(e) => match e.code() { + Some(code) => write!(f, "git exited {}", code), + None => write!(f, "git killed by signal {}", e.signal().unwrap()), + }, + } + } +} + +impl std::error::Error for Error {} + +type Result = std::result::Result; + +fn check_status(status: ExitStatus) -> Result<()> { + if status.success() { + Ok(()) + } else { + Err(Error::ExitFailure(status)) + } +} + +pub struct Nixpkgs<'a> { + path: &'a Path, + remote_name: &'a Path, +} + +impl<'a> Nixpkgs<'a> { + pub fn new(path: &'a Path, remote_name: &'a Path) -> Self { + Self { path, remote_name } + } + + fn git_command(&self, subcommand: impl AsRef) -> Command { + let mut command = Command::new("git"); + command.arg("-C"); + command.arg(&self.path); + command.arg(subcommand); + command + } + + async fn git_branch_contains(&self, commit: &str) -> Result> { + let output = self + .git_command("branch") + .args(&["-r", "--format=%(refname)", "--contains"]) + .arg(commit) + .stderr(Stdio::inherit()) + .output() + .await + .map_err(Error::Io)?; + + check_status(output.status)?; + + Ok(output.stdout) + } + + async fn git_fetch_nixpkgs(&self) -> Result<()> { + // TODO: add refspecs + self.git_command("fetch") + .arg(&self.remote_name) + .status() + .await + .map_err(Error::Io) + .and_then(check_status) + } + + pub async fn branches_containing_commit( + &self, + commit: &str, + out: &mut BTreeSet, + ) -> Result<()> { + let output = match self.git_branch_contains(commit).await { + Err(Error::ExitFailure(status)) if status.code().is_some() => { + eprintln!("pr-tracker: git branch --contains failed; updating branches"); + + if let Err(e) = self.git_fetch_nixpkgs().await { + eprintln!("pr-tracker: fetching nixpkgs: {}", e); + // Carry on, because it might have fetched what we + // need before dying. + } + + self.git_branch_contains(commit).await? + } + + Ok(output) => output, + Err(e) => return Err(e), + }; + + let mut prefix = PathBuf::from("refs/remotes/"); + prefix.push(&self.remote_name); + + for branch_name in output + .split(|byte| *byte == b'\n') + .filter(|b| !b.is_empty()) + .map(OsStr::from_bytes) + .map(Path::new) + .filter_map(|r| r.strip_prefix(&prefix).ok()) + .map(Into::into) + { + out.insert(branch_name); + } + + Ok(()) + } +} -- cgit 1.4.1