Struct git_ref::packed::Buffer

source ·
pub struct Buffer { /* private fields */ }
Expand description

A buffer containing a packed-ref file that is either memory mapped or fully in-memory depending on a cutoff.

The buffer is guaranteed to be sorted as per the packed-ref rules which allows some operations to be more efficient.

Implementations§

packed-refs specific functionality

Return an iterator of references stored in this packed refs buffer, ordered by reference name.

Note

There is no namespace support in packed iterators. It can be emulated using iter_prefixed(…).

Examples found in repository?
src/store/file/overlay_iter.rs (line 393)
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    fn iter_from_info<'s, 'p>(
        &'s self,
        git_dir_info: IterInfo<'_>,
        common_dir_info: Option<IterInfo<'_>>,
        packed: Option<&'p packed::Buffer>,
    ) -> std::io::Result<LooseThenPacked<'p, 's>> {
        Ok(LooseThenPacked {
            git_dir: self.git_dir(),
            common_dir: self.common_dir(),
            iter_packed: match packed {
                Some(packed) => Some(
                    match git_dir_info.prefix() {
                        Some(prefix) => packed.iter_prefixed(path_to_name(prefix).into_owned()),
                        None => packed.iter(),
                    }
                    .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
                    .peekable(),
                ),
                None => None,
            },
            iter_git_dir: git_dir_info.into_iter(),
            iter_common_dir: common_dir_info.map(IterInfo::into_iter),
            buf: Vec::new(),
            namespace: self.namespace.as_ref(),
        })
    }
More examples
Hide additional examples
src/store/packed/transaction.rs (line 130)
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
    pub fn commit(self) -> Result<(), commit::Error> {
        let mut edits = self.edits.expect("BUG: cannot call commit() before prepare(…)");
        if edits.is_empty() {
            return Ok(());
        }

        let mut file = self.lock.expect("a write lock for applying changes");
        let refs_sorted: Box<dyn Iterator<Item = Result<packed::Reference<'_>, packed::iter::Error>>> =
            match self.buffer.as_ref() {
                Some(buffer) => Box::new(buffer.iter()?),
                None => Box::new(std::iter::empty()),
            };

        let mut refs_sorted = refs_sorted.peekable();

        edits.sort_by(|l, r| l.inner.name.as_bstr().cmp(r.inner.name.as_bstr()));
        let mut peekable_sorted_edits = edits.iter().peekable();

        file.with_mut(|f| f.write_all(HEADER_LINE))?;

        let mut num_written_lines = 0;
        loop {
            match (refs_sorted.peek(), peekable_sorted_edits.peek()) {
                (Some(Err(_)), _) => {
                    let err = refs_sorted.next().expect("next").expect_err("err");
                    return Err(commit::Error::Iteration(err));
                }
                (None, None) => {
                    break;
                }
                (Some(Ok(_)), None) => {
                    let pref = refs_sorted.next().expect("next").expect("no err");
                    num_written_lines += 1;
                    file.with_mut(|out| write_packed_ref(out, pref))?;
                }
                (Some(Ok(pref)), Some(edit)) => {
                    use std::cmp::Ordering::*;
                    match pref.name.as_bstr().cmp(edit.inner.name.as_bstr()) {
                        Less => {
                            let pref = refs_sorted.next().expect("next").expect("valid");
                            num_written_lines += 1;
                            file.with_mut(|out| write_packed_ref(out, pref))?;
                        }
                        Greater => {
                            let edit = peekable_sorted_edits.next().expect("next");
                            file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
                        }
                        Equal => {
                            let _pref = refs_sorted.next().expect("next").expect("valid");
                            let edit = peekable_sorted_edits.next().expect("next");
                            file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
                        }
                    }
                }
                (None, Some(_)) => {
                    let edit = peekable_sorted_edits.next().expect("next");
                    file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
                }
            }
        }

        if num_written_lines == 0 {
            std::fs::remove_file(file.resource_path())?;
        } else {
            file.commit()?;
        }
        drop(refs_sorted);
        Ok(())
    }

Return an iterator yielding only references matching the given prefix, ordered by reference name.

Examples found in repository?
src/store/file/overlay_iter.rs (line 392)
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    fn iter_from_info<'s, 'p>(
        &'s self,
        git_dir_info: IterInfo<'_>,
        common_dir_info: Option<IterInfo<'_>>,
        packed: Option<&'p packed::Buffer>,
    ) -> std::io::Result<LooseThenPacked<'p, 's>> {
        Ok(LooseThenPacked {
            git_dir: self.git_dir(),
            common_dir: self.common_dir(),
            iter_packed: match packed {
                Some(packed) => Some(
                    match git_dir_info.prefix() {
                        Some(prefix) => packed.iter_prefixed(path_to_name(prefix).into_owned()),
                        None => packed.iter(),
                    }
                    .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
                    .peekable(),
                ),
                None => None,
            },
            iter_git_dir: git_dir_info.into_iter(),
            iter_common_dir: common_dir_info.map(IterInfo::into_iter),
            buf: Vec::new(),
            namespace: self.namespace.as_ref(),
        })
    }

Initialization

Open the file at path and map it into memory if the file size is larger than use_memory_map_if_larger_than_bytes.

In order to allow fast lookups and optimizations, the contents of the packed refs must be sorted. If that’s not the case, they will be sorted on the fly with the data being written into a memory buffer.

Examples found in repository?
src/store/file/packed.rs (line 25)
23
24
25
26
27
28
29
30
    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),
        }
    }

packed-refs specific functionality

Find a reference with the given name and return it.

Note that it will look it up verbatim and does not deal with namespaces or special prefixes like main-worktree/ or worktrees/<name>/, as this is left to the caller.

Examples found in repository?
src/store/packed/find.rs (line 64)
59
60
61
62
63
64
65
66
67
68
69
    pub fn find<'a, Name, E>(&self, name: Name) -> Result<packed::Reference<'_>, existing::Error>
    where
        Name: TryInto<&'a PartialNameRef, Error = E>,
        Error: From<E>,
    {
        match self.try_find(name) {
            Ok(Some(r)) => Ok(r),
            Ok(None) => Err(existing::Error::NotFound),
            Err(err) => Err(existing::Error::Find(err)),
        }
    }
More examples
Hide additional examples
src/store/file/transaction/prepare.rs (line 49)
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    fn lock_ref_and_apply_change(
        store: &file::Store,
        lock_fail_mode: git_lock::acquire::Fail,
        packed: Option<&packed::Buffer>,
        change: &mut Edit,
        has_global_lock: bool,
        direct_to_packed_refs: bool,
    ) -> Result<(), Error> {
        use std::io::Write;
        assert!(
            change.lock.is_none(),
            "locks can only be acquired once and it's all or nothing"
        );

        let existing_ref = store
            .ref_contents(change.update.name.as_ref())
            .map_err(Error::from)
            .and_then(|maybe_loose| {
                maybe_loose
                    .map(|buf| {
                        loose::Reference::try_from_path(change.update.name.clone(), &buf)
                            .map(Reference::from)
                            .map_err(Error::from)
                    })
                    .transpose()
            })
            .or_else(|err| match err {
                Error::ReferenceDecode(_) => Ok(None),
                other => Err(other),
            })
            .and_then(|maybe_loose| match (maybe_loose, packed) {
                (None, Some(packed)) => packed
                    .try_find(change.update.name.as_ref())
                    .map(|opt| opt.map(Into::into))
                    .map_err(Error::from),
                (None, None) => Ok(None),
                (maybe_loose, _) => Ok(maybe_loose),
            });
        let lock = match &mut change.update.change {
            Change::Delete { expected, .. } => {
                let (base, relative_path) = store.reference_path_with_base(change.update.name.as_ref());
                let lock = if has_global_lock {
                    None
                } else {
                    git_lock::Marker::acquire_to_hold_resource(
                        base.join(relative_path.as_ref()),
                        lock_fail_mode,
                        Some(base.clone().into_owned()),
                    )
                    .map_err(|err| Error::LockAcquire {
                        source: err,
                        full_name: "borrowchk won't allow change.name()".into(),
                    })?
                    .into()
                };

                let existing_ref = existing_ref?;
                match (&expected, &existing_ref) {
                    (PreviousValue::MustNotExist, _) => {
                        panic!("BUG: MustNotExist constraint makes no sense if references are to be deleted")
                    }
                    (PreviousValue::ExistingMustMatch(_), None)
                    | (PreviousValue::MustExist, Some(_))
                    | (PreviousValue::Any, Some(_))
                    | (PreviousValue::Any, None) => {}
                    (PreviousValue::MustExist, None) | (PreviousValue::MustExistAndMatch(_), None) => {
                        return Err(Error::DeleteReferenceMustExist {
                            full_name: change.name(),
                        })
                    }
                    (PreviousValue::MustExistAndMatch(previous), Some(existing))
                    | (PreviousValue::ExistingMustMatch(previous), Some(existing)) => {
                        let actual = existing.target.clone();
                        if *previous != actual {
                            let expected = previous.clone();
                            return Err(Error::ReferenceOutOfDate {
                                full_name: change.name(),
                                expected,
                                actual,
                            });
                        }
                    }
                }

                // Keep the previous value for the caller and ourselves. Maybe they want to keep a log of sorts.
                if let Some(existing) = existing_ref {
                    *expected = PreviousValue::MustExistAndMatch(existing.target);
                }

                lock
            }
            Change::Update { expected, new, .. } => {
                let (base, relative_path) = store.reference_path_with_base(change.update.name.as_ref());
                let obtain_lock = || {
                    git_lock::File::acquire_to_update_resource(
                        base.join(relative_path.as_ref()),
                        lock_fail_mode,
                        Some(base.clone().into_owned()),
                    )
                    .map_err(|err| Error::LockAcquire {
                        source: err,
                        full_name: "borrowchk won't allow change.name() and this will be corrected by caller".into(),
                    })
                };
                let mut lock = (!has_global_lock).then(obtain_lock).transpose()?;

                let existing_ref = existing_ref?;
                match (&expected, &existing_ref) {
                    (PreviousValue::Any, _)
                    | (PreviousValue::MustExist, Some(_))
                    | (PreviousValue::MustNotExist, None)
                    | (PreviousValue::ExistingMustMatch(_), None) => {}
                    (PreviousValue::MustExist, None) => {
                        let expected = Target::Peeled(store.object_hash.null());
                        let full_name = change.name();
                        return Err(Error::MustExist { full_name, expected });
                    }
                    (PreviousValue::MustNotExist, Some(existing)) => {
                        if existing.target != *new {
                            let new = new.clone();
                            return Err(Error::MustNotExist {
                                full_name: change.name(),
                                actual: existing.target.clone(),
                                new,
                            });
                        }
                    }
                    (PreviousValue::MustExistAndMatch(previous), Some(existing))
                    | (PreviousValue::ExistingMustMatch(previous), Some(existing)) => {
                        if *previous != existing.target {
                            let actual = existing.target.clone();
                            let expected = previous.to_owned();
                            let full_name = change.name();
                            return Err(Error::ReferenceOutOfDate {
                                full_name,
                                actual,
                                expected,
                            });
                        }
                    }

                    (PreviousValue::MustExistAndMatch(previous), None) => {
                        let expected = previous.to_owned();
                        let full_name = change.name();
                        return Err(Error::MustExist { full_name, expected });
                    }
                };

                fn new_would_change_existing(new: &Target, existing: &Target) -> (bool, bool) {
                    match (new, existing) {
                        (Target::Peeled(new), Target::Peeled(old)) => (old != new, false),
                        (Target::Symbolic(new), Target::Symbolic(old)) => (old != new, true),
                        (Target::Peeled(_), _) => (true, false),
                        (Target::Symbolic(_), _) => (true, true),
                    }
                }

                let (is_effective, is_symbolic) = if let Some(existing) = existing_ref {
                    let (effective, is_symbolic) = new_would_change_existing(new, &existing.target);
                    *expected = PreviousValue::MustExistAndMatch(existing.target);
                    (effective, is_symbolic)
                } else {
                    (true, matches!(new, Target::Symbolic(_)))
                };

                if (is_effective && !direct_to_packed_refs) || is_symbolic {
                    let mut lock = lock.take().map(Ok).unwrap_or_else(obtain_lock)?;

                    lock.with_mut(|file| match new {
                        Target::Peeled(oid) => write!(file, "{}", oid),
                        Target::Symbolic(name) => write!(file, "ref: {}", name.0),
                    })?;
                    Some(lock.close()?)
                } else {
                    None
                }
            }
        };
        change.lock = lock;
        Ok(())
    }

Find a reference with the given name and return it.

Examples found in repository?
src/store/packed/transaction.rs (line 59)
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
    pub fn prepare(
        mut self,
        edits: impl IntoIterator<Item = RefEdit>,
        find: &mut FindObjectFn<'_>,
    ) -> Result<Self, prepare::Error> {
        assert!(self.edits.is_none(), "BUG: cannot call prepare(…) more than once");
        let buffer = &self.buffer;
        // Remove all edits which are deletions that aren't here in the first place
        let mut edits: Vec<Edit> = edits
            .into_iter()
            .filter(|edit| {
                if let Change::Delete { .. } = edit.change {
                    buffer.as_ref().map_or(true, |b| b.find(edit.name.as_ref()).is_ok())
                } else {
                    true
                }
            })
            .map(|change| Edit {
                inner: change,
                peeled: None,
            })
            .collect();

        let mut buf = Vec::new();
        for edit in edits.iter_mut() {
            if let Change::Update {
                new: Target::Peeled(new),
                ..
            } = edit.inner.change
            {
                let mut next_id = new;
                edit.peeled = loop {
                    let kind = find(next_id, &mut buf)?;
                    match kind {
                        Some(kind) if kind == git_object::Kind::Tag => {
                            next_id = git_object::TagRefIter::from_bytes(&buf).target_id().map_err(|_| {
                                prepare::Error::Resolve(
                                    format!("Couldn't get target object id from tag {}", next_id).into(),
                                )
                            })?;
                        }
                        Some(_) => {
                            break if next_id == new { None } else { Some(next_id) };
                        }
                        None => {
                            return Err(prepare::Error::Resolve(
                                format!("Couldn't find object with id {}", next_id).into(),
                            ))
                        }
                    }
                };
            }
        }

        if edits.is_empty() {
            self.closed_lock = self
                .lock
                .take()
                .map(|l| l.close())
                .transpose()
                .map_err(prepare::Error::CloseLock)?;
        } else {
            // NOTE that we don't do any additional checks here but apply all edits unconditionally.
            // This is because this transaction system is internal and will be used correctly from the
            // loose ref store transactions, which do the necessary checking.
        }
        self.edits = Some(edits);
        Ok(self)
    }

Trait Implementations§

Converts this type into a shared reference of the (usually inferred) input type.
Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Encode the hex strict representing self into the result. Lower case letters are used (e.g. f9b4ca)
Encode the hex strict representing self into the result. Upper case letters are used (e.g. F9B4CA)
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.