| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ledger/bin/app/merging/common_ancestor.h" |
| |
| #include <lib/callback/waiter.h> |
| #include <lib/fit/function.h> |
| |
| #include <utility> |
| |
| #include "src/ledger/bin/app/page_utils.h" |
| #include "src/ledger/lib/coroutine/coroutine.h" |
| #include "src/ledger/lib/coroutine/coroutine_waiter.h" |
| #include "src/lib/fxl/memory/ref_ptr.h" |
| |
| namespace ledger { |
| |
| namespace { |
| |
| // Flags used for the commit graph search. |
| // Flag kBelowCommonAncestor implies kAncestorOfLeft and kAncestorOfRight. |
| using WalkFlags = std::bitset<3>; |
| enum WalkFlag { |
| kAncestorOfLeft = 0, // ancestors of the left head |
| kAncestorOfRight = 1, // ancestors of the right head |
| kBelowCommonAncestor = 2 // commits with a common ancestor as a descendant |
| }; |
| |
| // A map from commits to visit to flags. This wraps a |
| // std::map<std::unique_ptr<const storage::Commit>, WalkFlags> with logic to |
| // know whether there remain commits in the map that are not below a common |
| // ancestor. |
| class CommitWalkMap { |
| public: |
| // Returns the number of interesting nodes. |
| size_t interesting_size() { return interesting_nodes_; } |
| |
| // Pops and returns the first commit for GenerationOrder. Precondition: |
| // interesting_size() > 0. |
| std::pair<std::unique_ptr<const storage::Commit>, WalkFlags> Pop() { |
| FXL_DCHECK(interesting_size() > 0); |
| auto e = map_.extract(map_.begin()); |
| if (IsInteresting(e.mapped())) { |
| interesting_nodes_--; |
| } |
| return std::make_pair(std::move(e.key()), e.mapped()); |
| } |
| |
| // Returns the highest generation number present in the map. |
| uint64_t NextGeneration() { return map_.begin()->first->GetGeneration(); } |
| |
| // Adds |commit| to the map with flags |flag|, or updates the flags of |
| // |commit| to include |flag|. |
| void SetFlag(std::unique_ptr<const storage::Commit> commit, WalkFlags flag) { |
| auto [it, exists] = map_.emplace(std::move(commit), WalkFlags()); |
| if (IsInteresting(it->second)) { |
| // Newly inserted nodes have no flags and are not considered interesting. |
| interesting_nodes_--; |
| } |
| it->second |= flag; |
| if (IsInteresting(it->second)) { |
| interesting_nodes_++; |
| } |
| FXL_DCHECK(interesting_nodes_ <= map_.size()); |
| } |
| |
| private: |
| // Checks if some flags make a node interesting. |
| bool IsInteresting(WalkFlags flags) { |
| return flags.any() && !flags.test(WalkFlag::kBelowCommonAncestor); |
| } |
| |
| // The number of interesting elements in the map. |
| size_t interesting_nodes_ = 0; |
| // The underlying map. |
| std::map<std::unique_ptr<const storage::Commit>, WalkFlags, |
| GenerationComparator> |
| map_; |
| }; |
| |
| } // namespace |
| |
| storage::Status FindCommonAncestors( |
| coroutine::CoroutineHandler* handler, storage::PageStorage* storage, |
| std::unique_ptr<const storage::Commit> left, |
| std::unique_ptr<const storage::Commit> right, CommitComparison* comparison, |
| std::vector<std::unique_ptr<const storage::Commit>>* ancestors) { |
| FXL_DCHECK(ancestors->empty()); |
| |
| // The merge base is found by a highest-generation-first search in the commit |
| // graph starting from the two heads. The search order guarantees that child |
| // commits are visited before parents. We maintain a map from commits to be |
| // explored to flags. Since the flags depend on the child commits, they are |
| // correct when the node is visited. |
| CommitWalkMap walk_state; |
| walk_state.SetFlag(std::move(left), |
| WalkFlags().set(WalkFlag::kAncestorOfLeft)); |
| walk_state.SetFlag(std::move(right), |
| WalkFlags().set(WalkFlag::kAncestorOfRight)); |
| |
| // These booleans are set when we encounter a change commit that is an |
| // ancestor of left but not right, or right but not left. |
| bool left_has_changes = false; |
| bool right_has_changes = false; |
| |
| // Loop until we only find "BelowCommonAncestors" |
| while (walk_state.interesting_size() > 0) { |
| uint64_t expected_generation = walk_state.NextGeneration(); |
| auto waiter = fxl::MakeRefCounted<callback::Waiter< |
| storage::Status, |
| std::pair<std::unique_ptr<const storage::Commit>, WalkFlags>>>( |
| storage::Status::OK); |
| while (walk_state.interesting_size() > 0 && |
| expected_generation == walk_state.NextGeneration()) { |
| auto [commit, flags] = walk_state.Pop(); |
| bool is_merge = commit->GetParentIds().size() == 2; |
| // Fetch its parents |
| WalkFlags parent_flags = flags; |
| if (flags.test(WalkFlag::kAncestorOfLeft) && |
| flags.test(WalkFlag::kAncestorOfRight)) { |
| // The parents of common ancestors are still common ancestors, but do |
| // not need to be included in the set. We mark them as uninteresting. |
| parent_flags.set(WalkFlag::kBelowCommonAncestor); |
| } |
| for (const auto& parent_id : commit->GetParentIds()) { |
| storage->GetCommit( |
| parent_id, [callback = waiter->NewCallback(), flags = parent_flags]( |
| storage::Status status, |
| std::unique_ptr<const storage::Commit> result) { |
| callback(status, make_pair(std::move(result), flags)); |
| }); |
| } |
| |
| if (flags.test(WalkFlag::kBelowCommonAncestor)) { |
| // Stop processing uninteresting nodes. |
| continue; |
| } |
| |
| if (flags.count() == 2) { |
| // Push common ancestors. |
| ancestors->push_back(std::move(commit)); |
| } else if (!is_merge) { |
| // Flag change commits. |
| left_has_changes |= flags.test(WalkFlag::kAncestorOfLeft); |
| right_has_changes |= flags.test(WalkFlag::kAncestorOfRight); |
| } |
| } |
| storage::Status status; |
| std::vector<std::pair<std::unique_ptr<const storage::Commit>, WalkFlags>> |
| parents; |
| if (coroutine::Wait(handler, std::move(waiter), &status, &parents) == |
| coroutine::ContinuationStatus::INTERRUPTED) { |
| return storage::Status::INTERRUPTED; |
| } |
| if (status != storage::Status::OK) { |
| return status; |
| } |
| // Add the parents in the map of commits to be visited. |
| for (auto& [parent, flags] : parents) { |
| walk_state.SetFlag(std::move(parent), flags); |
| } |
| } |
| |
| // Subset detection |
| if (!left_has_changes && !right_has_changes) { |
| *comparison = CommitComparison::EQUIVALENT; |
| ancestors->clear(); |
| } else if (!left_has_changes) { |
| *comparison = CommitComparison::LEFT_SUBSET_OF_RIGHT; |
| ancestors->clear(); |
| } else if (!right_has_changes) { |
| *comparison = CommitComparison::RIGHT_SUBSET_OF_LEFT; |
| ancestors->clear(); |
| } else { |
| *comparison = CommitComparison::UNORDERED; |
| } |
| |
| return storage::Status::OK; |
| } |
| |
| bool GenerationComparator::operator()( |
| const std::unique_ptr<const storage::Commit>& lhs, |
| const std::unique_ptr<const storage::Commit>& rhs) const { |
| return std::forward_as_tuple(lhs->GetGeneration(), lhs->GetId()) > |
| std::forward_as_tuple(rhs->GetGeneration(), rhs->GetId()); |
| } |
| |
| } // namespace ledger |