Skip to content

Commit

Permalink
sparse: add convenience method for creating standalone profile
Browse files Browse the repository at this point in the history
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
  • Loading branch information
muirdm authored and facebook-github-bot committed Dec 12, 2023
1 parent a3dc461 commit b0d38df
Showing 1 changed file with 91 additions and 43 deletions.
134 changes: 91 additions & 43 deletions eden/scm/lib/sparse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<String>),
Profile(String),
ProfileName(String),
Profile(Profile),
}

#[derive(PartialEq)]
Expand Down Expand Up @@ -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<Self, io::Error> {
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<String>) {
self.version_override = version_override;
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand All @@ -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?;
}
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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());
}
}

0 comments on commit b0d38df

Please sign in to comment.