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