2016-11-02 03:48:26 +03:00
|
|
|
/*
|
2017-01-18 02:01:37 +03:00
|
|
|
* Copyright (c) 2017, Facebook, Inc.
|
2016-11-02 03:48:26 +03:00
|
|
|
* 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-10 06:35:35 +03:00
|
|
|
#include <folly/EvictingCacheMap.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 <folly/Format.h>
|
2016-12-10 06:35:35 +03:00
|
|
|
#include <folly/Unit.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-12-10 06:35:35 +03:00
|
|
|
#include "eden/fs/inodes/InodeBase.h"
|
2016-12-23 02:34:59 +03:00
|
|
|
#include "eden/fs/inodes/Overlay.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-12-16 00:00:31 +03:00
|
|
|
std::ostream& operator<<(
|
|
|
|
std::ostream& os,
|
|
|
|
const DirstateAddRemoveError& status) {
|
2016-12-10 06:35:35 +03:00
|
|
|
return os << status.errorMessage;
|
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:45 +03:00
|
|
|
std::string HgStatus::toString() const {
|
|
|
|
// Sort the entries in the map.
|
2016-12-17 04:48:28 +03:00
|
|
|
std::vector<std::pair<RelativePath, StatusCode>> entries(
|
2016-11-19 06:24:45 +03:00
|
|
|
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) {
|
2016-12-17 04:48:28 +03:00
|
|
|
appender.write(hgStatusCodeChar(pair.second));
|
2016-11-19 06:24:45 +03:00
|
|
|
appender(" ");
|
|
|
|
appender(pair.first.stringPiece());
|
|
|
|
appender("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf->moveToFbString().toStdString();
|
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:46 +03:00
|
|
|
namespace {
|
2016-12-13 22:59:36 +03:00
|
|
|
/**
|
|
|
|
* All entries added to the manifest must be under the `prefix`.
|
|
|
|
*/
|
2016-11-19 06:24:46 +03:00
|
|
|
template <typename RelativePathType>
|
|
|
|
void updateManifestWithDirectives(
|
2016-12-13 22:59:36 +03:00
|
|
|
RelativePathPiece prefix,
|
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,
|
2016-12-17 04:48:28 +03:00
|
|
|
std::unordered_map<RelativePath, StatusCode>* manifest) {
|
2016-11-19 06:24:46 +03:00
|
|
|
// We should make sure that every entry in userDirectives_ is accounted for in
|
|
|
|
// the HgStatus that we return.
|
|
|
|
for (auto& pair : *unaccountedUserDirectives) {
|
2016-12-13 22:59:36 +03:00
|
|
|
if (!prefix.isParentDirOf(RelativePathPiece(pair.first))) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-19 06:24:46 +03:00
|
|
|
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`.
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest->emplace(RelativePath(pair.first), StatusCode::MISSING);
|
2016-11-19 06:24:46 +03:00
|
|
|
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-12-17 04:48:28 +03:00
|
|
|
manifest->emplace(RelativePath(pair.first), StatusCode::REMOVED);
|
2016-11-19 06:24:46 +03:00
|
|
|
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,
|
2016-12-17 04:48:28 +03:00
|
|
|
std::unordered_map<RelativePath, StatusCode>* 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
|
|
|
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:
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest->emplace(pathToEntry, StatusCode::REMOVED);
|
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
|
|
|
break;
|
|
|
|
}
|
|
|
|
copyOfUserDirectives->erase(pathToEntry);
|
|
|
|
} else {
|
|
|
|
// The file is not present on disk, but the user never ran `hg rm`.
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest->emplace(pathToEntry, StatusCode::MISSING);
|
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-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 << "\"";
|
2016-12-13 04:48:45 +03:00
|
|
|
FileInodePtr ignoreInode;
|
2016-12-02 04:49:36 +03:00
|
|
|
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-12-13 22:59:36 +03:00
|
|
|
return getStatusForExistingDirectory(RelativePathPiece(""));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<HgStatus> Dirstate::getStatusForExistingDirectory(
|
|
|
|
RelativePathPiece directory) const {
|
|
|
|
auto hgDir = RelativePathPiece(".hg");
|
|
|
|
std::unordered_set<RelativePathPiece> toIgnore;
|
|
|
|
if (directory.empty()) {
|
|
|
|
toIgnore.insert(hgDir);
|
|
|
|
}
|
|
|
|
|
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-13 22:59:36 +03:00
|
|
|
auto modifiedDirectories =
|
|
|
|
getModifiedDirectories(mount_, directory, &toIgnore);
|
2016-12-17 04:48:28 +03:00
|
|
|
std::unordered_map<RelativePath, StatusCode> manifest;
|
2016-12-13 22:59:31 +03:00
|
|
|
if (modifiedDirectories.empty()) {
|
2016-11-19 06:24:46 +03:00
|
|
|
auto userDirectives = userDirectives_.rlock();
|
2016-12-13 22:59:36 +03:00
|
|
|
updateManifestWithDirectives(directory, &*userDirectives, &manifest);
|
2016-11-19 06:24:46 +03:00
|
|
|
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-12-13 22:59:31 +03:00
|
|
|
for (auto& directory : modifiedDirectories) {
|
2016-11-02 03:48:26 +03:00
|
|
|
// 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-12-17 04:48:28 +03:00
|
|
|
manifest.emplace(pathToEntry, StatusCode::ADDED);
|
2016-11-19 06:24:45 +03:00
|
|
|
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)) {
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest.emplace(pathToEntry, StatusCode::IGNORED);
|
2016-11-02 03:48:26 +03:00
|
|
|
} else {
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest.emplace(pathToEntry, StatusCode::NOT_TRACKED);
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-12-17 04:48:28 +03:00
|
|
|
manifest.emplace(pathToEntry, StatusCode::REMOVED);
|
2016-11-19 06:24:45 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
copyOfUserDirectives.erase(pathToEntry);
|
|
|
|
} else {
|
2016-12-17 04:48:28 +03:00
|
|
|
manifest.emplace(pathToEntry, StatusCode::MODIFIED);
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
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-12-13 22:59:36 +03:00
|
|
|
updateManifestWithDirectives(directory, ©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,
|
2016-12-17 04:48:28 +03:00
|
|
|
std::unordered_map<RelativePath, StatusCode>* 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
|
|
|
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,
|
2016-12-23 02:34:56 +03:00
|
|
|
const TreeInode::Entry* inodeEntry,
|
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.
|
2016-12-23 02:34:56 +03:00
|
|
|
if (((treeEntry->getMode() ^ inodeEntry->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-12-23 02:34:56 +03:00
|
|
|
if (inodeEntry->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.
|
2016-12-23 02:34:56 +03:00
|
|
|
auto fileInode = dynamic_cast<FileInode*>(inodeEntry->inode);
|
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-12-13 04:48:49 +03:00
|
|
|
return overlaySHA1 == blobSHA1;
|
2016-11-02 03:48:26 +03:00
|
|
|
} else {
|
2016-12-23 02:34:56 +03:00
|
|
|
auto optionalHash = inodeEntry->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;
|
|
|
|
}
|
|
|
|
|
2016-12-16 00:00:31 +03:00
|
|
|
namespace {
|
|
|
|
enum AddAction {
|
|
|
|
Add,
|
|
|
|
Erase,
|
|
|
|
};
|
|
|
|
|
|
|
|
void addDirstateAddRemoveError(
|
|
|
|
RelativePathPiece path,
|
|
|
|
StringPiece formatError,
|
|
|
|
std::vector<DirstateAddRemoveError>* errorsToReport) {
|
|
|
|
errorsToReport->push_back(DirstateAddRemoveError{
|
|
|
|
path.copy(), folly::sformat(formatError, path.stringPiece())});
|
|
|
|
}
|
|
|
|
|
|
|
|
void assignAddAction(
|
|
|
|
RelativePathPiece path,
|
2016-12-17 04:48:28 +03:00
|
|
|
StatusCode code,
|
2016-12-16 00:00:31 +03:00
|
|
|
std::unordered_map<RelativePath, AddAction>& actions) {
|
2016-12-17 04:48:28 +03:00
|
|
|
if (code == StatusCode::NOT_TRACKED) {
|
2016-12-16 00:00:31 +03:00
|
|
|
actions[path.copy()] = AddAction::Add;
|
2016-12-17 04:48:28 +03:00
|
|
|
} else if (code == StatusCode::REMOVED) {
|
2016-12-16 00:00:31 +03:00
|
|
|
actions[path.copy()] = AddAction::Erase;
|
|
|
|
}
|
|
|
|
// TODO(mbolin): Should we do anything for the other statuses? Do we
|
|
|
|
// need to complain or anything like that?
|
|
|
|
}
|
|
|
|
|
|
|
|
enum WorkingCopyStatus { File, Directory, DoesNotExist };
|
|
|
|
|
|
|
|
WorkingCopyStatus getPathStatus(
|
|
|
|
RelativePathPiece path,
|
|
|
|
const EdenMount* mount) {
|
|
|
|
try {
|
|
|
|
// Use getInodeBase() as a test of whether the path exists.
|
|
|
|
auto inodeBase = mount->getInodeBase(path);
|
2017-01-18 02:01:37 +03:00
|
|
|
if (inodeBase.asFilePtrOrNull() != nullptr) {
|
2016-12-16 00:00:31 +03:00
|
|
|
return WorkingCopyStatus::File;
|
|
|
|
} else {
|
|
|
|
return WorkingCopyStatus::Directory;
|
|
|
|
}
|
|
|
|
} catch (const std::system_error& e) {
|
|
|
|
if (e.code().value() != ENOENT) {
|
|
|
|
throw;
|
|
|
|
} else {
|
|
|
|
return WorkingCopyStatus::DoesNotExist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Dirstate::addAll(
|
|
|
|
const std::vector<RelativePathPiece>& paths,
|
|
|
|
std::vector<DirstateAddRemoveError>* errorsToReport) {
|
|
|
|
// Find all of the untracked files and then update userDirectives, as
|
|
|
|
// appropriate.
|
|
|
|
std::unordered_map<RelativePath, AddAction> actions;
|
|
|
|
for (auto& path : paths) {
|
|
|
|
auto pathStatus = getPathStatus(path, mount_);
|
|
|
|
if (pathStatus == WorkingCopyStatus::File) {
|
|
|
|
// Admittedly, this getStatusForExistingDirectory() call will also
|
|
|
|
// traverse subdirectories of path.dirname(), so it will do some extra
|
|
|
|
// work. Similarly, if paths contains a list of files in the same
|
|
|
|
// directory, getStatusForExistingDirectory() will be called once per
|
|
|
|
// file instead of once for all files in that directory. If this turns
|
|
|
|
// out to be a bottleneck, then we can do some extra bookkeeping to
|
|
|
|
// reduce lookups.
|
|
|
|
auto status = getStatusForExistingDirectory(path.dirname());
|
|
|
|
auto code = status->statusForPath(path);
|
|
|
|
assignAddAction(path, code, actions);
|
|
|
|
} else if (pathStatus == WorkingCopyStatus::Directory) {
|
|
|
|
auto status = getStatusForExistingDirectory(path);
|
|
|
|
for (auto& pair : *status->list()) {
|
|
|
|
// Only attempt to process the entry if it corresponds to a file in the
|
|
|
|
// working copy.
|
|
|
|
auto entryStatus = getPathStatus(pair.first, mount_);
|
|
|
|
if (entryStatus == WorkingCopyStatus::File) {
|
|
|
|
assignAddAction(pair.first, pair.second, actions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (pathStatus == WorkingCopyStatus::DoesNotExist) {
|
|
|
|
addDirstateAddRemoveError(
|
|
|
|
path, "{}: No such file or directory", errorsToReport);
|
|
|
|
} else {
|
|
|
|
throw std::runtime_error("Unhandled enum value");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply all of the updates to userDirectives in one go.
|
|
|
|
if (!actions.empty()) {
|
2016-11-19 06:24:45 +03:00
|
|
|
auto userDirectives = userDirectives_.wlock();
|
2016-12-16 00:00:31 +03:00
|
|
|
for (auto& pair : actions) {
|
|
|
|
auto action = pair.second;
|
|
|
|
switch (action) {
|
|
|
|
case AddAction::Add:
|
|
|
|
(*userDirectives)[pair.first] = overlay::UserStatusDirective::Add;
|
2016-11-19 06:24:45 +03:00
|
|
|
break;
|
2016-12-16 00:00:31 +03:00
|
|
|
case AddAction::Erase:
|
|
|
|
userDirectives->erase(pair.first);
|
2016-11-19 06:24:45 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-12-16 00:00:31 +03:00
|
|
|
persistence_.save(*userDirectives);
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-12-10 06:35:35 +03:00
|
|
|
namespace {
|
|
|
|
enum ShouldBeDeleted {
|
|
|
|
YES,
|
|
|
|
NO_BECAUSE_THE_FILE_WAS_ALREADY_DELETED,
|
|
|
|
NO_BECAUSE_THE_FILE_WAS_MODIFIED,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
*/
|
2016-12-10 06:35:35 +03:00
|
|
|
ShouldBeDeleted shouldFileBeDeletedByHgRemove(
|
2016-11-19 06:24:45 +03:00
|
|
|
RelativePathPiece file,
|
2016-12-13 04:48:45 +03:00
|
|
|
TreeInodePtr treeInode,
|
2016-11-19 06:24:45 +03:00
|
|
|
const TreeEntry* treeEntry,
|
2016-12-10 06:35:35 +03:00
|
|
|
ObjectStore* objectStore,
|
2016-12-16 00:00:31 +03:00
|
|
|
std::vector<DirstateAddRemoveError>* errorsToReport) {
|
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.
|
2016-12-10 06:35:35 +03:00
|
|
|
return ShouldBeDeleted::YES;
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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)) {
|
2016-12-10 06:35:35 +03:00
|
|
|
return ShouldBeDeleted::YES;
|
2016-11-19 06:24:45 +03:00
|
|
|
} else {
|
2016-12-16 00:00:31 +03:00
|
|
|
addDirstateAddRemoveError(
|
|
|
|
file,
|
2016-11-19 06:24:45 +03:00
|
|
|
"not removing {}: file is modified (use -f to force removal)",
|
2016-12-10 06:35:35 +03:00
|
|
|
errorsToReport);
|
|
|
|
return ShouldBeDeleted::NO_BECAUSE_THE_FILE_WAS_MODIFIED;
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2016-12-10 06:35:35 +03:00
|
|
|
return ShouldBeDeleted::NO_BECAUSE_THE_FILE_WAS_ALREADY_DELETED;
|
|
|
|
}
|
|
|
|
|
2016-12-10 06:35:39 +03:00
|
|
|
void collectAllPathsUnderTree(
|
|
|
|
const Tree* directory,
|
|
|
|
RelativePathPiece directoryName,
|
|
|
|
const ObjectStore* objectStore,
|
|
|
|
folly::EvictingCacheMap<RelativePath, folly::Unit>& collection) {
|
|
|
|
for (auto& entry : directory->getTreeEntries()) {
|
|
|
|
auto entryPath = directoryName + entry.getName();
|
|
|
|
if (entry.getFileType() != FileType::DIRECTORY) {
|
|
|
|
collection.set(entryPath, folly::unit);
|
|
|
|
} else {
|
|
|
|
auto tree = objectStore->getTree(entry.getHash());
|
|
|
|
collectAllPathsUnderTree(tree.get(), entryPath, objectStore, collection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-10 06:35:35 +03:00
|
|
|
void Dirstate::removeAll(
|
2016-12-16 00:00:31 +03:00
|
|
|
const std::vector<RelativePathPiece>& paths,
|
2016-12-10 06:35:35 +03:00
|
|
|
bool force,
|
2016-12-16 00:00:31 +03:00
|
|
|
std::vector<DirstateAddRemoveError>* errorsToReport) {
|
2016-12-10 06:35:35 +03:00
|
|
|
// First, let's collect all of the files to remove based on the `paths`
|
|
|
|
// argument. We use an EvictingCacheMap as a set that preserves iteration
|
|
|
|
// order.
|
|
|
|
folly::EvictingCacheMap<RelativePath, folly::Unit> pathsToRemove(0);
|
|
|
|
auto rootTree = mount_->getRootTree();
|
|
|
|
auto objectStore = mount_->getObjectStore();
|
2016-12-16 00:00:31 +03:00
|
|
|
for (auto& path : paths) {
|
2016-12-10 06:35:35 +03:00
|
|
|
// A file (or directory) must be tracked in order for it to be
|
|
|
|
// removed, though it does not need to exist on disk (it could be in the
|
|
|
|
// MISSING state when this is called, for example).
|
2016-12-10 06:35:39 +03:00
|
|
|
auto entry = getEntryForPath(path, rootTree.get(), objectStore);
|
2016-12-10 06:35:35 +03:00
|
|
|
if (entry != nullptr) {
|
|
|
|
if (entry->getFileType() == FileType::DIRECTORY) {
|
2016-12-10 06:35:39 +03:00
|
|
|
// This should take action on every file under entry, as well as any
|
|
|
|
// files that are tracked as ADDED under path. Note that removing a
|
|
|
|
// file that is tracked as ADDED will either trigger a "not removing:
|
|
|
|
// file has been marked for add" error, or it will perform the remove if
|
|
|
|
// --force has been specified. We leave that up to ::remove() to decide.
|
|
|
|
auto directory = objectStore->getTree(entry->getHash());
|
|
|
|
collectAllPathsUnderTree(
|
|
|
|
directory.get(), path, objectStore, pathsToRemove);
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.rlock();
|
|
|
|
for (auto& pair : *userDirectives) {
|
|
|
|
// Note that if the path is already marked as "Remove" in
|
|
|
|
// userDirectives, ::remove() is a noop, so we can filter out those
|
|
|
|
// paths here.
|
|
|
|
if (pair.second != overlay::UserStatusDirective::Remove &&
|
|
|
|
path.isParentDirOf(pair.first)) {
|
|
|
|
pathsToRemove.set(pair.first, folly::unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-10 06:35:35 +03:00
|
|
|
} else {
|
|
|
|
pathsToRemove.set(path.copy(), folly::unit);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The path does not exist in the manifest (but it could be tracked in
|
|
|
|
// userChanges!), so now we must check its local state to determine what
|
|
|
|
// to do next.
|
|
|
|
auto inodeBase = getInodeBaseOrNull(path);
|
|
|
|
if (inodeBase != nullptr) {
|
|
|
|
auto stat = inodeBase->getattr().get().st;
|
|
|
|
if (S_ISDIR(stat.st_mode)) {
|
2016-12-10 06:35:39 +03:00
|
|
|
// This is a case where the directory is exists in the working copy,
|
|
|
|
// but not in the manifest. It may have entries in userDirectives that
|
|
|
|
// could be affected by this `hg rm` call.
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.rlock();
|
|
|
|
for (auto& pair : *userDirectives) {
|
|
|
|
if (path.isParentDirOf(pair.first)) {
|
|
|
|
pathsToRemove.set(pair.first, folly::unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-10 06:35:35 +03:00
|
|
|
} else {
|
|
|
|
// We let remove() determine whether path is untracked or not.
|
|
|
|
pathsToRemove.set(path.copy(), folly::unit);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// path might not exist in the manifest, but it is possible that it is
|
|
|
|
// tracked and marked for addition. For example, the user may create a
|
|
|
|
// file, run `hg add`, and then delete it before running `hg remove`.
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.rlock();
|
|
|
|
auto result = userDirectives->find(path.copy());
|
|
|
|
if (result != userDirectives->end()) {
|
|
|
|
// We let remove() determine whether path is untracked or not.
|
|
|
|
pathsToRemove.set(path.copy(), folly::unit);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2016-12-16 00:00:31 +03:00
|
|
|
addDirstateAddRemoveError(
|
2016-12-10 06:35:35 +03:00
|
|
|
path, "{}: No such file or directory", errorsToReport);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(mbolin): It would be advantageous to redesign remove() to work in
|
|
|
|
// batches. Every call to remove() acquires the userDirectives lock and
|
|
|
|
// potentially writes to the persistence layer. It would be better to do both
|
|
|
|
// of those things once for the entire set of paths instead of once per path.
|
|
|
|
for (auto& pair : pathsToRemove) {
|
|
|
|
remove(pair.first, force, errorsToReport);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(mbolin): If one of the original paths corresponds to a directory and
|
|
|
|
// now that directory is empty (or contains only empty directories), then it
|
|
|
|
// should also be removed. Note that this is not guaranteed to be the case
|
|
|
|
// here because an individual file in the directory may have failed to have
|
|
|
|
// been removed because it was modified, ignored, etc.
|
2016-12-10 06:35:39 +03:00
|
|
|
// On further consideration, this is not specific to directory arguments. If
|
|
|
|
// `hg rm <path>` is called and <path> is the last file in the directory
|
|
|
|
// (other than the root), then the directory should be removed.
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
|
2016-12-10 06:35:35 +03:00
|
|
|
void Dirstate::remove(
|
|
|
|
RelativePathPiece path,
|
|
|
|
bool force,
|
2016-12-16 00:00:31 +03:00
|
|
|
std::vector<DirstateAddRemoveError>* errorsToReport) {
|
2016-11-19 06:24:45 +03:00
|
|
|
/*
|
2016-12-10 06:35:35 +03:00
|
|
|
* Analogous to `hg rm <path>`. Note that the caller is responsible for
|
|
|
|
* ensuring that `path` satisfies at least one of the following requirements:
|
|
|
|
* a. The path corresponds to a file (non-directory) in the working copy.
|
|
|
|
* b. The path corresponds to a file (non-directory) the manifest.
|
|
|
|
* c. The path corresponds to a file (non-directory) in userDirectives.
|
|
|
|
*
|
|
|
|
* Note that this can have one of several possible outcomes:
|
|
|
|
* 1. If the path is in the manifest but not in userDirectives, then it should
|
2016-11-19 06:24:45 +03:00
|
|
|
* 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)".
|
2016-12-10 06:35:35 +03:00
|
|
|
* 2. If the path is in userDirectives as REMOVED, then this should be a noop.
|
2016-11-19 06:24:45 +03:00
|
|
|
* 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
|
2016-12-10 06:35:35 +03:00
|
|
|
* the first one did. It should not return an error that the contents have
|
2016-11-19 06:24:45 +03:00
|
|
|
* changed, either.
|
2016-12-10 06:35:35 +03:00
|
|
|
* 3. If the path is in userDirectives as ADD, then there are two
|
|
|
|
* possibilities:
|
2016-11-19 06:24:45 +03:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
// 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-13 04:48:45 +03:00
|
|
|
TreeInodePtr 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();
|
2016-12-10 03:11:04 +03:00
|
|
|
if (value != ENOENT && value != ENOTDIR) {
|
2016-11-19 06:24:45 +03:00
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-13 04:48:45 +03:00
|
|
|
InodePtr 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) {
|
2016-12-16 00:00:31 +03:00
|
|
|
addDirstateAddRemoveError(
|
2016-12-10 06:35:35 +03:00
|
|
|
path, "not removing {}: file is untracked", errorsToReport);
|
|
|
|
return;
|
2016-11-19 06:24:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (inode != nullptr) {
|
|
|
|
if (force) {
|
|
|
|
shouldDelete = true;
|
|
|
|
} else {
|
2016-12-10 06:35:35 +03:00
|
|
|
auto shouldBeDeleted = shouldFileBeDeletedByHgRemove(
|
|
|
|
path,
|
|
|
|
parent,
|
|
|
|
entry.get(),
|
|
|
|
mount_->getObjectStore(),
|
|
|
|
errorsToReport);
|
|
|
|
switch (shouldBeDeleted) {
|
|
|
|
case ShouldBeDeleted::YES:
|
|
|
|
shouldDelete = true;
|
|
|
|
break;
|
|
|
|
case ShouldBeDeleted::NO_BECAUSE_THE_FILE_WAS_ALREADY_DELETED:
|
|
|
|
// We still need to update userDirectives to mark the file as
|
|
|
|
// removed in this case.
|
|
|
|
break;
|
|
|
|
case ShouldBeDeleted::NO_BECAUSE_THE_FILE_WAS_MODIFIED:
|
|
|
|
// If the file was modified, then it should not even be marked as
|
|
|
|
// removed. An error should have been added to errorsToReport, so
|
|
|
|
// we just abort the entire method at this point.
|
|
|
|
return;
|
|
|
|
}
|
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) {
|
2016-12-16 00:00:31 +03:00
|
|
|
addDirstateAddRemoveError(
|
2016-12-10 06:35:35 +03:00
|
|
|
path,
|
2016-11-19 06:24:45 +03:00
|
|
|
"not removing {}: file has been marked for add "
|
|
|
|
"(use 'hg forget' to undo add)",
|
2016-12-10 06:35:35 +03:00
|
|
|
errorsToReport);
|
|
|
|
return;
|
2016-11-19 06:24:45 +03:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-13 04:48:45 +03:00
|
|
|
InodePtr Dirstate::getInodeBaseOrNull(RelativePathPiece path) const {
|
2016-12-10 06:35:35 +03:00
|
|
|
try {
|
|
|
|
return mount_->getInodeBase(path);
|
|
|
|
} catch (const std::system_error& e) {
|
|
|
|
if (e.code().value() == ENOENT) {
|
|
|
|
return nullptr;
|
|
|
|
} else {
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-10 12:06:37 +03:00
|
|
|
void Dirstate::markCommitted(
|
|
|
|
Hash commitID,
|
2016-12-16 00:00:31 +03:00
|
|
|
const std::vector<RelativePathPiece>& pathsToClean,
|
|
|
|
const std::vector<RelativePathPiece>& pathsToDrop) {
|
2016-12-10 12:06:37 +03:00
|
|
|
// First, we update the root tree hash (as well as the hashes of other
|
|
|
|
// directories in the overlay). Currently, this is stored in two places: in
|
|
|
|
// the OverlayDir.treeHash for the root tree and in the SNAPSHOT file.
|
|
|
|
// In practice, the SNAPSHOT file is almost always ignored in code, so it
|
|
|
|
// mainly exists for debugging. Ultimately, we will probably drop the SNAPSHOT
|
|
|
|
// file.
|
|
|
|
auto objectStore = mount_->getObjectStore();
|
2016-12-14 05:11:05 +03:00
|
|
|
// Note: we immediately wait on the Future here.
|
|
|
|
// It may take a fairly long time to import the Tree.
|
|
|
|
auto treeForCommit = objectStore->getTreeForCommit(commitID).get();
|
2016-12-10 12:06:37 +03:00
|
|
|
if (treeForCommit == nullptr) {
|
|
|
|
throw std::runtime_error(folly::sformat(
|
|
|
|
"Cannot mark committed because commit for {} cannot be found.",
|
|
|
|
commitID.toString()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform a depth-first traversal of directories in the overlay and update
|
|
|
|
// the treeHash, as appropriate.
|
|
|
|
auto overlay = mount_->getOverlay();
|
2016-12-13 22:59:35 +03:00
|
|
|
auto hgDir = RelativePathPiece(".hg");
|
|
|
|
auto toIgnore = std::unordered_set<RelativePathPiece>{hgDir};
|
|
|
|
auto modifiedDirectories = getModifiedDirectoriesForMount(mount_, &toIgnore);
|
2016-12-13 22:59:31 +03:00
|
|
|
for (auto& directory : modifiedDirectories) {
|
2016-12-10 12:06:37 +03:00
|
|
|
auto treeForDirectory =
|
|
|
|
getTreeForDirectory(directory, treeForCommit.get(), objectStore);
|
|
|
|
if (treeForDirectory == nullptr) {
|
|
|
|
// This could happen because directory is:
|
|
|
|
// - .hg
|
|
|
|
// - a bind mount
|
|
|
|
// - an ignored directory.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto treeInode = mount_->getTreeInode(directory);
|
|
|
|
DCHECK(treeInode.get() != nullptr) << "Failed to get a TreeInode for "
|
|
|
|
<< directory;
|
|
|
|
|
|
|
|
auto dir = treeInode->getContents().wlock();
|
|
|
|
dir->treeHash = treeForDirectory->getHash();
|
|
|
|
overlay->saveOverlayDir(directory, &*dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that the hashes are written, we update the userDirectives.
|
|
|
|
{
|
|
|
|
auto userDirectives = userDirectives_.wlock();
|
|
|
|
// Do we need to do anything in the overlay at the end of this?
|
|
|
|
for (auto& path : pathsToClean) {
|
|
|
|
VLOG(1) << "calling clean on " << path;
|
|
|
|
auto numErased = userDirectives->erase(path.copy());
|
|
|
|
if (numErased == 0) {
|
|
|
|
VLOG(1)
|
|
|
|
<< "Was supposed to mark path " << path
|
|
|
|
<< " clean in the dirstate, but was not in userDirectives. "
|
|
|
|
<< "This is expected if the path was modified rather than added.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& path : pathsToDrop) {
|
|
|
|
VLOG(1) << "calling drop on " << path;
|
|
|
|
auto numErased = userDirectives->erase(path.copy());
|
|
|
|
if (numErased == 0) {
|
|
|
|
VLOG(1) << "Was supposed to drop path " << path
|
|
|
|
<< " in the dirstate, but was not in userDirectives.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
persistence_.save(*userDirectives);
|
|
|
|
}
|
|
|
|
|
|
|
|
// With respect to maintaining consistency, this is the least important I/O
|
|
|
|
// that we do, so we do it last.
|
|
|
|
mount_->getConfig()->setSnapshotID(commitID);
|
|
|
|
}
|
|
|
|
|
2016-12-17 04:48:28 +03:00
|
|
|
const char kStatusCodeCharClean = 'C';
|
|
|
|
const char kStatusCodeCharModified = 'M';
|
|
|
|
const char kStatusCodeCharAdded = 'A';
|
|
|
|
const char kStatusCodeCharRemoved = 'R';
|
|
|
|
const char kStatusCodeCharMissing = '!';
|
|
|
|
const char kStatusCodeCharNotTracked = '?';
|
|
|
|
const char kStatusCodeCharIgnored = 'I';
|
2016-11-19 06:24:45 +03:00
|
|
|
|
2016-12-17 04:48:28 +03:00
|
|
|
char hgStatusCodeChar(StatusCode code) {
|
2016-11-19 06:24:45 +03:00
|
|
|
switch (code) {
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::CLEAN:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharClean;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::MODIFIED:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharModified;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::ADDED:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharAdded;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::REMOVED:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharRemoved;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::MISSING:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharMissing;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::NOT_TRACKED:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharNotTracked;
|
2016-12-17 04:48:28 +03:00
|
|
|
case StatusCode::IGNORED:
|
2016-11-19 06:24:45 +03:00
|
|
|
return kStatusCodeCharIgnored;
|
|
|
|
}
|
|
|
|
throw std::runtime_error(folly::to<std::string>(
|
2016-12-17 04:48:28 +03:00
|
|
|
"Unrecognized StatusCode: ",
|
|
|
|
static_cast<typename std::underlying_type<StatusCode>::type>(code)));
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
|
2016-12-17 04:48:28 +03:00
|
|
|
StatusCode HgStatus::statusForPath(RelativePathPiece path) const {
|
2016-12-16 00:00:31 +03:00
|
|
|
auto result = statuses_.find(path.copy());
|
2016-11-02 03:48:26 +03:00
|
|
|
if (result != statuses_.end()) {
|
|
|
|
return result->second;
|
|
|
|
} else {
|
|
|
|
// TODO(mbolin): Verify that path is in the tree and throw if not?
|
2016-12-17 04:48:28 +03:00
|
|
|
return StatusCode::CLEAN;
|
2016-11-02 03:48:26 +03:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|