diff --git a/eden/mononoke/derived_data/skeleton_manifest/derive.rs b/eden/mononoke/derived_data/skeleton_manifest/derive.rs index ee67cd2da2..c081735306 100644 --- a/eden/mononoke/derived_data/skeleton_manifest/derive.rs +++ b/eden/mononoke/derived_data/skeleton_manifest/derive.rs @@ -406,6 +406,12 @@ mod test { ..Default::default() } ); + assert_eq!( + b_skeleton + .first_case_conflict(&ctx, repo.blobstore()) + .await?, + None, + ); // Changeset C introduces some case conflicts let c_bcs = changesets["C"].load(ctx.clone(), repo.blobstore()).await?; @@ -426,6 +432,15 @@ mod test { ..Default::default() } ); + assert_eq!( + c_skeleton + .first_case_conflict(&ctx, repo.blobstore()) + .await?, + Some(( + MPath::new(b"dir1/subdir1/SUBSUBDIR2")?, + MPath::new(b"dir1/subdir1/subsubdir2")? + )) + ); let c_sk_dir1 = skeleton_dir(&c_skeleton, b"dir1")? .id() @@ -465,6 +480,15 @@ mod test { let d_skeleton = d_skeleton_id.load(ctx.clone(), repo.blobstore()).await?; assert_eq!(d_skeleton.summary().child_case_conflicts, false); assert_eq!(d_skeleton.summary().descendant_case_conflicts, true); + assert_eq!( + d_skeleton + .first_case_conflict(&ctx, repo.blobstore()) + .await?, + Some(( + MPath::new(b"dir1/subdir1/subsubdir1/FILE1")?, + MPath::new(b"dir1/subdir1/subsubdir1/file1")? + )) + ); let d_sk_dir1 = skeleton_dir(&d_skeleton, b"dir1")? .id() diff --git a/eden/mononoke/mononoke_types/src/skeleton_manifest.rs b/eden/mononoke/mononoke_types/src/skeleton_manifest.rs index b8b62ee7fd..96df52e3f3 100644 --- a/eden/mononoke/mononoke_types/src/skeleton_manifest.rs +++ b/eden/mononoke/mononoke_types/src/skeleton_manifest.rs @@ -9,13 +9,16 @@ use anyhow::{Context, Result}; use crate::blob::{Blob, BlobstoreValue, SkeletonManifestBlob}; use crate::errors::ErrorKind; -use crate::path::MPathElement; +use crate::path::{MPath, MPathElement}; use crate::thrift; use crate::typed_hash::{SkeletonManifestId, SkeletonManifestIdContext}; +use blobstore::{Blobstore, StoreLoadable}; +use context::CoreContext; use fbthrift::compact_protocol; use sorted_vector_map::SortedVectorMap; -use std::collections::BTreeMap; +use std::borrow::Cow; +use std::collections::{BTreeMap, HashMap}; /// A skeleton manifest is a manifest node containing summary information about the /// the structure of files (their names, but not their contents) that is useful @@ -83,6 +86,44 @@ impl SkeletonManifest { &self.summary } + pub async fn first_case_conflict( + &self, + ctx: &CoreContext, + blobstore: &(impl Blobstore + Clone), + ) -> Result> { + let mut sk_mf = Cow::Borrowed(self); + let mut path: Option = None; + 'outer: loop { + if sk_mf.summary.child_case_conflicts { + let mut lower_map = HashMap::new(); + for name in sk_mf.subentries.keys() { + if let Some(lower_name) = name.to_lowercase_utf8() { + if let Some(other_name) = lower_map.insert(lower_name, name.clone()) { + return Ok(Some(( + MPath::join_opt_element(path.as_ref(), &other_name), + MPath::join_opt_element(path.as_ref(), name), + ))); + } + } + } + } + if sk_mf.summary.descendant_case_conflicts { + for (name, entry) in sk_mf.subentries.iter() { + if let SkeletonManifestEntry::Directory(subdir) = entry { + if subdir.summary.child_case_conflicts + || subdir.summary.descendant_case_conflicts + { + path = Some(MPath::join_opt_element(path.as_ref(), name)); + sk_mf = Cow::Owned(subdir.id.load(ctx.clone(), blobstore).await?); + continue 'outer; + } + } + } + } + return Ok(None); + } + } + pub(crate) fn from_thrift(t: thrift::SkeletonManifest) -> Result { let subentries = t .subentries