mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 14:28:17 +03:00
Flip Dirstate -> EdenMount dependency.
Summary: Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes pointers to a `MountPoint` and an `ObjectStore` because it does not need the entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create the `Dirstate` itself, but that will be done in a follow-up commit. Fortunately, it was pretty easy to remove the references to `edenMount_` in `Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`. The one thing that I also decided to move was `getModifiedDirectoriesForMount()` because I already needed to create an `EdenMounts` file (admittedly not a great name) to collect some utility functions that use members of an `EdenMount` while not having access to the `EdenMount` itself. As part of this change, all of the code in `eden/fs/model/hg` has been moved to `eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change the `Dirstate` from an Hg-specific concept to a more general concept. `LocalDirstatePersistence` is no longer one of two implementations of `DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is just one concrete implementation called `DirstatePersistence` that takes its implementation from `LocalDirstatePersistence`. Because there is no longer a `FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that uses a `TemporaryFile`. Because `TestMount` now takes responsibility for creating the `Dirstate`, it must also give callers the ability to specify the user directives. To that end, `TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a `getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated as part of this revision, but `DirstateTest` needed quite a few updates (which were generally mechanical). Reviewed By: simpkins Differential Revision: D4230154 fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
This commit is contained in:
parent
0a174e7128
commit
0f834ea809
@ -8,12 +8,16 @@
|
||||
*
|
||||
*/
|
||||
#include "Dirstate.h"
|
||||
#include <folly/Format.h>
|
||||
#include <folly/io/Cursor.h>
|
||||
#include <folly/io/IOBuf.h>
|
||||
#include "eden/fs/inodes/DirstatePersistence.h"
|
||||
#include "eden/fs/inodes/EdenMounts.h"
|
||||
#include "eden/fs/inodes/TreeEntryFileInode.h"
|
||||
#include "eden/fs/inodes/TreeInode.h"
|
||||
#include "eden/fs/model/Blob.h"
|
||||
#include "eden/fs/service/EdenMountHandler.h"
|
||||
#include "eden/fs/model/Tree.h"
|
||||
#include "eden/fs/store/ObjectStore.h"
|
||||
#include "eden/fs/store/ObjectStores.h"
|
||||
#include "eden/fuse/MountPoint.h"
|
||||
|
||||
@ -50,19 +54,19 @@ std::string HgStatus::toString() const {
|
||||
namespace {
|
||||
template <typename RelativePathType>
|
||||
void updateManifestWithDirectives(
|
||||
const std::unordered_map<RelativePathType, HgUserStatusDirective>*
|
||||
const std::unordered_map<RelativePathType, overlay::UserStatusDirective>*
|
||||
unaccountedUserDirectives,
|
||||
std::unordered_map<RelativePath, HgStatusCode>* manifest) {
|
||||
// We should make sure that every entry in userDirectives_ is accounted for in
|
||||
// the HgStatus that we return.
|
||||
for (auto& pair : *unaccountedUserDirectives) {
|
||||
switch (pair.second) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
// The file was marked for addition, but no longer exists in the working
|
||||
// copy. The user should either restore the file or run `hg forget`.
|
||||
manifest->emplace(RelativePath(pair.first), HgStatusCode::MISSING);
|
||||
break;
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
// The file was marked for removal, but it still exists in the working
|
||||
// copy without any modifications. Although it may seem strange, it
|
||||
// should still show up as REMOVED in `hg status` even though it is
|
||||
@ -74,11 +78,10 @@ void updateManifestWithDirectives(
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
std::unique_ptr<HgStatus> Dirstate::getStatus() const {
|
||||
// Find the modified directories in the overlay and compare them with what is
|
||||
// in the root tree.
|
||||
auto mountPoint = edenMount_->getMountPoint();
|
||||
auto modifiedDirectories = getModifiedDirectoriesForMount(edenMount_.get());
|
||||
auto modifiedDirectories = getModifiedDirectoriesForMount(mountPoint_);
|
||||
std::unordered_map<RelativePath, HgStatusCode> manifest;
|
||||
if (modifiedDirectories->empty()) {
|
||||
auto userDirectives = userDirectives_.rlock();
|
||||
@ -87,19 +90,18 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
}
|
||||
|
||||
auto userDirectives = userDirectives_.rlock();
|
||||
std::unordered_map<RelativePathPiece, HgUserStatusDirective>
|
||||
std::unordered_map<RelativePathPiece, overlay::UserStatusDirective>
|
||||
copyOfUserDirectives(userDirectives->begin(), userDirectives->end());
|
||||
|
||||
auto rootTree = edenMount_->getRootTree();
|
||||
auto objectStore = edenMount_->getObjectStore();
|
||||
auto rootTree = getRootTree();
|
||||
for (auto& directory : *modifiedDirectories) {
|
||||
// Get the directory as a TreeInode.
|
||||
auto dirInode = mountPoint->getDirInodeForPath(directory);
|
||||
auto dirInode = mountPoint_->getDirInodeForPath(directory);
|
||||
auto treeInode = std::dynamic_pointer_cast<TreeInode>(dirInode);
|
||||
DCHECK_NOTNULL(treeInode.get());
|
||||
|
||||
// Get the directory as a Tree.
|
||||
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
|
||||
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore_);
|
||||
DCHECK_NOTNULL(tree.get());
|
||||
|
||||
DirectoryDelta delta;
|
||||
@ -115,10 +117,10 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
if (result != userDirectives->end()) {
|
||||
auto statusCode = result->second;
|
||||
switch (statusCode) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
manifest.emplace(pathToEntry, HgStatusCode::ADDED);
|
||||
break;
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
// TODO(mbolin): Is there any weird sequence of modifications with
|
||||
// adding/removed files matched by .hgignore that could lead to this
|
||||
// state?
|
||||
@ -143,7 +145,7 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
if (result != userDirectives->end()) {
|
||||
auto statusCode = result->second;
|
||||
switch (statusCode) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
// TODO(mbolin): Is there any weird sequence of modifications with
|
||||
// adding/removed files matched by .hgignore that could lead to this
|
||||
// state?
|
||||
@ -151,7 +153,7 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
"Invariant violation: The user has marked {} for addition, "
|
||||
"but it already exists in the manifest.",
|
||||
pathToEntry.stringPiece()));
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
manifest.emplace(pathToEntry, HgStatusCode::REMOVED);
|
||||
break;
|
||||
}
|
||||
@ -171,7 +173,7 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
if (result != userDirectives->end()) {
|
||||
auto statusCode = result->second;
|
||||
switch (statusCode) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
// TODO(mbolin): Is there any weird sequence of modifications with
|
||||
// adding/removed files matched by .hgignore that could lead to this
|
||||
// state?
|
||||
@ -180,7 +182,7 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
"but it already exists in the manifest "
|
||||
"(and is currently removed from disk).",
|
||||
pathToEntry.stringPiece()));
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
manifest.emplace(pathToEntry, HgStatusCode::REMOVED);
|
||||
break;
|
||||
}
|
||||
@ -200,7 +202,7 @@ std::unique_ptr<HgStatus> Dirstate::getStatus() {
|
||||
bool hasMatchingAttributes(
|
||||
const TreeEntry* treeEntry,
|
||||
const TreeInode::Entry* treeInode,
|
||||
ObjectStore& objectStore,
|
||||
ObjectStore* objectStore,
|
||||
TreeInode& parent, // Has rlock
|
||||
const TreeInode::Dir& dir) {
|
||||
if (treeEntry->getMode() != treeInode->mode) {
|
||||
@ -218,7 +220,7 @@ bool hasMatchingAttributes(
|
||||
auto fileInode =
|
||||
std::dynamic_pointer_cast<TreeEntryFileInode>(overlayInode);
|
||||
auto overlaySHA1 = fileInode->getSHA1().get();
|
||||
auto blobSHA1 = objectStore.getSha1ForBlob(treeEntry->getHash());
|
||||
auto blobSHA1 = objectStore->getSha1ForBlob(treeEntry->getHash());
|
||||
return overlaySHA1 == *blobSHA1;
|
||||
} else {
|
||||
auto optionalHash = treeInode->hash;
|
||||
@ -270,7 +272,7 @@ void Dirstate::computeDelta(
|
||||
if (!hasMatchingAttributes(
|
||||
&base,
|
||||
overlayIterator->second.get(),
|
||||
*edenMount_->getObjectStore(),
|
||||
objectStore_,
|
||||
current,
|
||||
*dir)) {
|
||||
delta.modified.push_back(base.getName());
|
||||
@ -312,16 +314,16 @@ void Dirstate::add(RelativePathPiece path) {
|
||||
auto result = userDirectives->find(path.copy());
|
||||
if (result != userDirectives->end()) {
|
||||
switch (result->second) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
// No-op: already added.
|
||||
break;
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
userDirectives->erase(path.copy());
|
||||
persistence_->save(*userDirectives);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
(*userDirectives)[path.copy()] = HgUserStatusDirective::ADD;
|
||||
(*userDirectives)[path.copy()] = overlay::UserStatusDirective::Add;
|
||||
persistence_->save(*userDirectives);
|
||||
}
|
||||
}
|
||||
@ -337,7 +339,7 @@ bool shouldFileBeDeletedByHgRemove(
|
||||
RelativePathPiece file,
|
||||
std::shared_ptr<fusell::DirInode> parent,
|
||||
const TreeEntry* treeEntry,
|
||||
ObjectStore& objectStore) {
|
||||
ObjectStore* objectStore) {
|
||||
auto treeInode = std::dynamic_pointer_cast<TreeInode>(parent);
|
||||
if (treeInode == nullptr) {
|
||||
// The parent directory for the file is not in the overlay, so the file must
|
||||
@ -415,7 +417,7 @@ void Dirstate::remove(RelativePathPiece path, bool force) {
|
||||
// prefer not to do them while holding the lock.
|
||||
std::shared_ptr<fusell::DirInode> parent;
|
||||
try {
|
||||
parent = edenMount_->getMountPoint()->getDirInodeForPath(path.dirname());
|
||||
parent = mountPoint_->getDirInodeForPath(path.dirname());
|
||||
} catch (const std::system_error& e) {
|
||||
auto value = e.code().value();
|
||||
if (value == ENOENT || value == ENOTDIR) {
|
||||
@ -434,8 +436,7 @@ void Dirstate::remove(RelativePathPiece path, bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
auto entry = getEntryForFile(
|
||||
path, edenMount_->getRootTree().get(), edenMount_->getObjectStore());
|
||||
auto entry = getEntryForFile(path, getRootTree().get(), objectStore_);
|
||||
|
||||
auto shouldDelete = false;
|
||||
{
|
||||
@ -458,17 +459,17 @@ void Dirstate::remove(RelativePathPiece path, bool force) {
|
||||
// the file has been modified, so we must perform this check before
|
||||
// updating userDirectives.
|
||||
shouldDelete = shouldFileBeDeletedByHgRemove(
|
||||
path, parent, entry.get(), *edenMount_->getObjectStore());
|
||||
path, parent, entry.get(), objectStore_);
|
||||
}
|
||||
}
|
||||
(*userDirectives)[path.copy()] = HgUserStatusDirective::REMOVE;
|
||||
(*userDirectives)[path.copy()] = overlay::UserStatusDirective::Remove;
|
||||
persistence_->save(*userDirectives);
|
||||
} else {
|
||||
switch (result->second) {
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
case overlay::UserStatusDirective::Remove:
|
||||
// No-op: already removed.
|
||||
break;
|
||||
case HgUserStatusDirective::ADD:
|
||||
case overlay::UserStatusDirective::Add:
|
||||
if (inode != nullptr) {
|
||||
throw std::runtime_error(folly::sformat(
|
||||
"not removing {}: file has been marked for add "
|
||||
@ -484,7 +485,7 @@ void Dirstate::remove(RelativePathPiece path, bool force) {
|
||||
}
|
||||
|
||||
if (shouldDelete) {
|
||||
auto dispatcher = edenMount_->getMountPoint()->getDispatcher();
|
||||
auto dispatcher = mountPoint_->getDispatcher();
|
||||
try {
|
||||
dispatcher->unlink(parent->getNodeId(), path.basename()).get();
|
||||
} catch (const std::system_error& e) {
|
||||
@ -496,6 +497,10 @@ void Dirstate::remove(RelativePathPiece path, bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Tree> Dirstate::getRootTree() const {
|
||||
return getRootTreeForMountPoint(mountPoint_, objectStore_);
|
||||
}
|
||||
|
||||
const std::string kStatusCodeCharClean = "C";
|
||||
const std::string kStatusCodeCharModified = "M";
|
||||
const std::string kStatusCodeCharAdded = "A";
|
@ -9,9 +9,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include <folly/Synchronized.h>
|
||||
#include "eden/fs/inodes/EdenMount.h"
|
||||
#include "eden/fs/model/Tree.h"
|
||||
#include "eden/fs/store/ObjectStore.h"
|
||||
#include "eden/fs/inodes/DirstatePersistence.h"
|
||||
#include "eden/fs/inodes/gen-cpp2/overlay_types.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
namespace {
|
||||
@ -21,16 +20,13 @@ class DirectoryDelta;
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
class ObjectStore;
|
||||
class Tree;
|
||||
class TreeInode;
|
||||
|
||||
/**
|
||||
* Type of change to the manifest that the user has specified for a particular
|
||||
* file that will apply on the next commit.
|
||||
*/
|
||||
enum class HgUserStatusDirective {
|
||||
ADD,
|
||||
REMOVE,
|
||||
};
|
||||
namespace fusell {
|
||||
class MountPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -95,14 +91,6 @@ class HgStatus {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const HgStatus& status);
|
||||
|
||||
class DirstatePersistence {
|
||||
public:
|
||||
virtual ~DirstatePersistence() {}
|
||||
virtual void save(
|
||||
const std::unordered_map<RelativePath, HgUserStatusDirective>&
|
||||
userDirectives) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is designed to be a simple implemenation of an Hg dirstate. It's
|
||||
* "simple" in that every call to `getStatus()` walks the entire overlay to
|
||||
@ -126,22 +114,26 @@ class DirstatePersistence {
|
||||
class Dirstate {
|
||||
public:
|
||||
Dirstate(
|
||||
std::shared_ptr<EdenMount> edenMount,
|
||||
fusell::MountPoint* mountPoint,
|
||||
ObjectStore* objectStore,
|
||||
std::unique_ptr<DirstatePersistence> persistence,
|
||||
const std::unordered_map<RelativePath, HgUserStatusDirective>*
|
||||
const std::unordered_map<RelativePath, overlay::UserStatusDirective>*
|
||||
userDirectives)
|
||||
: userDirectives_(*userDirectives),
|
||||
edenMount_(std::move(edenMount)),
|
||||
persistence_(std::move(persistence)) {}
|
||||
: mountPoint_(mountPoint),
|
||||
objectStore_(objectStore),
|
||||
persistence_(std::move(persistence)),
|
||||
userDirectives_(*userDirectives) {}
|
||||
|
||||
Dirstate(
|
||||
std::shared_ptr<EdenMount> edenMount,
|
||||
fusell::MountPoint* mountPoint,
|
||||
ObjectStore* objectStore,
|
||||
std::unique_ptr<DirstatePersistence> persistence)
|
||||
: edenMount_(std::move(edenMount)),
|
||||
: mountPoint_(mountPoint),
|
||||
objectStore_(objectStore),
|
||||
persistence_(std::move(persistence)) {}
|
||||
|
||||
/** Analogous to calling `hg status`. */
|
||||
std::unique_ptr<HgStatus> getStatus();
|
||||
std::unique_ptr<HgStatus> getStatus() const;
|
||||
|
||||
/**
|
||||
* Analogous to `hg add <path>` where `<path>` is an ordinary file or symlink.
|
||||
@ -159,15 +151,19 @@ class Dirstate {
|
||||
TreeInode& current,
|
||||
DirectoryDelta& delta) const;
|
||||
|
||||
std::unique_ptr<Tree> getRootTree() const;
|
||||
|
||||
fusell::MountPoint* mountPoint_;
|
||||
ObjectStore* objectStore_;
|
||||
std::unique_ptr<DirstatePersistence> persistence_;
|
||||
/**
|
||||
* Manifest of files in the working copy whose status is not CLEAN. These are
|
||||
* also referred to as "nonnormal" files.
|
||||
* TODO(mbolin): Consider StringKeyedMap instead of unordered_map.
|
||||
*/
|
||||
folly::Synchronized<std::unordered_map<RelativePath, HgUserStatusDirective>>
|
||||
folly::Synchronized<
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>>
|
||||
userDirectives_;
|
||||
std::shared_ptr<EdenMount> edenMount_;
|
||||
std::unique_ptr<DirstatePersistence> persistence_;
|
||||
};
|
||||
}
|
||||
}
|
58
eden/fs/inodes/DirstatePersistence.cpp
Normal file
58
eden/fs/inodes/DirstatePersistence.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
#include "DirstatePersistence.h"
|
||||
#include <folly/FileUtil.h>
|
||||
#include <thrift/lib/cpp2/protocol/Serializer.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
using apache::thrift::CompactSerializer;
|
||||
|
||||
void DirstatePersistence::save(
|
||||
const std::unordered_map<RelativePath, overlay::UserStatusDirective>&
|
||||
userDirectives) {
|
||||
overlay::DirstateData dirstateData;
|
||||
std::map<std::string, overlay::UserStatusDirective> directives;
|
||||
for (auto& pair : userDirectives) {
|
||||
directives[pair.first.stringPiece().str()] = pair.second;
|
||||
}
|
||||
dirstateData.directives = directives;
|
||||
auto serializedData = CompactSerializer::serialize<std::string>(dirstateData);
|
||||
auto wrote = folly::writeFile(serializedData, storageFile_.c_str());
|
||||
|
||||
if (!wrote) {
|
||||
throw std::runtime_error(folly::to<std::string>(
|
||||
"Failed to persist Dirstate to file ", storageFile_));
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>
|
||||
DirstatePersistence::load() {
|
||||
std::string serializedData;
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective> entries;
|
||||
if (!folly::readFile(storageFile_.c_str(), serializedData)) {
|
||||
int err = errno;
|
||||
if (err == ENOENT) {
|
||||
return entries;
|
||||
}
|
||||
folly::throwSystemErrorExplicit(err, "failed to read ", storageFile_);
|
||||
}
|
||||
|
||||
auto dirstateData =
|
||||
CompactSerializer::deserialize<overlay::DirstateData>(serializedData);
|
||||
for (auto& pair : dirstateData.directives) {
|
||||
entries[RelativePath(pair.first)] = pair.second;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,30 +9,30 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "eden/fs/model/hg/Dirstate.h"
|
||||
#include <unordered_map>
|
||||
#include "eden/fs/inodes/gen-cpp2/overlay_types.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
/**
|
||||
* Implementation of DirstatePersistence that persists data to a local file.
|
||||
* Persists dirstate data to a local file.
|
||||
*/
|
||||
class LocalDirstatePersistence : public DirstatePersistence {
|
||||
class DirstatePersistence {
|
||||
public:
|
||||
explicit LocalDirstatePersistence(AbsolutePathPiece storageFile)
|
||||
explicit DirstatePersistence(AbsolutePathPiece storageFile)
|
||||
: storageFile_(storageFile) {}
|
||||
|
||||
virtual ~LocalDirstatePersistence() {}
|
||||
|
||||
void save(const std::unordered_map<RelativePath, HgUserStatusDirective>&
|
||||
userDirectives) override;
|
||||
void save(
|
||||
const std::unordered_map<RelativePath, overlay::UserStatusDirective>&
|
||||
userDirectives);
|
||||
|
||||
/**
|
||||
* If the underlying storage file does not exist, then this returns an empty
|
||||
* map.
|
||||
*/
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective> load();
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective> load();
|
||||
|
||||
private:
|
||||
AbsolutePath storageFile_;
|
@ -11,8 +11,9 @@
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "Overlay.h"
|
||||
#include "eden/fs/config/ClientConfig.h"
|
||||
#include "eden/fs/inodes/EdenMounts.h"
|
||||
#include "eden/fs/inodes/Overlay.h"
|
||||
#include "eden/fs/model/Tree.h"
|
||||
#include "eden/fs/store/ObjectStore.h"
|
||||
#include "eden/fuse/MountPoint.h"
|
||||
@ -77,14 +78,7 @@ const vector<BindMount>& EdenMount::getBindMounts() const {
|
||||
}
|
||||
|
||||
std::unique_ptr<Tree> EdenMount::getRootTree() const {
|
||||
auto rootAsDirInode = mountPoint_->getRootInode();
|
||||
auto rootAsTreeInode = std::dynamic_pointer_cast<TreeInode>(rootAsDirInode);
|
||||
{
|
||||
auto dir = rootAsTreeInode->getContents().rlock();
|
||||
auto& rootTreeHash = dir->treeHash.value();
|
||||
auto tree = getObjectStore()->getTree(rootTreeHash);
|
||||
return tree;
|
||||
}
|
||||
return getRootTreeForMountPoint(mountPoint_.get(), getObjectStore());
|
||||
}
|
||||
}
|
||||
} // facebook::eden
|
||||
|
81
eden/fs/inodes/EdenMounts.cpp
Normal file
81
eden/fs/inodes/EdenMounts.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EdenMounts.h"
|
||||
#include "eden/fs/inodes/TreeInode.h"
|
||||
#include "eden/fs/model/Tree.h"
|
||||
#include "eden/fs/store/ObjectStore.h"
|
||||
#include "eden/fuse/MountPoint.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
std::unique_ptr<Tree> getRootTreeForMountPoint(
|
||||
fusell::MountPoint* mountPoint,
|
||||
ObjectStore* objectStore) {
|
||||
auto rootAsDirInode = mountPoint->getRootInode();
|
||||
auto rootAsTreeInode = std::dynamic_pointer_cast<TreeInode>(rootAsDirInode);
|
||||
{
|
||||
auto dir = rootAsTreeInode->getContents().rlock();
|
||||
auto& rootTreeHash = dir->treeHash.value();
|
||||
auto tree = objectStore->getTree(rootTreeHash);
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
|
||||
void getModifiedDirectoriesRecursive(
|
||||
RelativePathPiece dirPath,
|
||||
TreeInode* dir,
|
||||
std::vector<RelativePath>* modifiedDirectories) {
|
||||
dir->getContents().withRLock([&](const auto& contents) mutable {
|
||||
if (!contents.materialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifiedDirectories->push_back(dirPath.copy());
|
||||
for (auto& entIter : contents.entries) {
|
||||
const auto& ent = entIter.second;
|
||||
if (S_ISDIR(ent->mode) && ent->materialized) {
|
||||
const auto& name = entIter.first;
|
||||
auto childInode = dir->lookupChildByNameLocked(&contents, name);
|
||||
auto childPath = dirPath + name;
|
||||
auto childDir = std::dynamic_pointer_cast<TreeInode>(childInode);
|
||||
DCHECK(childDir->getContents().rlock()->materialized)
|
||||
<< (dirPath + name) << " entry " << ent.get()
|
||||
<< " materialized is true, but the contained dir is !materialized";
|
||||
|
||||
getModifiedDirectoriesRecursive(
|
||||
childPath, childDir.get(), modifiedDirectories);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This function is not a method of MountPoint because it has a dependency on
|
||||
// TreeInode. If MountPoint depended on TreeInode, it would create a circular
|
||||
// dependency, which is why this function lives here.
|
||||
std::unique_ptr<std::vector<RelativePath>> getModifiedDirectoriesForMount(
|
||||
fusell::MountPoint* mountPoint) {
|
||||
auto inodeDispatcher = mountPoint->getDispatcher();
|
||||
auto rootInode = inodeDispatcher->getDirInode(FUSE_ROOT_ID);
|
||||
auto treeInode = std::dynamic_pointer_cast<TreeInode>(rootInode);
|
||||
if (treeInode) {
|
||||
auto modifiedDirectories = std::make_unique<std::vector<RelativePath>>();
|
||||
getModifiedDirectoriesRecursive(
|
||||
RelativePathPiece(), treeInode.get(), modifiedDirectories.get());
|
||||
return modifiedDirectories;
|
||||
} else {
|
||||
throw std::runtime_error(folly::to<std::string>(
|
||||
"Could not find root TreeInode for ", mountPoint->getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
eden/fs/inodes/EdenMounts.h
Normal file
41
eden/fs/inodes/EdenMounts.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
/**
|
||||
* Utility functions for use with various members of EdenMount.
|
||||
*/
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
class ObjectStore;
|
||||
class Tree;
|
||||
|
||||
namespace fusell {
|
||||
class MountPoint;
|
||||
}
|
||||
|
||||
std::unique_ptr<Tree> getRootTreeForMountPoint(
|
||||
fusell::MountPoint* mountPoint,
|
||||
ObjectStore* objectStore);
|
||||
|
||||
/**
|
||||
* @return vector with the RelativePath of every directory that is modified
|
||||
* according to the overlay in the mount. The vector will be ordered as a
|
||||
* depth-first traversal of the overlay.
|
||||
*/
|
||||
std::unique_ptr<std::vector<RelativePath>> getModifiedDirectoriesForMount(
|
||||
fusell::MountPoint* mountPoint);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ cpp_library(
|
||||
'@/eden/fs/journal:journal',
|
||||
'@/eden/fs/store:store',
|
||||
'@/eden/fuse:fusell',
|
||||
'@/eden/utils:utils',
|
||||
':serialization-cpp2',
|
||||
],
|
||||
external_deps = [
|
||||
|
@ -32,3 +32,12 @@ struct OverlayData {
|
||||
// fit in memory and thus that this won't be too big to work with.
|
||||
1: map<RelativePath, OverlayDir> localDirs
|
||||
}
|
||||
|
||||
enum UserStatusDirective {
|
||||
Add = 0x0,
|
||||
Remove = 0x1,
|
||||
}
|
||||
|
||||
struct DirstateData {
|
||||
1: map<RelativePath, UserStatusDirective> directives
|
||||
}
|
||||
|
@ -11,56 +11,58 @@
|
||||
#include <folly/experimental/TestUtil.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <thrift/lib/cpp2/protocol/Serializer.h>
|
||||
#include "eden/fs/model/hg/LocalDirstatePersistence.h"
|
||||
#include "eden/fs/model/hg/gen-cpp2/dirstate_types.h"
|
||||
#include "eden/fs/inodes/DirstatePersistence.h"
|
||||
#include "eden/fs/inodes/gen-cpp2/overlay_types.h"
|
||||
|
||||
using namespace facebook::eden;
|
||||
using apache::thrift::CompactSerializer;
|
||||
using folly::test::TemporaryFile;
|
||||
|
||||
TEST(LocalDirstatePersistence, saveAndReadDirectivesBackOut) {
|
||||
TEST(DirstatePersistence, saveAndReadDirectivesBackOut) {
|
||||
TemporaryFile storageFile;
|
||||
|
||||
AbsolutePath storageFilePath(storageFile.path().c_str());
|
||||
LocalDirstatePersistence persistence(storageFilePath);
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective> userDirectives = {
|
||||
{RelativePath("add.txt"), HgUserStatusDirective::ADD},
|
||||
{RelativePath("remove.txt"), HgUserStatusDirective::REMOVE},
|
||||
};
|
||||
DirstatePersistence persistence(storageFilePath);
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>
|
||||
userDirectives = {
|
||||
{RelativePath("add.txt"), overlay::UserStatusDirective::Add},
|
||||
{RelativePath("remove.txt"), overlay::UserStatusDirective::Remove},
|
||||
};
|
||||
persistence.save(userDirectives);
|
||||
|
||||
auto rehydratedDirectives = persistence.load();
|
||||
EXPECT_EQ(userDirectives, rehydratedDirectives);
|
||||
}
|
||||
|
||||
TEST(LocalDirstatePersistence, loadFromFileWithWellFormattedData) {
|
||||
TEST(DirstatePersistence, loadFromFileWithWellFormattedData) {
|
||||
TemporaryFile storageFile;
|
||||
|
||||
dirstate::DirstateData dirstateData;
|
||||
overlay::DirstateData dirstateData;
|
||||
dirstateData.directives = {
|
||||
{"add.txt", dirstate::HgUserStatusDirectiveValue::Add},
|
||||
{"remove.txt", dirstate::HgUserStatusDirectiveValue::Remove}};
|
||||
{"add.txt", overlay::UserStatusDirective::Add},
|
||||
{"remove.txt", overlay::UserStatusDirective::Remove}};
|
||||
auto serializedData = CompactSerializer::serialize<std::string>(dirstateData);
|
||||
folly::writeFull(
|
||||
storageFile.fd(), serializedData.data(), serializedData.size());
|
||||
|
||||
AbsolutePath storageFilePath(storageFile.path().c_str());
|
||||
LocalDirstatePersistence persistence(storageFilePath);
|
||||
DirstatePersistence persistence(storageFilePath);
|
||||
auto directives = persistence.load();
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective> expectedDirectives = {
|
||||
{RelativePath("add.txt"), HgUserStatusDirective::ADD},
|
||||
{RelativePath("remove.txt"), HgUserStatusDirective::REMOVE},
|
||||
};
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>
|
||||
expectedDirectives = {
|
||||
{RelativePath("add.txt"), overlay::UserStatusDirective::Add},
|
||||
{RelativePath("remove.txt"), overlay::UserStatusDirective::Remove},
|
||||
};
|
||||
EXPECT_EQ(expectedDirectives, directives);
|
||||
}
|
||||
|
||||
TEST(LocalDirstatePersistence, attemptLoadFromNonExistentFile) {
|
||||
TEST(DirstatePersistence, attemptLoadFromNonExistentFile) {
|
||||
AbsolutePath storageFilePath;
|
||||
{
|
||||
TemporaryFile storageFile;
|
||||
storageFilePath = AbsolutePath(storageFile.path().c_str());
|
||||
}
|
||||
LocalDirstatePersistence persistence(storageFilePath);
|
||||
DirstatePersistence persistence(storageFilePath);
|
||||
auto directives = persistence.load();
|
||||
EXPECT_EQ(0, directives.size());
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "eden/fs/model/hg/Dirstate.h"
|
||||
#include "eden/fs/inodes/Dirstate.h"
|
||||
#include "eden/fs/testharness/TestMount.h"
|
||||
|
||||
using namespace facebook::eden;
|
||||
@ -36,27 +36,19 @@ TEST(HgStatus, toString) {
|
||||
hgStatus.toString());
|
||||
}
|
||||
|
||||
class FakeDirstatePeristence : public DirstatePersistence {
|
||||
public:
|
||||
virtual ~FakeDirstatePeristence() {}
|
||||
void save(
|
||||
const std::unordered_map<RelativePath, HgUserStatusDirective>&) override {
|
||||
}
|
||||
};
|
||||
|
||||
void verifyExpectedDirstate(
|
||||
Dirstate& dirstate,
|
||||
const Dirstate* dirstate,
|
||||
std::unordered_map<std::string, HgStatusCode>&& statuses) {
|
||||
std::unordered_map<RelativePath, HgStatusCode> expected;
|
||||
for (auto& pair : statuses) {
|
||||
expected.emplace(RelativePath(pair.first), pair.second);
|
||||
}
|
||||
auto expectedStatus = HgStatus(std::move(expected));
|
||||
EXPECT_EQ(expectedStatus, *dirstate.getStatus());
|
||||
EXPECT_EQ(expectedStatus, *dirstate->getStatus().get());
|
||||
}
|
||||
|
||||
void verifyEmptyDirstate(Dirstate& dirstate) {
|
||||
auto status = dirstate.getStatus();
|
||||
void verifyEmptyDirstate(const Dirstate* dirstate) {
|
||||
auto status = dirstate->getStatus();
|
||||
EXPECT_EQ(0, status->size()) << "Expected dirstate to be empty.";
|
||||
}
|
||||
|
||||
@ -64,26 +56,23 @@ TEST(Dirstate, createDirstate) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
auto dirstate = testMount->getDirstate();
|
||||
verifyEmptyDirstate(dirstate);
|
||||
}
|
||||
|
||||
TEST(Dirstate, createDirstateWithInitialState) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"removed.txt", "nada"});
|
||||
builder.addUserDirectives({
|
||||
{RelativePath("deleted.txt"), overlay::UserStatusDirective::Remove},
|
||||
{RelativePath("missing.txt"), overlay::UserStatusDirective::Add},
|
||||
{RelativePath("newfile.txt"), overlay::UserStatusDirective::Add},
|
||||
{RelativePath("removed.txt"), overlay::UserStatusDirective::Remove},
|
||||
});
|
||||
auto testMount = builder.build();
|
||||
testMount->addFile("newfile.txt", "legitimate add");
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective> userDirectives{
|
||||
{RelativePath("deleted.txt"), HgUserStatusDirective::REMOVE},
|
||||
{RelativePath("missing.txt"), HgUserStatusDirective::ADD},
|
||||
{RelativePath("newfile.txt"), HgUserStatusDirective::ADD},
|
||||
{RelativePath("removed.txt"), HgUserStatusDirective::REMOVE},
|
||||
};
|
||||
Dirstate dirstate(
|
||||
testMount->getEdenMount(), std::move(persistence), &userDirectives);
|
||||
auto dirstate = testMount->getDirstate();
|
||||
verifyExpectedDirstate(
|
||||
dirstate,
|
||||
{
|
||||
@ -97,36 +86,30 @@ TEST(Dirstate, createDirstateWithInitialState) {
|
||||
TEST(Dirstate, createDirstateWithUntrackedFile) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
testMount->addFile("hello.txt", "some contents");
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::NOT_TRACKED}});
|
||||
}
|
||||
|
||||
TEST(Dirstate, createDirstateWithAddedFile) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
testMount->addFile("hello.txt", "some contents");
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
dirstate.add(RelativePathPiece("hello.txt"));
|
||||
|
||||
dirstate->add(RelativePathPiece("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
|
||||
}
|
||||
|
||||
TEST(Dirstate, createDirstateWithMissingFile) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
testMount->addFile("hello.txt", "some contents");
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
dirstate.add(RelativePathPiece("hello.txt"));
|
||||
dirstate->add(RelativePathPiece("hello.txt"));
|
||||
testMount->deleteFile("hello.txt");
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::MISSING}});
|
||||
}
|
||||
|
||||
@ -134,11 +117,9 @@ TEST(Dirstate, createDirstateWithModifiedFileContents) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "some contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->overwriteFile("hello.txt", "other contents");
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::MODIFIED}});
|
||||
}
|
||||
|
||||
@ -146,11 +127,9 @@ TEST(Dirstate, createDirstateWithTouchedFile) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "some contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->overwriteFile("hello.txt", "some contents");
|
||||
|
||||
// Although the file has been written, it has not changed in any significant
|
||||
// way.
|
||||
verifyEmptyDirstate(dirstate);
|
||||
@ -160,10 +139,9 @@ TEST(Dirstate, createDirstateWithFileAndThenHgRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "some contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
EXPECT_FALSE(testMount->hasFileAt("hello.txt"));
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
@ -173,11 +151,10 @@ TEST(Dirstate, createDirstateWithFileRemoveItAndThenHgRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "some contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->deleteFile("hello.txt");
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
}
|
||||
@ -186,13 +163,12 @@ TEST(Dirstate, createDirstateWithFileTouchItAndThenHgRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "original contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->overwriteFile("hello.txt", "some other contents");
|
||||
|
||||
try {
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
FAIL() << "Should error when trying to remove a modified file.";
|
||||
} catch (const std::runtime_error& e) {
|
||||
EXPECT_STREQ(
|
||||
@ -201,7 +177,7 @@ TEST(Dirstate, createDirstateWithFileTouchItAndThenHgRemoveIt) {
|
||||
}
|
||||
|
||||
testMount->overwriteFile("hello.txt", "original contents");
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
EXPECT_FALSE(testMount->hasFileAt("hello.txt"));
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
@ -211,14 +187,11 @@ TEST(Dirstate, createDirstateWithFileModifyItAndThenHgForceRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "original contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->overwriteFile("hello.txt", "some other contents");
|
||||
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ true);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ true);
|
||||
EXPECT_FALSE(testMount->hasFileAt("hello.txt"));
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
}
|
||||
|
||||
@ -226,16 +199,14 @@ TEST(Dirstate, ensureSubsequentCallsToHgRemoveHaveNoEffect) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "original contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
EXPECT_FALSE(testMount->hasFileAt("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
|
||||
// Calling `hg remove` again should have no effect and not throw any errors.
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
EXPECT_FALSE(testMount->hasFileAt("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
|
||||
@ -246,7 +217,7 @@ TEST(Dirstate, ensureSubsequentCallsToHgRemoveHaveNoEffect) {
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
|
||||
// Calling `hg remove` again should have no effect and not throw any errors.
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
EXPECT_TRUE(testMount->hasFileAt("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::REMOVED}});
|
||||
}
|
||||
@ -254,34 +225,30 @@ TEST(Dirstate, ensureSubsequentCallsToHgRemoveHaveNoEffect) {
|
||||
TEST(Dirstate, createDirstateHgAddFileRemoveItThenHgRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
testMount->addFile("hello.txt", "I will be added.");
|
||||
dirstate.add(RelativePathPiece("hello.txt"));
|
||||
dirstate->add(RelativePathPiece("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
|
||||
|
||||
testMount->deleteFile("hello.txt");
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::MISSING}});
|
||||
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
verifyEmptyDirstate(dirstate);
|
||||
}
|
||||
|
||||
TEST(Dirstate, createDirstateHgAddFileThenHgRemoveIt) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
testMount->addFile("hello.txt", "I will be added.");
|
||||
dirstate.add(RelativePathPiece("hello.txt"));
|
||||
dirstate->add(RelativePathPiece("hello.txt"));
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
|
||||
|
||||
try {
|
||||
dirstate.remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
dirstate->remove(RelativePathPiece("hello.txt"), /* force */ false);
|
||||
FAIL() << "Should error when trying to remove a file scheduled for add.";
|
||||
} catch (const std::runtime_error& e) {
|
||||
EXPECT_STREQ(
|
||||
@ -289,7 +256,6 @@ TEST(Dirstate, createDirstateHgAddFileThenHgRemoveIt) {
|
||||
"(use 'hg forget' to undo add)",
|
||||
e.what());
|
||||
}
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
|
||||
}
|
||||
|
||||
@ -297,10 +263,8 @@ TEST(Dirstate, createDirstateWithFileAndThenDeleteItWithoutCallingHgRemove) {
|
||||
TestMountBuilder builder;
|
||||
builder.addFile({"hello.txt", "some contents"});
|
||||
auto testMount = builder.build();
|
||||
auto dirstate = testMount->getDirstate();
|
||||
|
||||
auto persistence = std::make_unique<FakeDirstatePeristence>();
|
||||
Dirstate dirstate(testMount->getEdenMount(), std::move(persistence));
|
||||
testMount->deleteFile("hello.txt");
|
||||
|
||||
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::MISSING}});
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
#include "eden/fs/service/EdenMountHandler.h"
|
||||
#include "eden/fs/inodes/EdenMounts.h"
|
||||
#include "eden/fs/testharness/TestMount.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
@ -19,7 +19,8 @@ TEST(EdenMountHandler, getModifiedDirectoriesForMountWithNoModifications) {
|
||||
TestMountBuilder builder;
|
||||
auto testMount = builder.build();
|
||||
auto edenMount = testMount->getEdenMount();
|
||||
auto modifiedDirectories = getModifiedDirectoriesForMount(edenMount.get());
|
||||
auto modifiedDirectories =
|
||||
getModifiedDirectoriesForMount(edenMount->getMountPoint());
|
||||
std::vector<RelativePath> expected = {};
|
||||
EXPECT_EQ(expected, *modifiedDirectories.get());
|
||||
}
|
||||
@ -48,7 +49,8 @@ TEST(EdenMountHandler, getModifiedDirectoriesForMount) {
|
||||
testMount->addFile("a/b/c/file.txt", "");
|
||||
|
||||
auto edenMount = testMount->getEdenMount();
|
||||
auto modifiedDirectories = getModifiedDirectoriesForMount(edenMount.get());
|
||||
auto modifiedDirectories =
|
||||
getModifiedDirectoriesForMount(edenMount->getMountPoint());
|
||||
|
||||
std::vector<RelativePath> expected = {
|
||||
RelativePath(),
|
@ -3,7 +3,7 @@ cpp_unittest(
|
||||
headers = AutoHeaders.RECURSIVE_GLOB, # https://fburl.com/424819295
|
||||
srcs = glob(['*Test.cpp']),
|
||||
deps = [
|
||||
'//eden/fs/service:EdenMountHandler',
|
||||
'//eden/fs/inodes:inodes',
|
||||
'//eden/fs/testharness:testharness',
|
||||
'//eden/utils:utils',
|
||||
],
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
#include "eden/fs/model/hg/LocalDirstatePersistence.h"
|
||||
#include <folly/FileUtil.h>
|
||||
#include <thrift/lib/cpp2/protocol/Serializer.h>
|
||||
#include "eden/fs/model/hg/gen-cpp2/dirstate_types.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
using apache::thrift::CompactSerializer;
|
||||
|
||||
void LocalDirstatePersistence::save(
|
||||
const std::unordered_map<RelativePath, HgUserStatusDirective>&
|
||||
userDirectives) {
|
||||
dirstate::DirstateData dirstateData;
|
||||
std::map<std::string, dirstate::HgUserStatusDirectiveValue> directives;
|
||||
for (auto& pair : userDirectives) {
|
||||
dirstate::HgUserStatusDirectiveValue value;
|
||||
switch (pair.second) {
|
||||
case HgUserStatusDirective::ADD:
|
||||
value = dirstate::HgUserStatusDirectiveValue::Add;
|
||||
break;
|
||||
case HgUserStatusDirective::REMOVE:
|
||||
value = dirstate::HgUserStatusDirectiveValue::Remove;
|
||||
break;
|
||||
}
|
||||
directives[pair.first.stringPiece().str()] = value;
|
||||
}
|
||||
dirstateData.directives = directives;
|
||||
auto serializedData = CompactSerializer::serialize<std::string>(dirstateData);
|
||||
auto wrote = folly::writeFile(serializedData, storageFile_.c_str());
|
||||
|
||||
if (!wrote) {
|
||||
throw std::runtime_error(folly::to<std::string>(
|
||||
"Failed to persist Dirstate to file ", storageFile_));
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective>
|
||||
LocalDirstatePersistence::load() {
|
||||
std::string serializedData;
|
||||
std::unordered_map<RelativePath, HgUserStatusDirective> entries;
|
||||
if (!folly::readFile(storageFile_.c_str(), serializedData)) {
|
||||
int err = errno;
|
||||
if (err == ENOENT) {
|
||||
return entries;
|
||||
}
|
||||
folly::throwSystemErrorExplicit(err, "failed to read ", storageFile_);
|
||||
}
|
||||
|
||||
auto dirstateData =
|
||||
CompactSerializer::deserialize<dirstate::DirstateData>(serializedData);
|
||||
for (auto& pair : dirstateData.directives) {
|
||||
HgUserStatusDirective directive;
|
||||
switch (pair.second) {
|
||||
case dirstate::HgUserStatusDirectiveValue::Add:
|
||||
directive = HgUserStatusDirective::ADD;
|
||||
break;
|
||||
case dirstate::HgUserStatusDirectiveValue::Remove:
|
||||
directive = HgUserStatusDirective::REMOVE;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"Unexpected value loaded for HgUserStatusDirectiveValue");
|
||||
}
|
||||
entries[RelativePath(pair.first)] = directive;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
thrift_library(
|
||||
name = 'serialization',
|
||||
thrift_args = ['--strict'],
|
||||
thrift_srcs = {
|
||||
'dirstate.thrift': [],
|
||||
},
|
||||
languages = ['cpp2'],
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
name = 'hg',
|
||||
srcs = glob(['*.cpp']),
|
||||
headers = glob(['*.h']),
|
||||
deps = [
|
||||
':serialization-cpp2',
|
||||
'//eden/fs/inodes:inodes',
|
||||
'//eden/fs/model:model',
|
||||
'//eden/fs/service:EdenMountHandler',
|
||||
'//eden/fs/store:store',
|
||||
'//eden/utils:utils',
|
||||
'//folly:folly',
|
||||
],
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
namespace cpp2 facebook.eden.dirstate
|
||||
|
||||
typedef string RelativePath
|
||||
|
||||
enum HgUserStatusDirectiveValue {
|
||||
Add = 0x0,
|
||||
Remove = 0x1,
|
||||
}
|
||||
|
||||
struct DirstateData {
|
||||
1: map<RelativePath, HgUserStatusDirectiveValue> directives
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
cpp_unittest(
|
||||
name = 'test',
|
||||
headers = AutoHeaders.RECURSIVE_GLOB, # https://fburl.com/424819295
|
||||
srcs = glob(['*Test.cpp']),
|
||||
deps = [
|
||||
'//eden/fs/model/hg:hg',
|
||||
'//eden/fs/store/testutil:testutil',
|
||||
'//eden/fs/testharness:testharness',
|
||||
'//eden/utils:utils',
|
||||
],
|
||||
external_deps = [
|
||||
'gtest',
|
||||
],
|
||||
)
|
@ -13,6 +13,7 @@
|
||||
#include "eden/fs/inodes/EdenMount.h"
|
||||
#include "eden/fs/inodes/TreeEntryFileInode.h"
|
||||
#include "eden/fs/inodes/TreeInode.h"
|
||||
#include "eden/fs/service/gen-cpp2/eden_types.h"
|
||||
#include "eden/fuse/MountPoint.h"
|
||||
#include "eden/fuse/fuse_headers.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
@ -103,49 +104,5 @@ void getMaterializedEntriesRecursive(
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void getModifiedDirectoriesRecursive(
|
||||
RelativePathPiece dirPath,
|
||||
TreeInode* dir,
|
||||
std::vector<RelativePath>* modifiedDirectories) {
|
||||
dir->getContents().withRLock([&](const auto& contents) mutable {
|
||||
if (!contents.materialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
modifiedDirectories->push_back(dirPath.copy());
|
||||
for (auto& entIter : contents.entries) {
|
||||
const auto& ent = entIter.second;
|
||||
if (S_ISDIR(ent->mode) && ent->materialized) {
|
||||
const auto& name = entIter.first;
|
||||
auto childInode = dir->lookupChildByNameLocked(&contents, name);
|
||||
auto childPath = dirPath + name;
|
||||
auto childDir = std::dynamic_pointer_cast<TreeInode>(childInode);
|
||||
DCHECK(childDir->getContents().rlock()->materialized)
|
||||
<< (dirPath + name) << " entry " << ent.get()
|
||||
<< " materialized is true, but the contained dir is !materialized";
|
||||
|
||||
getModifiedDirectoriesRecursive(
|
||||
childPath, childDir.get(), modifiedDirectories);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unique_ptr<std::vector<RelativePath>> getModifiedDirectoriesForMount(
|
||||
EdenMount* edenMount) {
|
||||
auto inodeDispatcher = edenMount->getMountPoint()->getDispatcher();
|
||||
auto rootInode = inodeDispatcher->getDirInode(FUSE_ROOT_ID);
|
||||
auto treeInode = std::dynamic_pointer_cast<TreeInode>(rootInode);
|
||||
if (treeInode) {
|
||||
auto modifiedDirectories = std::make_unique<std::vector<RelativePath>>();
|
||||
getModifiedDirectoriesRecursive(
|
||||
RelativePathPiece(), treeInode.get(), modifiedDirectories.get());
|
||||
return modifiedDirectories;
|
||||
} else {
|
||||
throw std::runtime_error(folly::to<std::string>(
|
||||
"Could not find root TreeInode for ", edenMount->getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,24 +9,14 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "eden/fs/service/gen-cpp2/EdenService.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
class EdenMount;
|
||||
class MaterializedResult;
|
||||
|
||||
void getMaterializedEntriesForMount(
|
||||
EdenMount* edenMount,
|
||||
MaterializedResult& out);
|
||||
|
||||
/**
|
||||
* @return vector with the RelativePath of every directory that is modified
|
||||
* according to the overlay in the mount. The vector will be ordered as a
|
||||
* depth-first traversal of the overlay.
|
||||
*/
|
||||
std::unique_ptr<std::vector<RelativePath>> getModifiedDirectoriesForMount(
|
||||
EdenMount* edenMount);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <folly/experimental/TestUtil.h>
|
||||
#include <folly/io/IOBuf.h>
|
||||
#include "eden/fs/config/ClientConfig.h"
|
||||
#include "eden/fs/inodes/Dirstate.h"
|
||||
#include "eden/fs/inodes/FileData.h"
|
||||
#include "eden/fs/inodes/Overlay.h"
|
||||
#include "eden/fs/inodes/TreeEntryFileInode.h"
|
||||
@ -30,6 +31,7 @@ using facebook::eden::fusell::MountPoint;
|
||||
using folly::ByteRange;
|
||||
using folly::StringPiece;
|
||||
using folly::test::TemporaryDirectory;
|
||||
using folly::test::TemporaryFile;
|
||||
using std::make_shared;
|
||||
using std::make_unique;
|
||||
using std::shared_ptr;
|
||||
@ -82,6 +84,16 @@ unique_ptr<TestMount> TestMountBuilder::build() {
|
||||
shared_ptr<Overlay> overlay =
|
||||
make_shared<Overlay>(AbsolutePathPiece{overlayDir->path().string()});
|
||||
|
||||
// TODO(mbolin): Change EdenMount so that it creates the Dirstate.
|
||||
auto pathToDirstatePersistenceData = make_unique<TemporaryFile>();
|
||||
auto dirstatePersistence = make_unique<DirstatePersistence>(
|
||||
AbsolutePathPiece(pathToDirstatePersistenceData->path().string()));
|
||||
auto dirstate = make_unique<Dirstate>(
|
||||
mountPoint.get(),
|
||||
objectStore.get(),
|
||||
std::move(dirstatePersistence),
|
||||
&userDirectives_);
|
||||
|
||||
std::vector<BindMount> bindMounts;
|
||||
unique_ptr<EdenMount> edenMount = make_unique<EdenMount>(
|
||||
mountPoint, std::move(objectStore), overlay, bindMounts);
|
||||
@ -130,9 +142,19 @@ unique_ptr<TestMount> TestMountBuilder::build() {
|
||||
|
||||
return make_unique<TestMount>(
|
||||
std::move(edenMount),
|
||||
std::move(dirstate),
|
||||
std::move(mountPointDir),
|
||||
std::move(pathToRocksDb),
|
||||
std::move(overlayDir));
|
||||
std::move(overlayDir),
|
||||
std::move(pathToDirstatePersistenceData));
|
||||
}
|
||||
|
||||
void TestMountBuilder::addUserDirectives(
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>&&
|
||||
userDirectives) {
|
||||
for (auto& pair : userDirectives) {
|
||||
userDirectives_.emplace(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void TestMount::addFile(folly::StringPiece path, std::string contents) {
|
||||
|
@ -13,9 +13,11 @@
|
||||
#include <folly/experimental/TestUtil.h>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
#include "eden/fs/inodes/Dirstate.h"
|
||||
#include "eden/fs/inodes/DirstatePersistence.h"
|
||||
#include "eden/fs/inodes/EdenMount.h"
|
||||
#include "eden/fs/inodes/gen-cpp2/overlay_types.h"
|
||||
#include "eden/fs/model/TreeEntry.h"
|
||||
#include "eden/fs/store/ObjectStore.h"
|
||||
#include "eden/utils/PathFuncs.h"
|
||||
|
||||
namespace facebook {
|
||||
@ -44,13 +46,17 @@ class TestMount {
|
||||
public:
|
||||
TestMount(
|
||||
std::shared_ptr<EdenMount> edenMount,
|
||||
std::unique_ptr<Dirstate> dirstate,
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> mountPointDir,
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> pathToRocksDb,
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> overlayDir)
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> overlayDir,
|
||||
std::unique_ptr<folly::test::TemporaryFile> persistenceDataFile)
|
||||
: edenMount_(edenMount),
|
||||
dirstate_(std::move(dirstate)),
|
||||
mountPointDir_(std::move(mountPointDir)),
|
||||
pathToRocksDb_(std::move(pathToRocksDb)),
|
||||
overlayDir_(std::move(overlayDir)) {}
|
||||
overlayDir_(std::move(overlayDir)),
|
||||
persistenceDataFile_(std::move(persistenceDataFile)) {}
|
||||
|
||||
/**
|
||||
* Add file to the mount; it will be available in the overlay.
|
||||
@ -80,14 +86,20 @@ class TestMount {
|
||||
return edenMount_;
|
||||
}
|
||||
|
||||
Dirstate* getDirstate() {
|
||||
return dirstate_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<EdenMount> edenMount_;
|
||||
std::unique_ptr<Dirstate> dirstate_;
|
||||
|
||||
// The TestMount must hold onto these TemporaryDirectories because they need
|
||||
// to live for the duration of the test.
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> mountPointDir_;
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> pathToRocksDb_;
|
||||
std::unique_ptr<folly::test::TemporaryDirectory> overlayDir_;
|
||||
std::unique_ptr<folly::test::TemporaryFile> persistenceDataFile_;
|
||||
};
|
||||
|
||||
class TestMountBuilder {
|
||||
@ -104,8 +116,14 @@ class TestMountBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
void addUserDirectives(
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>&&
|
||||
userDirectives);
|
||||
|
||||
private:
|
||||
std::vector<TestMountFile> files_;
|
||||
std::unordered_map<RelativePath, overlay::UserStatusDirective>
|
||||
userDirectives_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,13 @@ class MountPoint {
|
||||
*/
|
||||
struct stat initStatData() const;
|
||||
|
||||
/**
|
||||
* @return vector with the RelativePath of every directory that is modified
|
||||
* according to the overlay in the mount. The vector will be ordered as a
|
||||
* depth-first traversal of the overlay.
|
||||
*/
|
||||
std::unique_ptr<std::vector<RelativePath>> getModifiedDirectories();
|
||||
|
||||
private:
|
||||
enum class Status { UNINIT, STARTING, RUNNING, ERROR };
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user