Skip to content

Commit

Permalink
Do not access remotes unless necessary (#115)
Browse files Browse the repository at this point in the history
* Make cache::clear part of ProtofetchGitCache

* Make sure all cache access is encapsulated in the ProtofetchGitCache

* Do not access remotes unless necessary
  • Loading branch information
rtimush authored Oct 31, 2023
1 parent 73af2f7 commit 619ffc9
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 233 deletions.
2 changes: 1 addition & 1 deletion src/api/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{env, error::Error, path::PathBuf};

use home::home_dir;

use crate::{cache::ProtofetchGitCache, Protofetch};
use crate::{git::cache::ProtofetchGitCache, Protofetch};

#[derive(Default)]
pub struct ProtofetchBuilder {
Expand Down
7 changes: 4 additions & 3 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::{
};

use crate::{
cache::ProtofetchGitCache,
cli::command_handlers::{do_clean, do_clear_cache, do_fetch, do_init, do_lock, do_migrate},
cli::command_handlers::{do_clean, do_fetch, do_init, do_lock, do_migrate},
git::cache::ProtofetchGitCache,
};

mod builder;
Expand Down Expand Up @@ -89,6 +89,7 @@ impl Protofetch {
}

pub fn clear_cache(&self) -> Result<(), Box<dyn Error>> {
do_clear_cache(&self.cache)
self.cache.clear()?;
Ok(())
}
}
33 changes: 33 additions & 0 deletions src/cache/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::path::PathBuf;

use crate::{
git::cache::ProtofetchGitCache,
model::protofetch::{Coordinate, DependencyName, RevisionSpecification},
};

use super::RepositoryCache;

impl RepositoryCache for ProtofetchGitCache {
fn fetch(
&self,
coordinate: &Coordinate,
specification: &RevisionSpecification,
commit_hash: &str,
) -> anyhow::Result<()> {
let repository = self.repository(coordinate)?;
repository.fetch_commit(specification, commit_hash)?;
Ok(())
}

fn create_worktree(
&self,
coordinate: &Coordinate,
commit_hash: &str,
name: &DependencyName,
) -> anyhow::Result<PathBuf> {
let path = self
.repository(coordinate)?
.create_worktree(name, commit_hash)?;
Ok(path)
}
}
21 changes: 21 additions & 0 deletions src/cache/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod git;

use std::path::PathBuf;

use crate::model::protofetch::{Coordinate, DependencyName, RevisionSpecification};

pub trait RepositoryCache {
fn fetch(
&self,
coordinate: &Coordinate,
specification: &RevisionSpecification,
commit_hash: &str,
) -> anyhow::Result<()>;

fn create_worktree(
&self,
coordinate: &Coordinate,
commit_hash: &str,
name: &DependencyName,
) -> anyhow::Result<PathBuf>;
}
25 changes: 3 additions & 22 deletions src/cli/command_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use log::{debug, info};

use crate::{
api::LockMode,
cache::ProtofetchGitCache,
fetch,
git::cache::ProtofetchGitCache,
model::{
protodep::ProtodepDescriptor,
protofetch::{lock::LockFile, Descriptor},
Expand All @@ -17,7 +17,6 @@ use std::{
};

const DEFAULT_OUTPUT_DIRECTORY_NAME: &str = "proto_src";
const CACHE_WORKSPACES_DIRECTORY_NAME: &str = "dependencies";

/// Handler to fetch command
pub fn do_fetch(
Expand All @@ -32,18 +31,13 @@ pub fn do_fetch(

let lockfile = do_lock(lock_mode, cache, root, module_file_name, lock_file_name)?;

let cache_dependencies_directory_path = cache.location.join(CACHE_WORKSPACES_DIRECTORY_NAME);
let output_directory_name = output_directory_name
.or_else(|| module_descriptor.proto_out_dir.as_ref().map(Path::new))
.unwrap_or(Path::new(DEFAULT_OUTPUT_DIRECTORY_NAME));
fetch::fetch_sources(cache, &lockfile, &cache_dependencies_directory_path)?;
fetch::fetch_sources(cache, &lockfile)?;

//Copy proto_out files to actual target
proto::copy_proto_files(
&root.join(output_directory_name),
&cache_dependencies_directory_path,
&lockfile,
)?;
proto::copy_proto_files(cache, &lockfile, &root.join(output_directory_name))?;

Ok(())
}
Expand Down Expand Up @@ -177,19 +171,6 @@ pub fn do_clean(
Ok(())
}

pub fn do_clear_cache(cache: &ProtofetchGitCache) -> Result<(), Box<dyn Error>> {
if cache.location.exists() {
info!(
"Clearing protofetch repository cache {}.",
&cache.location.display()
);
std::fs::remove_dir_all(&cache.location)?;
Ok(())
} else {
Ok(())
}
}

fn load_module_descriptor(
root: &Path,
module_file_name: &Path,
Expand Down
61 changes: 11 additions & 50 deletions src/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
str::Utf8Error,
};
use std::{collections::BTreeMap, str::Utf8Error};

use crate::{
cache::{CacheError, RepositoryCache},
cache::RepositoryCache,
model::protofetch::{
lock::{LockFile, LockedDependency},
Dependency, DependencyName, Descriptor,
},
proto_repository::ProtoRepository,
resolver::ModuleResolver,
};
use log::{debug, error, info};
use log::{error, info};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum FetchError {
#[error("Error while fetching repo from cache: {0}")]
Cache(#[from] CacheError),
Cache(anyhow::Error),
#[error("Git error: {0}")]
GitError(#[from] git2::Error),
#[error("Error while decoding utf8 bytes from blob: {0}")]
BlobRead(#[from] Utf8Error),
#[error("Error while parsing descriptor")]
Parsing(#[from] crate::model::ParseError),
#[error("Bad output dir {0}")]
BadOutputDir(String),
#[error("Error while processing protobuf repository: {0}")]
ProtoRepoError(#[from] crate::proto_repository::ProtoRepoError),
ProtoRepoError(#[from] crate::git::repository::ProtoRepoError),
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
#[error(transparent)]
Expand Down Expand Up @@ -114,47 +107,15 @@ pub fn lock(
})
}

pub fn fetch_sources<Cache: RepositoryCache>(
cache: &Cache,
lockfile: &LockFile,
cache_src_dir: &Path,
) -> Result<(), FetchError> {
pub fn fetch_sources(cache: &impl RepositoryCache, lockfile: &LockFile) -> Result<(), FetchError> {
info!("Fetching dependencies source files...");

if !cache_src_dir.exists() {
std::fs::create_dir_all(cache_src_dir)?;
for dep in &lockfile.dependencies {
cache
.fetch(&dep.coordinate, &dep.specification, &dep.commit_hash)
.map_err(FetchError::Cache)?;
}

if cache_src_dir.is_dir() {
for dep in &lockfile.dependencies {
//If the dependency is already in the cache, we don't need to fetch it again
if cache_src_dir
.join(&dep.name.value)
.join(PathBuf::from(&dep.commit_hash))
.exists()
{
debug!("Skipping fetching {:?}. Already in cache", dep.name);
continue;
}
let repo = cache.clone_or_update(&dep.coordinate)?;
let work_tree_res = repo.create_worktrees(
&lockfile.module_name,
&dep.name,
&dep.commit_hash,
cache_src_dir,
);
if let Err(err) = work_tree_res {
error!("Error while trying to create worktrees {err}. \
Most likely the worktree sources have been deleted but the worktree metadata has not. \
Please delete the cache and run protofetch fetch again.")
}
}
Ok(())
} else {
Err(FetchError::BadOutputDir(
cache_src_dir.to_str().unwrap_or("").to_string(),
))
}
Ok(())
}

#[cfg(test)]
Expand Down
103 changes: 46 additions & 57 deletions src/cache.rs → src/git/cache.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
use std::path::{Path, PathBuf};

use git2::Config;
use git2::{build::RepoBuilder, Cred, CredentialType, FetchOptions, RemoteCallbacks, Repository};
use log::trace;
use git2::{
build::RepoBuilder, Config, Cred, CredentialType, FetchOptions, RemoteCallbacks, Repository,
};
use log::{info, trace};
use thiserror::Error;

use crate::{model::protofetch::Coordinate, proto_repository::ProtoGitRepository};
use crate::{git::repository::ProtoGitRepository, model::protofetch::Coordinate};

use crate::proto_repository::ProtoRepository;

pub trait RepositoryCache {
type Repository: ProtoRepository;

fn clone_or_update(&self, entry: &Coordinate) -> Result<Self::Repository, CacheError>;
}
const WORKTREES_DIR: &str = "dependencies";

pub struct ProtofetchGitCache {
pub location: PathBuf,
location: PathBuf,
worktrees: PathBuf,
git_config: Config,
}

Expand All @@ -30,43 +26,48 @@ pub enum CacheError {
IO(#[from] std::io::Error),
}

impl RepositoryCache for ProtofetchGitCache {
type Repository = ProtoGitRepository;
impl ProtofetchGitCache {
pub fn new(location: PathBuf, git_config: Config) -> Result<ProtofetchGitCache, CacheError> {
if location.exists() {
if !location.is_dir() {
return Err(CacheError::BadLocation {
location: location.to_str().unwrap_or("").to_string(),
});
}
} else {
std::fs::create_dir_all(&location)?;
}

fn clone_or_update(&self, entry: &Coordinate) -> Result<Self::Repository, CacheError> {
let repo = match self.get_entry(entry) {
None => self.clone_repo(entry)?,
Some(path) => {
let mut repo = self.open_entry(&path)?;
let worktrees = location.join(WORKTREES_DIR);
Ok(ProtofetchGitCache {
location,
worktrees,
git_config,
})
}

self.fetch(&mut repo)?;
pub fn clear(&self) -> anyhow::Result<()> {
if self.location.exists() {
info!(
"Clearing protofetch repository cache {}.",
&self.location.display()
);
std::fs::remove_dir_all(&self.location)?;
}
Ok(())
}

repo
}
pub fn repository(&self, entry: &Coordinate) -> Result<ProtoGitRepository, CacheError> {
let repo = match self.get_entry(entry) {
None => self.clone_repo(entry)?,
Some(path) => self.open_entry(&path)?,
};

Ok(ProtoGitRepository::new(repo))
Ok(ProtoGitRepository::new(self, repo))
}
}

impl ProtofetchGitCache {
pub fn new(location: PathBuf, git_config: Config) -> Result<ProtofetchGitCache, CacheError> {
if location.exists() && location.is_dir() {
Ok(ProtofetchGitCache {
location,
git_config,
})
} else if !location.exists() {
std::fs::create_dir_all(&location)?;
Ok(ProtofetchGitCache {
location,
git_config,
})
} else {
Err(CacheError::BadLocation {
location: location.to_str().unwrap_or("").to_string(),
})
}
pub fn worktrees_path(&self) -> &Path {
&self.worktrees
}

fn get_entry(&self, entry: &Coordinate) -> Option<PathBuf> {
Expand All @@ -86,7 +87,7 @@ impl ProtofetchGitCache {

fn clone_repo(&self, entry: &Coordinate) -> Result<Repository, CacheError> {
let mut repo_builder = RepoBuilder::new();
let options = ProtofetchGitCache::fetch_options(&self.git_config)?;
let options = self.fetch_options()?;
repo_builder.bare(true).fetch_options(options);

let url = entry.url();
Expand All @@ -96,19 +97,7 @@ impl ProtofetchGitCache {
.map_err(|e| e.into())
}

fn fetch(&self, repo: &mut Repository) -> Result<(), CacheError> {
let mut remote = repo.find_remote("origin")?;
let refspecs: Vec<String> = remote
.refspecs()
.filter_map(|refspec| refspec.str().map(|s| s.to_string()))
.collect();
let options = &mut ProtofetchGitCache::fetch_options(&self.git_config)?;
remote.fetch(&refspecs, Some(options), None)?;

Ok(())
}

fn fetch_options(config: &Config) -> Result<FetchOptions<'_>, CacheError> {
pub(super) fn fetch_options(&self) -> Result<FetchOptions<'_>, CacheError> {
let mut callbacks = RemoteCallbacks::new();
// Consider using https://crates.io/crates/git2_credentials that supports
// more authentication options
Expand All @@ -129,7 +118,7 @@ impl ProtofetchGitCache {
}
// HTTP auth
if allowed_types.contains(CredentialType::USER_PASS_PLAINTEXT) {
return Cred::credential_helper(config, url, username);
return Cred::credential_helper(&self.git_config, url, username);
}
Err(git2::Error::from_str("no valid authentication available"))
});
Expand Down
2 changes: 2 additions & 0 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod cache;
pub mod repository;
Loading

0 comments on commit 619ffc9

Please sign in to comment.