Move batch processing of hg add <directory> to the server.

Summary:
This should make the common case of `hg add` or `hg add .` much more efficient
because it no longer performs a walk of the entire repository from the client
side.

Reviewed By: simpkins

Differential Revision: D4317871

fbshipit-source-id: 7061553fe0de0c4afa84b4f835316965088675e8
This commit is contained in:
Michael Bolin 2016-12-15 13:00:31 -08:00 committed by Facebook Github Bot
parent 970159aa5c
commit dfd1634170
6 changed files with 289 additions and 101 deletions

View File

@ -65,7 +65,9 @@ struct DirectoryDelta {
namespace facebook {
namespace eden {
std::ostream& operator<<(std::ostream& os, const DirstateRemoveError& status) {
std::ostream& operator<<(
std::ostream& os,
const DirstateAddRemoveError& status) {
return os << status.errorMessage;
}
@ -590,42 +592,108 @@ void Dirstate::computeDelta(
return;
}
void Dirstate::add(RelativePathPiece path) {
// TODO(mbolin): Verify that path corresponds to a regular file or symlink.
/*
* Analogous to `hg add <path>`. Note that this can have one of several
* possible outcomes:
* 1. If the path does not exist in the working copy, return an error. (Note
* that this happens even if path is in the userDirectives_ as REMOVE.
* 2. If the path refers to a directory, return an error. (Currently, the
* caller is responsible for enumerating the transitive set of files in
* the directory and invoking this method once for each file.)
* 3. If the path is already in the manifest, or if it is already present in
* userDirectives_ as ADD, then return a warning as Hg does:
* "<path> already tracked!".
* 4. If the path was in the userDirectives_ as REMOVE, then this call to
* add() cancels it out and should remove the entry from userDirectives_.
* 5. Otherwise, `path` must not be in userDirectives_, so add it.
*/
// TODO(mbolin): Honor the detailed behavior described above. Currently, we
// assume that none of the edge cases in 1-3 apply.
{
namespace {
enum AddAction {
Add,
Erase,
};
void addDirstateAddRemoveError(
RelativePathPiece path,
StringPiece formatError,
std::vector<DirstateAddRemoveError>* errorsToReport) {
errorsToReport->push_back(DirstateAddRemoveError{
path.copy(), folly::sformat(formatError, path.stringPiece())});
}
void assignAddAction(
RelativePathPiece path,
HgStatusCode code,
std::unordered_map<RelativePath, AddAction>& actions) {
if (code == HgStatusCode::NOT_TRACKED) {
actions[path.copy()] = AddAction::Add;
} else if (code == HgStatusCode::REMOVED) {
actions[path.copy()] = AddAction::Erase;
}
// TODO(mbolin): Should we do anything for the other statuses? Do we
// need to complain or anything like that?
}
enum WorkingCopyStatus { File, Directory, DoesNotExist };
WorkingCopyStatus getPathStatus(
RelativePathPiece path,
const EdenMount* mount) {
try {
// Use getInodeBase() as a test of whether the path exists.
auto inodeBase = mount->getInodeBase(path);
if (std::dynamic_pointer_cast<FileInode>(inodeBase) != nullptr) {
return WorkingCopyStatus::File;
} else {
return WorkingCopyStatus::Directory;
}
} catch (const std::system_error& e) {
if (e.code().value() != ENOENT) {
throw;
} else {
return WorkingCopyStatus::DoesNotExist;
}
}
}
}
void Dirstate::addAll(
const std::vector<RelativePathPiece>& paths,
std::vector<DirstateAddRemoveError>* errorsToReport) {
// Find all of the untracked files and then update userDirectives, as
// appropriate.
std::unordered_map<RelativePath, AddAction> actions;
for (auto& path : paths) {
auto pathStatus = getPathStatus(path, mount_);
if (pathStatus == WorkingCopyStatus::File) {
// Admittedly, this getStatusForExistingDirectory() call will also
// traverse subdirectories of path.dirname(), so it will do some extra
// work. Similarly, if paths contains a list of files in the same
// directory, getStatusForExistingDirectory() will be called once per
// file instead of once for all files in that directory. If this turns
// out to be a bottleneck, then we can do some extra bookkeeping to
// reduce lookups.
auto status = getStatusForExistingDirectory(path.dirname());
auto code = status->statusForPath(path);
assignAddAction(path, code, actions);
} else if (pathStatus == WorkingCopyStatus::Directory) {
auto status = getStatusForExistingDirectory(path);
for (auto& pair : *status->list()) {
// Only attempt to process the entry if it corresponds to a file in the
// working copy.
auto entryStatus = getPathStatus(pair.first, mount_);
if (entryStatus == WorkingCopyStatus::File) {
assignAddAction(pair.first, pair.second, actions);
}
}
} else if (pathStatus == WorkingCopyStatus::DoesNotExist) {
addDirstateAddRemoveError(
path, "{}: No such file or directory", errorsToReport);
} else {
throw std::runtime_error("Unhandled enum value");
}
}
// Apply all of the updates to userDirectives in one go.
if (!actions.empty()) {
auto userDirectives = userDirectives_.wlock();
auto result = userDirectives->find(path.copy());
if (result != userDirectives->end()) {
switch (result->second) {
case overlay::UserStatusDirective::Add:
// No-op: already added.
for (auto& pair : actions) {
auto action = pair.second;
switch (action) {
case AddAction::Add:
(*userDirectives)[pair.first] = overlay::UserStatusDirective::Add;
break;
case overlay::UserStatusDirective::Remove:
userDirectives->erase(path.copy());
persistence_.save(*userDirectives);
case AddAction::Erase:
userDirectives->erase(pair.first);
break;
}
} else {
(*userDirectives)[path.copy()] = overlay::UserStatusDirective::Add;
persistence_.save(*userDirectives);
}
persistence_.save(*userDirectives);
}
}
@ -635,14 +703,6 @@ enum ShouldBeDeleted {
NO_BECAUSE_THE_FILE_WAS_ALREADY_DELETED,
NO_BECAUSE_THE_FILE_WAS_MODIFIED,
};
void addDirstateRemoveError(
RelativePathPiece path,
StringPiece formatError,
std::vector<DirstateRemoveError>& errorsToReport) {
errorsToReport.emplace_back(DirstateRemoveError{
path.copy(), folly::sformat(formatError, path.stringPiece())});
}
}
/**
@ -656,7 +716,7 @@ ShouldBeDeleted shouldFileBeDeletedByHgRemove(
TreeInodePtr treeInode,
const TreeEntry* treeEntry,
ObjectStore* objectStore,
std::vector<DirstateRemoveError>& errorsToReport) {
std::vector<DirstateAddRemoveError>* errorsToReport) {
if (treeInode == nullptr) {
// The parent directory for the file is not in the overlay, so the file must
// not have been modified. As such, `hg remove` should result in deleting
@ -673,8 +733,8 @@ ShouldBeDeleted shouldFileBeDeletedByHgRemove(
treeEntry, entry.second.get(), objectStore, *treeInode, *dir)) {
return ShouldBeDeleted::YES;
} else {
addDirstateRemoveError(
file.copy(),
addDirstateAddRemoveError(
file,
"not removing {}: file is modified (use -f to force removal)",
errorsToReport);
return ShouldBeDeleted::NO_BECAUSE_THE_FILE_WAS_MODIFIED;
@ -707,16 +767,16 @@ void collectAllPathsUnderTree(
}
void Dirstate::removeAll(
const std::vector<RelativePathPiece>* paths,
const std::vector<RelativePathPiece>& paths,
bool force,
std::vector<DirstateRemoveError>& errorsToReport) {
std::vector<DirstateAddRemoveError>* errorsToReport) {
// First, let's collect all of the files to remove based on the `paths`
// argument. We use an EvictingCacheMap as a set that preserves iteration
// order.
folly::EvictingCacheMap<RelativePath, folly::Unit> pathsToRemove(0);
auto rootTree = mount_->getRootTree();
auto objectStore = mount_->getObjectStore();
for (auto path : *paths) {
for (auto& path : paths) {
// A file (or directory) must be tracked in order for it to be
// removed, though it does not need to exist on disk (it could be in the
// MISSING state when this is called, for example).
@ -782,7 +842,7 @@ void Dirstate::removeAll(
continue;
}
}
addDirstateRemoveError(
addDirstateAddRemoveError(
path, "{}: No such file or directory", errorsToReport);
}
}
@ -809,7 +869,7 @@ void Dirstate::removeAll(
void Dirstate::remove(
RelativePathPiece path,
bool force,
std::vector<DirstateRemoveError>& errorsToReport) {
std::vector<DirstateAddRemoveError>* errorsToReport) {
/*
* Analogous to `hg rm <path>`. Note that the caller is responsible for
* ensuring that `path` satisfies at least one of the following requirements:
@ -883,7 +943,7 @@ void Dirstate::remove(
// corresponding TreeEntry in the manifest and compare it to its Entry in
// the Overlay, if it exists.
if (entry == nullptr) {
addDirstateRemoveError(
addDirstateAddRemoveError(
path, "not removing {}: file is untracked", errorsToReport);
return;
}
@ -923,7 +983,7 @@ void Dirstate::remove(
break;
case overlay::UserStatusDirective::Add:
if (inode != nullptr) {
addDirstateRemoveError(
addDirstateAddRemoveError(
path,
"not removing {}: file has been marked for add "
"(use 'hg forget' to undo add)",
@ -965,8 +1025,8 @@ InodePtr Dirstate::getInodeBaseOrNull(RelativePathPiece path) const {
void Dirstate::markCommitted(
Hash commitID,
std::vector<RelativePathPiece>& pathsToClean,
std::vector<RelativePathPiece>& pathsToDrop) {
const std::vector<RelativePathPiece>& pathsToClean,
const std::vector<RelativePathPiece>& pathsToDrop) {
// First, we update the root tree hash (as well as the hashes of other
// directories in the overlay). Currently, this is stored in two places: in
// the OverlayDir.treeHash for the root tree and in the SNAPSHOT file.
@ -1071,8 +1131,8 @@ const std::string& HgStatusCode_toString(HgStatusCode code) {
static_cast<typename std::underlying_type<HgStatusCode>::type>(code)));
}
HgStatusCode HgStatus::statusForPath(RelativePath path) const {
auto result = statuses_.find(path);
HgStatusCode HgStatus::statusForPath(RelativePathPiece path) const {
auto result = statuses_.find(path.copy());
if (result != statuses_.end()) {
return result->second;
} else {

View File

@ -75,7 +75,7 @@ class HgStatus {
* What happens if `path` is not in the internal statuses_ map? Should it
* return CLEAN or something else?
*/
HgStatusCode statusForPath(RelativePath path) const;
HgStatusCode statusForPath(RelativePathPiece path) const;
size_t size() const {
return statuses_.size();
@ -102,21 +102,23 @@ class HgStatus {
std::ostream& operator<<(std::ostream& os, const HgStatus& status);
struct DirstateRemoveError {
struct DirstateAddRemoveError {
RelativePath path;
std::string errorMessage;
};
inline bool operator==(
const DirstateRemoveError& lhs,
const DirstateRemoveError& rhs) {
const DirstateAddRemoveError& lhs,
const DirstateAddRemoveError& rhs) {
return lhs.path == rhs.path && lhs.errorMessage == rhs.errorMessage;
}
inline bool operator!=(
const DirstateRemoveError& lhs,
const DirstateRemoveError& rhs) {
const DirstateAddRemoveError& lhs,
const DirstateAddRemoveError& rhs) {
return !(lhs == rhs);
}
std::ostream& operator<<(std::ostream& os, const DirstateRemoveError& status);
std::ostream& operator<<(
std::ostream& os,
const DirstateAddRemoveError& status);
/**
* This is designed to be a simple implemenation of an Hg dirstate. It's
@ -147,9 +149,16 @@ class Dirstate {
std::unique_ptr<HgStatus> getStatus() const;
/**
* Analogous to `hg add <path>` where `<path>` is an ordinary file or symlink.
* Analogous to `hg add <path1> <path2> ...` where each `<path>` identifies an
* untracked file (or directory that contains untracked files) to be tracked.
*
* Note that if `paths` is empty, then nothing will be added. To do the
* equivalent of `hg add .`, then `paths` should be a vector with one element
* whose value is `RelativePathPiece("")`.
*/
void add(RelativePathPiece path);
void addAll(
const std::vector<RelativePathPiece>& paths,
std::vector<DirstateAddRemoveError>* errorsToReport);
/**
* Analogous to `hg rm <path1> <path2> ...` where each `<path>` identifies a
@ -170,9 +179,9 @@ class Dirstate {
* `hg rm` should be 1.
*/
void removeAll(
const std::vector<RelativePathPiece>* paths,
const std::vector<RelativePathPiece>& paths,
bool force,
std::vector<DirstateRemoveError>& errorsToReport);
std::vector<DirstateAddRemoveError>* errorsToReport);
/**
* Called as part of `hg commit`, so this does three things (ideally
@ -185,8 +194,8 @@ class Dirstate {
*/
void markCommitted(
Hash commitID,
std::vector<RelativePathPiece>& pathsToClean,
std::vector<RelativePathPiece>& pathsToDrop);
const std::vector<RelativePathPiece>& pathsToClean,
const std::vector<RelativePathPiece>& pathsToDrop);
private:
/**
@ -203,7 +212,7 @@ class Dirstate {
void remove(
RelativePathPiece path,
bool force,
std::vector<DirstateRemoveError>& errorsToReport);
std::vector<DirstateAddRemoveError>* errorsToReport);
/**
* Compares the TreeEntries from a Tree in the base commit with those in the

View File

@ -52,14 +52,39 @@ void verifyEmptyDirstate(const Dirstate* dirstate) {
EXPECT_EQ(0, status->size()) << "Expected dirstate to be empty.";
}
/**
* Calls `dirstate->addAll({path}, errorsToReport)` and fails if
* errorsToReport is non-empty. Note that path may identify a file or a
* directory, though it must be an existing file.
*/
void scmAddFile(Dirstate* dirstate, std::string path) {
std::vector<DirstateAddRemoveError> errorsToReport;
std::vector<RelativePathPiece> paths({RelativePathPiece(path)});
dirstate->addAll(paths, &errorsToReport);
if (!errorsToReport.empty()) {
FAIL() << "Unexpected error: " << errorsToReport[0];
}
}
void scmAddFileAndExpect(
Dirstate* dirstate,
std::string path,
DirstateAddRemoveError expectedError) {
std::vector<DirstateAddRemoveError> errorsToReport;
std::vector<RelativePathPiece> paths({RelativePathPiece(path)});
dirstate->addAll(paths, &errorsToReport);
std::vector<DirstateAddRemoveError> expectedErrors({expectedError});
EXPECT_EQ(expectedErrors, errorsToReport);
}
/**
* Calls `dirstate->removeAll({path}, force, errorsToReport)` and fails if
* errorsToReport is non-empty.
*/
void scmRemoveFile(Dirstate* dirstate, std::string path, bool force) {
std::vector<DirstateRemoveError> errorsToReport;
std::vector<DirstateAddRemoveError> errorsToReport;
std::vector<RelativePathPiece> paths({RelativePathPiece(path)});
dirstate->removeAll(&paths, force, errorsToReport);
dirstate->removeAll(paths, force, &errorsToReport);
if (!errorsToReport.empty()) {
FAIL() << "Unexpected error: " << errorsToReport[0];
}
@ -73,11 +98,11 @@ void scmRemoveFileAndExpect(
Dirstate* dirstate,
std::string path,
bool force,
DirstateRemoveError expectedError) {
std::vector<DirstateRemoveError> errorsToReport;
DirstateAddRemoveError expectedError) {
std::vector<DirstateAddRemoveError> errorsToReport;
std::vector<RelativePathPiece> paths({RelativePathPiece(path)});
dirstate->removeAll(&paths, force, errorsToReport);
std::vector<DirstateRemoveError> expectedErrors({expectedError});
dirstate->removeAll(paths, force, &errorsToReport);
std::vector<DirstateAddRemoveError> expectedErrors({expectedError});
EXPECT_EQ(expectedErrors, errorsToReport);
}
@ -141,7 +166,7 @@ TEST(Dirstate, createDirstateWithAddedFile) {
auto dirstate = testMount->getDirstate();
testMount->addFile("hello.txt", "some contents");
dirstate->add(RelativePathPiece("hello.txt"));
scmAddFile(dirstate, "hello.txt");
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
}
@ -151,7 +176,7 @@ TEST(Dirstate, createDirstateWithMissingFile) {
auto dirstate = testMount->getDirstate();
testMount->addFile("hello.txt", "some contents");
dirstate->add(RelativePathPiece("hello.txt"));
scmAddFile(dirstate, "hello.txt");
testMount->deleteFile("hello.txt");
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::MISSING}});
}
@ -178,6 +203,88 @@ TEST(Dirstate, createDirstateWithTouchedFile) {
verifyEmptyDirstate(dirstate);
}
TEST(Dirstate, addDirectoriesWithMixOfFiles) {
TestMountBuilder builder;
builder.addFiles({
{"rootfile.txt", ""}, {"dir1/a.txt", "original contents"},
});
auto testMount = builder.build();
testMount->addFile("dir1/b.txt", "");
testMount->mkdir("dir2");
testMount->addFile("dir2/c.txt", "");
auto dirstate = testMount->getDirstate();
verifyExpectedDirstate(
dirstate,
{
{"dir1/b.txt", HgStatusCode::NOT_TRACKED},
{"dir2/c.txt", HgStatusCode::NOT_TRACKED},
});
// `hg add dir2` should ensure only things under dir2 are added.
scmAddFile(dirstate, "dir2");
verifyExpectedDirstate(
dirstate,
{
{"dir1/b.txt", HgStatusCode::NOT_TRACKED},
{"dir2/c.txt", HgStatusCode::ADDED},
});
// This is the equivalent of `hg forget dir1/a.txt`.
scmRemoveFile(dirstate, "dir1/a.txt", /* force */ false);
testMount->addFile("dir1/a.txt", "original contents");
verifyExpectedDirstate(
dirstate,
{
{"dir1/a.txt", HgStatusCode::REMOVED},
{"dir1/b.txt", HgStatusCode::NOT_TRACKED},
{"dir2/c.txt", HgStatusCode::ADDED},
});
// Running `hg add .` should remove the removal marker from dir1/a.txt because
// dir1/a.txt is still on disk.
scmAddFile(dirstate, "");
verifyExpectedDirstate(
dirstate,
{
{"dir1/b.txt", HgStatusCode::ADDED},
{"dir2/c.txt", HgStatusCode::ADDED},
});
scmRemoveFile(dirstate, "dir1/a.txt", /* force */ false);
testMount->addFile("dir1/a.txt", "different contents");
// Running `hg add dir1` should remove the removal marker from dir1/a.txt, but
// `hg status` should also reflect that it is modified.
scmAddFile(dirstate, "dir1");
verifyExpectedDirstate(
dirstate,
{
{"dir1/a.txt", HgStatusCode::MODIFIED},
{"dir1/b.txt", HgStatusCode::ADDED},
{"dir2/c.txt", HgStatusCode::ADDED},
});
scmRemoveFile(dirstate, "dir1/a.txt", /* force */ true);
// This should not add dir1/a.txt back because it is not on disk.
scmAddFile(dirstate, "dir1");
verifyExpectedDirstate(
dirstate,
{
{"dir1/a.txt", HgStatusCode::REMOVED},
{"dir1/b.txt", HgStatusCode::ADDED},
{"dir2/c.txt", HgStatusCode::ADDED},
});
scmAddFileAndExpect(
dirstate,
"dir3",
DirstateAddRemoveError{RelativePath("dir3"),
"dir3: No such file or directory"}
);
}
TEST(Dirstate, createDirstateWithFileAndThenHgRemoveIt) {
TestMountBuilder builder;
builder.addFile({"hello.txt", "some contents"});
@ -214,9 +321,9 @@ TEST(Dirstate, createDirstateWithFileTouchItAndThenHgRemoveIt) {
dirstate,
"hello.txt",
/* force */ false,
DirstateRemoveError{RelativePath("hello.txt"),
"not removing hello.txt: file is modified "
"(use -f to force removal)"});
DirstateAddRemoveError{RelativePath("hello.txt"),
"not removing hello.txt: file is modified "
"(use -f to force removal)"});
testMount->overwriteFile("hello.txt", "original contents");
scmRemoveFile(dirstate, "hello.txt", /* force */ false);
@ -270,7 +377,7 @@ TEST(Dirstate, createDirstateHgAddFileRemoveItThenHgRemoveIt) {
auto dirstate = testMount->getDirstate();
testMount->addFile("hello.txt", "I will be added.");
dirstate->add(RelativePathPiece("hello.txt"));
scmAddFile(dirstate, "hello.txt");
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
testMount->deleteFile("hello.txt");
@ -288,7 +395,7 @@ TEST(Dirstate, createDirstateHgAddFileRemoveItThenHgRemoveItInSubdirectory) {
testMount->mkdir("dir1");
testMount->mkdir("dir1/dir2");
testMount->addFile("dir1/dir2/hello.txt", "I will be added.");
dirstate->add(RelativePathPiece("dir1/dir2/hello.txt"));
scmAddFile(dirstate, "dir1/dir2/hello.txt");
verifyExpectedDirstate(
dirstate, {{"dir1/dir2/hello.txt", HgStatusCode::ADDED}});
@ -307,14 +414,14 @@ TEST(Dirstate, createDirstateHgAddFileThenHgRemoveIt) {
auto dirstate = testMount->getDirstate();
testMount->addFile("hello.txt", "I will be added.");
dirstate->add(RelativePathPiece("hello.txt"));
scmAddFile(dirstate, "hello.txt");
verifyExpectedDirstate(dirstate, {{"hello.txt", HgStatusCode::ADDED}});
scmRemoveFileAndExpect(
dirstate,
"hello.txt",
/* force */ false,
DirstateRemoveError{
DirstateAddRemoveError{
RelativePath("hello.txt"),
"not removing hello.txt: file has been marked for add "
"(use 'hg forget' to undo add)"});
@ -344,7 +451,7 @@ TEST(Dirstate, removeAllOnADirectoryWithFilesInVariousStates) {
testMount->deleteFile("mydir/b");
scmRemoveFile(dirstate, "mydir/c", /* force */ false);
testMount->addFile("mydir/d", "I will be added.");
dirstate->add(RelativePathPiece("mydir/d"));
scmAddFile(dirstate, "mydir/d");
testMount->addFile("mydir/e", "I will be untracked");
verifyExpectedDirstate(
dirstate,
@ -357,7 +464,7 @@ TEST(Dirstate, removeAllOnADirectoryWithFilesInVariousStates) {
dirstate,
"mydir",
/* force */ false,
DirstateRemoveError{
DirstateAddRemoveError{
RelativePath("mydir/d"),
"not removing mydir/d: "
"file has been marked for add (use 'hg forget' to undo add)"});
@ -385,13 +492,13 @@ TEST(Dirstate, createDirstateAndAddNewDirectory) {
testMount->mkdir("a-new-folder");
testMount->addFile("a-new-folder/add.txt", "");
testMount->addFile("a-new-folder/not-tracked.txt", "");
dirstate->add(RelativePathPiece("a-new-folder/add.txt"));
scmAddFile(dirstate, "a-new-folder/add.txt");
// Add one folder that appears after file-in-root.txt alphabetically.
testMount->mkdir("z-new-folder");
testMount->addFile("z-new-folder/add.txt", "");
testMount->addFile("z-new-folder/not-tracked.txt", "");
dirstate->add(RelativePathPiece("z-new-folder/add.txt"));
scmAddFile(dirstate, "z-new-folder/add.txt");
verifyExpectedDirstate(
dirstate,
@ -524,8 +631,8 @@ TEST(Dirstate, createDirstateAndAddSubtree) {
testMount->mkdir("dir1");
testMount->addFile("dir1/aFile.txt", "");
testMount->addFile("dir1/bFile.txt", "");
dirstate->add(RelativePathPiece("root1.txt"));
dirstate->add(RelativePathPiece("dir1/bFile.txt"));
scmAddFile(dirstate, "root1.txt");
scmAddFile(dirstate, "dir1/bFile.txt");
verifyExpectedDirstate(
dirstate,
{
@ -549,7 +656,7 @@ TEST(Dirstate, createDirstateAndAddSubtree) {
{"dir1/dir2/dir3/dir4/cFile.txt", HgStatusCode::NOT_TRACKED},
});
dirstate->add(RelativePathPiece("dir1/dir2/dir3/dir4/cFile.txt"));
scmAddFile(dirstate, "dir1/dir2/dir3/dir4/cFile.txt");
verifyExpectedDirstate(
dirstate,
{

View File

@ -368,17 +368,28 @@ void EdenServiceHandler::scmGetStatus(
}
void EdenServiceHandler::scmAdd(
std::vector<ScmAddRemoveError>& errorsToReport,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> path) {
std::unique_ptr<std::vector<std::string>> paths) {
auto dirstate = server_->getMount(*mountPoint)->getDirstate();
DCHECK(dirstate != nullptr) << "Failed to get dirstate for "
<< mountPoint.get();
dirstate->add(RelativePathPiece{*path});
std::vector<RelativePathPiece> relativePaths;
for (auto& path : *paths.get()) {
relativePaths.emplace_back(path);
}
std::vector<DirstateAddRemoveError> dirstateErrorsToReport;
dirstate->addAll(relativePaths, &dirstateErrorsToReport);
for (auto& error : dirstateErrorsToReport) {
errorsToReport.emplace_back();
errorsToReport.back().path = error.path.stringPiece().str();
errorsToReport.back().errorMessage = error.errorMessage;
}
}
void EdenServiceHandler::scmRemove(
std::vector<ScmRemoveError>& errorsToReport,
std::vector<ScmAddRemoveError>& errorsToReport,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::vector<std::string>> paths,
bool force) {
@ -390,8 +401,8 @@ void EdenServiceHandler::scmRemove(
for (auto& path : *paths.get()) {
relativePaths.emplace_back(path);
}
std::vector<DirstateRemoveError> dirstateErrorsToReport;
dirstate->removeAll(&relativePaths, force, dirstateErrorsToReport);
std::vector<DirstateAddRemoveError> dirstateErrorsToReport;
dirstate->removeAll(relativePaths, force, &dirstateErrorsToReport);
for (auto& error : dirstateErrorsToReport) {
errorsToReport.emplace_back();
errorsToReport.back().path = error.path.stringPiece().str();

View File

@ -71,11 +71,12 @@ class EdenServiceHandler : virtual public EdenServiceSvIf,
std::unique_ptr<std::string> mountPoint) override;
void scmAdd(
std::vector<ScmAddRemoveError>& errorsToReport,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> path) override;
std::unique_ptr<std::vector<std::string>> paths) override;
void scmRemove(
std::vector<ScmRemoveError>& errorsToReport,
std::vector<ScmAddRemoveError>& errorsToReport,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::vector<std::string>> paths,
bool force) override;

View File

@ -116,7 +116,7 @@ struct ThriftHgStatus {
* Note that the error message always contains the path, so it can be displayed
* to the user verbatim without having to prefix it with the path explicitly.
*/
struct ScmRemoveError {
struct ScmAddRemoveError {
1: string path
2: string errorMessage
}
@ -199,12 +199,12 @@ service EdenService extends fb303.FacebookService {
// TODO(mbolin): `hg status` has a ton of command line flags to support.
ThriftHgStatus scmGetStatus(1: string mountPoint) throws (1: EdenError ex)
void scmAdd(
list<ScmAddRemoveError> scmAdd(
1: string mountPoint,
2: string path
2: list<string> paths, // May be files or directories.
) throws (1: EdenError ex)
list<ScmRemoveError> scmRemove(
list<ScmAddRemoveError> scmRemove(
1: string mountPoint,
2: list<string> paths, // May be files or directories.
3: bool force