use crate::file::{ store_writable, transaction::prepare_and_commit::{committer, empty_store},};use git_lock::acquire::Fail;use git_ref::{ mutable::Target, transaction::{Change, RefEdit, RefLog},};use git_testtools::hex_to_id;use std::convert::TryInto; #[test]fn delete_a_ref_which_is_gone_succeeds() -> crate::Result { let (_keep, store) = empty_store()?; let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: None, log: RefLog::AndReference, }, name: "DOES_NOT_EXIST".try_into()?, deref: false, }), Fail::Immediately, )? .commit(&committer())?; assert_eq!(edits.len(), 1); Ok(())} #[test]fn delete_a_ref_which_is_gone_but_must_exist_fails() -> crate::Result { let (_keep, store) = empty_store()?; let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::must_exist()), log: RefLog::AndReference, }, name: "DOES_NOT_EXIST".try_into()?, deref: false, }), Fail::Immediately, ); match res { Ok(_) => unreachable!("must exist, but it doesn't actually exist"), Err(err) => assert_eq!( err.to_string(), "The reference 'DOES_NOT_EXIST' for deletion did not exist or could not be parsed" ), } Ok(())} #[test]fn delete_ref_and_reflog_on_symbolic_no_deref() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.loose_find_existing("HEAD")?; assert!(head.log_exists(&store)); let _main = store.loose_find_existing("main")?; let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::must_exist()), log: RefLog::AndReference, }, name: head.name.clone(), deref: false, }), Fail::Immediately, )? .commit(&committer())?; assert_eq!( edits, vec![RefEdit { change: Change::Delete { previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::AndReference, }, name: head.name, deref: false }], "the previous value was updated with the actual one" ); assert!( store.reflog_iter_rev("HEAD", &mut [0u8; 128])?.is_none(), "reflog was deleted" ); assert!(store.loose_find("HEAD")?.is_none(), "ref was deleted"); assert!(store.loose_find("main")?.is_some(), "referent still exists"); Ok(())} #[test]fn delete_ref_with_incorrect_previous_value_fails() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.loose_find_existing("HEAD")?; assert!(head.log_exists(&store)); let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::Only, }, name: head.name, deref: true, }), Fail::Immediately, ); match res { Err(err) => { assert_eq!(err.to_string(), "The reference 'refs/heads/main' should have content ref: refs/heads/main, actual content was 02a7a22d90d7c02fb494ed25551850b868e634f0"); } Ok(_) => unreachable!("must be err"), } // everything stays as is let head = store.loose_find_existing("HEAD")?; assert!(head.log_exists(&store)); let main = store.loose_find_existing("main").expect("referent still exists"); assert!(main.log_exists(&store)); Ok(())} #[test]fn delete_reflog_only_of_symbolic_no_deref() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.loose_find_existing("HEAD")?; assert!(head.log_exists(&store)); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::Symbolic("refs/heads/main".try_into()?)), log: RefLog::Only, }, name: head.name, deref: false, }), Fail::Immediately, )? .commit(&committer())?; assert_eq!(edits.len(), 1); let head = store.loose_find_existing("HEAD")?; assert!(!head.log_exists(&store)); let main = store.loose_find_existing("main").expect("referent still exists"); assert!(main.log_exists(&store), "log is untouched, too"); assert_eq!( main.target, head.follow_symbolic(&store, None).expect("a symref")?.target(), "head points to main" ); Ok(())} #[test]fn delete_reflog_only_of_symbolic_with_deref() -> crate::Result { let (_keep, store) = store_writable("make_repo_for_reflog.sh")?; let head = store.loose_find_existing("HEAD")?; assert!(head.log_exists(&store)); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::must_exist()), log: RefLog::Only, }, name: head.name, deref: true, }), Fail::Immediately, )? .commit(&committer())?; assert_eq!(edits.len(), 2); let head = store.loose_find_existing("HEAD")?; assert!(!head.log_exists(&store)); let main = store.loose_find_existing("main").expect("referent still exists"); assert!(!main.log_exists(&store), "log is removed"); assert_eq!( main.target, head.follow_symbolic(&store, None).expect("a symref")?.target(), "head points to main" ); Ok(())} #[test]/// Based on https://github.com/git/git/blob/master/refs/files-backend.c#L514:L515fn delete_broken_ref_that_must_exist_fails_as_it_is_no_valid_ref() -> crate::Result { let (_keep, store) = empty_store()?; std::fs::write(store.base.join("HEAD"), &b"broken")?; assert!(store.loose_find("HEAD").is_err(), "the ref is truly broken"); let res = store.transaction().prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::must_exist()), log: RefLog::AndReference, }, name: "HEAD".try_into()?, deref: true, }), Fail::Immediately, ); match res { Err(err) => { assert_eq!( err.to_string(), "The reference 'HEAD' for deletion did not exist or could not be parsed" ); } Ok(_) => unreachable!("expected error"), } Ok(())} #[test]/// Based on https://github.com/git/git/blob/master/refs/files-backend.c#L514:L515fn delete_broken_ref_that_may_not_exist_works_even_in_deref_mode() -> crate::Result { let (_keep, store) = empty_store()?; std::fs::write(store.base.join("HEAD"), &b"broken")?; assert!(store.loose_find("HEAD").is_err(), "the ref is truly broken"); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: None, log: RefLog::AndReference, }, name: "HEAD".try_into()?, deref: true, }), Fail::Immediately, )? .commit(&committer())?; assert!(store.loose_find("HEAD")?.is_none(), "the ref was deleted"); assert_eq!( edits, vec![RefEdit { change: Change::Delete { previous: None, log: RefLog::AndReference, }, name: "HEAD".try_into()?, deref: false, }] ); Ok(())} #[test]fn store_write_mode_has_no_effect_and_reflogs_are_always_deleted() -> crate::Result { for reflog_writemode in &[git_ref::file::WriteReflog::Normal, git_ref::file::WriteReflog::Disable] { let (_keep, mut store) = store_writable("make_repo_for_reflog.sh")?; store.write_reflog = *reflog_writemode; assert!(store.loose_find_existing("HEAD")?.log_exists(&store)); assert!(store.packed()?.is_none(), "there is no pack"); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: None, log: RefLog::Only, }, name: "HEAD".try_into()?, deref: false, }), Fail::Immediately, )? .commit(&committer())?; assert_eq!(edits.len(), 1); assert!( !store.loose_find_existing("HEAD")?.log_exists(&store), "log was deleted" ); assert!(store.packed()?.is_none(), "there still is no pack"); } Ok(())} #[test]fn packed_refs_are_consulted_when_determining_previous_value_of_ref_to_be_deleted_and_are_deleted_from_packed_ref_file() -> crate::Result { let (_keep, store) = store_writable("make_packed_ref_repository.sh")?; assert!( store.loose_find("main")?.is_none(), "no loose main available, it's packed" ); assert!( store.packed()?.expect("packed").find("main")?.is_some(), "packed main is available" ); let old_id = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03"); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::Peeled(old_id)), log: RefLog::AndReference, }, name: "refs/heads/main".try_into()?, deref: false, }), git_lock::acquire::Fail::Immediately, )? .commit(&committer())?; assert_eq!(edits.len(), 1, "an edit was performed in the packed refs store"); let packed = store.packed()?.expect("packed ref present"); assert!(packed.find("main")?.is_none(), "no main present after deletion"); Ok(())} #[test]fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_refs() -> crate::Result { let (_keep, store) = store_writable("make_packed_ref_repository_for_overlay.sh")?; let packed = store.packed()?.expect("packed-refs"); let branch = store.find_existing("newer-as-loose", Some(&packed))?; let branch_id = branch.target().as_id().map(ToOwned::to_owned).expect("peeled"); assert_ne!( packed.find_existing("newer-as-loose")?.target(), branch_id, "the packed ref is outdated" ); let edits = store .transaction() .prepare( Some(RefEdit { change: Change::Delete { previous: Some(Target::Peeled(branch_id)), log: RefLog::AndReference, }, name: branch.name().into(), deref: false, }), git_lock::acquire::Fail::Immediately, )? .commit(&committer())?; assert_eq!( edits.len(), 1, "only one edit even though technically two places were changed" ); assert!( store.find("newer-as-loose", store.packed()?.as_ref())?.is_none(), "reference is deleted everywhere" ); Ok(())} #[test]fn all_contained_references_deletes_the_packed_ref_file_too() { let (_keep, store) = store_writable("make_packed_ref_repository.sh").unwrap(); let edits = store .transaction() .prepare( store.packed().unwrap().expect("packed-refs").iter().unwrap().map(|r| { let r = r.expect("valid ref"); RefEdit { change: Change::Delete { previous: Target::Peeled(r.target()).into(), log: RefLog::AndReference, }, name: r.name.into(), deref: false, } }), git_lock::acquire::Fail::Immediately, ) .unwrap() .commit(&committer()) .unwrap(); assert!(!store.packed_refs_path().is_file(), "packed-refs was entirely removed"); let packed = store.packed().unwrap(); assert!(packed.is_none(), "it won't make up packed refs"); for edit in edits { assert!( store.find(edit.name.to_partial(), packed.as_ref()).unwrap().is_none(), "delete ref cannot be found" ); }}
use crate::file::{
store_writable,
transaction::prepare_and_commit::{committer, empty_store},
};
use git_lock::acquire::Fail;
use git_ref::{
mutable::Target,
transaction::{Change, RefEdit, RefLog},
};
use git_testtools::hex_to_id;
use std::convert::TryInto;
#[test]
fn delete_a_ref_which_is_gone_succeeds() -> crate::Result {
let (_keep, store) = empty_store()?;
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: None,
log: RefLog::AndReference,
},
name: "DOES_NOT_EXIST".try_into()?,
deref: false,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(edits.len(), 1);
Ok(())
}
#[test]
fn delete_a_ref_which_is_gone_but_must_exist_fails() -> crate::Result {
let (_keep, store) = empty_store()?;
let res = store.transaction().prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::must_exist()),
log: RefLog::AndReference,
},
name: "DOES_NOT_EXIST".try_into()?,
deref: false,
}),
Fail::Immediately,
);
match res {
Ok(_) => unreachable!("must exist, but it doesn't actually exist"),
Err(err) => assert_eq!(
err.to_string(),
"The reference 'DOES_NOT_EXIST' for deletion did not exist or could not be parsed"
),
}
Ok(())
}
#[test]
fn delete_ref_and_reflog_on_symbolic_no_deref() -> crate::Result {
let (_keep, store) = store_writable("make_repo_for_reflog.sh")?;
let head = store.loose_find_existing("HEAD")?;
assert!(head.log_exists(&store));
let _main = store.loose_find_existing("main")?;
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::must_exist()),
log: RefLog::AndReference,
},
name: head.name.clone(),
deref: false,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(
edits,
vec![RefEdit {
change: Change::Delete {
previous: Some(Target::Symbolic("refs/heads/main".try_into()?)),
log: RefLog::AndReference,
},
name: head.name,
deref: false
}],
"the previous value was updated with the actual one"
);
assert!(
store.reflog_iter_rev("HEAD", &mut [0u8; 128])?.is_none(),
"reflog was deleted"
);
assert!(store.loose_find("HEAD")?.is_none(), "ref was deleted");
assert!(store.loose_find("main")?.is_some(), "referent still exists");
Ok(())
}
#[test]
fn delete_ref_with_incorrect_previous_value_fails() -> crate::Result {
let (_keep, store) = store_writable("make_repo_for_reflog.sh")?;
let head = store.loose_find_existing("HEAD")?;
assert!(head.log_exists(&store));
let res = store.transaction().prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::Symbolic("refs/heads/main".try_into()?)),
log: RefLog::Only,
},
name: head.name,
deref: true,
}),
Fail::Immediately,
);
match res {
Err(err) => {
assert_eq!(err.to_string(), "The reference 'refs/heads/main' should have content ref: refs/heads/main, actual content was 02a7a22d90d7c02fb494ed25551850b868e634f0");
}
Ok(_) => unreachable!("must be err"),
}
// everything stays as is
let head = store.loose_find_existing("HEAD")?;
assert!(head.log_exists(&store));
let main = store.loose_find_existing("main").expect("referent still exists");
assert!(main.log_exists(&store));
Ok(())
}
#[test]
fn delete_reflog_only_of_symbolic_no_deref() -> crate::Result {
let (_keep, store) = store_writable("make_repo_for_reflog.sh")?;
let head = store.loose_find_existing("HEAD")?;
assert!(head.log_exists(&store));
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::Symbolic("refs/heads/main".try_into()?)),
log: RefLog::Only,
},
name: head.name,
deref: false,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(edits.len(), 1);
let head = store.loose_find_existing("HEAD")?;
assert!(!head.log_exists(&store));
let main = store.loose_find_existing("main").expect("referent still exists");
assert!(main.log_exists(&store), "log is untouched, too");
assert_eq!(
main.target,
head.follow_symbolic(&store, None).expect("a symref")?.target(),
"head points to main"
);
Ok(())
}
#[test]
fn delete_reflog_only_of_symbolic_with_deref() -> crate::Result {
let (_keep, store) = store_writable("make_repo_for_reflog.sh")?;
let head = store.loose_find_existing("HEAD")?;
assert!(head.log_exists(&store));
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::must_exist()),
log: RefLog::Only,
},
name: head.name,
deref: true,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(edits.len(), 2);
let head = store.loose_find_existing("HEAD")?;
assert!(!head.log_exists(&store));
let main = store.loose_find_existing("main").expect("referent still exists");
assert!(!main.log_exists(&store), "log is removed");
assert_eq!(
main.target,
head.follow_symbolic(&store, None).expect("a symref")?.target(),
"head points to main"
);
Ok(())
}
#[test]
/// Based on https://github.com/git/git/blob/master/refs/files-backend.c#L514:L515
fn delete_broken_ref_that_must_exist_fails_as_it_is_no_valid_ref() -> crate::Result {
let (_keep, store) = empty_store()?;
std::fs::write(store.base.join("HEAD"), &b"broken")?;
assert!(store.loose_find("HEAD").is_err(), "the ref is truly broken");
let res = store.transaction().prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::must_exist()),
log: RefLog::AndReference,
},
name: "HEAD".try_into()?,
deref: true,
}),
Fail::Immediately,
);
match res {
Err(err) => {
assert_eq!(
err.to_string(),
"The reference 'HEAD' for deletion did not exist or could not be parsed"
);
}
Ok(_) => unreachable!("expected error"),
}
Ok(())
}
#[test]
/// Based on https://github.com/git/git/blob/master/refs/files-backend.c#L514:L515
fn delete_broken_ref_that_may_not_exist_works_even_in_deref_mode() -> crate::Result {
let (_keep, store) = empty_store()?;
std::fs::write(store.base.join("HEAD"), &b"broken")?;
assert!(store.loose_find("HEAD").is_err(), "the ref is truly broken");
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: None,
log: RefLog::AndReference,
},
name: "HEAD".try_into()?,
deref: true,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert!(store.loose_find("HEAD")?.is_none(), "the ref was deleted");
assert_eq!(
edits,
vec![RefEdit {
change: Change::Delete {
previous: None,
log: RefLog::AndReference,
},
name: "HEAD".try_into()?,
deref: false,
}]
);
Ok(())
}
#[test]
fn store_write_mode_has_no_effect_and_reflogs_are_always_deleted() -> crate::Result {
for reflog_writemode in &[git_ref::file::WriteReflog::Normal, git_ref::file::WriteReflog::Disable] {
let (_keep, mut store) = store_writable("make_repo_for_reflog.sh")?;
store.write_reflog = *reflog_writemode;
assert!(store.loose_find_existing("HEAD")?.log_exists(&store));
assert!(store.packed()?.is_none(), "there is no pack");
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: None,
log: RefLog::Only,
},
name: "HEAD".try_into()?,
deref: false,
}),
Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(edits.len(), 1);
assert!(
!store.loose_find_existing("HEAD")?.log_exists(&store),
"log was deleted"
);
assert!(store.packed()?.is_none(), "there still is no pack");
}
Ok(())
}
#[test]
fn packed_refs_are_consulted_when_determining_previous_value_of_ref_to_be_deleted_and_are_deleted_from_packed_ref_file(
) -> crate::Result {
let (_keep, store) = store_writable("make_packed_ref_repository.sh")?;
assert!(
store.loose_find("main")?.is_none(),
"no loose main available, it's packed"
);
assert!(
store.packed()?.expect("packed").find("main")?.is_some(),
"packed main is available"
);
let old_id = hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03");
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::Peeled(old_id)),
log: RefLog::AndReference,
},
name: "refs/heads/main".try_into()?,
deref: false,
}),
git_lock::acquire::Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(edits.len(), 1, "an edit was performed in the packed refs store");
let packed = store.packed()?.expect("packed ref present");
assert!(packed.find("main")?.is_none(), "no main present after deletion");
Ok(())
}
#[test]
fn a_loose_ref_with_old_value_check_and_outdated_packed_refs_value_deletes_both_refs() -> crate::Result {
let (_keep, store) = store_writable("make_packed_ref_repository_for_overlay.sh")?;
let packed = store.packed()?.expect("packed-refs");
let branch = store.find_existing("newer-as-loose", Some(&packed))?;
let branch_id = branch.target().as_id().map(ToOwned::to_owned).expect("peeled");
assert_ne!(
packed.find_existing("newer-as-loose")?.target(),
branch_id,
"the packed ref is outdated"
);
let edits = store
.transaction()
.prepare(
Some(RefEdit {
change: Change::Delete {
previous: Some(Target::Peeled(branch_id)),
log: RefLog::AndReference,
},
name: branch.name().into(),
deref: false,
}),
git_lock::acquire::Fail::Immediately,
)?
.commit(&committer())?;
assert_eq!(
edits.len(),
1,
"only one edit even though technically two places were changed"
);
assert!(
store.find("newer-as-loose", store.packed()?.as_ref())?.is_none(),
"reference is deleted everywhere"
);
Ok(())
}
#[test]
fn all_contained_references_deletes_the_packed_ref_file_too() {
let (_keep, store) = store_writable("make_packed_ref_repository.sh").unwrap();
let edits = store
.transaction()
.prepare(
store.packed().unwrap().expect("packed-refs").iter().unwrap().map(|r| {
let r = r.expect("valid ref");
RefEdit {
change: Change::Delete {
previous: Target::Peeled(r.target()).into(),
log: RefLog::AndReference,
},
name: r.name.into(),
deref: false,
}
}),
git_lock::acquire::Fail::Immediately,
)
.unwrap()
.commit(&committer())
.unwrap();
assert!(!store.packed_refs_path().is_file(), "packed-refs was entirely removed");
let packed = store.packed().unwrap();
assert!(packed.is_none(), "it won't make up packed refs");
for edit in edits {
assert!(
store.find(edit.name.to_partial(), packed.as_ref()).unwrap().is_none(),
"delete ref cannot be found"
);
}
}