use git2::Status;
use super::git_context::StepChanges;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FileChangeType {
Added,
Modified,
Deleted,
Unknown,
}
pub(crate) fn classify_file_status(status: Status) -> FileChangeType {
if should_track_as_added(status) {
FileChangeType::Added
} else if should_track_as_modified(status) {
FileChangeType::Modified
} else if should_track_as_deleted(status) {
FileChangeType::Deleted
} else {
FileChangeType::Unknown
}
}
pub(crate) fn should_track_as_added(status: Status) -> bool {
status.contains(Status::WT_NEW) || status.contains(Status::INDEX_NEW)
}
pub(crate) fn should_track_as_modified(status: Status) -> bool {
status.contains(Status::WT_MODIFIED) || status.contains(Status::INDEX_MODIFIED)
}
pub(crate) fn should_track_as_deleted(status: Status) -> bool {
status.contains(Status::WT_DELETED) || status.contains(Status::INDEX_DELETED)
}
pub(crate) fn classify_delta_status(delta: git2::Delta) -> FileChangeType {
match delta {
git2::Delta::Added => FileChangeType::Added,
git2::Delta::Modified => FileChangeType::Modified,
git2::Delta::Deleted => FileChangeType::Deleted,
_ => FileChangeType::Unknown,
}
}
pub(crate) fn extract_file_path(delta: &git2::DiffDelta) -> Option<String> {
delta
.new_file()
.path()
.map(|p| p.to_string_lossy().to_string())
}
pub(crate) fn should_add_to_list(list: &[String], path: &str) -> bool {
!list.contains(&path.to_string())
}
pub(crate) fn add_unique_file(list: &mut Vec<String>, path: String) {
if should_add_to_list(list, &path) {
list.push(path);
}
}
pub(crate) fn normalize_file_list(list: &mut Vec<String>) {
list.sort();
list.dedup();
}
pub(crate) fn normalize_file_lists(changes: &mut StepChanges) {
normalize_file_list(&mut changes.files_added);
normalize_file_list(&mut changes.files_modified);
normalize_file_list(&mut changes.files_deleted);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_track_as_added_with_wt_new() {
assert!(should_track_as_added(Status::WT_NEW));
}
#[test]
fn test_should_track_as_added_with_index_new() {
assert!(should_track_as_added(Status::INDEX_NEW));
}
#[test]
fn test_should_track_as_added_with_combined_new() {
let status = Status::WT_NEW | Status::INDEX_NEW;
assert!(should_track_as_added(status));
}
#[test]
fn test_should_track_as_modified_with_wt_modified() {
assert!(should_track_as_modified(Status::WT_MODIFIED));
}
#[test]
fn test_should_track_as_modified_with_index_modified() {
assert!(should_track_as_modified(Status::INDEX_MODIFIED));
}
#[test]
fn test_should_track_as_modified_with_combined_modified() {
let status = Status::WT_MODIFIED | Status::INDEX_MODIFIED;
assert!(should_track_as_modified(status));
}
#[test]
fn test_should_track_as_deleted_with_wt_deleted() {
assert!(should_track_as_deleted(Status::WT_DELETED));
}
#[test]
fn test_should_track_as_deleted_with_index_deleted() {
assert!(should_track_as_deleted(Status::INDEX_DELETED));
}
#[test]
fn test_should_track_as_deleted_with_combined_deleted() {
let status = Status::WT_DELETED | Status::INDEX_DELETED;
assert!(should_track_as_deleted(status));
}
#[test]
fn test_classify_file_status_added() {
assert_eq!(classify_file_status(Status::WT_NEW), FileChangeType::Added);
assert_eq!(
classify_file_status(Status::INDEX_NEW),
FileChangeType::Added
);
}
#[test]
fn test_classify_file_status_modified() {
assert_eq!(
classify_file_status(Status::WT_MODIFIED),
FileChangeType::Modified
);
assert_eq!(
classify_file_status(Status::INDEX_MODIFIED),
FileChangeType::Modified
);
}
#[test]
fn test_classify_file_status_deleted() {
assert_eq!(
classify_file_status(Status::WT_DELETED),
FileChangeType::Deleted
);
assert_eq!(
classify_file_status(Status::INDEX_DELETED),
FileChangeType::Deleted
);
}
#[test]
fn test_classify_delta_status_added() {
assert_eq!(
classify_delta_status(git2::Delta::Added),
FileChangeType::Added
);
}
#[test]
fn test_classify_delta_status_modified() {
assert_eq!(
classify_delta_status(git2::Delta::Modified),
FileChangeType::Modified
);
}
#[test]
fn test_classify_delta_status_deleted() {
assert_eq!(
classify_delta_status(git2::Delta::Deleted),
FileChangeType::Deleted
);
}
#[test]
fn test_classify_delta_status_unknown() {
assert_eq!(
classify_delta_status(git2::Delta::Unmodified),
FileChangeType::Unknown
);
assert_eq!(
classify_delta_status(git2::Delta::Renamed),
FileChangeType::Unknown
);
assert_eq!(
classify_delta_status(git2::Delta::Copied),
FileChangeType::Unknown
);
}
#[test]
fn test_should_add_to_list_empty() {
let list: Vec<String> = vec![];
assert!(should_add_to_list(&list, "test.txt"));
}
#[test]
fn test_should_add_to_list_not_present() {
let list = vec!["file1.txt".to_string(), "file2.txt".to_string()];
assert!(should_add_to_list(&list, "file3.txt"));
}
#[test]
fn test_should_add_to_list_already_present() {
let list = vec!["file1.txt".to_string(), "file2.txt".to_string()];
assert!(!should_add_to_list(&list, "file1.txt"));
}
#[test]
fn test_add_unique_file_to_empty_list() {
let mut list = vec![];
add_unique_file(&mut list, "test.txt".to_string());
assert_eq!(list.len(), 1);
assert_eq!(list[0], "test.txt");
}
#[test]
fn test_add_unique_file_new_file() {
let mut list = vec!["file1.txt".to_string()];
add_unique_file(&mut list, "file2.txt".to_string());
assert_eq!(list.len(), 2);
assert!(list.contains(&"file2.txt".to_string()));
}
#[test]
fn test_add_unique_file_duplicate() {
let mut list = vec!["file1.txt".to_string()];
add_unique_file(&mut list, "file1.txt".to_string());
assert_eq!(list.len(), 1);
}
#[test]
fn test_add_unique_file_multiple_duplicates() {
let mut list = vec!["file1.txt".to_string()];
add_unique_file(&mut list, "file1.txt".to_string());
add_unique_file(&mut list, "file2.txt".to_string());
add_unique_file(&mut list, "file1.txt".to_string());
assert_eq!(list.len(), 2);
}
#[test]
fn test_normalize_file_list_empty() {
let mut list: Vec<String> = vec![];
normalize_file_list(&mut list);
assert!(list.is_empty());
}
#[test]
fn test_normalize_file_list_sorts() {
let mut list = vec![
"zebra.txt".to_string(),
"apple.txt".to_string(),
"middle.txt".to_string(),
];
normalize_file_list(&mut list);
assert_eq!(list[0], "apple.txt");
assert_eq!(list[1], "middle.txt");
assert_eq!(list[2], "zebra.txt");
}
#[test]
fn test_normalize_file_list_deduplicates() {
let mut list = vec![
"file1.txt".to_string(),
"file2.txt".to_string(),
"file1.txt".to_string(),
];
normalize_file_list(&mut list);
assert_eq!(list.len(), 2);
assert!(list.contains(&"file1.txt".to_string()));
assert!(list.contains(&"file2.txt".to_string()));
}
#[test]
fn test_normalize_file_list_sorts_and_deduplicates() {
let mut list = vec![
"zebra.txt".to_string(),
"apple.txt".to_string(),
"zebra.txt".to_string(),
"middle.txt".to_string(),
"apple.txt".to_string(),
];
normalize_file_list(&mut list);
assert_eq!(list.len(), 3);
assert_eq!(list[0], "apple.txt");
assert_eq!(list[1], "middle.txt");
assert_eq!(list[2], "zebra.txt");
}
#[test]
fn test_normalize_file_lists_normalizes_all() {
let mut changes = StepChanges {
files_added: vec![
"z.txt".to_string(),
"a.txt".to_string(),
"a.txt".to_string(),
],
files_modified: vec![
"y.txt".to_string(),
"b.txt".to_string(),
"b.txt".to_string(),
],
files_deleted: vec![
"x.txt".to_string(),
"c.txt".to_string(),
"c.txt".to_string(),
],
..Default::default()
};
normalize_file_lists(&mut changes);
assert_eq!(changes.files_added[0], "a.txt");
assert_eq!(changes.files_added[1], "z.txt");
assert_eq!(changes.files_modified[0], "b.txt");
assert_eq!(changes.files_modified[1], "y.txt");
assert_eq!(changes.files_deleted[0], "c.txt");
assert_eq!(changes.files_deleted[1], "x.txt");
assert_eq!(changes.files_added.len(), 2);
assert_eq!(changes.files_modified.len(), 2);
assert_eq!(changes.files_deleted.len(), 2);
}
#[test]
fn test_normalize_file_lists_handles_empty() {
let mut changes = StepChanges::default();
normalize_file_lists(&mut changes);
assert!(changes.files_added.is_empty());
assert!(changes.files_modified.is_empty());
assert!(changes.files_deleted.is_empty());
}
}