mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 07:17:55 +03:00
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:
parent
970159aa5c
commit
dfd1634170
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user