gix_submodule/lib.rs
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
//! Primitives for describing git submodules.
#![deny(rust_2018_idioms, missing_docs)]
#![forbid(unsafe_code)]
use std::{borrow::Cow, collections::BTreeMap};
use bstr::BStr;
/// All relevant information about a git module, typically from `.gitmodules` files.
///
/// Note that overrides from other configuration might be relevant, which is why this type
/// can be used to take these into consideration when presented with other configuration
/// from the superproject.
#[derive(Clone)]
pub struct File {
config: gix_config::File<'static>,
}
mod access;
///
pub mod config;
///
pub mod is_active_platform;
/// A platform to keep the state necessary to perform repeated active checks, created by [File::is_active_platform()].
pub struct IsActivePlatform {
pub(crate) search: Option<gix_pathspec::Search>,
}
/// Mutation
impl File {
/// This can be used to let `config` override some values we know about submodules, namely…
///
/// * `url`
/// * `fetchRecurseSubmodules`
/// * `ignore`
/// * `update`
/// * `branch`
///
/// These values aren't validated yet, which will happen upon query.
pub fn append_submodule_overrides(&mut self, config: &gix_config::File<'_>) -> &mut Self {
let mut values = BTreeMap::<_, Vec<_>>::new();
for (module_name, section) in config
.sections_by_name("submodule")
.into_iter()
.flatten()
.filter_map(|s| s.header().subsection_name().map(|n| (n, s)))
{
for field in ["url", "fetchRecurseSubmodules", "ignore", "update", "branch"] {
if let Some(value) = section.value(field) {
values.entry((module_name, field)).or_default().push(value);
}
}
}
let values = {
let mut v: Vec<_> = values.into_iter().collect();
v.sort_by_key(|a| a.0 .0);
v
};
let mut config_to_append = gix_config::File::new(config.meta_owned());
let mut prev_name = None;
for ((module_name, field), values) in values {
if prev_name.map_or(true, |pn: &BStr| pn != module_name) {
config_to_append
.new_section("submodule", Some(Cow::Owned(module_name.to_owned())))
.expect("all names come from valid configuration, so remain valid");
prev_name = Some(module_name);
}
config_to_append
.section_mut("submodule", Some(module_name))
.expect("always set at this point")
.push(
field.try_into().expect("statically known key"),
Some(values.last().expect("at least one value or we wouldn't be here")),
);
}
self.config.append(config_to_append);
self
}
}
///
mod init {
use std::path::PathBuf;
use crate::File;
impl std::fmt::Debug for File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("File")
.field("config_path", &self.config_path())
.field("config", &format_args!("r#\"{}\"#", self.config))
.finish()
}
}
/// A marker we use when listing names to not pick them up from overridden sections.
pub(crate) const META_MARKER: gix_config::Source = gix_config::Source::Api;
/// Lifecycle
impl File {
/// Parse `bytes` as git configuration, typically from `.gitmodules`, without doing any further validation.
/// `path` can be provided to keep track of where the file was read from in the underlying [`config`](Self::config())
/// instance.
/// `config` is used to [apply value overrides](File::append_submodule_overrides), which can be empty if overrides
/// should be applied at a later time.
///
/// Future access to the module information is lazy and configuration errors are exposed there on a per-value basis.
///
/// ### Security Considerations
///
/// The information itself should be used with care as it can direct the caller to fetch from remotes. It is, however,
/// on the caller to assure the input data can be trusted.
pub fn from_bytes(
bytes: &[u8],
path: impl Into<Option<PathBuf>>,
config: &gix_config::File<'_>,
) -> Result<Self, gix_config::parse::Error> {
let metadata = {
let mut meta = gix_config::file::Metadata::from(META_MARKER);
meta.path = path.into();
meta
};
let modules = gix_config::File::from_parse_events_no_includes(
gix_config::parse::Events::from_bytes_owned(bytes, None)?,
metadata,
);
let mut res = Self { config: modules };
res.append_submodule_overrides(config);
Ok(res)
}
/// Turn ourselves into the underlying parsed configuration file.
pub fn into_config(self) -> gix_config::File<'static> {
self.config
}
}
}