2016-11-02 03:48:26 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2016, 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "Dirstate.h"
|
2016-12-02 04:49:33 +03:00
|
|
|
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
#include <folly/Format.h>
|
2016-12-02 04:49:36 +03:00
|
|
|
#include <folly/experimental/StringKeyedUnorderedMap.h>
|
2016-11-19 06:24:45 +03:00
|
|
|
#include <folly/io/Cursor.h>
|
|
|
|
#include <folly/io/IOBuf.h>
|
2016-12-02 04:49:30 +03:00
|
|
|
#include "eden/fs/config/ClientConfig.h"
|
2016-12-02 04:49:33 +03:00
|
|
|
#include "eden/fs/inodes/DirstatePersistence.h"
|
2016-12-02 04:49:42 +03:00
|
|
|
#include "eden/fs/inodes/EdenDispatcher.h"
|
2016-12-02 04:49:30 +03:00
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
#include "eden/fs/inodes/EdenMounts.h"
|
2016-12-02 04:49:36 +03:00
|
|
|
#include "eden/fs/inodes/FileData.h"
|
2016-12-01 02:48:04 +03:00
|
|
|
#include "eden/fs/inodes/FileInode.h"
|
2016-11-02 03:48:26 +03:00
|
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
|
|
#include "eden/fs/model/Blob.h"
|
2016-12-02 04:49:36 +03:00
|
|
|
#include "eden/fs/model/git/GitIgnore.h"
|
|
|
|
#include "eden/fs/model/git/GitIgnorePattern.h"
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
#include "eden/fs/store/ObjectStore.h"
|
2016-11-02 03:48:26 +03:00
|
|
|
#include "eden/fs/store/ObjectStores.h"
|
|
|
|
#include "eden/fuse/MountPoint.h"
|
|
|
|
|
2016-12-02 04:49:36 +03:00
|
|
|
using folly::StringPiece;
|
|
|
|
|
2016-11-02 03:48:26 +03:00
|
|
|
namespace {
|
2016-12-10 02:40:45 +03:00
|
|
|
/**
|
|
|
|
* When comparing two file entries for equality from a source control
|
|
|
|
* perspective, exclude these bits when comparing their mode_t values.
|
|
|
|
* Specifically, we ignore the "group" and "others" permissions,
|
|
|
|
* as well as setuid, setgid, and the restricted deletion flag.
|
|
|
|
*/
|
|
|
|
constexpr mode_t kIgnoredModeBits =
|
|
|
|
(S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX);
|
|
|
|
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
/**
|
|
|
|
* Represents file (non-directory) changes in a directory. This reflects:
|
|
|
|
* - New file in directory.
|
|
|
|
* - File removed from directory (possibly replaced with directory of same
|
|
|
|
* name).
|
|
|
|
* - Subdirectory removed form directory (possibly replaced with file of same
|
|
|
|
* name).
|
|
|
|
*
|
|
|
|
* However, it does not reflect:
|
|
|
|
* - New subdirectory in directory.
|
|
|
|
*/
|
2016-11-02 03:48:26 +03:00
|
|
|
struct DirectoryDelta {
|
2016-11-19 06:24:45 +03:00
|
|
|
// The contents of each vector is sorted by compare().
|
2016-11-02 03:48:26 +03:00
|
|
|
std::vector<facebook::eden::PathComponent> added;
|
|
|
|
std::vector<facebook::eden::PathComponent> removed;
|
|
|
|
std::vector<facebook::eden::PathComponent> modified;
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
std::vector<facebook::eden::PathComponent> removedDirectories;
|
2016-11-02 03:48:26 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
std::string HgStatus::toString() const {
|
|
|
|
// Sort the entries in the map.
|
|
|
|
std::vector<std::pair<RelativePath, HgStatusCode>> entries(
|
|
|
|
statuses_.begin(), statuses_.end());
|
|
|
|
std::sort(entries.begin(), entries.end());
|
|
|
|
|
|
|
|
auto buf = folly::IOBuf::create(50 * entries.size());
|
|
|
|
folly::io::Appender appender(buf.get(), /* growSize */ 1024);
|
|
|
|
for (auto pair : entries) {
|
|
|
|
appender(HgStatusCode_toString(pair.second));
|
|
|
|
appender(" ");
|
|
|
|
appender(pair.first.stringPiece());
|
|
|
|
appender("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->moveToFbString().toStdString();
|
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:46 +03:00
|
|
|
namespace {
|
|
|
|
template <typename RelativePathType>
|
|
|
|
void updateManifestWithDirectives(
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
const std::unordered_map<RelativePathType, overlay::UserStatusDirective>*
|
2016-11-19 06:24:46 +03:00
|
|
|
unaccountedUserDirectives,
|
|
|
|
std::unordered_map<RelativePath, HgStatusCode>* manifest) {
|
|
|
|
// We should make sure that every entry in userDirectives_ is accounted for in
|
|
|
|
// the HgStatus that we return.
|
|
|
|
for (auto& pair : *unaccountedUserDirectives) {
|
|
|
|
switch (pair.second) {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Add:
|
2016-11-19 06:24:46 +03:00
|
|
|
// The file was marked for addition, but no longer exists in the working
|
|
|
|
// copy. The user should either restore the file or run `hg forget`.
|
|
|
|
manifest->emplace(RelativePath(pair.first), HgStatusCode::MISSING);
|
|
|
|
break;
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Remove:
|
2016-11-19 06:24:46 +03:00
|
|
|
// The file was marked for removal, but it still exists in the working
|
|
|
|
// copy without any modifications. Although it may seem strange, it
|
|
|
|
// should still show up as REMOVED in `hg status` even though it is
|
|
|
|
// still on disk.
|
2016-12-02 04:49:36 +03:00
|
|
|
//
|
|
|
|
// Note that even if the file matches an ignore pattern, we currently
|
|
|
|
// report it just as REMOVED. This matches mercurial's current
|
|
|
|
// behavior, but in the future it would probably be nicer to add a code
|
|
|
|
// for REMOVED+IGNORED.
|
2016-11-19 06:24:46 +03:00
|
|
|
manifest->emplace(RelativePath(pair.first), HgStatusCode::REMOVED);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
void processRemovedFile(
|
|
|
|
RelativePath pathToEntry,
|
|
|
|
std::unordered_map<RelativePath, HgStatusCode>* manifest,
|
|
|
|
const std::unordered_map<RelativePath, overlay::UserStatusDirective>*
|
|
|
|
userDirectives,
|
|
|
|
std::unordered_map<RelativePathPiece, overlay::UserStatusDirective>*
|
|
|
|
copyOfUserDirectives) {
|
|
|
|
auto result = userDirectives->find(pathToEntry);
|
|
|
|
if (result != userDirectives->end()) {
|
|
|
|
auto statusCode = result->second;
|
|
|
|
switch (statusCode) {
|
|
|
|
case overlay::UserStatusDirective::Add:
|
|
|
|
// TODO(mbolin): Is there any weird sequence of modifications with
|
|
|
|
// adding/removed files matched by .hgignore that could lead to this
|
|
|
|
// state?
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"Invariant violation: The user has marked {} for addition, "
|
|
|
|
"but it already exists in the manifest "
|
|
|
|
"(and is currently removed from disk).",
|
|
|
|
pathToEntry.stringPiece()));
|
|
|
|
case overlay::UserStatusDirective::Remove:
|
|
|
|
manifest->emplace(pathToEntry, HgStatusCode::REMOVED);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
copyOfUserDirectives->erase(pathToEntry);
|
|
|
|
} else {
|
|
|
|
// The file is not present on disk, but the user never ran `hg rm`.
|
|
|
|
manifest->emplace(pathToEntry, HgStatusCode::MISSING);
|
|
|
|
}
|
|
|
|
}
|
2016-12-02 04:49:36 +03:00
|
|
|
|
|
|
|
// Short-term helper class until we implement gitignore
|
|
|
|
// handling more efficiently.
|
|
|
|
class IgnoreChecker {
|
|
|
|
public:
|
|
|
|
explicit IgnoreChecker(EdenMount* mountPoint) : mountPoint_(mountPoint) {}
|
|
|
|
|
|
|
|
bool isIgnored(RelativePathPiece path) {
|
|
|
|
// If a path's parent directory is ignored, it is ignored
|
|
|
|
// (as long as it hasn't been explicitly added to the dirstate, which
|
|
|
|
// we already check before calling IgnoreChecker).
|
|
|
|
//
|
|
|
|
// Note that this is a potentially expensive recursion.
|
|
|
|
// We could memoize the results for directories. (Although eventually we
|
|
|
|
// should just store this data directly in the Inode objects.)
|
|
|
|
if (path.stringPiece().empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (isIgnored(path.dirname())) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
StringPiece piece = path.stringPiece();
|
|
|
|
auto idx = piece.rfind('/');
|
|
|
|
while (true) {
|
|
|
|
StringPiece dirName;
|
|
|
|
StringPiece childName;
|
|
|
|
if (idx == StringPiece::npos) {
|
|
|
|
dirName = StringPiece();
|
|
|
|
childName = piece;
|
|
|
|
} else {
|
|
|
|
dirName = StringPiece(piece.begin(), idx);
|
|
|
|
childName = StringPiece(piece.begin() + idx + 1, piece.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
VLOG(5) << "Check ignored: \"" << childName << "\" in \"" << dirName
|
|
|
|
<< "\"";
|
|
|
|
const GitIgnore* ignore = getIgnoreData(dirName);
|
|
|
|
auto matchResult = ignore->match(
|
|
|
|
RelativePathPiece(childName, detail::SkipPathSanityCheck()));
|
|
|
|
if (matchResult == GitIgnore::INCLUDE) {
|
|
|
|
// Explicitly included. We don't need to check parent directories.
|
|
|
|
return false;
|
|
|
|
} else if (matchResult == GitIgnore::EXCLUDE) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (idx == StringPiece::npos) {
|
|
|
|
// We checked everything up to the root. The path is not ignored.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
idx = dirName.rfind('/');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const GitIgnore* getIgnoreData(StringPiece directory) {
|
|
|
|
auto it = ignoreCache_.find(directory);
|
|
|
|
if (it != ignoreCache_.end()) {
|
|
|
|
return &(it->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
GitIgnore ignore;
|
|
|
|
loadIgnoreFile(directory, &ignore);
|
|
|
|
auto ret = ignoreCache_.emplace(directory, std::move(ignore));
|
|
|
|
return &(ret.first->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
void loadIgnoreFile(StringPiece directory, GitIgnore* ignore) {
|
|
|
|
// Ugh. This is rather inefficient.
|
|
|
|
auto ignorePath =
|
|
|
|
RelativePath(directory) + PathComponentPiece(".gitignore");
|
|
|
|
VLOG(4) << "Loading ignore file at \"" << ignorePath << "\"";
|
|
|
|
std::shared_ptr<FileInode> ignoreInode;
|
|
|
|
try {
|
2016-12-02 04:49:38 +03:00
|
|
|
ignoreInode = mountPoint_->getFileInode(ignorePath);
|
2016-12-02 04:49:36 +03:00
|
|
|
} catch (const std::system_error& ex) {
|
|
|
|
if (ex.code().category() != std::system_category() ||
|
|
|
|
(ex.code().value() != ENOENT && ex.code().value() != ENOTDIR)) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ignoreInode) {
|
|
|
|
// No gitignore file to load.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto data = ignoreInode->getOrLoadData();
|
|
|
|
data->materializeForRead(O_RDONLY, ignorePath, mountPoint_->getOverlay());
|
|
|
|
ignore->loadFile(data->readAll());
|
|
|
|
}
|
|
|
|
|
|
|
|
EdenMount* const mountPoint_{nullptr};
|
|
|
|
folly::StringKeyedUnorderedMap<GitIgnore> ignoreCache_;
|
|
|
|
};
|
2016-12-02 04:49:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Dirstate::Dirstate(EdenMount* mount)
|
2016-12-02 04:49:34 +03:00
|
|
|
: mount_(mount),
|
2016-12-02 04:49:33 +03:00
|
|
|
persistence_(mount->getConfig()->getDirstateStoragePath()) {
|
|
|
|
auto loadedData = persistence_.load();
|
|
|
|
userDirectives_.wlock()->swap(loadedData);
|
2016-12-02 04:49:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Dirstate::~Dirstate() {}
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
std::unique_ptr<HgStatus> Dirstate::getStatus() const {
|
2016-11-02 03:48:26 +03:00
|
|
|
// Find the modified directories in the overlay and compare them with what is
|
|
|
|
// in the root tree.
|
2016-12-02 04:49:37 +03:00
|
|
|
auto modifiedDirectories = getModifiedDirectoriesForMount(mount_);
|
2016-11-19 06:24:46 +03:00
|
|
|
std::unordered_map<RelativePath, HgStatusCode> manifest;
|
2016-11-02 03:48:26 +03:00
|
|
|
if (modifiedDirectories->empty()) {
|
2016-11-19 06:24:46 +03:00
|
|
|
auto userDirectives = userDirectives_.rlock();
|
|
|
|
updateManifestWithDirectives(&*userDirectives, &manifest);
|
|
|
|
return std::make_unique<HgStatus>(std::move(manifest));
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
auto userDirectives = userDirectives_.rlock();
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
std::unordered_map<RelativePathPiece, overlay::UserStatusDirective>
|
2016-11-19 06:24:45 +03:00
|
|
|
copyOfUserDirectives(userDirectives->begin(), userDirectives->end());
|
2016-11-02 03:48:26 +03:00
|
|
|
|
2016-12-02 04:49:36 +03:00
|
|
|
// TODO: This code is somewhat inefficient.
|
|
|
|
// We ideally should restructure this so that we can compute diff and ignore
|
|
|
|
// data in a single pass through the tree. (We shouldn't have separate walks
|
|
|
|
// in getModifiedDirectoriesForMount(), then the for loop below, plus
|
|
|
|
// additional computation to look up ignore data.)
|
|
|
|
//
|
|
|
|
// Doing this all at once would also make it possible to completely skip
|
|
|
|
// ignored directories that have no tracked files inside of them. Currently
|
|
|
|
// we can't do this. getModifiedDirectoriesForMount() has to descend into
|
|
|
|
// ignored directories because it doesn't know if userDirectives_ contains
|
|
|
|
// entries for files in these directories or not.
|
|
|
|
IgnoreChecker ignoreChecker(mount_);
|
|
|
|
|
2016-12-02 04:49:34 +03:00
|
|
|
auto rootTree = mount_->getRootTree();
|
2016-11-02 03:48:26 +03:00
|
|
|
for (auto& directory : *modifiedDirectories) {
|
|
|
|
// Get the directory as a TreeInode.
|
2016-12-02 04:49:34 +03:00
|
|
|
auto treeInode = mount_->getTreeInode(directory);
|
2016-11-26 23:00:19 +03:00
|
|
|
DCHECK(treeInode.get() != nullptr) << "Failed to get a TreeInode for "
|
|
|
|
<< directory;
|
2016-11-02 03:48:26 +03:00
|
|
|
|
|
|
|
// Get the directory as a Tree.
|
2016-12-02 04:49:34 +03:00
|
|
|
auto tree = getTreeForDirectory(
|
|
|
|
directory, rootTree.get(), mount_->getObjectStore());
|
2016-11-02 03:48:26 +03:00
|
|
|
DirectoryDelta delta;
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
std::vector<TreeEntry> emptyEntries;
|
|
|
|
// Note that if tree is NULL, then the directory must be new in the working
|
|
|
|
// copy because there is no corresponding Tree in the manifest. Defining
|
|
|
|
// treeEntries in this way avoids a heap allocation when tree is NULL.
|
|
|
|
const auto* treeEntries = tree ? &tree->getTreeEntries() : &emptyEntries;
|
|
|
|
computeDelta(treeEntries, *treeInode, delta);
|
|
|
|
|
|
|
|
for (auto& removedDirectory : delta.removedDirectories) {
|
|
|
|
// Must find the Tree that corresponds to removedDirectory and add
|
|
|
|
// everything under it as REMOVED or MISSING in the manifest, as
|
|
|
|
// appropriate.
|
|
|
|
auto entry = tree->getEntryPtr(removedDirectory);
|
|
|
|
auto subdirectory = directory + removedDirectory;
|
|
|
|
DCHECK(entry != nullptr) << "Failed to find TreeEntry for "
|
|
|
|
<< subdirectory;
|
|
|
|
DCHECK(entry->getType() == TreeEntryType::TREE)
|
|
|
|
<< "Removed directory " << subdirectory
|
|
|
|
<< " did not correspond to a Tree.";
|
2016-12-02 04:49:34 +03:00
|
|
|
auto removedTree = mount_->getObjectStore()->getTree(entry->getHash());
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
addDeletedEntries(
|
|
|
|
removedTree.get(),
|
|
|
|
subdirectory,
|
|
|
|
&manifest,
|
|
|
|
&*userDirectives,
|
|
|
|
©OfUserDirectives);
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
// Files in delta.added fall into one of three categories:
|
|
|
|
// 1. ADDED
|
|
|
|
// 2. NOT_TRACKED
|
|
|
|
// 3. IGNORED
|
2016-11-02 03:48:26 +03:00
|
|
|
for (auto& addedPath : delta.added) {
|
|
|
|
auto pathToEntry = directory + addedPath;
|
2016-11-19 06:24:45 +03:00
|
|
|
auto result = userDirectives->find(pathToEntry);
|
|
|
|
if (result != userDirectives->end()) {
|
2016-11-02 03:48:26 +03:00
|
|
|
auto statusCode = result->second;
|
2016-11-19 06:24:45 +03:00
|
|
|
switch (statusCode) {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Add:
|
2016-11-19 06:24:45 +03:00
|
|
|
manifest.emplace(pathToEntry, HgStatusCode::ADDED);
|
|
|
|
break;
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Remove:
|
2016-11-19 06:24:45 +03:00
|
|
|
// TODO(mbolin): Is there any weird sequence of modifications with
|
|
|
|
// adding/removed files matched by .hgignore that could lead to this
|
|
|
|
// state?
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"Invariant violation: The user has marked {} for removal, "
|
|
|
|
"but it does not exist in the manifest.",
|
|
|
|
pathToEntry.stringPiece()));
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
2016-11-19 06:24:45 +03:00
|
|
|
copyOfUserDirectives.erase(pathToEntry);
|
2016-12-02 04:49:36 +03:00
|
|
|
} else if (ignoreChecker.isIgnored(pathToEntry)) {
|
|
|
|
manifest.emplace(pathToEntry, HgStatusCode::IGNORED);
|
2016-11-02 03:48:26 +03:00
|
|
|
} else {
|
|
|
|
manifest.emplace(pathToEntry, HgStatusCode::NOT_TRACKED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-02 04:49:36 +03:00
|
|
|
// Files in delta.modified fall into one of two categories:
|
2016-11-19 06:24:45 +03:00
|
|
|
// 1. MODIFIED
|
|
|
|
// 2. REMOVED
|
2016-12-02 04:49:36 +03:00
|
|
|
//
|
|
|
|
// Technically, a file might be both REMOVED and IGNORED if the user asked
|
|
|
|
// us to remove the file from source control tracking, but it still exists
|
|
|
|
// in the file system, and it matches an ignore pattern. Unfortunately
|
|
|
|
// mercurial doesn't really represent the state properly in this case, and
|
|
|
|
// just lists the file REMOVED, and not IGNORED. (This does result in some
|
|
|
|
// bugs: "hg clean --all" won't actually delete the file in this case.)
|
|
|
|
//
|
|
|
|
// Eventually it might be nice if we represent this state properly in our
|
|
|
|
// thrift API. For now though the mercurial code can't make use of it
|
|
|
|
// properly though.
|
2016-11-02 03:48:26 +03:00
|
|
|
for (auto& modifiedPath : delta.modified) {
|
|
|
|
auto pathToEntry = directory + modifiedPath;
|
2016-11-19 06:24:45 +03:00
|
|
|
auto result = userDirectives->find(pathToEntry);
|
|
|
|
if (result != userDirectives->end()) {
|
|
|
|
auto statusCode = result->second;
|
|
|
|
switch (statusCode) {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Add:
|
2016-11-19 06:24:45 +03:00
|
|
|
// TODO(mbolin): Is there any weird sequence of modifications with
|
|
|
|
// adding/removed files matched by .hgignore that could lead to this
|
|
|
|
// state?
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"Invariant violation: The user has marked {} for addition, "
|
|
|
|
"but it already exists in the manifest.",
|
|
|
|
pathToEntry.stringPiece()));
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Remove:
|
2016-11-19 06:24:45 +03:00
|
|
|
manifest.emplace(pathToEntry, HgStatusCode::REMOVED);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
copyOfUserDirectives.erase(pathToEntry);
|
|
|
|
} else {
|
|
|
|
manifest.emplace(pathToEntry, HgStatusCode::MODIFIED);
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-12-02 04:49:36 +03:00
|
|
|
// Files in delta.removed fall into one of two categories:
|
2016-11-19 06:24:45 +03:00
|
|
|
// 1. REMOVED
|
|
|
|
// 2. MISSING
|
|
|
|
for (auto& removedPath : delta.removed) {
|
|
|
|
auto pathToEntry = directory + removedPath;
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
processRemovedFile(
|
|
|
|
pathToEntry, &manifest, &*userDirectives, ©OfUserDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:46 +03:00
|
|
|
updateManifestWithDirectives(©OfUserDirectives, &manifest);
|
2016-11-02 03:48:26 +03:00
|
|
|
|
|
|
|
return std::make_unique<HgStatus>(std::move(manifest));
|
|
|
|
}
|
|
|
|
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
void Dirstate::addDeletedEntries(
|
|
|
|
const Tree* tree,
|
|
|
|
RelativePathPiece pathToTree,
|
|
|
|
std::unordered_map<RelativePath, HgStatusCode>* manifest,
|
|
|
|
const std::unordered_map<RelativePath, overlay::UserStatusDirective>*
|
|
|
|
userDirectives,
|
|
|
|
std::unordered_map<RelativePathPiece, overlay::UserStatusDirective>*
|
|
|
|
copyOfUserDirectives) const {
|
|
|
|
for (auto& entry : tree->getTreeEntries()) {
|
|
|
|
auto pathToEntry = pathToTree + entry.getName();
|
|
|
|
if (entry.getType() == TreeEntryType::BLOB) {
|
|
|
|
processRemovedFile(
|
|
|
|
pathToEntry, manifest, userDirectives, copyOfUserDirectives);
|
|
|
|
} else {
|
2016-12-02 04:49:34 +03:00
|
|
|
auto subtree = mount_->getObjectStore()->getTree(entry.getHash());
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
addDeletedEntries(
|
|
|
|
subtree.get(),
|
|
|
|
pathToEntry,
|
|
|
|
manifest,
|
|
|
|
userDirectives,
|
|
|
|
copyOfUserDirectives);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Assumes that treeEntry and treeInode correspond to the same path. Returns
|
|
|
|
* true if both the mode_t and file contents match for treeEntry and treeInode.
|
|
|
|
*/
|
2016-11-02 03:48:26 +03:00
|
|
|
bool hasMatchingAttributes(
|
2016-11-19 06:24:45 +03:00
|
|
|
const TreeEntry* treeEntry,
|
|
|
|
const TreeInode::Entry* treeInode,
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
ObjectStore* objectStore,
|
2016-11-02 03:48:26 +03:00
|
|
|
TreeInode& parent, // Has rlock
|
|
|
|
const TreeInode::Dir& dir) {
|
2016-12-10 02:40:45 +03:00
|
|
|
// As far as comparing mode bits is concerned, we ignore the "group" and
|
|
|
|
// "other" permissions.
|
|
|
|
if (((treeEntry->getMode() ^ treeInode->mode) & ~kIgnoredModeBits) != 0) {
|
2016-11-02 03:48:26 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(t12183419): Once the file size is available in the TreeEntry,
|
|
|
|
// compare file sizes before fetching SHA-1s.
|
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
if (treeInode->materialized) {
|
2016-11-02 03:48:26 +03:00
|
|
|
// If the the inode is materialized, then we cannot trust the Hash on the
|
|
|
|
// TreeInode::Entry, so we must compare with the contents in the overlay.
|
|
|
|
auto overlayInode =
|
2016-11-19 06:24:45 +03:00
|
|
|
parent.lookupChildByNameLocked(&dir, treeEntry->getName());
|
2016-12-01 02:48:04 +03:00
|
|
|
auto fileInode = std::dynamic_pointer_cast<FileInode>(overlayInode);
|
2016-11-02 03:48:26 +03:00
|
|
|
auto overlaySHA1 = fileInode->getSHA1().get();
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
auto blobSHA1 = objectStore->getSha1ForBlob(treeEntry->getHash());
|
2016-11-02 03:48:26 +03:00
|
|
|
return overlaySHA1 == *blobSHA1;
|
|
|
|
} else {
|
2016-11-19 06:24:45 +03:00
|
|
|
auto optionalHash = treeInode->hash;
|
2016-11-02 03:48:26 +03:00
|
|
|
DCHECK(optionalHash.hasValue()) << "non-materialized file must have a hash";
|
2016-11-19 06:24:45 +03:00
|
|
|
return *optionalHash.get_pointer() == treeEntry->getHash();
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
/**
|
|
|
|
* @return true if `mode` corresponds to a file (regular or symlink) as opposed
|
|
|
|
* to a directory.
|
|
|
|
*/
|
|
|
|
inline bool isFile(mode_t mode) {
|
|
|
|
return S_ISREG(mode) || S_ISLNK(mode);
|
|
|
|
}
|
|
|
|
|
2016-11-02 03:48:26 +03:00
|
|
|
void Dirstate::computeDelta(
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
const std::vector<TreeEntry>* treeEntries,
|
2016-11-02 03:48:26 +03:00
|
|
|
TreeInode& current,
|
|
|
|
DirectoryDelta& delta) const {
|
|
|
|
auto dir = current.getContents().rlock();
|
|
|
|
auto& entries = dir->entries;
|
|
|
|
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
auto baseIterator = treeEntries->begin();
|
2016-11-02 03:48:26 +03:00
|
|
|
auto overlayIterator = entries.begin();
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
auto baseEnd = treeEntries->end();
|
2016-11-02 03:48:26 +03:00
|
|
|
auto overlayEnd = entries.end();
|
|
|
|
if (baseIterator == baseEnd && overlayIterator == overlayEnd) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (baseIterator == baseEnd) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
// Each remaining entry in overlayIterator should be added to delta.added
|
|
|
|
// (unless it is a directory).
|
2016-11-02 03:48:26 +03:00
|
|
|
while (overlayIterator != overlayEnd) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
auto mode = overlayIterator->second.get()->mode;
|
|
|
|
if (isFile(mode)) {
|
|
|
|
delta.added.push_back(overlayIterator->first);
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
++overlayIterator;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (overlayIterator == overlayEnd) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
// Each remaining entry in baseIterator should be added to delta.removed
|
|
|
|
// (unless it is a directory).
|
2016-11-02 03:48:26 +03:00
|
|
|
while (baseIterator != baseEnd) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
const auto& base = *baseIterator;
|
|
|
|
auto mode = base.getMode();
|
|
|
|
if (isFile(mode)) {
|
|
|
|
delta.removed.push_back(base.getName());
|
|
|
|
} else {
|
|
|
|
delta.removedDirectories.push_back(base.getName());
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
++baseIterator;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-11-29 22:18:32 +03:00
|
|
|
const auto& base = *baseIterator;
|
2016-11-02 03:48:26 +03:00
|
|
|
auto overlayName = overlayIterator->first;
|
|
|
|
auto cmp = base.getName().stringPiece().compare(overlayName.stringPiece());
|
|
|
|
if (cmp == 0) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
// There are entries in the base commit and the overlay with the same
|
|
|
|
// name. All four of the following are possible:
|
|
|
|
// 1. Both entries correspond to files.
|
|
|
|
// 2. Both entries correspond to directories.
|
|
|
|
// 3. The entry was a file in the base commit but is now a directory.
|
|
|
|
// 4. The entry was a directory in the base commit but is now a file.
|
|
|
|
auto isFileInBase = isFile((*baseIterator).getMode());
|
|
|
|
auto isFileInOverlay = isFile(overlayIterator->second.get()->mode);
|
|
|
|
|
|
|
|
if (isFileInBase && isFileInOverlay) {
|
|
|
|
if (!hasMatchingAttributes(
|
|
|
|
&base,
|
|
|
|
overlayIterator->second.get(),
|
2016-12-02 04:49:34 +03:00
|
|
|
mount_->getObjectStore(),
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
current,
|
|
|
|
*dir)) {
|
|
|
|
delta.modified.push_back(base.getName());
|
|
|
|
}
|
|
|
|
} else if (isFileInBase) {
|
|
|
|
// It was a file in the base, but now is a directory in the overlay.
|
|
|
|
// Hg should consider this file to be missing/removed.
|
|
|
|
delta.removed.push_back(base.getName());
|
|
|
|
} else if (isFileInOverlay) {
|
|
|
|
// It was a directory in the base, but now is a file in the overlay.
|
|
|
|
// Hg should consider this file to be added/untracked while the
|
|
|
|
// directory's contents should be considered removed.
|
|
|
|
delta.added.push_back(base.getName());
|
|
|
|
delta.removedDirectories.push_back(base.getName());
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
|
2016-11-02 03:48:26 +03:00
|
|
|
baseIterator++;
|
|
|
|
overlayIterator++;
|
|
|
|
} else if (cmp < 0) {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
auto mode = base.getMode();
|
|
|
|
if (isFile(mode)) {
|
|
|
|
delta.removed.push_back(base.getName());
|
|
|
|
} else {
|
|
|
|
delta.removedDirectories.push_back(base.getName());
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
baseIterator++;
|
|
|
|
} else {
|
Support directories in Dirstate::computeDelta().
Summary:
Previous to this commit, the `Dirstate` logic only worked correctly when the
changes occurred in the root directory. Obviously that is very limiting, so this
adds support for changes in arbitrary directories at arbitrary depths.
This also introduces support for things like a file being replaced by a
directory of same name or vice versa. The tests have been updated to verify
these cases.
One interesting design change that fell out of this was the addition of the
`removedDirectories` field to the `DirectoryDelta` struct. As you can see,
all entries in a removed directory need to be processed by the new
`addDeletedEntries()` function. These require special handling because deleted
directories do not show up in the traversal of modified directories.
In contrast, new directories do show up in the traversal, so they require a
different type of special handling. Specifically, this call will return `NULL`:
```
auto tree = getTreeForDirectory(directory, rootTree.get(), objectStore);
```
When this happens, we must pass an empty vector of tree entries to
`computeDelta()` rather than `&tree->getTreeEntries()`. Admittedly, the special
case of new directories is much simpler than the special case of deleted ones.
Reviewed By: simpkins
Differential Revision: D4219478
fbshipit-source-id: 4c805ba3d7688c4d12ab2ced003a7f5c19ca07eb
2016-11-29 17:49:47 +03:00
|
|
|
auto mode = overlayIterator->second.get()->mode;
|
|
|
|
if (isFile(mode)) {
|
|
|
|
delta.added.push_back(overlayName);
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
overlayIterator++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dirstate::add(RelativePathPiece path) {
|
|
|
|
// TODO(mbolin): Verify that path corresponds to a regular file or symlink.
|
2016-11-19 06:24:45 +03:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.wlock();
|
|
|
|
auto result = userDirectives->find(path.copy());
|
|
|
|
if (result != userDirectives->end()) {
|
|
|
|
switch (result->second) {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Add:
|
2016-11-19 06:24:45 +03:00
|
|
|
// No-op: already added.
|
|
|
|
break;
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Remove:
|
2016-11-19 06:24:45 +03:00
|
|
|
userDirectives->erase(path.copy());
|
2016-12-02 04:49:33 +03:00
|
|
|
persistence_.save(*userDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
(*userDirectives)[path.copy()] = overlay::UserStatusDirective::Add;
|
2016-12-02 04:49:33 +03:00
|
|
|
persistence_.save(*userDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
/**
|
|
|
|
* We need to delete the file from the working copy if either of the following
|
|
|
|
* hold (note that it is a precondition that the file exists):
|
|
|
|
* 1. The file is not materialized in the overlay, so it is unmodified.
|
|
|
|
* 2. The file is in the overlay, but matches what is in the manifest.
|
|
|
|
*/
|
|
|
|
bool shouldFileBeDeletedByHgRemove(
|
|
|
|
RelativePathPiece file,
|
2016-12-02 04:49:43 +03:00
|
|
|
std::shared_ptr<TreeInode> treeInode,
|
2016-11-19 06:24:45 +03:00
|
|
|
const TreeEntry* treeEntry,
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
ObjectStore* objectStore) {
|
2016-11-19 06:24:45 +03:00
|
|
|
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
|
|
|
|
// the file.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto name = file.basename();
|
|
|
|
auto dir = treeInode->getContents().rlock();
|
|
|
|
auto& entries = dir->entries;
|
|
|
|
for (auto& entry : entries) {
|
|
|
|
if (entry.first == name) {
|
|
|
|
if (hasMatchingAttributes(
|
|
|
|
treeEntry, entry.second.get(), objectStore, *treeInode, *dir)) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"not removing {}: file is modified (use -f to force removal)",
|
|
|
|
file.stringPiece()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have reached this point, then the file has already been removed. Note
|
|
|
|
// that this line of code should be unreachable given the preconditions of
|
|
|
|
// this function, but there could be a race condition where the file is
|
|
|
|
// deleted after this function is entered and before we reach this line of
|
|
|
|
// code, so we return false here just to be safe.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dirstate::remove(RelativePathPiece path, bool force) {
|
|
|
|
/*
|
|
|
|
* Analogous to `hg rm <path>`. Note that this can have one of several
|
|
|
|
* possible outcomes:
|
|
|
|
* 1. If the path does not exist in the working copy or the manifest, return
|
|
|
|
* an error.
|
|
|
|
* 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 in the manifest but not in userDirectives, then it should
|
|
|
|
* be marked as REMOVED, but there are several cases to consider:
|
|
|
|
* a. It has already been removed from the working copy. If the user ran
|
|
|
|
* `hg status` right now, the file would be reported as MISSING at this
|
|
|
|
* point. Regardless, it should now be set to REMOVED in userDirectives.
|
|
|
|
* b. It exists in the working copy and matches what is in the manifest.
|
|
|
|
* In this case, it should be set to REMOVED in userDirectives and
|
|
|
|
* removed from the working copy.
|
|
|
|
* c. It has local changes in the working copy. In this case, nothing
|
|
|
|
* should be modified and an error should be returned:
|
|
|
|
* "not removing: file is modified (use -f to force removal)".
|
|
|
|
* 4. If the path is in userDirectives as REMOVED, then this should be a noop.
|
|
|
|
* In particular, even if the user has performed the following sequence:
|
|
|
|
* $ hg rm a-file.txt # deletes a-file.txt
|
|
|
|
* $ echo random-stuff > a-file.txt
|
|
|
|
* $ hg rm a-file.txt # leaves a-file.txt alone
|
|
|
|
* The second call to `hg rm` should not delete a-file.txt even though
|
|
|
|
* the first one did. It should not raise an error that the contents have
|
|
|
|
* changed, either.
|
|
|
|
* 5. If the path is in userChanges as ADD, then there are two possibilities:
|
|
|
|
* a. If the file exists, then no action is taken and an error should be
|
|
|
|
* returned:
|
|
|
|
* "not removing: file has been marked for add "
|
|
|
|
* "(use 'hg forget' to undo add)".
|
|
|
|
* b. If the file does not exist, then technically, it is MISSING rather
|
|
|
|
* than ADDED at this point. Regardless, now its entry should be removed
|
|
|
|
* from userDirectives.
|
|
|
|
*/
|
|
|
|
// TODO(mbolin): Verify that path corresponds to a regular file or symlink in
|
|
|
|
// either the manifest or the working copy.
|
|
|
|
|
|
|
|
// We look up the InodeBase and TreeEntry for `path` before acquiring the
|
|
|
|
// write lock for userDirectives_ because these lookups could be slow, so we
|
|
|
|
// prefer not to do them while holding the lock.
|
2016-12-02 04:49:34 +03:00
|
|
|
std::shared_ptr<TreeInode> parent;
|
2016-11-19 06:24:45 +03:00
|
|
|
try {
|
2016-12-02 04:49:34 +03:00
|
|
|
parent = mount_->getTreeInode(path.dirname());
|
2016-11-19 06:24:45 +03:00
|
|
|
} catch (const std::system_error& e) {
|
|
|
|
auto value = e.code().value();
|
|
|
|
if (value == ENOENT || value == ENOTDIR) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-08 07:03:50 +03:00
|
|
|
std::shared_ptr<InodeBase> inode;
|
2016-11-19 06:24:45 +03:00
|
|
|
if (parent != nullptr) {
|
|
|
|
try {
|
|
|
|
inode = parent->getChildByName(path.basename()).get();
|
|
|
|
} catch (const std::system_error& e) {
|
|
|
|
if (e.code().value() != ENOENT) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-02 04:49:34 +03:00
|
|
|
auto entry = getEntryForFile(
|
|
|
|
path, mount_->getRootTree().get(), mount_->getObjectStore());
|
2016-11-19 06:24:45 +03:00
|
|
|
|
|
|
|
auto shouldDelete = false;
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.wlock();
|
|
|
|
auto result = userDirectives->find(path.copy());
|
|
|
|
if (result == userDirectives->end()) {
|
|
|
|
// When there is no entry for the file in userChanges, we find the
|
|
|
|
// corresponding TreeEntry in the manifest and compare it to its Entry in
|
|
|
|
// the Overlay, if it exists.
|
|
|
|
if (entry == nullptr) {
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"not removing {}: file is untracked", path.stringPiece()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inode != nullptr) {
|
|
|
|
if (force) {
|
|
|
|
shouldDelete = true;
|
|
|
|
} else {
|
|
|
|
// Note that shouldFileBeDeletedByHgRemove() may throw an exception if
|
|
|
|
// the file has been modified, so we must perform this check before
|
|
|
|
// updating userDirectives.
|
|
|
|
shouldDelete = shouldFileBeDeletedByHgRemove(
|
2016-12-02 04:49:34 +03:00
|
|
|
path, parent, entry.get(), mount_->getObjectStore());
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
}
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
(*userDirectives)[path.copy()] = overlay::UserStatusDirective::Remove;
|
2016-12-02 04:49:33 +03:00
|
|
|
persistence_.save(*userDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
} else {
|
|
|
|
switch (result->second) {
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Remove:
|
2016-11-19 06:24:45 +03:00
|
|
|
// No-op: already removed.
|
|
|
|
break;
|
Flip Dirstate -> EdenMount dependency.
Summary:
Previously, `Dirstate` took a `std::shared_ptr<EdenMount>`, but now it takes
pointers to a `MountPoint` and an `ObjectStore` because it does not need the
entire `EdenMount`. Ultimately, this will enable us to have `EdenMount` create
the `Dirstate` itself, but that will be done in a follow-up commit.
Fortunately, it was pretty easy to remove the references to `edenMount_` in
`Dirstate.cpp` and rewrite them in terms of `mountPoint_` or `objectStore_`.
The one thing that I also decided to move was `getModifiedDirectoriesForMount()`
because I already needed to create an `EdenMounts` file (admittedly not a
great name) to collect some utility functions that use members of an `EdenMount`
while not having access to the `EdenMount` itself.
As part of this change, all of the code in `eden/fs/model/hg` has been moved to
`eden/fs/inodes` so that it is alongside `EdenMount`. We are going to change
the `Dirstate` from an Hg-specific concept to a more general concept.
`LocalDirstatePersistence` is no longer one of two implementations of
`DirstatePersistence`. (The other was `FakeDirstatePersistence`.) Now there is
just one concrete implementation called `DirstatePersistence` that takes its
implementation from `LocalDirstatePersistence`. Because there is no longer a
`FakeDirstatePersistence`, `TestMount` must create a `DirstatePersistence` that
uses a `TemporaryFile`.
Because `TestMount` now takes responsibility for creating the `Dirstate`, it
must also give callers the ability to specify the user directives. To that end,
`TestMountBuilder` got an `addUserDirectives()` method while `TestMount` got a
`getDirstate()` method. Surprisingly, `TestMountTest` did not need to be updated
as part of this revision, but `DirstateTest` needed quite a few updates
(which were generally mechanical).
Reviewed By: simpkins
Differential Revision: D4230154
fbshipit-source-id: 9b8cb52b45ef5d75bc8f5e62a58fcd1cddc32bfa
2016-11-26 23:00:15 +03:00
|
|
|
case overlay::UserStatusDirective::Add:
|
2016-11-19 06:24:45 +03:00
|
|
|
if (inode != nullptr) {
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"not removing {}: file has been marked for add "
|
|
|
|
"(use 'hg forget' to undo add)",
|
|
|
|
path.stringPiece()));
|
|
|
|
} else {
|
|
|
|
userDirectives->erase(path.copy());
|
2016-12-02 04:49:33 +03:00
|
|
|
persistence_.save(*userDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldDelete) {
|
2016-12-02 04:49:34 +03:00
|
|
|
auto dispatcher = mount_->getDispatcher();
|
2016-11-19 06:24:45 +03:00
|
|
|
try {
|
|
|
|
dispatcher->unlink(parent->getNodeId(), path.basename()).get();
|
|
|
|
} catch (const std::system_error& e) {
|
|
|
|
// If the file has already been deleted, then mission accomplished.
|
|
|
|
if (e.code().value() != ENOENT) {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string kStatusCodeCharClean = "C";
|
|
|
|
const std::string kStatusCodeCharModified = "M";
|
|
|
|
const std::string kStatusCodeCharAdded = "A";
|
|
|
|
const std::string kStatusCodeCharRemoved = "R";
|
|
|
|
const std::string kStatusCodeCharMissing = "!";
|
|
|
|
const std::string kStatusCodeCharNotTracked = "?";
|
|
|
|
const std::string kStatusCodeCharIgnored = "I";
|
|
|
|
|
|
|
|
const std::string& HgStatusCode_toString(HgStatusCode code) {
|
|
|
|
switch (code) {
|
|
|
|
case HgStatusCode::CLEAN:
|
|
|
|
return kStatusCodeCharClean;
|
|
|
|
case HgStatusCode::MODIFIED:
|
|
|
|
return kStatusCodeCharModified;
|
|
|
|
case HgStatusCode::ADDED:
|
|
|
|
return kStatusCodeCharAdded;
|
|
|
|
case HgStatusCode::REMOVED:
|
|
|
|
return kStatusCodeCharRemoved;
|
|
|
|
case HgStatusCode::MISSING:
|
|
|
|
return kStatusCodeCharMissing;
|
|
|
|
case HgStatusCode::NOT_TRACKED:
|
|
|
|
return kStatusCodeCharNotTracked;
|
|
|
|
case HgStatusCode::IGNORED:
|
|
|
|
return kStatusCodeCharIgnored;
|
|
|
|
}
|
|
|
|
throw std::runtime_error(folly::to<std::string>(
|
|
|
|
"Unrecognized HgStatusCode: ",
|
|
|
|
static_cast<typename std::underlying_type<HgStatusCode>::type>(code)));
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
HgStatusCode HgStatus::statusForPath(RelativePath path) const {
|
|
|
|
auto result = statuses_.find(path);
|
|
|
|
if (result != statuses_.end()) {
|
|
|
|
return result->second;
|
|
|
|
} else {
|
|
|
|
// TODO(mbolin): Verify that path is in the tree and throw if not?
|
|
|
|
return HgStatusCode::CLEAN;
|
|
|
|
}
|
|
|
|
}
|
2016-11-19 06:24:45 +03:00
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const HgStatus& status) {
|
|
|
|
os << status.toString();
|
|
|
|
return os;
|
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
}
|