in watchman/watcher/eden.cpp [780:1012]
void timeGenerator(const Query* query, QueryContext* ctx) const override {
ctx->generationStarted();
auto client = getEdenClient(thriftChannel_);
FileDelta delta;
JournalPosition resultPosition;
if (ctx->since.is_timestamp()) {
throw QueryExecError(
"timestamp based since queries are not supported with eden");
}
// This is the fall back for a fresh instance result set.
// There are two different code paths that may need this, so
// it is broken out as a lambda.
auto getAllFiles = [this,
ctx,
&client,
includeDotfiles =
(query->glob_flags & WM_PERIOD) == 0]() {
if (ctx->query->empty_on_fresh_instance) {
// Avoid a full tree walk if we don't need it!
return std::vector<NameAndDType>();
}
std::string globPattern;
if (ctx->query->relative_root) {
w_string_piece rel(ctx->query->relative_root);
rel.advance(ctx->root->root_path.size() + 1);
globPattern.append(rel.data(), rel.size());
globPattern.append("/");
}
globPattern.append("**");
return globNameAndDType(
client.get(),
mountPoint_,
std::vector<std::string>{globPattern},
includeDotfiles);
};
std::vector<NameAndDType> fileInfo;
// We use the list of created files to synthesize the "new" field
// in the file results
std::unordered_set<std::string> createdFileNames;
// The code that was previously here was UB if given a timestamp since.
// Instead, at least throw an exception at this point.
auto& since_clock = std::get<QuerySince::Clock>(ctx->since.since);
if (since_clock.is_fresh_instance) {
// Earlier in the processing flow, we decided that the rootNumber
// didn't match the current root which means that eden was restarted.
// We need to translate this to a fresh instance result set and
// return a list of all possible matching files.
client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
fileInfo = getAllFiles();
} else {
// Query eden to fill in the mountGeneration field.
JournalPosition position;
client->sync_getCurrentJournalPosition(position, mountPoint_);
// dial back to the sequence number from the query
*position.sequenceNumber() = since_clock.ticks;
// Now we can get the change journal from eden
try {
client->sync_getFilesChangedSince(delta, mountPoint_, position);
createdFileNames.insert(
delta.createdPaths()->begin(), delta.createdPaths()->end());
// The list of changed files is the union of the created, added,
// and removed sets returned from eden in list form.
for (auto& name : *delta.changedPaths()) {
fileInfo.emplace_back(NameAndDType(std::move(name)));
}
for (auto& name : *delta.removedPaths()) {
fileInfo.emplace_back(NameAndDType(std::move(name)));
}
for (auto& name : *delta.createdPaths()) {
fileInfo.emplace_back(NameAndDType(std::move(name)));
}
bool didChangeCommits = delta.snapshotTransitions()->size() >= 2 ||
(delta.fromPosition()->snapshotHash() !=
delta.toPosition()->snapshotHash());
if (scm_ && didChangeCommits) {
// Check whether they checked out a new commit or reset the commit to
// a different hash. We interrogate source control to discover
// the set of changed files between those hashes, and then
// add in any paths that may have changed around snapshot hash
// changes events; These are files whose status cannot be
// determined purely from source control operations.
std::unordered_set<std::string> mergedFileList;
for (auto& info : fileInfo) {
mergedFileList.insert(info.name);
}
SCM::StatusResult changedBetweenCommits;
if (delta.snapshotTransitions()->empty()) {
auto fromHash =
folly::hexlify(*delta.fromPosition()->snapshotHash());
auto toHash = folly::hexlify(*delta.toPosition()->snapshotHash());
// Legacy path: this (incorrectly) ignores any commit transitions
// between the initial commit hash and the final commit hash.
log(ERR,
"since ",
*position.sequenceNumber(),
" we changed commit hashes from ",
fromHash,
" to ",
toHash,
"\n");
std::vector<std::string> commits{
std::move(fromHash), std::move(toHash)};
changedBetweenCommits = getSCM()->getFilesChangedBetweenCommits(
std::move(commits),
/*requestId*/ nullptr,
ctx->query->alwaysIncludeDirectories);
} else if (delta.snapshotTransitions()->size() >= 2) {
std::vector<std::string> commits;
commits.reserve(delta.snapshotTransitions()->size());
for (auto& hash : *delta.snapshotTransitions()) {
commits.push_back(folly::hexlify(hash));
}
log(ERR,
"since ",
*position.sequenceNumber(),
" we changed commit hashes ",
folly::join(" -> ", commits),
"\n");
changedBetweenCommits = getSCM()->getFilesChangedBetweenCommits(
std::move(commits),
/*requestId*/ nullptr,
ctx->query->alwaysIncludeDirectories);
}
for (auto& fileName : changedBetweenCommits.changedFiles) {
mergedFileList.insert(std::string{fileName.view()});
}
for (auto& fileName : changedBetweenCommits.removedFiles) {
mergedFileList.insert(std::string{fileName.view()});
}
for (auto& fileName : changedBetweenCommits.addedFiles) {
mergedFileList.insert(std::string{fileName.view()});
createdFileNames.insert(std::string{fileName.view()});
}
// We don't know whether the unclean paths are added, removed
// or just changed. We're going to treat them as changed.
mergedFileList.insert(
std::make_move_iterator(delta.uncleanPaths()->begin()),
std::make_move_iterator(delta.uncleanPaths()->end()));
// Replace the list of fileNames with the de-duped set
// of names we've extracted from source control
fileInfo.clear();
for (auto name : mergedFileList) {
fileInfo.emplace_back(std::move(name));
}
}
resultPosition = *delta.toPosition();
log(DBG,
"wanted from ",
*position.sequenceNumber(),
" result delta from ",
*delta.fromPosition()->sequenceNumber(),
" to ",
*delta.toPosition()->sequenceNumber(),
" with ",
fileInfo.size(),
" changed files\n");
} catch (const EdenError& err) {
// ERANGE: mountGeneration differs
// EDOM: journal was truncated.
// For other situations we let the error propagate.
XCHECK(err.errorCode_ref());
if (*err.errorCode_ref() != ERANGE && *err.errorCode_ref() != EDOM) {
throw;
}
// mountGeneration differs, or journal was truncated,
// so treat this as equivalent to a fresh instance result
since_clock.is_fresh_instance = true;
client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
fileInfo = getAllFiles();
} catch (const SCMError& err) {
// Most likely this means a checkout occurred but we encountered
// an error trying to get the list of files changed between the two
// commits. Generate a fresh instance result since we were unable
// to compute the list of files changed.
log(ERR,
"SCM error while processing EdenFS journal update: ",
err.what(),
"\n");
since_clock.is_fresh_instance = true;
client->sync_getCurrentJournalPosition(resultPosition, mountPoint_);
fileInfo = getAllFiles();
}
}
// Filter out any ignored files
filterOutPaths(fileInfo, ctx);
for (auto& item : fileInfo) {
// a file is considered new if it was present in the created files
// set returned from eden.
bool isNew = createdFileNames.find(item.name) != createdFileNames.end();
auto file = make_unique<EdenFileResult>(
rootPath_,
thriftChannel_,
w_string::pathCat({mountPoint_, item.name}),
&resultPosition,
isNew,
item.dtype);
if (since_clock.is_fresh_instance) {
// Fresh instance queries only return data about files
// that currently exist, and we know this to be true
// here because our list of files comes from evaluating
// a glob.
file->setExists(true);
}
w_query_process_file(ctx->query, ctx, std::move(file));
}
ctx->bumpNumWalked(fileInfo.size());
}