mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
fc07c3b6e6
Summary: Add a fetch context interface to ObjectStore that allows tracing cache hits, backing store fetches, and fetch durations in the context of a diff or checkout operation. Reviewed By: simpkins Differential Revision: D19135625 fbshipit-source-id: d0d8f134b1c89f7ba4971a404a46a69a1704ba5c
423 lines
12 KiB
C++
423 lines
12 KiB
C++
/*
|
|
* 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/inodes/DeferredDiffEntry.h"
|
|
|
|
#include <folly/Unit.h>
|
|
#include <folly/futures/Future.h>
|
|
#include <folly/logging/xlog.h>
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
#include "eden/fs/store/BlobMetadata.h"
|
|
#include "eden/fs/store/Diff.h"
|
|
#include "eden/fs/store/DiffCallback.h"
|
|
#include "eden/fs/store/DiffContext.h"
|
|
#include "eden/fs/store/ObjectStore.h"
|
|
#include "eden/fs/utils/Bug.h"
|
|
#include "eden/fs/utils/Future.h"
|
|
|
|
using folly::Future;
|
|
using folly::makeFuture;
|
|
using folly::Unit;
|
|
using std::make_unique;
|
|
using std::shared_ptr;
|
|
using std::unique_ptr;
|
|
using std::vector;
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
namespace {
|
|
|
|
class UntrackedDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
UntrackedDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
InodePtr inode,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
inode_{std::move(inode)} {}
|
|
|
|
/*
|
|
* This is a template just to avoid ambiguity with the prior constructor,
|
|
* since folly::Future<X> can unfortunately be implicitly constructed from X.
|
|
*/
|
|
template <
|
|
typename InodeFuture,
|
|
typename X = typename std::enable_if<
|
|
std::is_same<folly::Future<InodePtr>, InodeFuture>::value,
|
|
void>>
|
|
UntrackedDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
InodeFuture&& inodeFuture,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
inodeFuture_{std::forward<InodeFuture>(inodeFuture)} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
// If we have an inodeFuture_ to wait on, wait for it to finish,
|
|
// then store the resulting inode_ and invoke run() again.
|
|
if (inodeFuture_.valid()) {
|
|
CHECK(!inode_) << "cannot have both inode_ and inodeFuture_ set";
|
|
return std::move(inodeFuture_).thenValue([this](InodePtr inode) {
|
|
inode_ = std::move(inode);
|
|
inodeFuture_ = folly::Future<InodePtr>::makeEmpty();
|
|
return run();
|
|
});
|
|
}
|
|
|
|
auto treeInode = inode_.asTreePtrOrNull();
|
|
if (!treeInode.get()) {
|
|
return EDEN_BUG_FUTURE(Unit)
|
|
<< "UntrackedDiffEntry should only used with tree inodes";
|
|
}
|
|
|
|
// Recursively diff the untracked directory.
|
|
return treeInode->diff(context_, getPath(), nullptr, ignore_, isIgnored_);
|
|
}
|
|
|
|
private:
|
|
const GitIgnoreStack* ignore_{nullptr};
|
|
bool isIgnored_{false};
|
|
InodePtr inode_;
|
|
folly::Future<InodePtr> inodeFuture_ = folly::Future<InodePtr>::makeEmpty();
|
|
};
|
|
|
|
class ModifiedDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
ModifiedDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
InodePtr inode,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
scmEntry_{scmEntry},
|
|
inode_{std::move(inode)} {}
|
|
|
|
ModifiedDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
folly::Future<InodePtr>&& inodeFuture,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
scmEntry_{scmEntry},
|
|
inodeFuture_{std::move(inodeFuture)} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
// If we have an inodeFuture_, wait on it to complete.
|
|
// TODO: Load the inode in parallel with loading the source control data
|
|
// below.
|
|
if (inodeFuture_.valid()) {
|
|
CHECK(!inode_) << "cannot have both inode_ and inodeFuture_ set";
|
|
return std::move(inodeFuture_).thenValue([this](InodePtr inode) {
|
|
inode_ = std::move(inode);
|
|
inodeFuture_ = Future<InodePtr>::makeEmpty();
|
|
return run();
|
|
});
|
|
}
|
|
|
|
if (scmEntry_.isTree()) {
|
|
return runForScmTree();
|
|
} else {
|
|
return runForScmBlob();
|
|
}
|
|
}
|
|
|
|
private:
|
|
folly::Future<folly::Unit> runForScmTree() {
|
|
auto treeInode = inode_.asTreePtrOrNull();
|
|
if (!treeInode) {
|
|
// This is a Tree in the source control state, but a file or symlink
|
|
// in the current filesystem state.
|
|
// Report this file as untracked, and everything in the source control
|
|
// tree as removed.
|
|
if (isIgnored_) {
|
|
if (context_->listIgnored) {
|
|
XLOG(DBG6) << "directory --> ignored file: " << getPath();
|
|
context_->callback->ignoredFile(getPath());
|
|
}
|
|
} else {
|
|
XLOG(DBG6) << "directory --> untracked file: " << getPath();
|
|
context_->callback->addedFile(getPath());
|
|
}
|
|
// Since this is a file or symlink in the current filesystem state, but a
|
|
// Tree in the source control state, we have to record the files from the
|
|
// Tree as removed. We can delegate this work to the source control tree
|
|
// differ.
|
|
return diffRemovedTree(context_, getPath(), scmEntry_.getHash());
|
|
}
|
|
|
|
{
|
|
auto contents = treeInode->getContents().wlock();
|
|
if (!contents->isMaterialized()) {
|
|
if (contents->treeHash.value() == scmEntry_.getHash()) {
|
|
// It did not change since it was loaded,
|
|
// and it matches the scmEntry we're diffing against.
|
|
return makeFuture();
|
|
} else {
|
|
auto contentsHash = contents->treeHash.value();
|
|
contents.unlock();
|
|
return diffTrees(
|
|
context_,
|
|
getPath(),
|
|
scmEntry_.getHash(),
|
|
contentsHash,
|
|
ignore_,
|
|
isIgnored_);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Possibly modified directory. Load the Tree in question.
|
|
return context_->store
|
|
->getTree(scmEntry_.getHash(), context_->getFetchContext())
|
|
.thenValue([this, treeInode = std::move(treeInode)](
|
|
shared_ptr<const Tree>&& tree) {
|
|
return treeInode->diff(
|
|
context_, getPath(), std::move(tree), ignore_, isIgnored_);
|
|
});
|
|
}
|
|
|
|
folly::Future<folly::Unit> runForScmBlob() {
|
|
auto fileInode = inode_.asFilePtrOrNull();
|
|
if (!fileInode) {
|
|
// This is a file in the source control state, but a directory
|
|
// in the current filesystem state.
|
|
// Report this file as removed, and everything in the source control
|
|
// tree as untracked/ignored.
|
|
XLOG(DBG5) << "removed file: " << getPath();
|
|
context_->callback->removedFile(getPath());
|
|
auto treeInode = inode_.asTreePtr();
|
|
if (isIgnored_ && !context_->listIgnored) {
|
|
return makeFuture();
|
|
}
|
|
return treeInode->diff(context_, getPath(), nullptr, ignore_, isIgnored_);
|
|
}
|
|
|
|
return fileInode
|
|
->isSameAs(
|
|
scmEntry_.getHash(),
|
|
scmEntry_.getType(),
|
|
context_->getFetchContext())
|
|
.thenValue([this](bool isSame) {
|
|
if (!isSame) {
|
|
XLOG(DBG5) << "modified file: " << getPath();
|
|
context_->callback->modifiedFile(getPath());
|
|
}
|
|
});
|
|
}
|
|
|
|
const GitIgnoreStack* ignore_{nullptr};
|
|
bool isIgnored_{false};
|
|
TreeEntry scmEntry_;
|
|
folly::Future<InodePtr> inodeFuture_ = folly::Future<InodePtr>::makeEmpty();
|
|
InodePtr inode_;
|
|
shared_ptr<const Tree> scmTree_;
|
|
};
|
|
|
|
class ModifiedBlobDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
ModifiedBlobDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
Hash currentBlobHash)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
scmEntry_{scmEntry},
|
|
currentBlobHash_{currentBlobHash} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
auto f1 = context_->store->getBlobSha1(
|
|
scmEntry_.getHash(), context_->getFetchContext());
|
|
auto f2 = context_->store->getBlobSha1(
|
|
currentBlobHash_, context_->getFetchContext());
|
|
return collectSafe(f1, f2).thenValue(
|
|
[this](const std::tuple<Hash, Hash>& info) {
|
|
const auto& [info1, info2] = info;
|
|
if (info1 != info2) {
|
|
XLOG(DBG5) << "modified file: " << getPath();
|
|
context_->callback->modifiedFile(getPath());
|
|
}
|
|
});
|
|
}
|
|
|
|
private:
|
|
TreeEntry scmEntry_;
|
|
Hash currentBlobHash_;
|
|
};
|
|
|
|
class ModifiedScmDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
ModifiedScmDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Hash scmHash,
|
|
Hash wdHash,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
scmHash_{scmHash},
|
|
wdHash_{wdHash} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
return diffTrees(
|
|
context_, getPath(), scmHash_, wdHash_, ignore_, isIgnored_);
|
|
}
|
|
|
|
private:
|
|
const GitIgnoreStack* ignore_{nullptr};
|
|
bool isIgnored_{false};
|
|
Hash scmHash_;
|
|
Hash wdHash_;
|
|
};
|
|
|
|
class AddedScmDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
AddedScmDiffEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Hash wdHash,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored)
|
|
: DeferredDiffEntry{context, std::move(path)},
|
|
ignore_{ignore},
|
|
isIgnored_{isIgnored},
|
|
wdHash_{wdHash} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
return diffAddedTree(context_, getPath(), wdHash_, ignore_, isIgnored_);
|
|
}
|
|
|
|
private:
|
|
const GitIgnoreStack* ignore_{nullptr};
|
|
bool isIgnored_{false};
|
|
Hash wdHash_;
|
|
};
|
|
|
|
class RemovedScmDiffEntry : public DeferredDiffEntry {
|
|
public:
|
|
RemovedScmDiffEntry(DiffContext* context, RelativePath path, Hash scmHash)
|
|
: DeferredDiffEntry{context, std::move(path)}, scmHash_{scmHash} {}
|
|
|
|
folly::Future<folly::Unit> run() override {
|
|
return diffRemovedTree(context_, getPath(), scmHash_);
|
|
}
|
|
|
|
private:
|
|
Hash scmHash_;
|
|
};
|
|
|
|
} // unnamed namespace
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createUntrackedEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
InodePtr inode,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<UntrackedDiffEntry>(
|
|
context, std::move(path), std::move(inode), ignore, isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry>
|
|
DeferredDiffEntry::createUntrackedEntryFromInodeFuture(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Future<InodePtr>&& inodeFuture,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<UntrackedDiffEntry>(
|
|
context, std::move(path), std::move(inodeFuture), ignore, isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createModifiedEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
InodePtr inode,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<ModifiedDiffEntry>(
|
|
context, std::move(path), scmEntry, std::move(inode), ignore, isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry>
|
|
DeferredDiffEntry::createModifiedEntryFromInodeFuture(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
folly::Future<InodePtr>&& inodeFuture,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<ModifiedDiffEntry>(
|
|
context,
|
|
std::move(path),
|
|
scmEntry,
|
|
std::move(inodeFuture),
|
|
ignore,
|
|
isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createModifiedEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
const TreeEntry& scmEntry,
|
|
Hash currentBlobHash) {
|
|
return make_unique<ModifiedBlobDiffEntry>(
|
|
context, std::move(path), scmEntry, currentBlobHash);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createModifiedScmEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Hash scmHash,
|
|
Hash wdHash,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<ModifiedScmDiffEntry>(
|
|
context, std::move(path), scmHash, wdHash, ignore, isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createAddedScmEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Hash wdHash,
|
|
const GitIgnoreStack* ignore,
|
|
bool isIgnored) {
|
|
return make_unique<AddedScmDiffEntry>(
|
|
context, std::move(path), wdHash, ignore, isIgnored);
|
|
}
|
|
|
|
unique_ptr<DeferredDiffEntry> DeferredDiffEntry::createRemovedScmEntry(
|
|
DiffContext* context,
|
|
RelativePath path,
|
|
Hash scmHash) {
|
|
return make_unique<RemovedScmDiffEntry>(context, std::move(path), scmHash);
|
|
}
|
|
|
|
} // namespace eden
|
|
} // namespace facebook
|