mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
add eden glob command
Summary: It's silly to use `eden prefetch --no-prefetch` to efficiently glob for filenames. Introduce an `eden glob` command which resolves a glob relative to the current working directory. Reviewed By: genevievehelsel Differential Revision: D25450358 fbshipit-source-id: 45d6dc870d21510e51d5662c75e80385886899fc
This commit is contained in:
parent
7a3ac07f7f
commit
68cf44a8d1
@ -27,7 +27,7 @@ from eden.fs.cli.telemetry import TelemetrySample
|
|||||||
from eden.fs.cli.util import check_health_using_lockfile, wait_for_instance_healthy
|
from eden.fs.cli.util import check_health_using_lockfile, wait_for_instance_healthy
|
||||||
from eden.thrift.legacy import EdenClient, EdenNotRunningError
|
from eden.thrift.legacy import EdenClient, EdenNotRunningError
|
||||||
from facebook.eden import EdenService
|
from facebook.eden import EdenService
|
||||||
from facebook.eden.ttypes import GlobParams, MountInfo as ThriftMountInfo, MountState
|
from facebook.eden.ttypes import MountInfo as ThriftMountInfo, MountState
|
||||||
from fb303_core.ttypes import fb303_status
|
from fb303_core.ttypes import fb303_status
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@ -2068,6 +2068,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|||||||
stats_mod.StatsCmd,
|
stats_mod.StatsCmd,
|
||||||
trace_mod.TraceCmd,
|
trace_mod.TraceCmd,
|
||||||
redirect_mod.RedirectCmd,
|
redirect_mod.RedirectCmd,
|
||||||
|
prefetch_mod.GlobCmd,
|
||||||
prefetch_mod.PrefetchCmd,
|
prefetch_mod.PrefetchCmd,
|
||||||
prefetch_profile_mod.PrefetchProfileCmd,
|
prefetch_profile_mod.PrefetchProfileCmd,
|
||||||
]
|
]
|
||||||
|
@ -5,29 +5,93 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import NamedTuple, List
|
||||||
|
|
||||||
from facebook.eden.ttypes import GlobParams
|
from facebook.eden.ttypes import GlobParams
|
||||||
|
|
||||||
from .cmd_util import require_checkout
|
from .cmd_util import require_checkout
|
||||||
|
from .config import EdenCheckout, EdenInstance
|
||||||
from .subcmd import Subcmd
|
from .subcmd import Subcmd
|
||||||
|
|
||||||
|
|
||||||
|
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument(
|
||||||
|
"--repo", help="Specify path to repo root (default: root of cwd)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--pattern-file",
|
||||||
|
help=(
|
||||||
|
"Specify path to a file that lists patterns/files to match, one per line"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"PATTERN", nargs="*", help="Filename patterns to match via fnmatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckoutAndPatterns(NamedTuple):
|
||||||
|
instance: EdenInstance
|
||||||
|
checkout: EdenCheckout
|
||||||
|
rel_path: Path
|
||||||
|
patterns: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def find_checkout_and_patterns(
|
||||||
|
args: argparse.Namespace,
|
||||||
|
) -> CheckoutAndPatterns:
|
||||||
|
instance, checkout, rel_path = require_checkout(args, args.repo)
|
||||||
|
if args.repo and rel_path != Path("."):
|
||||||
|
print(f"{args.repo} is not the root of an eden repo", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
patterns = list(args.PATTERN)
|
||||||
|
if args.pattern_file is not None:
|
||||||
|
with open(args.pattern_file) as f:
|
||||||
|
patterns.extend(pat.strip() for pat in f.readlines())
|
||||||
|
|
||||||
|
return CheckoutAndPatterns(
|
||||||
|
instance=instance,
|
||||||
|
checkout=checkout,
|
||||||
|
rel_path=rel_path,
|
||||||
|
patterns=patterns,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GlobCmd(Subcmd):
|
||||||
|
NAME = "glob"
|
||||||
|
HELP = "Print matching filenames"
|
||||||
|
|
||||||
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
||||||
|
add_common_arguments(parser)
|
||||||
|
|
||||||
|
def run(self, args: argparse.Namespace) -> int:
|
||||||
|
checkout_and_patterns = find_checkout_and_patterns(args)
|
||||||
|
|
||||||
|
with checkout_and_patterns.instance.get_thrift_client_legacy() as client:
|
||||||
|
result = client.globFiles(
|
||||||
|
GlobParams(
|
||||||
|
mountPoint=bytes(checkout_and_patterns.checkout.path),
|
||||||
|
globs=checkout_and_patterns.patterns,
|
||||||
|
includeDotfiles=False,
|
||||||
|
prefetchFiles=False,
|
||||||
|
suppressFileList=False,
|
||||||
|
prefetchMetadata=False,
|
||||||
|
searchRoot=os.fsencode(checkout_and_patterns.rel_path),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for name in result.matchingFiles:
|
||||||
|
print(os.fsdecode(name))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class PrefetchCmd(Subcmd):
|
class PrefetchCmd(Subcmd):
|
||||||
NAME = "prefetch"
|
NAME = "prefetch"
|
||||||
HELP = "Prefetch content for matching file patterns"
|
HELP = "Prefetch content for matching file patterns"
|
||||||
|
|
||||||
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument(
|
add_common_arguments(parser)
|
||||||
"--repo", help="Specify path to repo root (default: root of cwd)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--pattern-file",
|
|
||||||
help=(
|
|
||||||
"Specify path to a file that lists patterns/files "
|
|
||||||
"to match, one per line"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--silent",
|
"--silent",
|
||||||
help="Do not print the names of the matching files",
|
help="Do not print the names of the matching files",
|
||||||
@ -40,25 +104,15 @@ class PrefetchCmd(Subcmd):
|
|||||||
default=False,
|
default=False,
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"PATTERN", nargs="*", help="Filename patterns to match via fnmatch"
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self, args: argparse.Namespace) -> int:
|
def run(self, args: argparse.Namespace) -> int:
|
||||||
instance, checkout, rel_path = require_checkout(args, args.repo)
|
checkout_and_patterns = find_checkout_and_patterns(args)
|
||||||
if args.repo and rel_path != Path("."):
|
|
||||||
print(f"{args.repo} is not the root of an eden repo")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if args.pattern_file is not None:
|
with checkout_and_patterns.instance.get_thrift_client_legacy() as client:
|
||||||
with open(args.pattern_file) as f:
|
|
||||||
args.PATTERN += [pat.strip() for pat in f.readlines()]
|
|
||||||
|
|
||||||
with instance.get_thrift_client_legacy() as client:
|
|
||||||
result = client.globFiles(
|
result = client.globFiles(
|
||||||
GlobParams(
|
GlobParams(
|
||||||
mountPoint=bytes(checkout.path),
|
mountPoint=bytes(checkout_and_patterns.checkout.path),
|
||||||
globs=args.PATTERN,
|
globs=checkout_and_patterns.patterns,
|
||||||
includeDotfiles=False,
|
includeDotfiles=False,
|
||||||
prefetchFiles=not args.no_prefetch,
|
prefetchFiles=not args.no_prefetch,
|
||||||
suppressFileList=args.silent,
|
suppressFileList=args.silent,
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
#include "eden/fs/store/LocalStore.h"
|
#include "eden/fs/store/LocalStore.h"
|
||||||
#include "eden/fs/store/ObjectFetchContext.h"
|
#include "eden/fs/store/ObjectFetchContext.h"
|
||||||
#include "eden/fs/store/ObjectStore.h"
|
#include "eden/fs/store/ObjectStore.h"
|
||||||
|
#include "eden/fs/store/PathLoader.h"
|
||||||
#include "eden/fs/store/hg/HgQueuedBackingStore.h"
|
#include "eden/fs/store/hg/HgQueuedBackingStore.h"
|
||||||
#include "eden/fs/telemetry/Tracing.h"
|
#include "eden/fs/telemetry/Tracing.h"
|
||||||
#include "eden/fs/utils/Bug.h"
|
#include "eden/fs/utils/Bug.h"
|
||||||
@ -1151,6 +1152,8 @@ folly::Future<std::unique_ptr<Glob>> EdenServiceHandler::future_globFiles(
|
|||||||
// if none are specified. The results will be collected here.
|
// if none are specified. The results will be collected here.
|
||||||
std::vector<folly::Future<std::vector<GlobNode::GlobResult>>> globResults{};
|
std::vector<folly::Future<std::vector<GlobNode::GlobResult>>> globResults{};
|
||||||
|
|
||||||
|
RelativePath searchRoot{*params->searchRoot_ref()};
|
||||||
|
|
||||||
auto rootHashes = params->revisions_ref();
|
auto rootHashes = params->revisions_ref();
|
||||||
if (!rootHashes->empty()) {
|
if (!rootHashes->empty()) {
|
||||||
// Note that we MUST reserve here, otherwise while emplacing we might
|
// Note that we MUST reserve here, otherwise while emplacing we might
|
||||||
@ -1160,32 +1163,53 @@ folly::Future<std::unique_ptr<Glob>> EdenServiceHandler::future_globFiles(
|
|||||||
for (auto& rootHash : *rootHashes) {
|
for (auto& rootHash : *rootHashes) {
|
||||||
const Hash& originHash =
|
const Hash& originHash =
|
||||||
originHashes->emplace_back(hashFromThrift(rootHash));
|
originHashes->emplace_back(hashFromThrift(rootHash));
|
||||||
globResults.emplace_back(edenMount->getObjectStore()
|
|
||||||
->getTreeForCommit(originHash, fetchContext)
|
globResults.emplace_back(
|
||||||
.thenValue([edenMount,
|
edenMount->getObjectStore()
|
||||||
globRoot,
|
->getTreeForCommit(originHash, fetchContext)
|
||||||
&fetchContext,
|
.thenValue([edenMount,
|
||||||
fileBlobsToPrefetch,
|
globRoot,
|
||||||
&originHash](auto&& rootTree) {
|
&fetchContext,
|
||||||
return globRoot->evaluate(
|
fileBlobsToPrefetch,
|
||||||
edenMount->getObjectStore(),
|
searchRoot](std::shared_ptr<const Tree>&& rootTree) {
|
||||||
fetchContext,
|
return resolveTree(
|
||||||
RelativePathPiece(),
|
*edenMount->getObjectStore(),
|
||||||
rootTree,
|
fetchContext,
|
||||||
fileBlobsToPrefetch,
|
std::move(rootTree),
|
||||||
originHash);
|
searchRoot);
|
||||||
}));
|
})
|
||||||
|
.thenValue([edenMount,
|
||||||
|
globRoot,
|
||||||
|
&fetchContext,
|
||||||
|
fileBlobsToPrefetch,
|
||||||
|
&originHash](std::shared_ptr<const Tree>&& tree) {
|
||||||
|
return globRoot->evaluate(
|
||||||
|
edenMount->getObjectStore(),
|
||||||
|
fetchContext,
|
||||||
|
RelativePathPiece(),
|
||||||
|
tree,
|
||||||
|
fileBlobsToPrefetch,
|
||||||
|
originHash);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const Hash& originHash =
|
const Hash& originHash =
|
||||||
originHashes->emplace_back(edenMount->getParentCommits().parent1());
|
originHashes->emplace_back(edenMount->getParentCommits().parent1());
|
||||||
globResults.emplace_back(globRoot->evaluate(
|
globResults.emplace_back(
|
||||||
edenMount->getObjectStore(),
|
edenMount->getInode(searchRoot, helper->getFetchContext())
|
||||||
fetchContext,
|
.thenValue([helper = helper.get(),
|
||||||
RelativePathPiece(),
|
globRoot,
|
||||||
edenMount->getRootInode(),
|
edenMount,
|
||||||
fileBlobsToPrefetch,
|
fileBlobsToPrefetch,
|
||||||
originHash));
|
&originHash](InodePtr inode) {
|
||||||
|
return globRoot->evaluate(
|
||||||
|
edenMount->getObjectStore(),
|
||||||
|
helper->getFetchContext(),
|
||||||
|
RelativePathPiece(),
|
||||||
|
inode.asTreePtr(),
|
||||||
|
fileBlobsToPrefetch,
|
||||||
|
originHash);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapFuture(
|
return wrapFuture(
|
||||||
|
@ -689,6 +689,9 @@ struct GlobParams {
|
|||||||
// in general we want to prefetch metadata, but some large globs can
|
// in general we want to prefetch metadata, but some large globs can
|
||||||
// trigger too many metadata prefetches, so we allow skipping this.
|
// trigger too many metadata prefetches, so we allow skipping this.
|
||||||
8: bool prefetchMetadata = true;
|
8: bool prefetchMetadata = true;
|
||||||
|
// The directory from which the glob should be evaluated. Defaults to the
|
||||||
|
// repository root.
|
||||||
|
9: PathString searchRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Glob {
|
struct Glob {
|
||||||
|
76
eden/fs/store/PathLoader.cpp
Normal file
76
eden/fs/store/PathLoader.cpp
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This software may be used and distributed according to the terms of the
|
||||||
|
* GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "eden/fs/store/PathLoader.h"
|
||||||
|
#include <vector>
|
||||||
|
#include "eden/fs/model/Tree.h"
|
||||||
|
#include "eden/fs/service/gen-cpp2/eden_constants.h"
|
||||||
|
#include "eden/fs/store/ObjectStore.h"
|
||||||
|
#include "eden/fs/utils/EdenError.h"
|
||||||
|
|
||||||
|
namespace facebook::eden {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ResolveTreeContext {
|
||||||
|
std::vector<PathComponent> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
folly::Future<std::shared_ptr<const Tree>> resolveTree(
|
||||||
|
std::shared_ptr<ResolveTreeContext> ctx,
|
||||||
|
ObjectStore& objectStore,
|
||||||
|
ObjectFetchContext& fetchContext,
|
||||||
|
std::shared_ptr<const Tree> root,
|
||||||
|
size_t index) {
|
||||||
|
if (index == ctx->components.size()) {
|
||||||
|
return std::move(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* child = root->getEntryPtr(ctx->components[index]);
|
||||||
|
if (!child) {
|
||||||
|
throw newEdenError(
|
||||||
|
ENOENT,
|
||||||
|
EdenErrorType::POSIX_ERROR,
|
||||||
|
"no child with name ",
|
||||||
|
ctx->components[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!child->isTree()) {
|
||||||
|
throw newEdenError(
|
||||||
|
ENOTDIR,
|
||||||
|
EdenErrorType::POSIX_ERROR,
|
||||||
|
"child is not tree ",
|
||||||
|
ctx->components[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectStore.getTree(child->getHash(), fetchContext)
|
||||||
|
.thenValue([ctx = std::move(ctx), &objectStore, &fetchContext, index](
|
||||||
|
std::shared_ptr<const Tree>&& tree) mutable {
|
||||||
|
return resolveTree(
|
||||||
|
ctx, objectStore, fetchContext, std::move(tree), index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
folly::Future<std::shared_ptr<const Tree>> resolveTree(
|
||||||
|
ObjectStore& objectStore,
|
||||||
|
ObjectFetchContext& fetchContext,
|
||||||
|
std::shared_ptr<const Tree> root,
|
||||||
|
RelativePathPiece path) {
|
||||||
|
// Don't do anything fancy with lifetimes and just get this correct as simply
|
||||||
|
// as possible. There's room for optimization if it matters.
|
||||||
|
auto ctx = std::make_shared<ResolveTreeContext>();
|
||||||
|
for (auto c : path.components()) {
|
||||||
|
ctx->components.emplace_back(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveTree(
|
||||||
|
std::move(ctx), objectStore, fetchContext, std::move(root), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace facebook::eden
|
26
eden/fs/store/PathLoader.h
Normal file
26
eden/fs/store/PathLoader.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This software may be used and distributed according to the terms of the
|
||||||
|
* GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <folly/futures/Future.h>
|
||||||
|
#include "eden/fs/store/ObjectFetchContext.h"
|
||||||
|
#include "eden/fs/utils/PathFuncs.h"
|
||||||
|
|
||||||
|
namespace facebook::eden {
|
||||||
|
|
||||||
|
class ObjectFetchContext;
|
||||||
|
class ObjectStore;
|
||||||
|
class Tree;
|
||||||
|
|
||||||
|
folly::Future<std::shared_ptr<const Tree>> resolveTree(
|
||||||
|
ObjectStore& objectStore,
|
||||||
|
ObjectFetchContext& fetchContext,
|
||||||
|
std::shared_ptr<const Tree> root,
|
||||||
|
RelativePathPiece path);
|
||||||
|
|
||||||
|
} // namespace facebook::eden
|
@ -38,6 +38,8 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
self.repo.write_file("java/com/example/foo/bar/Bar.java", "")
|
self.repo.write_file("java/com/example/foo/bar/Bar.java", "")
|
||||||
self.repo.write_file("java/com/example/foo/bar/baz/Baz.java", "")
|
self.repo.write_file("java/com/example/foo/bar/baz/Baz.java", "")
|
||||||
|
|
||||||
|
self.repo.write_file("other/exclude.java", "")
|
||||||
|
|
||||||
self.commit1 = self.repo.commit("Commit 1.")
|
self.commit1 = self.repo.commit("Commit 1.")
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
@ -53,64 +55,65 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
self.addCleanup(self.client.close)
|
self.addCleanup(self.client.close)
|
||||||
|
|
||||||
def test_exact_path_component_match(self) -> None:
|
def test_exact_path_component_match(self) -> None:
|
||||||
self.assert_glob(["hello"], ["hello"])
|
self.assert_glob(["hello"], [b"hello"])
|
||||||
self.assert_glob(["ddir/subdir/.dotfile"], ["ddir/subdir/.dotfile"])
|
self.assert_glob(["ddir/subdir/.dotfile"], [b"ddir/subdir/.dotfile"])
|
||||||
|
|
||||||
def test_wildcard_path_component_match(self) -> None:
|
def test_wildcard_path_component_match(self) -> None:
|
||||||
self.assert_glob(["hel*"], ["hello"])
|
self.assert_glob(["hel*"], [b"hello"])
|
||||||
self.assert_glob(["ad*"], ["adir"])
|
self.assert_glob(["ad*"], [b"adir"])
|
||||||
self.assert_glob_with_dtypes(["ad*"], [("adir", "d")])
|
self.assert_glob_with_dtypes(["ad*"], [(b"adir", "d")])
|
||||||
self.assert_glob(["a*/file"], ["adir/file"])
|
self.assert_glob(["a*/file"], [b"adir/file"])
|
||||||
self.assert_glob_with_dtypes(["a*/file"], [("adir/file", "f")])
|
self.assert_glob_with_dtypes(["a*/file"], [(b"adir/file", "f")])
|
||||||
|
|
||||||
def test_no_accidental_substring_match(self) -> None:
|
def test_no_accidental_substring_match(self) -> None:
|
||||||
self.assert_glob(["hell"], [], msg="No accidental substring match")
|
self.assert_glob(["hell"], [], msg="No accidental substring match")
|
||||||
|
|
||||||
def test_match_all_files_in_directory(self) -> None:
|
def test_match_all_files_in_directory(self) -> None:
|
||||||
self.assert_glob(["bdir/*"], ["bdir/file", "bdir/otherfile"])
|
self.assert_glob(["bdir/*"], [b"bdir/file", b"bdir/otherfile"])
|
||||||
|
|
||||||
def test_match_all_files_in_directory_with_dotfile(self) -> None:
|
def test_match_all_files_in_directory_with_dotfile(self) -> None:
|
||||||
self.assert_glob(["ddir/subdir/*"], ["ddir/subdir/notdotfile"])
|
self.assert_glob(["ddir/subdir/*"], [b"ddir/subdir/notdotfile"])
|
||||||
|
|
||||||
def test_overlapping_globs(self) -> None:
|
def test_overlapping_globs(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["adir/*", "**/file"],
|
["adir/*", "**/file"],
|
||||||
["adir/file", "bdir/file"],
|
[b"adir/file", b"bdir/file"],
|
||||||
msg="De-duplicate results from multiple globs",
|
msg="De-duplicate results from multiple globs",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_recursive_wildcard_prefix(self) -> None:
|
def test_recursive_wildcard_prefix(self) -> None:
|
||||||
self.assert_glob(["**/file"], ["adir/file", "bdir/file"])
|
self.assert_glob(["**/file"], [b"adir/file", b"bdir/file"])
|
||||||
|
|
||||||
def test_recursive_wildcard_suffix(self) -> None:
|
def test_recursive_wildcard_suffix(self) -> None:
|
||||||
self.assert_glob(["adir/**"], ["adir/file"])
|
self.assert_glob(["adir/**"], [b"adir/file"])
|
||||||
self.assert_glob(["adir/**/*"], ["adir/file"])
|
self.assert_glob(["adir/**/*"], [b"adir/file"])
|
||||||
|
|
||||||
def test_recursive_wildcard_suffix_with_dotfile(self) -> None:
|
def test_recursive_wildcard_suffix_with_dotfile(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["ddir/**"], ["ddir/notdotfile", "ddir/subdir", "ddir/subdir/notdotfile"]
|
["ddir/**"], [b"ddir/notdotfile", b"ddir/subdir", b"ddir/subdir/notdotfile"]
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["ddir/**"],
|
["ddir/**"],
|
||||||
[
|
[
|
||||||
"ddir/notdotfile",
|
b"ddir/notdotfile",
|
||||||
"ddir/subdir",
|
b"ddir/subdir",
|
||||||
"ddir/subdir/.dotfile",
|
b"ddir/subdir/.dotfile",
|
||||||
"ddir/subdir/notdotfile",
|
b"ddir/subdir/notdotfile",
|
||||||
],
|
],
|
||||||
include_dotfiles=True,
|
include_dotfiles=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["ddir/**/*"], ["ddir/notdotfile", "ddir/subdir", "ddir/subdir/notdotfile"]
|
["ddir/**/*"],
|
||||||
|
[b"ddir/notdotfile", b"ddir/subdir", b"ddir/subdir/notdotfile"],
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["ddir/**/*"],
|
["ddir/**/*"],
|
||||||
[
|
[
|
||||||
"ddir/notdotfile",
|
b"ddir/notdotfile",
|
||||||
"ddir/subdir",
|
b"ddir/subdir",
|
||||||
"ddir/subdir/.dotfile",
|
b"ddir/subdir/.dotfile",
|
||||||
"ddir/subdir/notdotfile",
|
b"ddir/subdir/notdotfile",
|
||||||
],
|
],
|
||||||
include_dotfiles=True,
|
include_dotfiles=True,
|
||||||
)
|
)
|
||||||
@ -119,14 +122,14 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["java/com/**/*.java"],
|
["java/com/**/*.java"],
|
||||||
[
|
[
|
||||||
"java/com/example/Example.java",
|
b"java/com/example/Example.java",
|
||||||
"java/com/example/foo/Foo.java",
|
b"java/com/example/foo/Foo.java",
|
||||||
"java/com/example/foo/bar/Bar.java",
|
b"java/com/example/foo/bar/Bar.java",
|
||||||
"java/com/example/foo/bar/baz/Baz.java",
|
b"java/com/example/foo/bar/baz/Baz.java",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["java/com/example/*/*.java"], ["java/com/example/foo/Foo.java"]
|
["java/com/example/*/*.java"], [b"java/com/example/foo/Foo.java"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_malformed_query(self) -> None:
|
def test_malformed_query(self) -> None:
|
||||||
@ -154,37 +157,37 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
self.assertEqual(EdenErrorType.ARGUMENT_ERROR, ctx.exception.errorType)
|
self.assertEqual(EdenErrorType.ARGUMENT_ERROR, ctx.exception.errorType)
|
||||||
|
|
||||||
def test_glob_on_non_current_commit(self) -> None:
|
def test_glob_on_non_current_commit(self) -> None:
|
||||||
self.assert_glob(["hello"], ["hello"], commits=[bytes.fromhex(self.commit0)])
|
self.assert_glob(["hello"], [b"hello"], commits=[bytes.fromhex(self.commit0)])
|
||||||
self.assert_glob(["hola"], ["hola"], commits=[bytes.fromhex(self.commit0)])
|
self.assert_glob(["hola"], [b"hola"], commits=[bytes.fromhex(self.commit0)])
|
||||||
|
|
||||||
def test_glob_multiple_commits(self) -> None:
|
def test_glob_multiple_commits(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
["hello", "hello"],
|
[b"hello", b"hello"],
|
||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["h*"],
|
["h*"],
|
||||||
["hello", "hello", "hola"],
|
[b"hello", b"hello", b"hola"],
|
||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["a*/*ile"],
|
["a*/*ile"],
|
||||||
["adir/file", "adir/phile"],
|
[b"adir/file", b"adir/phile"],
|
||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_prefetch_matching_files(self) -> None:
|
def test_prefetch_matching_files(self) -> None:
|
||||||
self.assert_glob(["hello"], ["hello"], prefetching=True)
|
self.assert_glob(["hello"], [b"hello"], prefetching=True)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
["hello"],
|
[b"hello"],
|
||||||
prefetching=True,
|
prefetching=True,
|
||||||
commits=[bytes.fromhex(self.commit0)],
|
commits=[bytes.fromhex(self.commit0)],
|
||||||
)
|
)
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
["hello", "hello"],
|
[b"hello", b"hello"],
|
||||||
prefetching=True,
|
prefetching=True,
|
||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
@ -192,13 +195,13 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
def test_simple_matching_commit(self) -> None:
|
def test_simple_matching_commit(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
expected_matches=["hello"],
|
expected_matches=[b"hello"],
|
||||||
expected_commits=[bytes.fromhex(self.commit1)],
|
expected_commits=[bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
expected_matches=["hello"],
|
expected_matches=[b"hello"],
|
||||||
expected_commits=[bytes.fromhex(self.commit0)],
|
expected_commits=[bytes.fromhex(self.commit0)],
|
||||||
commits=[bytes.fromhex(self.commit0)],
|
commits=[bytes.fromhex(self.commit0)],
|
||||||
)
|
)
|
||||||
@ -206,7 +209,7 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
def test_duplicate_file_multiple_commits(self) -> None:
|
def test_duplicate_file_multiple_commits(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["hello"],
|
["hello"],
|
||||||
expected_matches=["hello", "hello"],
|
expected_matches=[b"hello", b"hello"],
|
||||||
expected_commits=[
|
expected_commits=[
|
||||||
bytes.fromhex(self.commit0),
|
bytes.fromhex(self.commit0),
|
||||||
bytes.fromhex(self.commit1),
|
bytes.fromhex(self.commit1),
|
||||||
@ -214,26 +217,58 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_multiple_file_multiple_commits(self) -> None:
|
def test_multiple_file_multiple_commits(self) -> None:
|
||||||
self.assert_glob(
|
self.assert_glob(
|
||||||
["a*/*ile"],
|
["a*/*ile"],
|
||||||
[b"adir/file", b"adir/phile"],
|
[b"adir/file", b"adir/phile"],
|
||||||
expected_commits=[
|
expected_commits=[
|
||||||
bytes.fromhex(self.commit1),
|
bytes.fromhex(self.commit1),
|
||||||
bytes.fromhex(self.commit0),
|
bytes.fromhex(self.commit0),
|
||||||
],
|
],
|
||||||
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
commits=[bytes.fromhex(self.commit0), bytes.fromhex(self.commit1)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_search_root(self) -> None:
|
||||||
|
self.assert_glob(
|
||||||
|
["**/*.java"],
|
||||||
|
expected_matches=[
|
||||||
|
b"example/Example.java",
|
||||||
|
b"example/foo/Foo.java",
|
||||||
|
b"example/foo/bar/Bar.java",
|
||||||
|
b"example/foo/bar/baz/Baz.java",
|
||||||
|
],
|
||||||
|
search_root=b"java/com",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_search_root_with_specified_commits(self) -> None:
|
||||||
|
self.assert_glob(
|
||||||
|
["**/*.java"],
|
||||||
|
expected_matches=[
|
||||||
|
b"example/Example.java",
|
||||||
|
b"example/foo/Foo.java",
|
||||||
|
b"example/foo/bar/Bar.java",
|
||||||
|
b"example/foo/bar/baz/Baz.java",
|
||||||
|
],
|
||||||
|
expected_commits=[
|
||||||
|
bytes.fromhex(self.commit1),
|
||||||
|
bytes.fromhex(self.commit1),
|
||||||
|
bytes.fromhex(self.commit1),
|
||||||
|
bytes.fromhex(self.commit1),
|
||||||
|
],
|
||||||
|
commits=[bytes.fromhex(self.commit1)],
|
||||||
|
search_root=b"java/com",
|
||||||
|
)
|
||||||
|
|
||||||
def assert_glob(
|
def assert_glob(
|
||||||
self,
|
self,
|
||||||
globs: List[str],
|
globs: List[str],
|
||||||
expected_matches: List[str],
|
expected_matches: List[bytes],
|
||||||
include_dotfiles: bool = False,
|
include_dotfiles: bool = False,
|
||||||
msg: Optional[str] = None,
|
msg: Optional[str] = None,
|
||||||
commits: Optional[List[bytes]] = None,
|
commits: Optional[List[bytes]] = None,
|
||||||
prefetching: bool = False,
|
prefetching: bool = False,
|
||||||
expected_commits: Optional[List[bytes]] = None,
|
expected_commits: Optional[List[bytes]] = None,
|
||||||
|
search_root: Optional[bytes] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
params = GlobParams(
|
params = GlobParams(
|
||||||
mountPoint=self.mount_path_bytes,
|
mountPoint=self.mount_path_bytes,
|
||||||
@ -241,13 +276,10 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
includeDotfiles=include_dotfiles,
|
includeDotfiles=include_dotfiles,
|
||||||
prefetchFiles=prefetching,
|
prefetchFiles=prefetching,
|
||||||
revisions=commits,
|
revisions=commits,
|
||||||
|
searchRoot=search_root,
|
||||||
)
|
)
|
||||||
result = self.client.globFiles(params)
|
result = self.client.globFiles(params)
|
||||||
path_results = (
|
self.assertEqual(expected_matches, sorted(result.matchingFiles), msg=msg)
|
||||||
path.decode("utf-8", errors="surrogateescape")
|
|
||||||
for path in result.matchingFiles
|
|
||||||
)
|
|
||||||
self.assertEqual(expected_matches, sorted(path_results), msg=msg)
|
|
||||||
self.assertFalse(result.dtypes)
|
self.assertFalse(result.dtypes)
|
||||||
|
|
||||||
if expected_commits:
|
if expected_commits:
|
||||||
@ -258,7 +290,7 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
def assert_glob_with_dtypes(
|
def assert_glob_with_dtypes(
|
||||||
self,
|
self,
|
||||||
globs: List[str],
|
globs: List[str],
|
||||||
expected_matches: List[Tuple[str, str]],
|
expected_matches: List[Tuple[bytes, str]],
|
||||||
include_dotfiles: bool = False,
|
include_dotfiles: bool = False,
|
||||||
msg: Optional[str] = None,
|
msg: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -270,10 +302,7 @@ class GlobTest(testcase.EdenRepoTest):
|
|||||||
)
|
)
|
||||||
result = self.client.globFiles(params)
|
result = self.client.globFiles(params)
|
||||||
actual_results = zip(
|
actual_results = zip(
|
||||||
(
|
result.matchingFiles,
|
||||||
path.decode("utf-8", errors="surrogateescape")
|
|
||||||
for path in result.matchingFiles
|
|
||||||
),
|
|
||||||
(_dtype_to_str(dtype) for dtype in result.dtypes),
|
(_dtype_to_str(dtype) for dtype in result.dtypes),
|
||||||
)
|
)
|
||||||
self.assertEqual(expected_matches, sorted(actual_results), msg=msg)
|
self.assertEqual(expected_matches, sorted(actual_results), msg=msg)
|
||||||
|
Loading…
Reference in New Issue
Block a user