diff --git a/src/git/merging.rs b/src/git/merging.rs index 65d956f..9ff61cd 100644 --- a/src/git/merging.rs +++ b/src/git/merging.rs @@ -161,6 +161,42 @@ pub fn conflicts(text: &str) -> Vec { found } +/// Regions as Git grouped them in marked `text`; base from diff3 sections. +pub fn marked(text: &str) -> Vec { + let lines: Vec = text + .tokenize_lines() + .into_iter() + .map(str::to_string) + .collect(); + let same = |range: std::ops::Range| Region { + kind: RegionKind::Unchanged, + base: lines[range.clone()].to_vec(), + ours: lines[range.clone()].to_vec(), + theirs: lines[range].to_vec(), + }; + let mut out = Vec::new(); + let mut at = 0; + for conflict in conflicts(text) { + if conflict.start > at { + out.push(same(at..conflict.start)); + } + let ours_end = conflict.base.unwrap_or(conflict.middle); + out.push(Region { + kind: RegionKind::Conflict, + ours: lines[conflict.start + 1..ours_end].to_vec(), + base: conflict + .base + .map_or_else(Vec::new, |base| lines[base + 1..conflict.middle].to_vec()), + theirs: lines[conflict.middle + 1..conflict.end].to_vec(), + }); + at = conflict.end + 1; + } + if at < lines.len() { + out.push(same(at..lines.len())); + } + out +} + /// `text` with conflict `ix` settled; none if absent. pub fn resolve(text: &str, ix: usize, take: Take) -> Option { let Some(conflict) = conflicts(text).get(ix).copied() else { @@ -191,6 +227,25 @@ pub fn resolve(text: &str, ix: usize, take: Take) -> Option { mod tests { use super::*; + #[test] + fn marked_text_keeps_gits_grouping_and_its_base() { + let text = "first\n<<<<<<< ours\nOURS a\nunchanged\nOURS b\n||||||| base\na\nunchanged\nb\n=======\nTHEIRS a\nunchanged\nTHEIRS b\n>>>>>>> theirs\nlast\n"; + let found = marked(text); + let kinds: Vec = found.iter().map(|region| region.kind).collect(); + assert_eq!( + kinds, + [ + RegionKind::Unchanged, + RegionKind::Conflict, + RegionKind::Unchanged + ], + "one block, as Git wrote it" + ); + assert_eq!(found[1].base, ["a\n", "unchanged\n", "b\n"]); + assert_eq!(found[1].ours.len(), 3); + assert_eq!(found[2].ours, ["last\n"]); + } + #[test] fn a_diff3_conflict_leaves_its_base_out() { let text = "a\n<<<<<<< HEAD\nours\n||||||| base\nold\n=======\ntheirs\n>>>>>>> side\nz\n"; diff --git a/src/git/mod.rs b/src/git/mod.rs index 1e86e38..9fc1339 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -25,7 +25,9 @@ pub use diff::{DiffLine, LineKind, Stretch, diff, mark_words, pairs, stat}; pub use graph::{GraphRow, Half, Stroke, lanes}; pub use history::FileHistory; pub use merge::{ConflictResolver, ThreeWayMerge}; -pub use merging::{Conflict, Region, RegionKind, Take, conflicts, regions, resolve, result}; +pub use merging::{ + Conflict, Region, RegionKind, Take, conflicts, marked, regions, resolve, result, +}; pub use refs::{Branch, BranchList, BranchSelector}; pub use review::{PullRequest, PullRequestCard, PullState, ReviewComment, ReviewNote}; pub use rows::Place;