sapling/eden/fs/journal/JournalDelta.h
Michael Bolin b4f3c70c6a Distinguish between "renaming" and "replacing" a file in the journal.
Summary:
Historically, we have seen a number of messages like the following in the Eden
logs:

```
Journal for .hg/blackbox.log holds invalid Created, Created sequence
```

Apparently we were getting these invalid sequences because we were not always
recording a "rename" correctly. The "rename" constructor for a `JournalDelta`
assumed that the source path should be included in the list of "removed" files
while the destination path should be included in the list of "created" files.
However, that is not accurate if the destination path already existed before
the user ran `mv`.

Fortunately, we already check whether the destination file exists in
`TreeInode::doRename()`, so it is straightforward to determine whether the
action is a "rename" (destination does not exist) or an "replace" (destination
already exists) and then classify the destination path accordingly.

As demonstrated by the new test introduced in this commit
(`JournalUpdateTest::moveFileReplace`), in the old implementation,
a file that was removed after it was overwritten would not show up as
removed in the merged `JournalDelta`. Because Watchman relies on
`JournalDelta::merge()` via the Thrift method `getFilesChangedSince()`,
this would cause Watchman to report such a file as still existing even
though it was removed.

This definitely caused bugs in Nuclide. It is likely that other tools that rely
on Watchman in Eden (such as Buck) may have also done incorrect things
because of this bug, so this could explain past reported issues.

Reviewed By: simpkins

Differential Revision: D7888249

fbshipit-source-id: 3e57963f27c5421a6175d1a759db8d9597ed76f3
2018-05-07 14:23:13 -07:00

88 lines
3.3 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#pragma once
#include "Journal.h"
#include <chrono>
#include <unordered_set>
#include "eden/fs/model/Hash.h"
#include "eden/fs/utils/PathFuncs.h"
namespace facebook {
namespace eden {
class JournalDelta {
public:
enum Created { CREATED };
enum Removed { REMOVED };
enum Renamed { RENAME };
enum Replaced { REPLACE };
JournalDelta() = default;
JournalDelta(JournalDelta&&) = default;
JournalDelta& operator=(JournalDelta&&) = default;
JournalDelta(const JournalDelta&) = delete;
JournalDelta& operator=(const JournalDelta&) = delete;
JournalDelta(std::initializer_list<RelativePath> overlayFileNames);
JournalDelta(RelativePathPiece fileName, Created);
JournalDelta(RelativePathPiece fileName, Removed);
/**
* "Renamed" means that that newName was created as a result of the mv(1).
*/
JournalDelta(RelativePathPiece oldName, RelativePathPiece newName, Renamed);
/**
* "Replaced" means that that newName was overwritten by oldName as a result
* of the mv(1).
*/
JournalDelta(RelativePathPiece oldName, RelativePathPiece newName, Replaced);
/** the prior delta and its chain */
std::shared_ptr<const JournalDelta> previous;
/** The current sequence range.
* This is a range to accommodate merging a range into a single entry. */
Journal::SequenceNumber fromSequence;
Journal::SequenceNumber toSequence;
/** The time at which the change was recorded.
* This is a range to accommodate merging a range into a single entry. */
std::chrono::steady_clock::time_point fromTime;
std::chrono::steady_clock::time_point toTime;
/** The snapshot hash that we started and ended up on.
* This will often be the same unless we perform a checkout or make
* a new snapshot from the snapshotable files in the overlay. */
Hash fromHash;
Hash toHash;
/** The set of files that changed in the overlay in this update */
std::unordered_set<RelativePath> changedFilesInOverlay;
/** The set of files that were created in the overlay in this update */
std::unordered_set<RelativePath> createdFilesInOverlay;
/** The set of files that were removed in the overlay in this update */
std::unordered_set<RelativePath> removedFilesInOverlay;
/** The set of files that had differing status across a checkout or
* some other operation that changes the snapshot hash */
std::unordered_set<RelativePath> uncleanPaths;
/** Merge the deltas running back from this delta for all deltas
* whose toSequence is >= limitSequence.
* The default limit value is 0 which is never assigned by the Journal
* and thus indicates that all deltas should be merged.
* if pruneAfterLimit is true and we stop due to hitting limitSequence,
* then the returned delta will have previous=nullptr rather than
* maintaining the chain.
* If the limitSequence means that no deltas will match, returns nullptr.
* */
std::unique_ptr<JournalDelta> merge(
Journal::SequenceNumber limitSequence = 0,
bool pruneAfterLimit = false) const;
};
} // namespace eden
} // namespace facebook