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