add scmGetStatusBetweenRevisions thrift call

Summary:
The goal is to provide a fast path for watchman to flesh
out the total set of changed files when it needs relay that information
on to consumers.

We choose not to include the full list in the Journal when checking out
between revisions because it will not always be needed and may be an
expensive `O(repo)` operation to compute.  This means that watchman
needs to expand that information for itself, and that is currently
a fairly slow query to invoke through mercurial.

Since watchman is responding to journal events from eden we know that
we have tree data for the old and new hashes and thus we should be
able to efficiently compute that diff.

This implementation is slightly awful because it will instantiate an
unlinked TreeInode object for one side of the query, and will in
turn populate any children that differ as it walks down the tree.
A follow on diff will look at making a flavor of the diff code that
can diff raw Tree objects instead.

Reviewed By: bolinfest

Differential Revision: D6305844

fbshipit-source-id: 7506c9ba1f4febebcdc283c414261810a3951588
This commit is contained in:
Wez Furlong 2017-11-28 19:29:14 -08:00 committed by Facebook Github Bot
parent 9e5b839243
commit 28e74f1ba6
8 changed files with 131 additions and 3 deletions

View File

@ -116,5 +116,15 @@ folly::Future<std::unique_ptr<ScmStatus>> diffMountForStatus(
});
}
folly::Future<std::unique_ptr<ScmStatus>>
diffRevisions(EdenMount* mount, const Hash& fromHash, const Hash& toHash) {
auto callback = std::make_unique<ThriftStatusCallback>();
auto callbackPtr = callback.get();
return mount->diffRevisions(callbackPtr, fromHash, toHash)
.then([callback = std::move(callback)]() {
return std::make_unique<ScmStatus>(callback->extractStatus());
});
}
} // namespace eden
} // namespace facebook

View File

@ -9,6 +9,7 @@
*/
#pragma once
#include <iosfwd>
#include "eden/fs/model/Hash.h"
#include "eden/fs/service/gen-cpp2/EdenService.h"
namespace folly {
@ -33,5 +34,8 @@ folly::Future<std::unique_ptr<ScmStatus>> diffMountForStatus(
const EdenMount* mount,
bool listIgnored);
folly::Future<std::unique_ptr<ScmStatus>>
diffRevisions(EdenMount* mount, const Hash& fromHash, const Hash& toHash);
} // namespace eden
} // namespace facebook

View File

@ -476,6 +476,38 @@ Future<Unit> EdenMount::diff(InodeDiffCallback* callback, bool listIgnored)
.ensure(std::move(stateHolder));
}
folly::Future<folly::Unit> EdenMount::diffRevisions(
InodeDiffCallback* callback,
Hash fromHash,
Hash toHash) {
auto fromTreeFuture = objectStore_->getTreeForCommit(fromHash);
auto toTreeFuture = objectStore_->getTreeForCommit(toHash);
auto context = make_unique<DiffContext>(
callback, /*listIgnored=*/false, getObjectStore());
const DiffContext* ctxPtr = context.get();
// stateHolder() exists to ensure that the DiffContext and GitIgnoreStack
// exists until the diff completes.
auto stateHolder = [ctx = std::move(context)]() {};
return collectAll(fromTreeFuture, toTreeFuture)
.then([this, ctxPtr](std::tuple<
folly::Try<std::shared_ptr<const Tree>>,
folly::Try<std::shared_ptr<const Tree>>>& tup) {
auto fromTree = std::get<0>(tup).value();
auto toTree = std::get<1>(tup).value();
auto rootInode = TreeInodePtr::makeNew(this, std::move(fromTree));
return rootInode->diff(
ctxPtr,
RelativePathPiece{},
std::move(toTree),
ctxPtr->getToplevelIgnore(),
false);
})
.ensure(std::move(stateHolder));
}
void EdenMount::resetParents(const ParentCommits& parents) {
// Hold the snapshot lock around the entire operation.
auto parentsLock = parentInfo_.wlock();

View File

@ -268,17 +268,34 @@ class EdenMount {
* multiple different threads, and the callback is responsible for
* performing synchronization (if it is needed).
* @param listIgnored Whether or not to inform the callback of ignored files.
* When listIgnored to false can speed up the diff computation, as the
* code does not need to descend into ignord directories at all.
* When listIgnored is set to false can speed up the diff computation, as
* the code does not need to descend into ignored directories at all.
*
* @return Returns a folly::Future that will be fulfilled when the diff
* operation is complete. This is marked FOLLY_NODISCARD to
* make sure callers do not forget to wait for the operatio to complete.
* make sure callers do not forget to wait for the operation to complete.
*/
FOLLY_NODISCARD folly::Future<folly::Unit> diff(
InodeDiffCallback* callback,
bool listIgnored = false) const;
/**
* Compute the differences between the trees in the specified commits.
* This does not care about the working copy aside from using it as the
* source of the backing store for the commits.
*
* @param callback This callback will be invoked as differences are found.
* Note that the callback methods may be invoked simultaneously from
* multiple different threads, and the callback is responsible for
* performing synchronization (if it is needed).
*
* @return Returns a folly::Future that will be fulfilled when the diff
* operation is complete. This is marked FOLLY_NODISCARD to
* make sure callers do not forget to wait for the operation to complete.
*/
FOLLY_NODISCARD folly::Future<folly::Unit>
diffRevisions(InodeDiffCallback* callback, Hash fromHash, Hash toHash);
/**
* Reset the state to point to the specified parent commit(s), without
* modifying the working directory contents at all.

View File

@ -15,9 +15,12 @@
#include "eden/fs/inodes/FileInode.h"
#include "eden/fs/inodes/InodeDiffCallback.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/testharness/FakeBackingStore.h"
#include "eden/fs/testharness/FakeTreeBuilder.h"
#include "eden/fs/testharness/StoredObject.h"
#include "eden/fs/testharness/TestChecks.h"
#include "eden/fs/testharness/TestMount.h"
#include "eden/fs/testharness/TestUtil.h"
using namespace facebook::eden;
using ::testing::UnorderedElementsAre;
@ -951,3 +954,37 @@ TEST(DiffTest, ignoreHidden) {
EXPECT_THAT(
result.getModified(), UnorderedElementsAre(RelativePath{"a/c/1.txt"}));
}
TEST(DiffTest, diffRevisions) {
DiffTest test;
auto firstCommitHash = makeTestHash("a");
auto secondCommitHash = makeTestHash("b");
auto firstRoot = test.getBuilder().getRoot();
auto firstCommit =
test.getMount().getBackingStore()->putCommit(firstCommitHash, firstRoot);
firstCommit->setReady();
auto b2 = test.getBuilder().clone();
b2.replaceFile("src/1.txt", "This file has been updated.\n");
auto newRoot =
b2.finalize(test.getMount().getBackingStore(), /*setReady=*/true);
auto secondCommit =
test.getMount().getBackingStore()->putCommit(secondCommitHash, newRoot);
secondCommit->setReady();
DiffResultsCallback callback;
auto diffFuture = test.getMount().getEdenMount()->diffRevisions(
&callback, firstCommitHash, secondCommitHash);
EXPECT_FUTURE_RESULT(diffFuture);
auto result = callback.extractResults();
EXPECT_THAT(result.getErrors(), UnorderedElementsAre());
EXPECT_THAT(result.getUntracked(), UnorderedElementsAre());
EXPECT_THAT(result.getIgnored(), UnorderedElementsAre());
EXPECT_THAT(result.getRemoved(), UnorderedElementsAre());
EXPECT_THAT(
result.getModified(), UnorderedElementsAre(RelativePath{"src/1.txt"}));
}

View File

@ -428,6 +428,19 @@ EdenServiceHandler::future_getScmStatus(
return diffMountForStatus(mount.get(), listIgnored);
}
folly::Future<std::unique_ptr<ScmStatus>>
EdenServiceHandler::future_getScmStatusBetweenRevisions(
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> oldHash,
std::unique_ptr<std::string> newHash) {
INSTRUMENT_THRIFT_CALL(
DBG2,
*mountPoint,
folly::format("oldHash={}, newHash={}", *oldHash, *newHash));
auto mount = server_->getMount(*mountPoint);
return diffRevisions(mount.get(), Hash{*oldHash}, Hash{*newHash});
}
void EdenServiceHandler::debugGetScmTree(
vector<ScmTreeEntry>& entries,
unique_ptr<string> mountPoint,

View File

@ -94,6 +94,11 @@ class EdenServiceHandler : virtual public StreamingEdenServiceSvIf,
std::unique_ptr<std::string> mountPoint,
bool listIgnored) override;
folly::Future<std::unique_ptr<ScmStatus>> future_getScmStatusBetweenRevisions(
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> oldHash,
std::unique_ptr<std::string> newHash) override;
void debugGetScmTree(
std::vector<ScmTreeEntry>& entries,
std::unique_ptr<std::string> mountPoint,

View File

@ -389,6 +389,16 @@ service EdenService extends fb303.FacebookService {
2: bool listIgnored,
) throws (1: EdenError ex)
/**
* Computes the status between two specified revisions.
* This does not care about the state of the working copy.
*/
ScmStatus getScmStatusBetweenRevisions(
1: string mountPoint,
2: BinaryHash oldHash,
3: BinaryHash newHash,
) throws (1: EdenError ex)
//////// SCM Commit-Related APIs ////////
/**