introduce Filter class

Summary:
When implementing Mercurial filtering logic for the FilteredBackingStore, I realized that simply having a FilterCallback function would be insufficient for most filtering implementations.

There's global state that one may want to keep track of, validation logic, and possible caching that can be done by the filter handler.

Therefore, in this diff I'm introducing the Filter class. Each FilteredBackingStore user will implement their own Filter class to define how filtering is done. These custom Filter classes can add things like validation, caching, etc.

The next diff introduces the HgSparseFilter class and tests it.

Reviewed By: genevievehelsel

Differential Revision: D47161201

fbshipit-source-id: 2bf119ad8a2d01274b272f03ffb5b130e030a0ca
This commit is contained in:
Michael Cuevas 2023-07-10 19:33:04 -07:00 committed by Facebook GitHub Bot
parent b9e06424dc
commit 0484a46a24
5 changed files with 87 additions and 39 deletions

View File

@ -12,29 +12,23 @@
#include "eden/fs/model/Tree.h"
#include "eden/fs/store/filter/FilteredObjectId.h"
#include "eden/fs/utils/ImmediateFuture.h"
#include "eden/fs/utils/NotImplemented.h"
namespace facebook::eden {
FilteredBackingStore::FilteredBackingStore(
std::shared_ptr<BackingStore> backingStore,
FilterCallback filterCallback)
: backingStore_{std::move(backingStore)},
filterCallback_{filterCallback} {};
std::unique_ptr<Filter> filter)
: backingStore_{std::move(backingStore)}, filter_{std::move(filter)} {};
FilteredBackingStore::~FilteredBackingStore() {}
namespace {
// Determine whether a path is affected by a filter change from One -> Two or
// vice versa.
bool pathAffectedByFilterChange(
bool FilteredBackingStore::pathAffectedByFilterChange(
RelativePathPiece pathOne,
RelativePathPiece pathTwo,
folly::StringPiece filterOne,
folly::StringPiece filterTwo,
FilterCallback filterCallback) {
auto pathOneIncluded = filterCallback(filterOne, pathOne);
auto pathTwoIncluded = filterCallback(filterTwo, pathTwo);
folly::StringPiece filterIdOne,
folly::StringPiece filterIdTwo) {
auto pathOneIncluded = filter_->isPathFiltered(pathOne, filterIdOne);
auto pathTwoIncluded = filter_->isPathFiltered(pathTwo, filterIdTwo);
// If a path is in neither or both filters, then it wouldn't be affected by
// any change (it is present in both or absent in both).
if (pathOneIncluded == pathTwoIncluded) {
@ -57,7 +51,6 @@ std::tuple<std::string, RootId> parseFilterIdFromRootId(const RootId& rootId) {
auto filterId = rootId.value().substr(separatorIdx + 1);
return {std::move(filterId), std::move(root)};
}
} // namespace
ObjectComparison FilteredBackingStore::compareObjectsById(
const ObjectId& one,
@ -111,8 +104,7 @@ ObjectComparison FilteredBackingStore::compareObjectsById(
filteredOne.path(),
filteredTwo.path(),
filteredOne.filter(),
filteredTwo.filter(),
filterCallback_);
filteredTwo.filter());
if (pathAffected) {
return ObjectComparison::Different;
} else {
@ -143,14 +135,15 @@ ObjectComparison FilteredBackingStore::compareObjectsById(
PathMap<TreeEntry> FilteredBackingStore::filterImpl(
const TreePtr unfilteredTree,
RelativePathPiece treePath,
folly::StringPiece filter) {
folly::StringPiece filterId) {
auto pathMap = PathMap<TreeEntry>{unfilteredTree->getCaseSensitivity()};
for (const auto& [path, entry] : *unfilteredTree) {
auto relPath = RelativePath{treePath} + path;
if (!filterCallback_(filter, relPath.piece())) {
if (!filter_->isPathFiltered(relPath.piece(), filterId)) {
ObjectId oid;
if (entry.getType() == TreeEntryType::TREE) {
auto foid = FilteredObjectId(relPath.piece(), filter, entry.getHash());
auto foid =
FilteredObjectId(relPath.piece(), filterId, entry.getHash());
oid = ObjectId{foid.getValue()};
} else {
auto foid = FilteredObjectId{entry.getHash()};
@ -167,9 +160,9 @@ PathMap<TreeEntry> FilteredBackingStore::filterImpl(
ImmediateFuture<TreePtr> FilteredBackingStore::getRootTree(
const RootId& rootId,
const ObjectFetchContextPtr& context) {
auto [filter, parsedRootId] = parseFilterIdFromRootId(rootId);
auto [filterId, parsedRootId] = parseFilterIdFromRootId(rootId);
return backingStore_->getRootTree(parsedRootId, context)
.thenValue([filterId = filter,
.thenValue([filterId = filterId,
self = shared_from_this()](TreePtr rootTree) {
if (!rootTree) {
return rootTree;

View File

@ -8,6 +8,7 @@
#pragma once
#include "eden/fs/store/BackingStore.h"
#include "eden/fs/store/filter/Filter.h"
#include "eden/fs/store/filter/FilteredObjectId.h"
#include "eden/fs/utils/PathMap.h"
#include "eden/fs/utils/RefPtr.h"
@ -16,10 +17,6 @@ namespace facebook::eden {
class BackingStore;
// True if path is filtered in the given filterId, false otherwise.
using FilterCallback = std::function<
bool(folly::StringPiece /*filterId*/, RelativePathPiece /*path*/)>;
/**
* Implementation of a BackingStore that allows filtering sets odf paths from
* the checkout.
@ -34,7 +31,7 @@ class FilteredBackingStore
public:
FilteredBackingStore(
std::shared_ptr<BackingStore> backingStore,
FilterCallback filterCallback);
std::unique_ptr<Filter> filter);
~FilteredBackingStore() override;
@ -105,7 +102,7 @@ class FilteredBackingStore
// Allows FilteredBackingStore creator to specify how they want to filter
// paths. This returns true if the given path is filtered in the given
// filterId
FilterCallback filterCallback_;
std::unique_ptr<Filter> filter_;
/*
* Does the actual filtering logic for tree and root-tree objects.
@ -113,7 +110,17 @@ class FilteredBackingStore
PathMap<TreeEntry> filterImpl(
const TreePtr unfilteredTree,
RelativePathPiece treePath,
folly::StringPiece filter);
folly::StringPiece filterId);
/*
* Determine whether a path is affected by a filter change from One -> Two or
* vice versa.
*/
bool pathAffectedByFilterChange(
RelativePathPiece pathOne,
RelativePathPiece pathTwo,
folly::StringPiece filterIdOne,
folly::StringPiece filterIdTwo);
};
} // namespace facebook::eden

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#pragma once
#include <folly/Range.h>
#include "eden/fs/utils/PathFuncs.h"
namespace facebook::eden {
class Filter {
public:
virtual ~Filter() {}
/*
* Checks whether a path is filtered by the given filter.
*/
virtual bool isPathFiltered(
RelativePathPiece path,
folly::StringPiece filterId) = 0;
};
} // namespace facebook::eden

View File

@ -16,7 +16,7 @@
#include "eden/fs/model/TestOps.h"
#include "eden/fs/store/FilteredBackingStore.h"
#include "eden/fs/telemetry/EdenStats.h"
#include "eden/fs/testharness/FakeFilter.h"
#include "eden/fs/testharness/TestUtil.h"
#include "eden/fs/utils/PathFuncs.h"
@ -36,9 +36,9 @@ class FilteredBackingStoreTest : public ::testing::Test {
protected:
void SetUp() override {
wrappedStore_ = std::make_shared<FakeBackingStore>();
stats_ = makeRefPtr<EdenStats>();
auto fakeFilter = std::make_unique<FakeFilter>();
filteredStore_ = std::make_shared<FilteredBackingStore>(
wrappedStore_, defaultFilterCallback);
wrappedStore_, std::move(fakeFilter));
}
void TearDown() override {
@ -46,15 +46,7 @@ class FilteredBackingStoreTest : public ::testing::Test {
}
std::shared_ptr<FakeBackingStore> wrappedStore_;
EdenStatsPtr stats_;
std::shared_ptr<FilteredBackingStore> filteredStore_;
private:
static bool defaultFilterCallback(
const folly::StringPiece filterId,
const RelativePathPiece& path) {
return path.view().find(filterId) != std::string::npos;
}
};
/**

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#pragma once
#include "eden/fs/store/filter/Filter.h"
namespace facebook::eden {
/**
* A BackingStore implementation for test code.
*/
class FakeFilter final : public Filter {
public:
~FakeFilter() override {}
/*
* Checks whether a path is filtered by the given filter.
*/
bool isPathFiltered(RelativePathPiece path, folly::StringPiece filterId)
override {
return path.view().find(filterId) != std::string::npos;
}
};
} // namespace facebook::eden