1use std::collections::VecDeque;
18use std::slice;
19use std::sync::Arc;
20
21use itertools::Itertools as _;
22use thiserror::Error;
23
24use crate::backend::BackendError;
25use crate::backend::BackendResult;
26use crate::backend::CommitId;
27use crate::commit::Commit;
28use crate::dag_walk;
29use crate::op_store::OpStoreError;
30use crate::op_store::OpStoreResult;
31use crate::op_store::OperationId;
32use crate::op_walk;
33use crate::operation::Operation;
34use crate::repo::ReadonlyRepo;
35use crate::repo::Repo as _;
36use crate::store::Store;
37
38#[derive(Clone, Debug)]
40pub struct CommitEvolutionEntry {
41 pub commit: Commit,
43 pub operation: Option<Operation>,
45}
46
47impl CommitEvolutionEntry {
48 pub fn predecessor_ids(&self) -> &[CommitId] {
50 match &self.operation {
51 Some(op) => op.predecessors_for_commit(self.commit.id()).unwrap(),
52 None => &self.commit.store_commit().predecessors,
53 }
54 }
55
56 pub fn predecessors(&self) -> impl ExactSizeIterator<Item = BackendResult<Commit>> + use<'_> {
58 let store = self.commit.store();
59 self.predecessor_ids().iter().map(|id| store.get_commit(id))
60 }
61}
62
63#[allow(missing_docs)]
64#[derive(Debug, Error)]
65pub enum WalkPredecessorsError {
66 #[error(transparent)]
67 Backend(#[from] BackendError),
68 #[error(transparent)]
69 OpStore(#[from] OpStoreError),
70 #[error("Predecessors cycle detected at operation {0}")]
71 CycleDetected(OperationId),
72}
73
74pub fn walk_predecessors(
76 repo: &ReadonlyRepo,
77 start_commits: &[CommitId],
78) -> impl Iterator<Item = Result<CommitEvolutionEntry, WalkPredecessorsError>> {
79 WalkPredecessors {
80 store: repo.store().clone(),
81 op_ancestors: op_walk::walk_ancestors(slice::from_ref(repo.operation())),
82 to_visit: start_commits.to_vec(),
83 queued: VecDeque::new(),
84 }
85}
86
87struct WalkPredecessors<I> {
88 store: Arc<Store>,
89 op_ancestors: I,
90 to_visit: Vec<CommitId>,
91 queued: VecDeque<CommitEvolutionEntry>,
92}
93
94impl<I> WalkPredecessors<I>
95where
96 I: Iterator<Item = OpStoreResult<Operation>>,
97{
98 fn try_next(&mut self) -> Result<Option<CommitEvolutionEntry>, WalkPredecessorsError> {
99 while !self.to_visit.is_empty() && self.queued.is_empty() {
100 let Some(op) = self.op_ancestors.next().transpose()? else {
101 self.flush_commits()?;
103 break;
104 };
105 if !op.stores_commit_predecessors() {
106 self.scan_commits()?;
110 break;
111 }
112 self.visit_op(&op)?;
113 }
114 Ok(self.queued.pop_front())
115 }
116
117 fn visit_op(&mut self, op: &Operation) -> Result<(), WalkPredecessorsError> {
119 let mut visited = Vec::new(); let mut i = 0;
121 while let Some(cur_id) = self.to_visit.get(i) {
122 if let Some(next_ids) = op.predecessors_for_commit(cur_id) {
123 if visited.contains(cur_id) {
124 return Err(WalkPredecessorsError::CycleDetected(op.id().clone()));
125 }
126 let commit = self.store.get_commit(cur_id)?;
127 visited.extend(self.to_visit.splice(i..=i, next_ids.iter().rev().cloned()));
130 self.queued.push_back(CommitEvolutionEntry {
131 commit,
132 operation: Some(op.clone()),
133 });
134 } else {
135 i += 1;
136 }
137 }
138 Ok(())
139 }
140
141 fn scan_commits(&mut self) -> BackendResult<()> {
143 let commits = dag_walk::topo_order_reverse_ok(
146 self.to_visit.drain(..).map(|id| self.store.get_commit(&id)),
147 |commit: &Commit| commit.id().clone(),
148 |commit: &Commit| {
149 let ids = &commit.store_commit().predecessors;
161 ids.iter()
162 .rev()
163 .map(|id| self.store.get_commit(id))
164 .collect_vec()
165 },
166 )?;
167 self.queued
168 .extend(commits.into_iter().map(|commit| CommitEvolutionEntry {
169 commit,
170 operation: None,
171 }));
172 Ok(())
173 }
174
175 fn flush_commits(&mut self) -> BackendResult<()> {
177 self.queued.reserve(self.to_visit.len());
180 for id in self.to_visit.drain(..) {
181 let commit = self.store.get_commit(&id)?;
182 self.queued.push_back(CommitEvolutionEntry {
183 commit,
184 operation: None,
185 });
186 }
187 Ok(())
188 }
189}
190
191impl<I> Iterator for WalkPredecessors<I>
192where
193 I: Iterator<Item = OpStoreResult<Operation>>,
194{
195 type Item = Result<CommitEvolutionEntry, WalkPredecessorsError>;
196
197 fn next(&mut self) -> Option<Self::Item> {
198 self.try_next().transpose()
199 }
200}