1use std::collections::{BTreeMap, HashMap, HashSet};
8
9use crate::diff::{detect_renames, diff_trees, DiffStatus};
10use crate::index::{Index, IndexEntry};
11use crate::merge_file::{merge, ConflictStyle, MergeFavor, MergeInput};
12use crate::objects::{parse_tree, ObjectId, ObjectKind};
13use crate::odb::Odb;
14use crate::repo::Repository;
15use crate::write_tree::write_tree_from_index;
16
17#[derive(Debug)]
19pub struct TreeMergeOutput {
20 pub index: Index,
22 pub conflict_content: BTreeMap<Vec<u8>, ObjectId>,
24}
25
26#[derive(Clone, Copy, Debug, Default)]
27pub struct WhitespaceMergeOptions {
28 pub ignore_all_space: bool,
29 pub ignore_space_change: bool,
30 pub ignore_space_at_eol: bool,
31 pub ignore_cr_at_eol: bool,
32}
33
34#[derive(Clone, Copy, Debug)]
36pub enum TheirsConflictLabel<'a> {
37 PathUtf8,
39 Fixed(&'a str),
41}
42
43#[derive(Clone, Copy, Debug)]
45pub struct TreeMergeConflictPresentation<'a> {
46 pub label_ours: &'a str,
48 pub label_theirs: TheirsConflictLabel<'a>,
50 pub label_base: &'a str,
52 pub style: ConflictStyle,
54 pub checkout_merge: bool,
56}
57
58impl Default for TreeMergeConflictPresentation<'_> {
59 fn default() -> Self {
60 Self {
61 label_ours: "HEAD",
62 label_theirs: TheirsConflictLabel::PathUtf8,
63 label_base: "merged common ancestors",
64 style: ConflictStyle::Merge,
65 checkout_merge: false,
66 }
67 }
68}
69
70pub fn merge_trees_three_way(
88 repo: &Repository,
89 base_tree: ObjectId,
90 ours_tree: ObjectId,
91 theirs_tree: ObjectId,
92 favor: MergeFavor,
93 ws: WhitespaceMergeOptions,
94 diff_algorithm: Option<&str>,
95 presentation: TreeMergeConflictPresentation<'_>,
96) -> crate::error::Result<TreeMergeOutput> {
97 let odb = &repo.odb;
98 let base = tree_to_map(tree_to_index_entries(repo, &base_tree, "")?);
99 let ours = tree_to_map(tree_to_index_entries(repo, &ours_tree, "")?);
100 let theirs = tree_to_map(tree_to_index_entries(repo, &theirs_tree, "")?);
101
102 let ours_old_to_new = rename_pairs_base_to_other(odb, &base_tree, &ours_tree)?;
103 let theirs_pairs = rename_pairs_base_to_other(odb, &base_tree, &theirs_tree)?;
104
105 let mut ours_new_to_old: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
106 let mut ours_best_by_dest: HashMap<Vec<u8>, (Vec<u8>, u32)> = HashMap::new();
107 for (old, new, score) in &ours_old_to_new {
108 let new_b = new.clone();
109 let should_take = match ours_best_by_dest.get(&new_b) {
110 None => true,
111 Some((_, s)) => *score > *s,
112 };
113 if should_take {
114 ours_best_by_dest.insert(new_b, (old.clone(), *score));
115 }
116 }
117 for (new_path, (old_path, _)) in ours_best_by_dest {
118 ours_new_to_old.insert(new_path, old_path);
119 }
120 let mut theirs_old_to_new: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
121 let mut best_by_dest: HashMap<Vec<u8>, (Vec<u8>, u32)> = HashMap::new();
122 for (old, new, score) in theirs_pairs {
123 let new_b = new.clone();
124 let should_take = match best_by_dest.get(&new_b) {
125 None => true,
126 Some((_, s)) => score > *s,
127 };
128 if should_take {
129 best_by_dest.insert(new_b, (old.clone(), score));
130 }
131 }
132 for (new_path, (old_path, _)) in best_by_dest {
133 theirs_old_to_new.insert(old_path, new_path);
134 }
135
136 three_way_on_aligned_paths(
137 repo,
138 &base,
139 &ours,
140 &theirs,
141 &ours_new_to_old,
142 &theirs_old_to_new,
143 favor,
144 ws,
145 diff_algorithm,
146 presentation,
147 )
148}
149
150fn rename_pairs_base_to_other(
151 odb: &Odb,
152 base_tree: &ObjectId,
153 other_tree: &ObjectId,
154) -> crate::error::Result<Vec<(Vec<u8>, Vec<u8>, u32)>> {
155 let mut entries = diff_trees(odb, Some(base_tree), Some(other_tree), "")?;
156 entries = detect_renames(odb, None, entries, 50);
157 let mut out = Vec::new();
158 for e in entries {
159 if e.status != DiffStatus::Renamed {
160 continue;
161 }
162 let Some(old) = e.old_path else {
163 continue;
164 };
165 let Some(new) = e.new_path else {
166 continue;
167 };
168 let score = e.score.unwrap_or(0);
169 out.push((old.into_bytes(), new.into_bytes(), score));
170 }
171 Ok(out)
172}
173
174fn three_way_on_aligned_paths(
175 repo: &Repository,
176 base: &HashMap<Vec<u8>, IndexEntry>,
177 ours: &HashMap<Vec<u8>, IndexEntry>,
178 theirs: &HashMap<Vec<u8>, IndexEntry>,
179 ours_new_to_old: &HashMap<Vec<u8>, Vec<u8>>,
180 theirs_old_to_new: &HashMap<Vec<u8>, Vec<u8>>,
181 favor: MergeFavor,
182 ws: WhitespaceMergeOptions,
183 diff_algorithm: Option<&str>,
184 presentation: TreeMergeConflictPresentation<'_>,
185) -> crate::error::Result<TreeMergeOutput> {
186 let mut out = Index::new();
187 let mut conflict_content = BTreeMap::new();
188 let mut handled_base: HashSet<Vec<u8>> = HashSet::new();
189 let mut handled_theirs: HashSet<Vec<u8>> = HashSet::new();
190
191 for op in sorted_paths(ours.keys()) {
192 let bp = if let Some(old) = ours_new_to_old.get(&op) {
193 Some(old.clone())
194 } else if base.contains_key(&op) {
195 Some(op.clone())
196 } else {
197 None
198 };
199
200 let tp = if let Some(ref bpath) = bp {
201 theirs_old_to_new
202 .get(bpath)
203 .cloned()
204 .unwrap_or_else(|| bpath.clone())
205 } else {
206 op.clone()
207 };
208
209 let b = bp.as_ref().and_then(|p| base.get(p));
210 let o = ours.get(&op);
211 let t = theirs.get(&tp);
212 if t.is_some() {
213 handled_theirs.insert(tp.clone());
214 }
215
216 if let Some(ref p) = bp {
217 handled_base.insert(p.clone());
218 }
219
220 let out_path = if bp.as_ref().is_some_and(|b| b == &op) && tp != op {
224 tp.clone()
225 } else {
226 op.clone()
227 };
228
229 merge_one_path(
230 repo,
231 &mut out,
232 &mut conflict_content,
233 &out_path,
234 b,
235 o,
236 t,
237 favor,
238 ws,
239 diff_algorithm,
240 presentation,
241 )?;
242 }
243
244 for bp in sorted_paths(base.keys()) {
245 if handled_base.contains(&bp) {
246 continue;
247 }
248 let tp = theirs_old_to_new
249 .get(&bp)
250 .cloned()
251 .unwrap_or_else(|| bp.clone());
252 let b = base.get(&bp);
253 let o: Option<&IndexEntry> = None;
254 let t = theirs.get(&tp);
255 if t.is_some() {
256 handled_theirs.insert(tp.clone());
257 }
258 merge_one_path(
259 repo,
260 &mut out,
261 &mut conflict_content,
262 &bp,
263 b,
264 o,
265 t,
266 favor,
267 ws,
268 diff_algorithm,
269 presentation,
270 )?;
271 }
272
273 for tp in sorted_paths(theirs.keys()) {
274 if handled_theirs.contains(&tp) {
275 continue;
276 }
277 let b: Option<&IndexEntry> = None;
278 let o: Option<&IndexEntry> = None;
279 let t = theirs.get(&tp);
280 merge_one_path(
281 repo,
282 &mut out,
283 &mut conflict_content,
284 &tp,
285 b,
286 o,
287 t,
288 favor,
289 ws,
290 diff_algorithm,
291 presentation,
292 )?;
293 }
294
295 out.sort();
296 Ok(TreeMergeOutput {
297 index: out,
298 conflict_content,
299 })
300}
301
302fn sorted_paths<'a>(keys: impl Iterator<Item = &'a Vec<u8>>) -> Vec<Vec<u8>> {
303 let mut v: Vec<Vec<u8>> = keys.cloned().collect();
304 v.sort();
305 v
306}
307
308fn merge_one_path(
309 repo: &Repository,
310 index: &mut Index,
311 conflict_content: &mut BTreeMap<Vec<u8>, ObjectId>,
312 out_path: &[u8],
313 b: Option<&IndexEntry>,
314 o: Option<&IndexEntry>,
315 t: Option<&IndexEntry>,
316 favor: MergeFavor,
317 ws: WhitespaceMergeOptions,
318 diff_algorithm: Option<&str>,
319 presentation: TreeMergeConflictPresentation<'_>,
320) -> crate::error::Result<()> {
321 match (b, o, t) {
322 (_, Some(oe), Some(te)) if same_blob(oe, te) => {
323 let mut e = oe.clone();
324 e.path = out_path.to_vec();
325 e.flags = path_len_flags(out_path);
326 index.entries.push(e);
327 }
328 (Some(be), Some(oe), Some(te)) if same_blob(be, oe) => {
329 let mut e = te.clone();
330 e.path = out_path.to_vec();
331 e.flags = path_len_flags(out_path);
332 index.entries.push(e);
333 }
334 (Some(be), Some(oe), Some(te)) if same_blob(be, te) => {
335 let mut e = oe.clone();
336 e.path = out_path.to_vec();
337 e.flags = path_len_flags(out_path);
338 index.entries.push(e);
339 }
340 (Some(be), Some(oe), Some(te))
341 if be.mode == 0o160000 && oe.mode == 0o160000 && te.mode == 0o160000 =>
342 {
343 if same_blob(oe, te) {
344 let mut e = oe.clone();
345 e.path = out_path.to_vec();
346 e.flags = path_len_flags(out_path);
347 index.entries.push(e);
348 } else if same_blob(be, oe) {
349 let mut e = te.clone();
350 e.path = out_path.to_vec();
351 e.flags = path_len_flags(out_path);
352 index.entries.push(e);
353 } else if same_blob(be, te) {
354 let mut e = oe.clone();
355 e.path = out_path.to_vec();
356 e.flags = path_len_flags(out_path);
357 index.entries.push(e);
358 } else {
359 stage_entry(index, out_path, be, 1);
360 stage_entry(index, out_path, oe, 2);
361 stage_entry(index, out_path, te, 3);
362 }
363 }
364 (Some(be), Some(oe), Some(te)) => {
365 content_merge_or_conflict(
366 repo,
367 index,
368 conflict_content,
369 out_path,
370 be,
371 oe,
372 te,
373 favor,
374 ws,
375 diff_algorithm,
376 presentation,
377 )?;
378 }
379 (None, Some(oe), None) => {
380 let mut e = oe.clone();
381 e.path = out_path.to_vec();
382 e.flags = path_len_flags(out_path);
383 index.entries.push(e);
384 }
385 (None, None, Some(te)) => {
386 let mut e = te.clone();
387 e.path = out_path.to_vec();
388 e.flags = path_len_flags(out_path);
389 index.entries.push(e);
390 }
391 (None, Some(oe), Some(te)) if same_blob(oe, te) => {
392 let mut e = oe.clone();
393 e.path = out_path.to_vec();
394 e.flags = path_len_flags(out_path);
395 index.entries.push(e);
396 }
397 (None, Some(oe), Some(te)) => {
398 add_add_content_conflict(
404 repo,
405 index,
406 conflict_content,
407 out_path,
408 oe,
409 te,
410 favor,
411 ws,
412 diff_algorithm,
413 presentation,
414 )?;
415 }
416 (Some(_), None, None) => {}
417 (Some(be), Some(oe), None) if same_blob(be, oe) => {}
418 (Some(be), None, Some(te)) if same_blob(be, te) => {}
419 (Some(be), Some(oe), None) => {
420 stage_entry(index, out_path, be, 1);
421 stage_entry(index, out_path, oe, 2);
422 }
423 (Some(be), None, Some(te)) => {
424 stage_entry(index, out_path, be, 1);
425 stage_entry(index, out_path, te, 3);
426 }
427 (None, None, None) => {}
428 }
429 Ok(())
430}
431
432fn path_len_flags(path: &[u8]) -> u16 {
433 path.len().min(0xFFF) as u16
434}
435
436fn same_blob(a: &IndexEntry, b: &IndexEntry) -> bool {
437 a.oid == b.oid && a.mode == b.mode
438}
439
440fn stage_entry(index: &mut Index, path: &[u8], src: &IndexEntry, stage: u8) {
441 let mut e = src.clone();
442 e.path = path.to_vec();
443 e.flags = path_len_flags(path) | ((stage as u16) << 12);
444 index.entries.push(e);
445}
446
447fn content_merge_or_conflict(
448 repo: &Repository,
449 index: &mut Index,
450 conflict_content: &mut BTreeMap<Vec<u8>, ObjectId>,
451 path: &[u8],
452 base: &IndexEntry,
453 ours: &IndexEntry,
454 theirs: &IndexEntry,
455 favor: MergeFavor,
456 ws: WhitespaceMergeOptions,
457 diff_algorithm: Option<&str>,
458 presentation: TreeMergeConflictPresentation<'_>,
459) -> crate::error::Result<()> {
460 if base.mode == 0o160000 || ours.mode == 0o160000 || theirs.mode == 0o160000 {
461 stage_entry(index, path, base, 1);
462 stage_entry(index, path, ours, 2);
463 stage_entry(index, path, theirs, 3);
464 return Ok(());
465 }
466
467 let base_obj = repo.odb.read(&base.oid)?;
473 let ours_obj = repo.odb.read(&ours.oid)?;
474 let theirs_obj = repo.odb.read(&theirs.oid)?;
475
476 if crate::merge_file::is_binary(&base_obj.data)
477 || crate::merge_file::is_binary(&ours_obj.data)
478 || crate::merge_file::is_binary(&theirs_obj.data)
479 {
480 match favor {
481 MergeFavor::Theirs => {
482 let mut e = theirs.clone();
483 e.path = path.to_vec();
484 e.flags = path_len_flags(path);
485 index.entries.push(e);
486 return Ok(());
487 }
488 MergeFavor::Ours => {
489 let mut e = ours.clone();
490 e.path = path.to_vec();
491 e.flags = path_len_flags(path);
492 index.entries.push(e);
493 return Ok(());
494 }
495 _ => {
496 stage_entry(index, path, base, 1);
497 stage_entry(index, path, ours, 2);
498 stage_entry(index, path, theirs, 3);
499 return Ok(());
500 }
501 }
502 }
503
504 let path_label = String::from_utf8_lossy(path);
505 let label_theirs: std::borrow::Cow<'_, str> = match presentation.label_theirs {
506 TheirsConflictLabel::PathUtf8 => path_label.clone(),
507 TheirsConflictLabel::Fixed(s) => std::borrow::Cow::Borrowed(s),
508 };
509 let input = MergeInput {
510 base: &base_obj.data,
511 ours: &ours_obj.data,
512 theirs: &theirs_obj.data,
513 label_ours: presentation.label_ours,
514 label_base: presentation.label_base,
515 label_theirs: label_theirs.as_ref(),
516 favor,
517 style: presentation.style,
518 marker_size: 7,
519 diff_algorithm: diff_algorithm.map(str::to_owned),
520 ignore_all_space: ws.ignore_all_space,
521 ignore_space_change: ws.ignore_space_change,
522 ignore_space_at_eol: ws.ignore_space_at_eol,
523 ignore_cr_at_eol: ws.ignore_cr_at_eol,
524 };
525
526 let result = merge(&input)?;
527
528 if result.conflicts > 0 {
529 let conflict_oid = repo.odb.write(ObjectKind::Blob, &result.content)?;
530 conflict_content.insert(path.to_vec(), conflict_oid);
531 stage_entry(index, path, base, 1);
532 stage_entry(index, path, ours, 2);
533 stage_entry(index, path, theirs, 3);
534 } else {
535 let merged_oid = repo.odb.write(ObjectKind::Blob, &result.content)?;
536 let mut entry = ours.clone();
537 entry.path = path.to_vec();
538 entry.flags = path_len_flags(path);
539 entry.oid = merged_oid;
540 if base.mode == ours.mode && base.mode != theirs.mode {
541 entry.mode = theirs.mode;
542 }
543 index.entries.push(entry);
544 }
545
546 Ok(())
547}
548
549#[allow(clippy::too_many_arguments)]
557fn add_add_content_conflict(
558 repo: &Repository,
559 index: &mut Index,
560 conflict_content: &mut BTreeMap<Vec<u8>, ObjectId>,
561 path: &[u8],
562 ours: &IndexEntry,
563 theirs: &IndexEntry,
564 favor: MergeFavor,
565 ws: WhitespaceMergeOptions,
566 diff_algorithm: Option<&str>,
567 presentation: TreeMergeConflictPresentation<'_>,
568) -> crate::error::Result<()> {
569 if ours.mode == 0o160000 || theirs.mode == 0o160000 {
571 stage_entry(index, path, ours, 2);
572 stage_entry(index, path, theirs, 3);
573 return Ok(());
574 }
575
576 let ours_obj = repo.odb.read(&ours.oid)?;
577 let theirs_obj = repo.odb.read(&theirs.oid)?;
578
579 if crate::merge_file::is_binary(&ours_obj.data)
581 || crate::merge_file::is_binary(&theirs_obj.data)
582 {
583 match favor {
584 MergeFavor::Theirs => {
585 let mut e = theirs.clone();
586 e.path = path.to_vec();
587 e.flags = path_len_flags(path);
588 index.entries.push(e);
589 }
590 MergeFavor::Ours => {
591 let mut e = ours.clone();
592 e.path = path.to_vec();
593 e.flags = path_len_flags(path);
594 index.entries.push(e);
595 }
596 _ => {
597 stage_entry(index, path, ours, 2);
598 stage_entry(index, path, theirs, 3);
599 }
600 }
601 return Ok(());
602 }
603
604 let path_label = String::from_utf8_lossy(path);
605 let label_theirs: std::borrow::Cow<'_, str> = match presentation.label_theirs {
606 TheirsConflictLabel::PathUtf8 => path_label.clone(),
607 TheirsConflictLabel::Fixed(s) => std::borrow::Cow::Borrowed(s),
608 };
609 let input = MergeInput {
610 base: b"",
611 ours: &ours_obj.data,
612 theirs: &theirs_obj.data,
613 label_ours: presentation.label_ours,
614 label_base: presentation.label_base,
615 label_theirs: label_theirs.as_ref(),
616 favor,
617 style: presentation.style,
618 marker_size: 7,
619 diff_algorithm: diff_algorithm.map(str::to_owned),
620 ignore_all_space: ws.ignore_all_space,
621 ignore_space_change: ws.ignore_space_change,
622 ignore_space_at_eol: ws.ignore_space_at_eol,
623 ignore_cr_at_eol: ws.ignore_cr_at_eol,
624 };
625
626 let result = merge(&input)?;
627
628 let both_regular_files =
635 matches!(ours.mode, 0o100644 | 0o100755) && matches!(theirs.mode, 0o100644 | 0o100755);
636 let mode_conflict = both_regular_files && ours.mode != theirs.mode && favor == MergeFavor::None;
637
638 if result.conflicts > 0 || mode_conflict {
639 let conflict_oid = repo.odb.write(ObjectKind::Blob, &result.content)?;
640 conflict_content.insert(path.to_vec(), conflict_oid);
641 stage_entry(index, path, ours, 2);
642 stage_entry(index, path, theirs, 3);
643 } else {
644 let merged_oid = repo.odb.write(ObjectKind::Blob, &result.content)?;
647 let mut entry = ours.clone();
648 entry.path = path.to_vec();
649 entry.flags = path_len_flags(path);
650 entry.oid = merged_oid;
651 index.entries.push(entry);
652 }
653
654 Ok(())
655}
656
657fn tree_to_index_entries(
658 repo: &Repository,
659 oid: &ObjectId,
660 prefix: &str,
661) -> crate::error::Result<Vec<IndexEntry>> {
662 let obj = repo.odb.read(oid)?;
663 if obj.kind != ObjectKind::Tree {
664 return Err(crate::error::Error::CorruptObject(format!(
665 "expected tree, got {}",
666 obj.kind.as_str()
667 )));
668 }
669 let entries = parse_tree(&obj.data)?;
670 let mut result = Vec::new();
671
672 for te in entries {
673 let name = String::from_utf8_lossy(&te.name).into_owned();
674 let path = if prefix.is_empty() {
675 name.clone()
676 } else {
677 format!("{prefix}/{name}")
678 };
679
680 if te.mode == 0o040000 {
681 let sub = tree_to_index_entries(repo, &te.oid, &path)?;
682 result.extend(sub);
683 } else {
684 let path_bytes = path.into_bytes();
685 result.push(IndexEntry {
686 ctime_sec: 0,
687 ctime_nsec: 0,
688 mtime_sec: 0,
689 mtime_nsec: 0,
690 dev: 0,
691 ino: 0,
692 mode: te.mode,
693 uid: 0,
694 gid: 0,
695 size: 0,
696 oid: te.oid,
697 flags: path_bytes.len().min(0xFFF) as u16,
698 flags_extended: None,
699 path: path_bytes,
700 base_index_pos: 0,
701 });
702 }
703 }
704 Ok(result)
705}
706
707fn tree_to_map(entries: Vec<IndexEntry>) -> HashMap<Vec<u8>, IndexEntry> {
708 let mut out = HashMap::new();
709 for e in entries {
710 out.insert(e.path.clone(), e);
711 }
712 out
713}
714
715#[must_use]
717pub fn index_tree_oid_matches_head(
718 odb: &Odb,
719 index: &Index,
720 head_tree_oid: &ObjectId,
721) -> crate::error::Result<bool> {
722 let merged = write_tree_from_index(odb, index, "")?;
723 Ok(merged == *head_tree_oid)
724}