sparse: add bindings for Rust sparse library

Summary:
Add bindings the Python sparse extension can use to create treematchers representing the sparse profile.

We return a list of treematchers and rule details instead of a single matcher because the sparse "explain" functionality only exists at the Python matcher level, so passing a single Rust matcher would lose that information.

Reviewed By: quark-zju

Differential Revision: D49928284

fbshipit-source-id: 828c931f4d6c1f17a43a506bee0e7b2227817900
This commit is contained in:
Muir Manders 2023-10-05 14:14:25 -07:00 committed by Facebook GitHub Bot
parent 82d1faa01d
commit 0078dd4c3c
5 changed files with 70 additions and 3 deletions

View File

@ -463,6 +463,10 @@ impl Matcher {
Ok((false, "no rules matched".to_string()))
}
pub fn into_matchers(self) -> Vec<(TreeMatcher, Vec<String>)> {
self.matchers.into_iter().zip(self.rule_origins).collect()
}
}
impl MatcherTrait for Matcher {

View File

@ -85,7 +85,7 @@ pub fn repo_matcher_with_overrides(
Ok(Some((matcher, hasher.finish())))
}
fn build_matcher(
pub fn build_matcher(
prof: &sparse::Root,
manifest: impl Manifest + Send + Sync + 'static,
store: Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync>,
@ -180,7 +180,7 @@ pub fn config_overrides(config: impl Config) -> HashMap<String, String> {
overrides
}
fn disk_overrides(dot_path: &Path) -> anyhow::Result<HashMap<String, String>> {
pub fn disk_overrides(dot_path: &Path) -> anyhow::Result<HashMap<String, String>> {
match util::file::open(dot_path.join(CONFIG_OVERRIDE_CACHE), "r") {
Ok(f) => Ok(serde_json::from_reader(f)?),
Err(err) if err.kind() != std::io::ErrorKind::NotFound => Err(err.into()),

View File

@ -78,6 +78,7 @@ pub struct WorkingCopy {
format: StorageFormat,
treestate: Arc<Mutex<TreeState>>,
tree_resolver: ArcReadTreeManifest,
filestore: ArcReadFileContents,
filesystem: Mutex<FileSystem>,
ignore_matcher: Arc<GitignoreMatcher>,
locker: Arc<RepoLocker>,
@ -112,7 +113,7 @@ impl WorkingCopy {
file_system_type,
treestate.clone(),
tree_resolver.clone(),
filestore,
filestore.clone(),
locker.clone(),
)?);
@ -131,6 +132,7 @@ impl WorkingCopy {
ident,
treestate,
tree_resolver,
filestore,
filesystem,
ignore_matcher,
locker,
@ -167,6 +169,14 @@ impl WorkingCopy {
self.treestate.lock().set_parents(parents)
}
pub fn filestore(&self) -> ArcReadFileContents {
self.filestore.clone()
}
pub fn tree_resolver(&self) -> ArcReadTreeManifest {
self.tree_resolver.clone()
}
pub(crate) fn current_manifests(
treestate: &TreeState,
tree_resolver: &ArcReadTreeManifest,

View File

@ -16,4 +16,7 @@ pyconfigloader = { version = "0.1.0", path = "../pyconfigloader" }
pypathmatcher = { version = "0.1.0", path = "../pypathmatcher" }
pystatus = { version = "0.1.0", path = "../pystatus" }
pytreestate = { version = "0.1.0", path = "../pytreestate" }
sparse = { version = "0.1.0", path = "../../../../lib/sparse" }
types = { version = "0.1.0", path = "../../../../lib/types" }
util = { version = "0.1.0", path = "../../../../lib/util" }
workingcopy = { version = "0.1.0", path = "../../../../lib/workingcopy" }

View File

@ -17,6 +17,7 @@ use std::time::SystemTime;
use anyhow::anyhow;
use anyhow::Error;
use cpython::*;
use cpython_ext::convert::Serde;
use cpython_ext::error::ResultPyErrExt;
use cpython_ext::PyPathBuf;
use io::IO;
@ -25,10 +26,12 @@ use pathmatcher::Matcher;
use pyconfigloader::config;
use pypathmatcher::extract_matcher;
use pypathmatcher::extract_option_matcher;
use pypathmatcher::treematcher;
use pytreestate::treestate;
use rsworkingcopy::walker::WalkError;
use rsworkingcopy::walker::Walker;
use rsworkingcopy::workingcopy::WorkingCopy;
use types::HgId;
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
let name = [package, "workingcopy"].join(".");
@ -117,6 +120,53 @@ py_class!(pub class workingcopy |py| {
}).map_pyerr(py)?
)
}
// Fetch list of (treematcher, rule_details) to be unioned together.
// rule_details drive the sparse "explain" functionality.
def sparsematchers(
&self,
nodes: Serde<Vec<HgId>>,
raw_config: Option<(String, String)>,
debug_version: Option<String>,
no_catch_all: bool,
) -> PyResult<Vec<(treematcher, Vec<String>)>> {
let wc = self.inner(py).read();
let mut prof = match raw_config {
Some((contents, source)) => sparse::Root::from_bytes(contents.into_bytes(), source).map_pyerr(py)?,
None => {
let repo_sparse_path = wc.dot_hg_path().join("sparse");
match util::file::read(&repo_sparse_path) {
Ok(contents) => sparse::Root::from_bytes(contents, repo_sparse_path.display().to_string()).map_pyerr(py)?,
Err(e) if e.is_not_found() => return Ok(Vec::new()),
Err(e) => return Err(e).map_pyerr(py),
}
},
};
if debug_version.is_some() {
prof.set_version_override(debug_version);
}
prof.set_skip_catch_all(no_catch_all);
let overrides = rsworkingcopy::sparse::disk_overrides(wc.dot_hg_path()).map_pyerr(py)?;
let mut all_tree_matchers = Vec::new();
for node in &*nodes {
let mf = wc.tree_resolver().get(node).map_pyerr(py)?;
let matcher = rsworkingcopy::sparse::build_matcher(&prof, mf.read().clone(), wc.filestore(), &overrides).map_pyerr(py)?.0;
let tree_matchers = matcher.into_matchers();
if tree_matchers.is_empty() {
return Ok(Vec::new());
}
all_tree_matchers.extend(tree_matchers.into_iter());
}
all_tree_matchers
.into_iter()
.map(|(tm, origins)| Ok((treematcher::create_instance(py, Arc::new(tm))?, origins)))
.collect::<PyResult<Vec<_>>>()
}
});
fn parse_git_submodules(py: Python, data: &PyBytes) -> PyResult<Vec<(String, String, String)>> {