1use std::collections::BTreeMap;
20use std::collections::HashMap;
21use std::collections::HashSet;
22use std::hash::Hash;
23use std::ops::Deref as _;
24use std::rc::Rc;
25use std::sync::Arc;
26use std::sync::Mutex;
27
28use futures::StreamExt as _;
29use futures::TryStreamExt as _;
30use futures::executor::block_on_stream;
31use futures::future::try_join_all;
32use itertools::Itertools as _;
33use thiserror::Error;
34
35use crate::backend::BackendError;
36use crate::backend::BackendResult;
37use crate::backend::ChangeId;
38use crate::backend::CommitId;
39use crate::backend::Signature;
40use crate::backend::TreeId;
41use crate::commit::Commit;
42use crate::conflict_labels::ConflictLabels;
43use crate::evolution::WalkPredecessorsError;
44use crate::evolution::walk_predecessors;
45use crate::graph_dominators::FlowGraph;
46use crate::graph_dominators::SimpleDirectedGraph;
47use crate::graph_dominators::ValueCache;
48use crate::index::IndexError;
49use crate::merge::Merge;
50use crate::merge::MergeBuilder;
51use crate::merge::SameChange;
52use crate::merged_tree::MergedTree;
53use crate::repo::MutableRepo;
54use crate::repo::ReadonlyRepo;
55use crate::repo::Repo as _;
56use crate::revset::ResolvedRevsetExpression;
57use crate::revset::RevsetEvaluationError;
58use crate::revset::RevsetExpression;
59use crate::rewrite::merge_commit_trees_no_resolve;
60use crate::store::Store;
61
62pub type CommitsByChangeId = BTreeMap<ChangeId, Vec<Commit>>;
64
65#[derive(Debug, PartialEq, Eq, Clone)]
68pub enum ConvergedAttribute<T> {
69 Solved(T),
71 Unsolved {
73 base_commit: CommitId,
77 excluded_divergent_commits: HashSet<CommitId>,
80 },
81}
82
83#[derive(Debug, PartialEq, Eq, Clone)]
85pub struct ConvergeResult {
86 pub author: ConvergedAttribute<Signature>,
88 pub description: ConvergedAttribute<String>,
90 pub parents: ConvergedAttribute<Vec<CommitId>>,
92 pub tree: Option<TreeIdsAndLabels>,
94}
95
96#[derive(Debug, Error)]
98pub enum ConvergeError {
99 #[error(transparent)]
101 Backend(#[from] BackendError),
102 #[error(transparent)]
104 Index(#[from] IndexError),
105 #[error(transparent)]
108 RevsetEvaluation(#[from] RevsetEvaluationError),
109 #[error(transparent)]
112 WalkPredecessors(#[from] WalkPredecessorsError),
113 #[error(transparent)]
115 IO(#[from] std::io::Error),
116 #[error(transparent)]
118 Other(Box<dyn std::error::Error + Send + Sync>),
119}
120
121pub async fn find_divergent_changes(
127 repo: &Arc<ReadonlyRepo>,
128 revset_expression: Arc<ResolvedRevsetExpression>,
129) -> Result<CommitsByChangeId, ConvergeError> {
130 let mut result = CommitsByChangeId::new();
131 let mut stream = revset_expression.evaluate(repo.as_ref())?.stream();
132 while let Some(commit_id) = stream.try_next().await? {
133 let commit = repo.store().get_commit_async(&commit_id).await?;
134 result
135 .entry(commit.change_id().clone())
136 .or_default()
137 .push(commit);
138 }
139 result.retain(|_, commits| commits.len() > 1);
142 Ok(result)
143}
144
145pub async fn converge_change(
151 truncated_evolution_graph: &TruncatedEvolutionGraph,
152 author: Option<Signature>,
153 description: Option<String>,
154 parents: Option<Vec<CommitId>>,
155 tree: Option<TreeIdsAndLabels>,
156) -> Result<ConvergeResult, ConvergeError> {
157 let author = if let Some(author) = author {
158 ConvergedAttribute::Solved(author)
159 } else {
160 converge_author(truncated_evolution_graph).await?
161 };
162 let description = if let Some(description) = description {
163 ConvergedAttribute::Solved(description)
164 } else {
165 converge_description(truncated_evolution_graph).await?
166 };
167 let parents = if let Some(parents) = parents {
168 ConvergedAttribute::Solved(parents)
169 } else {
170 converge_parents(truncated_evolution_graph).await?
171 };
172
173 let tree = if let Some(tree) = tree {
174 Some(tree)
175 } else if let ConvergedAttribute::Solved(parents) = &parents {
176 let tree = converge_trees(truncated_evolution_graph, parents).await?;
177 Some(TreeIdsAndLabels::new(tree))
178 } else {
179 None
180 };
181
182 Ok(ConvergeResult {
183 author,
184 description,
185 parents,
186 tree,
187 })
188}
189
190pub async fn apply_solution(
193 author: Signature,
194 description: String,
195 parents: Vec<CommitId>,
196 tree: TreeIdsAndLabels,
197 change_id: ChangeId,
198 divergent_commit_ids: &Vec<CommitId>,
199 repo_mut: &mut MutableRepo,
200) -> Result<(Commit, usize), ConvergeError> {
201 let merged_tree = tree.to_merged_tree(repo_mut.store());
202 let solution = repo_mut
203 .new_commit(parents, merged_tree)
204 .set_change_id(change_id.clone())
205 .set_description(description)
206 .set_author(author)
207 .set_predecessors(divergent_commit_ids.clone())
208 .write()
209 .await?;
210 for divergent_commit_id in divergent_commit_ids {
211 repo_mut.set_rewritten_commit(divergent_commit_id.clone(), solution.id().clone());
212 }
213 let num_rebased = repo_mut.rebase_descendants().await?;
214 Ok((solution, num_rebased))
215}
216
217pub struct TruncatedEvolutionGraph {
223 repo: Arc<ReadonlyRepo>,
225 divergent_commits: Vec<Commit>,
227 divergent_commit_ids: Vec<CommitId>,
229 pub flow_graph: FlowGraph<CommitId>,
235}
236
237impl TruncatedEvolutionGraph {
238 pub async fn new(
241 repo: Arc<ReadonlyRepo>,
242 divergent_commits: Vec<Commit>,
243 ) -> Result<Self, ConvergeError> {
244 validate(
245 divergent_commits.len() > 1,
246 &format!(
247 "Expected multiple divergent commits, got {}",
248 divergent_commits.len()
249 ),
250 )?;
251
252 let divergent_commit_ids = divergent_commits
253 .iter()
254 .map(|c| c.id().clone())
255 .collect_vec();
256
257 let divergent_change_id = if divergent_commits.iter().map(|c| c.change_id()).all_equal() {
260 divergent_commits.first().unwrap().change_id().clone()
261 } else {
262 return Err(ConvergeError::Other(
263 "all divergent commits must have the same change-id".into(),
264 ));
265 };
266
267 let mut edges = vec![];
269 let mut seen = HashSet::new();
270 let mut to_visit = HashSet::with_capacity(divergent_commit_ids.len());
271 to_visit.extend(divergent_commit_ids.iter().cloned());
272
273 let evolution_nodes = block_on_stream(
274 walk_predecessors(&repo, divergent_commit_ids.as_slice()).boxed_local(),
275 );
276
277 let mut initial_nodes = vec![];
281
282 for node in evolution_nodes {
283 let entry = node?;
284 let commit_id = entry.commit.id();
285 if *entry.commit.change_id() != divergent_change_id {
286 continue;
288 }
289 to_visit.remove(commit_id);
290 if !seen.insert(commit_id.clone()) {
291 continue;
299 }
300 let predecessors = entry
301 .predecessors()
302 .await?
303 .iter()
304 .filter_map(|commit| {
305 if *commit.change_id() == divergent_change_id {
306 Some(commit.id().clone())
307 } else {
308 None
309 }
310 })
311 .collect_vec();
312 for predecessor in &predecessors {
313 edges.push((predecessor.clone(), commit_id.clone()));
314 }
315 if predecessors.is_empty() {
316 initial_nodes.push(commit_id.clone());
317 if to_visit.is_empty() {
318 break;
319 }
320 } else {
321 to_visit.extend(predecessors);
322 }
323 }
324
325 validate(
326 !initial_nodes.is_empty(),
327 "Unexpected error: initial_nodes should not be empty",
328 )?;
329
330 let initial_node = if initial_nodes.len() == 1 {
332 initial_nodes[0].clone()
333 } else {
334 let root_commit_id = repo.store().root_commit_id().clone();
338 for initial_node in initial_nodes {
339 edges.push((root_commit_id.clone(), initial_node));
340 }
341 root_commit_id
342 };
343
344 let flow_graph = FlowGraph::new(SimpleDirectedGraph::new(edges), initial_node);
345 Ok(Self {
346 repo,
347 divergent_commits,
348 divergent_commit_ids,
349 flow_graph,
350 })
351 }
352
353 pub fn repo(&self) -> &Arc<ReadonlyRepo> {
355 &self.repo
356 }
357
358 pub fn divergent_commits(&self) -> &Vec<Commit> {
360 &self.divergent_commits
361 }
362
363 pub fn divergent_commit_ids(&self) -> &Vec<CommitId> {
365 &self.divergent_commit_ids
366 }
367
368 pub fn change_id(&self) -> &ChangeId {
371 self.divergent_commits[0].change_id()
372 }
373}
374
375async fn converge_author(
376 graph: &TruncatedEvolutionGraph,
377) -> Result<ConvergedAttribute<Signature>, ConvergeError> {
378 let value_fn = async |c: &Commit| Ok(c.author().clone());
379 let excluded_divergent_commits = HashSet::default();
380 let (value_merge, base_commit) =
381 create_value_merge(graph, &excluded_divergent_commits, value_fn).await?;
382 if let Some(value) = value_merge.resolve_trivial(SameChange::Accept) {
383 Ok(ConvergedAttribute::Solved(value.clone()))
384 } else {
385 Ok(ConvergedAttribute::Unsolved {
386 base_commit,
387 excluded_divergent_commits: HashSet::default(),
388 })
389 }
390}
391
392async fn converge_description(
393 graph: &TruncatedEvolutionGraph,
394) -> Result<ConvergedAttribute<String>, ConvergeError> {
395 let value_fn = async |c: &Commit| Ok(c.description().to_string());
396 let excluded_divergent_commits = HashSet::default();
397 let (value_merge, base_commit) =
398 create_value_merge(graph, &excluded_divergent_commits, value_fn).await?;
399 if let Some(value) = value_merge.resolve_trivial(SameChange::Accept) {
400 Ok(ConvergedAttribute::Solved(value.clone()))
401 } else {
402 Ok(ConvergedAttribute::Unsolved {
403 base_commit,
404 excluded_divergent_commits: HashSet::default(),
405 })
406 }
407}
408
409async fn converge_parents(
410 graph: &TruncatedEvolutionGraph,
411) -> Result<ConvergedAttribute<Vec<CommitId>>, ConvergeError> {
412 let viable_commits = remove_descendants(graph.repo(), graph.divergent_commit_ids()).await?;
416 let excluded_divergent_commits: HashSet<CommitId> = graph
417 .divergent_commit_ids()
418 .iter()
419 .filter(|commit_id| !viable_commits.contains(commit_id))
420 .cloned()
421 .collect();
422
423 let get_parents_fn = async |c: &Commit| Ok(c.parent_ids().to_vec());
424 let (value_merge, base_commit) =
425 create_value_merge(graph, &excluded_divergent_commits, get_parents_fn).await?;
426 if let Some(value) = value_merge.resolve_trivial(SameChange::Accept) {
427 Ok(ConvergedAttribute::Solved(value.clone()))
428 } else {
429 Ok(ConvergedAttribute::Unsolved {
430 base_commit,
431 excluded_divergent_commits,
432 })
433 }
434}
435
436#[derive(Eq, Hash, PartialEq, Clone, Debug)]
439pub struct TreeIdsAndLabels {
440 pub tree_ids: Merge<TreeId>,
442 pub labels: ConflictLabels,
444}
445
446impl TreeIdsAndLabels {
447 pub fn new(merged_tree: MergedTree) -> Self {
449 let (tree_ids, labels) = merged_tree.into_tree_ids_and_labels();
450 Self { tree_ids, labels }
451 }
452
453 pub fn to_merged_tree(&self, store: &Arc<Store>) -> MergedTree {
455 MergedTree::new(store.clone(), self.tree_ids.clone(), self.labels.clone())
456 }
457}
458
459async fn converge_trees(
491 truncated_evolution_graph: &TruncatedEvolutionGraph,
492 parents: &[CommitId],
493) -> Result<MergedTree, ConvergeError> {
494 let repo = truncated_evolution_graph.repo();
495 let parent_commits: Vec<Commit> =
496 try_join_all(parents.iter().map(|id| repo.store().get_commit_async(id))).await?;
497 let parents_merged_tree = merge_commit_trees_no_resolve(repo.as_ref(), &parent_commits).await?;
498 let rebased_resolved_trees = Arc::new(Mutex::new(HashMap::<CommitId, TreeIdsAndLabels>::new()));
499
500 let value_fn = async |commit: &Commit| -> Result<Merge<TreeId>, ConvergeError> {
505 let tree_ids_and_labels = TreeIdsAndLabels::new(
506 rebase_tree_onto_solution_parents(commit, parents, &parents_merged_tree, repo).await?,
507 );
508 rebased_resolved_trees
509 .lock()
510 .unwrap()
511 .insert(commit.id().clone(), tree_ids_and_labels.clone());
512 Ok(tree_ids_and_labels.tree_ids.clone())
517 };
518
519 let mut value_cache = ValueCache::new(async |commit_id: &CommitId| {
520 let commit = repo.store().get_commit_async(commit_id).await?;
521 value_fn(&commit).await
522 });
523 let dominator_value = truncated_evolution_graph
526 .flow_graph
527 .find_dominator_value_with_value_cache(
528 truncated_evolution_graph.divergent_commit_ids(),
529 &mut value_cache,
530 )
531 .await
532 .map_err(|e| ConvergeError::Other(e.into()))?;
533 let dominator_producer =
534 get_value_producer(truncated_evolution_graph, &dominator_value, &value_cache).await?;
535
536 let base_commit = repo.store().get_commit_async(&dominator_producer).await?;
537 let rebased_resolved_trees = Arc::try_unwrap(rebased_resolved_trees)
538 .map_err(|_| ConvergeError::Other("Failed to unwrap rebased_resolved_trees Arc".into()))?
539 .into_inner()
540 .unwrap();
541
542 let mut terms: Vec<(MergedTree, String)> = Vec::new();
543 let base_term = get_term_for_tree_merge(
544 &base_commit,
545 parents,
546 &rebased_resolved_trees,
547 "converge base",
548 );
549
550 terms.push(base_term.clone());
552 for divergent_commit in truncated_evolution_graph.divergent_commits() {
553 terms.push(base_term.clone());
555 terms.push(get_term_for_tree_merge(
557 divergent_commit,
558 parents,
559 &rebased_resolved_trees,
560 "divergent commit",
561 ));
562 }
563 Ok(MergedTree::merge(MergeBuilder::from_iter(terms).build()).await?)
564}
565
566fn get_term_for_tree_merge(
567 commit: &Commit,
568 parents: &[CommitId],
569 rebased_resolved_trees: &HashMap<CommitId, TreeIdsAndLabels>,
570 prefix: &str,
571) -> (MergedTree, String) {
572 let rebased_and_resolved_tree = rebased_resolved_trees
573 .get(commit.id())
574 .unwrap()
575 .to_merged_tree(commit.store());
576 let conflict_label = if commit.parent_ids() == parents {
577 format!("{prefix}: {}", commit.conflict_label())
578 } else {
579 format!(
580 "{prefix}: tree of {} rebased onto parents",
581 commit.conflict_label()
582 )
583 };
584 (rebased_and_resolved_tree, conflict_label)
585}
586
587async fn create_value_merge<T, VF>(
592 graph: &TruncatedEvolutionGraph,
593 excluded_divergent_commits: &HashSet<CommitId>,
594 value_fn: VF,
595) -> Result<(Merge<T>, CommitId), ConvergeError>
596where
597 T: Eq + Hash + Clone,
598 VF: AsyncFn(&Commit) -> Result<T, ConvergeError>,
599{
600 let mut value_cache = ValueCache::new(async |commit_id: &CommitId| {
601 let commit = graph.repo().store().get_commit_async(commit_id).await?;
602 value_fn(&commit).await
603 });
604
605 let divergent_commits = graph
606 .divergent_commit_ids()
607 .iter()
608 .filter(|id| !excluded_divergent_commits.contains(*id));
609
610 let dominator_value = graph
613 .flow_graph
614 .find_dominator_value_with_value_cache(divergent_commits.clone(), &mut value_cache)
615 .await
616 .map_err(|e| ConvergeError::Other(e.into()))?;
617 let dominator_producer = get_value_producer(graph, &dominator_value, &value_cache).await?;
618
619 let mut merge_builder = MergeBuilder::default();
620 merge_builder.extend([(*dominator_value).clone()]);
622 for divergent_commit in divergent_commits {
623 let commit_value = value_cache.get_value(divergent_commit).await?;
624 merge_builder.extend([(*dominator_value).clone(), (*commit_value).clone()]);
626 }
627 Ok((merge_builder.build(), dominator_producer))
628}
629
630async fn get_value_producer<T, VF>(
633 truncated_evolution_graph: &TruncatedEvolutionGraph,
634 value: &Rc<T>,
635 value_cache: &ValueCache<CommitId, T, VF>,
636) -> Result<CommitId, ConvergeError>
637where
638 T: Eq + Hash,
639 VF: AsyncFn(&CommitId) -> Result<T, ConvergeError>,
640{
641 let producers = value_cache.get_nodes_for_value(value).unwrap();
642 match producers.len() {
643 0 => unreachable!(), 1 => return Ok(producers[0].clone()),
645 _ => {}
646 }
647
648 let resolved_change_targets = truncated_evolution_graph
657 .repo()
658 .resolve_change_id(truncated_evolution_graph.change_id())
659 .await?;
660 let input_position: HashMap<&CommitId, usize> = truncated_evolution_graph
661 .divergent_commit_ids()
662 .iter()
663 .enumerate()
664 .map(|(position, commit_id)| (commit_id, position))
665 .collect();
666
667 type SortingKey = (usize, usize, i64, CommitId);
670 let producers: Vec<_> = try_join_all(producers.iter().map(
671 async |commit_id| -> Result<SortingKey, ConvergeError> {
672 let change_offset = match &resolved_change_targets {
673 Some(change_targets) => change_targets.find_offset(commit_id).unwrap_or(usize::MAX),
674 None => usize::MAX,
675 };
676 let input_position = *input_position.get(commit_id).unwrap_or(&usize::MAX);
677 let commit = truncated_evolution_graph
678 .repo()
679 .store()
680 .get_commit_async(commit_id)
681 .await?;
682 let millis_since_unix_epoch = commit.committer().timestamp.timestamp.0;
685 Ok((
686 change_offset,
687 input_position,
688 -millis_since_unix_epoch,
689 commit_id.clone(),
690 ))
691 },
692 ))
693 .await?;
694 let (_, _, _, commit_id) = producers.iter().min().unwrap().clone();
695 Ok(commit_id)
696}
697
698async fn rebase_tree_onto_solution_parents(
699 c: &Commit,
700 parents: &[CommitId],
701 parents_merged_tree: &MergedTree,
702 repo: &Arc<ReadonlyRepo>,
703) -> BackendResult<MergedTree> {
704 if c.parent_ids() == parents {
705 return Ok(c.tree());
706 }
707 let mut terms: Vec<(MergedTree, String)> = Vec::new();
708 terms.push((
710 parents_merged_tree.clone(),
711 "converge solution parent(s)".to_string(),
712 ));
713 terms.push((
715 c.parent_tree_no_resolve(repo.as_ref()).await?,
716 c.parents_conflict_label().await?,
717 ));
718 terms.push((c.tree(), c.conflict_label()));
720 MergedTree::merge(MergeBuilder::from_iter(terms).build()).await
721}
722
723pub async fn remove_descendants(
726 repo: &Arc<ReadonlyRepo>,
727 commit_ids: &[CommitId],
728) -> Result<HashSet<CommitId>, ConvergeError> {
729 if commit_ids.is_empty() {
730 return Ok(HashSet::default());
731 }
732 let revset_expression = Arc::new(RevsetExpression::Commits(commit_ids.to_vec())).roots();
733 let mut result = HashSet::with_capacity(commit_ids.len());
734 let mut stream = revset_expression.evaluate(repo.deref())?.stream();
735 while let Some(commit_id) = stream.try_next().await? {
736 result.insert(commit_id);
737 }
738
739 validate(
740 !result.is_empty(),
741 &format!("the result of remove_descendants should never be empty; commits: {commit_ids:?}"),
742 )?;
743 Ok(result)
744}
745
746fn validate(predicate: bool, msg: &str) -> Result<(), ConvergeError> {
747 if !predicate {
748 Err(ConvergeError::Other(msg.into()))
749 } else {
750 Ok(())
751 }
752}