1use std::collections::BTreeMap;
2use std::collections::HashSet;
3use std::collections::VecDeque;
4
5use crate::legacy::graph::Graph;
6use crate::legacy::graph::Node;
7
8pub fn protect_branches(
9 graph: &mut Graph,
10 repo: &dyn crate::legacy::git::Repo,
11 protected_branches: &crate::legacy::git::Branches,
12) {
13 let protected_oids: HashSet<_> = protected_branches
14 .iter()
15 .flat_map(|(_, branches)| branches.iter().map(|b| b.id))
16 .collect();
17
18 protect_commits(graph, repo, protected_oids);
19}
20
21pub fn protect_commits(
22 graph: &mut Graph,
23 repo: &dyn crate::legacy::git::Repo,
24 protected_oids: HashSet<git2::Oid>,
25) {
26 let root_id = graph.root_id();
27
28 for protected_oid in protected_oids.into_iter().filter(|protected_oid| {
29 repo.merge_base(root_id, *protected_oid)
30 .map(|merge_base_oid| merge_base_oid == root_id)
31 .unwrap_or(false)
32 }) {
33 for commit_id in crate::legacy::git::commit_range(repo, protected_oid..=root_id)
34 .expect("IDs already validated")
35 {
36 let commit = repo
37 .find_commit(commit_id)
38 .expect("commit_range returns valid commits");
39 if let Some(node) = graph.get_mut(commit.id) {
40 if node.action.is_protected() {
41 break;
42 }
43 node.action = crate::legacy::graph::Action::Protected;
44 }
45 if commit.id == root_id {
46 break;
47 }
48 }
49 }
50}
51
52pub fn protect_large_branches(graph: &mut Graph, max: usize) -> Vec<String> {
53 let mut large_branches = Vec::new();
54
55 let mut protected_queue = VecDeque::new();
56 if graph.root().action.is_protected() {
57 protected_queue.push_back(graph.root_id());
58 }
59 while let Some(current_id) = protected_queue.pop_front() {
60 let current_children = graph
61 .get(current_id)
62 .expect("all children exist")
63 .children
64 .clone();
65
66 for child_id in current_children {
67 let child_action = graph.get(child_id).expect("all children exist").action;
68 if child_action.is_protected() {
69 protected_queue.push_back(child_id);
70 } else {
71 let protected =
72 protect_large_branches_recursive(graph, child_id, 0, max, &mut large_branches);
73 if protected {
74 protected_queue.push_back(child_id);
75 }
76 }
77 }
78 }
79
80 large_branches
81}
82
83fn protect_large_branches_recursive(
84 graph: &mut Graph,
85 node_id: git2::Oid,
86 count: usize,
87 max: usize,
88 large_branches: &mut Vec<String>,
89) -> bool {
90 let mut needs_protection = false;
91
92 if !graph
93 .get(node_id)
94 .expect("all children exist")
95 .branches
96 .is_empty()
97 {
98 } else if count <= max {
99 let current_children = graph
100 .get(node_id)
101 .expect("all children exist")
102 .children
103 .clone();
104
105 for child_id in current_children {
106 needs_protection |=
107 protect_large_branches_recursive(graph, child_id, count + 1, max, large_branches);
108 }
109 if needs_protection {
110 let node = graph.get_mut(node_id).expect("all children exist");
111 node.action = crate::legacy::graph::Action::Protected;
112 }
113 } else {
114 mark_branch_protected(graph, node_id, large_branches);
115 needs_protection = true;
116 }
117
118 needs_protection
119}
120
121fn mark_branch_protected(graph: &mut Graph, node_id: git2::Oid, branches: &mut Vec<String>) {
122 let mut protected_queue = VecDeque::new();
123 protected_queue.push_back(node_id);
124 while let Some(current_id) = protected_queue.pop_front() {
125 let current = graph.get_mut(current_id).expect("all children exist");
126 current.action = crate::legacy::graph::Action::Protected;
127
128 if current.branches.is_empty() {
129 protected_queue.extend(&graph.get(current_id).expect("all children exist").children);
130 } else {
131 branches.extend(
132 current
133 .branches
134 .iter()
135 .filter_map(|b| b.local_name().map(String::from)),
137 );
138 }
139 }
140}
141
142pub fn protect_old_branches(
143 graph: &mut Graph,
144 earlier_than: std::time::SystemTime,
145 ignore: &[git2::Oid],
146) -> Vec<String> {
147 let mut old_branches = Vec::new();
148
149 let mut protected_queue = VecDeque::new();
150 if graph.root().action.is_protected() {
151 protected_queue.push_back(graph.root_id());
152 }
153 while let Some(current_id) = protected_queue.pop_front() {
154 let current_children = graph
155 .get(current_id)
156 .expect("all children exist")
157 .children
158 .clone();
159
160 for child_id in current_children {
161 let child_action = graph.get(child_id).expect("all children exist").action;
162 if child_action.is_protected() {
163 protected_queue.push_back(child_id);
164 } else {
165 if is_branch_old(graph, child_id, earlier_than, ignore) {
166 mark_branch_protected(graph, child_id, &mut old_branches);
167 }
168 }
169 }
170 }
171
172 old_branches
173}
174
175pub fn trim_old_branches(
176 graph: &mut Graph,
177 earlier_than: std::time::SystemTime,
178 ignore: &[git2::Oid],
179) -> Vec<String> {
180 let mut old_branches = Vec::new();
181
182 let mut protected_queue = VecDeque::new();
183 if graph.root().action.is_protected() {
184 protected_queue.push_back(graph.root_id());
185 }
186 while let Some(current_id) = protected_queue.pop_front() {
187 let current_children = graph
188 .get(current_id)
189 .expect("all children exist")
190 .children
191 .clone();
192
193 for child_id in current_children {
194 let child_action = graph.get(child_id).expect("all children exist").action;
195 if child_action.is_protected() {
196 protected_queue.push_back(child_id);
197 } else {
198 if is_branch_old(graph, child_id, earlier_than, ignore) {
199 let removed = graph
200 .remove_child(current_id, child_id)
201 .expect("all children exist");
202 old_branches.extend(removed.breadth_first_iter().flat_map(|n| {
203 n.branches
204 .iter()
205 .filter_map(|b| b.local_name().map(String::from))
207 }));
208 }
209 }
210 }
211 }
212
213 old_branches
214}
215
216fn is_branch_old(
217 graph: &Graph,
218 node_id: git2::Oid,
219 earlier_than: std::time::SystemTime,
220 ignore: &[git2::Oid],
221) -> bool {
222 if ignore.contains(&node_id) {
223 return false;
224 }
225
226 let current = graph.get(node_id).expect("all children exist");
227
228 if earlier_than < current.commit.time {
229 return false;
230 }
231
232 for child_id in current.children.iter().copied() {
233 if !is_branch_old(graph, child_id, earlier_than, ignore) {
234 return false;
235 }
236 }
237
238 true
239}
240
241pub fn protect_foreign_branches(
242 graph: &mut Graph,
243 user: &str,
244 ignore: &[git2::Oid],
245) -> Vec<String> {
246 let mut foreign_branches = Vec::new();
247
248 let mut protected_queue = VecDeque::new();
249 if graph.root().action.is_protected() {
250 protected_queue.push_back(graph.root_id());
251 }
252 while let Some(current_id) = protected_queue.pop_front() {
253 let current_children = graph
254 .get(current_id)
255 .expect("all children exist")
256 .children
257 .clone();
258
259 for child_id in current_children {
260 let child_action = graph.get(child_id).expect("all children exist").action;
261 if child_action.is_protected() {
262 protected_queue.push_back(child_id);
263 } else {
264 if !is_personal_branch(graph, child_id, user, ignore) {
265 mark_branch_protected(graph, child_id, &mut foreign_branches);
266 }
267 }
268 }
269 }
270
271 foreign_branches
272}
273
274pub fn trim_foreign_branches(graph: &mut Graph, user: &str, ignore: &[git2::Oid]) -> Vec<String> {
275 let mut foreign_branches = Vec::new();
276
277 let mut protected_queue = VecDeque::new();
278 if graph.root().action.is_protected() {
279 protected_queue.push_back(graph.root_id());
280 }
281 while let Some(current_id) = protected_queue.pop_front() {
282 let current_children = graph
283 .get(current_id)
284 .expect("all children exist")
285 .children
286 .clone();
287
288 for child_id in current_children {
289 let child_action = graph.get(child_id).expect("all children exist").action;
290 if child_action.is_protected() {
291 protected_queue.push_back(child_id);
292 } else {
293 if !is_personal_branch(graph, child_id, user, ignore) {
294 let removed = graph
295 .remove_child(current_id, child_id)
296 .expect("all children exist");
297 foreign_branches.extend(removed.breadth_first_iter().flat_map(|n| {
298 n.branches
299 .iter()
300 .filter_map(|b| b.local_name().map(String::from))
302 }));
303 }
304 }
305 }
306 }
307
308 foreign_branches
309}
310
311fn is_personal_branch(graph: &Graph, node_id: git2::Oid, user: &str, ignore: &[git2::Oid]) -> bool {
312 if ignore.contains(&node_id) {
313 return true;
314 }
315
316 let current = graph.get(node_id).expect("all children exist");
317
318 if current.commit.committer.as_deref() == Some(user)
319 || current.commit.author.as_deref() == Some(user)
320 {
321 return true;
322 }
323
324 for child_id in current.children.iter().copied() {
325 if is_personal_branch(graph, child_id, user, ignore) {
326 return true;
327 }
328 }
329
330 false
331}
332
333pub fn rebase_development_branches(graph: &mut Graph, new_base_id: git2::Oid) {
340 debug_assert!(graph.get(new_base_id).is_some());
341
342 let mut protected_queue = VecDeque::new();
343 if graph.root().action.is_protected() {
344 protected_queue.push_back(graph.root_id());
345 }
346 while let Some(current_id) = protected_queue.pop_front() {
347 let current_children = graph
348 .get(current_id)
349 .expect("all children exist")
350 .children
351 .clone();
352
353 let mut rebaseable = Vec::new();
354 for child_id in current_children {
355 let child_action = graph.get(child_id).expect("all children exist").action;
356 if child_action.is_protected() {
357 protected_queue.push_back(child_id);
358 } else {
359 rebaseable.push(child_id);
360 }
361 }
362
363 if !rebaseable.is_empty() {
364 let current = graph.get_mut(current_id).expect("all children exist");
365 for child_id in rebaseable.iter() {
366 current.children.remove(child_id);
367 }
368 graph
369 .get_mut(new_base_id)
370 .expect("pre-asserted")
371 .children
372 .extend(rebaseable);
373 }
374 }
375}
376
377pub fn fast_forward_pulled_branches(graph: &mut Graph, pull_start: git2::Oid, pull_end: git2::Oid) {
382 if pull_start == pull_end {
383 return;
384 }
385
386 let branches = std::mem::take(
387 &mut graph
388 .get_mut(pull_start)
389 .expect("all children exist")
390 .branches,
391 );
392 let (start_branches, end_branches) = branches.into_iter().partition(|b| b.remote.is_some());
393 graph
394 .get_mut(pull_start)
395 .expect("all children exist")
396 .branches = start_branches;
397 graph
398 .get_mut(pull_end)
399 .expect("all children exist")
400 .branches
401 .extend(end_branches);
402}
403
404pub fn pushable(graph: &mut Graph) {
405 let mut node_queue: VecDeque<(git2::Oid, Option<&str>)> = VecDeque::new();
406
407 if graph.root().action.is_protected() {
409 node_queue.push_back((graph.root_id(), None));
410 }
411 while let Some((current_id, mut cause)) = node_queue.pop_front() {
412 let current = graph.get_mut(current_id).expect("all children exist");
413 if current.action.is_protected() {
414 if !current.branches.is_empty() {
415 let branch = ¤t.branches[0];
416 log::debug!("{} isn't pushable, branch is protected", branch);
417 }
419 } else {
420 if cause.is_some() {
421 } else if !current.branches.is_empty()
423 && current.branches.iter().all(|b| Some(b.id) == b.push_id)
424 {
425 cause = Some("already pushed");
426 } else if current.commit.wip_summary().is_some() {
427 cause = Some("contains WIP commit");
428 }
429
430 if !current.branches.is_empty() {
431 let branch = ¤t.branches[0];
432 if let Some(c) = cause {
433 log::debug!("{} isn't pushable, {}", branch, c);
434 cause = Some("parent isn't pushable");
435 } else {
436 log::debug!("{} is pushable", branch);
437 current.pushable = true;
438 cause = Some("parent is pushable");
439 }
440 }
441 }
442
443 node_queue.extend(current.children.iter().copied().map(|id| (id, cause)));
444 }
445}
446
447pub fn drop_squashed_by_tree_id(
461 graph: &mut Graph,
462 pulled_tree_ids: impl Iterator<Item = git2::Oid>,
463) {
464 let pulled_tree_ids: HashSet<_> = pulled_tree_ids.collect();
465
466 let mut protected_queue = VecDeque::new();
467 let root_action = graph.root().action;
468 if root_action.is_protected() {
469 protected_queue.push_back(graph.root_id());
470 }
471 while let Some(current_id) = protected_queue.pop_front() {
472 let current_children = graph
473 .get(current_id)
474 .expect("all children exist")
475 .children
476 .clone();
477
478 for child_id in current_children {
479 let child_action = graph.get(child_id).expect("all children exist").action;
480 if child_action.is_protected() || child_action.is_delete() {
481 protected_queue.push_back(child_id);
482 } else {
483 drop_first_branch_by_tree_id(graph, child_id, HashSet::new(), &pulled_tree_ids);
484 }
485 }
486 }
487}
488
489fn drop_first_branch_by_tree_id(
490 graph: &mut Graph,
491 node_id: git2::Oid,
492 mut branch_ids: HashSet<git2::Oid>,
493 pulled_tree_ids: &HashSet<git2::Oid>,
494) {
495 branch_ids.insert(node_id);
496
497 let node = graph.get(node_id).expect("all children exist");
498 debug_assert!(!node.action.is_protected());
499 if node.commit.revert_summary().is_some() {
500 return;
503 }
504
505 let is_branch = !node.branches.is_empty();
506 let node_tree_id = node.commit.tree_id;
507
508 if is_branch {
509 if pulled_tree_ids.contains(&node_tree_id) {
510 for branch_id in branch_ids {
511 graph.get_mut(branch_id).expect("all children exist").action =
512 crate::legacy::graph::Action::Delete;
513 }
514 }
515 } else {
516 let node_children = graph
517 .get(node_id)
518 .expect("all children exist")
519 .children
520 .clone();
521 match node_children.len() {
522 0 => {}
523 1 => {
524 let child_id = node_children.into_iter().next().unwrap();
525 drop_first_branch_by_tree_id(graph, child_id, branch_ids, pulled_tree_ids);
526 }
527 _ => {
528 for child_id in node_children {
529 drop_first_branch_by_tree_id(
530 graph,
531 child_id,
532 branch_ids.clone(),
533 pulled_tree_ids,
534 );
535 }
536 }
537 }
538 }
539}
540
541pub fn drop_merged_branches(
546 graph: &mut Graph,
547 pulled_ids: impl Iterator<Item = git2::Oid>,
548 protected_branches: &crate::legacy::git::Branches,
549) -> Vec<String> {
550 let mut removed = Vec::new();
551 let protected_branch_names: HashSet<_> = protected_branches
552 .iter()
553 .flat_map(|(_, b)| b.iter())
554 .filter_map(|b| b.local_name())
556 .collect();
557
558 for pulled_id in pulled_ids {
559 if let Some(node) = graph.get_mut(pulled_id) {
561 if !node.branches.is_empty() {
562 for i in (node.branches.len() - 1)..=0 {
563 let branch = &node.branches[i];
564 if let Some(local_name) = branch.local_name() {
565 if !protected_branch_names.contains(local_name) {
566 let branch = node.branches.remove(i);
567 assert!(branch.remote.is_none());
568 removed.push(branch.name);
569 }
570 }
571 }
572 }
573 }
574 }
575
576 removed
577}
578
579pub fn fixup(graph: &mut Graph, effect: crate::config::Fixup) {
580 if effect == crate::config::Fixup::Ignore {
581 return;
582 }
583
584 let mut protected_queue = VecDeque::new();
585 let root_action = graph.root().action;
586 if root_action.is_protected() {
587 protected_queue.push_back(graph.root_id());
588 }
589 while let Some(current_id) = protected_queue.pop_front() {
590 let current_children = graph
591 .get(current_id)
592 .expect("all children exist")
593 .children
594 .clone();
595
596 for child_id in current_children {
597 let child_action = graph.get(child_id).expect("all children exist").action;
598 if child_action.is_protected() || child_action.is_delete() {
599 protected_queue.push_back(child_id);
600 } else {
601 fixup_branch(graph, current_id, child_id, effect);
602 }
603 }
604 }
605}
606
607fn fixup_branch(
608 graph: &mut Graph,
609 base_id: git2::Oid,
610 mut node_id: git2::Oid,
611 effect: crate::config::Fixup,
612) {
613 debug_assert_ne!(effect, crate::config::Fixup::Ignore);
614
615 let mut outstanding = BTreeMap::new();
616 let node_children = graph
617 .get(node_id)
618 .expect("all children exist")
619 .children
620 .clone();
621 for child_id in node_children {
622 fixup_node(graph, node_id, child_id, effect, &mut outstanding);
623 }
624 if !outstanding.is_empty() {
625 let node = graph.get_mut(node_id).expect("all children exist");
626 if let Some(fixup_ids) = outstanding.remove(&node.commit.summary) {
627 if effect == crate::config::Fixup::Squash {
628 for fixup_id in fixup_ids.iter().copied() {
629 let fixup = graph.get_mut(fixup_id).expect("all children exist");
630 assert!(fixup.action == crate::legacy::graph::Action::Pick);
631 fixup.action = crate::legacy::graph::Action::Fixup;
632 }
633 }
634 splice_after(graph, node_id, fixup_ids);
635 }
636 debug_assert_ne!(
637 graph.get(node_id).expect("all children exist").action,
638 crate::legacy::graph::Action::Protected,
639 "Unexpected result for {base_id}"
640 );
641 for fixup_ids in outstanding.into_values() {
642 node_id = splice_between(graph, base_id, node_id, fixup_ids);
643 }
644 }
645}
646
647fn fixup_node(
648 graph: &mut Graph,
649 base_id: git2::Oid,
650 node_id: git2::Oid,
651 effect: crate::config::Fixup,
652 outstanding: &mut BTreeMap<bstr::BString, Vec<git2::Oid>>,
653) {
654 debug_assert_ne!(effect, crate::config::Fixup::Ignore);
655
656 let node_children = graph
657 .get(node_id)
658 .expect("all children exist")
659 .children
660 .clone();
661 for child_id in node_children {
662 fixup_node(graph, node_id, child_id, effect, outstanding);
663 }
664
665 let mut patch = None;
666 let mut fixup_ids = Vec::new();
667 {
668 let node = graph.get_mut(node_id).expect("all children exist");
669 debug_assert_ne!(node.action, crate::legacy::graph::Action::Protected);
670 debug_assert_ne!(node.action, crate::legacy::graph::Action::Delete);
671 if let Some(summary) = node.commit.fixup_summary() {
672 outstanding
673 .entry(summary.to_owned())
674 .or_default()
675 .push(node_id);
676
677 let mut children = Default::default();
678 std::mem::swap(&mut node.children, &mut children);
679 let mut branches = Default::default();
680 std::mem::swap(&mut node.branches, &mut branches);
681 patch = Some((children, branches));
682 } else if let Some(ids) = outstanding.remove(&node.commit.summary) {
683 fixup_ids = ids;
684 }
685 }
686
687 if let Some((children, branches)) = patch {
688 debug_assert!(fixup_ids.is_empty());
689
690 let base = graph.get_mut(base_id).expect("all children exist");
691 debug_assert_ne!(base.action, crate::legacy::graph::Action::Protected);
692 debug_assert_ne!(base.action, crate::legacy::graph::Action::Delete);
693 base.children.remove(&node_id);
694 base.children.extend(children);
695 base.branches.extend(branches);
696 } else if !fixup_ids.is_empty() {
697 if effect == crate::config::Fixup::Squash {
698 for fixup_id in fixup_ids.iter().copied() {
699 let fixup = graph.get_mut(fixup_id).expect("all children exist");
700 assert!(fixup.action == crate::legacy::graph::Action::Pick);
701 fixup.action = crate::legacy::graph::Action::Fixup;
702 }
703 }
704 splice_after(graph, node_id, fixup_ids);
705 }
706}
707
708fn splice_between(
710 graph: &mut Graph,
711 parent_id: git2::Oid,
712 child_id: git2::Oid,
713 node_ids: Vec<git2::Oid>,
714) -> git2::Oid {
715 let mut new_child_id = child_id;
716 for node_id in node_ids {
717 let node = graph.get_mut(node_id).expect("all children exist");
718 debug_assert!(node.children.is_empty());
719 node.children.insert(new_child_id);
720 new_child_id = node.commit.id;
721 }
722 let parent = graph.get_mut(parent_id).expect("all children exist");
723 parent.children.remove(&child_id);
724 parent.children.insert(new_child_id);
725 new_child_id
726}
727
728fn splice_after(graph: &mut Graph, node_id: git2::Oid, fixup_ids: Vec<git2::Oid>) {
730 if fixup_ids.is_empty() {
731 return;
732 }
733
734 let mut new_children = Default::default();
735 let mut new_branches = Default::default();
736 {
737 let node = graph.get_mut(node_id).expect("all children exist");
738 std::mem::swap(&mut node.children, &mut new_children);
739 std::mem::swap(&mut node.branches, &mut new_branches);
740 }
741
742 let mut last_id = node_id;
743 for fixup_id in fixup_ids.into_iter().rev() {
744 let last = graph.get_mut(last_id).expect("all children exist");
745 last.children.insert(fixup_id);
746 last_id = fixup_id;
747 }
748
749 {
750 let last = graph.get_mut(last_id).expect("all children exist");
751 debug_assert!(last.children.is_empty());
752 debug_assert!(last.branches.is_empty());
753 std::mem::swap(&mut last.children, &mut new_children);
754 std::mem::swap(&mut last.branches, &mut new_branches);
755 }
756}
757
758pub fn realign_stacks(graph: &mut Graph) {
760 let mut protected_queue = VecDeque::new();
761 let root_action = graph.root().action;
762 if root_action.is_protected() {
763 protected_queue.push_back(graph.root_id());
764 }
765 while let Some(current_id) = protected_queue.pop_front() {
766 let current_children = graph
767 .get(current_id)
768 .expect("all children exist")
769 .children
770 .clone();
771
772 for child_id in current_children {
773 let child_action = graph.get(child_id).expect("all children exist").action;
774 if child_action.is_protected() || child_action.is_delete() {
775 protected_queue.push_back(child_id);
776 } else {
777 realign_stack(graph, child_id);
778 }
779 }
780 }
781}
782
783fn realign_stack(graph: &mut Graph, node_id: git2::Oid) {
784 let mut children = std::collections::BTreeSet::new();
785
786 let mut current_id = node_id;
787 loop {
788 let current = graph.get_mut(current_id).expect("all children exist");
789 if current.branches.is_empty() {
790 let mut current_children: Vec<_> = current.children.iter().copied().collect();
791 match current_children.len() {
792 0 => {
793 current.children.extend(children);
794 return;
795 }
796 1 => {
797 current_id = current_children.into_iter().next().unwrap();
798 }
799 _ => {
800 current_children.sort_unstable_by_key(|id| {
805 graph.get(*id).expect("all children exist").commit.time
806 });
807 let newest = current_children.pop().unwrap();
808 {
809 let current = graph.get_mut(current_id).expect("all children exist");
810 for child_id in ¤t_children {
811 current.children.remove(child_id);
812 }
813 }
814 children.extend(current_children);
815 current_id = newest;
816 }
817 }
818 } else {
819 if !children.is_empty() {
820 current.children.extend(children);
821 }
822 return;
823 }
824 }
825}
826
827pub fn merge_stacks(graph: &mut Graph) {
829 let mut protected_queue = VecDeque::new();
830 let root_action = graph.root().action;
831 if root_action.is_protected() {
832 protected_queue.push_back(graph.root_id());
833 }
834 while let Some(current_id) = protected_queue.pop_front() {
835 let current_children = graph
836 .get(current_id)
837 .expect("all children exist")
838 .children
839 .clone();
840
841 let mut unprotected_children = CommitTimesByTreeId::new();
842 for child_id in current_children {
843 let child_action = graph.get(child_id).expect("all children exist").action;
844 if child_action.is_protected() || child_action.is_delete() {
845 protected_queue.push_back(child_id);
846 } else {
847 let child = graph.get(child_id).expect("all children exist");
848
849 unprotected_children
850 .entry(child.commit.tree_id)
851 .or_default()
852 .push((child.commit.time, child_id));
853 }
854 }
855 if !unprotected_children.is_empty() {
856 merge_child_stacks(graph, current_id, unprotected_children);
857 }
858 }
859}
860
861type CommitTimesByTreeId = BTreeMap<git2::Oid, Vec<(std::time::SystemTime, git2::Oid)>>;
862
863fn merge_child_stacks(graph: &mut Graph, node_id: git2::Oid, children: CommitTimesByTreeId) {
864 let mut queue = VecDeque::new();
865 queue.push_back((node_id, children));
866 while let Some((current_id, children)) = queue.pop_front() {
867 for (_, mut children) in children {
868 match children.len() {
869 0 => unreachable!("each entry has at least one commit"),
870 1 => {
871 enqueue_merge_stack(&mut queue, graph, children[0].1);
872 }
873 _ => {
874 children.sort_unstable();
875 let last_index = children.len() - 1;
876 let last_child_id = children[last_index].1;
877
878 for (_, child_id) in &children[0..last_index] {
879 let mut grandchildren = Default::default();
880 let mut branches = Default::default();
881
882 let child = graph.get_mut(*child_id).expect("all children exist");
883 std::mem::swap(&mut child.branches, &mut branches);
884 std::mem::swap(&mut child.children, &mut grandchildren);
885
886 let last_child = graph.get_mut(last_child_id).expect("all children exist");
887 last_child.branches.extend(branches);
888 last_child.children.extend(grandchildren);
889
890 graph.remove_child(current_id, *child_id);
891 }
892
893 enqueue_merge_stack(&mut queue, graph, last_child_id);
894 }
895 }
896 }
897 }
898}
899
900fn enqueue_merge_stack(
901 queue: &mut VecDeque<(git2::Oid, CommitTimesByTreeId)>,
902 graph: &Graph,
903 node_id: git2::Oid,
904) {
905 let mut unprotected_children = CommitTimesByTreeId::new();
906 for child_id in graph
907 .get(node_id)
908 .expect("all children exist")
909 .children
910 .iter()
911 .copied()
912 {
913 let child = graph.get(child_id).expect("all children exist");
914
915 unprotected_children
916 .entry(child.commit.tree_id)
917 .or_default()
918 .push((child.commit.time, child_id));
919 }
920 if !unprotected_children.is_empty() {
921 queue.push_back((node_id, unprotected_children));
922 }
923}
924
925pub fn to_script(graph: &Graph) -> crate::legacy::git::Script {
926 let mut script = crate::legacy::git::Script::new();
927
928 let mut protected_queue = VecDeque::new();
929 if graph.root().action.is_protected() {
930 protected_queue.push_back(graph.root_id());
931 }
932 while let Some(current_id) = protected_queue.pop_front() {
933 let current = graph.get(current_id).expect("all children exist");
934
935 for child_id in current.children.iter().copied() {
936 let child = graph.get(child_id).expect("all children exist");
937 let child_action = child.action;
938 if child_action.is_protected() {
939 if !child.branches.is_empty() {
940 let stack_mark = child.commit.id;
942 script
943 .commands
944 .push(crate::legacy::git::Command::SwitchCommit(stack_mark));
945 for branch in child.branches.iter() {
946 if let Some(local_name) = branch.local_name() {
947 script
948 .commands
949 .push(crate::legacy::git::Command::CreateBranch(
950 local_name.to_owned(),
951 ));
952 }
953 }
954 }
955 protected_queue.push_back(child_id);
956 } else if let Some(mut dependent) = node_to_script(graph, child_id) {
957 dependent
958 .commands
959 .insert(0, crate::legacy::git::Command::SwitchCommit(current_id));
960 script.dependents.push(dependent);
961 }
962 }
963 }
964
965 script
966}
967
968fn node_to_script(graph: &Graph, node_id: git2::Oid) -> Option<crate::legacy::git::Script> {
969 let mut script = crate::legacy::git::Script::new();
970
971 let node = graph.get(node_id).expect("all children exist");
972 match node.action {
973 crate::legacy::graph::Action::Pick => {
974 script
975 .commands
976 .push(crate::legacy::git::Command::CherryPick(node.commit.id));
977 for branch in node.branches.iter() {
978 if let Some(local_name) = branch.local_name() {
979 script
980 .commands
981 .push(crate::legacy::git::Command::CreateBranch(
982 local_name.to_owned(),
983 ));
984 }
985 }
986
987 let node_dependents: Vec<_> = node
988 .children
989 .iter()
990 .copied()
991 .filter_map(|child_id| node_to_script(graph, child_id))
992 .collect();
993 if !node_dependents.is_empty() {
994 let transaction = !node.branches.is_empty();
996 extend_dependents(node, &mut script, node_dependents, transaction);
997 }
998 }
999 crate::legacy::graph::Action::Fixup => {
1000 script
1001 .commands
1002 .push(crate::legacy::git::Command::Fixup(node.commit.id));
1003 for branch in node.branches.iter() {
1006 if let Some(local_name) = branch.local_name() {
1007 script
1008 .commands
1009 .push(crate::legacy::git::Command::CreateBranch(
1010 local_name.to_owned(),
1011 ));
1012 }
1013 }
1014
1015 let node_dependents: Vec<_> = node
1016 .children
1017 .iter()
1018 .copied()
1019 .filter_map(|child_id| node_to_script(graph, child_id))
1020 .collect();
1021 if !node_dependents.is_empty() {
1022 let transaction = !node.branches.is_empty();
1024 extend_dependents(node, &mut script, node_dependents, transaction);
1025 }
1026 }
1027 crate::legacy::graph::Action::Protected => {
1028 let node_dependents: Vec<_> = node
1029 .children
1030 .iter()
1031 .copied()
1032 .filter_map(|child_id| node_to_script(graph, child_id))
1033 .collect();
1034 if !node_dependents.is_empty() || !node.branches.is_empty() {
1035 let stack_mark = node.commit.id;
1036 script
1037 .commands
1038 .push(crate::legacy::git::Command::SwitchCommit(stack_mark));
1039 for branch in node.branches.iter() {
1041 if let Some(local_name) = branch.local_name() {
1042 script
1043 .commands
1044 .push(crate::legacy::git::Command::CreateBranch(
1045 local_name.to_owned(),
1046 ));
1047 }
1048 }
1049
1050 let transaction = false;
1052 extend_dependents(node, &mut script, node_dependents, transaction);
1053 }
1054 }
1055 crate::legacy::graph::Action::Delete => {
1056 for branch in node.branches.iter() {
1057 if let Some(local_name) = branch.local_name() {
1058 script
1059 .commands
1060 .push(crate::legacy::git::Command::CreateBranch(
1061 local_name.to_owned(),
1062 ));
1063 }
1064 }
1065
1066 let node_dependents: Vec<_> = node
1067 .children
1068 .iter()
1069 .copied()
1070 .filter_map(|child_id| node_to_script(graph, child_id))
1071 .collect();
1072 if !node_dependents.is_empty() {
1073 let transaction = !node.branches.is_empty();
1075 extend_dependents(node, &mut script, node_dependents, transaction);
1076 }
1077 }
1078 }
1079
1080 if script.is_empty() {
1081 None
1082 } else {
1083 Some(script)
1084 }
1085}
1086
1087fn extend_dependents(
1088 node: &Node,
1089 script: &mut crate::legacy::git::Script,
1090 mut dependents: Vec<crate::legacy::git::Script>,
1091 transaction: bool,
1092) {
1093 if !transaction && dependents.len() == 1 {
1095 let dependent = dependents.remove(0);
1096 script.commands.extend(dependent.commands);
1097 script.dependents.extend(dependent.dependents);
1098 } else {
1099 let stack_mark = node.commit.id;
1101 script
1102 .commands
1103 .push(crate::legacy::git::Command::RegisterMark(stack_mark));
1104 for dependent in dependents.iter_mut() {
1105 dependent
1106 .commands
1107 .insert(0, crate::legacy::git::Command::SwitchMark(stack_mark));
1108 }
1109 script.dependents.extend(dependents);
1110 }
1111}