mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 16:31:02 +03:00
a887c14404
Summary: Accumulate every commit transition in the journal, rather than treating every merged range of journal deltas as having a "from" commit and a "to" commit. This makes it possible for Watchman to accurately report the set of changed files across an ABA situation or rapid `hg next` and `hg prev` operations. Reviewed By: genevievehelsel Differential Revision: D26193478 fbshipit-source-id: 8b54b9d5bcefa1811008a3b6e9c3aa25a69471ca
216 lines
6.6 KiB
C++
216 lines
6.6 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This software may be used and distributed according to the terms of the
|
|
* GNU General Public License version 2.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <type_traits>
|
|
#include <unordered_set>
|
|
#include <variant>
|
|
#include "eden/fs/model/Hash.h"
|
|
#include "eden/fs/utils/PathFuncs.h"
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
struct PathChangeInfo {
|
|
PathChangeInfo() : existedBefore{false}, existedAfter{false} {}
|
|
|
|
PathChangeInfo(bool before, bool after)
|
|
: existedBefore{before}, existedAfter{after} {}
|
|
|
|
bool isNew() const {
|
|
return !existedBefore && existedAfter;
|
|
}
|
|
|
|
bool operator==(const PathChangeInfo& other) const {
|
|
return existedBefore == other.existedBefore &&
|
|
existedAfter == other.existedAfter;
|
|
}
|
|
|
|
bool operator!=(const PathChangeInfo& other) const {
|
|
return !(*this == other);
|
|
}
|
|
|
|
/// Whether this path existed at the start of this delta.
|
|
bool existedBefore : 1;
|
|
|
|
/**
|
|
* Whether this path existed at the end of this delta.
|
|
* If existedAfter && !existedBefore, then the file can be considered new in
|
|
* this delta.
|
|
*/
|
|
bool existedAfter : 1;
|
|
|
|
// TODO: It may make sense to maintain an existenceChanged bit to distinguish
|
|
// between a file being changed and it being removed and added in the same
|
|
// delta.
|
|
};
|
|
|
|
class JournalDelta {
|
|
public:
|
|
using SequenceNumber = uint64_t;
|
|
|
|
/** The ID of this Delta in the Journal */
|
|
JournalDelta::SequenceNumber sequenceID;
|
|
/** The time at which the change was recorded. */
|
|
std::chrono::steady_clock::time_point time;
|
|
};
|
|
|
|
/** A delta that stores information about changed files */
|
|
class FileChangeJournalDelta : public JournalDelta {
|
|
public:
|
|
enum Created { CREATED };
|
|
enum Removed { REMOVED };
|
|
enum Changed { CHANGED };
|
|
enum Renamed { RENAMED };
|
|
enum Replaced { REPLACED };
|
|
|
|
FileChangeJournalDelta() = default;
|
|
FileChangeJournalDelta(FileChangeJournalDelta&&) = default;
|
|
FileChangeJournalDelta& operator=(FileChangeJournalDelta&&) = default;
|
|
FileChangeJournalDelta(const FileChangeJournalDelta&) = delete;
|
|
FileChangeJournalDelta& operator=(const FileChangeJournalDelta&) = delete;
|
|
FileChangeJournalDelta(RelativePathPiece fileName, Created);
|
|
FileChangeJournalDelta(RelativePathPiece fileName, Removed);
|
|
FileChangeJournalDelta(RelativePathPiece fileName, Changed);
|
|
|
|
/**
|
|
* "Renamed" means that that newName was created as a result of the mv(1).
|
|
*/
|
|
FileChangeJournalDelta(
|
|
RelativePathPiece oldName,
|
|
RelativePathPiece newName,
|
|
Renamed);
|
|
|
|
/**
|
|
* "Replaced" means that that newName was overwritten by oldName as a result
|
|
* of the mv(1).
|
|
*/
|
|
FileChangeJournalDelta(
|
|
RelativePathPiece oldName,
|
|
RelativePathPiece newName,
|
|
Replaced);
|
|
|
|
/** Which of these paths actually contain information */
|
|
RelativePath path1;
|
|
RelativePath path2;
|
|
PathChangeInfo info1;
|
|
PathChangeInfo info2;
|
|
bool isPath1Valid = false;
|
|
bool isPath2Valid = false;
|
|
|
|
std::unordered_map<RelativePath, PathChangeInfo> getChangedFilesInOverlay()
|
|
const;
|
|
|
|
/** Checks whether this delta is a modification */
|
|
bool isModification() const;
|
|
|
|
/** Checks whether this delta and other are the same disregarding time and
|
|
* sequenceID [whether they do the same action] */
|
|
bool isSameAction(const FileChangeJournalDelta& other) const;
|
|
|
|
/** Get memory used (in bytes) by this Delta */
|
|
size_t estimateMemoryUsage() const;
|
|
};
|
|
|
|
/** A delta that stores information about changing commits */
|
|
class HashUpdateJournalDelta : public JournalDelta {
|
|
public:
|
|
HashUpdateJournalDelta() = default;
|
|
HashUpdateJournalDelta(HashUpdateJournalDelta&&) = default;
|
|
HashUpdateJournalDelta& operator=(HashUpdateJournalDelta&&) = default;
|
|
HashUpdateJournalDelta(const HashUpdateJournalDelta&) = delete;
|
|
HashUpdateJournalDelta& operator=(const HashUpdateJournalDelta&) = delete;
|
|
|
|
/** 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;
|
|
|
|
/** 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;
|
|
|
|
/** Get memory used (in bytes) by this Delta */
|
|
size_t estimateMemoryUsage() const;
|
|
};
|
|
|
|
class JournalDeltaPtr {
|
|
public:
|
|
/* implicit */ JournalDeltaPtr(std::nullptr_t);
|
|
|
|
/* implicit */ JournalDeltaPtr(FileChangeJournalDelta* p);
|
|
|
|
/* implicit */ JournalDeltaPtr(HashUpdateJournalDelta* p);
|
|
|
|
size_t estimateMemoryUsage() const;
|
|
|
|
explicit operator bool() const noexcept {
|
|
return !std::holds_alternative<std::monostate>(data_);
|
|
}
|
|
|
|
/** If this JournalDeltaPtr points to a FileChangeJournalDelta then returns
|
|
* the raw pointer, if it does not point to a FileChangeJournalDelta then
|
|
* return nullptr. */
|
|
FileChangeJournalDelta* getAsFileChangeJournalDelta();
|
|
|
|
const JournalDelta* operator->() const noexcept;
|
|
|
|
private:
|
|
std::variant<std::monostate, FileChangeJournalDelta*, HashUpdateJournalDelta*>
|
|
data_;
|
|
};
|
|
|
|
struct JournalDeltaRange {
|
|
/**
|
|
* The current sequence range.
|
|
* This is a range to accommodate merging a range into a single entry.
|
|
*/
|
|
JournalDelta::SequenceNumber fromSequence;
|
|
JournalDelta::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;
|
|
|
|
/**
|
|
* If the current commit did not change during this window, the list contains
|
|
* one entry: the current snapshot hash. If the range contains an update
|
|
* operation, the list contains two entries: the previous hash and the new
|
|
* hash. If two commits have occurred in this range, there are three entries,
|
|
* and so on.
|
|
*
|
|
* This entries in this list are not unique. [A, B, C] is different than [A,
|
|
* C, B], and [A, B, A] is common too.
|
|
*/
|
|
std::vector<Hash> snapshotTransitions;
|
|
|
|
/**
|
|
* The set of files that changed in the overlay in this update, including
|
|
* some information about the changes.
|
|
*/
|
|
std::unordered_map<RelativePath, PathChangeInfo> changedFilesInOverlay;
|
|
/** 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;
|
|
|
|
bool isTruncated = false;
|
|
JournalDeltaRange() {
|
|
// 1 and 2 entries are the most common by far.
|
|
snapshotTransitions.reserve(2);
|
|
}
|
|
JournalDeltaRange(JournalDeltaRange&&) = default;
|
|
JournalDeltaRange& operator=(JournalDeltaRange&&) = default;
|
|
};
|
|
|
|
} // namespace eden
|
|
} // namespace facebook
|