diff options
Diffstat (limited to 'vendor/gix-odb/src/store_impls/dynamic/load_index.rs')
-rw-r--r-- | vendor/gix-odb/src/store_impls/dynamic/load_index.rs | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/vendor/gix-odb/src/store_impls/dynamic/load_index.rs b/vendor/gix-odb/src/store_impls/dynamic/load_index.rs new file mode 100644 index 000000000..86cf6c43b --- /dev/null +++ b/vendor/gix-odb/src/store_impls/dynamic/load_index.rs @@ -0,0 +1,719 @@ +use std::{ + collections::{BTreeMap, VecDeque}, + ffi::OsStr, + ops::Deref, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicU16, AtomicUsize, Ordering}, + Arc, + }, + time::SystemTime, +}; + +use crate::store::{handle, types, RefreshMode}; + +pub(crate) struct Snapshot { + /// Indices ready for object lookup or contains checks, ordered usually by modification data, recent ones first. + pub(crate) indices: Vec<handle::IndexLookup>, + /// A set of loose objects dbs to search once packed objects weren't found. + pub(crate) loose_dbs: Arc<Vec<crate::loose::Store>>, + /// remember what this state represents and to compare to other states. + pub(crate) marker: types::SlotIndexMarker, +} + +mod error { + use std::path::PathBuf; + + use gix_pack::multi_index::PackIndex; + + /// Returned by [`crate::at_opts()`] + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error("The objects directory at '{0}' is not an accessible directory")] + Inaccessible(PathBuf), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Alternate(#[from] crate::alternate::Error), + #[error("The slotmap turned out to be too small with {} entries, would need {} more", .current, .needed)] + InsufficientSlots { current: usize, needed: usize }, + /// The problem here is that some logic assumes that more recent generations are higher than previous ones. If we would overflow, + /// we would break that invariant which can lead to the wrong object from being returned. It would probably be super rare, but… + /// let's not risk it. + #[error( + "Would have overflown amount of max possible generations of {}", + super::Generation::MAX + )] + GenerationOverflow, + #[error("Cannot numerically handle more than {limit} packs in a single multi-pack index, got {actual} in file {index_path:?}")] + TooManyPacksInMultiIndex { + actual: PackIndex, + limit: PackIndex, + index_path: PathBuf, + }, + } +} + +pub use error::Error; + +use crate::store::types::{Generation, IndexAndPacks, MutableIndexAndPack, PackId, SlotMapIndex}; + +impl super::Store { + /// Load all indices, refreshing from disk only if needed. + pub(crate) fn load_all_indices(&self) -> Result<Snapshot, Error> { + let mut snapshot = self.collect_snapshot(); + while let Some(new_snapshot) = self.load_one_index(RefreshMode::Never, snapshot.marker)? { + snapshot = new_snapshot + } + Ok(snapshot) + } + + /// If `None` is returned, there is new indices and the caller should give up. This is a possibility even if it's allowed to refresh + /// as here might be no change to pick up. + pub(crate) fn load_one_index( + &self, + refresh_mode: RefreshMode, + marker: types::SlotIndexMarker, + ) -> Result<Option<Snapshot>, Error> { + let index = self.index.load(); + if !index.is_initialized() { + return self.consolidate_with_disk_state(true /* needs_init */, false /*load one new index*/); + } + + if marker.generation != index.generation || marker.state_id != index.state_id() { + // We have a more recent state already, provide it. + Ok(Some(self.collect_snapshot())) + } else { + // always compare to the latest state + // Nothing changed in the mean time, try to load another index… + if self.load_next_index(index) { + Ok(Some(self.collect_snapshot())) + } else { + // …and if that didn't yield anything new consider refreshing our disk state. + match refresh_mode { + RefreshMode::Never => Ok(None), + RefreshMode::AfterAllIndicesLoaded => { + self.consolidate_with_disk_state(false /* needs init */, true /*load one new index*/) + } + } + } + } + } + + /// load a new index (if not yet loaded), and return true if one was indeed loaded (leading to a state_id() change) of the current index. + /// Note that interacting with the slot-map is inherently racy and we have to deal with it, being conservative in what we even try to load + /// as our index might already be out-of-date as we try to use it to learn what's next. + fn load_next_index(&self, mut index: arc_swap::Guard<Arc<SlotMapIndex>>) -> bool { + 'retry_with_changed_index: loop { + let previous_state_id = index.state_id(); + 'retry_with_next_slot_index: loop { + match index + .next_index_to_load + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |current| { + (current != index.slot_indices.len()).then_some(current + 1) + }) { + Ok(slot_map_index) => { + // This slot-map index is in bounds and was only given to us. + let _ongoing_operation = IncOnNewAndDecOnDrop::new(&index.num_indices_currently_being_loaded); + let slot = &self.files[index.slot_indices[slot_map_index]]; + let _lock = slot.write.lock(); + if slot.generation.load(Ordering::SeqCst) > index.generation { + // There is a disk consolidation in progress which just overwrote a slot that cold be disposed with some other + // index, one we didn't intend to load. + // Continue with the next slot index in the hope there is something else we can do… + continue 'retry_with_next_slot_index; + } + let mut bundle = slot.files.load_full(); + let bundle_mut = Arc::make_mut(&mut bundle); + if let Some(files) = bundle_mut.as_mut() { + // these are always expected to be set, unless somebody raced us. We handle this later by retrying. + let _loaded_count = IncOnDrop(&index.loaded_indices); + match files.load_index(self.object_hash) { + Ok(_) => { + slot.files.store(bundle); + break 'retry_with_next_slot_index; + } + Err(_) => { + slot.files.store(bundle); + continue 'retry_with_next_slot_index; + } + } + } + } + Err(_nothing_more_to_load) => { + // There can be contention as many threads start working at the same time and take all the + // slots to load indices for. Some threads might just be left-over and have to wait for something + // to change. + let num_load_operations = index.num_indices_currently_being_loaded.deref(); + // TODO: potentially hot loop - could this be a condition variable? + while num_load_operations.load(Ordering::Relaxed) != 0 { + std::thread::yield_now() + } + break 'retry_with_next_slot_index; + } + } + } + if previous_state_id == index.state_id() { + let potentially_new_index = self.index.load(); + if Arc::as_ptr(&potentially_new_index) == Arc::as_ptr(&index) { + // There isn't a new index with which to retry the whole ordeal, so nothing could be done here. + return false; + } else { + // the index changed, worth trying again + index = potentially_new_index; + continue 'retry_with_changed_index; + } + } else { + // something inarguably changed, probably an index was loaded. 'probably' because we consider failed loads valid attempts, + // even they don't change anything for the caller which would then do a round for nothing. + return true; + } + } + } + + /// refresh and possibly clear out our existing data structures, causing all pack ids to be invalidated. + /// `load_new_index` is an optimization to at least provide one newly loaded pack after refreshing the slot map. + pub(crate) fn consolidate_with_disk_state( + &self, + needs_init: bool, + load_new_index: bool, + ) -> Result<Option<Snapshot>, Error> { + let index = self.index.load(); + let previous_index_state = Arc::as_ptr(&index) as usize; + + // IMPORTANT: get a lock after we recorded the previous state. + let write = self.write.lock(); + let objects_directory = &self.path; + + // Now we know the index isn't going to change anymore, even though threads might still load indices in the meantime. + let index = self.index.load(); + if previous_index_state != Arc::as_ptr(&index) as usize { + // Someone else took the look before and changed the index. Return it without doing any additional work. + return Ok(Some(self.collect_snapshot())); + } + + let was_uninitialized = !index.is_initialized(); + + // We might not be able to detect by pointer if the state changed, as this itself is racy. So we keep track of double-initialization + // using a flag, which means that if `needs_init` was true we saw the index uninitialized once, but now that we are here it's + // initialized meaning that somebody was faster and we couldn't detect it by comparisons to the index. + // If so, make sure we collect the snapshot instead of returning None in case nothing actually changed, which is likely with a + // race like this. + if !was_uninitialized && needs_init { + return Ok(Some(self.collect_snapshot())); + } + self.num_disk_state_consolidation.fetch_add(1, Ordering::Relaxed); + + let db_paths: Vec<_> = std::iter::once(objects_directory.to_owned()) + .chain(crate::alternate::resolve(objects_directory, &self.current_dir)?) + .collect(); + + // turn db paths into loose object databases. Reuse what's there, but only if it is in the right order. + let loose_dbs = if was_uninitialized + || db_paths.len() != index.loose_dbs.len() + || db_paths + .iter() + .zip(index.loose_dbs.iter().map(|ldb| &ldb.path)) + .any(|(lhs, rhs)| lhs != rhs) + { + Arc::new( + db_paths + .iter() + .map(|path| crate::loose::Store::at(path, self.object_hash)) + .collect::<Vec<_>>(), + ) + } else { + Arc::clone(&index.loose_dbs) + }; + + let indices_by_modification_time = Self::collect_indices_and_mtime_sorted_by_size( + db_paths, + index.slot_indices.len().into(), + self.use_multi_pack_index.then_some(self.object_hash), + )?; + let mut idx_by_index_path: BTreeMap<_, _> = index + .slot_indices + .iter() + .filter_map(|&idx| { + let f = &self.files[idx]; + Option::as_ref(&f.files.load()).map(|f| (f.index_path().to_owned(), idx)) + }) + .collect(); + + let mut new_slot_map_indices = Vec::new(); // these indices into the slot map still exist there/didn't change + let mut index_paths_to_add = was_uninitialized + .then(|| VecDeque::with_capacity(indices_by_modification_time.len())) + .unwrap_or_default(); + + // Figure out this number based on what we see while handling the existing indices + let mut num_loaded_indices = 0; + for (index_info, mtime) in indices_by_modification_time.into_iter().map(|(a, b, _)| (a, b)) { + match idx_by_index_path.remove(index_info.path()) { + Some(slot_idx) => { + let slot = &self.files[slot_idx]; + let files_guard = slot.files.load(); + let files = + Option::as_ref(&files_guard).expect("slot is set or we wouldn't know it points to this file"); + if index_info.is_multi_index() && files.mtime() != mtime { + // we have a changed multi-pack index. We can't just change the existing slot as it may alter slot indices + // that are currently available. Instead we have to move what's there into a new slot, along with the changes, + // and later free the slot or dispose of the index in the slot (like we do for removed/missing files). + index_paths_to_add.push_back((index_info, mtime, Some(slot_idx))); + // If the current slot is loaded, the soon-to-be copied multi-index path will be loaded as well. + if files.index_is_loaded() { + num_loaded_indices += 1; + } + } else { + // packs and indices are immutable, so no need to check modification times. Unchanged multi-pack indices also + // are handled like this just to be sure they are in the desired state. For these, the only way this could happen + // is if somebody deletes and then puts back + if Self::assure_slot_matches_index(&write, slot, index_info, mtime, index.generation) { + num_loaded_indices += 1; + } + new_slot_map_indices.push(slot_idx); + } + } + None => index_paths_to_add.push_back((index_info, mtime, None)), + } + } + let needs_stable_indices = self.maintain_stable_indices(&write); + + let mut next_possibly_free_index = index + .slot_indices + .iter() + .max() + .map(|idx| (idx + 1) % self.files.len()) + .unwrap_or(0); + let mut num_indices_checked = 0; + let mut needs_generation_change = false; + let mut slot_indices_to_remove: Vec<_> = idx_by_index_path.into_values().collect(); + while let Some((mut index_info, mtime, move_from_slot_idx)) = index_paths_to_add.pop_front() { + 'increment_slot_index: loop { + if num_indices_checked == self.files.len() { + return Err(Error::InsufficientSlots { + current: self.files.len(), + needed: index_paths_to_add.len() + 1, /*the one currently popped off*/ + }); + } + let slot_index = next_possibly_free_index; + let slot = &self.files[slot_index]; + next_possibly_free_index = (next_possibly_free_index + 1) % self.files.len(); + num_indices_checked += 1; + match move_from_slot_idx { + Some(move_from_slot_idx) => { + debug_assert!(index_info.is_multi_index(), "only set for multi-pack indices"); + if slot_index == move_from_slot_idx { + // don't try to move onto ourselves + continue 'increment_slot_index; + } + match Self::try_set_index_slot( + &write, + slot, + index_info, + mtime, + index.generation, + needs_stable_indices, + ) { + Ok(dest_was_empty) => { + slot_indices_to_remove.push(move_from_slot_idx); + new_slot_map_indices.push(slot_index); + // To avoid handling out the wrong pack (due to reassigned pack ids), declare this a new generation. + if !dest_was_empty { + needs_generation_change = true; + } + break 'increment_slot_index; + } + Err(unused_index_info) => index_info = unused_index_info, + } + } + None => { + match Self::try_set_index_slot( + &write, + slot, + index_info, + mtime, + index.generation, + needs_stable_indices, + ) { + Ok(dest_was_empty) => { + new_slot_map_indices.push(slot_index); + if !dest_was_empty { + needs_generation_change = true; + } + break 'increment_slot_index; + } + Err(unused_index_info) => index_info = unused_index_info, + } + } + } + // This isn't racy as it's only us who can change the Option::Some/None state of a slot. + } + } + assert_eq!( + index_paths_to_add.len(), + 0, + "By this time we have assigned all new files to slots" + ); + + let generation = if needs_generation_change { + index.generation.checked_add(1).ok_or(Error::GenerationOverflow)? + } else { + index.generation + }; + let index_unchanged = index.slot_indices == new_slot_map_indices; + if generation != index.generation { + assert!( + !index_unchanged, + "if the generation changed, the slot index must have changed for sure" + ); + } + if !index_unchanged || loose_dbs != index.loose_dbs { + let new_index = Arc::new(SlotMapIndex { + slot_indices: new_slot_map_indices, + loose_dbs, + generation, + // if there was a prior generation, some indices might already be loaded. But we deal with it by trying to load the next index then, + // until we find one. + next_index_to_load: index_unchanged + .then(|| Arc::clone(&index.next_index_to_load)) + .unwrap_or_default(), + loaded_indices: index_unchanged + .then(|| Arc::clone(&index.loaded_indices)) + .unwrap_or_else(|| Arc::new(num_loaded_indices.into())), + num_indices_currently_being_loaded: Default::default(), + }); + self.index.store(new_index); + } + + // deleted items - remove their slots AFTER we have set the new index if we may alter indices, otherwise we only declare them garbage. + // removing slots may cause pack loading to fail, and they will then reload their indices. + for slot in slot_indices_to_remove.into_iter().map(|idx| &self.files[idx]) { + let _lock = slot.write.lock(); + let mut files = slot.files.load_full(); + let files_mut = Arc::make_mut(&mut files); + if needs_stable_indices { + if let Some(files) = files_mut.as_mut() { + files.trash(); + // generation stays the same, as it's the same value still but scheduled for eventual removal. + } + } else { + *files_mut = None; + }; + slot.files.store(files); + if !needs_stable_indices { + // Not racy due to lock, generation must be set after unsetting the slot value AND storing it. + slot.generation.store(generation, Ordering::SeqCst); + } + } + + let new_index = self.index.load(); + Ok(if index.state_id() == new_index.state_id() { + // there was no change, and nothing was loaded in the meantime, reflect that in the return value to not get into loops + None + } else { + if load_new_index { + self.load_next_index(new_index); + } + Some(self.collect_snapshot()) + }) + } + + pub(crate) fn collect_indices_and_mtime_sorted_by_size( + db_paths: Vec<PathBuf>, + initial_capacity: Option<usize>, + multi_pack_index_object_hash: Option<gix_hash::Kind>, + ) -> Result<Vec<(Either, SystemTime, u64)>, Error> { + let mut indices_by_modification_time = Vec::with_capacity(initial_capacity.unwrap_or_default()); + for db_path in db_paths { + let packs = db_path.join("pack"); + let entries = match std::fs::read_dir(packs) { + Ok(e) => e, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }; + let indices = entries + .filter_map(Result::ok) + .filter_map(|e| e.metadata().map(|md| (e.path(), md)).ok()) + .filter(|(_, md)| md.file_type().is_file()) + .filter(|(p, _)| { + let ext = p.extension(); + (ext == Some(OsStr::new("idx")) && p.with_extension("pack").is_file()) + || (multi_pack_index_object_hash.is_some() && ext.is_none() && is_multipack_index(p)) + }) + .map(|(p, md)| md.modified().map_err(Error::from).map(|mtime| (p, mtime, md.len()))) + .collect::<Result<Vec<_>, _>>()?; + + let multi_index_info = multi_pack_index_object_hash + .and_then(|hash| { + indices.iter().find_map(|(p, a, b)| { + is_multipack_index(p) + .then(|| { + // we always open the multi-pack here to be able to remove indices + gix_pack::multi_index::File::at(p) + .ok() + .filter(|midx| midx.object_hash() == hash) + .map(|midx| (midx, *a, *b)) + }) + .flatten() + .map(|t| { + if t.0.num_indices() > PackId::max_packs_in_multi_index() { + Err(Error::TooManyPacksInMultiIndex { + index_path: p.to_owned(), + actual: t.0.num_indices(), + limit: PackId::max_packs_in_multi_index(), + }) + } else { + Ok(t) + } + }) + }) + }) + .transpose()?; + if let Some((multi_index, mtime, flen)) = multi_index_info { + let index_names_in_multi_index: Vec<_> = + multi_index.index_names().iter().map(|p| p.as_path()).collect(); + let mut indices_not_in_multi_index: Vec<(Either, _, _)> = indices + .into_iter() + .filter_map(|(path, a, b)| { + (path != multi_index.path() + && !index_names_in_multi_index + .contains(&Path::new(path.file_name().expect("file name present")))) + .then_some((Either::IndexPath(path), a, b)) + }) + .collect(); + indices_not_in_multi_index.insert(0, (Either::MultiIndexFile(Arc::new(multi_index)), mtime, flen)); + indices_by_modification_time.extend(indices_not_in_multi_index); + } else { + indices_by_modification_time.extend( + indices + .into_iter() + .filter_map(|(p, a, b)| (!is_multipack_index(&p)).then_some((Either::IndexPath(p), a, b))), + ) + } + } + // Unlike libgit2, do not sort by modification date, but by size and put the biggest indices first. That way + // the chance to hit an object should be higher. We leave it to the handle to sort by LRU. + // Git itself doesn't change the order which may safe time, but we want it to be stable which also helps some tests. + indices_by_modification_time.sort_by(|l, r| l.2.cmp(&r.2).reverse()); + Ok(indices_by_modification_time) + } + + /// returns Ok<dest slot was empty> if the copy could happen because dest-slot was actually free or disposable , and Some(true) if it was empty + #[allow(clippy::too_many_arguments)] + fn try_set_index_slot( + lock: &parking_lot::MutexGuard<'_, ()>, + dest_slot: &MutableIndexAndPack, + index_info: Either, + mtime: SystemTime, + current_generation: Generation, + needs_stable_indices: bool, + ) -> Result<bool, Either> { + let (dest_slot_was_empty, generation) = match &**dest_slot.files.load() { + Some(bundle) => { + if bundle.index_path() == index_info.path() || (bundle.is_disposable() && needs_stable_indices) { + // it might be possible to see ourselves in case all slots are taken, but there are still a few more destination + // slots to look for. + return Err(index_info); + } + // Since we overwrite an existing slot, we have to increment the generation to prevent anyone from trying to use it while + // before we are replacing it with a different value. + // In detail: + // We need to declare this to be the future to avoid anything in that slot to be returned to people who + // last saw the old state. They will then try to get a new index which by that time, might be happening + // in time so they get the latest one. If not, they will probably get into the same situation again until + // it finally succeeds. Alternatively, the object will be reported unobtainable, but at least it won't return + // some other object. + (false, current_generation + 1) + } + None => { + // For multi-pack indices: + // Do NOT copy the packs over, they need to be reopened to get the correct pack id matching the new slot map index. + // If we are allowed to delete the original, and nobody has the pack referenced, it is closed which is preferred. + // Thus we simply always start new with packs in multi-pack indices. + // In the worst case this could mean duplicate file handle usage though as the old and the new index can't share + // packs due to the intrinsic id. + // Note that the ID is used for cache access, too, so it must be unique. It must also be mappable from pack-id to slotmap id. + (true, current_generation) + } + }; + Self::set_slot_to_index(lock, dest_slot, index_info, mtime, generation); + Ok(dest_slot_was_empty) + } + + fn set_slot_to_index( + _lock: &parking_lot::MutexGuard<'_, ()>, + slot: &MutableIndexAndPack, + index_info: Either, + mtime: SystemTime, + generation: Generation, + ) { + let _lock = slot.write.lock(); + let mut files = slot.files.load_full(); + let files_mut = Arc::make_mut(&mut files); + // set the generation before we actually change the value, otherwise readers of old generations could observe the new one. + // We rather want them to turn around here and update their index, which, by that time, might actually already be available. + // If not, they would fail unable to load a pack or index they need, but that's preferred over returning wrong objects. + // Safety: can't race as we hold the lock, have to set the generation beforehand to help avoid others to observe the value. + slot.generation.store(generation, Ordering::SeqCst); + *files_mut = Some(index_info.into_index_and_packs(mtime)); + slot.files.store(files); + } + + /// Returns true if the index was left in a loaded state. + fn assure_slot_matches_index( + _lock: &parking_lot::MutexGuard<'_, ()>, + slot: &MutableIndexAndPack, + index_info: Either, + mtime: SystemTime, + current_generation: Generation, + ) -> bool { + match Option::as_ref(&slot.files.load()) { + Some(bundle) => { + assert_eq!( + bundle.index_path(), + index_info.path(), + "Parallel writers cannot change the file the slot points to." + ); + if bundle.is_disposable() { + // put it into the correct mode, it's now available for sure so should not be missing or garbage. + // The latter can happen if files are removed and put back for some reason, but we should definitely + // have them in a decent state now that we know/think they are there. + let _lock = slot.write.lock(); + let mut files = slot.files.load_full(); + let files_mut = Arc::make_mut(&mut files) + .as_mut() + .expect("BUG: cannot change from something to nothing, would be race"); + files_mut.put_back(); + debug_assert_eq!( + files_mut.mtime(), + mtime, + "BUG: we can only put back files that didn't obviously change" + ); + // Safety: can't race as we hold the lock, must be set before replacing the data. + // NOTE that we don't change the generation as it's still the very same index we talk about, it doesn't change + // identity. + slot.generation.store(current_generation, Ordering::SeqCst); + slot.files.store(files); + } else { + // it's already in the correct state, either loaded or unloaded. + } + bundle.index_is_loaded() + } + None => { + unreachable!("BUG: a slot can never be deleted if we have it recorded in the index WHILE changing said index. There shouldn't be a race") + } + } + } + + /// Stability means that indices returned by this API will remain valid. + /// Without that constraint, we may unload unused packs and indices, and may rebuild the slotmap index. + /// + /// Note that this must be called with a lock to the relevant state held to assure these values don't change while + /// we are working on said index. + fn maintain_stable_indices(&self, _guard: &parking_lot::MutexGuard<'_, ()>) -> bool { + self.num_handles_stable.load(Ordering::SeqCst) > 0 + } + + pub(crate) fn collect_snapshot(&self) -> Snapshot { + let index = self.index.load(); + let indices = if index.is_initialized() { + index + .slot_indices + .iter() + .map(|idx| (*idx, &self.files[*idx])) + .filter_map(|(id, file)| { + let lookup = match (**file.files.load()).as_ref()? { + types::IndexAndPacks::Index(bundle) => handle::SingleOrMultiIndex::Single { + index: bundle.index.loaded()?.clone(), + data: bundle.data.loaded().cloned(), + }, + types::IndexAndPacks::MultiIndex(multi) => handle::SingleOrMultiIndex::Multi { + index: multi.multi_index.loaded()?.clone(), + data: multi.data.iter().map(|f| f.loaded().cloned()).collect(), + }, + }; + handle::IndexLookup { file: lookup, id }.into() + }) + .collect() + } else { + Vec::new() + }; + + Snapshot { + indices, + loose_dbs: Arc::clone(&index.loose_dbs), + marker: index.marker(), + } + } +} + +// Outside of this method we will never assign new slot indices. +fn is_multipack_index(path: &Path) -> bool { + path.file_name() == Some(OsStr::new("multi-pack-index")) +} + +struct IncOnNewAndDecOnDrop<'a>(&'a AtomicU16); +impl<'a> IncOnNewAndDecOnDrop<'a> { + pub fn new(v: &'a AtomicU16) -> Self { + v.fetch_add(1, Ordering::SeqCst); + Self(v) + } +} +impl<'a> Drop for IncOnNewAndDecOnDrop<'a> { + fn drop(&mut self) { + self.0.fetch_sub(1, Ordering::SeqCst); + } +} + +struct IncOnDrop<'a>(&'a AtomicUsize); +impl<'a> Drop for IncOnDrop<'a> { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } +} + +pub(crate) enum Either { + IndexPath(PathBuf), + MultiIndexFile(Arc<gix_pack::multi_index::File>), +} + +impl Either { + fn path(&self) -> &Path { + match self { + Either::IndexPath(p) => p, + Either::MultiIndexFile(f) => f.path(), + } + } + + fn into_index_and_packs(self, mtime: SystemTime) -> IndexAndPacks { + match self { + Either::IndexPath(path) => IndexAndPacks::new_single(path, mtime), + Either::MultiIndexFile(file) => IndexAndPacks::new_multi_from_open_file(file, mtime), + } + } + + fn is_multi_index(&self) -> bool { + matches!(self, Either::MultiIndexFile(_)) + } +} + +impl Eq for Either {} + +impl PartialEq<Self> for Either { + fn eq(&self, other: &Self) -> bool { + self.path().eq(other.path()) + } +} + +impl PartialOrd<Self> for Either { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + self.path().partial_cmp(other.path()) + } +} + +impl Ord for Either { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.path().cmp(other.path()) + } +} |