1use std::collections::HashSet;
7
8use cadvm_store::{Category, ObjectId};
9
10use crate::error::Result;
11use crate::repo::{Head, Repository};
12
13#[derive(Debug, Clone, Default)]
15pub struct GcPlan {
16 pub commits: Vec<ObjectId>,
17 pub manifests: Vec<ObjectId>,
18 pub blobs: Vec<ObjectId>,
19 pub chunks: Vec<ObjectId>,
20}
21
22impl GcPlan {
23 pub fn total(&self) -> usize {
25 self.commits.len() + self.manifests.len() + self.blobs.len() + self.chunks.len()
26 }
27
28 pub fn is_empty(&self) -> bool {
29 self.total() == 0
30 }
31}
32
33#[derive(Debug, Default)]
35struct Reachable {
36 commits: HashSet<ObjectId>,
37 manifests: HashSet<ObjectId>,
38 blobs: HashSet<ObjectId>,
39 chunks: HashSet<ObjectId>,
40}
41
42fn reachable(repo: &Repository) -> Result<Reachable> {
43 let mut seen = Reachable::default();
44
45 let mut frontier: Vec<ObjectId> = Vec::new();
47 for branch in repo.list_branches()? {
48 if let Some(id) = repo.read_ref(&branch)? {
49 frontier.push(id);
50 }
51 }
52 if let Head::Detached(id) = repo.read_head()? {
53 frontier.push(id);
54 }
55
56 while let Some(commit_id) = frontier.pop() {
58 if !seen.commits.insert(commit_id.clone()) {
59 continue;
60 }
61 let commit = repo.read_commit(&commit_id)?;
62 seen.manifests.insert(commit.manifest.clone());
63 let manifest = repo.read_manifest(&commit.manifest)?;
64 for entry in manifest.files.values() {
65 for chunk in &entry.blob_ref.chunks {
69 seen.chunks.insert(chunk.hash.clone());
70 }
71 }
72 for parent in commit.parents {
73 frontier.push(parent);
74 }
75 }
76
77 Ok(seen)
78}
79
80pub fn plan(repo: &Repository) -> Result<GcPlan> {
82 let live = reachable(repo)?;
83 let store = repo.store();
84
85 let unref = |cat: Category, live: &HashSet<ObjectId>| -> Result<Vec<ObjectId>> {
86 Ok(store
87 .list(cat)?
88 .into_iter()
89 .filter(|id| !live.contains(id))
90 .collect())
91 };
92
93 Ok(GcPlan {
94 commits: unref(Category::Commit, &live.commits)?,
95 manifests: unref(Category::Manifest, &live.manifests)?,
96 blobs: unref(Category::Blob, &live.blobs)?,
97 chunks: unref(Category::Chunk, &live.chunks)?,
98 })
99}
100
101pub fn prune(repo: &Repository, plan: &GcPlan) -> Result<usize> {
103 let store = repo.store();
104 let mut removed = 0usize;
105 for id in &plan.commits {
106 removed += store.remove(Category::Commit, id)? as usize;
107 }
108 for id in &plan.manifests {
109 removed += store.remove(Category::Manifest, id)? as usize;
110 }
111 for id in &plan.blobs {
112 removed += store.remove(Category::Blob, id)? as usize;
113 }
114 for id in &plan.chunks {
115 removed += store.remove(Category::Chunk, id)? as usize;
116 }
117 Ok(removed)
118}