1use std::{collections::HashSet, path::Path};
7
8use anyhow::{Result, anyhow};
9use objects::object::{ChangeId, ContentHash};
10use repo::Repository;
11
12pub struct LocalSync {
14 source: Repository,
15}
16
17impl LocalSync {
18 pub fn open(path: &Path) -> Result<Self> {
20 let source = Repository::open(path)?;
21 Ok(Self { source })
22 }
23
24 pub fn source(&self) -> &Repository {
26 &self.source
27 }
28
29 pub fn list_threads(&self) -> Result<Vec<(String, ChangeId)>> {
31 let mut threads = Vec::new();
32 for thread in self.source.refs().list_threads()? {
33 if let Some(state_id) = self.source.refs().get_thread(&thread)? {
34 threads.push((thread, state_id));
35 }
36 }
37 Ok(threads)
38 }
39
40 pub fn list_markers(&self) -> Result<Vec<(String, ChangeId)>> {
42 let mut markers = Vec::new();
43 for marker in self.source.refs().list_markers()? {
44 if let Some(state_id) = self.source.refs().get_marker(&marker)? {
45 markers.push((marker, state_id));
46 }
47 }
48 Ok(markers)
49 }
50
51 pub fn fetch_state(&self, target: &Repository, state_id: &ChangeId) -> Result<usize> {
53 let mut copied = 0;
54 let mut visited = HashSet::new();
55 self.copy_state_recursive(target, state_id, &mut visited, &mut copied, None)?;
56 Ok(copied)
57 }
58
59 pub fn fetch_state_with_depth(
64 &self,
65 target: &Repository,
66 state_id: &ChangeId,
67 depth: u32,
68 ) -> Result<usize> {
69 let mut copied = 0;
70 let mut visited = HashSet::new();
71 self.copy_state_recursive(target, state_id, &mut visited, &mut copied, Some(depth))?;
72 Ok(copied)
73 }
74
75 fn copy_state_recursive(
76 &self,
77 target: &Repository,
78 state_id: &ChangeId,
79 visited: &mut HashSet<ChangeId>,
80 copied: &mut usize,
81 max_depth: Option<u32>,
82 ) -> Result<()> {
83 if visited.contains(state_id) {
84 return Ok(());
85 }
86 visited.insert(*state_id);
87
88 if target.store().has_state(state_id)? {
90 return Ok(());
91 }
92
93 let state = self
95 .source
96 .store()
97 .get_state(state_id)?
98 .ok_or_else(|| anyhow!("State {} not found in source", state_id))?;
99
100 self.copy_tree_recursive(target, &state.tree, copied)?;
102 if let Some(provenance_root) = state.provenance {
103 self.copy_tree_recursive(target, &provenance_root, copied)?;
104 }
105 if let Some(context_root) = state.context {
106 self.copy_tree_recursive(target, &context_root, copied)?;
107 }
108
109 if let Some(depth) = max_depth {
111 if depth > 0 {
112 for parent in &state.parents {
113 self.copy_state_recursive(target, parent, visited, copied, Some(depth - 1))?;
114 }
115 } else {
116 target.set_shallow(state_id, &state.parents)?;
118 }
119 } else {
120 for parent in &state.parents {
121 self.copy_state_recursive(target, parent, visited, copied, None)?;
122 }
123 }
124
125 target.store().put_state(&state)?;
127 *copied += 1;
128
129 Ok(())
130 }
131
132 fn copy_tree_recursive(
133 &self,
134 target: &Repository,
135 tree_hash: &ContentHash,
136 copied: &mut usize,
137 ) -> Result<()> {
138 if target.store().has_tree(tree_hash)? {
140 return Ok(());
141 }
142
143 let tree = self
145 .source
146 .store()
147 .get_tree(tree_hash)?
148 .ok_or_else(|| anyhow!("Tree {} not found in source", tree_hash))?;
149
150 for entry in tree.entries() {
152 match entry.entry_type {
153 objects::object::EntryType::Blob => {
154 if !target.store().has_blob(&entry.hash)? {
155 let blob = self.source.require_blob(&entry.hash)?;
156 target.store().put_blob(&blob)?;
157 *copied += 1;
158 }
159 }
160 objects::object::EntryType::Tree => {
161 self.copy_tree_recursive(target, &entry.hash, copied)?;
162 }
163 objects::object::EntryType::Symlink => {
164 if !target.store().has_blob(&entry.hash)? {
165 let blob = self.source.require_blob(&entry.hash)?;
166 target.store().put_blob(&blob)?;
167 *copied += 1;
168 }
169 }
170 }
171 }
172
173 target.store().put_tree(&tree)?;
175 *copied += 1;
176
177 Ok(())
178 }
179
180 pub fn copy_blob(&self, target: &Repository, hash: &ContentHash) -> Result<bool> {
182 if target.store().has_blob(hash)? {
183 return Ok(false);
184 }
185
186 let blob = self.source.require_blob(hash)?;
187
188 target.store().put_blob(&blob)?;
189 Ok(true)
190 }
191}