sapling/eden/fs/service/EdenServiceHandler.h
Michael Bolin 5e2afa735f Change how the UNTRACKED_ADDED conflict and merges are handled.
Summary:
Previously, we used the Mercurial code `g` when faced with an `UNTRACKED_ADDED`
file conflict, but that was allowing merges to silently succeed that should not
have. This revision changes our logic to use the code `m` for merge, which
unearthed that we were not honoring the user's `update.check` setting properly.

Because we use `update.check=noconflict` internally at Facebook, we changed the
Eden integration tests to default to verifying Hg running with this setting. To
support it properly, we had to port this code from `update.py` in Mercurial to
our own `_determine_actions_for_conflicts()` function:

```
if updatecheck == 'noconflict':
    for f, (m, args, msg) in actionbyfile.iteritems():
        if m not in ('g', 'k', 'e', 'r', 'pr'):
            msg = _("conflicting changes")
            hint = _("commit or update --clean to discard changes")
            raise error.Abort(msg, hint=hint)
```

However, this introduced an interesting issue where the `checkOutRevision()`
Thrift call from Hg would update the `SNAPSHOT` file on the server, but
`.hg/dirstate` would not get updated with the new parents until the update
completed on the client. With the new call to `raise error.Abort` on the client,
we could get in a state where the `SNAPSHOT` file had the hash of the commit
assuming the update succeeded, but `.hg/dirstate` reflected the reality where it
failed.

To that end, we changed `checkOutRevision()` to take a new parameter,
`checkoutMode`, which can take on one of three values: `NORMAL`, `DRY_RUN`, and
`FORCE`. Now if the user tries to do an ordinary `hg update` with
`update.check=noconflict`, we first do a `DRY_RUN` and examine the potential
conflicts. Only if the conflicts should not block the update do we proceed with
a call to `checkOutRevision()` in `NORMAL` mode.

To make this work, we had to make a number of changes to `CheckoutAction`,
`CheckoutContext`, `EdenMount`, and `TreeInode` to keep track of the
`checkoutMode` and ensure that no changes are made to the working copy when a
`DRY_RUN` is in effect.

One minor issue (for which there is a `TODO`) is that a `DRY_RUN` will not
report any `DIRECTORY_NOT_EMPTY` conflicts that may exist. As `TreeInode` is
implemented today, it is a bit messy to report this type of conflict without
modifying the working copy along the way.

Finally, any `UNTRACKED_ADDED` conflict should cause an update to
abort to match the behavior in stock Mercurial if the user has the following
config setting:

```
[commands]
update.check = noconflict
```

Though the original name for this setting was:

```
[experimental]
updatecheck = noconflict
```

Although I am on Mercurial 4.4.1, the `update.check` setting does not seem to
take effect when I run the integration tests, but the `updatecheck` setting
does, so for now, I set both in `hg_extension_test_base.py` with a `TODO` to
remove `updatecheck` once I can get `update.check` to do its job.

Reviewed By: simpkins

Differential Revision: D6366007

fbshipit-source-id: bb3ecb1270e77d59d7d9e7baa36ada61971bbc49
2017-11-29 21:50:34 -08:00

179 lines
5.3 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include "common/fb303/cpp/FacebookBase2.h"
#include "eden/fs/service/gen-cpp2/StreamingEdenService.h"
#include "eden/fs/utils/PathFuncs.h"
namespace folly {
template <typename T>
class Future;
}
namespace facebook {
namespace eden {
class Hash;
class EdenMount;
class EdenServer;
class TreeInode;
/*
* Handler for the EdenService thrift interface
*/
class EdenServiceHandler : virtual public StreamingEdenServiceSvIf,
public facebook::fb303::FacebookBase2 {
public:
explicit EdenServiceHandler(EdenServer* server);
facebook::fb303::cpp2::fb_status getStatus() override;
void mount(std::unique_ptr<MountInfo> info) override;
void unmount(std::unique_ptr<std::string> mountPoint) override;
void listMounts(std::vector<MountInfo>& results) override;
void checkOutRevision(
std::vector<CheckoutConflict>& results,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> hash,
CheckoutMode checkoutMode) override;
void resetParentCommits(
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<WorkingDirectoryParents> parents) override;
void getBindMounts(
std::vector<std::string>& out,
std::unique_ptr<std::string> mountPoint) override;
void getSHA1(
std::vector<SHA1Result>& out,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::vector<std::string>> paths) override;
void getCurrentJournalPosition(
JournalPosition& out,
std::unique_ptr<std::string> mountPoint) override;
void getFilesChangedSince(
FileDelta& out,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<JournalPosition> fromPosition) override;
void getFileInformation(
std::vector<FileInformationOrError>& out,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::vector<std::string>> paths) override;
void glob(
std::vector<std::string>& out,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::vector<std::string>> globs) override;
void async_tm_subscribe(
std::unique_ptr<apache::thrift::StreamingHandlerCallback<
std::unique_ptr<JournalPosition>>> callback,
std::unique_ptr<std::string> mountPoint) override;
void getManifestEntry(
ManifestEntry& out,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> relativePath) override;
folly::Future<std::unique_ptr<ScmStatus>> future_getScmStatus(
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,
std::unique_ptr<std::string> id,
bool localStoreOnly) override;
void debugGetScmBlob(
std::string& data,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> id,
bool localStoreOnly) override;
void debugGetScmBlobMetadata(
ScmBlobMetadata& metadata,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> id,
bool localStoreOnly) override;
void debugInodeStatus(
std::vector<TreeInodeDebugInfo>& inodeInfo,
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> path) override;
void debugGetInodePath(
InodePathDebugInfo& inodePath,
std::unique_ptr<std::string> mountPoint,
int64_t inodeNumber) override;
void debugSetLogLevel(
SetLogLevelResult& result,
std::unique_ptr<std::string> category,
std::unique_ptr<std::string> level) override;
int64_t unloadInodeForPath(
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> path,
std::unique_ptr<TimeSpec> age) override;
void flushStatsNow() override;
void invalidateKernelInodeCache(
std::unique_ptr<std::string> mountPoint,
std::unique_ptr<std::string> path) override;
void getStatInfo(InternalStats& result) override;
/**
* When this Thrift handler is notified to shutdown, it notifies the
* EdenServer to shut down, as well.
*/
void shutdown() override;
private:
// Forbidden copy constructor and assignment operator
EdenServiceHandler(EdenServiceHandler const&) = delete;
EdenServiceHandler& operator=(EdenServiceHandler const&) = delete;
folly::Future<Hash> getSHA1ForPath(
folly::StringPiece mountPoint,
folly::StringPiece path);
folly::Future<Hash> getSHA1ForPathDefensively(
folly::StringPiece mountPoint,
folly::StringPiece path) noexcept;
/**
* If `filename` exists in the manifest as a file (not a directory), returns
* the mode of the file as recorded in the manifest.
*/
folly::Optional<mode_t> isInManifestAsFile(
const EdenMount* mount,
const RelativePathPiece filename);
EdenServer* const server_;
};
} // namespace eden
} // namespace facebook