1use sley_core::{BString, GitError, ObjectFormat, ObjectId, Result};
4use sley_index::{is_gitlink, is_symlink_mode};
5use sley_object::{Commit, EncodedObject, ObjectType, Tree, TreeEntries, TreeEntry};
6use sley_odb::{FileObjectDatabase, ObjectReader, ObjectWriter};
7use std::collections::{BTreeMap, BTreeSet};
8use std::path::Path;
9use std::sync::Arc;
10
11use crate::blob_merge::{
12 merge_blobs, ConflictStyle, MergeBlobOptions, MergeBlobResult, MergeFavor,
13};
14use crate::line_diff::WsIgnore;
15use crate::name_status::{
16 diff_name_status_maps_with_renames, flatten_tree, is_type_change, DiffNameStatusOptions,
17 MergeEntryMap, NameStatus, NameStatusEntry, TrackedEntry, DEFAULT_RENAME_THRESHOLD,
18};
19
20pub struct MergeTreesOptions<'a> {
43 pub ours_label: &'a str,
45 pub theirs_label: &'a str,
47 pub ancestor_label: &'a str,
50 pub favor: MergeFavor,
52 pub path_favor: Option<&'a dyn Fn(&[u8]) -> MergeFavor>,
56 pub path_marker_size: Option<&'a dyn Fn(&[u8]) -> usize>,
59 pub detect_renames: bool,
63 pub rename_threshold: u8,
65 pub rename_limit: usize,
69 pub directory_renames: DirectoryRenames,
75 pub style: ConflictStyle,
77 pub ws_ignore: WsIgnore,
80}
81
82#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
85pub enum DirectoryRenames {
86 #[default]
88 False,
89 True,
91 Conflict,
94}
95
96impl Default for MergeTreesOptions<'_> {
97 fn default() -> Self {
98 Self {
99 ours_label: "ours",
100 theirs_label: "theirs",
101 ancestor_label: "merged common ancestors",
102 favor: MergeFavor::None,
103 path_favor: None,
104 path_marker_size: None,
105 detect_renames: false,
106 rename_threshold: DEFAULT_RENAME_THRESHOLD,
107 rename_limit: 0,
108 directory_renames: DirectoryRenames::False,
109 style: ConflictStyle::Merge,
110 ws_ignore: WsIgnore::EMPTY,
111 }
112 }
113}
114
115fn merge_favor_for_path(options: &MergeTreesOptions<'_>, path: &[u8]) -> MergeFavor {
116 if options.favor != MergeFavor::None {
117 return options.favor;
118 }
119 options
120 .path_favor
121 .map(|resolver| resolver(path))
122 .unwrap_or(MergeFavor::None)
123}
124
125fn merge_marker_size_for_path(options: &MergeTreesOptions<'_>, path: &[u8]) -> usize {
126 options
127 .path_marker_size
128 .map(|resolver| resolver(path))
129 .unwrap_or(7)
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum MergeConflictKind {
136 Content { add_add: bool },
139 ModifyDelete {
141 deleted_in: String,
143 modified_in: String,
145 },
146 RenameContent {
149 old_path: Vec<u8>,
151 },
152 RenameRenameTwoToOne {
155 ours_path: Vec<u8>,
157 theirs_path: Vec<u8>,
159 },
160 RenameRenameOneToTwo {
163 old_path: Vec<u8>,
165 ours_path: Vec<u8>,
167 theirs_path: Vec<u8>,
169 ours_label: String,
171 theirs_label: String,
173 },
174 RenameRenameOneToTwoStage,
177 DirRenameSplit {
180 source_dir: Vec<u8>,
182 },
183 RenameDelete {
185 old_path: Vec<u8>,
187 renamed_in: String,
189 deleted_in: String,
191 },
192 FileDirectory {
198 original_path: Vec<u8>,
200 moved_from: String,
202 },
203 DirRenameLocation {
209 old_path: Vec<u8>,
212 renamed_from: Option<Vec<u8>>,
216 added_in: String,
218 dir_renamed_in: String,
220 back_to_self: bool,
226 },
227 DirRenameImplicitCollision {
234 sources: Vec<Vec<u8>>,
236 },
237 DistinctTypes {
247 original_path: Vec<u8>,
249 ours_renamed: Option<Vec<u8>>,
252 theirs_renamed: Option<Vec<u8>>,
255 },
256 DistinctTypesStage,
259}
260
261#[derive(Debug, Clone)]
263pub struct MergedPath {
264 pub path: Vec<u8>,
266 pub stages: MergeStages,
269 pub result: Option<(u32, ObjectId)>,
272 pub worktree: Option<(u32, Vec<u8>)>,
276 pub conflict: Option<MergeConflictKind>,
278 pub auto_merged: bool,
283}
284
285impl MergedPath {
286 pub fn is_clean(&self) -> bool {
288 self.conflict.is_none()
289 }
290}
291
292#[derive(Debug, Clone, Default)]
294pub struct MergeStages {
295 pub base: Option<(u32, ObjectId)>,
296 pub ours: Option<(u32, ObjectId)>,
297 pub theirs: Option<(u32, ObjectId)>,
298}
299
300#[derive(Debug, Clone)]
303pub struct MergeTreesResult {
304 pub tree: ObjectId,
307 pub paths: Vec<MergedPath>,
309 pub clean: bool,
311 pub cleanup_paths: Vec<Vec<u8>>,
315 pub info_messages: Vec<MergeInfoMessage>,
317}
318
319impl MergeTreesResult {
320 pub fn conflicts(&self) -> impl Iterator<Item = &MergedPath> {
322 self.paths.iter().filter(|entry| entry.conflict.is_some())
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
328pub enum MergeInfoMessage {
329 DirRenameSkippedDueToRerename {
332 old_dir: Vec<u8>,
333 path: Vec<u8>,
334 new_dir: Vec<u8>,
335 },
336 DirRenameApplied {
339 old_path: Vec<u8>,
340 new_path: Vec<u8>,
341 renamed_from: Option<Vec<u8>>,
342 added_in: String,
343 dir_renamed_in: String,
344 },
345 DirRenameLocationConflict {
350 old_path: Vec<u8>,
351 new_path: Vec<u8>,
352 renamed_from: Option<Vec<u8>>,
353 added_in: String,
354 dir_renamed_in: String,
355 },
356 RenameDeleteConflict {
360 old_path: Vec<u8>,
361 new_path: Vec<u8>,
362 renamed_in: String,
363 deleted_in: String,
364 },
365}
366
367
368pub fn is_mergeable_file_mode(mode: u32) -> bool {
371 mode == 0o100644 || mode == 0o100755
372}
373
374pub fn merge_trees(
387 db: &FileObjectDatabase,
388 format: ObjectFormat,
389 base: Option<&ObjectId>,
390 ours: &ObjectId,
391 theirs: &ObjectId,
392 options: &MergeTreesOptions<'_>,
393) -> Result<MergeTreesResult> {
394 let base_map = match base {
395 Some(tree) => flatten_tree(db, format, tree)?,
396 None => MergeEntryMap::new(),
397 };
398 let ours_map = flatten_tree(db, format, ours)?;
399 let theirs_map = flatten_tree(db, format, theirs)?;
400 merge_entry_maps(db, format, &base_map, &ours_map, &theirs_map, options)
401}
402
403pub fn merge_entry_maps(
407 db: &FileObjectDatabase,
408 format: ObjectFormat,
409 base_map: &MergeEntryMap,
410 ours_map: &MergeEntryMap,
411 theirs_map: &MergeEntryMap,
412 options: &MergeTreesOptions<'_>,
413) -> Result<MergeTreesResult> {
414 let (mut renames, side_renames) = if options.detect_renames {
421 let (renames, ours_side, theirs_side) =
422 detect_merge_renames(db, format, base_map, ours_map, theirs_map, options)?;
423 (renames, Some((ours_side, theirs_side)))
424 } else {
425 (MergeRenames::default(), None)
426 };
427
428 let (mut eff_base, mut eff_ours, mut eff_theirs) =
430 apply_merge_renames(base_map, ours_map, theirs_map, &renames);
431
432 let mut dir_rename_dirty = false;
440 let mut rehomed_paths: BTreeMap<Vec<u8>, RehomeSides> = BTreeMap::new();
441 let mut dir_rename_two_to_one: Vec<DirRenameTwoToOne> = Vec::new();
442 let mut dir_rename_collisions: Vec<DirRenameCollision> = Vec::new();
443 let mut dir_rename_splits: BTreeSet<Vec<u8>> = BTreeSet::new();
444 let mut dir_rename_back_to_self: BTreeSet<Vec<u8>> = BTreeSet::new();
445 let mut info_messages = Vec::new();
446 let mut cleanup_paths: BTreeSet<Vec<u8>> = renames
447 .dest_to_source
448 .values()
449 .map(|rename| rename.source.clone())
450 .collect();
451 if options.directory_renames != DirectoryRenames::False
452 && let Some((ours_side, theirs_side)) = &side_renames
453 {
454 let dir_renames = compute_directory_renames(ours_map, theirs_map, ours_side, theirs_side);
455 let outcome = apply_directory_renames(
456 base_map,
457 &eff_base,
458 &eff_ours,
459 &eff_theirs,
460 ours_side,
461 theirs_side,
462 &dir_renames,
463 &renames.dest_to_source,
464 );
465 eff_base = outcome.base;
466 eff_ours = outcome.ours;
467 eff_theirs = outcome.theirs;
468 rehomed_paths = outcome.rehomed;
469 dir_rename_collisions = outcome.collisions;
470 dir_rename_splits = outcome.splits;
471 dir_rename_back_to_self = outcome.back_to_self;
472 info_messages = outcome.info_messages;
473 dir_rename_dirty = outcome.dirty;
474 remap_rename_destinations(&mut renames, &rehomed_paths);
475 drop_collapsed_rename_rename_conflicts(&mut renames);
476 dir_rename_two_to_one = collect_dir_rename_two_to_one(&renames, &rehomed_paths);
477 }
478 for info in rehomed_paths
479 .values()
480 .flat_map(|sides| [&sides.ours, &sides.theirs])
481 .flatten()
482 {
483 cleanup_paths.insert(info.old_path.clone());
484 }
485 if options.directory_renames == DirectoryRenames::True {
486 for (dest, sides) in &rehomed_paths {
487 for info in [&sides.ours, &sides.theirs].into_iter().flatten() {
488 let (added_in, dir_renamed_in) = if info.added_on_ours {
489 (
490 options.ours_label.to_string(),
491 options.theirs_label.to_string(),
492 )
493 } else {
494 (
495 options.theirs_label.to_string(),
496 options.ours_label.to_string(),
497 )
498 };
499 info_messages.push(MergeInfoMessage::DirRenameApplied {
500 old_path: info.old_path.clone(),
501 new_path: dest.clone(),
502 renamed_from: info.renamed_from.clone(),
503 added_in,
504 dir_renamed_in,
505 });
506 }
507 }
508 }
509 let dir_rename_conflict_paths: BTreeMap<Vec<u8>, RehomeSides> =
512 if options.directory_renames == DirectoryRenames::Conflict {
513 rehomed_paths.clone()
514 } else {
515 BTreeMap::new()
516 };
517
518 let mut all_paths = BTreeSet::new();
519 all_paths.extend(eff_base.keys().cloned());
520 all_paths.extend(eff_ours.keys().cloned());
521 all_paths.extend(eff_theirs.keys().cloned());
522
523 let mut paths: Vec<MergedPath> = Vec::new();
524 let mut leaves: MergeEntryMap = BTreeMap::new();
525 let mut clean = true;
526
527 for path in all_paths {
528 let base = eff_base.get(&path).cloned();
529 let ours = eff_ours.get(&path).cloned();
530 let theirs = eff_theirs.get(&path).cloned();
531 let rename = renames.dest_to_source.get(&path);
532 let old_path = rename.map(|r| r.source.clone());
533 let favor = merge_favor_for_path(options, &path);
534
535 if ours == theirs {
537 if let Some(entry) = ours {
538 leaves.insert(path.clone(), entry);
539 }
540 paths.push(clean_path(path, ours));
541 continue;
542 }
543 if ours == base {
544 if let Some(entry) = &theirs {
545 leaves.insert(path.clone(), *entry);
546 }
547 paths.push(clean_path(path, theirs));
548 continue;
549 }
550 if theirs == base {
551 if let Some(entry) = &ours {
552 leaves.insert(path.clone(), *entry);
553 }
554 paths.push(clean_path(path, ours));
555 continue;
556 }
557
558 let content_mergeable = matches!(&ours, Some((mode, _)) if is_mergeable_file_mode(*mode))
560 && matches!(&theirs, Some((mode, _)) if is_mergeable_file_mode(*mode))
561 && match &base {
562 Some((mode, _)) => is_mergeable_file_mode(*mode),
563 None => true,
564 };
565
566 if let (true, Some((ours_mode, ours_oid)), Some((theirs_mode, theirs_oid))) =
567 (content_mergeable, &ours, &theirs)
568 {
569 let add_add = base.is_none();
570 let base_bytes = match &base {
571 Some((_, oid)) => merge_blob_bytes(db, oid)?,
572 None => Vec::new(),
573 };
574 let ours_bytes = merge_blob_bytes(db, ours_oid)?;
575 let theirs_bytes = merge_blob_bytes(db, theirs_oid)?;
576 let rehome = rehomed_paths.get(&path);
581 let (base_label_owned, ours_label, theirs_label) = match rename {
586 Some(MergeRename { source, side }) => {
587 let (ours_path, theirs_path) = match side {
588 RenameSide::Theirs => (source.as_slice(), path.as_slice()),
590 RenameSide::Ours => (path.as_slice(), source.as_slice()),
592 };
593 (
594 qualify_label(options.ancestor_label, source.as_slice()),
595 qualify_label(options.ours_label, ours_path),
596 qualify_label(options.theirs_label, theirs_path),
597 )
598 }
599 None => {
600 let ours_path = rehome
601 .and_then(|info| info.ours.as_ref())
602 .map_or(path.as_slice(), |info| info.old_path.as_slice());
603 let theirs_path = rehome
604 .and_then(|info| info.theirs.as_ref())
605 .map_or(path.as_slice(), |info| info.old_path.as_slice());
606 if ours_path != path.as_slice() || theirs_path != path.as_slice() {
607 (
608 qualify_label(options.ancestor_label, path.as_slice()),
609 qualify_label(options.ours_label, ours_path),
610 qualify_label(options.theirs_label, theirs_path),
611 )
612 } else {
613 (
614 options.ancestor_label.to_string(),
615 options.ours_label.to_string(),
616 options.theirs_label.to_string(),
617 )
618 }
619 }
620 };
621 let result = merge_blobs(
622 &base_bytes,
623 &ours_bytes,
624 &theirs_bytes,
625 &MergeBlobOptions {
626 ours_label: &ours_label,
627 theirs_label: &theirs_label,
628 base_label: &base_label_owned,
629 style: options.style,
630 favor,
631 ws_ignore: options.ws_ignore,
632 marker_size: merge_marker_size_for_path(options, &path),
633 },
634 );
635
636 let base_mode = base.as_ref().map(|(mode, _)| *mode);
637 let (resolved_mode, mode_conflict) =
638 merge_file_modes(base_mode, *ours_mode, *theirs_mode);
639
640 if !result.conflicted && !mode_conflict {
641 let oid = db.write_object(EncodedObject::new(ObjectType::Blob, result.content))?;
642 leaves.insert(path.clone(), (resolved_mode, oid));
643 paths.push(clean_path_auto(path, Some((resolved_mode, oid)), true));
644 } else if favor != MergeFavor::None && !mode_conflict {
645 let chosen = if favor == MergeFavor::Ours {
646 ours
647 } else {
648 theirs
649 };
650 if let Some(entry) = chosen {
651 leaves.insert(path.clone(), entry);
652 }
653 paths.push(clean_path_auto(path, chosen, true));
654 } else {
655 clean = false;
656 let oid =
657 db.write_object(EncodedObject::new(ObjectType::Blob, result.content.clone()))?;
658 leaves.insert(path.clone(), (resolved_mode, oid));
659 let worktree_mode = if *ours_mode == *theirs_mode {
660 *ours_mode
661 } else {
662 0o100644
663 };
664 let conflict = if let Some(old) = &old_path {
665 MergeConflictKind::RenameContent {
666 old_path: old.clone(),
667 }
668 } else if add_add {
669 match rehome.and_then(|info| Some((info.ours.as_ref()?, info.theirs.as_ref()?)))
670 {
671 Some((ours_info, theirs_info)) => MergeConflictKind::RenameRenameTwoToOne {
672 ours_path: ours_info.old_path.clone(),
673 theirs_path: theirs_info.old_path.clone(),
674 },
675 None => MergeConflictKind::Content { add_add },
676 }
677 } else {
678 MergeConflictKind::Content { add_add }
679 };
680 paths.push(MergedPath {
681 path: path.clone(),
682 stages: stages_for(&base, &ours, &theirs),
683 result: Some((resolved_mode, oid)),
684 worktree: Some((worktree_mode, result.content)),
685 conflict: Some(conflict),
686 auto_merged: true,
687 });
688 }
689 } else if base.is_some() && (ours.is_none() || theirs.is_none()) {
690 clean = false;
692 let (deleted_in, modified_in, surviving) = if ours.is_none() {
693 (
694 options.ours_label.to_string(),
695 options.theirs_label.to_string(),
696 theirs,
697 )
698 } else {
699 (
700 options.theirs_label.to_string(),
701 options.ours_label.to_string(),
702 ours,
703 )
704 };
705 let worktree = match &surviving {
706 Some((mode, oid)) => Some((*mode, merge_worktree_bytes(db, *mode, oid)?)),
707 None => None,
708 };
709 if let Some(entry) = surviving {
710 leaves.insert(path.clone(), entry);
711 }
712 paths.push(MergedPath {
713 path: path.clone(),
714 stages: stages_for(&base, &ours, &theirs),
715 result: surviving,
716 worktree,
717 conflict: Some(MergeConflictKind::ModifyDelete {
718 deleted_in,
719 modified_in,
720 }),
721 auto_merged: false,
722 });
723 } else if let (Some(&(ours_mode, ours_oid)), Some(&(theirs_mode, theirs_oid))) =
724 (ours.as_ref(), theirs.as_ref())
725 && sley_index::is_symlink_mode(ours_mode)
726 && sley_index::is_symlink_mode(theirs_mode)
727 {
728 match favor {
735 MergeFavor::Ours => {
736 leaves.insert(path.clone(), (ours_mode, ours_oid));
737 paths.push(clean_path_auto(
738 path.clone(),
739 Some((ours_mode, ours_oid)),
740 false,
741 ));
742 }
743 MergeFavor::Theirs => {
744 leaves.insert(path.clone(), (theirs_mode, theirs_oid));
745 paths.push(clean_path_auto(
746 path.clone(),
747 Some((theirs_mode, theirs_oid)),
748 false,
749 ));
750 }
751 MergeFavor::None | MergeFavor::Union => {
752 clean = false;
753 leaves.insert(path.clone(), (ours_mode, ours_oid));
754 let worktree =
755 Some((ours_mode, merge_worktree_bytes(db, ours_mode, &ours_oid)?));
756 paths.push(MergedPath {
757 path: path.clone(),
758 stages: stages_for(&base, &ours, &theirs),
759 result: Some((ours_mode, ours_oid)),
760 worktree,
761 conflict: Some(MergeConflictKind::Content {
762 add_add: base.is_none(),
763 }),
764 auto_merged: false,
765 });
766 }
767 }
768 } else if let (Some((ours_mode, ours_oid)), Some((theirs_mode, theirs_oid))) =
769 (ours, theirs)
770 && is_type_change(ours_mode, theirs_mode)
771 {
772 clean = false;
779 let (rename_ours, rename_theirs) = if is_mergeable_file_mode(ours_mode) {
782 (true, false)
783 } else if is_mergeable_file_mode(theirs_mode) {
784 (false, true)
785 } else {
786 (true, true)
787 };
788 let ours_base = base.filter(|(mode, _)| !is_type_change(*mode, ours_mode));
791 let theirs_base = base.filter(|(mode, _)| !is_type_change(*mode, theirs_mode));
792 let ours_path = if rename_ours {
795 unique_df_path(&path, options.ours_label, &leaves, &paths)
796 } else {
797 path.clone()
798 };
799 leaves.insert(ours_path.clone(), (ours_mode, ours_oid));
800 let theirs_path = if rename_theirs {
801 unique_df_path(&path, options.theirs_label, &leaves, &paths)
802 } else {
803 path.clone()
804 };
805 leaves.insert(theirs_path.clone(), (theirs_mode, theirs_oid));
806
807 let ours_carries_message = !rename_ours || rename_theirs;
810 let distinct = MergeConflictKind::DistinctTypes {
811 original_path: path.clone(),
812 ours_renamed: rename_ours.then(|| ours_path.clone()),
813 theirs_renamed: rename_theirs.then(|| theirs_path.clone()),
814 };
815 let ours_worktree = Some((ours_mode, merge_worktree_bytes(db, ours_mode, &ours_oid)?));
816 paths.push(MergedPath {
817 path: ours_path,
818 stages: MergeStages {
819 base: ours_base,
820 ours: Some((ours_mode, ours_oid)),
821 theirs: None,
822 },
823 result: Some((ours_mode, ours_oid)),
824 worktree: ours_worktree,
825 conflict: Some(if ours_carries_message {
826 distinct.clone()
827 } else {
828 MergeConflictKind::DistinctTypesStage
829 }),
830 auto_merged: false,
831 });
832 let theirs_worktree = Some((
833 theirs_mode,
834 merge_worktree_bytes(db, theirs_mode, &theirs_oid)?,
835 ));
836 paths.push(MergedPath {
837 path: theirs_path,
838 stages: MergeStages {
839 base: theirs_base,
840 ours: None,
841 theirs: Some((theirs_mode, theirs_oid)),
842 },
843 result: Some((theirs_mode, theirs_oid)),
844 worktree: theirs_worktree,
845 conflict: Some(if ours_carries_message {
846 MergeConflictKind::DistinctTypesStage
847 } else {
848 distinct
849 }),
850 auto_merged: false,
851 });
852 } else {
853 clean = false;
856 let add_add = base.is_none();
857 let surviving = ours.or(theirs);
858 let worktree = match &surviving {
859 Some((mode, oid)) => Some((*mode, merge_worktree_bytes(db, *mode, oid)?)),
860 None => None,
861 };
862 if let Some(entry) = surviving {
863 leaves.insert(path.clone(), entry);
864 }
865 paths.push(MergedPath {
866 path: path.clone(),
867 stages: stages_for(&base, &ours, &theirs),
868 result: surviving,
869 worktree,
870 conflict: Some(MergeConflictKind::Content { add_add }),
871 auto_merged: false,
872 });
873 }
874 }
875
876 if !renames.rename_rename_one_to_two.is_empty() {
877 apply_rename_rename_one_to_two_conflicts(
878 db,
879 base_map,
880 &eff_ours,
881 &eff_theirs,
882 &renames.rename_rename_one_to_two,
883 &mut paths,
884 &mut leaves,
885 options,
886 )?;
887 clean = false;
888 }
889
890 if !dir_rename_two_to_one.is_empty() {
891 apply_dir_rename_two_to_one_conflicts(
892 db,
893 &eff_ours,
894 &eff_theirs,
895 &dir_rename_two_to_one,
896 &mut paths,
897 &mut leaves,
898 options,
899 )?;
900 clean = false;
901 }
902
903 if !renames.rename_rename_two_to_one.is_empty() || !renames.rename_adds.is_empty() {
908 apply_rename_two_to_one_and_add_conflicts(
909 db,
910 base_map,
911 ours_map,
912 theirs_map,
913 &renames,
914 &mut paths,
915 &mut leaves,
916 options,
917 )?;
918 clean = false;
919 }
920
921 if !renames.rename_deletes.is_empty() {
926 for (dest, rd) in &renames.rename_deletes {
927 let Some(slot) = paths.iter_mut().find(|p| &p.path == dest) else {
929 continue;
930 };
931 if slot.conflict.is_some() {
932 continue;
933 }
934 let base_entry = base_map.get(&rd.source).copied();
935 let renamed_entry = slot.result;
936 let (ours_stage, theirs_stage) = match rd.side {
939 RenameSide::Ours => (renamed_entry, None),
940 RenameSide::Theirs => (None, renamed_entry),
941 };
942 let (renamed_in, deleted_in) = match rd.side {
943 RenameSide::Ours => (
944 options.ours_label.to_string(),
945 options.theirs_label.to_string(),
946 ),
947 RenameSide::Theirs => (
948 options.theirs_label.to_string(),
949 options.ours_label.to_string(),
950 ),
951 };
952 let worktree = match &renamed_entry {
953 Some((mode, oid)) => Some((*mode, merge_worktree_bytes(db, *mode, oid)?)),
954 None => None,
955 };
956 slot.stages = MergeStages {
957 base: base_entry,
958 ours: ours_stage,
959 theirs: theirs_stage,
960 };
961 slot.worktree = worktree;
962 slot.conflict = Some(MergeConflictKind::RenameDelete {
963 old_path: rd.source.clone(),
964 renamed_in,
965 deleted_in,
966 });
967 clean = false;
968 }
969 }
970
971 if dir_rename_dirty {
978 clean = false;
979 }
980 for collision in &dir_rename_collisions {
986 clean = false;
987 if let Some(slot) = paths.iter_mut().find(|p| p.path == collision.dest)
988 && slot.conflict.is_none()
989 {
990 slot.conflict = Some(MergeConflictKind::DirRenameImplicitCollision {
991 sources: collision.sources.clone(),
992 });
993 } else if !paths.iter().any(|p| p.path == collision.dest) {
994 paths.push(MergedPath {
995 path: collision.dest.clone(),
996 stages: MergeStages::default(),
997 result: None,
998 worktree: None,
999 conflict: Some(MergeConflictKind::DirRenameImplicitCollision {
1000 sources: collision.sources.clone(),
1001 }),
1002 auto_merged: false,
1003 });
1004 }
1005 }
1006 for source_dir in &dir_rename_splits {
1007 clean = false;
1008 paths.push(MergedPath {
1009 path: source_dir.clone(),
1010 stages: MergeStages::default(),
1011 result: None,
1012 worktree: None,
1013 conflict: Some(MergeConflictKind::DirRenameSplit {
1014 source_dir: source_dir.clone(),
1015 }),
1016 auto_merged: false,
1017 });
1018 }
1019 if !dir_rename_conflict_paths.is_empty() {
1020 clean = false;
1021 for (dest, infos) in &dir_rename_conflict_paths {
1022 for info in [&infos.ours, &infos.theirs].into_iter().flatten() {
1023 let (added_in, dir_renamed_in) = if info.added_on_ours {
1024 (
1026 options.ours_label.to_string(),
1027 options.theirs_label.to_string(),
1028 )
1029 } else {
1030 (
1031 options.theirs_label.to_string(),
1032 options.ours_label.to_string(),
1033 )
1034 };
1035 let back_to_self = dir_rename_back_to_self.contains(dest);
1042 if let Some(slot) = paths.iter_mut().find(|p| &p.path == dest)
1043 && slot.conflict.is_none()
1044 {
1045 if back_to_self {
1046 slot.stages = MergeStages {
1047 base: eff_base.get(dest).copied(),
1048 ours: eff_ours.get(dest).copied(),
1049 theirs: eff_theirs.get(dest).copied(),
1050 };
1051 slot.worktree = match &slot.result {
1052 Some((mode, oid)) => {
1053 Some((*mode, merge_worktree_bytes(db, *mode, oid)?))
1054 }
1055 None => slot.worktree.clone(),
1056 };
1057 }
1058 slot.conflict = Some(MergeConflictKind::DirRenameLocation {
1059 old_path: info.old_path.clone(),
1060 renamed_from: info.renamed_from.clone(),
1061 added_in,
1062 dir_renamed_in,
1063 back_to_self,
1064 });
1065 } else {
1066 info_messages.push(MergeInfoMessage::DirRenameLocationConflict {
1067 old_path: info.old_path.clone(),
1068 new_path: dest.clone(),
1069 renamed_from: info.renamed_from.clone(),
1070 added_in,
1071 dir_renamed_in,
1072 });
1073 }
1074 }
1075 }
1076 }
1077
1078 resolve_directory_file_conflicts(
1086 db,
1087 &mut paths,
1088 &mut leaves,
1089 &mut clean,
1090 &eff_ours,
1091 &eff_theirs,
1092 options,
1093 &mut info_messages,
1094 )?;
1095
1096 let tree = write_merged_tree(db, &leaves)?;
1097
1098 cleanup_paths.retain(|path| !leaves.contains_key(path));
1099
1100 Ok(MergeTreesResult {
1101 tree,
1102 paths,
1103 clean,
1104 cleanup_paths: cleanup_paths.into_iter().collect(),
1105 info_messages,
1106 })
1107}
1108
1109fn flatten_branch_label(branch: &str) -> String {
1113 branch.replace('/', "_")
1114}
1115
1116fn unique_df_path(
1120 path: &[u8],
1121 branch: &str,
1122 leaves: &MergeEntryMap,
1123 paths: &[MergedPath],
1124) -> Vec<u8> {
1125 let mut base = path.to_vec();
1126 base.push(b'~');
1127 base.extend_from_slice(flatten_branch_label(branch).as_bytes());
1128 let taken = |candidate: &[u8]| {
1129 leaves.contains_key(candidate) || paths.iter().any(|p| p.path == candidate)
1130 };
1131 if !taken(&base) {
1132 return base;
1133 }
1134 let mut suffix = 0usize;
1135 loop {
1136 let mut candidate = base.clone();
1137 candidate.push(b'_');
1138 candidate.extend_from_slice(suffix.to_string().as_bytes());
1139 if !taken(&candidate) {
1140 return candidate;
1141 }
1142 suffix += 1;
1143 }
1144}
1145
1146#[allow(clippy::too_many_arguments)]
1150fn resolve_directory_file_conflicts(
1151 db: &FileObjectDatabase,
1152 paths: &mut Vec<MergedPath>,
1153 leaves: &mut MergeEntryMap,
1154 clean: &mut bool,
1155 eff_ours: &MergeEntryMap,
1156 eff_theirs: &MergeEntryMap,
1157 options: &MergeTreesOptions<'_>,
1158 info_messages: &mut Vec<MergeInfoMessage>,
1159) -> Result<()> {
1160 let mut directory_prefixes: BTreeSet<Vec<u8>> = BTreeSet::new();
1163 for key in leaves.keys() {
1164 let mut idx = 0;
1165 while let Some(pos) = key[idx..].iter().position(|b| *b == b'/') {
1166 let end = idx + pos;
1167 directory_prefixes.insert(key[..end].to_vec());
1168 idx = end + 1;
1169 }
1170 }
1171 if directory_prefixes.is_empty() {
1172 return Ok(());
1173 }
1174
1175 let colliding: Vec<Vec<u8>> = leaves
1177 .keys()
1178 .filter(|key| directory_prefixes.contains(*key))
1179 .cloned()
1180 .collect();
1181
1182 for original in colliding {
1183 let Some(entry) = leaves.remove(&original) else {
1184 continue;
1185 };
1186 let moved_bytes = merge_worktree_bytes(db, entry.0, &entry.1)?;
1189 let ours_has_file = eff_ours.contains_key(&original);
1195 let theirs_has_file = eff_theirs.contains_key(&original);
1196 let from_ours = ours_has_file || !theirs_has_file;
1197 let branch = if from_ours {
1198 options.ours_label
1199 } else {
1200 options.theirs_label
1201 };
1202 let new_path = unique_df_path(&original, branch, leaves, paths);
1203 leaves.insert(new_path.clone(), entry);
1204 *clean = false;
1205
1206 if let Some(slot) = paths.iter_mut().find(|p| p.path == original) {
1209 if let Some(MergeConflictKind::RenameDelete {
1210 old_path,
1211 renamed_in,
1212 deleted_in,
1213 }) = &slot.conflict
1214 {
1215 info_messages.push(MergeInfoMessage::RenameDeleteConflict {
1216 old_path: old_path.clone(),
1217 new_path: original.clone(),
1218 renamed_in: renamed_in.clone(),
1219 deleted_in: deleted_in.clone(),
1220 });
1221 }
1222 slot.path = new_path.clone();
1223 slot.result = Some(entry);
1224 if slot.stages.base.is_none()
1227 && slot.stages.ours.is_none()
1228 && slot.stages.theirs.is_none()
1229 {
1230 slot.stages = MergeStages {
1231 base: None,
1232 ours: if from_ours { Some(entry) } else { None },
1233 theirs: if from_ours { None } else { Some(entry) },
1234 };
1235 }
1236 slot.worktree = Some((entry.0, moved_bytes));
1242 slot.conflict = Some(MergeConflictKind::FileDirectory {
1243 original_path: original.clone(),
1244 moved_from: branch.to_string(),
1245 });
1246 } else {
1247 paths.push(MergedPath {
1248 path: new_path.clone(),
1249 stages: MergeStages {
1250 base: None,
1251 ours: if from_ours { Some(entry) } else { None },
1252 theirs: if from_ours { None } else { Some(entry) },
1253 },
1254 result: Some(entry),
1255 worktree: Some((entry.0, moved_bytes)),
1256 conflict: Some(MergeConflictKind::FileDirectory {
1257 original_path: original.clone(),
1258 moved_from: branch.to_string(),
1259 }),
1260 auto_merged: false,
1261 });
1262 }
1263 }
1264
1265 paths.sort_by(|a, b| a.path.cmp(&b.path));
1267 Ok(())
1268}
1269
1270fn clean_path(path: Vec<u8>, result: Option<(u32, ObjectId)>) -> MergedPath {
1272 clean_path_auto(path, result, false)
1273}
1274
1275fn clean_path_auto(
1278 path: Vec<u8>,
1279 result: Option<(u32, ObjectId)>,
1280 auto_merged: bool,
1281) -> MergedPath {
1282 MergedPath {
1283 path,
1284 stages: MergeStages::default(),
1285 result,
1286 worktree: None,
1287 conflict: None,
1288 auto_merged,
1289 }
1290}
1291
1292fn stages_for(
1294 base: &Option<(u32, ObjectId)>,
1295 ours: &Option<(u32, ObjectId)>,
1296 theirs: &Option<(u32, ObjectId)>,
1297) -> MergeStages {
1298 MergeStages {
1299 base: *base,
1300 ours: *ours,
1301 theirs: *theirs,
1302 }
1303}
1304
1305fn merge_blob_bytes(reader: &impl ObjectReader, oid: &ObjectId) -> Result<Vec<u8>> {
1307 let object = reader.read_object(oid)?;
1308 if object.object_type != ObjectType::Blob {
1309 return Err(GitError::InvalidObject(format!(
1310 "expected blob {}, found {}",
1311 oid,
1312 object.object_type.as_str()
1313 )));
1314 }
1315 Ok(object.body.clone())
1316}
1317
1318fn merge_worktree_bytes(reader: &impl ObjectReader, mode: u32, oid: &ObjectId) -> Result<Vec<u8>> {
1319 if sley_index::is_gitlink(mode) {
1320 Ok(Vec::new())
1321 } else {
1322 merge_blob_bytes(reader, oid)
1323 }
1324}
1325
1326fn merge_file_modes(base: Option<u32>, ours: u32, theirs: u32) -> (u32, bool) {
1329 if ours == theirs {
1330 return (ours, false);
1331 }
1332 match base {
1333 Some(base) if ours == base => (theirs, false),
1334 Some(base) if theirs == base => (ours, false),
1335 _ => (ours, true),
1336 }
1337}
1338
1339fn write_merged_tree(db: &FileObjectDatabase, leaves: &MergeEntryMap) -> Result<ObjectId> {
1342 let mut root = MergeTreeNode::default();
1343 for (path, (mode, oid)) in leaves {
1344 root.insert(path, *mode, *oid);
1345 }
1346 root.write(db)
1347}
1348
1349#[derive(Default)]
1350struct MergeTreeNode {
1351 blobs: BTreeMap<Vec<u8>, (u32, ObjectId)>,
1352 subtrees: BTreeMap<Vec<u8>, MergeTreeNode>,
1353}
1354
1355impl MergeTreeNode {
1356 fn insert(&mut self, path: &[u8], mode: u32, oid: ObjectId) {
1357 match path.iter().position(|byte| *byte == b'/') {
1358 Some(slash) => {
1359 let component = path[..slash].to_vec();
1360 let rest = &path[slash + 1..];
1361 self.subtrees
1362 .entry(component)
1363 .or_default()
1364 .insert(rest, mode, oid);
1365 }
1366 None => {
1367 self.blobs.insert(path.to_vec(), (mode, oid));
1368 }
1369 }
1370 }
1371
1372 fn write(&self, db: &FileObjectDatabase) -> Result<ObjectId> {
1373 let mut entries: Vec<TreeEntry> = Vec::new();
1374 for (name, (mode, oid)) in &self.blobs {
1375 entries.push(TreeEntry {
1376 mode: *mode,
1377 name: BString::from(name.clone()),
1378 oid: *oid,
1379 });
1380 }
1381 for (name, subtree) in &self.subtrees {
1382 let oid = subtree.write(db)?;
1383 entries.push(TreeEntry {
1384 mode: 0o040000,
1385 name: BString::from(name.clone()),
1386 oid,
1387 });
1388 }
1389 entries.sort_by_key(merge_tree_sort_key);
1390 let tree = Tree { entries };
1391 db.write_object(EncodedObject::new(ObjectType::Tree, tree.write()))
1392 }
1393}
1394
1395fn merge_tree_sort_key(entry: &TreeEntry) -> Vec<u8> {
1396 let mut key = entry.name.as_bytes().to_vec();
1397 if entry.mode == 0o040000 {
1398 key.push(b'/');
1399 }
1400 key
1401}
1402
1403#[derive(Clone, Copy, PartialEq, Eq)]
1407enum RenameSide {
1408 Ours,
1409 Theirs,
1410}
1411
1412#[derive(Clone)]
1414struct MergeRename {
1415 source: Vec<u8>,
1416 side: RenameSide,
1417}
1418
1419#[derive(Clone)]
1423struct RenameDelete {
1424 source: Vec<u8>,
1426 side: RenameSide,
1428}
1429
1430#[derive(Default)]
1434struct MergeRenames {
1435 dest_to_source: BTreeMap<Vec<u8>, MergeRename>,
1439 rename_deletes: BTreeMap<Vec<u8>, RenameDelete>,
1442 rename_rename_one_to_two: BTreeMap<Vec<u8>, RenameRenameOneToTwo>,
1444 rename_rename_two_to_one: BTreeMap<Vec<u8>, RenameRenameTwoToOne>,
1447 rename_adds: BTreeMap<Vec<u8>, RenameAdd>,
1450}
1451
1452#[derive(Clone)]
1453struct RenameRenameOneToTwo {
1454 ours_dest: Vec<u8>,
1455 theirs_dest: Vec<u8>,
1456}
1457
1458#[derive(Clone)]
1462struct RenameRenameTwoToOne {
1463 ours_source: Vec<u8>,
1465 theirs_source: Vec<u8>,
1467}
1468
1469#[derive(Clone)]
1473struct RenameAdd {
1474 source: Vec<u8>,
1476 side: RenameSide,
1478}
1479
1480struct SideRenames {
1485 pairs: Vec<(Vec<u8>, Vec<u8>)>,
1486}
1487
1488fn detect_merge_renames(
1498 db: &FileObjectDatabase,
1499 format: ObjectFormat,
1500 base_map: &MergeEntryMap,
1501 ours_map: &MergeEntryMap,
1502 theirs_map: &MergeEntryMap,
1503 options: &MergeTreesOptions<'_>,
1504) -> Result<(MergeRenames, SideRenames, SideRenames)> {
1505 let mut renames = MergeRenames::default();
1506
1507 let ours_side = collect_side_renames(
1509 db,
1510 format,
1511 base_map,
1512 ours_map,
1513 theirs_map,
1514 RenameSide::Ours,
1515 options.rename_threshold,
1516 options.rename_limit,
1517 &mut renames,
1518 )?;
1519 let theirs_side = collect_side_renames(
1521 db,
1522 format,
1523 base_map,
1524 theirs_map,
1525 ours_map,
1526 RenameSide::Theirs,
1527 options.rename_threshold,
1528 options.rename_limit,
1529 &mut renames,
1530 )?;
1531
1532 collect_rename_rename_one_to_two(&mut renames, &ours_side, &theirs_side);
1533 collect_rename_rename_two_to_one_and_adds(
1534 &mut renames,
1535 &ours_side,
1536 &theirs_side,
1537 base_map,
1538 ours_map,
1539 theirs_map,
1540 );
1541
1542 Ok((renames, ours_side, theirs_side))
1543}
1544
1545fn collect_rename_rename_two_to_one_and_adds(
1554 renames: &mut MergeRenames,
1555 ours_side: &SideRenames,
1556 theirs_side: &SideRenames,
1557 base_map: &MergeEntryMap,
1558 ours_map: &MergeEntryMap,
1559 theirs_map: &MergeEntryMap,
1560) {
1561 let ours_by_dest: BTreeMap<&[u8], &[u8]> = ours_side
1562 .pairs
1563 .iter()
1564 .map(|(old, new)| (new.as_slice(), old.as_slice()))
1565 .collect();
1566 let theirs_by_dest: BTreeMap<&[u8], &[u8]> = theirs_side
1567 .pairs
1568 .iter()
1569 .map(|(old, new)| (new.as_slice(), old.as_slice()))
1570 .collect();
1571
1572 for (dest, ours_src) in &ours_by_dest {
1576 let Some(theirs_src) = theirs_by_dest.get(dest) else {
1577 continue;
1578 };
1579 if ours_src == theirs_src {
1580 continue;
1581 }
1582 if renames.rename_rename_one_to_two.contains_key(*dest) {
1584 continue;
1585 }
1586 renames.rename_rename_two_to_one.insert(
1587 dest.to_vec(),
1588 RenameRenameTwoToOne {
1589 ours_source: ours_src.to_vec(),
1590 theirs_source: theirs_src.to_vec(),
1591 },
1592 );
1593 }
1594
1595 for (dest, ours_src) in &ours_by_dest {
1598 if renames.rename_rename_two_to_one.contains_key(*dest)
1599 || renames.rename_rename_one_to_two.contains_key(*dest)
1600 {
1601 continue;
1602 }
1603 if theirs_map.contains_key(*dest)
1604 && !base_map.contains_key(*dest)
1605 && !theirs_by_dest.contains_key(dest)
1606 {
1607 renames.rename_adds.insert(
1608 dest.to_vec(),
1609 RenameAdd {
1610 source: ours_src.to_vec(),
1611 side: RenameSide::Ours,
1612 },
1613 );
1614 }
1615 }
1616 for (dest, theirs_src) in &theirs_by_dest {
1618 if renames.rename_rename_two_to_one.contains_key(*dest)
1619 || renames.rename_rename_one_to_two.contains_key(*dest)
1620 || renames.rename_adds.contains_key(*dest)
1621 {
1622 continue;
1623 }
1624 if ours_map.contains_key(*dest)
1625 && !base_map.contains_key(*dest)
1626 && !ours_by_dest.contains_key(dest)
1627 {
1628 renames.rename_adds.insert(
1629 dest.to_vec(),
1630 RenameAdd {
1631 source: theirs_src.to_vec(),
1632 side: RenameSide::Theirs,
1633 },
1634 );
1635 }
1636 }
1637}
1638
1639fn collect_rename_rename_one_to_two(
1640 renames: &mut MergeRenames,
1641 ours_side: &SideRenames,
1642 theirs_side: &SideRenames,
1643) {
1644 let ours_by_source: BTreeMap<&[u8], &[u8]> = ours_side
1645 .pairs
1646 .iter()
1647 .map(|(old, new)| (old.as_slice(), new.as_slice()))
1648 .collect();
1649 for (old, theirs_new) in &theirs_side.pairs {
1650 let Some(ours_new) = ours_by_source.get(old.as_slice()) else {
1651 continue;
1652 };
1653 if *ours_new == theirs_new.as_slice() {
1654 continue;
1655 }
1656 renames.rename_deletes.remove(*ours_new);
1657 renames.rename_deletes.remove(theirs_new);
1658 renames.dest_to_source.remove(*ours_new);
1659 renames.dest_to_source.remove(theirs_new);
1660 renames.rename_rename_one_to_two.insert(
1661 old.clone(),
1662 RenameRenameOneToTwo {
1663 ours_dest: (*ours_new).to_vec(),
1664 theirs_dest: theirs_new.clone(),
1665 },
1666 );
1667 }
1668}
1669
1670#[allow(clippy::too_many_arguments)]
1675fn collect_side_renames(
1676 db: &FileObjectDatabase,
1677 format: ObjectFormat,
1678 base_map: &MergeEntryMap,
1679 side_map: &MergeEntryMap,
1680 other_map: &MergeEntryMap,
1681 side: RenameSide,
1682 threshold: u8,
1683 rename_limit: usize,
1684 renames: &mut MergeRenames,
1685) -> Result<SideRenames> {
1686 let base_tree = entry_map_as_tracked(base_map);
1689 let side_tree = entry_map_as_tracked(side_map);
1690 let options = DiffNameStatusOptions {
1691 detect_renames: true,
1692 detect_copies: false,
1693 find_copies_harder: false,
1694 rename_empty: false,
1695 detect_inexact: true,
1696 rename_threshold: threshold,
1697 copy_threshold: threshold,
1698 rename_limit,
1699 ..Default::default()
1700 };
1701 let changes = diff_name_status_maps_with_renames(
1702 &base_tree,
1703 &side_tree,
1704 base_tree.keys().chain(side_tree.keys()),
1705 options,
1706 |oid| merge_blob_bytes(db, oid).ok().map(|b| Arc::from(b.into_boxed_slice())),
1707 )?;
1708
1709 let mut pairs = Vec::new();
1710 for change in changes {
1711 let NameStatus::Renamed(_) = change.status else {
1712 continue;
1713 };
1714 let Some(old_path) = change.old_path.as_ref() else {
1715 continue;
1716 };
1717 let old = old_path.as_bytes().to_vec();
1718 let new = change.path.as_bytes().to_vec();
1719 pairs.push((old.clone(), new.clone()));
1721
1722 if !other_map.contains_key(&old) {
1727 if base_map.contains_key(&old) && !other_map.contains_key(&new) {
1732 renames
1733 .rename_deletes
1734 .entry(new.clone())
1735 .or_insert(RenameDelete {
1736 source: old.clone(),
1737 side,
1738 });
1739 }
1740 continue;
1741 }
1742 if other_map.contains_key(&new) {
1745 continue;
1746 }
1747 renames
1751 .dest_to_source
1752 .entry(new)
1753 .or_insert(MergeRename { source: old, side });
1754 }
1755
1756 let _ = format;
1757 Ok(SideRenames { pairs })
1758}
1759
1760fn apply_merge_renames(
1766 base_map: &MergeEntryMap,
1767 ours_map: &MergeEntryMap,
1768 theirs_map: &MergeEntryMap,
1769 renames: &MergeRenames,
1770) -> (MergeEntryMap, MergeEntryMap, MergeEntryMap) {
1771 if renames.dest_to_source.is_empty() {
1772 return (base_map.clone(), ours_map.clone(), theirs_map.clone());
1773 }
1774 let mut base = base_map.clone();
1775 let mut ours = ours_map.clone();
1776 let mut theirs = theirs_map.clone();
1777
1778 for (new, rename) in &renames.dest_to_source {
1779 let old = &rename.source;
1780 if let Some(entry) = base.remove(old) {
1782 base.entry(new.clone()).or_insert(entry);
1783 }
1784 for side in [&mut ours, &mut theirs] {
1786 if let Some(entry) = side.remove(old) {
1787 side.entry(new.clone()).or_insert(entry);
1788 }
1789 }
1790 }
1791 (base, ours, theirs)
1792}
1793
1794fn parent_dir(path: &[u8]) -> Option<&[u8]> {
1798 path.iter().rposition(|b| *b == b'/').map(|i| &path[..i])
1799}
1800
1801fn apply_dir_rename(old_dir: &[u8], new_dir: &[u8], path: &[u8]) -> Vec<u8> {
1805 let rest_start = if new_dir.is_empty() {
1808 old_dir.len() + 1
1809 } else {
1810 old_dir.len()
1811 };
1812 let mut out = new_dir.to_vec();
1813 out.extend_from_slice(&path[rest_start..]);
1814 out
1815}
1816
1817fn check_dir_renamed<'a>(
1821 path: &[u8],
1822 dir_renames: &'a BTreeMap<Vec<u8>, Vec<u8>>,
1823) -> Option<(&'a [u8], &'a [u8])> {
1824 let mut cur = parent_dir(path);
1825 while let Some(dir) = cur {
1826 if let Some((old_dir, new_dir)) = dir_renames.get_key_value(dir) {
1827 return Some((old_dir.as_slice(), new_dir.as_slice()));
1828 }
1829 cur = parent_dir(dir);
1830 }
1831 None
1832}
1833
1834struct DirectoryRenameMaps {
1837 ours: BTreeMap<Vec<u8>, Vec<u8>>,
1840 theirs: BTreeMap<Vec<u8>, Vec<u8>>,
1842 ours_split: BTreeSet<Vec<u8>>,
1846 theirs_split: BTreeSet<Vec<u8>>,
1848}
1849
1850fn compute_directory_renames(
1859 ours_map: &MergeEntryMap,
1860 theirs_map: &MergeEntryMap,
1861 ours_side: &SideRenames,
1862 theirs_side: &SideRenames,
1863) -> DirectoryRenameMaps {
1864 let ours = compute_side_dir_renames(&ours_side.pairs, ours_map);
1865 let theirs = compute_side_dir_renames(&theirs_side.pairs, theirs_map);
1866
1867 let mut ours_map_out = ours.renames;
1871 let mut theirs_map_out = theirs.renames;
1872 let dup: Vec<Vec<u8>> = ours_map_out
1873 .keys()
1874 .filter(|k| theirs_map_out.contains_key(*k))
1875 .cloned()
1876 .collect();
1877 for k in dup {
1878 ours_map_out.remove(&k);
1879 theirs_map_out.remove(&k);
1880 }
1881
1882 DirectoryRenameMaps {
1883 ours: ours_map_out,
1884 theirs: theirs_map_out,
1885 ours_split: ours.split,
1886 theirs_split: theirs.split,
1887 }
1888}
1889
1890struct SideDirRenames {
1892 renames: BTreeMap<Vec<u8>, Vec<u8>>,
1893 split: BTreeSet<Vec<u8>>,
1894}
1895
1896fn compute_side_dir_renames(
1899 pairs: &[(Vec<u8>, Vec<u8>)],
1900 side_map: &MergeEntryMap,
1901) -> SideDirRenames {
1902 let mut counts: BTreeMap<Vec<u8>, BTreeMap<Vec<u8>, usize>> = BTreeMap::new();
1910 for (old, new) in pairs {
1911 update_dir_rename_counts(&mut counts, old, new);
1912 }
1913
1914 let mut renames = BTreeMap::new();
1915 let mut split = BTreeSet::new();
1916 for (old_dir, targets) in counts {
1917 let mut max = 0usize;
1918 let mut bad_max = 0usize;
1919 let mut best: Option<Vec<u8>> = None;
1920 for (target, count) in &targets {
1921 if *count == max {
1922 bad_max = max;
1923 } else if *count > max {
1924 max = *count;
1925 best = Some(target.clone());
1926 }
1927 }
1928 if max == 0 {
1929 continue;
1930 }
1931 if bad_max == max {
1932 split.insert(old_dir);
1933 continue;
1934 }
1935 if let Some(best) = best
1940 && directory_fully_removed(&old_dir, side_map)
1941 {
1942 renames.insert(old_dir, best);
1943 }
1944 }
1945
1946 SideDirRenames { renames, split }
1947}
1948
1949fn update_dir_rename_counts(
1955 counts: &mut BTreeMap<Vec<u8>, BTreeMap<Vec<u8>, usize>>,
1956 old: &[u8],
1957 new: &[u8],
1958) {
1959 let mut old_dir = old.to_vec();
1961 let mut new_dir = new.to_vec();
1962 let mut first = true;
1963 loop {
1964 let old_has = dir_munge(&mut old_dir);
1967 let new_has = dir_munge(&mut new_dir);
1968
1969 if !first {
1973 let old_sub = trailing_component(old, &old_dir);
1974 let new_sub = trailing_component(new, &new_dir);
1975 if old_sub != new_sub {
1976 break;
1977 }
1978 }
1979
1980 if old_dir == new_dir {
1981 break;
1984 }
1985 *counts
1986 .entry(old_dir.clone())
1987 .or_default()
1988 .entry(new_dir.clone())
1989 .or_default() += 1;
1990
1991 first = false;
1992 if old_dir.is_empty() || new_dir.is_empty() {
1994 break;
1995 }
1996 if !old_has || !new_has {
1999 break;
2000 }
2001 }
2002}
2003
2004fn dir_munge(buf: &mut Vec<u8>) -> bool {
2008 match buf.iter().rposition(|b| *b == b'/') {
2009 Some(i) => {
2010 buf.truncate(i);
2011 true
2012 }
2013 None => {
2014 buf.clear();
2015 false
2016 }
2017 }
2018}
2019
2020fn trailing_component<'a>(full: &'a [u8], dir: &[u8]) -> &'a [u8] {
2024 if dir.is_empty() {
2025 full
2026 } else {
2027 &full[dir.len() + 1..]
2029 }
2030}
2031
2032fn directory_fully_removed(dir: &[u8], side_map: &MergeEntryMap) -> bool {
2035 let mut prefix = dir.to_vec();
2036 prefix.push(b'/');
2037 for path in side_map.keys() {
2038 if path.starts_with(&prefix) {
2039 return false;
2040 }
2041 }
2042 true
2043}
2044
2045struct DirRenameMove {
2049 from: Vec<u8>,
2052 to: Vec<u8>,
2054 renamed_from: Option<Vec<u8>>,
2058}
2059
2060struct DirRenameTwoToOne {
2061 dest: Vec<u8>,
2062 ours_source: Vec<u8>,
2063 theirs_source: Vec<u8>,
2064 ours_label_path: Vec<u8>,
2065 theirs_label_path: Vec<u8>,
2066}
2067
2068#[derive(Clone)]
2071struct RehomeInfo {
2072 old_path: Vec<u8>,
2074 renamed_from: Option<Vec<u8>>,
2076 added_on_ours: bool,
2079}
2080
2081#[derive(Clone, Default)]
2083struct RehomeSides {
2084 ours: Option<RehomeInfo>,
2085 theirs: Option<RehomeInfo>,
2086}
2087
2088struct DirRenameCollision {
2093 dest: Vec<u8>,
2095 sources: Vec<Vec<u8>>,
2097}
2098
2099struct DirRenameOutcome {
2101 base: MergeEntryMap,
2105 ours: MergeEntryMap,
2106 theirs: MergeEntryMap,
2107 rehomed: BTreeMap<Vec<u8>, RehomeSides>,
2109 collisions: Vec<DirRenameCollision>,
2113 splits: BTreeSet<Vec<u8>>,
2115 back_to_self: BTreeSet<Vec<u8>>,
2121 dirty: bool,
2124 info_messages: Vec<MergeInfoMessage>,
2125}
2126
2127#[allow(clippy::too_many_arguments)]
2142fn apply_directory_renames(
2143 base_map: &MergeEntryMap,
2144 eff_base: &MergeEntryMap,
2145 eff_ours: &MergeEntryMap,
2146 eff_theirs: &MergeEntryMap,
2147 ours_side: &SideRenames,
2148 theirs_side: &SideRenames,
2149 dir_renames: &DirectoryRenameMaps,
2150 file_rename_dests: &BTreeMap<Vec<u8>, MergeRename>,
2151) -> DirRenameOutcome {
2152 let mut base = eff_base.clone();
2153 let mut ours = eff_ours.clone();
2154 let mut theirs = eff_theirs.clone();
2155 let mut rehomed = BTreeMap::new();
2156 let mut collisions = Vec::new();
2157 let mut splits = BTreeSet::new();
2158 let mut back_to_self = BTreeSet::new();
2159 let mut info_messages = Vec::new();
2160 let mut dirty = false;
2161
2162 let ours_excl = exclusion_dirs(&dir_renames.ours);
2166 let theirs_excl = exclusion_dirs(&dir_renames.theirs);
2167
2168 let ours_moves = plan_rehome(
2172 base_map,
2173 &ours,
2174 ours_side,
2175 &dir_renames.theirs,
2176 &ours_excl,
2177 &dir_renames.theirs_split,
2178 &mut collisions,
2179 &mut splits,
2180 &mut info_messages,
2181 &mut dirty,
2182 );
2183 let theirs_moves = plan_rehome(
2184 base_map,
2185 &theirs,
2186 theirs_side,
2187 &dir_renames.ours,
2188 &theirs_excl,
2189 &dir_renames.ours_split,
2190 &mut collisions,
2191 &mut splits,
2192 &mut info_messages,
2193 &mut dirty,
2194 );
2195
2196 apply_rehome_moves(
2197 base_map,
2198 file_rename_dests,
2199 &mut base,
2200 &mut ours,
2201 &mut theirs,
2202 ours_moves,
2203 true,
2204 &mut rehomed,
2205 &mut collisions,
2206 &mut back_to_self,
2207 &mut dirty,
2208 );
2209 apply_rehome_moves(
2210 base_map,
2211 file_rename_dests,
2212 &mut base,
2213 &mut ours,
2214 &mut theirs,
2215 theirs_moves,
2216 false,
2217 &mut rehomed,
2218 &mut collisions,
2219 &mut back_to_self,
2220 &mut dirty,
2221 );
2222
2223 DirRenameOutcome {
2224 base,
2225 ours,
2226 theirs,
2227 rehomed,
2228 collisions,
2229 splits,
2230 back_to_self,
2231 dirty,
2232 info_messages,
2233 }
2234}
2235
2236fn exclusion_dirs(side_dir_renames: &BTreeMap<Vec<u8>, Vec<u8>>) -> BTreeSet<Vec<u8>> {
2240 side_dir_renames.keys().cloned().collect()
2241}
2242
2243#[allow(clippy::too_many_arguments)]
2258fn plan_rehome(
2259 base_map: &MergeEntryMap,
2260 side: &MergeEntryMap,
2261 side_renames: &SideRenames,
2262 renamer_dirs: &BTreeMap<Vec<u8>, Vec<u8>>,
2263 exclusions: &BTreeSet<Vec<u8>>,
2264 split_dirs: &BTreeSet<Vec<u8>>,
2265 collisions: &mut Vec<DirRenameCollision>,
2266 splits: &mut BTreeSet<Vec<u8>>,
2267 info_messages: &mut Vec<MergeInfoMessage>,
2268 dirty: &mut bool,
2269) -> Vec<DirRenameMove> {
2270 if renamer_dirs.is_empty() && split_dirs.is_empty() {
2271 return Vec::new();
2272 }
2273
2274 let side_rename_src: BTreeMap<&[u8], &[u8]> = side_renames
2277 .pairs
2278 .iter()
2279 .map(|(o, n)| (n.as_slice(), o.as_slice()))
2280 .collect();
2281
2282 let candidates: Vec<Vec<u8>> = side
2283 .keys()
2284 .filter(|p| !base_map.contains_key(*p) || side_rename_src.contains_key(p.as_slice()))
2285 .cloned()
2286 .collect();
2287
2288 let mut planned: BTreeMap<Vec<u8>, Vec<DirRenameMove>> = BTreeMap::new();
2290 for path in candidates {
2291 if let Some(split_dir) = check_dir_split(&path, split_dirs) {
2292 splits.insert(split_dir.to_vec());
2293 *dirty = true;
2294 continue;
2295 }
2296 let Some((old_dir, new_dir)) = check_dir_renamed(&path, renamer_dirs) else {
2297 continue;
2298 };
2299 let new_dir_is_exclusion = exclusions.contains(new_dir);
2303 let new_dir_inside_exclusion = exclusions
2304 .iter()
2305 .any(|dir| directory_contains_proper(dir, new_dir));
2306 if new_dir_is_exclusion
2307 || (new_dir_inside_exclusion
2308 && !side_has_pure_add_under_dir(side, base_map, &side_rename_src, old_dir))
2309 {
2310 info_messages.push(MergeInfoMessage::DirRenameSkippedDueToRerename {
2311 old_dir: old_dir.to_vec(),
2312 path: path.clone(),
2313 new_dir: new_dir.to_vec(),
2314 });
2315 continue;
2316 }
2317 let dest = apply_dir_rename(old_dir, new_dir, &path);
2318 if dest == path {
2319 continue;
2321 }
2322 let renamed_from = side_rename_src.get(path.as_slice()).map(|s| s.to_vec());
2323 planned
2324 .entry(dest.clone())
2325 .or_default()
2326 .push(DirRenameMove {
2327 from: path,
2328 to: dest,
2329 renamed_from,
2330 });
2331 }
2332
2333 let mut moves = Vec::new();
2334 for (dest, group) in planned {
2335 if group.len() > 1 {
2336 *dirty = true;
2339 collisions.push(DirRenameCollision {
2340 dest,
2341 sources: group.into_iter().map(|m| m.from).collect(),
2342 });
2343 continue;
2344 }
2345 moves.push(group.into_iter().next().expect("non-empty"));
2346 }
2347 moves
2348}
2349
2350fn check_dir_split<'a>(path: &[u8], split_dirs: &'a BTreeSet<Vec<u8>>) -> Option<&'a [u8]> {
2351 let mut dir = parent_dir(path)?;
2352 loop {
2353 if let Some(split_dir) = split_dirs.get(dir) {
2354 return Some(split_dir);
2355 }
2356 dir = parent_dir(dir)?;
2357 }
2358}
2359
2360fn directory_contains_proper(parent: &[u8], child: &[u8]) -> bool {
2361 !parent.is_empty()
2362 && child.len() > parent.len()
2363 && child.starts_with(parent)
2364 && child[parent.len()] == b'/'
2365}
2366
2367fn side_has_pure_add_under_dir(
2368 side: &MergeEntryMap,
2369 base_map: &MergeEntryMap,
2370 side_rename_src: &BTreeMap<&[u8], &[u8]>,
2371 dir: &[u8],
2372) -> bool {
2373 side.keys().any(|path| {
2374 path_is_under_dir(path, dir)
2375 && !base_map.contains_key(path)
2376 && !side_rename_src.contains_key(path.as_slice())
2377 })
2378}
2379
2380fn path_is_under_dir(path: &[u8], dir: &[u8]) -> bool {
2381 !dir.is_empty() && path.len() > dir.len() && path.starts_with(dir) && path[dir.len()] == b'/'
2382}
2383
2384#[allow(clippy::too_many_arguments)]
2393fn apply_rehome_moves(
2394 original_base: &MergeEntryMap,
2395 file_rename_dests: &BTreeMap<Vec<u8>, MergeRename>,
2396 base: &mut MergeEntryMap,
2397 ours: &mut MergeEntryMap,
2398 theirs: &mut MergeEntryMap,
2399 moves: Vec<DirRenameMove>,
2400 side_is_ours: bool,
2401 rehomed: &mut BTreeMap<Vec<u8>, RehomeSides>,
2402 collisions: &mut Vec<DirRenameCollision>,
2403 back_to_self: &mut BTreeSet<Vec<u8>>,
2404 dirty: &mut bool,
2405) {
2406 for mv in moves {
2407 let occupied_on_this_side = if side_is_ours {
2412 ours.contains_key(&mv.to) || map_has_directory_at(ours, &mv.to)
2413 } else {
2414 theirs.contains_key(&mv.to) || map_has_directory_at(theirs, &mv.to)
2415 };
2416 let occupied_by_cross_rename =
2417 file_rename_dests
2418 .get(&mv.to)
2419 .is_some_and(|rename| match (side_is_ours, rename.side) {
2420 (true, RenameSide::Theirs) | (false, RenameSide::Ours) => true,
2421 (true, RenameSide::Ours) | (false, RenameSide::Theirs) => false,
2422 });
2423 let base_entry_at_dest = original_base.get(&mv.to).copied();
2424 let base_entry_at_source = original_base.get(&mv.from).copied();
2425 let other_side_entry_at_dest = if side_is_ours {
2426 theirs.get(&mv.to).copied()
2427 } else {
2428 ours.get(&mv.to).copied()
2429 };
2430 let other_side_entry_at_source = if side_is_ours {
2431 theirs.get(&mv.from).copied()
2432 } else {
2433 ours.get(&mv.from).copied()
2434 };
2435 let base_entry_for_shifted_source = base_entry_at_source.or(base_entry_at_dest);
2436 let rename_back_to_modified_source = mv
2437 .renamed_from
2438 .as_ref()
2439 .is_some_and(|source| source == &mv.to)
2440 && base_entry_at_dest.is_some()
2441 && (other_side_entry_at_dest.is_some_and(|entry| Some(entry) != base_entry_at_dest)
2442 || other_side_entry_at_source
2443 .is_some_and(|entry| Some(entry) != base_entry_for_shifted_source));
2444 if ((base_entry_at_dest.is_some() && !rename_back_to_modified_source)
2445 || (occupied_on_this_side && !occupied_by_cross_rename))
2446 && mv.to != mv.from
2447 {
2448 *dirty = true;
2449 collisions.push(DirRenameCollision {
2450 dest: mv.to.clone(),
2451 sources: vec![mv.from.clone()],
2452 });
2453 continue;
2454 }
2455 let mut moved = false;
2456 if occupied_by_cross_rename {
2457 base.remove(&mv.from);
2458 if side_is_ours {
2459 if let Some(entry) = ours.remove(&mv.from) {
2460 ours.insert(mv.to.clone(), entry);
2461 moved = true;
2462 }
2463 theirs.remove(&mv.from);
2464 } else {
2465 ours.remove(&mv.from);
2466 if let Some(entry) = theirs.remove(&mv.from) {
2467 theirs.insert(mv.to.clone(), entry);
2468 moved = true;
2469 }
2470 }
2471 } else {
2472 for m in [&mut *base, &mut *ours, &mut *theirs] {
2476 if let Some(entry) = m.remove(&mv.from) {
2477 m.insert(mv.to.clone(), entry);
2478 moved = true;
2479 }
2480 }
2481 }
2482 if moved {
2483 if rename_back_to_modified_source {
2484 back_to_self.insert(mv.to.clone());
2485 }
2486 let info = RehomeInfo {
2487 old_path: mv.from.clone(),
2488 renamed_from: mv.renamed_from.clone(),
2489 added_on_ours: side_is_ours,
2490 };
2491 let entry = rehomed.entry(mv.to.clone()).or_default();
2492 if side_is_ours {
2493 entry.ours = Some(info);
2494 } else {
2495 entry.theirs = Some(info);
2496 }
2497 }
2498 }
2499}
2500
2501fn collect_dir_rename_two_to_one(
2502 renames: &MergeRenames,
2503 rehomed: &BTreeMap<Vec<u8>, RehomeSides>,
2504) -> Vec<DirRenameTwoToOne> {
2505 let mut conflicts = Vec::new();
2506 for (dest, sides) in rehomed {
2507 let Some(file_rename) = renames.dest_to_source.get(dest) else {
2508 continue;
2509 };
2510 match file_rename.side {
2511 RenameSide::Ours => {
2512 let Some(info) = sides.theirs.as_ref() else {
2513 continue;
2514 };
2515 let Some(theirs_source) = info.renamed_from.as_ref() else {
2516 continue;
2517 };
2518 conflicts.push(DirRenameTwoToOne {
2519 dest: dest.clone(),
2520 ours_source: file_rename.source.clone(),
2521 theirs_source: theirs_source.clone(),
2522 ours_label_path: dest.clone(),
2523 theirs_label_path: info.old_path.clone(),
2524 });
2525 }
2526 RenameSide::Theirs => {
2527 let Some(info) = sides.ours.as_ref() else {
2528 continue;
2529 };
2530 let Some(ours_source) = info.renamed_from.as_ref() else {
2531 continue;
2532 };
2533 conflicts.push(DirRenameTwoToOne {
2534 dest: dest.clone(),
2535 ours_source: ours_source.clone(),
2536 theirs_source: file_rename.source.clone(),
2537 ours_label_path: info.old_path.clone(),
2538 theirs_label_path: dest.clone(),
2539 });
2540 }
2541 }
2542 }
2543 conflicts
2544}
2545
2546fn map_has_directory_at(map: &MergeEntryMap, path: &[u8]) -> bool {
2547 let mut prefix = path.to_vec();
2548 prefix.push(b'/');
2549 map.keys().any(|candidate| candidate.starts_with(&prefix))
2550}
2551
2552fn remap_rename_destinations(renames: &mut MergeRenames, rehomed: &BTreeMap<Vec<u8>, RehomeSides>) {
2553 if rehomed.is_empty() {
2554 return;
2555 }
2556 let mut remapped_deletes = BTreeMap::new();
2557 for (dest, rd) in std::mem::take(&mut renames.rename_deletes) {
2558 let new_dest = rehomed
2559 .iter()
2560 .find_map(|(new_dest, sides)| {
2561 let moved = sides
2562 .ours
2563 .as_ref()
2564 .is_some_and(|info| info.old_path == dest)
2565 || sides
2566 .theirs
2567 .as_ref()
2568 .is_some_and(|info| info.old_path == dest);
2569 moved.then(|| new_dest.clone())
2570 })
2571 .unwrap_or(dest);
2572 remapped_deletes.insert(new_dest, rd);
2573 }
2574 renames.rename_deletes = remapped_deletes;
2575
2576 for rename in renames.rename_rename_one_to_two.values_mut() {
2577 for (dest, sides) in rehomed {
2578 if sides
2579 .ours
2580 .as_ref()
2581 .is_some_and(|info| info.old_path == rename.ours_dest)
2582 {
2583 rename.ours_dest = dest.clone();
2584 }
2585 if sides
2586 .theirs
2587 .as_ref()
2588 .is_some_and(|info| info.old_path == rename.theirs_dest)
2589 {
2590 rename.theirs_dest = dest.clone();
2591 }
2592 }
2593 }
2594}
2595
2596fn drop_collapsed_rename_rename_conflicts(renames: &mut MergeRenames) {
2597 renames
2598 .rename_rename_one_to_two
2599 .retain(|_, rename| rename.ours_dest != rename.theirs_dest);
2600}
2601
2602fn apply_dir_rename_two_to_one_conflicts(
2603 db: &FileObjectDatabase,
2604 eff_ours: &MergeEntryMap,
2605 eff_theirs: &MergeEntryMap,
2606 conflicts: &[DirRenameTwoToOne],
2607 paths: &mut [MergedPath],
2608 leaves: &mut MergeEntryMap,
2609 options: &MergeTreesOptions<'_>,
2610) -> Result<()> {
2611 for conflict in conflicts {
2612 let Some(slot) = paths.iter_mut().find(|path| path.path == conflict.dest) else {
2613 continue;
2614 };
2615 let ours_entry = eff_ours.get(&conflict.dest).copied();
2616 let theirs_entry = eff_theirs.get(&conflict.dest).copied();
2617 let (Some((ours_mode, ours_oid)), Some((theirs_mode, theirs_oid))) =
2618 (ours_entry, theirs_entry)
2619 else {
2620 continue;
2621 };
2622 let ours_bytes = merge_blob_bytes(db, &ours_oid)?;
2623 let theirs_bytes = merge_blob_bytes(db, &theirs_oid)?;
2624 let (resolved_mode, mode_conflict) = merge_file_modes(None, ours_mode, theirs_mode);
2625 let favor = merge_favor_for_path(options, &conflict.dest);
2626 let result = if is_mergeable_file_mode(ours_mode) && is_mergeable_file_mode(theirs_mode) {
2627 merge_blobs(
2628 &[],
2629 &ours_bytes,
2630 &theirs_bytes,
2631 &MergeBlobOptions {
2632 ours_label: &qualify_label(options.ours_label, &conflict.ours_label_path),
2633 theirs_label: &qualify_label(options.theirs_label, &conflict.theirs_label_path),
2634 base_label: options.ancestor_label,
2635 style: options.style,
2636 favor,
2637 ws_ignore: options.ws_ignore,
2638 marker_size: merge_marker_size_for_path(options, &conflict.dest),
2639 },
2640 )
2641 } else {
2642 MergeBlobResult {
2643 content: ours_bytes.clone(),
2644 conflicted: true,
2645 }
2646 };
2647 let oid = db.write_object(EncodedObject::new(ObjectType::Blob, result.content.clone()))?;
2648 leaves.insert(conflict.dest.clone(), (resolved_mode, oid));
2649 slot.stages = MergeStages {
2650 base: None,
2651 ours: ours_entry,
2652 theirs: theirs_entry,
2653 };
2654 slot.result = Some((resolved_mode, oid));
2655 slot.worktree = Some((
2656 if ours_mode == theirs_mode {
2657 ours_mode
2658 } else {
2659 0o100644
2660 },
2661 result.content,
2662 ));
2663 slot.conflict = Some(MergeConflictKind::RenameRenameTwoToOne {
2664 ours_path: conflict.ours_source.clone(),
2665 theirs_path: conflict.theirs_source.clone(),
2666 });
2667 slot.auto_merged = !mode_conflict;
2668 }
2669 Ok(())
2670}
2671
2672fn rename_merged_leaf(
2678 db: &FileObjectDatabase,
2679 base: Option<(u32, ObjectId)>,
2680 ours: Option<(u32, ObjectId)>,
2681 theirs: Option<(u32, ObjectId)>,
2682 path: &[u8],
2683 options: &MergeTreesOptions<'_>,
2684) -> Result<Option<(u32, ObjectId)>> {
2685 match (ours, theirs) {
2686 (None, None) => Ok(None),
2687 (Some(entry), None) | (None, Some(entry)) => Ok(Some(entry)),
2688 (Some((ours_mode, ours_oid)), Some((theirs_mode, theirs_oid))) => {
2689 if (ours_mode, ours_oid) == (theirs_mode, theirs_oid) {
2690 return Ok(Some((ours_mode, ours_oid)));
2691 }
2692 if !is_mergeable_file_mode(ours_mode) || !is_mergeable_file_mode(theirs_mode) {
2693 return Ok(Some((ours_mode, ours_oid)));
2694 }
2695 let base_bytes = match base {
2696 Some((_, oid)) => merge_blob_bytes(db, &oid)?,
2697 None => Vec::new(),
2698 };
2699 let favor = merge_favor_for_path(options, path);
2700 let result = merge_blobs(
2701 &base_bytes,
2702 &merge_blob_bytes(db, &ours_oid)?,
2703 &merge_blob_bytes(db, &theirs_oid)?,
2704 &MergeBlobOptions {
2705 ours_label: options.ours_label,
2706 theirs_label: options.theirs_label,
2707 base_label: options.ancestor_label,
2708 style: options.style,
2709 favor,
2710 ws_ignore: options.ws_ignore,
2711 marker_size: merge_marker_size_for_path(options, path),
2712 },
2713 );
2714 let (mode, _) = merge_file_modes(base.map(|(mode, _)| mode), ours_mode, theirs_mode);
2715 let oid = db.write_object(EncodedObject::new(ObjectType::Blob, result.content))?;
2716 Ok(Some((mode, oid)))
2717 }
2718 }
2719}
2720
2721#[allow(clippy::too_many_arguments)]
2729fn apply_rename_two_to_one_and_add_conflicts(
2730 db: &FileObjectDatabase,
2731 base_map: &MergeEntryMap,
2732 ours_map: &MergeEntryMap,
2733 theirs_map: &MergeEntryMap,
2734 renames: &MergeRenames,
2735 paths: &mut Vec<MergedPath>,
2736 leaves: &mut MergeEntryMap,
2737 options: &MergeTreesOptions<'_>,
2738) -> Result<()> {
2739 let mut consumed_sources: Vec<Vec<u8>> = Vec::new();
2740
2741 for (dest, conflict) in &renames.rename_rename_two_to_one {
2742 let ours_leaf = rename_merged_leaf(
2745 db,
2746 base_map.get(&conflict.ours_source).copied(),
2747 ours_map.get(dest).copied(),
2748 theirs_map.get(&conflict.ours_source).copied(),
2749 dest,
2750 options,
2751 )?;
2752 let theirs_leaf = rename_merged_leaf(
2753 db,
2754 base_map.get(&conflict.theirs_source).copied(),
2755 ours_map.get(&conflict.theirs_source).copied(),
2756 theirs_map.get(dest).copied(),
2757 dest,
2758 options,
2759 )?;
2760 write_two_sided_dest_conflict(
2761 db,
2762 dest,
2763 ours_leaf,
2764 theirs_leaf,
2765 MergeConflictKind::RenameRenameTwoToOne {
2766 ours_path: conflict.ours_source.clone(),
2767 theirs_path: conflict.theirs_source.clone(),
2768 },
2769 options,
2770 paths,
2771 leaves,
2772 )?;
2773 consumed_sources.push(conflict.ours_source.clone());
2774 consumed_sources.push(conflict.theirs_source.clone());
2775 }
2776
2777 for (dest, add) in &renames.rename_adds {
2778 let (ours_leaf, theirs_leaf) = match add.side {
2779 RenameSide::Ours => (
2780 rename_merged_leaf(
2781 db,
2782 base_map.get(&add.source).copied(),
2783 ours_map.get(dest).copied(),
2784 theirs_map.get(&add.source).copied(),
2785 dest,
2786 options,
2787 )?,
2788 theirs_map.get(dest).copied(),
2789 ),
2790 RenameSide::Theirs => (
2791 ours_map.get(dest).copied(),
2792 rename_merged_leaf(
2793 db,
2794 base_map.get(&add.source).copied(),
2795 ours_map.get(&add.source).copied(),
2796 theirs_map.get(dest).copied(),
2797 dest,
2798 options,
2799 )?,
2800 ),
2801 };
2802 write_two_sided_dest_conflict(
2803 db,
2804 dest,
2805 ours_leaf,
2806 theirs_leaf,
2807 MergeConflictKind::Content { add_add: true },
2808 options,
2809 paths,
2810 leaves,
2811 )?;
2812 consumed_sources.push(add.source.clone());
2813 }
2814
2815 for source in &consumed_sources {
2821 leaves.remove(source);
2822 if let Some(slot) = paths.iter_mut().find(|path| &path.path == source) {
2823 slot.stages = MergeStages::default();
2824 slot.result = None;
2825 slot.worktree = None;
2826 slot.conflict = None;
2827 slot.auto_merged = false;
2828 } else {
2829 paths.push(MergedPath {
2830 path: source.clone(),
2831 stages: MergeStages::default(),
2832 result: None,
2833 worktree: None,
2834 conflict: None,
2835 auto_merged: false,
2836 });
2837 }
2838 }
2839 Ok(())
2840}
2841
2842#[allow(clippy::too_many_arguments)]
2847fn write_two_sided_dest_conflict(
2848 db: &FileObjectDatabase,
2849 dest: &[u8],
2850 ours_leaf: Option<(u32, ObjectId)>,
2851 theirs_leaf: Option<(u32, ObjectId)>,
2852 kind: MergeConflictKind,
2853 options: &MergeTreesOptions<'_>,
2854 paths: &mut Vec<MergedPath>,
2855 leaves: &mut MergeEntryMap,
2856) -> Result<()> {
2857 let ours_bytes = match ours_leaf {
2858 Some((mode, oid)) => Some((mode, merge_worktree_bytes(db, mode, &oid)?)),
2859 None => None,
2860 };
2861 let theirs_bytes = match theirs_leaf {
2862 Some((mode, oid)) => Some((mode, merge_worktree_bytes(db, mode, &oid)?)),
2863 None => None,
2864 };
2865 let (worktree_mode, worktree_content, result_leaf) = match (&ours_bytes, &theirs_bytes) {
2866 (Some((ours_mode, ours_content)), Some((theirs_mode, theirs_content))) => {
2867 let favor = merge_favor_for_path(options, dest);
2868 let merged = merge_blobs(
2869 &[],
2870 ours_content,
2871 theirs_content,
2872 &MergeBlobOptions {
2873 ours_label: options.ours_label,
2874 theirs_label: options.theirs_label,
2875 base_label: options.ancestor_label,
2876 style: options.style,
2877 favor,
2878 ws_ignore: options.ws_ignore,
2879 marker_size: merge_marker_size_for_path(options, dest),
2880 },
2881 );
2882 let mode = if ours_mode == theirs_mode {
2883 *ours_mode
2884 } else {
2885 0o100644
2886 };
2887 let oid =
2888 db.write_object(EncodedObject::new(ObjectType::Blob, merged.content.clone()))?;
2889 (mode, merged.content, Some((mode, oid)))
2890 }
2891 (Some((mode, content)), None) | (None, Some((mode, content))) => {
2892 (*mode, content.clone(), ours_leaf.or(theirs_leaf))
2893 }
2894 (None, None) => (0o100644, Vec::new(), None),
2895 };
2896
2897 let slot = MergedPath {
2898 path: dest.to_vec(),
2899 stages: MergeStages {
2900 base: None,
2901 ours: ours_leaf,
2902 theirs: theirs_leaf,
2903 },
2904 result: result_leaf,
2905 worktree: Some((worktree_mode, worktree_content)),
2906 conflict: Some(kind),
2907 auto_merged: true,
2908 };
2909 if let Some(existing) = paths.iter_mut().find(|path| path.path == dest) {
2910 *existing = slot;
2911 } else {
2912 paths.push(slot);
2913 }
2914 if let Some(leaf) = result_leaf {
2915 leaves.insert(dest.to_vec(), leaf);
2916 } else {
2917 leaves.remove(dest);
2918 }
2919 Ok(())
2920}
2921
2922#[allow(clippy::too_many_arguments)]
2923fn apply_rename_rename_one_to_two_conflicts(
2924 db: &FileObjectDatabase,
2925 base_map: &MergeEntryMap,
2926 eff_ours: &MergeEntryMap,
2927 eff_theirs: &MergeEntryMap,
2928 conflicts: &BTreeMap<Vec<u8>, RenameRenameOneToTwo>,
2929 paths: &mut Vec<MergedPath>,
2930 leaves: &mut MergeEntryMap,
2931 options: &MergeTreesOptions<'_>,
2932) -> Result<()> {
2933 for (old_path, conflict) in conflicts {
2934 let base_entry = base_map.get(old_path).copied();
2935 let ours_entry = eff_ours.get(&conflict.ours_dest).copied();
2936 let theirs_entry = eff_theirs.get(&conflict.theirs_dest).copied();
2937 let theirs_add_at_ours_dest = eff_theirs.get(&conflict.ours_dest).copied();
2938 let ours_add_at_theirs_dest = eff_ours.get(&conflict.theirs_dest).copied();
2939
2940 leaves.remove(old_path);
2941 leaves.remove(&conflict.ours_dest);
2942 leaves.remove(&conflict.theirs_dest);
2943 paths.retain(|path| {
2944 path.path != *old_path
2945 && path.path != conflict.ours_dest
2946 && path.path != conflict.theirs_dest
2947 });
2948
2949 paths.push(MergedPath {
2950 path: old_path.clone(),
2951 stages: MergeStages {
2952 base: base_entry,
2953 ours: None,
2954 theirs: None,
2955 },
2956 result: None,
2957 worktree: None,
2958 conflict: Some(MergeConflictKind::RenameRenameOneToTwo {
2959 old_path: old_path.clone(),
2960 ours_path: conflict.ours_dest.clone(),
2961 theirs_path: conflict.theirs_dest.clone(),
2962 ours_label: options.ours_label.to_string(),
2963 theirs_label: options.theirs_label.to_string(),
2964 }),
2965 auto_merged: false,
2966 });
2967
2968 let ours_worktree = match ours_entry {
2969 Some((mode, oid)) => Some((mode, merge_worktree_bytes(db, mode, &oid)?)),
2970 None => None,
2971 };
2972 paths.push(MergedPath {
2973 path: conflict.ours_dest.clone(),
2974 stages: MergeStages {
2975 base: None,
2976 ours: ours_entry,
2977 theirs: theirs_add_at_ours_dest,
2978 },
2979 result: None,
2980 worktree: ours_worktree,
2981 conflict: Some(MergeConflictKind::RenameRenameOneToTwoStage),
2982 auto_merged: false,
2983 });
2984
2985 let theirs_worktree = match theirs_entry {
2986 Some((mode, oid)) => Some((mode, merge_worktree_bytes(db, mode, &oid)?)),
2987 None => None,
2988 };
2989 paths.push(MergedPath {
2990 path: conflict.theirs_dest.clone(),
2991 stages: MergeStages {
2992 base: None,
2993 ours: ours_add_at_theirs_dest,
2994 theirs: theirs_entry,
2995 },
2996 result: None,
2997 worktree: theirs_worktree,
2998 conflict: Some(MergeConflictKind::RenameRenameOneToTwoStage),
2999 auto_merged: false,
3000 });
3001 }
3002 Ok(())
3003}
3004
3005fn qualify_label(label: &str, path: &[u8]) -> String {
3008 format!("{label}:{}", String::from_utf8_lossy(path))
3009}
3010
3011fn entry_map_as_tracked(map: &MergeEntryMap) -> BTreeMap<Vec<u8>, TrackedEntry> {
3014 map.iter()
3015 .map(|(path, (mode, oid))| {
3016 (
3017 path.clone(),
3018 TrackedEntry {
3019 mode: *mode,
3020 oid: *oid,
3021 },
3022 )
3023 })
3024 .collect()
3025}
3026
3027pub fn virtual_ancestor_entry_map(
3036 db: &FileObjectDatabase,
3037 format: ObjectFormat,
3038 bases: &[ObjectId],
3039 merge_bases: impl Fn(&ObjectId, &ObjectId) -> Result<Vec<ObjectId>>,
3040) -> Result<MergeEntryMap> {
3041 let first = bases
3042 .first()
3043 .ok_or_else(|| GitError::Command("virtual ancestor needs at least one base".into()))?;
3044 let acc_tree = commit_tree_oid(db, format, first)?;
3045 let mut acc_map = flatten_tree(db, format, &acc_tree)?;
3046 let mut acc_commits = vec![*first];
3047
3048 for base in &bases[1..] {
3049 let other_tree = commit_tree_oid(db, format, base)?;
3050 let other_map = flatten_tree(db, format, &other_tree)?;
3051 let sub_bases = merge_bases(&acc_commits[0], base)?;
3052 let sub_base_map = match sub_bases.first() {
3053 Some(sb) => {
3054 let sb_tree = commit_tree_oid(db, format, sb)?;
3055 flatten_tree(db, format, &sb_tree)?
3056 }
3057 None => MergeEntryMap::new(),
3058 };
3059 let merge = merge_entry_maps(
3060 db,
3061 format,
3062 &sub_base_map,
3063 &acc_map,
3064 &other_map,
3065 &MergeTreesOptions {
3066 ours_label: "Temporary merge branch 1",
3067 theirs_label: "Temporary merge branch 2",
3068 detect_renames: true,
3069 rename_threshold: DEFAULT_RENAME_THRESHOLD,
3070 ..Default::default()
3071 },
3072 )?;
3073 let mut next = MergeEntryMap::new();
3074 for entry in merge.paths {
3075 if let Some(leaf) = fold_virtual_ancestor_path(db, &entry)? {
3076 next.insert(entry.path, leaf);
3077 }
3078 }
3079 acc_map = next;
3080 acc_commits = vec![*base];
3081 }
3082 Ok(acc_map)
3083}
3084
3085fn commit_tree_oid(
3086 db: &FileObjectDatabase,
3087 format: ObjectFormat,
3088 commit_oid: &ObjectId,
3089) -> Result<ObjectId> {
3090 let object = db.read_object(commit_oid)?;
3091 if object.object_type != ObjectType::Commit {
3092 return Err(GitError::InvalidObject(format!(
3093 "expected commit {commit_oid}, found {}",
3094 object.object_type.as_str()
3095 )));
3096 }
3097 Ok(Commit::parse_ref(format, &object.body)?.tree)
3098}
3099
3100fn fold_virtual_ancestor_path(
3101 db: &FileObjectDatabase,
3102 entry: &MergedPath,
3103) -> Result<Option<(u32, ObjectId)>> {
3104 if entry.conflict.is_none() {
3105 return Ok(entry.result);
3106 }
3107 let base = entry.stages.base;
3108 let ours = entry.stages.ours;
3109 let theirs = entry.stages.theirs;
3110 if let Some((mode, _)) = entry.worktree.as_ref() {
3111 if is_symlink_mode(*mode) {
3112 return Ok(base);
3113 }
3114 if is_gitlink(*mode) {
3115 return Ok(theirs.or(ours));
3116 }
3117 if let Some((mode, bytes)) = &entry.worktree {
3118 let oid = db.write_object(EncodedObject::new(ObjectType::Blob, bytes.clone()))?;
3119 return Ok(Some((*mode, oid)));
3120 }
3121 }
3122 Ok(ours.or(theirs))
3123}