skeleton_manifest: add first_case_conflict method

Summary:
Add a method to `SkeletonManifest` which finds the paths to the first case
conflict, if there is one.

First means lexicographically first within directories, and with the shortest path.

Reviewed By: ahornby, StanislavGlebik

Differential Revision: D24990175

fbshipit-source-id: ec10f66582b81c40740823e32362ca489a6ebb4d
This commit is contained in:
Mark Juggurnauth-Thomas 2020-11-18 02:08:36 -08:00 committed by Facebook GitHub Bot
parent aea605a82d
commit eddc285862
2 changed files with 67 additions and 2 deletions

View File

@ -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()

View File

@ -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<Option<(MPath, MPath)>> {
let mut sk_mf = Cow::Borrowed(self);
let mut path: Option<MPath> = 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<SkeletonManifest> {
let subentries = t
.subentries