1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::{fs, io, path::PathBuf, str::FromStr, sync::atomic::AtomicBool};

use git_repository::{odb::pack, Progress};

use crate::OutputFormat;

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum IterationMode {
    AsIs,
    Verify,
    Restore,
}

impl IterationMode {
    pub fn variants() -> &'static [&'static str] {
        &["as-is", "verify", "restore"]
    }
}

impl Default for IterationMode {
    fn default() -> Self {
        IterationMode::Verify
    }
}

impl FromStr for IterationMode {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use IterationMode::*;
        let slc = s.to_ascii_lowercase();
        Ok(match slc.as_str() {
            "as-is" => AsIs,
            "verify" => Verify,
            "restore" => Restore,
            _ => return Err("invalid value".into()),
        })
    }
}

impl From<IterationMode> for pack::data::input::Mode {
    fn from(v: IterationMode) -> Self {
        use pack::data::input::Mode::*;
        match v {
            IterationMode::AsIs => AsIs,
            IterationMode::Verify => Verify,
            IterationMode::Restore => Restore,
        }
    }
}

pub struct Context<'a, W: io::Write> {
    pub thread_limit: Option<usize>,
    pub iteration_mode: IterationMode,
    pub format: OutputFormat,
    pub should_interrupt: &'a AtomicBool,
    pub out: W,
    pub object_hash: git_repository::hash::Kind,
}

pub fn stream_len(mut s: impl io::Seek) -> io::Result<u64> {
    use io::SeekFrom;
    let old_pos = s.seek(SeekFrom::Current(0))?;
    let len = s.seek(SeekFrom::End(0))?;
    if old_pos != len {
        s.seek(SeekFrom::Start(old_pos))?;
    }
    Ok(len)
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 2..=3;

pub enum PathOrRead {
    Path(PathBuf),
    Read(Box<dyn std::io::Read + Send + 'static>),
}

pub fn from_pack<P>(
    pack: PathOrRead,
    directory: Option<PathBuf>,
    progress: P,
    ctx: Context<'static, impl io::Write>,
) -> anyhow::Result<()>
where
    P: Progress,
    P::SubProgress: 'static,
{
    use anyhow::Context;
    let options = pack::bundle::write::Options {
        thread_limit: ctx.thread_limit,
        iteration_mode: ctx.iteration_mode.into(),
        index_version: pack::index::Version::default(),
        object_hash: ctx.object_hash,
    };
    let out = ctx.out;
    let format = ctx.format;
    let res = match pack {
        PathOrRead::Path(pack) => {
            let pack_len = pack.metadata()?.len();
            let pack_file = fs::File::open(pack)?;
            pack::Bundle::write_to_directory_eagerly(
                pack_file,
                Some(pack_len),
                directory,
                progress,
                ctx.should_interrupt,
                None,
                options,
            )
        }
        PathOrRead::Read(input) => pack::Bundle::write_to_directory_eagerly(
            input,
            None,
            directory,
            progress,
            ctx.should_interrupt,
            None,
            options,
        ),
    }
    .with_context(|| "Failed to write pack and index")?;
    match format {
        OutputFormat::Human => drop(human_output(out, res)),
        #[cfg(feature = "serde1")]
        OutputFormat::Json => serde_json::to_writer_pretty(out, &res)?,
    };
    Ok(())
}

fn human_output(mut out: impl io::Write, res: pack::bundle::write::Outcome) -> io::Result<()> {
    writeln!(&mut out, "index: {}", res.index.index_hash)?;
    writeln!(&mut out, "pack: {}", res.index.data_hash)
}