1use objects::{object::StateId, store::ObjectStore};
10
11use crate::{
12 ObjectInfo, ObjectType, ObjectTypeBucket, PlannedObject, Result, StateClosureOptions,
13 enumerate_state_closure_plan_with_options,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum GitLaneTransferIntent {
18 #[default]
21 HeddleObjectsOnly,
22 ExistingImplementation,
26 BlockedOnSleyReachablePackPlanning,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct RepositoryTransferPlan<T = PlannedObject> {
34 pub partitions: TransferPartitions<T>,
35 pub stats: TransferPlanStats,
36 pub git_lane: GitLaneTransferIntent,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct TransferPartitions<T = PlannedObject> {
41 pub packable_objects: Vec<T>,
42 pub sidecar_objects: Vec<T>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub struct TransferPlanStats {
47 pub total_objects: usize,
48 pub packable_objects: usize,
49 pub sidecar_objects: usize,
50 pub blobs: usize,
51 pub trees: usize,
52 pub states: usize,
53 pub actions: usize,
54 pub redactions: usize,
55 pub state_visibilities: usize,
56 pub state_attachments: usize,
57}
58
59impl RepositoryTransferPlan<PlannedObject> {
60 pub fn from_state_closure_plan(
61 store: &impl ObjectStore,
62 root: StateId,
63 options: StateClosureOptions,
64 git_lane: GitLaneTransferIntent,
65 ) -> Result<Self> {
66 let objects = enumerate_state_closure_plan_with_options(store, root, options)?;
67 Ok(Self::from_planned_objects(objects, git_lane))
68 }
69
70 pub fn from_planned_objects(
71 objects: impl IntoIterator<Item = PlannedObject>,
72 git_lane: GitLaneTransferIntent,
73 ) -> Self {
74 build_plan(objects, planned_object_type, git_lane)
75 }
76}
77
78impl RepositoryTransferPlan<ObjectInfo> {
79 pub fn from_object_infos(
80 objects: impl IntoIterator<Item = ObjectInfo>,
81 git_lane: GitLaneTransferIntent,
82 ) -> Self {
83 build_plan(objects, object_info_type, git_lane)
84 }
85}
86
87impl<T> RepositoryTransferPlan<T> {
88 pub fn requires_native_pack(&self, include_full_closure: bool) -> bool {
89 include_full_closure || self.stats.packable_objects > 0
90 }
91
92 pub fn is_heddle_only(&self) -> bool {
93 self.git_lane == GitLaneTransferIntent::HeddleObjectsOnly
94 }
95}
96
97impl<T> TransferPartitions<T> {
98 pub fn is_empty(&self) -> bool {
99 self.packable_objects.is_empty() && self.sidecar_objects.is_empty()
100 }
101
102 pub fn len(&self) -> usize {
103 self.packable_objects.len() + self.sidecar_objects.len()
104 }
105
106 pub fn iter(&self) -> impl Iterator<Item = &T> {
107 self.packable_objects
108 .iter()
109 .chain(self.sidecar_objects.iter())
110 }
111
112 pub fn is_sidecar_object_type(obj_type: ObjectType) -> bool {
113 !obj_type.packable()
114 }
115}
116
117impl<T> Default for TransferPartitions<T> {
118 fn default() -> Self {
119 Self {
120 packable_objects: Vec::new(),
121 sidecar_objects: Vec::new(),
122 }
123 }
124}
125
126impl TransferPlanStats {
127 fn record(&mut self, obj_type: ObjectType) {
128 self.total_objects += 1;
129 if TransferPartitions::<()>::is_sidecar_object_type(obj_type) {
130 self.sidecar_objects += 1;
131 } else {
132 self.packable_objects += 1;
133 }
134 match obj_type.bucket() {
135 ObjectTypeBucket::Blob => self.blobs += 1,
136 ObjectTypeBucket::Tree => self.trees += 1,
137 ObjectTypeBucket::State => self.states += 1,
138 ObjectTypeBucket::Action => self.actions += 1,
139 ObjectTypeBucket::Redaction => self.redactions += 1,
140 ObjectTypeBucket::StateVisibility => self.state_visibilities += 1,
141 ObjectTypeBucket::StateAttachment => self.state_attachments += 1,
142 }
143 }
144}
145
146fn build_plan<T>(
147 objects: impl IntoIterator<Item = T>,
148 object_type: fn(&T) -> ObjectType,
149 git_lane: GitLaneTransferIntent,
150) -> RepositoryTransferPlan<T> {
151 let mut partitions = TransferPartitions::default();
152 let mut stats = TransferPlanStats::default();
153
154 for object in objects {
155 let obj_type = object_type(&object);
156 stats.record(obj_type);
157 if TransferPartitions::<T>::is_sidecar_object_type(obj_type) {
158 partitions.sidecar_objects.push(object);
159 } else {
160 partitions.packable_objects.push(object);
161 }
162 }
163
164 RepositoryTransferPlan {
165 partitions,
166 stats,
167 git_lane,
168 }
169}
170
171fn planned_object_type(object: &PlannedObject) -> ObjectType {
172 object.obj_type
173}
174
175fn object_info_type(object: &ObjectInfo) -> ObjectType {
176 object.obj_type
177}
178
179#[cfg(test)]
180mod tests {
181 use objects::object::{ContentHash, StateId};
182
183 use super::*;
184 use crate::ObjectId;
185
186 fn hash(byte: u8) -> ContentHash {
187 ContentHash::from_bytes([byte; 32])
188 }
189
190 #[test]
191 fn partitions_split_native_pack_objects_from_sidecars() {
192 let state = StateId::from_bytes([9; 32]);
193 let plan = RepositoryTransferPlan::from_planned_objects(
194 vec![
195 PlannedObject {
196 id: ObjectId::Hash(hash(1)),
197 obj_type: ObjectType::Blob,
198 },
199 PlannedObject {
200 id: ObjectId::Hash(hash(2)),
201 obj_type: ObjectType::Tree,
202 },
203 PlannedObject {
204 id: ObjectId::Hash(hash(1)),
205 obj_type: ObjectType::Redaction,
206 },
207 PlannedObject {
208 id: ObjectId::StateId(state),
209 obj_type: ObjectType::StateVisibility,
210 },
211 ],
212 GitLaneTransferIntent::HeddleObjectsOnly,
213 );
214
215 assert_eq!(plan.partitions.packable_objects.len(), 2);
216 assert_eq!(plan.partitions.sidecar_objects.len(), 2);
217 assert_eq!(plan.stats.total_objects, 4);
218 assert_eq!(plan.stats.packable_objects, 2);
219 assert_eq!(plan.stats.sidecar_objects, 2);
220 assert_eq!(plan.stats.blobs, 1);
221 assert_eq!(plan.stats.trees, 1);
222 assert_eq!(plan.stats.redactions, 1);
223 assert_eq!(plan.stats.state_visibilities, 1);
224 assert!(plan.requires_native_pack(false));
225 }
226
227 #[test]
228 fn sidecar_only_plan_does_not_require_native_pack() {
229 let state = StateId::from_bytes([3; 32]);
230 let plan = RepositoryTransferPlan::from_object_infos(
231 vec![ObjectInfo {
232 id: ObjectId::StateId(state),
233 obj_type: ObjectType::StateVisibility,
234 size: 128,
235 delta_base: None,
236 }],
237 GitLaneTransferIntent::HeddleObjectsOnly,
238 );
239
240 assert!(!plan.requires_native_pack(false));
241 assert!(plan.requires_native_pack(true));
242 assert_eq!(plan.stats.packable_objects, 0);
243 assert_eq!(plan.stats.sidecar_objects, 1);
244 }
245
246 #[test]
247 fn git_lane_intents_name_current_and_sley_gated_paths() {
248 let hosted = RepositoryTransferPlan::from_planned_objects(
249 Vec::<PlannedObject>::new(),
250 GitLaneTransferIntent::ExistingImplementation,
251 );
252 let sley_blocked = RepositoryTransferPlan::from_planned_objects(
253 Vec::<PlannedObject>::new(),
254 GitLaneTransferIntent::BlockedOnSleyReachablePackPlanning,
255 );
256
257 assert_eq!(
258 hosted.git_lane,
259 GitLaneTransferIntent::ExistingImplementation
260 );
261 assert_eq!(
262 sley_blocked.git_lane,
263 GitLaneTransferIntent::BlockedOnSleyReachablePackPlanning
264 );
265 assert!(!hosted.is_heddle_only());
266 assert!(!sley_blocked.is_heddle_only());
267 }
268}