Skip to main content

git_internal/internal/pack/encode/
output.rs

1//! Top-level convenience: encode entries directly to a `.pack` / `.idx` file pair.
2//!
3//! The pack is first written to a temporary file because its final name contains the checksum,
4//! which is not known until encoding completes. A background task drains the encoder's pack
5//! channel while the caller-facing task performs encoding. After the pack is finalized and
6//! renamed, a second writer drains the generated index bytes.
7
8use std::path::PathBuf;
9
10use chrono::Utc;
11use tokio::{fs::File, io::AsyncWriteExt as TokioAsyncWriteExt, sync::mpsc};
12
13use super::PackEncoder;
14use crate::{
15    errors::GitError,
16    internal::{
17        metadata::{EntryMeta, MetaAttached},
18        pack::entry::Entry,
19    },
20};
21
22/// Consume entries and write a matching `.pack`/`.idx` pair into `output_dir`.
23///
24/// The pack is first written to a temporary file because its final name contains the checksum,
25/// which is not known until encoding completes. A background task drains the encoder's pack
26/// channel while the caller-facing task performs encoding. After the pack is finalized and
27/// renamed, a second writer drains the generated index bytes.
28///
29/// `object_number` must equal the number of entries eventually received. A `window_size` of zero
30/// disables delta compression; any non-zero value selects the delta-search path. The default build
31/// uses Rabin fingerprinting; builds without `diff_rabin` use Myers or Patience.
32pub async fn encode_and_output_to_files(
33    raw_entries_rx: mpsc::Receiver<MetaAttached<Entry, EntryMeta>>,
34    object_number: usize,
35    output_dir: PathBuf,
36    window_size: usize,
37) -> Result<(), GitError> {
38    let (pack_tx, mut pack_rx) = mpsc::channel(1024);
39    let (idx_tx, mut idx_rx) = mpsc::channel(1024);
40    let mut pack_encoder = PackEncoder::new_with_idx(object_number, window_size, pack_tx, idx_tx);
41
42    // The checksum-based final filename is unknown until the complete pack has been hashed.
43    let now = Utc::now();
44    let timestamp = now.format("%Y%m%d%H%M%S%.3f").to_string();
45    let tmp_path = output_dir.join(format!("{}objects.pack.tmp", timestamp));
46    let mut pack_file = File::create(&tmp_path).await?;
47
48    // Drain pack chunks concurrently so a full channel does not stall the encoder behind file I/O.
49    let pack_writer = tokio::spawn(async move {
50        while let Some(chunk) = pack_rx.recv().await {
51            TokioAsyncWriteExt::write_all(&mut pack_file, &chunk).await?;
52        }
53        TokioAsyncWriteExt::flush(&mut pack_file).await?;
54        Ok::<(), GitError>(())
55    });
56
57    pack_encoder.encode(raw_entries_rx).await?;
58
59    // Closing PackEncoder's sender ends the writer loop; wait before renaming the file.
60    let pack_write_result = pack_writer
61        .await
62        .map_err(|e| GitError::PackEncodeError(format!("pack writer task join error: {e}")))?;
63    pack_write_result?;
64
65    let final_pack_name =
66        output_dir.join(format!("pack-{}.pack", pack_encoder.final_hash.unwrap()));
67    let final_idx_name = output_dir.join(format!("pack-{}.idx", pack_encoder.final_hash.unwrap()));
68    tokio::fs::rename(tmp_path, &final_pack_name).await?;
69
70    let mut idx_file = File::create(&final_idx_name).await?;
71    let idx_writer = tokio::spawn(async move {
72        while let Some(chunk) = idx_rx.recv().await {
73            TokioAsyncWriteExt::write_all(&mut idx_file, &chunk).await?;
74        }
75        TokioAsyncWriteExt::flush(&mut idx_file).await?;
76        Ok::<(), GitError>(())
77    });
78
79    // Index generation is deferred until pack offsets and the final pack checksum are known.
80    pack_encoder.encode_idx_file().await?;
81
82    let idx_write_result = idx_writer
83        .await
84        .map_err(|e| GitError::PackEncodeError(format!("idx writer task join error: {e}")))?;
85    idx_write_result?;
86
87    Ok(())
88}