From b0d38dfd07bdebe7ed962d9e190ef6e0629ce201 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Mon, 11 Dec 2023 16:28:29 -0800 Subject: [PATCH] sparse: add convenience method for creating standalone profile Summary: Eden is supporting sparse, but doesn't really have a "root" sparse config. They do want v2 semantics, but previously only top-level-profiles (i.e. direct children of the root config) could be v2. With this new "single_profile" function, you can create an adhoc top-level-profile without needing to care about the root config. Reviewed By: MichaelCuevas Differential Revision: D52052741 fbshipit-source-id: a50900af9f5752f24d4331c457c53b7429637f3b --- eden/scm/lib/sparse/src/lib.rs | 134 ++++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 43 deletions(-) diff --git a/eden/scm/lib/sparse/src/lib.rs b/eden/scm/lib/sparse/src/lib.rs index 5b823a33c506b..faab5b521c406 100644 --- a/eden/scm/lib/sparse/src/lib.rs +++ b/eden/scm/lib/sparse/src/lib.rs @@ -39,7 +39,7 @@ mod extra_use { use extra_use::*; -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct Profile { // Where this profile came from (typically a file path). source: String, @@ -70,11 +70,12 @@ pub enum Pattern { Exclude(String), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ProfileEntry { // Pattern plus additional source for this rule (e.g. "hgrc.dynamic"). Pattern(Pattern, Option), - Profile(String), + ProfileName(String), + Profile(Profile), } #[derive(PartialEq)] @@ -132,6 +133,20 @@ impl Root { }) } + /// Load a single top-level-profile as if you had a root profile that simply %include'd it. + /// This allows you to create an adhoc v2 profile without needing a root config. + pub fn single_profile(data: impl AsRef<[u8]>, source: String) -> Result { + Ok(Self { + prof: Profile { + source: "dummy root".to_string(), + entries: vec![ProfileEntry::Profile(Profile::from_bytes(data, source)?)], + ..Default::default() + }, + version_override: None, + skip_catch_all: false, + }) + } + pub fn set_version_override(&mut self, version_override: Option) { self.version_override = version_override; } @@ -181,42 +196,44 @@ impl Root { let mut only_v1 = true; for entry in self.prof.entries.iter() { - match entry { - ProfileEntry::Pattern(p, src) => push_rule(( - p.clone(), - join_source(self.prof.source.clone(), src.as_deref()), - )), - ProfileEntry::Profile(child_path) => { - let mut child = match fetch(child_path.clone()).await? { - Some(data) => Profile::from_bytes(data, child_path.clone())?, - None => continue, - }; - - if let Some(version_override) = &self.version_override { - child.version = Some(version_override.clone()); - } + let mut child = match entry { + ProfileEntry::Pattern(p, src) => { + push_rule(( + p.clone(), + join_source(self.prof.source.clone(), src.as_deref()), + )); + continue; + } + ProfileEntry::ProfileName(child_path) => match fetch(child_path.clone()).await? { + Some(data) => Profile::from_bytes(data, child_path.clone())?, + None => continue, + }, + ProfileEntry::Profile(prof) => prof.clone(), + }; - let child_rules: VecDeque<(Pattern, String)> = child - .rules(&mut fetch) - .await? - .into_iter() - .map(|(p, s)| (p, format!("{} -> {}", self.prof.source, s))) - .collect(); - - if child.is_v2() { - only_v1 = false; - - let (matcher_rules, origins) = prepare_rules(child_rules)?; - matchers.push(TreeMatcher::from_rules( - matcher_rules.iter(), - self.prof.case_sensitive, - )?); - rule_origins.push(origins); - } else { - for rule in child_rules { - push_rule(rule); - } - } + if let Some(version_override) = &self.version_override { + child.version = Some(version_override.clone()); + } + + let child_rules: VecDeque<(Pattern, String)> = child + .rules(&mut fetch) + .await? + .into_iter() + .map(|(p, s)| (p, format!("{} -> {}", self.prof.source, s))) + .collect(); + + if child.is_v2() { + only_v1 = false; + + let (matcher_rules, origins) = prepare_rules(child_rules)?; + matchers.push(TreeMatcher::from_rules( + matcher_rules.iter(), + self.prof.case_sensitive, + )?); + rule_origins.push(origins); + } else { + for rule in child_rules { + push_rule(rule); } } } @@ -308,7 +325,7 @@ impl Profile { } prof.entries - .push(ProfileEntry::Profile(p.trim().to_string())); + .push(ProfileEntry::ProfileName(p.trim().to_string())); } else if let Some(section_start) = SectionType::from_str(trimmed) { section_type = section_start; current_metadata_val = None; @@ -401,7 +418,7 @@ impl Profile { ProfileEntry::Pattern(p, psrc) => { rules.push((p.clone(), join_source(source.clone(), psrc.as_deref()))) } - ProfileEntry::Profile(child_path) => { + ProfileEntry::ProfileName(child_path) => { let entry = seen.entry(child_path.clone()); let data = match entry { Entry::Occupied(e) => match e.into_mut() { @@ -419,13 +436,16 @@ impl Profile { } }; - let mut child = Profile::from_bytes(data, child_path.clone())?; - rules_inner(&mut child, fetch, rules, Some(&source), seen).await?; + let child = Profile::from_bytes(data, child_path.clone())?; + rules_inner(&child, fetch, rules, Some(&source), seen).await?; if let Some((_, in_progress)) = seen.get_mut(child_path) { *in_progress = false; } } + ProfileEntry::Profile(child) => { + rules_inner(child, fetch, rules, Some(&source), seen).await?; + } } } @@ -625,7 +645,8 @@ mod tests { match entry { ProfileEntry::Pattern(Pattern::Include(p), _) => inc.push(p.as_ref()), ProfileEntry::Pattern(Pattern::Exclude(p), _) => exc.push(p.as_ref()), - ProfileEntry::Profile(p) => profs.push(p.as_ref()), + ProfileEntry::ProfileName(p) => profs.push(p.as_ref()), + ProfileEntry::Profile(p) => profs.push(p.source.as_ref()), } } (inc, exc, profs) @@ -1186,4 +1207,31 @@ path:foo .unwrap(); assert!(!matcher.matches("foo".try_into().unwrap()).unwrap()); } + + #[tokio::test] + async fn test_single_profile() { + let single = b" +[metadata] +version = 2 + +[exclude] +foo/bar + +[include] +foo +"; + + // Sanity check that normal loading of this profile does not get v2 semantics. + let not_single = Root::from_bytes(single, "base".to_string()).unwrap(); + let matcher = not_single + .matcher(|_| async { unreachable!() }) + .await + .unwrap(); + assert!(!matcher.matches("foo/bar".try_into().unwrap()).unwrap()); + + // Using single_profile gets v2 semantics. + let single = Root::single_profile(single, "base".to_string()).unwrap(); + let matcher = single.matcher(|_| async { unreachable!() }).await.unwrap(); + assert!(matcher.matches("foo/bar".try_into().unwrap()).unwrap()); + } }