just playing with tangled
at diffedit3 140 lines 5.6 kB view raw
1// Copyright 2024 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use std::collections::HashMap; 16 17use indexmap::IndexSet; 18use itertools::Itertools; 19use jj_lib::backend::CommitId; 20use jj_lib::commit::{Commit, CommitIteratorExt}; 21use tracing::instrument; 22 23use crate::cli_util::{CommandHelper, RevisionArg}; 24use crate::command_error::CommandError; 25use crate::ui::Ui; 26 27/// Parallelize revisions by making them siblings 28/// 29/// Running `jj parallelize 1::2` will transform the history like this: 30/// ```text 31/// 3 32/// | 3 33/// 2 / \ 34/// | -> 1 2 35/// 1 \ / 36/// | 0 37/// 0 38/// ``` 39/// 40/// The command effectively says "these revisions are actually independent", 41/// meaning that they should no longer be ancestors/descendants of each other. 42/// However, revisions outside the set that were previously ancestors of a 43/// revision in the set will remain ancestors of it. For example, revision 0 44/// above remains an ancestor of both 1 and 2. Similarly, 45/// revisions outside the set that were previously descendants of a revision 46/// in the set will remain descendants of it. For example, revision 3 above 47/// remains a descendant of both 1 and 2. 48/// 49/// Therefore, `jj parallelize '1 | 3'` is a no-op. That's because 2, which is 50/// not in the target set, was a descendant of 1 before, so it remains a 51/// descendant, and it was an ancestor of 3 before, so it remains an ancestor. 52#[derive(clap::Args, Clone, Debug)] 53#[command(verbatim_doc_comment)] 54pub(crate) struct ParallelizeArgs { 55 /// Revisions to parallelize 56 revisions: Vec<RevisionArg>, 57} 58 59#[instrument(skip_all)] 60pub(crate) fn cmd_parallelize( 61 ui: &mut Ui, 62 command: &CommandHelper, 63 args: &ParallelizeArgs, 64) -> Result<(), CommandError> { 65 let mut workspace_command = command.workspace_helper(ui)?; 66 // The target commits are the commits being parallelized. They are ordered 67 // here with children before parents. 68 let target_commits: Vec<Commit> = workspace_command 69 .parse_union_revsets(&args.revisions)? 70 .evaluate_to_commits()? 71 .try_collect()?; 72 workspace_command.check_rewritable(target_commits.iter().ids())?; 73 74 let mut tx = workspace_command.start_transaction(); 75 76 // New parents for commits in the target set. Since commits in the set are now 77 // supposed to be independent, they inherit the parent's non-target parents, 78 // recursively. 79 let mut new_target_parents: HashMap<CommitId, Vec<CommitId>> = HashMap::new(); 80 for commit in target_commits.iter().rev() { 81 let mut new_parents = vec![]; 82 for old_parent in commit.parent_ids() { 83 if let Some(grand_parents) = new_target_parents.get(old_parent) { 84 new_parents.extend_from_slice(grand_parents); 85 } else { 86 new_parents.push(old_parent.clone()); 87 } 88 } 89 new_target_parents.insert(commit.id().clone(), new_parents); 90 } 91 92 // If a commit outside the target set has a commit in the target set as parent, 93 // then - after the transformation - it should also have that commit's 94 // parents as direct parents, if those commits are also in the target set. 95 let mut new_child_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new(); 96 for commit in target_commits.iter().rev() { 97 let mut new_parents = IndexSet::new(); 98 for old_parent in commit.parent_ids() { 99 if let Some(parents) = new_child_parents.get(old_parent) { 100 new_parents.extend(parents.iter().cloned()); 101 } 102 } 103 new_parents.insert(commit.id().clone()); 104 new_child_parents.insert(commit.id().clone(), new_parents); 105 } 106 107 tx.mut_repo().transform_descendants( 108 command.settings(), 109 target_commits.iter().ids().cloned().collect_vec(), 110 |mut rewriter| { 111 // Commits in the target set do not depend on each other but they still depend 112 // on other parents 113 if let Some(new_parents) = new_target_parents.get(rewriter.old_commit().id()) { 114 rewriter.set_new_rewritten_parents(new_parents.clone()); 115 } else if rewriter 116 .old_commit() 117 .parent_ids() 118 .iter() 119 .any(|id| new_child_parents.contains_key(id)) 120 { 121 let mut new_parents = vec![]; 122 for parent in rewriter.old_commit().parent_ids() { 123 if let Some(parents) = new_child_parents.get(parent) { 124 new_parents.extend(parents.iter().cloned()); 125 } else { 126 new_parents.push(parent.clone()); 127 } 128 } 129 rewriter.set_new_rewritten_parents(new_parents); 130 } 131 if rewriter.parents_changed() { 132 let builder = rewriter.rebase(command.settings())?; 133 builder.write()?; 134 } 135 Ok(()) 136 }, 137 )?; 138 139 tx.finish(ui, format!("parallelize {} commits", target_commits.len())) 140}