mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-01 11:49:01 +00:00
git: add .gitmodules parser
This only parses the fields relevant to us, i.e.: - name: the stable identifier of the submodule - path: the path to the submodule in the current commit - url: the remote we can clone the submodule from The full list of .gitmodules fields can be found at https://git-scm.com/docs/gitmodules.
This commit is contained in:
parent
fee7eb5813
commit
7afaa2487b
2 changed files with 136 additions and 1 deletions
|
@ -14,10 +14,12 @@
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use git2::Oid;
|
use git2::Oid;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::backend::{CommitId, ObjectId};
|
use crate::backend::{CommitId, ObjectId};
|
||||||
|
@ -777,3 +779,83 @@ pub struct Progress {
|
||||||
pub bytes_downloaded: Option<u64>,
|
pub bytes_downloaded: Option<u64>,
|
||||||
pub overall: f32,
|
pub overall: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PartialSubmoduleConfig {
|
||||||
|
path: Option<String>,
|
||||||
|
url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents configuration from a submodule, e.g. in .gitmodules
|
||||||
|
/// This doesn't include all possible fields, only the ones we care about
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct SubmoduleConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum GitConfigParseError {
|
||||||
|
#[error("Unexpected io error when parsing config: {0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error("Unexpected git error when parsing config: {0}")]
|
||||||
|
InternalGitError(#[from] git2::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_gitmodules(
|
||||||
|
config: &mut dyn Read,
|
||||||
|
) -> Result<BTreeMap<String, SubmoduleConfig>, GitConfigParseError> {
|
||||||
|
// git2 can only read from a path, so set one up
|
||||||
|
let mut temp_file = NamedTempFile::new()?;
|
||||||
|
std::io::copy(config, &mut temp_file)?;
|
||||||
|
let path = temp_file.into_temp_path();
|
||||||
|
let git_config = git2::Config::open(&path)?;
|
||||||
|
// Partial config value for each submodule name
|
||||||
|
let mut partial_configs: BTreeMap<String, PartialSubmoduleConfig> = BTreeMap::new();
|
||||||
|
|
||||||
|
let entries = git_config.entries(Some(r#"submodule\..+\."#))?;
|
||||||
|
entries.for_each(|entry| {
|
||||||
|
let (config_name, config_value) = match (entry.name(), entry.value()) {
|
||||||
|
// Reject non-utf8 entries
|
||||||
|
(Some(name), Some(value)) => (name, value),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// config_name is of the form submodule.<name>.<variable>
|
||||||
|
let (submod_name, submod_var) = config_name
|
||||||
|
.strip_prefix("submodule.")
|
||||||
|
.unwrap()
|
||||||
|
.split_once('.')
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let map_entry = partial_configs.entry(submod_name.to_string()).or_default();
|
||||||
|
|
||||||
|
match (submod_var.to_ascii_lowercase().as_str(), &map_entry) {
|
||||||
|
// TODO Git warns when a duplicate config entry is found, we should
|
||||||
|
// consider doing the same.
|
||||||
|
("path", PartialSubmoduleConfig { path: None, .. }) => {
|
||||||
|
map_entry.path = Some(config_value.to_string())
|
||||||
|
}
|
||||||
|
("url", PartialSubmoduleConfig { url: None, .. }) => {
|
||||||
|
map_entry.url = Some(config_value.to_string())
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let ret = partial_configs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, val)| {
|
||||||
|
Some((
|
||||||
|
name.clone(),
|
||||||
|
SubmoduleConfig {
|
||||||
|
name,
|
||||||
|
path: val.path?,
|
||||||
|
url: val.url?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ use jujutsu_lib::backend::{
|
||||||
use jujutsu_lib::commit::Commit;
|
use jujutsu_lib::commit::Commit;
|
||||||
use jujutsu_lib::commit_builder::CommitBuilder;
|
use jujutsu_lib::commit_builder::CommitBuilder;
|
||||||
use jujutsu_lib::git;
|
use jujutsu_lib::git;
|
||||||
use jujutsu_lib::git::{GitFetchError, GitPushError, GitRefUpdate};
|
use jujutsu_lib::git::{GitFetchError, GitPushError, GitRefUpdate, SubmoduleConfig};
|
||||||
use jujutsu_lib::git_backend::GitBackend;
|
use jujutsu_lib::git_backend::GitBackend;
|
||||||
use jujutsu_lib::op_store::{BranchTarget, RefTarget};
|
use jujutsu_lib::op_store::{BranchTarget, RefTarget};
|
||||||
use jujutsu_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
|
use jujutsu_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
|
||||||
|
@ -2184,3 +2184,56 @@ fn create_rooted_commit<'repo>(
|
||||||
.set_author(signature.clone())
|
.set_author(signature.clone())
|
||||||
.set_committer(signature)
|
.set_committer(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_gitmodules() {
|
||||||
|
let result = git::parse_gitmodules(
|
||||||
|
&mut r#"
|
||||||
|
[submodule "wellformed"]
|
||||||
|
url = https://github.com/martinvonz/jj
|
||||||
|
path = mod
|
||||||
|
update = checkout # Extraneous config
|
||||||
|
|
||||||
|
[submodule "uppercase"]
|
||||||
|
URL = https://github.com/martinvonz/jj
|
||||||
|
PATH = mod2
|
||||||
|
|
||||||
|
[submodule "repeated_keys"]
|
||||||
|
url = https://github.com/martinvonz/jj
|
||||||
|
path = mod3
|
||||||
|
url = https://github.com/chooglen/jj
|
||||||
|
path = mod4
|
||||||
|
|
||||||
|
# The following entries aren't expected in a well-formed .gitmodules
|
||||||
|
[submodule "missing_url"]
|
||||||
|
path = mod
|
||||||
|
|
||||||
|
[submodule]
|
||||||
|
ignoreThisSection = foo
|
||||||
|
|
||||||
|
[randomConfig]
|
||||||
|
ignoreThisSection = foo
|
||||||
|
"#
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let expected = btreemap! {
|
||||||
|
"wellformed".to_string() => SubmoduleConfig {
|
||||||
|
name: "wellformed".to_string(),
|
||||||
|
url: "https://github.com/martinvonz/jj".to_string(),
|
||||||
|
path: "mod".to_string(),
|
||||||
|
},
|
||||||
|
"uppercase".to_string() => SubmoduleConfig {
|
||||||
|
name: "uppercase".to_string(),
|
||||||
|
url: "https://github.com/martinvonz/jj".to_string(),
|
||||||
|
path: "mod2".to_string(),
|
||||||
|
},
|
||||||
|
"repeated_keys".to_string() => SubmoduleConfig {
|
||||||
|
name: "repeated_keys".to_string(),
|
||||||
|
url: "https://github.com/martinvonz/jj".to_string(),
|
||||||
|
path: "mod3".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue