Skip to main content

gitoxide_core/pack/
index.rs

1use std::{fs, io, path::PathBuf, str::FromStr, sync::atomic::AtomicBool};
2
3use gix::{NestedProgress, odb::pack};
4
5use crate::OutputFormat;
6
7#[derive(Default, Clone, Eq, PartialEq, Debug)]
8pub enum IterationMode {
9    AsIs,
10    #[default]
11    Verify,
12    Restore,
13}
14
15impl IterationMode {
16    pub fn variants() -> &'static [&'static str] {
17        &["as-is", "verify", "restore"]
18    }
19}
20
21impl FromStr for IterationMode {
22    type Err = String;
23
24    fn from_str(s: &str) -> Result<Self, Self::Err> {
25        use IterationMode::*;
26        let slc = s.to_ascii_lowercase();
27        Ok(match slc.as_str() {
28            "as-is" => AsIs,
29            "verify" => Verify,
30            "restore" => Restore,
31            _ => return Err("invalid value".into()),
32        })
33    }
34}
35
36impl From<IterationMode> for pack::data::input::Mode {
37    fn from(v: IterationMode) -> Self {
38        use pack::data::input::Mode::*;
39        match v {
40            IterationMode::AsIs => AsIs,
41            IterationMode::Verify => Verify,
42            IterationMode::Restore => Restore,
43        }
44    }
45}
46
47pub struct Context<'a, W: io::Write> {
48    pub thread_limit: Option<usize>,
49    pub iteration_mode: IterationMode,
50    pub format: OutputFormat,
51    pub should_interrupt: &'a AtomicBool,
52    pub out: W,
53    pub object_hash: gix::hash::Kind,
54}
55
56pub fn stream_len(mut s: impl io::Seek) -> io::Result<u64> {
57    use io::SeekFrom;
58    let old_pos = s.stream_position()?;
59    let len = s.seek(SeekFrom::End(0))?;
60    if old_pos != len {
61        s.seek(SeekFrom::Start(old_pos))?;
62    }
63    Ok(len)
64}
65
66pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 2..=3;
67
68pub enum PathOrRead {
69    Path(PathBuf),
70    Read(Box<dyn std::io::Read + Send + 'static>),
71}
72
73pub fn from_pack(
74    pack: PathOrRead,
75    directory: Option<PathBuf>,
76    mut progress: impl NestedProgress + 'static,
77    ctx: Context<'static, impl io::Write>,
78) -> anyhow::Result<()> {
79    use anyhow::Context;
80    let options = pack::bundle::write::Options {
81        thread_limit: ctx.thread_limit,
82        iteration_mode: ctx.iteration_mode.into(),
83        index_version: pack::index::Version::default(),
84        object_hash: ctx.object_hash,
85        alloc_limit_bytes: None,
86        compression: gix::zlib::Compression::BEST_SPEED,
87    };
88    let out = ctx.out;
89    let format = ctx.format;
90    let res = match pack {
91        PathOrRead::Path(pack) => {
92            let pack_len = pack.metadata()?.len();
93            let pack_file = fs::File::open(pack)?;
94            pack::Bundle::write_to_directory_eagerly(
95                Box::new(pack_file),
96                Some(pack_len),
97                directory,
98                &mut progress,
99                ctx.should_interrupt,
100                None::<gix::objs::find::Never>,
101                options,
102            )
103        }
104        PathOrRead::Read(input) => pack::Bundle::write_to_directory_eagerly(
105            input,
106            None,
107            directory,
108            &mut progress,
109            ctx.should_interrupt,
110            None::<gix::objs::find::Never>,
111            options,
112        ),
113    }
114    .with_context(|| "Failed to write pack and index")?;
115    match format {
116        OutputFormat::Human => drop(human_output(out, res)),
117        #[cfg(feature = "serde")]
118        OutputFormat::Json => serde_json::to_writer_pretty(out, &res)?,
119    }
120    Ok(())
121}
122
123fn human_output(mut out: impl io::Write, res: pack::bundle::write::Outcome) -> io::Result<()> {
124    writeln!(&mut out, "index: {}", res.index.index_hash)?;
125    writeln!(&mut out, "pack: {}", res.index.data_hash)
126}