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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//! Bindings to libgit2's git_libgit2_opts function.

use std::ffi::CString;
use std::ptr;

use crate::string_array::StringArray;
use crate::util::Binding;
use crate::{raw, Buf, ConfigLevel, Error, IntoCString};

/// Set the search path for a level of config data. The search path applied to
/// shared attributes and ignore files, too.
///
/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
///
/// `path` lists directories delimited by `GIT_PATH_LIST_SEPARATOR`.
/// Use magic path `$PATH` to include the old value of the path
/// (if you want to prepend or append, for instance).
///
/// This function is unsafe as it mutates the global state but cannot guarantee
/// thread-safety. It needs to be externally synchronized with calls to access
/// the global state.
pub unsafe fn set_search_path<P>(level: ConfigLevel, path: P) -> Result<(), Error>
where
    P: IntoCString,
{
    crate::init();
    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int,
        level as libc::c_int,
        path.into_c_string()?.as_ptr()
    ));
    Ok(())
}

/// Reset the search path for a given level of config data to the default
/// (generally based on environment variables).
///
/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
///
/// This function is unsafe as it mutates the global state but cannot guarantee
/// thread-safety. It needs to be externally synchronized with calls to access
/// the global state.
pub unsafe fn reset_search_path(level: ConfigLevel) -> Result<(), Error> {
    crate::init();
    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int,
        level as libc::c_int,
        core::ptr::null::<u8>()
    ));
    Ok(())
}

/// Get the search path for a given level of config data.
///
/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
///
/// This function is unsafe as it mutates the global state but cannot guarantee
/// thread-safety. It needs to be externally synchronized with calls to access
/// the global state.
pub unsafe fn get_search_path(level: ConfigLevel) -> Result<CString, Error> {
    crate::init();
    let buf = Buf::new();
    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_GET_SEARCH_PATH as libc::c_int,
        level as libc::c_int,
        buf.raw() as *const _
    ));
    buf.into_c_string()
}

/// Controls whether or not libgit2 will cache loaded objects.  Enabled by
/// default, but disabling this can improve performance and memory usage if
/// loading a large number of objects that will not be referenced again.
/// Disabling this will cause repository objects to clear their caches when next
/// accessed.
pub fn enable_caching(enabled: bool) {
    crate::init();
    let error = unsafe {
        raw::git_libgit2_opts(
            raw::GIT_OPT_ENABLE_CACHING as libc::c_int,
            enabled as libc::c_int,
        )
    };
    // This function cannot actually fail, but the function has an error return
    // for other options that can.
    debug_assert!(error >= 0);
}

/// Controls whether or not libgit2 will verify when writing an object that all
/// objects it references are valid. Enabled by default, but disabling this can
/// significantly improve performance, at the cost of potentially allowing the
/// creation of objects that reference invalid objects (due to programming
/// error or repository corruption).
pub fn strict_object_creation(enabled: bool) {
    crate::init();
    let error = unsafe {
        raw::git_libgit2_opts(
            raw::GIT_OPT_ENABLE_STRICT_OBJECT_CREATION as libc::c_int,
            enabled as libc::c_int,
        )
    };
    // This function cannot actually fail, but the function has an error return
    // for other options that can.
    debug_assert!(error >= 0);
}

/// Controls whether or not libgit2 will verify that objects loaded have the
/// expected hash. Enabled by default, but disabling this can significantly
/// improve performance, at the cost of relying on repository integrity
/// without checking it.
pub fn strict_hash_verification(enabled: bool) {
    crate::init();
    let error = unsafe {
        raw::git_libgit2_opts(
            raw::GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION as libc::c_int,
            enabled as libc::c_int,
        )
    };
    // This function cannot actually fail, but the function has an error return
    // for other options that can.
    debug_assert!(error >= 0);
}

/// Returns the list of git extensions that are supported. This is the list of
/// built-in extensions supported by libgit2 and custom extensions that have
/// been added with [`set_extensions`]. Extensions that have been negated will
/// not be returned.
///
/// # Safety
///
/// libgit2 stores user extensions in a static variable.
/// This function is effectively reading a `static mut` and should be treated as such
pub unsafe fn get_extensions() -> Result<StringArray, Error> {
    crate::init();

    let mut extensions = raw::git_strarray {
        strings: ptr::null_mut(),
        count: 0,
    };

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_GET_EXTENSIONS as libc::c_int,
        &mut extensions
    ));

    Ok(StringArray::from_raw(extensions))
}

/// Set that the given git extensions are supported by the caller. Extensions
/// supported by libgit2 may be negated by prefixing them with a `!`.
/// For example: setting extensions to `[ "!noop", "newext" ]` indicates that
/// the caller does not want to support repositories with the `noop` extension
/// but does want to support repositories with the `newext` extension.
///
/// # Safety
///
/// libgit2 stores user extensions in a static variable.
/// This function is effectively modifying a `static mut` and should be treated as such
pub unsafe fn set_extensions<E>(extensions: &[E]) -> Result<(), Error>
where
    for<'x> &'x E: IntoCString,
{
    crate::init();

    let extensions = extensions
        .iter()
        .map(|e| e.into_c_string())
        .collect::<Result<Vec<_>, _>>()?;

    let extension_ptrs = extensions.iter().map(|e| e.as_ptr()).collect::<Vec<_>>();

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_EXTENSIONS as libc::c_int,
        extension_ptrs.as_ptr(),
        extension_ptrs.len() as libc::size_t
    ));

    Ok(())
}

/// Set whether or not to verify ownership before performing a repository.
/// Enabled by default, but disabling this can lead to code execution vulnerabilities.
pub unsafe fn set_verify_owner_validation(enabled: bool) -> Result<(), Error> {
    crate::init();
    let error = raw::git_libgit2_opts(
        raw::GIT_OPT_SET_OWNER_VALIDATION as libc::c_int,
        enabled as libc::c_int,
    );
    // This function cannot actually fail, but the function has an error return
    // for other options that can.
    debug_assert!(error >= 0);
    Ok(())
}

/// Set the SSL certificate-authority location to `file`. `file` is the location
/// of a file containing several certificates concatenated together.
pub unsafe fn set_ssl_cert_file<P>(file: P) -> Result<(), Error>
where
    P: IntoCString,
{
    crate::init();

    unsafe {
        try_call!(raw::git_libgit2_opts(
            raw::GIT_OPT_SET_SSL_CERT_LOCATIONS as libc::c_int,
            file.into_c_string()?.as_ptr(),
            core::ptr::null::<libc::c_char>()
        ));
    }

    Ok(())
}

/// Set the SSL certificate-authority location to `path`. `path` is the location
/// of a directory holding several certificates, one per file.
pub unsafe fn set_ssl_cert_dir<P>(path: P) -> Result<(), Error>
where
    P: IntoCString,
{
    crate::init();

    unsafe {
        try_call!(raw::git_libgit2_opts(
            raw::GIT_OPT_SET_SSL_CERT_LOCATIONS as libc::c_int,
            core::ptr::null::<libc::c_char>(),
            path.into_c_string()?.as_ptr()
        ));
    }

    Ok(())
}

/// Get the maximum mmap window size
///
/// # Safety
/// This function is reading a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn get_mwindow_size() -> Result<libc::size_t, Error> {
    crate::init();

    let mut size = 0;

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_GET_MWINDOW_SIZE as libc::c_int,
        &mut size
    ));

    Ok(size)
}

/// Set the maximum mmap window size
///
/// # Safety
/// This function is modifying a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn set_mwindow_size(size: libc::size_t) -> Result<(), Error> {
    crate::init();

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_MWINDOW_SIZE as libc::c_int,
        size
    ));

    Ok(())
}

/// Get the maximum memory that will be mapped in total by the library
///
/// # Safety
/// This function is reading a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn get_mwindow_mapped_limit() -> Result<libc::size_t, Error> {
    crate::init();

    let mut limit = 0;

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_GET_MWINDOW_MAPPED_LIMIT as libc::c_int,
        &mut limit
    ));

    Ok(limit)
}

/// Set the maximum amount of memory that can be mapped at any time
/// by the library.
///
/// # Safety
/// This function is modifying a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn set_mwindow_mapped_limit(limit: libc::size_t) -> Result<(), Error> {
    crate::init();

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_MWINDOW_MAPPED_LIMIT as libc::c_int,
        limit
    ));

    Ok(())
}

/// Get the maximum number of files that will be mapped at any time by the
/// library.
///
/// # Safety
/// This function is reading a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn get_mwindow_file_limit() -> Result<libc::size_t, Error> {
    crate::init();

    let mut limit = 0;

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_GET_MWINDOW_FILE_LIMIT as libc::c_int,
        &mut limit
    ));

    Ok(limit)
}

/// Set the maximum number of files that can be mapped at any time
/// by the library. The default (0) is unlimited.
///
/// # Safety
/// This function is modifying a C global without synchronization, so it is not
/// thread safe, and should only be called before any thread is spawned.
pub unsafe fn set_mwindow_file_limit(limit: libc::size_t) -> Result<(), Error> {
    crate::init();

    try_call!(raw::git_libgit2_opts(
        raw::GIT_OPT_SET_MWINDOW_FILE_LIMIT as libc::c_int,
        limit
    ));

    Ok(())
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn smoke() {
        strict_hash_verification(false);
    }

    #[test]
    fn mwindow_size() {
        unsafe {
            assert!(set_mwindow_size(1024).is_ok());
            assert!(get_mwindow_size().unwrap() == 1024);
        }
    }

    #[test]
    fn mwindow_mapped_limit() {
        unsafe {
            assert!(set_mwindow_mapped_limit(1024).is_ok());
            assert!(get_mwindow_mapped_limit().unwrap() == 1024);
        }
    }

    #[test]
    fn mwindow_file_limit() {
        unsafe {
            assert!(set_mwindow_file_limit(1024).is_ok());
            assert!(get_mwindow_file_limit().unwrap() == 1024);
        }
    }
}