use crate::core;
use crate::core::versions::MinOxenVersion;
use crate::error::OxenError;
use crate::model::Commit;
use crate::model::Workspace;
use crate::model::file::TempFilePathNew;
use crate::model::{Branch, User};
use crate::view::ErrorFileInfo;
use std::path::{Path, PathBuf};
pub fn exists(workspace: &Workspace, path: impl AsRef<Path>) -> Result<bool, OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::workspaces::files::exists(workspace, path),
}
}
pub async fn add(workspace: &Workspace, path: impl AsRef<Path>) -> Result<PathBuf, OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::workspaces::files::add(workspace, path).await,
}
}
pub async fn add_with_opts(
workspace: &Workspace,
path: impl AsRef<Path>,
update_timestamp: bool,
) -> Result<PathBuf, OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => {
core::v_latest::workspaces::files::add_with_opts(workspace, path, update_timestamp)
.await
}
}
}
pub async fn rm(
workspace: &Workspace,
path: impl AsRef<Path>,
) -> Result<Vec<ErrorFileInfo>, OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::workspaces::files::rm(workspace, path).await,
}
}
pub fn unstage(workspace: &Workspace, path: impl AsRef<Path>) -> Result<(), OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::workspaces::files::unstage(workspace, path),
}
}
pub async fn import(
url: &str,
auth: &str,
directory: PathBuf,
filename: Option<String>,
workspace: &Workspace,
update_timestamp: bool,
) -> Result<(), OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => {
core::v_latest::workspaces::files::import(
url,
auth,
directory,
filename,
workspace,
update_timestamp,
)
.await?;
Ok(())
}
}
}
pub async fn upload_zip(
commit_message: &str,
user: &User,
temp_files: Vec<TempFilePathNew>,
workspace: &Workspace,
branch: &Branch,
) -> Result<Commit, OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => {
core::v_latest::workspaces::files::upload_zip(
commit_message,
user,
temp_files,
workspace,
branch,
)
.await
}
}
}
pub fn mv(
workspace: &Workspace,
path: impl AsRef<Path>,
new_path: impl AsRef<Path>,
) -> Result<(), OxenError> {
match workspace.base_repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::workspaces::files::mv(workspace, path, new_path),
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::config::UserConfig;
use crate::error::OxenError;
use crate::model::NewCommitBody;
use crate::repositories::{self, workspaces};
use crate::test;
#[tokio::test]
async fn test_mv_file_in_workspace() -> Result<(), OxenError> {
if std::env::consts::OS == "windows" {
return Ok(());
}
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let branch_name = "test-mv";
let branch = repositories::branches::create_checkout(&repo, branch_name)?;
let commit = repositories::commits::get_by_id(&repo, &branch.commit_id)?.unwrap();
let workspace_id = UserConfig::identifier()?;
let workspace = repositories::workspaces::create(&repo, &commit, workspace_id, true)?;
let original_path = Path::new("annotations")
.join("train")
.join("bounding_box.csv");
let new_path = Path::new("renamed").join("data").join("bbox_renamed.csv");
workspaces::files::mv(&workspace, &original_path, &new_path)?;
let status = workspaces::status::status(&workspace)?;
println!("Status after mv: {status:?}");
let removed_entry = status.staged_files.get(&original_path);
assert!(
removed_entry.is_some(),
"Original path should be in staged_files"
);
assert_eq!(
removed_entry.unwrap().status,
crate::model::StagedEntryStatus::Removed,
"Original path should have Removed status"
);
let added_entry = status.staged_files.get(&new_path);
assert!(added_entry.is_some(), "New path should be staged");
assert_eq!(
added_entry.unwrap().status,
crate::model::StagedEntryStatus::Added,
"New path should have Added status"
);
assert!(
!status.moved_files.is_empty(),
"Move should be detected in moved_files"
);
let user = UserConfig::get()?.to_user();
let new_commit = NewCommitBody {
author: user.name.clone(),
email: user.email.clone(),
message: "Moved file to new location".to_string(),
};
let commit =
workspaces::commit(&workspace, &new_commit, branch_name.to_string()).await?;
let new_file = repositories::tree::get_file_by_path(&repo, &commit, &new_path)?;
assert!(
new_file.is_some(),
"File should exist at new path after commit"
);
let old_file = repositories::tree::get_file_by_path(&repo, &commit, &original_path)?;
assert!(
old_file.is_none(),
"File should not exist at original path after commit"
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_exists_returns_false_when_no_files_staged() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let file = repo.path.join("hello.txt");
crate::util::fs::write_to_path(&file, "hello")?;
repositories::add(&repo, &file).await?;
let commit = repositories::commit(&repo, "Add hello.txt")?;
let workspace =
repositories::workspaces::create(&repo, &commit, "test-workspace", false)?;
let result = workspaces::files::exists(&workspace, std::path::Path::new("hello.txt"))?;
assert!(!result);
Ok(())
})
.await
}
#[tokio::test]
async fn test_exists_false_before_staging_true_after() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let file = repo.path.join("hello.txt");
crate::util::fs::write_to_path(&file, "hello")?;
repositories::add(&repo, &file).await?;
let commit = repositories::commit(&repo, "Add hello.txt")?;
let workspace =
repositories::workspaces::create(&repo, &commit, "test-workspace", false)?;
let workspace_file = workspace.workspace_repo.path.join("hello.txt");
crate::util::fs::write_to_path(&workspace_file, "hello world")?;
let hello = Path::new("hello.txt");
let nonexistent = Path::new("does_not_exist.txt");
assert!(!workspaces::files::exists(&workspace, hello)?);
assert!(!workspaces::files::exists(&workspace, nonexistent)?);
workspaces::files::add(&workspace, &workspace_file).await?;
assert!(workspaces::files::exists(&workspace, hello)?);
assert!(!workspaces::files::exists(&workspace, nonexistent)?);
Ok(())
})
.await
}
#[tokio::test]
async fn test_import_rejects_private_ip() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let file = repo.path.join("hello.txt");
crate::util::fs::write_to_path(&file, "hello")?;
repositories::add(&repo, &file).await?;
let commit = repositories::commit(&repo, "Add hello.txt")?;
let workspace = repositories::workspaces::create_temporary(&repo, &commit).await?;
let cases = [
("http://127.0.0.1/secret", "loopback address"),
("http://10.0.0.1/internal", "private address"),
(
"http://169.254.169.254/latest/meta-data/",
"link-local/metadata address",
),
("http://[::1]/secret", "IPv6 loopback"),
("file:///etc/passwd", "non-HTTP scheme"),
];
for (url, label) in cases {
let result = workspaces::files::import(
url,
"",
std::path::PathBuf::from("data"),
None,
&workspace,
false,
)
.await;
assert!(result.is_err(), "should reject {label}");
}
Ok(())
})
.await
}
}