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
use std::{ops::Deref, option::Option::None, rc::Rc, sync::Arc};

use git_hash::ObjectId;

use crate::store_impls::{linked, loose};

#[allow(clippy::large_enum_variant)]
enum DbState {
    Pack { pack_index: usize, entry_index: u32 },
    Loose { iter: loose::Iter },
}

impl Default for DbState {
    fn default() -> Self {
        DbState::Pack {
            pack_index: 0,
            entry_index: 0,
        }
    }
}

/// An iterator over all objects of a linked database
pub struct AllObjects<Db> {
    db: Db,
    db_index: usize,
    db_state: DbState,
}

impl<Db> AllObjects<Db>
where
    Db: Deref<Target = linked::Store>,
{
    /// Create a new iterator from a linked database
    pub fn new(db: Db) -> Self {
        let db_index = 0;
        let db_state = {
            let db = &db
                .dbs
                .get(db_index)
                .expect("at least one db or no linked::Store at all");
            if db.bundles.is_empty() {
                DbState::Loose { iter: db.loose.iter() }
            } else {
                DbState::default()
            }
        };
        AllObjects { db, db_index, db_state }
    }
}

impl<Db> Iterator for AllObjects<Db>
where
    Db: Deref<Target = linked::Store>,
{
    type Item = Result<ObjectId, loose::iter::Error>;

    fn next(&mut self) -> Option<Self::Item> {
        let db = &self.db;
        if self.db_index == db.dbs.len() {
            return None;
        }

        match &mut self.db_state {
            DbState::Pack {
                pack_index,
                entry_index,
            } => {
                let db = &db.dbs[self.db_index];
                match db.bundles.get(*pack_index) {
                    Some(bundle) => {
                        if *entry_index < bundle.index.num_objects() {
                            let oid = bundle.index.oid_at_index(*entry_index).to_owned();
                            *entry_index += 1;
                            Some(Ok(oid))
                        } else {
                            *pack_index += 1;
                            *entry_index = 0;
                            self.next()
                        }
                    }
                    None => {
                        self.db_state = DbState::Loose { iter: db.loose.iter() };
                        self.next()
                    }
                }
            }
            DbState::Loose { iter } => match iter.next() {
                Some(id) => Some(id),
                None => {
                    self.db_index += 1;
                    self.db_state = Default::default();
                    self.next()
                }
            },
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let packed_objects = self.db.dbs.iter().fold(0usize, |dbc, db| {
            dbc.saturating_add(
                db.bundles
                    .iter()
                    .fold(0, |pc, pack| pc.saturating_add(pack.index.num_objects() as usize)),
            )
        });
        (packed_objects, None)
    }
}

impl linked::Store {
    /// Return an iterator over all objects in all linked databases, database after database, first packed
    /// objects with the 'best' packs first, followed by loose objects.
    /// For specialized iterations, use the `dbs` fields directly as all databases are accessible.
    pub fn iter(&self) -> AllObjects<&linked::Store> {
        AllObjects::new(self)
    }

    /// Like [`iter()`][linked::Store::iter()] but works with this instance living in an [`Arc`]
    ///
    /// Useful in conjunction with `'static threads`.
    pub fn arc_iter(self: &Arc<linked::Store>) -> AllObjects<Arc<linked::Store>> {
        AllObjects::new(Arc::clone(self))
    }

    /// Like [`iter()`][linked::Store::iter()] but works with this instance living in an [`Rc`]
    pub fn rc_iter(self: &Rc<linked::Store>) -> AllObjects<Rc<linked::Store>> {
        AllObjects::new(Rc::clone(self))
    }
}