use std::collections::BTreeMap;
use crate::objects::ObjectId;
use crate::refs::SYMREF_MAXDEPTH;
use super::apply::{
apply_case_only_ref_rename, apply_reflog_rename, apply_update, simulate_batch_apply,
};
use super::error::RefStoreError;
use super::transaction::RefTransaction;
use super::validation::validate_storable_refname as validate_name;
use super::{RawRef, RefUpdate};
pub use super::apply::RefBatchState;
pub use super::transaction::expected_matches;
pub use super::validation::verify_create_conflicts;
pub fn validate_storable_refname(name: &str) -> Result<(), RefStoreError> {
validate_name(name)
}
pub fn validate_transaction(txn: &RefTransaction) -> Result<(), RefStoreError> {
txn.validate()
}
pub fn check_transaction(state: &RefBatchState, txn: &RefTransaction) -> Result<(), RefStoreError> {
txn.validate()?;
verify_create_conflicts(&state.refs, txn.updates())?;
simulate_batch_apply(
state,
txn.updates(),
txn.reflog_renames(),
txn.case_only_renames(),
)
}
pub(crate) fn check_transaction_updates(
state: &RefBatchState,
updates: &[RefUpdate],
) -> Result<(), RefStoreError> {
verify_create_conflicts(&state.refs, updates)?;
simulate_batch_apply(state, updates, &[], &[])
}
pub fn apply_transaction(
state: &mut RefBatchState,
txn: &RefTransaction,
) -> Result<(), RefStoreError> {
for update in txn.updates() {
apply_update(state, update)?;
}
for rename in txn.case_only_renames() {
apply_case_only_ref_rename(state, rename)?;
}
for rename in txn.reflog_renames() {
apply_reflog_rename(state, rename)?;
}
Ok(())
}
pub fn resolve_symbolic(
refs: &BTreeMap<String, RawRef>,
name: &str,
) -> Result<ObjectId, RefStoreError> {
let mut current = name;
for _ in 0..SYMREF_MAXDEPTH {
match refs.get(current) {
None => return Err(RefStoreError::not_found(current)),
Some(RawRef::Direct(oid)) => return Ok(*oid),
Some(RawRef::Symbolic(target)) => current = target,
}
}
Err(RefStoreError::SymrefLoop)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::refs::store::{Expected, RefUpdateFlags, ReflogUpdate};
use crate::refs::RefnameUnavailable;
fn oid(byte: u8) -> ObjectId {
let mut bytes = [0u8; 20];
bytes[19] = byte;
ObjectId::from_bytes(&bytes).expect("valid oid")
}
fn put(name: &str, value: RawRef, expected: Expected) -> RefUpdate {
RefUpdate {
name: name.to_owned(),
new_value: Some(value),
expected,
reflog: None,
flags: RefUpdateFlags::default(),
}
}
fn state(refs: &[(&str, RawRef)]) -> RefBatchState {
RefBatchState {
refs: refs
.iter()
.map(|(name, value)| ((*name).to_owned(), value.clone()))
.collect(),
reflogs: BTreeMap::new(),
}
}
#[test]
fn check_rejects_bad_names_before_anything_else() {
let txn = RefTransaction::new()
.update(put(
"refs/heads/a..b",
RawRef::Direct(oid(1)),
Expected::Missing,
))
.expect("txn");
let err = check_transaction(&RefBatchState::default(), &txn).expect_err("bad name");
assert!(
matches!(&err, RefStoreError::InvalidRefName { name } if name == "refs/heads/a..b")
);
assert!(validate_storable_refname("HEAD").is_ok());
assert!(validate_storable_refname("refs/heads/ok").is_ok());
}
#[test]
fn check_reports_df_conflicts_both_ways() {
let txn = RefTransaction::new()
.update(put(
"refs/heads/a",
RawRef::Direct(oid(2)),
Expected::Missing,
))
.expect("txn");
let stored = state(&[("refs/heads/a/b", RawRef::Direct(oid(1)))]);
let err = check_transaction(&stored, &txn).expect_err("descendant blocks");
assert!(matches!(
err,
RefStoreError::NameUnavailable {
reason: RefnameUnavailable::DescendantExists { .. }
}
));
let txn = RefTransaction::new()
.update(put(
"refs/heads/a/b/c",
RawRef::Direct(oid(2)),
Expected::Missing,
))
.expect("txn");
let err = check_transaction(&stored, &txn).expect_err("ancestor blocks");
assert!(matches!(
err,
RefStoreError::NameUnavailable {
reason: RefnameUnavailable::AncestorExists { .. }
}
));
}
#[test]
fn check_compares_through_symrefs() {
let stored = state(&[
("refs/heads/main", RawRef::Direct(oid(1))),
("HEAD", RawRef::Symbolic("refs/heads/main".to_owned())),
]);
let good = RefTransaction::new()
.update(put("HEAD", RawRef::Direct(oid(2)), Expected::Oid(oid(1))))
.expect("txn");
check_transaction(&stored, &good).expect("peeled old value matches");
let stale = RefTransaction::new()
.update(put("HEAD", RawRef::Direct(oid(2)), Expected::Oid(oid(9))))
.expect("txn");
assert!(matches!(
check_transaction(&stored, &stale),
Err(RefStoreError::ExpectedMismatch { .. })
));
}
#[test]
fn apply_logs_symref_and_target() {
let mut stored = state(&[
("refs/heads/main", RawRef::Direct(oid(1))),
("HEAD", RawRef::Symbolic("refs/heads/main".to_owned())),
]);
let mut update = put("HEAD", RawRef::Direct(oid(2)), Expected::Oid(oid(1)));
update.reflog = Some(ReflogUpdate {
identity: "T <t@example.com>".to_owned(),
message: "commit: two".to_owned(),
time: time::OffsetDateTime::UNIX_EPOCH,
old_oid: None,
new_oid: None,
});
let txn = RefTransaction::new().update(update).expect("txn");
check_transaction(&stored, &txn).expect("check");
apply_transaction(&mut stored, &txn).expect("apply");
assert_eq!(
stored.refs.get("HEAD"),
Some(&RawRef::Symbolic("refs/heads/main".to_owned()))
);
assert_eq!(
resolve_symbolic(&stored.refs, "HEAD").expect("resolve"),
oid(2)
);
for name in ["HEAD", "refs/heads/main"] {
let log = stored.reflogs.get(name).expect("reflog");
assert_eq!(log.len(), 1, "{name}");
assert_eq!((log[0].old_oid, log[0].new_oid), (oid(1), oid(2)), "{name}");
assert_eq!(log[0].identity, "T <t@example.com> 0 +0000", "{name}");
}
}
#[test]
fn resolve_symbolic_reports_missing_target_and_loops() {
let refs = state(&[
("HEAD", RawRef::Symbolic("refs/heads/unborn".to_owned())),
("refs/heads/x", RawRef::Symbolic("refs/heads/y".to_owned())),
("refs/heads/y", RawRef::Symbolic("refs/heads/x".to_owned())),
])
.refs;
assert!(matches!(
resolve_symbolic(&refs, "HEAD"),
Err(RefStoreError::NotFound { name }) if name == "refs/heads/unborn"
));
assert!(matches!(
resolve_symbolic(&refs, "refs/heads/x"),
Err(RefStoreError::SymrefLoop)
));
}
}