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