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