use crate::api;
use crate::constants;
use crate::core::index::{CommitEntryReader, CommitWriter, RefWriter};
use crate::error::OxenError;
use crate::model::DataTypeStat;
use crate::model::EntryDataType;
use crate::model::RepoStats;
use crate::model::{CommitStats, LocalRepository, RepositoryNew};
use crate::util;
use jwalk::WalkDir;
use std::collections::HashMap;
use std::path::Path;
pub fn get_by_namespace_and_name(
sync_dir: &Path,
namespace: impl AsRef<str>,
name: impl AsRef<str>,
) -> Result<Option<LocalRepository>, OxenError> {
let namespace = namespace.as_ref();
let name = name.as_ref();
let repo_dir = sync_dir.join(namespace).join(name);
if !repo_dir.exists() {
log::debug!("Repo does not exist: {:?}", repo_dir);
return Ok(None);
}
let repo = LocalRepository::from_dir(&repo_dir)?;
Ok(Some(repo))
}
pub fn get_commit_stats_from_id(
repo: &LocalRepository,
commit_id: &str,
) -> Result<Option<CommitStats>, OxenError> {
match api::local::commits::get_by_id(repo, commit_id) {
Ok(Some(commit)) => {
let reader = CommitEntryReader::new(repo, &commit)?;
Ok(Some(CommitStats {
commit,
num_entries: reader.num_entries()?,
num_synced_files: util::fs::rcount_files_in_dir(&repo.path),
}))
}
Ok(None) => Ok(None),
Err(err) => {
log::error!("unable to get commit by id: {}", commit_id);
Err(err)
}
}
}
pub fn get_repo_stats(repo: &LocalRepository) -> RepoStats {
let mut data_size: u64 = 0;
let mut data_types: HashMap<EntryDataType, DataTypeStat> = HashMap::new();
match api::local::commits::head_commit(repo) {
Ok(commit) => match api::local::entries::list_all(repo, &commit) {
Ok(entries) => {
for entry in entries {
data_size += entry.num_bytes;
let full_path = repo.path.join(&entry.path);
let data_type = util::fs::file_data_type(&full_path);
let data_type_stat = DataTypeStat {
data_size: entry.num_bytes,
data_type: data_type.to_owned(),
file_count: 1,
};
let stat = data_types.entry(data_type).or_insert(data_type_stat);
stat.file_count += 1;
stat.data_size += entry.num_bytes;
}
}
Err(err) => {
log::error!("Err: could not list entries for repo stats {err}");
}
},
Err(err) => {
log::error!("Err: could not get repo stats {err}");
}
}
RepoStats {
data_size,
data_types,
}
}
pub fn list_namespaces(sync_dir: &Path) -> Result<Vec<String>, OxenError> {
log::debug!(
"api::local::entries::list_namespaces repositories for sync dir: {:?}",
sync_dir
);
let mut namespaces: Vec<String> = vec![];
for path in std::fs::read_dir(sync_dir)? {
let path = path.unwrap().path();
if is_namespace_dir(&path) {
let name = path.file_name().unwrap().to_str().unwrap();
namespaces.push(String::from(name));
}
}
Ok(namespaces)
}
fn is_namespace_dir(path: &Path) -> bool {
if let Some(name) = path.to_str() {
return path.is_dir()
&& !name.starts_with(constants::OXEN_HIDDEN_DIR)
&& !list_repos_in_namespace(path).is_empty();
}
false
}
pub fn list_repos_in_namespace(namespace_path: &Path) -> Vec<LocalRepository> {
log::debug!(
"api::local::entries::list_repos_in_namespace repositories for dir: {:?}",
namespace_path
);
let mut repos: Vec<LocalRepository> = vec![];
for entry in WalkDir::new(namespace_path)
.into_iter()
.filter_map(|e| e.ok())
{
let local_dir = entry.path();
let oxen_dir = util::fs::oxen_hidden_dir(&local_dir);
log::debug!(
"api::local::entries::list_repos_in_namespace got local dir {:?}",
local_dir
);
if oxen_dir.exists() {
if let Ok(repository) = LocalRepository::from_dir(&local_dir) {
repos.push(repository);
}
}
}
repos
}
pub fn create_empty(
sync_dir: &Path,
new_repo: RepositoryNew,
) -> Result<LocalRepository, OxenError> {
let repo_dir = sync_dir
.join(&new_repo.namespace)
.join(Path::new(&new_repo.name));
if repo_dir.exists() {
log::error!("Repository already exists {repo_dir:?}");
return Err(OxenError::repo_already_exists(new_repo));
}
log::debug!("create_empty repo dir: {:?}", repo_dir);
std::fs::create_dir_all(&repo_dir)?;
let hidden_dir = util::fs::oxen_hidden_dir(&repo_dir);
log::debug!("create_empty hidden dir: {:?}", hidden_dir);
std::fs::create_dir_all(&hidden_dir)?;
let config_path = util::fs::config_filepath(&repo_dir);
let local_repo = LocalRepository::new(&repo_dir)?;
local_repo.save(&config_path)?;
let history_dir = util::fs::oxen_hidden_dir(&repo_dir).join(constants::HISTORY_DIR);
std::fs::create_dir_all(history_dir)?;
{
log::debug!("create_empty BEFORE ref writer: {:?}", local_repo.path);
let ref_writer = RefWriter::new(&local_repo)?;
ref_writer.set_head(constants::DEFAULT_BRANCH_NAME);
log::debug!("create_empty AFTER ref writer: {:?}", local_repo.path);
}
if let Some(root_commit) = &new_repo.root_commit {
let commit_writer = CommitWriter::new(&local_repo)?;
commit_writer.add_commit_from_empty_status(root_commit)?;
}
Ok(local_repo)
}
pub fn delete(repo: LocalRepository) -> Result<LocalRepository, OxenError> {
if !repo.path.exists() {
let err = format!("Repository does not exist {:?}", repo.path);
return Err(OxenError::basic_str(err));
}
log::debug!("Deleting repo directory: {:?}", repo);
util::fs::remove_dir_all(&repo.path)?;
Ok(repo)
}
#[cfg(test)]
mod tests {
use crate::api;
use crate::command;
use crate::constants;
use crate::error::OxenError;
use crate::model::{Commit, LocalRepository, RepositoryNew};
use crate::test;
use std::path::Path;
use time::OffsetDateTime;
#[test]
fn test_local_repository_api_create_empty_with_commit() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace: &str = "test-namespace";
let name: &str = "test-repo-name";
let initial_commit_id = format!("{}", uuid::Uuid::new_v4());
let timestamp = OffsetDateTime::now_utc();
let repo_new = RepositoryNew {
namespace: String::from(namespace),
name: String::from(name),
root_commit: Some(Commit {
id: initial_commit_id,
parent_ids: vec![],
message: String::from(constants::INITIAL_COMMIT_MSG),
author: String::from("Ox"),
email: String::from("ox@oxen.ai"),
timestamp,
}),
};
let _repo = api::local::repositories::create_empty(sync_dir, repo_new)?;
let repo_path = Path::new(&sync_dir)
.join(Path::new(namespace))
.join(Path::new(name));
assert!(repo_path.exists());
let _repo = LocalRepository::from_dir(&repo_path)?;
Ok(())
})
}
#[test]
fn test_local_repository_api_create_empty_no_commit() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace: &str = "test-namespace";
let name: &str = "test-repo-name";
let repo_new = RepositoryNew {
namespace: String::from(namespace),
name: String::from(name),
root_commit: None,
};
let _repo = api::local::repositories::create_empty(sync_dir, repo_new)?;
let repo_path = Path::new(&sync_dir)
.join(Path::new(namespace))
.join(Path::new(name));
assert!(repo_path.exists());
let _repo = LocalRepository::from_dir(&repo_path)?;
Ok(())
})
}
#[test]
fn test_local_repository_api_list_namespaces_one() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace: &str = "test-namespace";
let name: &str = "cool-repo";
let namespace_dir = sync_dir.join(namespace);
std::fs::create_dir_all(&namespace_dir)?;
let repo_dir = namespace_dir.join(name);
command::init(&repo_dir)?;
let namespaces = api::local::repositories::list_namespaces(sync_dir)?;
assert_eq!(namespaces.len(), 1);
assert_eq!(namespaces[0], namespace);
Ok(())
})
}
#[test]
fn test_local_repository_api_list_multiple_namespaces() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace_1 = "my-namespace-1";
let namespace_1_dir = sync_dir.join(namespace_1);
let namespace_2 = "my-namespace-2";
let namespace_2_dir = sync_dir.join(namespace_2);
let namespace_3 = "my-namespace-3";
let _ = sync_dir.join(namespace_3);
let _ = command::init(&namespace_1_dir.join("testing1"))?;
let _ = command::init(&namespace_1_dir.join("testing2"))?;
let _ = command::init(&namespace_2_dir.join("testing3"))?;
let repos = api::local::repositories::list_namespaces(sync_dir)?;
assert_eq!(repos.len(), 2);
Ok(())
})
}
#[test]
fn test_local_repository_api_list_multiple_within_namespace() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace = "my-namespace";
let namespace_dir = sync_dir.join(namespace);
let _ = command::init(&namespace_dir.join("testing1"))?;
let _ = command::init(&namespace_dir.join("testing2"))?;
let _ = command::init(&namespace_dir.join("testing3"))?;
let repos = api::local::repositories::list_repos_in_namespace(&namespace_dir);
assert_eq!(repos.len(), 3);
Ok(())
})
}
#[test]
fn test_local_repository_api_get_by_name() -> Result<(), OxenError> {
test::run_empty_dir_test(|sync_dir| {
let namespace = "my-namespace";
let name = "my-repo";
let repo_dir = sync_dir.join(namespace).join(name);
std::fs::create_dir_all(&repo_dir)?;
let _ = command::init(&repo_dir)?;
let _repo =
api::local::repositories::get_by_namespace_and_name(sync_dir, namespace, name)?
.unwrap();
Ok(())
})
}
}