2017-02-16 07:31:48 +03:00
|
|
|
/*
|
2019-06-20 02:58:25 +03:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2017-02-16 07:31:48 +03:00
|
|
|
*
|
2019-06-20 02:58:25 +03:00
|
|
|
* This software may be used and distributed according to the terms of the
|
|
|
|
* GNU General Public License version 2.
|
2017-02-16 07:31:48 +03:00
|
|
|
*/
|
2019-10-11 15:26:59 +03:00
|
|
|
|
2017-02-16 07:31:48 +03:00
|
|
|
#include "eden/fs/inodes/CheckoutContext.h"
|
|
|
|
|
2018-05-01 07:20:51 +03:00
|
|
|
#include <folly/logging/xlog.h>
|
2018-03-27 21:12:59 +03:00
|
|
|
|
2019-12-11 05:11:21 +03:00
|
|
|
#include "eden/fs/config/CheckoutConfig.h"
|
2017-02-16 07:31:48 +03:00
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
|
|
#include "eden/fs/inodes/InodePtr.h"
|
|
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
|
|
|
|
|
|
using folly::Future;
|
|
|
|
using std::vector;
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
|
|
|
CheckoutContext::CheckoutContext(
|
2018-03-27 21:12:59 +03:00
|
|
|
EdenMount* mount,
|
2017-07-27 21:48:19 +03:00
|
|
|
folly::Synchronized<EdenMount::ParentInfo>::LockedPtr&& parentsLock,
|
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-30 08:38:12 +03:00
|
|
|
CheckoutMode checkoutMode)
|
2018-03-27 21:12:59 +03:00
|
|
|
: checkoutMode_{checkoutMode},
|
|
|
|
mount_{mount},
|
|
|
|
parentsLock_(std::move(parentsLock)) {}
|
2017-02-16 07:31:48 +03:00
|
|
|
|
|
|
|
CheckoutContext::~CheckoutContext() {}
|
|
|
|
|
|
|
|
void CheckoutContext::start(RenameLock&& renameLock) {
|
|
|
|
renameLock_ = std::move(renameLock);
|
|
|
|
}
|
|
|
|
|
2018-03-27 21:12:59 +03:00
|
|
|
Future<vector<CheckoutConflict>> CheckoutContext::finish(Hash newSnapshot) {
|
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-30 08:38:12 +03:00
|
|
|
// Only update the parents if it is not a dry run.
|
|
|
|
if (!isDryRun()) {
|
2019-12-11 05:11:21 +03:00
|
|
|
auto oldParents = parentsLock_->parents;
|
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-30 08:38:12 +03:00
|
|
|
// Update the in-memory snapshot ID
|
|
|
|
parentsLock_->parents.setParents(newSnapshot);
|
2019-12-11 05:11:21 +03:00
|
|
|
|
|
|
|
auto config = mount_->getConfig();
|
|
|
|
// Save the new snapshot hash to the config
|
|
|
|
config->setParentCommits(newSnapshot);
|
|
|
|
XLOG(DBG1) << "updated snapshot for " << config->getMountPath() << " from "
|
|
|
|
<< oldParents << " to " << newSnapshot;
|
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-30 08:38:12 +03:00
|
|
|
}
|
2017-02-16 07:31:48 +03:00
|
|
|
|
2018-03-27 21:12:59 +03:00
|
|
|
// Release the rename lock.
|
|
|
|
// This allows any filesystem unlink() or rename() operations to proceed.
|
2017-02-16 07:31:48 +03:00
|
|
|
renameLock_.unlock();
|
2018-03-27 21:12:59 +03:00
|
|
|
|
2020-04-02 00:51:06 +03:00
|
|
|
#ifndef _WIN32
|
2018-03-27 21:12:59 +03:00
|
|
|
// If we have a FUSE channel, flush all invalidations we sent to the kernel
|
|
|
|
// as part of the checkout operation. This will ensure that other processes
|
|
|
|
// will see up-to-date data once we return.
|
|
|
|
//
|
|
|
|
// We do this after releasing the rename lock since some of the invalidation
|
|
|
|
// operations may be blocked waiting on FUSE unlink() and rename() operations
|
|
|
|
// complete.
|
|
|
|
auto* fuseChannel = mount_->getFuseChannel();
|
|
|
|
if (!isDryRun() && fuseChannel) {
|
|
|
|
XLOG(DBG4) << "waiting for inode invalidations to complete";
|
2018-08-22 21:57:22 +03:00
|
|
|
return fuseChannel->flushInvalidations().thenValue([this](auto&&) {
|
2018-03-27 21:12:59 +03:00
|
|
|
XLOG(DBG4) << "finished processing inode invalidations";
|
|
|
|
parentsLock_.unlock();
|
|
|
|
return std::move(*conflicts_.wlock());
|
|
|
|
});
|
|
|
|
}
|
2020-04-02 00:51:06 +03:00
|
|
|
#endif
|
2018-03-27 21:12:59 +03:00
|
|
|
|
|
|
|
// Release the parentsLock_.
|
|
|
|
// Once this is released other checkout operations may proceed.
|
2017-04-27 21:43:34 +03:00
|
|
|
parentsLock_.unlock();
|
2017-02-16 07:31:48 +03:00
|
|
|
|
|
|
|
// Return conflicts_ via a move operation. We don't need them any more, and
|
|
|
|
// can give ownership directly to our caller.
|
2017-03-15 23:02:23 +03:00
|
|
|
return std::move(*conflicts_.wlock());
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheckoutContext::addConflict(ConflictType type, RelativePathPiece path) {
|
|
|
|
// Errors should be added using addError()
|
|
|
|
CHECK(type != ConflictType::ERROR)
|
|
|
|
<< "attempted to add error using addConflict(): " << path;
|
|
|
|
|
|
|
|
CheckoutConflict conflict;
|
|
|
|
conflict.path = path.value().str();
|
|
|
|
conflict.type = type;
|
2017-03-15 23:02:23 +03:00
|
|
|
conflicts_.wlock()->push_back(std::move(conflict));
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheckoutContext::addConflict(
|
|
|
|
ConflictType type,
|
|
|
|
TreeInode* parent,
|
|
|
|
PathComponentPiece name) {
|
|
|
|
// addConflict() should never be called with an unlinked TreeInode.
|
|
|
|
//
|
|
|
|
// We are holding the RenameLock for the duration of the checkout operation,
|
|
|
|
// and we only operate on TreeInode's that still exist in the file system
|
|
|
|
// namespace. Therefore parent->getPath() must always return non-none value
|
|
|
|
// here.
|
|
|
|
auto parentPath = parent->getPath();
|
2018-10-24 04:48:38 +03:00
|
|
|
CHECK(parentPath.has_value());
|
2017-02-16 07:31:48 +03:00
|
|
|
|
|
|
|
addConflict(type, parentPath.value() + name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckoutContext::addConflict(ConflictType type, InodeBase* inode) {
|
|
|
|
// As above, the inode in question must have a path here.
|
|
|
|
auto path = inode->getPath();
|
2018-10-24 04:48:38 +03:00
|
|
|
CHECK(path.has_value());
|
2017-02-16 07:31:48 +03:00
|
|
|
addConflict(type, path.value());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckoutContext::addError(
|
|
|
|
TreeInode* parent,
|
|
|
|
PathComponentPiece name,
|
|
|
|
const folly::exception_wrapper& ew) {
|
|
|
|
// As above in addConflict(), the parent tree must have a valid path here.
|
|
|
|
auto parentPath = parent->getPath();
|
2018-10-24 04:48:38 +03:00
|
|
|
CHECK(parentPath.has_value());
|
2017-02-16 07:31:48 +03:00
|
|
|
|
|
|
|
auto path = parentPath.value() + name;
|
|
|
|
CheckoutConflict conflict;
|
2018-01-04 03:31:08 +03:00
|
|
|
conflict.path = path.value();
|
2017-02-16 07:31:48 +03:00
|
|
|
conflict.type = ConflictType::ERROR;
|
|
|
|
conflict.message = folly::exceptionStr(ew).toStdString();
|
2017-03-15 23:02:23 +03:00
|
|
|
conflicts_.wlock()->push_back(std::move(conflict));
|
2017-02-16 07:31:48 +03:00
|
|
|
}
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|