1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::path::PathBuf;

use git_features::threading::OwnShared;

use crate::store_impl::{file, packed};

impl file::Store {
    /// Return a packed transaction ready to receive updates. Use this to create or update `packed-refs`.
    /// Note that if you already have a [`packed::Buffer`] then use its [`packed::Buffer::into_transaction()`] method instead.
    pub(crate) fn packed_transaction(
        &self,
        lock_mode: git_lock::acquire::Fail,
    ) -> Result<packed::Transaction, transaction::Error> {
        let lock = git_lock::File::acquire_to_update_resource(self.packed_refs_path(), lock_mode, None)?;
        // We 'steal' the possibly existing packed buffer which may safe time if it's already there and fresh.
        // If nothing else is happening, nobody will get to see the soon stale buffer either, but if so, they will pay
        // for reloading it. That seems preferred over always loading up a new one.
        Ok(packed::Transaction::new_from_pack_and_lock(
            self.assure_packed_refs_uptodate()?,
            lock,
        ))
    }

    /// Try to open a new packed buffer. It's not an error if it doesn't exist, but yields `Ok(None)`.
    pub fn open_packed_buffer(&self) -> Result<Option<packed::Buffer>, packed::buffer::open::Error> {
        let need_more_than_this_many_bytes_to_use_mmap = 32 * 1024;
        match packed::Buffer::open(self.packed_refs_path(), need_more_than_this_many_bytes_to_use_mmap) {
            Ok(buf) => Ok(Some(buf)),
            Err(packed::buffer::open::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
            Err(err) => Err(err),
        }
    }

    /// Return a possibly cached packed buffer with shared ownership. At retrieval it will assure it's up to date, but
    /// after that it can be considered a snapshot as it cannot change anymore.
    ///
    /// Use this to make successive calls to [`file::Store::try_find_packed()`]
    /// or obtain iterators using [`file::Store::iter_packed()`] in a way that assures the packed-refs content won't change.
    pub fn cached_packed_buffer(&self) -> Result<Option<OwnShared<packed::Buffer>>, packed::buffer::open::Error> {
        self.assure_packed_refs_uptodate()
    }

    /// Return the path at which packed-refs would usually be stored
    pub fn packed_refs_path(&self) -> PathBuf {
        self.base.join("packed-refs")
    }
}

///
pub mod transaction {
    use quick_error::quick_error;

    use crate::store_impl::packed;

    quick_error! {
        /// The error returned by [`file::Store::packed_transaction`][crate::file::Store::packed_transaction()].
        #[derive(Debug)]
        #[allow(missing_docs)]
        pub enum Error {
            BufferOpen(err: packed::buffer::open::Error) {
                display("An existing pack couldn't be opened or read when preparing a transaction")
                source(err)
                from()
            }
            TransactionLock(err: git_lock::acquire::Error) {
                display("The lock for a packed transaction could not be obtained")
                source(err)
                from()
            }
        }
    }
}

pub(crate) mod modifiable {
    use std::time::SystemTime;

    use git_features::threading::{get_mut, get_ref, OwnShared};

    use crate::{file, packed};

    #[derive(Debug, Default)]
    pub(crate) struct State {
        buffer: Option<OwnShared<packed::Buffer>>,
        modified: Option<SystemTime>,
    }

    impl file::Store {
        /// Always reload the internally cached packed buffer from disk. This can be necessary if the caller knows something changed
        /// but fears the change is not picked up due to lack of precision in fstat mtime calls.
        pub(crate) fn force_refresh_packed_buffer(&self) -> Result<(), packed::buffer::open::Error> {
            let mut state = get_mut(&self.packed);
            state.buffer = self.open_packed_buffer()?.map(OwnShared::new);
            Ok(())
        }
        pub(crate) fn assure_packed_refs_uptodate(
            &self,
        ) -> Result<Option<OwnShared<packed::Buffer>>, packed::buffer::open::Error> {
            let packed_refs_path = self.packed_refs_path();
            let packed_refs_modified_time = || packed_refs_path.metadata().and_then(|m| m.modified()).ok();
            let state = get_ref(&self.packed);
            let recent_modification = packed_refs_modified_time();
            let buffer = match (&state.modified, recent_modification) {
                (None, None) => state.buffer.clone(),
                (Some(_), None) => {
                    drop(state);
                    let mut state = get_mut(&self.packed);
                    // Still in the same situation? If so, drop the loaded buffer
                    if let (Some(_), None) = (state.modified, packed_refs_modified_time()) {
                        state.buffer = None;
                        state.modified = None;
                    }
                    state.buffer.clone()
                }
                (Some(cached_time), Some(modified_time)) => {
                    if *cached_time < modified_time {
                        drop(state);
                        let mut state = get_mut(&self.packed);
                        // in the common case, we check again and do what we do only if we are
                        // still in the same situation, writers pile up.
                        match (state.modified, packed_refs_modified_time()) {
                            (Some(cached_time), Some(modified_time)) if cached_time < modified_time => {
                                state.buffer = self.open_packed_buffer()?.map(OwnShared::new);
                                state.modified = Some(modified_time);
                            }
                            _ => {}
                        }
                        state.buffer.clone()
                    } else {
                        // Note that this relies on sub-section precision or else is a race when the packed file was just changed.
                        // It's nothing we can know though, so… up to the caller unfortunately.
                        state.buffer.clone()
                    }
                }
                (None, Some(_modified_time)) => {
                    drop(state);
                    let mut state = get_mut(&self.packed);
                    // Still in the same situation? If so, load the buffer.
                    if let (None, Some(modified_time)) = (state.modified, packed_refs_modified_time()) {
                        state.buffer = self.open_packed_buffer()?.map(OwnShared::new);
                        state.modified = Some(modified_time);
                    }
                    state.buffer.clone()
                }
            };
            Ok(buffer)
        }
    }
}