Skip to content

Commit 8c72a23

Browse files
committed
feat: add Store::alternate_db_paths().
Provides a way to learn about loose database paths that are provided by git alternates.
1 parent 1bd93be commit 8c72a23

File tree

5 files changed

+165
-99
lines changed

5 files changed

+165
-99
lines changed

gix-odb/src/store_impls/dynamic/mod.rs

Lines changed: 1 addition & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -82,102 +82,4 @@ mod metrics;
8282
mod access;
8383

8484
///
85-
pub mod structure {
86-
use std::path::PathBuf;
87-
88-
use crate::{store::load_index, types::IndexAndPacks, Store};
89-
90-
/// A record of a structural element of an object database.
91-
#[derive(Debug, Clone, PartialEq, Eq)]
92-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93-
pub enum Record {
94-
/// A loose object database.
95-
LooseObjectDatabase {
96-
/// The root of the object database.
97-
objects_directory: PathBuf,
98-
/// The amount of object files.
99-
num_objects: usize,
100-
},
101-
/// A pack index file
102-
Index {
103-
/// The location of the index file,
104-
path: PathBuf,
105-
/// Whether or not the index is mapped into memory.
106-
state: IndexState,
107-
},
108-
/// A multi-index file
109-
MultiIndex {
110-
/// The location of the multi-index file,
111-
path: PathBuf,
112-
/// Whether or not the index is mapped into memory.
113-
state: IndexState,
114-
},
115-
/// An empty slot was encountered, this is possibly happening as the ODB changes during query with
116-
/// a file being removed.
117-
Empty,
118-
}
119-
120-
#[derive(Debug, Clone, PartialEq, Eq)]
121-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122-
/// Possible stats of pack indices.
123-
pub enum IndexState {
124-
/// The index is active in memory because a mapping exists.
125-
Loaded,
126-
/// The index couldn't be unloaded as it was still in use, but that can happen another time.
127-
Disposable,
128-
/// The index isn't loaded/memory mapped.
129-
Unloaded,
130-
}
131-
132-
impl Store {
133-
/// Return information about all files known to us as well as their loading state.
134-
///
135-
/// Note that this call is expensive as it gathers additional information about loose object databases.
136-
/// Note that it may change as we collect information due to the highly volatile nature of the
137-
/// implementation. The likelihood of actual changes is low though as these still depend on something
138-
/// changing on disk and somebody reading at the same time.
139-
pub fn structure(&self) -> Result<Vec<Record>, load_index::Error> {
140-
let index = self.index.load();
141-
if !index.is_initialized() {
142-
self.consolidate_with_disk_state(true, false /*load one new index*/)?;
143-
}
144-
let index = self.index.load();
145-
let mut res: Vec<_> = index
146-
.loose_dbs
147-
.iter()
148-
.map(|db| Record::LooseObjectDatabase {
149-
objects_directory: db.path.clone(),
150-
num_objects: db.iter().count(),
151-
})
152-
.collect();
153-
154-
for slot in index.slot_indices.iter().map(|idx| &self.files[*idx]) {
155-
let files = slot.files.load();
156-
let record = match &**files {
157-
Some(index) => {
158-
let state = if index.is_disposable() {
159-
IndexState::Disposable
160-
} else if index.index_is_loaded() {
161-
IndexState::Loaded
162-
} else {
163-
IndexState::Unloaded
164-
};
165-
match index {
166-
IndexAndPacks::Index(b) => Record::Index {
167-
path: b.index.path().into(),
168-
state,
169-
},
170-
IndexAndPacks::MultiIndex(b) => Record::MultiIndex {
171-
path: b.multi_index.path().into(),
172-
state,
173-
},
174-
}
175-
}
176-
None => Record::Empty,
177-
};
178-
res.push(record);
179-
}
180-
Ok(res)
181-
}
182-
}
183-
}
85+
pub mod structure;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::path::PathBuf;
2+
3+
use crate::{store::load_index, types::IndexAndPacks, Store};
4+
5+
/// A record of a structural element of an object database.
6+
#[derive(Debug, Clone, PartialEq, Eq)]
7+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8+
pub enum Record {
9+
/// A loose object database.
10+
LooseObjectDatabase {
11+
/// The root of the object database.
12+
objects_directory: PathBuf,
13+
/// The amount of object files.
14+
num_objects: usize,
15+
},
16+
/// A pack index file
17+
Index {
18+
/// The location of the index file,
19+
path: PathBuf,
20+
/// Whether or not the index is mapped into memory.
21+
state: IndexState,
22+
},
23+
/// A multi-index file
24+
MultiIndex {
25+
/// The location of the multi-index file,
26+
path: PathBuf,
27+
/// Whether or not the index is mapped into memory.
28+
state: IndexState,
29+
},
30+
/// An empty slot was encountered, this is possibly happening as the ODB changes during query with
31+
/// a file being removed.
32+
Empty,
33+
}
34+
35+
#[derive(Debug, Clone, PartialEq, Eq)]
36+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37+
/// Possible stats of pack indices.
38+
pub enum IndexState {
39+
/// The index is active in memory because a mapping exists.
40+
Loaded,
41+
/// The index couldn't be unloaded as it was still in use, but that can happen another time.
42+
Disposable,
43+
/// The index isn't loaded/memory mapped.
44+
Unloaded,
45+
}
46+
47+
impl Store {
48+
/// Return information about all files known to us as well as their loading state.
49+
///
50+
/// Note that this call is expensive as it gathers additional information about loose object databases.
51+
/// Note that it may change as we collect information due to the highly volatile nature of the
52+
/// implementation. The likelihood of actual changes is low though as these still depend on something
53+
/// changing on disk and somebody reading at the same time.
54+
pub fn structure(&self) -> Result<Vec<Record>, load_index::Error> {
55+
let index = self.index.load();
56+
if !index.is_initialized() {
57+
self.consolidate_with_disk_state(true, false /*load one new index*/)?;
58+
}
59+
let index = self.index.load();
60+
let mut res: Vec<_> = index
61+
.loose_dbs
62+
.iter()
63+
.map(|db| Record::LooseObjectDatabase {
64+
objects_directory: db.path.clone(),
65+
num_objects: db.iter().count(),
66+
})
67+
.collect();
68+
69+
for slot in index.slot_indices.iter().map(|idx| &self.files[*idx]) {
70+
let files = slot.files.load();
71+
let record = match &**files {
72+
Some(index) => {
73+
let state = if index.is_disposable() {
74+
IndexState::Disposable
75+
} else if index.index_is_loaded() {
76+
IndexState::Loaded
77+
} else {
78+
IndexState::Unloaded
79+
};
80+
match index {
81+
IndexAndPacks::Index(b) => Record::Index {
82+
path: b.index.path().into(),
83+
state,
84+
},
85+
IndexAndPacks::MultiIndex(b) => Record::MultiIndex {
86+
path: b.multi_index.path().into(),
87+
state,
88+
},
89+
}
90+
}
91+
None => Record::Empty,
92+
};
93+
res.push(record);
94+
}
95+
Ok(res)
96+
}
97+
98+
/// Provide a list of all `objects` directories of `alternate` object database paths.
99+
/// This list might be empty if there are no alternates.
100+
///
101+
/// Read more about alternates in the documentation of the [`resolve`][crate::alternate::resolve()] function.
102+
pub fn alternate_db_paths(&self) -> Result<Vec<PathBuf>, load_index::Error> {
103+
let index = self.index.load();
104+
if !index.is_initialized() {
105+
self.consolidate_with_disk_state(true, false /*load one new index*/)?;
106+
}
107+
let index = self.index.load();
108+
Ok(index
109+
.loose_dbs
110+
.iter()
111+
.skip(
112+
1, /* first odb is always the primary one, all the follows is alternates */
113+
)
114+
.map(|db| db.path.clone())
115+
.collect())
116+
}
117+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
repo_with_loose_objects.tar.xz
2+
make_alternates_odb.tar.xz
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
git checkout -b main
7+
touch this
8+
git add this
9+
git commit -q -m c1
10+
echo hello >> this
11+
git commit -q -am c2
12+
13+
(git init object_source && cd object_source
14+
git checkout -b main
15+
touch this
16+
git add this
17+
git commit -q -m alternate-c1
18+
)
19+
20+
echo $PWD/object_source/.git/objects > .git/objects/info/alternates
21+

gix-odb/tests/odb/store/dynamic.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ fn multi_index_access() -> crate::Result {
135135
);
136136

137137
assert_eq!(handle.store_ref().structure()?.len(), 2);
138+
assert_eq!(
139+
handle.store_ref().alternate_db_paths()?.len(),
140+
0,
141+
"there are no alternates"
142+
);
138143
Ok(())
139144
}
140145

@@ -215,6 +220,26 @@ fn write() -> crate::Result {
215220
Ok(())
216221
}
217222

223+
#[test]
224+
fn alternate_dbs_query() -> crate::Result {
225+
let dir = gix_testtools::scripted_fixture_read_only("make_alternates_odb.sh")?;
226+
let handle = gix_odb::at(dir.join(".git/objects"))?;
227+
228+
let alternates = handle.store_ref().alternate_db_paths()?;
229+
assert_eq!(alternates.len(), 1, "exactly one alternate");
230+
assert_eq!(
231+
alternates[0]
232+
.ancestors()
233+
.nth(2)
234+
.expect("alternate repo work-dir")
235+
.file_name()
236+
.expect("present"),
237+
"object_source",
238+
"the alternate object location is well-known"
239+
);
240+
Ok(())
241+
}
242+
218243
#[test]
219244
fn object_replacement() -> crate::Result {
220245
let dir = gix_testtools::scripted_fixture_read_only("make_replaced_history.sh")?;

0 commit comments

Comments
 (0)