450 lines · 14.3 KB · Rust
RawDownload
use std::convert::TryInto; use git_lock::acquire::Fail;use git_ref::{    file::ReferenceExt,    transaction::{Change, PreviousValue, RefEdit, RefLog},    Reference, Target,};use git_testtools::hex_to_id; use crate::file::{    store_writable,    transaction::prepare_and_commit::{committer, empty_store},}; #[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 {                    expected: PreviousValue::Any,                    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 {                expected: PreviousValue::MustExist,                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.find_loose("HEAD")?;    assert!(head.log_exists(&store));    let _main = store.find_loose("main")?;     let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: PreviousValue::MustExist,                    log: RefLog::AndReference,                },                name: head.name.clone(),                deref: false,            }),            Fail::Immediately,        )?        .commit(&committer())?;     assert_eq!(        edits,        vec![RefEdit {            change: Change::Delete {                expected: PreviousValue::MustExistAndMatch(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.try_find_loose("HEAD")?.is_none(), "ref was deleted");    assert!(store.try_find_loose("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.find_loose("HEAD")?;    assert!(head.log_exists(&store));     let res = store.transaction().prepare(        Some(RefEdit {            change: Change::Delete {                expected: PreviousValue::MustExistAndMatch(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.find_loose("HEAD")?;    assert!(head.log_exists(&store));    let main = store.find_loose("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.find_loose("HEAD")?;    assert!(head.log_exists(&store));     let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: PreviousValue::MustExistAndMatch(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: Reference = store.find_loose("HEAD")?.into();    assert!(!head.log_exists(&store));    let main = store.find_loose("main").expect("referent still exists");    assert!(main.log_exists(&store), "log is untouched, too");    assert_eq!(        main.target,        head.follow(&store).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.find_loose("HEAD")?;    assert!(head.log_exists(&store));     let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: PreviousValue::MustExist,                    log: RefLog::Only,                },                name: head.name,                deref: true,            }),            Fail::Immediately,        )?        .commit(&committer())?;     assert_eq!(edits.len(), 2);    let head: Reference = store.find_loose("HEAD")?.into();    assert!(!head.log_exists(&store));    let main = store.find_loose("main").expect("referent still exists");    assert!(!main.log_exists(&store), "log is removed");    assert_eq!(        main.target,        head.follow(&store).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.try_find_loose("HEAD").is_err(), "the ref is truly broken");     let res = store.transaction().prepare(        Some(RefEdit {            change: Change::Delete {                expected: PreviousValue::MustExist,                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]fn non_existing_can_be_deleted_with_the_may_exist_match_constraint() -> crate::Result {    let (_keep, store) = empty_store()?;    let previous_value =        PreviousValue::ExistingMustMatch(Target::Peeled(hex_to_id("134385f6d781b7e97062102c6a483440bfda2a03")));    let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: previous_value.clone(),                    log: RefLog::AndReference,                },                name: "refs/heads/not-there".try_into()?,                deref: true,            }),            Fail::Immediately,        )?        .commit(&committer())?;     assert_eq!(        edits,        vec![RefEdit {            change: Change::Delete {                expected: previous_value,                log: RefLog::AndReference,            },            name: "refs/heads/not-there".try_into()?,            deref: false,        }]    );    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.try_find_loose("HEAD").is_err(), "the ref is truly broken");     let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: PreviousValue::Any,                    log: RefLog::AndReference,                },                name: "HEAD".try_into()?,                deref: true,            }),            Fail::Immediately,        )?        .commit(&committer())?;     assert!(store.try_find_loose("HEAD")?.is_none(), "the ref was deleted");    assert_eq!(        edits,        vec![RefEdit {            change: Change::Delete {                expected: PreviousValue::Any,                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::store::WriteReflog::Normal,        git_ref::store::WriteReflog::Disable,    ] {        let (_keep, mut store) = store_writable("make_repo_for_reflog.sh")?;        store.write_reflog = *reflog_writemode;        assert!(store.find_loose("HEAD")?.log_exists(&store));        assert!(store.open_packed_buffer()?.is_none(), "there is no pack");         let edits = store            .transaction()            .prepare(                Some(RefEdit {                    change: Change::Delete {                        expected: PreviousValue::Any,                        log: RefLog::Only,                    },                    name: "HEAD".try_into()?,                    deref: false,                }),                Fail::Immediately,            )?            .commit(&committer())?;        assert_eq!(edits.len(), 1);        assert!(!store.find_loose("HEAD")?.log_exists(&store), "log was deleted");        assert!(store.open_packed_buffer()?.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.try_find_loose("main")?.is_none(),        "no loose main available, it's packed"    );    assert!(        store.open_packed_buffer()?.expect("packed").try_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 {                    expected: PreviousValue::MustExistAndMatch(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.open_packed_buffer()?.expect("packed ref present");    assert!(packed.try_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.open_packed_buffer()?.expect("packed-refs");    let branch = store.find("newer-as-loose")?;    let branch_id = branch.target.try_id().map(ToOwned::to_owned).expect("peeled");    assert_ne!(        packed.find("newer-as-loose")?.target(),        branch_id,        "the packed ref is outdated"    );     let edits = store        .transaction()        .prepare(            Some(RefEdit {                change: Change::Delete {                    expected: PreviousValue::MustExistAndMatch(Target::Peeled(branch_id)),                    log: RefLog::AndReference,                },                name: branch.name,                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.try_find("newer-as-loose",)?.is_none(),        "reference is deleted everywhere"    );    Ok(())} #[test]fn all_contained_references_deletes_the_packed_ref_file_too() -> crate::Result {    for mode in ["must-exist", "may-exist"] {        let (_keep, store) = store_writable("make_packed_ref_repository.sh")?;         let edits = store            .transaction()            .prepare(                store.open_packed_buffer()?.expect("packed-refs").iter()?.map(|r| {                    let r = r.expect("valid ref");                    RefEdit {                        change: Change::Delete {                            expected: match mode {                                "must-exist" => PreviousValue::MustExistAndMatch(Target::Peeled(r.target())),                                "may-exist" => PreviousValue::ExistingMustMatch(Target::Peeled(r.target())),                                _ => unimplemented!("unknown mode: {}", mode),                            },                            log: RefLog::AndReference,                        },                        name: r.name.into(),                        deref: false,                    }                }),                git_lock::acquire::Fail::Immediately,            )?            .commit(&committer())?;         assert!(!store.packed_refs_path().is_file(), "packed-refs was entirely removed");         let packed = store.open_packed_buffer()?;        assert!(packed.is_none(), "it won't make up packed refs");        for edit in edits {            assert!(                store.try_find(edit.name.to_partial())?.is_none(),                "delete ref cannot be found"            );        }    }    Ok(())}