use crate::utils::git;
use crate::{Repository, Result};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResetMode {
Soft,
Mixed,
Hard,
}
impl ResetMode {
pub const fn as_str(&self) -> &'static str {
match self {
ResetMode::Soft => "--soft",
ResetMode::Mixed => "--mixed",
ResetMode::Hard => "--hard",
}
}
}
pub fn reset<P: AsRef<Path>>(repo_path: P, mode: ResetMode, commit: &str) -> Result<()> {
let args = vec!["reset", mode.as_str(), commit];
git(&args, Some(repo_path.as_ref()))?;
Ok(())
}
impl Repository {
pub fn reset_soft(&self, commit: &str) -> Result<()> {
Self::ensure_git()?;
reset(self.repo_path(), ResetMode::Soft, commit)?;
Ok(())
}
pub fn reset_mixed(&self, commit: &str) -> Result<()> {
Self::ensure_git()?;
reset(self.repo_path(), ResetMode::Mixed, commit)?;
Ok(())
}
pub fn reset_hard(&self, commit: &str) -> Result<()> {
Self::ensure_git()?;
reset(self.repo_path(), ResetMode::Hard, commit)?;
Ok(())
}
pub fn reset_with_mode(&self, commit: &str, mode: ResetMode) -> Result<()> {
Self::ensure_git()?;
reset(self.repo_path(), mode, commit)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Repository;
use std::path::PathBuf;
use std::{env, fs};
fn create_test_repo(test_name: &str) -> (PathBuf, Repository) {
let temp_dir = env::temp_dir().join(format!("rustic_git_reset_test_{}", test_name));
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir).unwrap();
}
let repo = Repository::init(&temp_dir, false).unwrap();
repo.config()
.set_user("Test User", "test@example.com")
.unwrap();
(temp_dir, repo)
}
fn create_file_and_commit(
repo: &Repository,
temp_dir: &Path,
filename: &str,
content: &str,
message: &str,
) -> String {
let file_path = temp_dir.join(filename);
fs::write(&file_path, content).unwrap();
repo.add(&[filename]).unwrap();
repo.commit(message).unwrap().to_string()
}
#[test]
fn test_reset_mode_as_str() {
assert_eq!(ResetMode::Soft.as_str(), "--soft");
assert_eq!(ResetMode::Mixed.as_str(), "--mixed");
assert_eq!(ResetMode::Hard.as_str(), "--hard");
}
#[test]
fn test_reset_soft() {
let (temp_dir, repo) = create_test_repo("reset_soft");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
reset(&temp_dir, ResetMode::Soft, &first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 1);
assert!(
status
.staged_files()
.any(|f| f.path.file_name().unwrap() == "file2.txt")
);
assert!(temp_dir.join("file2.txt").exists());
fs::remove_dir_all(&temp_dir).unwrap();
}
#[test]
fn test_reset_mixed() {
let (temp_dir, repo) = create_test_repo("reset_mixed");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
reset(&temp_dir, ResetMode::Mixed, &first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert!(temp_dir.join("file2.txt").exists());
assert!(
status
.untracked_entries()
.any(|f| f.path.file_name().unwrap() == "file2.txt")
);
}
#[test]
fn test_reset_hard() {
let (temp_dir, repo) = create_test_repo("reset_hard");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
reset(&temp_dir, ResetMode::Hard, &first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert!(!temp_dir.join("file2.txt").exists());
assert_eq!(status.untracked_entries().count(), 0);
}
#[test]
fn test_reset_invalid_commit() {
let (temp_dir, _repo) = create_test_repo("reset_invalid_commit");
let result = reset(&temp_dir, ResetMode::Mixed, "invalid_commit_hash");
assert!(result.is_err());
}
#[test]
fn test_reset_head() {
let (temp_dir, repo) = create_test_repo("reset_head");
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "Initial commit");
fs::write(temp_dir.join("file1.txt"), "modified").unwrap();
repo.add(&["file1.txt"]).unwrap();
reset(temp_dir, ResetMode::Mixed, "HEAD").unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert_eq!(status.unstaged_files().count(), 1);
}
#[test]
fn test_repository_reset_soft() {
let (temp_dir, repo) = create_test_repo("repository_reset_soft");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
repo.reset_soft(&first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 1);
assert!(
status
.staged_files()
.any(|f| f.path.file_name().unwrap() == "file2.txt")
);
}
#[test]
fn test_repository_reset_mixed() {
let (temp_dir, repo) = create_test_repo("repository_reset_mixed");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
repo.reset_mixed(&first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert!(temp_dir.join("file2.txt").exists());
assert!(
status
.untracked_entries()
.any(|f| f.path.file_name().unwrap() == "file2.txt")
);
}
#[test]
fn test_repository_reset_hard() {
let (temp_dir, repo) = create_test_repo("repository_reset_hard");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
repo.reset_hard(&first_commit).unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert!(!temp_dir.join("file2.txt").exists());
assert_eq!(status.untracked_entries().count(), 0);
fs::remove_dir_all(&temp_dir).unwrap();
}
#[test]
fn test_repository_reset_with_mode() {
let (temp_dir, repo) = create_test_repo("repository_reset_with_mode");
let first_commit =
create_file_and_commit(&repo, &temp_dir, "file1.txt", "content1", "First commit");
let _second_commit =
create_file_and_commit(&repo, &temp_dir, "file2.txt", "content2", "Second commit");
repo.reset_with_mode(&first_commit, ResetMode::Mixed)
.unwrap();
let status = repo.status().unwrap();
assert_eq!(status.staged_files().count(), 0);
assert!(temp_dir.join("file2.txt").exists());
fs::remove_dir_all(&temp_dir).unwrap();
}
}