Serialize this change to a writer using the V3 format. Writes the complete V3 change file (header, hash table, sections, trailer) to the given writer. The content hash is computed incrementally as sections are written. # Arguments `writer` - Where to write the serialized change # Returns The blake3 content hash of the change (its identity). # Errors Returns an error if serialization, compre
(&self, writer: &mut W)
| 305 | /// |
| 306 | /// Returns an error if serialization, compression, or I/O fails. |
| 307 | pub fn serialize<W: Write>(&self, writer: &mut W) -> Result<Hash, ChangeError> { |
| 308 | use format_v3::*; |
| 309 | |
| 310 | // 1. Build the hash dedup table from all referenced hashes |
| 311 | // Use a placeholder self-hash (we'll know the real one after finalize) |
| 312 | let placeholder_hash = [0u8; 32]; |
| 313 | let mut hash_table = HashDedupTable::new(placeholder_hash); |
| 314 | |
| 315 | // Collect all unique hashes from dependencies |
| 316 | for dep in &self.hashed.dependencies { |
| 317 | hash_table.insert(*dep.as_bytes())?; |
| 318 | } |
| 319 | for dep in &self.hashed.extra_known { |
| 320 | hash_table.insert(*dep.as_bytes())?; |
| 321 | } |
| 322 | |
| 323 | // Collect all unique hashes from hunks |
| 324 | self.collect_hunk_hashes(&mut hash_table)?; |
| 325 | |
| 326 | // 2. Compute section counts |
| 327 | let has_provenance = !self.hashed.provenance.is_empty(); |
| 328 | let has_unhashed = self.unhashed.is_some(); |
| 329 | let has_content = !self.contents.is_empty(); |
| 330 | |
| 331 | // Chunk content with FastCDC for delta transfer + parallel compression |
| 332 | let content_chunks = if has_content { |
| 333 | format_v3::chunk_content(&self.contents, &format_v3::ChunkingOptions::default()) |
| 334 | } else { |
| 335 | Vec::new() |
| 336 | }; |
| 337 | |
| 338 | // For now, we write all hunks in a single GRAPH section (per-file splitting is future work) |
| 339 | let graph_section_count = if self.hashed.hunks.is_empty() { |
| 340 | 0u32 |
| 341 | } else { |
| 342 | 1 |
| 343 | }; |
| 344 | let semantic_section_count = if self.hashed.file_ops.is_empty() { |
| 345 | 0u32 |
| 346 | } else { |
| 347 | 1 |
| 348 | }; |
| 349 | let contents_chunks_count = content_chunks.len() as u32; |
| 350 | |
| 351 | // 3. Build file header |
| 352 | let mut file_header_builder = FileHeader::builder() |
| 353 | .hash_table_entries(hash_table.len() as u32) |
| 354 | .graph_section_count(graph_section_count) |
| 355 | .semantic_section_count(semantic_section_count) |
| 356 | .contents_chunks(contents_chunks_count); |
| 357 | |
| 358 | if has_provenance { |
| 359 | file_header_builder = file_header_builder.with_provenance(); |
| 360 | } |
| 361 | if has_unhashed { |
| 362 | file_header_builder = file_header_builder.with_unhashed(); |
| 363 | } |
| 364 |