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
134
use std::convert::TryFrom;

use super::Error;
use crate::{bstr::ByteSlice, revision::spec::parse::ObjectKindHint};

pub(crate) fn interpolate_context<'a>(
    git_install_dir: Option<&'a std::path::Path>,
    home_dir: Option<&'a std::path::Path>,
) -> git_config::path::interpolate::Context<'a> {
    git_config::path::interpolate::Context {
        git_install_dir,
        home_dir,
        home_for_user: Some(git_config::path::interpolate::home_for_user), // TODO: figure out how to configure this
    }
}

pub(crate) fn base_options(lossy: Option<bool>) -> git_config::file::init::Options<'static> {
    git_config::file::init::Options {
        lossy: lossy.unwrap_or(!cfg!(debug_assertions)),
        ..Default::default()
    }
}

pub(crate) fn config_bool(
    config: &git_config::File<'_>,
    key: &str,
    default: bool,
    lenient: bool,
) -> Result<bool, Error> {
    let (section, key) = key.split_once('.').expect("valid section.key format");
    match config
        .boolean(section, None, key)
        .unwrap_or(Ok(default))
        .map_err(|err| Error::DecodeBoolean {
            value: err.input,
            key: key.into(),
        }) {
        Ok(v) => Ok(v),
        Err(_err) if lenient => Ok(default),
        Err(err) => Err(err),
    }
}

pub(crate) fn query_refupdates(
    config: &git_config::File<'static>,
    lenient_config: bool,
) -> Result<Option<git_ref::store::WriteReflog>, Error> {
    match config
        .boolean("core", None, "logAllRefUpdates")
        .and_then(|b| b.ok())
        .map(|b| {
            b.then(|| git_ref::store::WriteReflog::Normal)
                .unwrap_or(git_ref::store::WriteReflog::Disable)
        }) {
        Some(val) => Ok(Some(val)),
        None => match config.string("core", None, "logAllRefUpdates") {
            Some(val) if val.eq_ignore_ascii_case(b"always") => Ok(Some(git_ref::store::WriteReflog::Always)),
            Some(_val) if lenient_config => Ok(None),
            Some(val) => Err(Error::LogAllRefUpdates {
                value: val.into_owned(),
            }),
            None => Ok(None),
        },
    }
}

pub(crate) fn check_lenient<T, E>(v: Result<Option<T>, E>, lenient: bool) -> Result<Option<T>, E> {
    match v {
        Ok(v) => Ok(v),
        Err(_) if lenient => Ok(None),
        Err(err) => Err(err),
    }
}

pub(crate) fn check_lenient_default<T, E>(v: Result<T, E>, lenient: bool, default: impl FnOnce() -> T) -> Result<T, E> {
    match v {
        Ok(v) => Ok(v),
        Err(_) if lenient => Ok(default()),
        Err(err) => Err(err),
    }
}

pub(crate) fn parse_core_abbrev(
    config: &git_config::File<'static>,
    object_hash: git_hash::Kind,
) -> Result<Option<usize>, Error> {
    match config.string("core", None, "abbrev") {
        Some(hex_len_str) => {
            if hex_len_str.trim().is_empty() {
                return Err(Error::EmptyValue { key: "core.abbrev" });
            }
            if hex_len_str.trim().eq_ignore_ascii_case(b"auto") {
                Ok(None)
            } else {
                let value_bytes = hex_len_str.as_ref();
                if let Ok(false) = git_config::Boolean::try_from(value_bytes).map(Into::into) {
                    Ok(object_hash.len_in_hex().into())
                } else {
                    let value = git_config::Integer::try_from(value_bytes)
                        .map_err(|_| Error::CoreAbbrev {
                            value: hex_len_str.clone().into_owned(),
                            max: object_hash.len_in_hex() as u8,
                        })?
                        .to_decimal()
                        .ok_or_else(|| Error::CoreAbbrev {
                            value: hex_len_str.clone().into_owned(),
                            max: object_hash.len_in_hex() as u8,
                        })?;
                    if value < 4 || value as usize > object_hash.len_in_hex() {
                        return Err(Error::CoreAbbrev {
                            value: hex_len_str.clone().into_owned(),
                            max: object_hash.len_in_hex() as u8,
                        });
                    }
                    Ok(Some(value as usize))
                }
            }
        }
        None => Ok(None),
    }
}

pub(crate) fn disambiguate_hint(config: &git_config::File<'static>) -> Option<ObjectKindHint> {
    config.string("core", None, "disambiguate").and_then(|value| {
        Some(match value.as_ref().as_ref() {
            b"commit" => ObjectKindHint::Commit,
            b"committish" => ObjectKindHint::Committish,
            b"tree" => ObjectKindHint::Tree,
            b"treeish" => ObjectKindHint::Treeish,
            b"blob" => ObjectKindHint::Blob,
            _ => return None,
        })
    })
}