mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
unbox Dir Entries
Summary: Dir's contents were represented as a vector of 64-bit pointers to 48-byte structs. This change removes that layer of indirection, reducing memory usage and slightly pessimizing insertion. The diff is mostly mechanical outside of the TreeInode.h changes and calls to emplace.. I'll run memory tests tomorrow, though it's a gamble as to whether private bytes will show a difference. I may need to shrink the Entry struct too. Reviewed By: wez Differential Revision: D6804957 fbshipit-source-id: b126656dbc7951565e74b6401adde6353e809056
This commit is contained in:
parent
a3b148fddb
commit
d0823ab865
@ -620,8 +620,8 @@ void InodeMap::onInodeUnreferenced(
|
||||
const auto& parentContents = parentInfo.getParentContents();
|
||||
auto it = parentContents->entries.find(parentInfo.getName());
|
||||
CHECK(it != parentContents->entries.end());
|
||||
CHECK_EQ(it->second->getInode(), inode);
|
||||
it->second->clearInode();
|
||||
CHECK_EQ(it->second.getInode(), inode);
|
||||
it->second.clearInode();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,14 +218,13 @@ Optional<TreeInode::Dir> Overlay::loadOverlayDir(
|
||||
const auto& name = iter.first;
|
||||
const auto& value = iter.second;
|
||||
|
||||
unique_ptr<TreeInode::Entry> entry;
|
||||
if (value.inodeNumber == 0) {
|
||||
auto hash = Hash(folly::ByteRange(folly::StringPiece(value.hash)));
|
||||
entry = make_unique<TreeInode::Entry>(value.mode, hash);
|
||||
result.entries.emplace(PathComponentPiece{name}, value.mode, hash);
|
||||
} else {
|
||||
entry = make_unique<TreeInode::Entry>(value.mode, value.inodeNumber);
|
||||
result.entries.emplace(
|
||||
PathComponentPiece{name}, value.mode, value.inodeNumber);
|
||||
}
|
||||
result.entries.emplace(PathComponentPiece(name), std::move(entry));
|
||||
}
|
||||
|
||||
return folly::Optional<TreeInode::Dir>(std::move(result));
|
||||
@ -242,16 +241,16 @@ void Overlay::saveOverlayDir(
|
||||
DCHECK(dir->isMaterialized());
|
||||
for (auto& entIter : dir->entries) {
|
||||
const auto& entName = entIter.first;
|
||||
const auto ent = entIter.second.get();
|
||||
const auto& ent = entIter.second;
|
||||
|
||||
overlay::OverlayEntry oent;
|
||||
oent.mode = ent->getModeUnsafe();
|
||||
if (ent->isMaterialized()) {
|
||||
oent.inodeNumber = ent->getInodeNumber();
|
||||
oent.mode = ent.getModeUnsafe();
|
||||
if (ent.isMaterialized()) {
|
||||
oent.inodeNumber = ent.getInodeNumber();
|
||||
DCHECK_NE(oent.inodeNumber, 0);
|
||||
} else {
|
||||
oent.inodeNumber = 0;
|
||||
auto entHash = ent->getHash();
|
||||
auto entHash = ent.getHash();
|
||||
auto bytes = entHash.getBytes();
|
||||
oent.hash = std::string(
|
||||
reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
|
@ -205,8 +205,8 @@ Future<InodePtr> TreeInode::getOrLoadChild(PathComponentPiece name) {
|
||||
|
||||
// Check to see if the entry is already loaded
|
||||
auto& entryPtr = iter->second;
|
||||
if (entryPtr->getInode()) {
|
||||
return makeFuture<InodePtr>(InodePtr::newPtrLocked(entryPtr->getInode()));
|
||||
if (entryPtr.getInode()) {
|
||||
return makeFuture<InodePtr>(InodePtr::newPtrLocked(entryPtr.getInode()));
|
||||
}
|
||||
|
||||
// The entry is not loaded yet. Ask the InodeMap about the entry.
|
||||
@ -215,28 +215,27 @@ Future<InodePtr> TreeInode::getOrLoadChild(PathComponentPiece name) {
|
||||
folly::Promise<InodePtr> promise;
|
||||
returnFuture = promise.getFuture();
|
||||
bool startLoad;
|
||||
if (entryPtr->hasInodeNumber()) {
|
||||
childNumber = entryPtr->getInodeNumber();
|
||||
if (entryPtr.hasInodeNumber()) {
|
||||
childNumber = entryPtr.getInodeNumber();
|
||||
startLoad = getInodeMap()->shouldLoadChild(
|
||||
this, name, childNumber, std::move(promise));
|
||||
} else {
|
||||
childNumber =
|
||||
getInodeMap()->newChildLoadStarted(this, name, std::move(promise));
|
||||
// Immediately record the newly allocated inode number
|
||||
entryPtr->setInodeNumber(childNumber);
|
||||
entryPtr.setInodeNumber(childNumber);
|
||||
startLoad = true;
|
||||
}
|
||||
if (startLoad) {
|
||||
// The inode is not already being loaded. We have to start loading it
|
||||
// now.
|
||||
auto loadFuture =
|
||||
startLoadingInodeNoThrow(entryPtr.get(), name, childNumber);
|
||||
auto loadFuture = startLoadingInodeNoThrow(entryPtr, name, childNumber);
|
||||
if (loadFuture.isReady() && loadFuture.hasValue()) {
|
||||
// If we finished loading the inode immediately, just call
|
||||
// InodeMap::inodeLoadComplete() now, since we still have the data_
|
||||
// lock.
|
||||
auto childInode = loadFuture.get();
|
||||
entryPtr->setInode(childInode.get());
|
||||
entryPtr.setInode(childInode.get());
|
||||
promises = getInodeMap()->inodeLoadComplete(childInode.get());
|
||||
childInodePtr = InodePtr::newPtrLocked(childInode.release());
|
||||
} else {
|
||||
@ -323,16 +322,16 @@ fusell::InodeNumber TreeInode::getChildInodeNumber(PathComponentPiece name) {
|
||||
}
|
||||
|
||||
auto& ent = iter->second;
|
||||
if (ent->getInode()) {
|
||||
return ent->getInode()->getNodeId();
|
||||
if (ent.getInode()) {
|
||||
return ent.getInode()->getNodeId();
|
||||
}
|
||||
|
||||
if (ent->hasInodeNumber()) {
|
||||
return ent->getInodeNumber();
|
||||
if (ent.hasInodeNumber()) {
|
||||
return ent.getInodeNumber();
|
||||
}
|
||||
|
||||
auto inodeNumber = getInodeMap()->allocateInodeNumber();
|
||||
ent->setInodeNumber(inodeNumber);
|
||||
ent.setInodeNumber(inodeNumber);
|
||||
return inodeNumber;
|
||||
}
|
||||
|
||||
@ -410,10 +409,10 @@ void TreeInode::loadChildInode(
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entryPtr = iter->second;
|
||||
auto& entry = iter->second;
|
||||
// InodeMap makes sure to only try loading each inode once, so this entry
|
||||
// should not already be loaded.
|
||||
if (entryPtr->getInode() != nullptr) {
|
||||
if (entry.getInode() != nullptr) {
|
||||
auto bug = EDEN_BUG()
|
||||
<< "InodeMap requested to load inode " << number << "(" << name
|
||||
<< " in " << getNodeId() << "), which is already loaded";
|
||||
@ -428,7 +427,7 @@ void TreeInode::loadChildInode(
|
||||
return;
|
||||
}
|
||||
|
||||
future = startLoadingInodeNoThrow(entryPtr.get(), name, number);
|
||||
future = startLoadingInodeNoThrow(entry, name, number);
|
||||
}
|
||||
registerInodeLoadComplete(future.value(), name, number);
|
||||
}
|
||||
@ -474,7 +473,7 @@ void TreeInode::inodeLoadComplete(
|
||||
childName,
|
||||
"inode removed before loading finished");
|
||||
}
|
||||
iter->second->setInode(childInode.get());
|
||||
iter->second.setInode(childInode.get());
|
||||
// Make sure that we are still holding the contents_ lock when
|
||||
// calling inodeLoadComplete(). This ensures that no-one can look up
|
||||
// the inode by name before it is also available in the InodeMap.
|
||||
@ -491,7 +490,7 @@ void TreeInode::inodeLoadComplete(
|
||||
}
|
||||
|
||||
Future<unique_ptr<InodeBase>> TreeInode::startLoadingInodeNoThrow(
|
||||
Entry* entry,
|
||||
const Entry& entry,
|
||||
PathComponentPiece name,
|
||||
fusell::InodeNumber number) noexcept {
|
||||
// The callers of startLoadingInodeNoThrow() need to make sure that they
|
||||
@ -513,13 +512,13 @@ Future<unique_ptr<InodeBase>> TreeInode::startLoadingInodeNoThrow(
|
||||
}
|
||||
|
||||
Future<unique_ptr<InodeBase>> TreeInode::startLoadingInode(
|
||||
Entry* entry,
|
||||
const Entry& entry,
|
||||
PathComponentPiece name,
|
||||
fusell::InodeNumber number) {
|
||||
XLOG(DBG5) << "starting to load inode " << number << ": " << getLogPath()
|
||||
<< " / \"" << name << "\"";
|
||||
DCHECK(entry->getInode() == nullptr);
|
||||
if (!S_ISDIR(entry->getMode())) {
|
||||
DCHECK(entry.getInode() == nullptr);
|
||||
if (!S_ISDIR(entry.getMode())) {
|
||||
// If this is a file we can just go ahead and create it now;
|
||||
// we don't need to load anything else.
|
||||
//
|
||||
@ -530,13 +529,13 @@ Future<unique_ptr<InodeBase>> TreeInode::startLoadingInode(
|
||||
number,
|
||||
inodePtrFromThis(),
|
||||
name,
|
||||
entry->getMode(),
|
||||
entry->getOptionalHash());
|
||||
entry.getMode(),
|
||||
entry.getOptionalHash());
|
||||
}
|
||||
|
||||
if (!entry->isMaterialized()) {
|
||||
if (!entry.isMaterialized()) {
|
||||
return getStore()
|
||||
->getTree(entry->getHash())
|
||||
->getTree(entry.getHash())
|
||||
.then(
|
||||
[self = inodePtrFromThis(),
|
||||
childName = PathComponent{name},
|
||||
@ -548,7 +547,7 @@ Future<unique_ptr<InodeBase>> TreeInode::startLoadingInode(
|
||||
}
|
||||
|
||||
// No corresponding TreeEntry, this exists only in the overlay.
|
||||
CHECK_EQ(number, entry->getInodeNumber());
|
||||
CHECK_EQ(number, entry.getInodeNumber());
|
||||
auto overlayDir = getOverlay()->loadOverlayDir(number);
|
||||
if (!overlayDir) {
|
||||
auto bug = EDEN_BUG() << "missing overlay for " << getLogPath() << " / "
|
||||
@ -638,13 +637,13 @@ void TreeInode::childMaterialized(
|
||||
<< getLogPath() << ": entry not present";
|
||||
}
|
||||
|
||||
auto* childEntry = iter->second.get();
|
||||
if (contents->isMaterialized() && childEntry->isMaterialized()) {
|
||||
auto& childEntry = iter->second;
|
||||
if (contents->isMaterialized() && childEntry.isMaterialized()) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
childEntry->setMaterialized(childNodeId);
|
||||
childEntry.setMaterialized(childNodeId);
|
||||
contents->setMaterialized();
|
||||
getOverlay()->saveOverlayDir(this->getNodeId(), &*contents);
|
||||
}
|
||||
@ -671,15 +670,14 @@ void TreeInode::childDematerialized(
|
||||
<< getLogPath() << ": entry not present";
|
||||
}
|
||||
|
||||
auto* childEntry = iter->second.get();
|
||||
if (!childEntry->isMaterialized() &&
|
||||
childEntry->getHash() == childScmHash) {
|
||||
auto& childEntry = iter->second;
|
||||
if (!childEntry.isMaterialized() && childEntry.getHash() == childScmHash) {
|
||||
// Nothing to do. Our child's state and our own are both unchanged.
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the child dematerialized.
|
||||
childEntry->setDematerialized(childScmHash);
|
||||
childEntry.setDematerialized(childScmHash);
|
||||
|
||||
// Mark us materialized!
|
||||
//
|
||||
@ -713,9 +711,8 @@ TreeInode::Dir TreeInode::buildDirFromTree(
|
||||
|
||||
dir.treeHash = tree->getHash();
|
||||
for (const auto& treeEntry : tree->getTreeEntries()) {
|
||||
Entry entry{treeEntry.getMode(), treeEntry.getHash()};
|
||||
dir.entries.emplace(
|
||||
treeEntry.getName(), std::make_unique<Entry>(std::move(entry)));
|
||||
treeEntry.getName(), treeEntry.getMode(), treeEntry.getHash());
|
||||
}
|
||||
// Set timestamps to lastCheckoutTime
|
||||
dir.timeStamps.setTimestampValues(lastCheckoutTime);
|
||||
@ -762,8 +759,14 @@ TreeInode::create(PathComponentPiece name, mode_t mode, int /*flags*/) {
|
||||
mode = S_IFREG | (07777 & mode);
|
||||
|
||||
// Record the new entry
|
||||
auto& entry = contents->entries[name];
|
||||
entry = std::make_unique<Entry>(mode, childNumber);
|
||||
auto insertion = contents->entries.emplace(name, mode, childNumber);
|
||||
auto& entry = insertion.first->second;
|
||||
if (!insertion.second) {
|
||||
// FUSE will never call into this code path if a file is being replaced.
|
||||
auto bug = EDEN_BUG()
|
||||
<< "create() on path component that already exists" << name;
|
||||
return makeFuture<TreeInode::CreateResult>(bug.toException());
|
||||
}
|
||||
|
||||
// Build a corresponding FileInode. This code does not have to do anything
|
||||
// special to handle O_EXCL, because the kernel and FUSE take care of making
|
||||
@ -780,7 +783,7 @@ TreeInode::create(PathComponentPiece name, mode_t mode, int /*flags*/) {
|
||||
std::move(file),
|
||||
currentTime);
|
||||
|
||||
entry->setInode(inode.get());
|
||||
entry.setInode(inode.get());
|
||||
inodeMap->inodeCreated(inode);
|
||||
|
||||
auto now = getMount()->getClock().getRealtime();
|
||||
@ -872,17 +875,17 @@ FileInodePtr TreeInode::symlink(
|
||||
" bytes");
|
||||
}
|
||||
|
||||
auto entry = std::make_unique<Entry>(S_IFLNK | 0770, childNumber);
|
||||
auto entry = Entry(S_IFLNK | 0770, childNumber);
|
||||
|
||||
// build a corresponding FileInode
|
||||
inode = FileInodePtr::makeNew(
|
||||
childNumber,
|
||||
this->inodePtrFromThis(),
|
||||
name,
|
||||
entry->getMode(),
|
||||
entry.getMode(),
|
||||
std::move(file),
|
||||
currentTime);
|
||||
entry->setInode(inode.get());
|
||||
entry.setInode(inode.get());
|
||||
inodeMap->inodeCreated(inode);
|
||||
contents->entries.emplace(name, std::move(entry));
|
||||
|
||||
@ -950,17 +953,17 @@ FileInodePtr TreeInode::mknod(PathComponentPiece name, mode_t mode, dev_t dev) {
|
||||
auto currentTime = getNow();
|
||||
folly::File file =
|
||||
getOverlay()->createOverlayFile(childNumber, currentTime);
|
||||
auto entry = std::make_unique<Entry>(mode, childNumber);
|
||||
auto entry = Entry(mode, childNumber);
|
||||
|
||||
// build a corresponding FileInode
|
||||
inode = FileInodePtr::makeNew(
|
||||
childNumber,
|
||||
this->inodePtrFromThis(),
|
||||
name,
|
||||
entry->getMode(),
|
||||
entry.getMode(),
|
||||
std::move(file),
|
||||
currentTime);
|
||||
entry->setInode(inode.get());
|
||||
entry.setInode(inode.get());
|
||||
inodeMap->inodeCreated(inode);
|
||||
contents->entries.emplace(name, std::move(entry));
|
||||
|
||||
@ -1024,8 +1027,7 @@ TreeInodePtr TreeInode::mkdir(PathComponentPiece name, mode_t mode) {
|
||||
overlay->saveOverlayDir(childNumber, &emptyDir);
|
||||
|
||||
// Add a new entry to contents_.entries
|
||||
auto emplaceResult = contents->entries.emplace(
|
||||
name, std::make_unique<Entry>(mode, childNumber));
|
||||
auto emplaceResult = contents->entries.emplace(name, mode, childNumber);
|
||||
CHECK(emplaceResult.second)
|
||||
<< "directory contents should not have changed since the check above";
|
||||
auto& entry = emplaceResult.first->second;
|
||||
@ -1033,7 +1035,7 @@ TreeInodePtr TreeInode::mkdir(PathComponentPiece name, mode_t mode) {
|
||||
// Create the TreeInode
|
||||
newChild = TreeInodePtr::makeNew(
|
||||
childNumber, this->inodePtrFromThis(), name, std::move(emptyDir));
|
||||
entry->setInode(newChild.get());
|
||||
entry.setInode(newChild.get());
|
||||
inodeMap->inodeCreated(newChild);
|
||||
|
||||
// Save our updated overlay data
|
||||
@ -1178,20 +1180,20 @@ int TreeInode::tryRemoveChild(
|
||||
return ENOENT;
|
||||
}
|
||||
auto& ent = entIter->second;
|
||||
if (!ent->getInode()) {
|
||||
if (!ent.getInode()) {
|
||||
// The inode in question is not loaded. The caller will need to load it
|
||||
// and retry (if they want to retry).
|
||||
return EBADF;
|
||||
}
|
||||
if (child) {
|
||||
if (ent->getInode() != child.get()) {
|
||||
if (ent.getInode() != child.get()) {
|
||||
// This entry no longer refers to what the caller expected.
|
||||
return EBADF;
|
||||
}
|
||||
} else {
|
||||
// Make sure the entry being removed is the expected file/directory type.
|
||||
auto* currentChild =
|
||||
dynamic_cast<typename InodePtrType::InodeType*>(ent->getInode());
|
||||
dynamic_cast<typename InodePtrType::InodeType*>(ent.getInode());
|
||||
if (!currentChild) {
|
||||
return InodePtrType::InodeType::WRONG_TYPE_ERRNO;
|
||||
}
|
||||
@ -1291,12 +1293,12 @@ class TreeInode::TreeRenameLocks {
|
||||
return destContents_;
|
||||
}
|
||||
|
||||
const PathMap<std::unique_ptr<Entry>>::iterator& destChildIter() const {
|
||||
const PathMap<Entry>::iterator& destChildIter() const {
|
||||
return destChildIter_;
|
||||
}
|
||||
InodeBase* destChild() const {
|
||||
DCHECK(destChildExists());
|
||||
return destChildIter_->second->getInode();
|
||||
return destChildIter_->second.getInode();
|
||||
}
|
||||
|
||||
bool destChildExists() const {
|
||||
@ -1304,7 +1306,7 @@ class TreeInode::TreeRenameLocks {
|
||||
}
|
||||
bool destChildIsDirectory() const {
|
||||
DCHECK(destChildExists());
|
||||
return destChildIter_->second->isDirectory();
|
||||
return destChildIter_->second.isDirectory();
|
||||
}
|
||||
bool destChildIsEmpty() const {
|
||||
DCHECK_NOTNULL(destChildContents_);
|
||||
@ -1349,7 +1351,7 @@ class TreeInode::TreeRenameLocks {
|
||||
* This may point to destContents_->entries.end() if the destination child
|
||||
* does not exist.
|
||||
*/
|
||||
PathMap<std::unique_ptr<Entry>>::iterator destChildIter_;
|
||||
PathMap<Entry>::iterator destChildIter_;
|
||||
};
|
||||
|
||||
Future<Unit> TreeInode::rename(
|
||||
@ -1376,13 +1378,13 @@ Future<Unit> TreeInode::rename(
|
||||
// The source path does not exist. Fail the rename.
|
||||
return makeFuture<Unit>(InodeError(ENOENT, inodePtrFromThis(), name));
|
||||
}
|
||||
Entry* srcEntry = srcIter->second.get();
|
||||
Entry& srcEntry = srcIter->second;
|
||||
|
||||
// Perform as much input validation as possible now, before starting inode
|
||||
// loads that might be necessary.
|
||||
|
||||
// Validate invalid file/directory replacement
|
||||
if (srcEntry->isDirectory()) {
|
||||
if (srcEntry.isDirectory()) {
|
||||
// The source is a directory.
|
||||
// The destination must not exist, or must be an empty directory,
|
||||
// or the exact same directory.
|
||||
@ -1393,7 +1395,7 @@ Future<Unit> TreeInode::rename(
|
||||
<< destName;
|
||||
return makeFuture<Unit>(InodeError(ENOTDIR, destParent, destName));
|
||||
} else if (
|
||||
locks.destChild() != srcEntry->getInode() &&
|
||||
locks.destChild() != srcEntry.getInode() &&
|
||||
!locks.destChildIsEmpty()) {
|
||||
XLOG(DBG4) << "attempted to rename directory " << getLogPath() << "/"
|
||||
<< name << " over non-empty directory "
|
||||
@ -1421,7 +1423,7 @@ Future<Unit> TreeInode::rename(
|
||||
}
|
||||
|
||||
// Check to see if we need to load the source or destination inodes
|
||||
needSrc = !srcEntry->getInode();
|
||||
needSrc = !srcEntry.getInode();
|
||||
needDest = locks.destChildExists() && !locks.destChild();
|
||||
|
||||
// If we don't have to load anything now, we can immediately perform the
|
||||
@ -1477,14 +1479,14 @@ bool isAncestor(const RenameLock& renameLock, TreeInode* a, TreeInode* b) {
|
||||
Future<Unit> TreeInode::doRename(
|
||||
TreeRenameLocks&& locks,
|
||||
PathComponentPiece srcName,
|
||||
PathMap<std::unique_ptr<Entry>>::iterator srcIter,
|
||||
PathMap<Entry>::iterator srcIter,
|
||||
TreeInodePtr destParent,
|
||||
PathComponentPiece destName) {
|
||||
Entry* srcEntry = srcIter->second.get();
|
||||
Entry& srcEntry = srcIter->second;
|
||||
|
||||
// If the source and destination refer to exactly the same file,
|
||||
// then just succeed immediately. Nothing needs to be done in this case.
|
||||
if (locks.destChildExists() && srcEntry->getInode() == locks.destChild()) {
|
||||
if (locks.destChildExists() && srcEntry.getInode() == locks.destChild()) {
|
||||
return folly::Unit{};
|
||||
}
|
||||
|
||||
@ -1497,11 +1499,11 @@ Future<Unit> TreeInode::doRename(
|
||||
// We don't have to worry about the source being a child of the destination
|
||||
// directory. That will have already been caught by the earlier check that
|
||||
// ensures the destination directory is non-empty.
|
||||
if (srcEntry->isDirectory()) {
|
||||
if (srcEntry.isDirectory()) {
|
||||
// Our caller has already verified that the source is also a
|
||||
// directory here.
|
||||
auto* srcTreeInode =
|
||||
boost::polymorphic_downcast<TreeInode*>(srcEntry->getInode());
|
||||
boost::polymorphic_downcast<TreeInode*>(srcEntry.getInode());
|
||||
if (srcTreeInode == destParent.get() ||
|
||||
isAncestor(locks.renameLock(), srcTreeInode, destParent.get())) {
|
||||
return makeFuture<Unit>(InodeError(EINVAL, destParent, destName));
|
||||
@ -1512,7 +1514,7 @@ Future<Unit> TreeInode::doRename(
|
||||
// Update the destination with the source data (this copies in the hash if
|
||||
// it happens to be set).
|
||||
std::unique_ptr<InodeBase> deletedInode;
|
||||
auto* childInode = srcEntry->getInode();
|
||||
auto* childInode = srcEntry.getInode();
|
||||
if (locks.destChildExists()) {
|
||||
deletedInode = locks.destChild()->markUnlinked(
|
||||
destParent.get(), destName, locks.renameLock());
|
||||
@ -1707,7 +1709,7 @@ Future<Unit> TreeInode::diff(
|
||||
Entry* inodeEntry = nullptr;
|
||||
auto iter = contents->entries.find(kIgnoreFilename);
|
||||
if (iter != contents->entries.end()) {
|
||||
inodeEntry = iter->second.get();
|
||||
inodeEntry = &iter->second;
|
||||
if (inodeEntry->isDirectory()) {
|
||||
// Ignore .gitignore directories
|
||||
XLOG(DBG4) << "Ignoring .gitignore directory in " << getLogPath();
|
||||
@ -1729,7 +1731,7 @@ Future<Unit> TreeInode::diff(
|
||||
inode = InodePtr::newPtrLocked(inodeEntry->getInode());
|
||||
} else {
|
||||
inodeFuture = loadChildLocked(
|
||||
*contents, kIgnoreFilename, inodeEntry, &pendingLoads);
|
||||
*contents, kIgnoreFilename, *inodeEntry, &pendingLoads);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1909,7 +1911,7 @@ Future<Unit> TreeInode::computeDiff(
|
||||
entryIgnored));
|
||||
} else {
|
||||
auto inodeFuture = self->loadChildLocked(
|
||||
*contents, name, inodeEntry, &pendingLoads);
|
||||
*contents, name, *inodeEntry, &pendingLoads);
|
||||
deferredEntries.emplace_back(
|
||||
DeferredDiffEntry::createUntrackedEntryFromInodeFuture(
|
||||
context,
|
||||
@ -1982,7 +1984,7 @@ Future<Unit> TreeInode::computeDiff(
|
||||
// This inode is not loaded but is materialized.
|
||||
// We'll have to load it to confirm if it is the same or different.
|
||||
auto inodeFuture = self->loadChildLocked(
|
||||
*contents, scmEntry.getName(), inodeEntry, &pendingLoads);
|
||||
*contents, scmEntry.getName(), *inodeEntry, &pendingLoads);
|
||||
deferredEntries.emplace_back(
|
||||
DeferredDiffEntry::createModifiedEntryFromInodeFuture(
|
||||
context,
|
||||
@ -2000,7 +2002,7 @@ Future<Unit> TreeInode::computeDiff(
|
||||
// This is a modified directory. We have to load it then recurse
|
||||
// into it to find files with differences.
|
||||
auto inodeFuture = self->loadChildLocked(
|
||||
*contents, scmEntry.getName(), inodeEntry, &pendingLoads);
|
||||
*contents, scmEntry.getName(), *inodeEntry, &pendingLoads);
|
||||
deferredEntries.emplace_back(
|
||||
DeferredDiffEntry::createModifiedEntryFromInodeFuture(
|
||||
context,
|
||||
@ -2061,7 +2063,7 @@ Future<Unit> TreeInode::computeDiff(
|
||||
// inode entries are both sorted in the same order.
|
||||
vector<TreeEntry> emptyEntries;
|
||||
const auto& scEntries = tree ? tree->getTreeEntries() : emptyEntries;
|
||||
const auto& inodeEntries = contents->entries;
|
||||
auto& inodeEntries = contents->entries;
|
||||
size_t scIdx = 0;
|
||||
auto inodeIter = inodeEntries.begin();
|
||||
while (true) {
|
||||
@ -2072,7 +2074,7 @@ Future<Unit> TreeInode::computeDiff(
|
||||
}
|
||||
|
||||
// This entry is present locally but not in the source control tree.
|
||||
processUntracked(inodeIter->first, inodeIter->second.get());
|
||||
processUntracked(inodeIter->first, &inodeIter->second);
|
||||
++inodeIter;
|
||||
} else if (inodeIter == inodeEntries.end()) {
|
||||
// This entry is present in the old tree but not the old one.
|
||||
@ -2082,11 +2084,11 @@ Future<Unit> TreeInode::computeDiff(
|
||||
processRemoved(scEntries[scIdx]);
|
||||
++scIdx;
|
||||
} else if (scEntries[scIdx].getName() > inodeIter->first) {
|
||||
processUntracked(inodeIter->first, inodeIter->second.get());
|
||||
processUntracked(inodeIter->first, &inodeIter->second);
|
||||
++inodeIter;
|
||||
} else {
|
||||
const auto& scmEntry = scEntries[scIdx];
|
||||
auto* inodeEntry = inodeIter->second.get();
|
||||
auto* inodeEntry = &inodeIter->second;
|
||||
++scIdx;
|
||||
++inodeIter;
|
||||
processBothPresent(scmEntry, inodeEntry);
|
||||
@ -2341,9 +2343,10 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
// and does not currently exist in the filesystem. Go ahead and add it
|
||||
// now.
|
||||
if (!ctx->isDryRun()) {
|
||||
auto newEntry =
|
||||
make_unique<Entry>(newScmEntry->getMode(), newScmEntry->getHash());
|
||||
contents.entries.emplace(newScmEntry->getName(), std::move(newEntry));
|
||||
contents.entries.emplace(
|
||||
newScmEntry->getName(),
|
||||
newScmEntry->getMode(),
|
||||
newScmEntry->getHash());
|
||||
}
|
||||
} else if (!newScmEntry) {
|
||||
// This file exists in the old tree, but is being removed in the new
|
||||
@ -2359,9 +2362,10 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
ConflictType::REMOVED_MODIFIED, this, oldScmEntry->getName());
|
||||
if (ctx->forceUpdate()) {
|
||||
DCHECK(!ctx->isDryRun());
|
||||
auto newEntry =
|
||||
make_unique<Entry>(newScmEntry->getMode(), newScmEntry->getHash());
|
||||
contents.entries.emplace(newScmEntry->getName(), std::move(newEntry));
|
||||
contents.entries.emplace(
|
||||
newScmEntry->getName(),
|
||||
newScmEntry->getMode(),
|
||||
newScmEntry->getHash());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2370,9 +2374,9 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
}
|
||||
|
||||
auto& entry = it->second;
|
||||
if (entry->getInode()) {
|
||||
if (entry.getInode()) {
|
||||
// If the inode is already loaded, create a CheckoutAction to process it
|
||||
auto childPtr = InodePtr::newPtrLocked(entry->getInode());
|
||||
auto childPtr = InodePtr::newPtrLocked(entry.getInode());
|
||||
return make_unique<CheckoutAction>(
|
||||
ctx, oldScmEntry, newScmEntry, std::move(childPtr));
|
||||
}
|
||||
@ -2385,12 +2389,11 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
//
|
||||
// This also handles materialized inodes--an inode cannot be materialized if
|
||||
// it does not have an inode number assigned to it.
|
||||
if (entry->hasInodeNumber()) {
|
||||
if (entry.hasInodeNumber()) {
|
||||
// This child is potentially modified, but is not currently loaded.
|
||||
// Start loading it and create a CheckoutAction to process it once it
|
||||
// is loaded.
|
||||
auto inodeFuture =
|
||||
loadChildLocked(contents, name, entry.get(), pendingLoads);
|
||||
auto inodeFuture = loadChildLocked(contents, name, entry, pendingLoads);
|
||||
return make_unique<CheckoutAction>(
|
||||
ctx, oldScmEntry, newScmEntry, std::move(inodeFuture));
|
||||
}
|
||||
@ -2399,16 +2402,15 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
auto conflictType = ConflictType::ERROR;
|
||||
if (!oldScmEntry) {
|
||||
conflictType = ConflictType::UNTRACKED_ADDED;
|
||||
} else if (entry->getHash() != oldScmEntry->getHash()) {
|
||||
} else if (entry.getHash() != oldScmEntry->getHash()) {
|
||||
conflictType = ConflictType::MODIFIED_MODIFIED;
|
||||
}
|
||||
if (conflictType != ConflictType::ERROR) {
|
||||
// If this is are a directory we unfortunately have to load the directory
|
||||
// and recurse into it just so we can accurately report the list of files
|
||||
// with conflicts.
|
||||
if (entry->isDirectory()) {
|
||||
auto inodeFuture =
|
||||
loadChildLocked(contents, name, entry.get(), pendingLoads);
|
||||
if (entry.isDirectory()) {
|
||||
auto inodeFuture = loadChildLocked(contents, name, entry, pendingLoads);
|
||||
return make_unique<CheckoutAction>(
|
||||
ctx, oldScmEntry, newScmEntry, std::move(inodeFuture));
|
||||
}
|
||||
@ -2429,7 +2431,7 @@ unique_ptr<CheckoutAction> TreeInode::processCheckoutEntry(
|
||||
if (!newScmEntry) {
|
||||
contents.entries.erase(it);
|
||||
} else {
|
||||
*entry = Entry{newScmEntry->getMode(), newScmEntry->getHash()};
|
||||
entry = Entry{newScmEntry->getMode(), newScmEntry->getHash()};
|
||||
}
|
||||
|
||||
// Note that we intentionally don't bother calling
|
||||
@ -2469,7 +2471,7 @@ Future<Unit> TreeInode::checkoutUpdateEntry(
|
||||
<< inode->getLogPath();
|
||||
return folly::makeFuture<Unit>(bug.toException());
|
||||
}
|
||||
if (it->second->getInode() != inode.get()) {
|
||||
if (it->second.getInode() != inode.get()) {
|
||||
auto bug = EDEN_BUG()
|
||||
<< "entry changed while holding rename lock during checkout: "
|
||||
<< inode->getLogPath();
|
||||
@ -2481,8 +2483,7 @@ Future<Unit> TreeInode::checkoutUpdateEntry(
|
||||
deletedInode = inode->markUnlinked(this, name, ctx->renameLock());
|
||||
if (newScmEntry) {
|
||||
DCHECK_EQ(newScmEntry->getName(), name);
|
||||
it->second =
|
||||
make_unique<Entry>(newScmEntry->getMode(), newScmEntry->getHash());
|
||||
it->second = Entry(newScmEntry->getMode(), newScmEntry->getHash());
|
||||
} else {
|
||||
contents->entries.erase(it);
|
||||
}
|
||||
@ -2545,9 +2546,8 @@ Future<Unit> TreeInode::checkoutUpdateEntry(
|
||||
// Add the new entry
|
||||
auto contents = parentInode->contents_.wlock();
|
||||
DCHECK_EQ(TreeEntryType::BLOB, newScmEntry->getType());
|
||||
auto newTreeEntry =
|
||||
make_unique<Entry>(newScmEntry->getMode(), newScmEntry->getHash());
|
||||
auto ret = contents->entries.emplace(name, std::move(newTreeEntry));
|
||||
auto ret = contents->entries.emplace(
|
||||
name, newScmEntry->getMode(), newScmEntry->getHash());
|
||||
if (!ret.second) {
|
||||
// Hmm. Someone else already created a new entry in this location
|
||||
// before we had a chance to add our new entry. We don't block new
|
||||
@ -2616,14 +2616,14 @@ void TreeInode::saveOverlayPostCheckout(
|
||||
// operation.) Even if the child is still identical to its source
|
||||
// control state we still want to make sure we are materialized if the
|
||||
// child is.
|
||||
if (inodeIter->second->isMaterialized()) {
|
||||
if (inodeIter->second.isMaterialized()) {
|
||||
return folly::none;
|
||||
}
|
||||
|
||||
// If if the child is not materialized, it is the same as some source
|
||||
// control object. However, if it isn't the same as the object in our
|
||||
// Tree, we have to materialize ourself.
|
||||
if (inodeIter->second->getHash() != scmIter->getHash()) {
|
||||
if (inodeIter->second.getHash() != scmIter->getHash()) {
|
||||
return folly::none;
|
||||
}
|
||||
}
|
||||
@ -2740,31 +2740,31 @@ folly::Future<folly::Unit> recursivelyLoadMaterializedChildren(
|
||||
folly::Future<InodePtr> TreeInode::loadChildLocked(
|
||||
Dir& /* contents */,
|
||||
PathComponentPiece name,
|
||||
Entry* entry,
|
||||
Entry& entry,
|
||||
std::vector<IncompleteInodeLoad>* pendingLoads) {
|
||||
DCHECK(!entry->getInode());
|
||||
DCHECK(!entry.getInode());
|
||||
|
||||
bool startLoad;
|
||||
fusell::InodeNumber childNumber;
|
||||
folly::Promise<InodePtr> promise;
|
||||
auto future = promise.getFuture();
|
||||
if (entry->hasInodeNumber()) {
|
||||
childNumber = entry->getInodeNumber();
|
||||
if (entry.hasInodeNumber()) {
|
||||
childNumber = entry.getInodeNumber();
|
||||
startLoad = getInodeMap()->shouldLoadChild(
|
||||
this, name, childNumber, std::move(promise));
|
||||
} else {
|
||||
childNumber =
|
||||
getInodeMap()->newChildLoadStarted(this, name, std::move(promise));
|
||||
// Immediately record the newly allocated inode number
|
||||
entry->setInodeNumber(childNumber);
|
||||
entry.setInodeNumber(childNumber);
|
||||
startLoad = true;
|
||||
}
|
||||
|
||||
if (startLoad) {
|
||||
auto loadFuture =
|
||||
startLoadingInodeNoThrow(entry, name, entry->getInodeNumber());
|
||||
startLoadingInodeNoThrow(entry, name, entry.getInodeNumber());
|
||||
pendingLoads->emplace_back(
|
||||
this, std::move(loadFuture), name, entry->getInodeNumber());
|
||||
this, std::move(loadFuture), name, entry.getInodeNumber());
|
||||
}
|
||||
|
||||
return future;
|
||||
@ -2783,17 +2783,17 @@ folly::Future<folly::Unit> TreeInode::loadMaterializedChildren(
|
||||
|
||||
for (auto& entry : contents->entries) {
|
||||
const auto& name = entry.first;
|
||||
const auto& ent = entry.second;
|
||||
if (!ent->isMaterialized()) {
|
||||
auto& ent = entry.second;
|
||||
if (!ent.isMaterialized()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ent->getInode()) {
|
||||
if (ent.getInode()) {
|
||||
// Already loaded, most likely via prefetch
|
||||
continue;
|
||||
}
|
||||
|
||||
auto future = loadChildLocked(*contents, name, ent.get(), &pendingLoads);
|
||||
auto future = loadChildLocked(*contents, name, ent, &pendingLoads);
|
||||
inodeFutures.emplace_back(std::move(future));
|
||||
}
|
||||
}
|
||||
@ -2827,22 +2827,22 @@ void TreeInode::unloadChildrenNow() {
|
||||
auto inodeMapLock = inodeMap->lockForUnload();
|
||||
|
||||
for (auto& entry : contents->entries) {
|
||||
if (!entry.second->getInode()) {
|
||||
if (!entry.second.getInode()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* asTree = dynamic_cast<TreeInode*>(entry.second->getInode());
|
||||
auto* asTree = dynamic_cast<TreeInode*>(entry.second.getInode());
|
||||
if (asTree) {
|
||||
treeChildren.push_back(TreeInodePtr::newPtrLocked(asTree));
|
||||
} else {
|
||||
if (entry.second->getInode()->isPtrAcquireCountZero()) {
|
||||
if (entry.second.getInode()->isPtrAcquireCountZero()) {
|
||||
// Unload the inode
|
||||
inodeMap->unloadInode(
|
||||
entry.second->getInode(), this, entry.first, false, inodeMapLock);
|
||||
entry.second.getInode(), this, entry.first, false, inodeMapLock);
|
||||
// Record that we should now delete this inode after releasing
|
||||
// the locks.
|
||||
toDelete.push_back(entry.second->getInode());
|
||||
entry.second->clearInode();
|
||||
toDelete.push_back(entry.second.getInode());
|
||||
entry.second.clearInode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2880,11 +2880,11 @@ uint64_t TreeInode::unloadChildrenLastAccessedBefore(const timespec& cutoff) {
|
||||
{
|
||||
auto contents = contents_.rlock();
|
||||
for (auto& entry : contents->entries) {
|
||||
if (!entry.second->getInode()) {
|
||||
if (!entry.second.getInode()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* asFile = dynamic_cast<FileInode*>(entry.second->getInode());
|
||||
auto* asFile = dynamic_cast<FileInode*>(entry.second.getInode());
|
||||
if (asFile) {
|
||||
potentialUnload.push_back(FileInodePtr::newPtrLocked(asFile));
|
||||
}
|
||||
@ -2926,16 +2926,16 @@ uint64_t TreeInode::unloadChildrenLastAccessedBefore(const timespec& cutoff) {
|
||||
auto inodeMapLock = inodeMap->lockForUnload();
|
||||
|
||||
for (auto& entry : contents->entries) {
|
||||
if (!entry.second->getInode()) {
|
||||
if (!entry.second.getInode()) {
|
||||
continue;
|
||||
}
|
||||
auto asTree = dynamic_cast<TreeInode*>(entry.second->getInode());
|
||||
auto asTree = dynamic_cast<TreeInode*>(entry.second.getInode());
|
||||
if (asTree) {
|
||||
treeChildren.push_back(TreeInodePtr::newPtrLocked(asTree));
|
||||
} else {
|
||||
// Check if the entry is present in the toUnload list(atime greater than
|
||||
// age)
|
||||
auto entryInode = entry.second->getInode();
|
||||
auto entryInode = entry.second.getInode();
|
||||
if (toUnload.count(entryInode) && entryInode->isPtrAcquireCountZero()) {
|
||||
// Unload the inode
|
||||
inodeMap->unloadInode(
|
||||
@ -2943,7 +2943,7 @@ uint64_t TreeInode::unloadChildrenLastAccessedBefore(const timespec& cutoff) {
|
||||
// Record that we should now delete this inode after releasing
|
||||
// the locks.
|
||||
toDelete.push_back(entryInode);
|
||||
entry.second->clearInode();
|
||||
entry.second.clearInode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2986,31 +2986,31 @@ void TreeInode::getDebugStatus(vector<TreeInodeDebugInfo>& results) const {
|
||||
info.treeHash = thriftHash(contents->treeHash);
|
||||
|
||||
for (const auto& entry : contents->entries) {
|
||||
if (entry.second->getInode()) {
|
||||
if (entry.second.getInode()) {
|
||||
// A child inode exists, so just grab an InodePtr and add it to the
|
||||
// childInodes list. We will process all loaded children after
|
||||
// releasing our own contents_ lock (since we need to grab each child
|
||||
// Inode's own lock to get its data).
|
||||
childInodes.emplace_back(
|
||||
entry.first, InodePtr::newPtrLocked(entry.second->getInode()));
|
||||
entry.first, InodePtr::newPtrLocked(entry.second.getInode()));
|
||||
} else {
|
||||
// We can store data about unloaded entries immediately, since we have
|
||||
// the authoritative data ourself, and don't need to ask a separate
|
||||
// InodeBase object.
|
||||
info.entries.emplace_back();
|
||||
auto& infoEntry = info.entries.back();
|
||||
auto* inodeEntry = entry.second.get();
|
||||
auto& inodeEntry = entry.second;
|
||||
infoEntry.name = entry.first.stringPiece().str();
|
||||
if (inodeEntry->hasInodeNumber()) {
|
||||
infoEntry.inodeNumber = inodeEntry->getInodeNumber();
|
||||
if (inodeEntry.hasInodeNumber()) {
|
||||
infoEntry.inodeNumber = inodeEntry.getInodeNumber();
|
||||
} else {
|
||||
infoEntry.inodeNumber = 0;
|
||||
}
|
||||
infoEntry.mode = inodeEntry->getMode();
|
||||
infoEntry.mode = inodeEntry.getMode();
|
||||
infoEntry.loaded = false;
|
||||
infoEntry.materialized = inodeEntry->isMaterialized();
|
||||
infoEntry.materialized = inodeEntry.isMaterialized();
|
||||
if (!infoEntry.materialized) {
|
||||
infoEntry.hash = thriftHash(inodeEntry->getHash());
|
||||
infoEntry.hash = thriftHash(inodeEntry.getHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,10 +221,13 @@ class TreeInode : public InodeBase {
|
||||
InodeBase* inode_{nullptr};
|
||||
};
|
||||
|
||||
// TODO: We can do better than this.
|
||||
static_assert(sizeof(Entry) == 48, "Entry is six words");
|
||||
|
||||
/** Represents a directory in the overlay */
|
||||
struct Dir {
|
||||
/** The direct children of this directory */
|
||||
PathMap<std::unique_ptr<Entry>> entries;
|
||||
PathMap<Entry> entries;
|
||||
InodeTimestamps timeStamps;
|
||||
/**
|
||||
* If this TreeInode is unmaterialized (identical to an existing source
|
||||
@ -574,12 +577,12 @@ class TreeInode : public InodeBase {
|
||||
std::unique_ptr<InodeBase> childInode);
|
||||
|
||||
folly::Future<std::unique_ptr<InodeBase>> startLoadingInodeNoThrow(
|
||||
Entry* entry,
|
||||
const Entry& entry,
|
||||
PathComponentPiece name,
|
||||
fusell::InodeNumber number) noexcept;
|
||||
|
||||
folly::Future<std::unique_ptr<InodeBase>> startLoadingInode(
|
||||
Entry* entry,
|
||||
const Entry& entry,
|
||||
PathComponentPiece name,
|
||||
fusell::InodeNumber number);
|
||||
|
||||
@ -594,7 +597,7 @@ class TreeInode : public InodeBase {
|
||||
folly::Future<folly::Unit> doRename(
|
||||
TreeRenameLocks&& locks,
|
||||
PathComponentPiece srcName,
|
||||
PathMap<std::unique_ptr<Entry>>::iterator srcIter,
|
||||
PathMap<Entry>::iterator srcIter,
|
||||
TreeInodePtr destParent,
|
||||
PathComponentPiece destName);
|
||||
|
||||
@ -687,7 +690,7 @@ class TreeInode : public InodeBase {
|
||||
folly::Future<InodePtr> loadChildLocked(
|
||||
Dir& dir,
|
||||
PathComponentPiece name,
|
||||
Entry* entry,
|
||||
Entry& entry,
|
||||
std::vector<IncompleteInodeLoad>* pendingLoads);
|
||||
|
||||
/**
|
||||
|
@ -73,7 +73,7 @@ folly::Future<fusell::DirList> TreeInodeDirHandle::readdir(
|
||||
entries.emplace_back("..", dtype_t::Dir, parent_inode);
|
||||
|
||||
for (const auto& entry : dir->entries) {
|
||||
entries.emplace_back(entry.first.value(), entry.second->getDtype());
|
||||
entries.emplace_back(entry.first.value(), entry.second.getDtype());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ Future<unordered_set<RelativePath>> GlobNode::evaluate(
|
||||
}
|
||||
|
||||
// Not the leaf of a pattern; if this is a dir, we need to recurse
|
||||
if (it->second->isDirectory()) {
|
||||
if (it->second.isDirectory()) {
|
||||
recurse.emplace_back(std::make_pair(it->first, node.get()));
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,7 @@ Future<unordered_set<RelativePath>> GlobNode::evaluate(
|
||||
}
|
||||
// Not the leaf of a pattern; if this is a dir, we need to
|
||||
// recurse
|
||||
if (entry.second->isDirectory()) {
|
||||
if (entry.second.isDirectory()) {
|
||||
recurse.emplace_back(std::make_pair(entry.first, node.get()));
|
||||
}
|
||||
}
|
||||
@ -208,7 +208,7 @@ Future<unordered_set<RelativePath>> GlobNode::evaluateRecursiveComponent(
|
||||
|
||||
// Remember to recurse through child dirs after we've released
|
||||
// the lock on the contents.
|
||||
if (entry.second->isDirectory()) {
|
||||
if (entry.second.isDirectory()) {
|
||||
subDirNames.emplace_back(candidateName);
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ TEST(TestMount, createSimpleTestMount) {
|
||||
auto dir = dirTreeEntry->getContents().rlock();
|
||||
auto& rootEntries = dir->entries;
|
||||
auto& path1Entry = rootEntries.at(PathComponentPiece("path1"));
|
||||
ASSERT_FALSE(path1Entry->isMaterialized());
|
||||
EXPECT_EQ(expectedSha1, path1Entry->getHash())
|
||||
ASSERT_FALSE(path1Entry.isMaterialized());
|
||||
EXPECT_EQ(expectedSha1, path1Entry.getHash())
|
||||
<< "Getting the Entry from the root Dir should also work.";
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user