gitoxide_core/pack/
index.rs1use 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}