1use std::collections::{HashMap, HashSet};
16
17use itertools::{process_results, Itertools};
18
19use crate::backend::{BackendError, BackendResult, CommitId, ObjectId};
20use crate::commit::Commit;
21use crate::dag_walk;
22use crate::op_store::RefTarget;
23use crate::repo::{MutableRepo, Repo};
24use crate::repo_path::RepoPath;
25use crate::revset::RevsetExpression;
26use crate::settings::UserSettings;
27use crate::tree::{merge_trees, Tree};
28use crate::view::RefName;
29
30pub fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> Tree {
31 let store = repo.store();
32 if commits.is_empty() {
33 store
34 .get_tree(&RepoPath::root(), store.empty_tree_id())
35 .unwrap()
36 } else {
37 let index = repo.index();
38 let mut new_tree = commits[0].tree();
39 let commit_ids = commits
40 .iter()
41 .map(|commit| commit.id().clone())
42 .collect_vec();
43 for (i, other_commit) in commits.iter().enumerate().skip(1) {
44 let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &[commit_ids[i].clone()]);
45 let ancestors = ancestor_ids
46 .iter()
47 .map(|id| store.get_commit(id).unwrap())
48 .collect_vec();
49 let ancestor_tree = merge_commit_trees(repo, &ancestors);
50 let new_tree_id = merge_trees(&new_tree, &ancestor_tree, &other_commit.tree()).unwrap();
51 new_tree = store.get_tree(&RepoPath::root(), &new_tree_id).unwrap();
52 }
53 new_tree
54 }
55}
56
57pub fn rebase_commit(
58 settings: &UserSettings,
59 mut_repo: &mut MutableRepo,
60 old_commit: &Commit,
61 new_parents: &[Commit],
62) -> BackendResult<Commit> {
63 let old_parents = old_commit.parents();
64 let old_parent_trees = old_parents
65 .iter()
66 .map(|parent| parent.store_commit().root_tree.clone())
67 .collect_vec();
68 let new_parent_trees = new_parents
69 .iter()
70 .map(|parent| parent.store_commit().root_tree.clone())
71 .collect_vec();
72 let new_tree_id = if new_parent_trees == old_parent_trees {
73 old_commit.tree_id().clone()
75 } else {
76 let old_base_tree = merge_commit_trees(mut_repo, &old_parents);
77 let new_base_tree = merge_commit_trees(mut_repo, new_parents);
78 merge_trees(&new_base_tree, &old_base_tree, &old_commit.tree()).unwrap()
80 };
81 let new_parent_ids = new_parents
82 .iter()
83 .map(|commit| commit.id().clone())
84 .collect();
85 mut_repo
86 .rewrite_commit(settings, old_commit)
87 .set_parents(new_parent_ids)
88 .set_tree(new_tree_id)
89 .write()
90}
91
92pub fn back_out_commit(
93 settings: &UserSettings,
94 mut_repo: &mut MutableRepo,
95 old_commit: &Commit,
96 new_parents: &[Commit],
97) -> BackendResult<Commit> {
98 let old_base_tree = merge_commit_trees(mut_repo, &old_commit.parents());
99 let new_base_tree = merge_commit_trees(mut_repo, new_parents);
100 let new_tree_id = merge_trees(&new_base_tree, &old_commit.tree(), &old_base_tree).unwrap();
102 let new_parent_ids = new_parents
103 .iter()
104 .map(|commit| commit.id().clone())
105 .collect();
106 mut_repo
108 .new_commit(settings, new_parent_ids, new_tree_id)
109 .set_description(format!("backout of commit {}", &old_commit.id().hex()))
110 .write()
111}
112
113pub struct DescendantRebaser<'settings, 'repo> {
118 settings: &'settings UserSettings,
119 mut_repo: &'repo mut MutableRepo,
120 new_parents: HashMap<CommitId, Vec<CommitId>>,
125 divergent: HashMap<CommitId, Vec<CommitId>>,
126 to_visit: Vec<CommitId>,
128 abandoned: HashSet<CommitId>,
132 new_commits: HashSet<CommitId>,
133 rebased: HashMap<CommitId, CommitId>,
134 branches: HashMap<CommitId, HashSet<String>>,
136 heads_to_add: HashSet<CommitId>,
139 heads_to_remove: Vec<CommitId>,
140}
141
142impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
143 pub fn new(
144 settings: &'settings UserSettings,
145 mut_repo: &'repo mut MutableRepo,
146 rewritten: HashMap<CommitId, HashSet<CommitId>>,
147 abandoned: HashSet<CommitId>,
148 ) -> DescendantRebaser<'settings, 'repo> {
149 let root_commit_id = mut_repo.store().root_commit_id();
150 assert!(!abandoned.contains(root_commit_id));
151 assert!(!rewritten.contains_key(root_commit_id));
152 let old_commits_expression = RevsetExpression::commits(rewritten.keys().cloned().collect())
153 .union(&RevsetExpression::commits(
154 abandoned.iter().cloned().collect(),
155 ));
156 let heads_to_add_expression = old_commits_expression
157 .parents()
158 .minus(&old_commits_expression);
159 let heads_to_add = heads_to_add_expression
160 .evaluate(mut_repo, None)
161 .unwrap()
162 .iter()
163 .commit_ids()
164 .collect();
165
166 let to_visit_expression = old_commits_expression.descendants();
167 let to_visit_revset = to_visit_expression.evaluate(mut_repo, None).unwrap();
168 let to_visit_entries = to_visit_revset.iter().collect_vec();
169 drop(to_visit_revset);
170 let index = mut_repo.index();
171 let to_visit_set: HashSet<CommitId> = to_visit_entries
172 .iter()
173 .map(|entry| entry.commit_id())
174 .collect();
175 let mut visited = HashSet::new();
176 let to_visit = dag_walk::topo_order_reverse(
179 to_visit_entries,
180 Box::new(|entry| entry.commit_id()),
181 Box::new(|entry| {
182 visited.insert(entry.commit_id());
183 let mut dependents = vec![];
184 for parent in entry.parents() {
185 if let Some(targets) = rewritten.get(&parent.commit_id()) {
186 for target in targets {
187 if to_visit_set.contains(target) && !visited.contains(target) {
188 dependents.push(index.entry_by_id(target).unwrap());
189 }
190 }
191 }
192 if to_visit_set.contains(&parent.commit_id()) {
193 dependents.push(parent);
194 }
195 }
196 dependents
197 }),
198 );
199 let to_visit = to_visit.iter().map(|entry| entry.commit_id()).collect_vec();
200
201 let new_commits = rewritten.values().flatten().cloned().collect();
202
203 let mut new_parents = HashMap::new();
204 let mut divergent = HashMap::new();
205 for (old_commit, new_commits) in rewritten {
206 if new_commits.len() == 1 {
207 new_parents.insert(old_commit, vec![new_commits.iter().next().unwrap().clone()]);
208 } else {
209 let new_commits = mut_repo.index().heads(&mut new_commits.iter());
211 divergent.insert(old_commit, new_commits);
212 }
213 }
214
215 let mut branches: HashMap<_, HashSet<_>> = HashMap::new();
218 for (branch_name, branch_target) in mut_repo.view().branches() {
219 if let Some(local_target) = &branch_target.local_target {
220 for commit in local_target.adds() {
221 branches
222 .entry(commit)
223 .or_default()
224 .insert(branch_name.clone());
225 }
226 for commit in local_target.removes() {
227 branches
228 .entry(commit)
229 .or_default()
230 .insert(branch_name.clone());
231 }
232 }
233 }
234
235 DescendantRebaser {
236 settings,
237 mut_repo,
238 new_parents,
239 divergent,
240 to_visit,
241 abandoned,
242 new_commits,
243 rebased: Default::default(),
244 branches,
245 heads_to_add,
246 heads_to_remove: Default::default(),
247 }
248 }
249
250 pub fn rebased(&self) -> &HashMap<CommitId, CommitId> {
254 &self.rebased
255 }
256
257 fn new_parents(&self, old_ids: &[CommitId]) -> Vec<CommitId> {
258 let mut new_ids = vec![];
259 for old_id in old_ids {
260 if let Some(new_parent_ids) = self.new_parents.get(old_id) {
261 for new_parent_id in new_parent_ids {
262 if let Some(newer_parent_id) = self.rebased.get(new_parent_id) {
264 new_ids.push(newer_parent_id.clone());
265 } else {
266 new_ids.push(new_parent_id.clone());
267 }
268 }
269 } else if let Some(new_parent_id) = self.rebased.get(old_id) {
270 new_ids.push(new_parent_id.clone());
271 } else {
272 new_ids.push(old_id.clone());
273 };
274 }
275 new_ids
276 }
277
278 fn ref_target_update(old_id: CommitId, new_ids: Vec<CommitId>) -> (RefTarget, RefTarget) {
279 let old_ids = std::iter::repeat(old_id).take(new_ids.len()).collect_vec();
280 (
281 RefTarget::Conflict {
282 removes: vec![],
283 adds: old_ids,
284 },
285 RefTarget::Conflict {
286 removes: vec![],
287 adds: new_ids,
288 },
289 )
290 }
291
292 fn update_references(
293 &mut self,
294 old_commit_id: CommitId,
295 new_commit_ids: Vec<CommitId>,
296 edit: bool,
297 ) -> Result<(), BackendError> {
298 self.update_wc_commits(&old_commit_id, &new_commit_ids[0], edit)?;
300
301 if let Some(branch_names) = self.branches.get(&old_commit_id).cloned() {
302 let mut branch_updates = vec![];
303 for branch_name in &branch_names {
304 for new_commit_id in &new_commit_ids {
305 self.branches
306 .entry(new_commit_id.clone())
307 .or_default()
308 .insert(branch_name.clone());
309 }
310 let local_target = self.mut_repo.get_local_branch(branch_name).unwrap();
311 for old_add in local_target.adds() {
312 if old_add == old_commit_id {
313 branch_updates.push(branch_name.clone());
314 }
315 }
316 }
317 let (old_target, new_target) =
318 DescendantRebaser::ref_target_update(old_commit_id.clone(), new_commit_ids);
319 for branch_name in branch_updates {
320 self.mut_repo.merge_single_ref(
321 &RefName::LocalBranch(branch_name),
322 Some(&old_target),
323 Some(&new_target),
324 );
325 }
326 }
327
328 self.heads_to_add.remove(&old_commit_id);
329 if !self.new_commits.contains(&old_commit_id) || self.rebased.contains_key(&old_commit_id) {
330 self.heads_to_remove.push(old_commit_id);
331 }
332 Ok(())
333 }
334
335 fn update_wc_commits(
336 &mut self,
337 old_commit_id: &CommitId,
338 new_commit_id: &CommitId,
339 edit: bool,
340 ) -> Result<(), BackendError> {
341 let workspaces_to_update = self
342 .mut_repo
343 .view()
344 .workspaces_for_wc_commit_id(old_commit_id);
345 if workspaces_to_update.is_empty() {
346 return Ok(());
347 }
348
349 let new_commit = self.mut_repo.store().get_commit(new_commit_id)?;
350 let new_wc_commit = if edit {
351 new_commit
352 } else {
353 self.mut_repo
354 .new_commit(
355 self.settings,
356 vec![new_commit.id().clone()],
357 new_commit.tree_id().clone(),
358 )
359 .write()?
360 };
361 for workspace_id in workspaces_to_update.into_iter() {
362 self.mut_repo.edit(workspace_id, &new_wc_commit).unwrap();
363 }
364 Ok(())
365 }
366
367 pub fn rebase_next(&mut self) -> Result<Option<RebasedDescendant>, BackendError> {
370 while let Some(old_commit_id) = self.to_visit.pop() {
371 if let Some(new_parent_ids) = self.new_parents.get(&old_commit_id).cloned() {
372 self.update_references(old_commit_id, new_parent_ids, true)?;
377 continue;
378 }
379 if let Some(divergent_ids) = self.divergent.get(&old_commit_id).cloned() {
380 self.update_references(old_commit_id, divergent_ids, true)?;
383 continue;
384 }
385 let old_commit = self.mut_repo.store().get_commit(&old_commit_id)?;
386 let old_parent_ids = old_commit.parent_ids();
387 let new_parent_ids = self.new_parents(old_parent_ids);
388 if self.abandoned.contains(&old_commit_id) {
389 self.new_parents
391 .insert(old_commit_id.clone(), new_parent_ids.clone());
392 self.update_references(old_commit_id, new_parent_ids, false)?;
393 continue;
394 } else if new_parent_ids == old_parent_ids {
395 continue;
397 }
398
399 let head_set: HashSet<_> = self
401 .mut_repo
402 .index()
403 .heads(&mut new_parent_ids.iter())
404 .iter()
405 .cloned()
406 .collect();
407 let new_parents = process_results(
408 new_parent_ids
409 .iter()
410 .filter(|new_parent| head_set.contains(new_parent))
411 .map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id)),
412 |iter| iter.collect_vec(),
413 )?;
414 let new_commit =
415 rebase_commit(self.settings, self.mut_repo, &old_commit, &new_parents)?;
416 self.rebased
417 .insert(old_commit_id.clone(), new_commit.id().clone());
418 self.update_references(old_commit_id, vec![new_commit.id().clone()], true)?;
419 return Ok(Some(RebasedDescendant {
420 old_commit,
421 new_commit,
422 }));
423 }
424 let mut view = self.mut_repo.view().store_view().clone();
428 for commit_id in &self.heads_to_remove {
429 view.head_ids.remove(commit_id);
430 }
431 for commit_id in &self.heads_to_add {
432 view.head_ids.insert(commit_id.clone());
433 }
434 self.heads_to_remove.clear();
435 self.heads_to_add.clear();
436 self.mut_repo.set_view(view);
437 self.mut_repo.clear_rewritten_commits();
438 self.mut_repo.clear_abandoned_commits();
439 Ok(None)
440 }
441
442 pub fn rebase_all(&mut self) -> Result<(), BackendError> {
443 while self.rebase_next()?.is_some() {}
444 Ok(())
445 }
446}
447
448#[derive(Debug, PartialEq, Eq, Clone)]
449pub struct RebasedDescendant {
450 pub old_commit: Commit,
451 pub new_commit: Commit,
452}