use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::VcsError;
use crate::hash::ObjectId;
use crate::store::{HeadState, ReflogEntry, Store};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResetMode {
Soft,
Mixed,
Hard,
}
pub fn reset(
store: &mut dyn Store,
target: ObjectId,
mode: ResetMode,
author: &str,
) -> Result<ResetOutcome, VcsError> {
let old_head = crate::store::resolve_head(store)?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
match store.get_head()? {
HeadState::Branch(name) => {
let ref_name = format!("refs/heads/{name}");
store.set_ref(&ref_name, target)?;
store.append_reflog(
&ref_name,
ReflogEntry {
old_id: old_head,
new_id: target,
author: author.to_owned(),
timestamp,
message: format!("reset: moving to {}", target.short()),
},
)?;
}
HeadState::Detached(_) => {
store.set_head(HeadState::Detached(target))?;
}
}
store.append_reflog(
"HEAD",
ReflogEntry {
old_id: old_head,
new_id: target,
author: author.to_owned(),
timestamp,
message: format!("reset: moving to {}", target.short()),
},
)?;
Ok(ResetOutcome {
old_head,
new_head: target,
mode,
should_clear_index: mode != ResetMode::Soft,
should_write_working: mode == ResetMode::Hard,
})
}
#[derive(Clone, Debug)]
pub struct ResetOutcome {
pub old_head: Option<ObjectId>,
pub new_head: ObjectId,
pub mode: ResetMode,
pub should_clear_index: bool,
pub should_write_working: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MemStore;
use crate::error::VcsError;
use crate::object::{CommitObject, Object};
#[test]
fn reset_soft_moves_ref() -> Result<(), VcsError> {
let mut store = MemStore::new();
let c0_id = store.put(&Object::Commit(CommitObject {
schema_id: ObjectId::from_bytes([0; 32]),
parents: vec![],
migration_id: None,
protocol: "test".into(),
author: "test".into(),
timestamp: 100,
message: "c0".into(),
renames: vec![],
protocol_id: None,
data_ids: vec![],
complement_ids: vec![],
}))?;
let c1_id = store.put(&Object::Commit(CommitObject {
schema_id: ObjectId::from_bytes([1; 32]),
parents: vec![c0_id],
migration_id: None,
protocol: "test".into(),
author: "test".into(),
timestamp: 200,
message: "c1".into(),
renames: vec![],
protocol_id: None,
data_ids: vec![],
complement_ids: vec![],
}))?;
store.set_ref("refs/heads/main", c1_id)?;
let outcome = reset(&mut store, c0_id, ResetMode::Soft, "test")?;
assert!(!outcome.should_clear_index);
assert!(!outcome.should_write_working);
assert_eq!(store.get_ref("refs/heads/main")?, Some(c0_id));
Ok(())
}
#[test]
fn reset_mixed_clears_index() -> Result<(), VcsError> {
let mut store = MemStore::new();
let c0_id = store.put(&Object::Commit(CommitObject {
schema_id: ObjectId::from_bytes([0; 32]),
parents: vec![],
migration_id: None,
protocol: "test".into(),
author: "test".into(),
timestamp: 100,
message: "c0".into(),
renames: vec![],
protocol_id: None,
data_ids: vec![],
complement_ids: vec![],
}))?;
store.set_ref("refs/heads/main", c0_id)?;
let outcome = reset(&mut store, c0_id, ResetMode::Mixed, "test")?;
assert!(outcome.should_clear_index);
assert!(!outcome.should_write_working);
Ok(())
}
#[test]
fn reset_hard_writes_working() -> Result<(), VcsError> {
let mut store = MemStore::new();
let c0_id = store.put(&Object::Commit(CommitObject {
schema_id: ObjectId::from_bytes([0; 32]),
parents: vec![],
migration_id: None,
protocol: "test".into(),
author: "test".into(),
timestamp: 100,
message: "c0".into(),
renames: vec![],
protocol_id: None,
data_ids: vec![],
complement_ids: vec![],
}))?;
store.set_ref("refs/heads/main", c0_id)?;
let outcome = reset(&mut store, c0_id, ResetMode::Hard, "test")?;
assert!(outcome.should_clear_index);
assert!(outcome.should_write_working);
Ok(())
}
#[test]
fn reset_appends_reflog() -> Result<(), VcsError> {
let mut store = MemStore::new();
let c0_id = store.put(&Object::Commit(CommitObject {
schema_id: ObjectId::from_bytes([0; 32]),
parents: vec![],
migration_id: None,
protocol: "test".into(),
author: "test".into(),
timestamp: 100,
message: "c0".into(),
renames: vec![],
protocol_id: None,
data_ids: vec![],
complement_ids: vec![],
}))?;
store.set_ref("refs/heads/main", c0_id)?;
reset(&mut store, c0_id, ResetMode::Soft, "alice")?;
let log = store.read_reflog("HEAD", None)?;
assert_eq!(log.len(), 1);
assert!(log[0].message.contains("reset"));
Ok(())
}
}