pub mod authentication;
pub mod commit;
pub mod signature;
pub use self::{commit::Commit, signature::Signature};
use self::authentication::with_authentication;
use crate::{
collection::Collection,
error::{Error, ErrorKind},
};
use git2;
use std::{fs, path::PathBuf, vec};
pub const DEFAULT_URL: &str = "https://github.com/RustSec/advisory-db.git";
pub const DAYS_UNTIL_STALE: usize = 90;
pub(crate) const ADVISORY_DB_DIRECTORY: &str = "advisory-db";
const LOCAL_MASTER_REF: &str = "refs/heads/master";
const REMOTE_MASTER_REF: &str = "refs/remotes/origin/master";
pub struct Repository {
path: PathBuf,
repo: git2::Repository,
}
impl Repository {
pub fn default_path() -> PathBuf {
home::cargo_home()
.unwrap_or_else(|err| {
panic!("Error locating Cargo home directory: {}", err);
})
.join(ADVISORY_DB_DIRECTORY)
}
pub fn fetch_default_repo() -> Result<Self, Error> {
Self::fetch(DEFAULT_URL, Repository::default_path(), true)
}
pub fn fetch<P: Into<PathBuf>>(
url: &str,
into_path: P,
ensure_fresh: bool,
) -> Result<Self, Error> {
if !url.starts_with("https://") {
fail!(
ErrorKind::BadParam,
"expected {} to start with https://",
url
);
}
let path = into_path.into();
if let Some(parent) = path.parent() {
if !parent.is_dir() {
fs::create_dir_all(parent)?;
}
} else {
fail!(ErrorKind::BadParam, "invalid directory: {}", path.display())
}
if path.is_dir() && fs::read_dir(&path)?.next().is_none() {
fs::remove_dir(&path)?;
}
let git_config = git2::Config::new()?;
with_authentication(url, &git_config, |f| {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(f);
let mut proxy_opts = git2::ProxyOptions::new();
proxy_opts.auto();
let mut fetch_opts = git2::FetchOptions::new();
fetch_opts.remote_callbacks(callbacks);
fetch_opts.proxy_options(proxy_opts);
if path.exists() {
let repo = git2::Repository::open(&path)?;
let refspec = LOCAL_MASTER_REF.to_owned() + ":" + REMOTE_MASTER_REF;
let mut remote = repo.remote_anonymous(url)?;
remote.fetch(&[refspec.as_str()], Some(&mut fetch_opts), None)?;
let remote_master_ref = repo.find_reference(REMOTE_MASTER_REF)?;
let remote_target = remote_master_ref.target().unwrap();
let mut local_master_ref = repo.find_reference(LOCAL_MASTER_REF)?;
local_master_ref.set_target(
remote_target,
&format!(
"rustsec: moving master to {}: {}",
REMOTE_MASTER_REF, &remote_target
),
)?;
} else {
git2::build::RepoBuilder::new()
.fetch_options(fetch_opts)
.clone(url, &path)?;
}
Ok(())
})?;
let repo = Self::open(path)?;
let latest_commit = repo.latest_commit()?;
latest_commit.reset(&repo)?;
if latest_commit.signature.is_none() {
fail!(
ErrorKind::Repo,
"no signature on commit {}: {} ({})",
latest_commit.commit_id,
latest_commit.summary,
latest_commit.author
);
}
if ensure_fresh {
latest_commit.ensure_fresh()?;
}
Ok(repo)
}
pub fn open<P: Into<PathBuf>>(into_path: P) -> Result<Self, Error> {
let path = into_path.into();
let repo = git2::Repository::open(&path)?;
if repo.state() == git2::RepositoryState::Clean {
Ok(Repository { path, repo })
} else {
fail!(ErrorKind::Repo, "bad repository state: {:?}", repo.state())
}
}
pub fn latest_commit(&self) -> Result<Commit, Error> {
Commit::from_repo_head(self)
}
pub fn advisories(&self) -> Result<Vec<PathBuf>, Error> {
let mut paths = vec![];
for collection in &[Collection::Crates, Collection::Rust] {
let collection_path = self.path.join(collection.as_str());
if let Ok(collection_entry) = fs::read_dir(&collection_path) {
for dir_entry in collection_entry {
for advisory_entry in fs::read_dir(dir_entry?.path())? {
paths.push(advisory_entry?.path().to_owned());
}
}
}
}
Ok(paths)
}
}