gitoxide_core/pack/
index.rs

1use std::{fs, io, path::PathBuf, str::FromStr, sync::atomic::AtomicBool};
2
3use gix::{odb::pack, NestedProgress};
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    };
86    let out = ctx.out;
87    let format = ctx.format;
88    let res = match pack {
89        PathOrRead::Path(pack) => {
90            let pack_len = pack.metadata()?.len();
91            let pack_file = fs::File::open(pack)?;
92            pack::Bundle::write_to_directory_eagerly(
93                Box::new(pack_file),
94                Some(pack_len),
95                directory,
96                &mut progress,
97                ctx.should_interrupt,
98                None::<gix::objs::find::Never>,
99                options,
100            )
101        }
102        PathOrRead::Read(input) => pack::Bundle::write_to_directory_eagerly(
103            input,
104            None,
105            directory,
106            &mut progress,
107            ctx.should_interrupt,
108            None::<gix::objs::find::Never>,
109            options,
110        ),
111    }
112    .with_context(|| "Failed to write pack and index")?;
113    match format {
114        OutputFormat::Human => drop(human_output(out, res)),
115        #[cfg(feature = "serde")]
116        OutputFormat::Json => serde_json::to_writer_pretty(out, &res)?,
117    }
118    Ok(())
119}
120
121fn human_output(mut out: impl io::Write, res: pack::bundle::write::Outcome) -> io::Result<()> {
122    writeln!(&mut out, "index: {}", res.index.index_hash)?;
123    writeln!(&mut out, "pack: {}", res.index.data_hash)
124}