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
use std::path::PathBuf;
use tokio::fs;
use super::LocalStore;
use crate::namespace::Namespace;
pub(super) struct Found {
pub path: PathBuf,
pub oid: String,
}
// Everything a repository holds, and whether that is everything. The second half
// is not decoration: every command built on this walk reports by silence, and an
// operator reads the report rather than the log. A collection that could not list
// a prefix, or a migration that skipped a fanout, must not come back looking like
// it finished.
pub(super) struct Walk {
pub objects: Vec<Found>,
pub complete: bool,
}
impl LocalStore {
pub(super) async fn objects_of(&self, ns: &Namespace) -> Walk {
let mut walk = Walk {
objects: Vec::new(),
complete: true,
};
let root = self.root.join(ns.org()).join(ns.repo());
let Some(prefixes) = self.entries(&root, &mut walk).await else {
// A repository with nothing in it has no directory, and that is not
// an incomplete walk — it is an empty one.
return Walk {
objects: Vec::new(),
complete: true,
};
};
for prefix in prefixes {
let Some(fanouts) = self.entries(&prefix, &mut walk).await else {
continue;
};
for fanout in fanouts {
let Some(objects) = self.entries(&fanout, &mut walk).await else {
continue;
};
for path in objects {
let oid = path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_default();
if Self::validate_oid(&oid).is_ok() {
walk.objects.push(Found { path, oid });
}
}
}
}
walk
}
// None means this directory could not be read at all, which is different from
// an empty one and is recorded as such.
async fn entries(&self, directory: &std::path::Path, walk: &mut Walk) -> Option<Vec<PathBuf>> {
let mut entries = match fs::read_dir(directory).await {
Ok(entries) => entries,
Err(error) => {
tracing::warn!(?directory, %error, "could not be listed");
walk.complete = false;
return None;
}
};
let mut found = Vec::new();
loop {
match entries.next_entry().await {
Ok(Some(entry)) => found.push(entry.path()),
Ok(None) => return Some(found),
Err(error) => {
tracing::warn!(?directory, %error, "listing stopped early");
walk.complete = false;
return Some(found);
}
}
}
}
}