use std::path::Path;
use crate::core;
use crate::core::versions::MinOxenVersion;
use crate::error::OxenError;
use crate::model::staged_data::StagedDataOpts;
use crate::model::{LocalRepository, StagedData};
pub async fn status(repo: &LocalRepository) -> Result<StagedData, OxenError> {
match repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::status::status(repo).await,
}
}
pub async fn status_from_opts(
repo: &LocalRepository,
opts: &StagedDataOpts,
) -> Result<StagedData, OxenError> {
match repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v10 not supported"),
_ => core::v_latest::status::status_from_opts(repo, opts).await,
}
}
pub async fn status_from_dir(
repo: &LocalRepository,
dir: impl AsRef<Path>,
) -> Result<StagedData, OxenError> {
match repo.min_version() {
MinOxenVersion::V0_10_0 => panic!("v0.10.0 no longer supported"),
_ => core::v_latest::status::status_from_dir(repo, dir).await,
}
}
#[cfg(test)]
mod tests {
use crate::error::OxenError;
use crate::model::StagedEntryStatus;
use crate::model::staged_data::StagedDataOpts;
use crate::opts::RestoreOpts;
use crate::opts::RmOpts;
use crate::repositories;
use crate::test;
use crate::util;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
#[tokio::test]
async fn test_command_status_empty() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let repo_status = repositories::status(&repo).await?;
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 0);
assert_eq!(repo_status.untracked_dirs.len(), 0);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_distinguishes_unrestorable_from_modified() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let path = repo.path.join("hello.txt");
util::fs::write_to_path(&path, "Hello World")?;
repositories::add(&repo, &path).await?;
let commit = repositories::commit(&repo, "Add hello.txt")?;
util::fs::write_to_path(&path, "drifted")?;
let status_before = repositories::status(&repo).await?;
assert!(
status_before
.modified_files
.iter()
.any(|p| p.ends_with("hello.txt"))
);
assert!(status_before.unrestorable_files.is_empty());
let head_node = repositories::tree::get_node_by_path(&repo, &commit, "hello.txt")?
.expect("hello.txt must be in HEAD's tree");
let blob_hash = head_node.hash.to_string();
let version_store = repo.version_store()?;
let blob_path = version_store.get_version_path(&blob_hash).await?;
assert!(blob_path.exists());
util::fs::remove_file(&*blob_path)?;
let status_after = repositories::status(&repo).await?;
assert!(
status_after
.unrestorable_files
.iter()
.any(|p| p.ends_with("hello.txt")),
"expected hello.txt under unrestorable_files, got: {:?}",
status_after.unrestorable_files
);
assert!(
!status_after
.modified_files
.iter()
.any(|p| p.ends_with("hello.txt")),
"expected hello.txt NOT under modified_files (it's unrestorable), got: {:?}",
status_after.modified_files
);
assert!(!status_after.is_clean());
assert!(status_after.has_modified_entries());
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_status_nothing_staged_full_directory() -> Result<(), OxenError> {
test::run_training_data_repo_test_no_commits_async(|repo| async move {
let repo_status = repositories::status(&repo).await?;
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 4);
assert_eq!(repo_status.untracked_dirs.len(), 5);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_add_one_file_top_level() -> Result<(), OxenError> {
test::run_training_data_repo_test_no_commits_async(|repo| async move {
repositories::add(&repo, repo.path.join(Path::new("labels.txt"))).await?;
let repo_status = repositories::status(&repo).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 1);
assert_eq!(repo_status.staged_files.len(), 1);
assert_eq!(repo_status.untracked_files.len(), 3);
assert_eq!(repo_status.untracked_dirs.len(), 5);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_status_shows_intermediate_directory_if_file_added()
-> Result<(), OxenError> {
test::run_training_data_repo_test_no_commits_async(|repo| async move {
repositories::add(
&repo,
repo.path.join(Path::new("annotations/train/one_shot.csv")),
)
.await?;
let repo_status = repositories::status(&repo).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 1);
assert_eq!(repo_status.staged_files.len(), 1);
assert_eq!(repo_status.untracked_dirs.len(), 5);
assert_eq!(repo_status.untracked_files.len(), 8);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_modified_files_status_with_search_paths() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let one_shot_relative_path = Path::new("annotations/train/one_shot.csv");
let one_shot_path = repo.path.join(one_shot_relative_path);
test::modify_txt_file(&one_shot_path, "new one shot coming in hot")?;
let labels_path = repo.path.join(Path::new("labels.txt"));
test::modify_txt_file(&labels_path, "new labels coming in hot")?;
let opts =
StagedDataOpts::from_paths(&[repo.path.join(Path::new("annotations/train"))]);
let repo_status = repositories::status::status_from_opts(&repo, &opts).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 0);
assert_eq!(repo_status.untracked_dirs.len(), 0);
assert_eq!(repo_status.modified_files.len(), 1);
assert!(
repo_status
.modified_files
.contains(&one_shot_relative_path.to_path_buf())
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_modified_files_status_with_file_search_paths() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let one_shot_relative_path = Path::new("annotations/train/one_shot.csv");
let one_shot_path = repo.path.join(one_shot_relative_path);
test::modify_txt_file(&one_shot_path, "new one shot coming in hot")?;
let labels_path = repo.path.join(Path::new("labels.txt"));
test::modify_txt_file(&labels_path, "new labels coming in hot")?;
let opts = StagedDataOpts::from_paths(&[repo
.path
.join(Path::new("annotations/train/one_shot.csv"))]);
let repo_status = repositories::status::status_from_opts(&repo, &opts).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 0);
assert_eq!(repo_status.untracked_dirs.len(), 0);
assert_eq!(repo_status.modified_files.len(), 1);
assert!(
repo_status
.modified_files
.contains(&one_shot_relative_path.to_path_buf())
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_ignore_directory_with_modified_files() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let one_shot_relative_path = Path::new("annotations/train/one_shot.csv");
let one_shot_path = repo.path.join(one_shot_relative_path);
test::modify_txt_file(&one_shot_path, "new one shot coming in hot")?;
let labels_relative_path = Path::new("labels.txt");
let labels_path = repo.path.join(labels_relative_path);
test::modify_txt_file(&labels_path, "new labels coming in hot")?;
let opts = StagedDataOpts {
ignore: Some(HashSet::from([repo.path.join(Path::new("annotations"))])),
..Default::default()
};
let repo_status = repositories::status::status_from_opts(&repo, &opts).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 0);
assert_eq!(repo_status.untracked_dirs.len(), 0);
assert_eq!(repo_status.modified_files.len(), 1);
assert!(
repo_status
.modified_files
.contains(&labels_relative_path.to_path_buf())
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_added_files_status_with_search_paths() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let one_shot_relative_path = Path::new("annotations/train/one_shot.csv");
let one_shot_path = repo.path.join(one_shot_relative_path);
test::modify_txt_file(&one_shot_path, "new one shot coming in hot")?;
repositories::add(&repo, &one_shot_path).await?;
let two_shot_relative_path = Path::new("annotations/train/two_shot.csv");
let two_shot_path = repo.path.join(two_shot_relative_path);
test::modify_txt_file(&two_shot_path, "new two shot coming in hot")?;
let untracked_relative_path = Path::new("untracked.txt");
let untracked_path = repo.path.join(untracked_relative_path);
test::modify_txt_file(&untracked_path, "I'm sneaking in there untracked")?;
let opts =
StagedDataOpts::from_paths(&[repo.path.join(Path::new("annotations/train"))]);
let repo_status = repositories::status::status_from_opts(&repo, &opts).await?;
repo_status.print();
assert_eq!(repo_status.staged_dirs.len(), 1);
assert_eq!(repo_status.staged_files.len(), 1);
assert_eq!(repo_status.untracked_files.len(), 0);
assert_eq!(repo_status.untracked_dirs.len(), 0);
assert_eq!(repo_status.modified_files.len(), 1);
assert!(
repo_status
.modified_files
.contains(&two_shot_relative_path.to_path_buf())
);
assert_eq!(repo_status.staged_files.len(), 1);
assert!(
repo_status
.staged_files
.contains_key(&one_shot_relative_path.to_path_buf())
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_commit_nothing_staged() -> Result<(), OxenError> {
test::run_empty_local_repo_test(|repo| {
let commits = repositories::commits::list(&repo)?;
let initial_len = commits.len();
let result = repositories::commit(&repo, "Should not work");
assert!(result.is_err());
let commits = repositories::commits::list(&repo)?;
assert_eq!(commits.len(), initial_len);
Ok(())
})
}
#[tokio::test]
async fn test_command_commit_nothing_staged_but_file_modified() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let commits = repositories::commits::list(&repo)?;
let initial_len = commits.len();
let labels_path = repo.path.join("labels.txt");
util::fs::write_to_path(labels_path, "changing this guy, but not committing")?;
let result = repositories::commit(&repo, "Should not work");
assert!(result.is_err());
let commits = repositories::commits::list(&repo)?;
assert_eq!(commits.len(), initial_len);
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_status_has_txt_file() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let hello_file = repo.path.join("hello.txt");
util::fs::write_to_path(hello_file, "Hello World")?;
let repo_status = repositories::status(&repo).await?;
assert_eq!(repo_status.staged_dirs.len(), 0);
assert_eq!(repo_status.staged_files.len(), 0);
assert_eq!(repo_status.untracked_files.len(), 1);
assert_eq!(repo_status.untracked_dirs.len(), 0);
Ok(())
})
.await
}
#[tokio::test]
async fn test_merge_conflict_shows_in_status() -> Result<(), OxenError> {
test::run_select_data_repo_test_no_commits_async("labels", |repo| async move {
let labels_path = repo.path.join("labels.txt");
repositories::add(&repo, &labels_path).await?;
repositories::commit(&repo, "adding initial labels file")?;
let og_branch = repositories::branches::current_branch(&repo)?.unwrap();
let branch_name = "change-labels";
repositories::branches::create_checkout(&repo, branch_name)?;
test::modify_txt_file(&labels_path, "cat\ndog\nnone")?;
repositories::add(&repo, &labels_path).await?;
repositories::commit(&repo, "adding none category")?;
repositories::checkout(&repo, og_branch.name).await?;
test::modify_txt_file(&labels_path, "cat\ndog\nperson")?;
repositories::add(&repo, &labels_path).await?;
repositories::commit(&repo, "adding person category")?;
let commit = repositories::merge::merge(&repo, branch_name).await?;
assert!(commit.is_none());
let status = repositories::status(&repo).await?;
assert_eq!(status.merge_conflicts.len(), 1);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_rm_regular_file() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let og_basename = PathBuf::from("README.md");
let og_file = repo.path.join(&og_basename);
util::fs::remove_file(og_file)?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.removed_files.len(), 1);
let opts = RmOpts::from_path(&og_basename);
repositories::rm(&repo, &opts).await?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.staged_files.len(), 1);
assert_eq!(
status.staged_files[&og_basename].status,
StagedEntryStatus::Removed
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_rm_directory_file() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let og_basename = PathBuf::from("README.md");
let og_file = repo.path.join(&og_basename);
util::fs::remove_file(og_file)?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.removed_files.len(), 1);
let opts = RmOpts::from_path(&og_basename);
repositories::rm(&repo, &opts).await?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.staged_files.len(), 1);
assert_eq!(
status.staged_files[&og_basename].status,
StagedEntryStatus::Removed
);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_move_regular_file() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let og_basename = PathBuf::from("README.md");
let og_file = repo.path.join(&og_basename);
let new_basename = PathBuf::from("README2.md");
let new_file = repo.path.join(new_basename);
util::fs::rename(&og_file, &new_file)?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 0);
assert_eq!(status.removed_files.len(), 1);
assert_eq!(status.untracked_files.len(), 1);
repositories::add(&repo, &og_file).await?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 0);
assert_eq!(status.staged_files.len(), 1);
repositories::add(&repo, &new_file).await?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 1);
assert_eq!(status.staged_files.len(), 2);
repositories::restore(&repo, RestoreOpts::from_staged_path(og_basename)).await?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 0);
assert_eq!(status.staged_files.len(), 1);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_move_dir() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let og_basename = PathBuf::from("train");
let og_dir = repo.path.join(og_basename);
let new_basename = PathBuf::from("new_train").join("train2");
let new_dir = repo.path.join(new_basename);
util::fs::create_dir_all(&new_dir)?;
util::fs::rename(&og_dir, &new_dir)?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.moved_files.len(), 0);
assert_eq!(status.untracked_dirs.len(), 1);
assert_eq!(status.removed_files.len(), 1);
repositories::add(&repo, &og_dir).await?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 0);
assert_eq!(status.staged_files.len(), 7);
assert_eq!(status.staged_dirs.len(), 1);
repositories::add(&repo, &new_dir).await?;
let status = repositories::status(&repo).await?;
assert_eq!(status.moved_files.len(), 7);
assert_eq!(status.staged_files.len(), 14);
assert_eq!(status.staged_dirs.len(), 2);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_list_added_directories() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let repo_path = &repo.path;
let training_data_dir = PathBuf::from("training_data");
let sub_dir = repo_path.join(&training_data_dir);
util::fs::create_dir_all(&sub_dir)?;
let _ = test::add_txt_file_to_dir(&sub_dir, "Hello 1")?;
let _ = test::add_txt_file_to_dir(&sub_dir, "Hello 2")?;
repositories::add(&repo, &sub_dir).await?;
let status = repositories::status(&repo).await?;
println!("status: {status:?}");
status.print();
let dirs = status.staged_dirs;
assert_eq!(dirs.len(), 1);
let added_dir = dirs.get(&training_data_dir).unwrap();
assert_eq!(added_dir.path, training_data_dir);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_remove_file_top_level() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let repo_path = &repo.path;
let file_to_rm = repo_path.join("labels.txt");
let status = repositories::status(&repo).await?;
status.print();
util::fs::remove_file(&file_to_rm)?;
let status = repositories::status(&repo).await?;
status.print();
let files = status.removed_files;
assert_eq!(files.len(), 1);
assert_eq!(status.staged_dirs.len(), 0);
assert_eq!(status.staged_files.len(), 0);
assert_eq!(status.untracked_dirs.len(), 0);
assert_eq!(status.untracked_files.len(), 0);
assert_eq!(status.modified_files.len(), 0);
let relative_path = util::fs::path_relative_to_dir(&file_to_rm, repo_path)?;
assert!(files.contains(&relative_path));
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_remove_file_in_subdirectory() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let repo_path = &repo.path;
let one_shot_file = repo_path
.join("annotations")
.join("train")
.join("one_shot.csv");
util::fs::remove_file(&one_shot_file)?;
let status = repositories::status(&repo).await?;
status.print();
let files = status.removed_files;
assert_eq!(files.len(), 1);
let relative_path = util::fs::path_relative_to_dir(&one_shot_file, repo_path)?;
assert!(files.contains(&relative_path));
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_remove_file_with_explicit_file_path() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let repo_path = &repo.path;
let target_file = repo_path
.join("annotations")
.join("train")
.join("one_shot.csv");
util::fs::remove_file(&target_file)?;
let opts = StagedDataOpts::from_paths(std::slice::from_ref(&target_file));
let status = repositories::status::status_from_opts(&repo, &opts).await?;
status.print();
let relative_path = util::fs::path_relative_to_dir(&target_file, repo_path)?;
assert_eq!(status.removed_files.len(), 1);
assert!(status.removed_files.contains(&relative_path));
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_modify_file_in_subdirectory() -> Result<(), OxenError> {
test::run_training_data_repo_test_fully_committed_async(|repo| async move {
let repo_path = &repo.path;
let one_shot_file = repo_path
.join("annotations")
.join("train")
.join("one_shot.csv");
let one_shot_file = test::modify_txt_file(one_shot_file, "new content coming in hot")?;
let status = repositories::status(&repo).await?;
status.print();
let files = status.modified_files;
assert_eq!(files.len(), 1);
let relative_path = util::fs::path_relative_to_dir(one_shot_file, repo_path)?;
assert!(files.contains(&relative_path));
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_list_untracked_directories_after_add() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let repo_path = &repo.path;
let train_dir = repo_path.join("train");
util::fs::create_dir_all(&train_dir)?;
let _ = test::add_img_file_to_dir(
&train_dir,
test::REPO_ROOT.join("data/test/images/cat_1.jpg").as_path(),
)?;
let _ = test::add_img_file_to_dir(
&train_dir,
test::REPO_ROOT.join("data/test/images/dog_1.jpg").as_path(),
)?;
let _ = test::add_img_file_to_dir(
&train_dir,
test::REPO_ROOT.join("data/test/images/cat_2.jpg").as_path(),
)?;
let _ = test::add_img_file_to_dir(
&train_dir,
test::REPO_ROOT.join("data/test/images/dog_2.jpg").as_path(),
)?;
let test_dir = repo_path.join("test");
util::fs::create_dir_all(&test_dir)?;
let _ = test::add_img_file_to_dir(
&test_dir,
test::REPO_ROOT.join("data/test/images/cat_3.jpg").as_path(),
)?;
let _ = test::add_img_file_to_dir(
&test_dir,
test::REPO_ROOT.join("data/test/images/dog_3.jpg").as_path(),
)?;
let valid_dir = repo_path.join("valid");
util::fs::create_dir_all(&valid_dir)?;
let _ = test::add_img_file_to_dir(
&valid_dir,
test::REPO_ROOT.join("data/test/images/dog_4.jpg").as_path(),
)?;
let base_file_1 = test::add_txt_file_to_dir(repo_path, "Hello 1")?;
let _base_file_2 = test::add_txt_file_to_dir(repo_path, "Hello 2")?;
let _base_file_3 = test::add_txt_file_to_dir(repo_path, "Hello 3")?;
let status = repositories::status(&repo).await?;
status.print();
let untracked_dirs = status.untracked_dirs;
assert_eq!(untracked_dirs.len(), 3);
repositories::add(&repo, &train_dir).await?;
repositories::add(&repo, &base_file_1).await?;
let status = repositories::status(&repo).await?;
println!("status: {status:?}");
status.print();
let staged_files = status.staged_files;
let staged_dirs = status.staged_dirs;
let untracked_files = status.untracked_files;
let untracked_dirs = status.untracked_dirs;
assert_eq!(staged_files.len(), 5);
assert_eq!(staged_dirs.len(), 2);
assert_eq!(untracked_files.len(), 2);
assert_eq!(untracked_dirs.len(), 2);
Ok(())
})
.await
}
#[tokio::test]
async fn test_status_list_modified_files() -> Result<(), OxenError> {
test::run_empty_local_repo_test_async(|repo| async move {
let repo_path = &repo.path;
let hello_file = test::add_txt_file_to_dir(repo_path, "Hello 1")?;
repositories::add(&repo, &hello_file).await?;
repositories::commit(&repo, "added hello 1")?;
let status = repositories::status(&repo).await?;
let mod_files = status.modified_files;
assert_eq!(mod_files.len(), 0);
let hello_file = test::modify_txt_file(hello_file, "Hello 2")?;
let status = repositories::status(&repo).await?;
status.print();
let mod_files = status.modified_files;
assert_eq!(mod_files.len(), 1);
let relative_path = util::fs::path_relative_to_dir(hello_file, repo_path)?;
assert!(mod_files.contains(&relative_path));
Ok(())
})
.await
}
#[tokio::test]
async fn test_command_status_modified_file_in_subdirectory() -> Result<(), OxenError> {
test::run_select_data_repo_test_no_commits_async("annotations", |repo| async move {
let one_shot_path = repo.path.join("annotations/train/one_shot.csv");
repositories::add(&repo, &repo.path).await?;
repositories::commit(&repo, "Adding one shot")?;
let branch_name = "feature/modify-data";
repositories::branches::create_checkout(&repo, branch_name)?;
let file_contents = "file,label\ntrain/cat_1.jpg,0\n";
test::modify_txt_file(one_shot_path, file_contents)?;
let status = repositories::status(&repo).await?;
status.print();
assert_eq!(status.modified_files.len(), 1);
assert!(
status
.modified_files
.contains(&PathBuf::from("annotations/train/one_shot.csv"))
);
Ok(())
})
.await
}
}