@@ -115,6 +115,40 @@ pub enum ConflictStyle {
115115 Diff3 ,
116116}
117117
118+ /// Style used when rendering conflict markers after a hunk that ends in an
119+ /// incomplete line (one without a trailing newline).
120+ ///
121+ /// An incomplete line can only appear as the final line of a file, so this
122+ /// only affects conflicts that include the end of the file.
123+ #[ derive( Copy , Clone , Debug ) ]
124+ pub enum IncompleteHunkStyle {
125+ /// Appends conflict markers directly to the incomplete line, matching the
126+ /// behavior of GNU `diff3 -m`.
127+ ///
128+ /// ```console
129+ /// <<<<<<< ours
130+ /// ours line||||||| original
131+ /// original line=======
132+ /// theirs line>>>>>>> theirs
133+ /// ```
134+ Diff3 ,
135+
136+ /// Inserts a newline after the incomplete line so that every conflict
137+ /// marker starts at the beginning of a line, matching the behavior of
138+ /// `git merge-file`.
139+ ///
140+ /// ```console
141+ /// <<<<<<< ours
142+ /// ours line
143+ /// ||||||| original
144+ /// original line
145+ /// =======
146+ /// theirs line
147+ /// >>>>>>> theirs
148+ /// ```
149+ Git ,
150+ }
151+
118152/// A collection of options for modifying the way a merge is performed
119153///
120154/// # Examples
@@ -145,6 +179,7 @@ pub enum ConflictStyle {
145179pub struct MergeOptions {
146180 conflict_marker_length : usize ,
147181 style : ConflictStyle ,
182+ incomplete_hunk_style : IncompleteHunkStyle ,
148183}
149184
150185impl MergeOptions {
@@ -153,10 +188,12 @@ impl MergeOptions {
153188 /// ## Defaults
154189 /// * conflict_marker_length = 7
155190 /// * style = ConflictStyle::Diff3
191+ /// * incomplete_hunk_style = IncompleteHunkStyle::Diff3
156192 pub fn new ( ) -> Self {
157193 Self {
158194 conflict_marker_length : DEFAULT_CONFLICT_MARKER_LENGTH ,
159195 style : ConflictStyle :: Diff3 ,
196+ incomplete_hunk_style : IncompleteHunkStyle :: Diff3 ,
160197 }
161198 }
162199
@@ -172,6 +209,13 @@ impl MergeOptions {
172209 self
173210 }
174211
212+ /// Set the style used when rendering conflict markers after a hunk that
213+ /// ends in an incomplete line
214+ pub fn set_incomplete_hunk_style ( & mut self , style : IncompleteHunkStyle ) -> & mut Self {
215+ self . incomplete_hunk_style = style;
216+ self
217+ }
218+
175219 /// Merge two files, given a common ancestor, based on the configured options
176220 pub fn merge < ' a > (
177221 & self ,
@@ -200,6 +244,7 @@ impl MergeOptions {
200244 & merge,
201245 self . conflict_marker_length ,
202246 self . style ,
247+ self . incomplete_hunk_style ,
203248 )
204249 }
205250
@@ -231,6 +276,7 @@ impl MergeOptions {
231276 & merge,
232277 self . conflict_marker_length ,
233278 self . style ,
279+ self . incomplete_hunk_style ,
234280 )
235281 }
236282}
@@ -556,6 +602,7 @@ fn output_result<'a, T: ?Sized>(
556602 merge : & [ MergeRange < T > ] ,
557603 marker_len : usize ,
558604 style : ConflictStyle ,
605+ incomplete_hunk_style : IncompleteHunkStyle ,
559606) -> Result < String , String > {
560607 let mut conflicts = 0 ;
561608 let mut output = String :: new ( ) ;
@@ -566,17 +613,35 @@ fn output_result<'a, T: ?Sized>(
566613 output. extend ( ancestor[ range. range ( ) ] . iter ( ) . copied ( ) ) ;
567614 }
568615 MergeRange :: Conflict ( ancestor_range, ours_range, theirs_range) => {
569- add_conflict_marker ( & mut output, '<' , marker_len, Some ( "ours" ) ) ;
616+ add_conflict_marker (
617+ & mut output,
618+ '<' ,
619+ marker_len,
620+ Some ( "ours" ) ,
621+ incomplete_hunk_style,
622+ ) ;
570623 output. extend ( ours[ ours_range. range ( ) ] . iter ( ) . copied ( ) ) ;
571624
572625 if let ConflictStyle :: Diff3 = style {
573- add_conflict_marker ( & mut output, '|' , marker_len, Some ( "original" ) ) ;
626+ add_conflict_marker (
627+ & mut output,
628+ '|' ,
629+ marker_len,
630+ Some ( "original" ) ,
631+ incomplete_hunk_style,
632+ ) ;
574633 output. extend ( ancestor[ ancestor_range. range ( ) ] . iter ( ) . copied ( ) ) ;
575634 }
576635
577- add_conflict_marker ( & mut output, '=' , marker_len, None ) ;
636+ add_conflict_marker ( & mut output, '=' , marker_len, None , incomplete_hunk_style ) ;
578637 output. extend ( theirs[ theirs_range. range ( ) ] . iter ( ) . copied ( ) ) ;
579- add_conflict_marker ( & mut output, '>' , marker_len, Some ( "theirs" ) ) ;
638+ add_conflict_marker (
639+ & mut output,
640+ '>' ,
641+ marker_len,
642+ Some ( "theirs" ) ,
643+ incomplete_hunk_style,
644+ ) ;
580645 conflicts += 1 ;
581646 }
582647 MergeRange :: Ours ( range) => {
@@ -603,8 +668,12 @@ fn add_conflict_marker(
603668 marker : char ,
604669 marker_len : usize ,
605670 filename : Option < & str > ,
671+ incomplete_hunk_style : IncompleteHunkStyle ,
606672) {
607- if !output. is_empty ( ) && !output. ends_with ( '\n' ) {
673+ if matches ! ( incomplete_hunk_style, IncompleteHunkStyle :: Git )
674+ && !output. is_empty ( )
675+ && !output. ends_with ( '\n' )
676+ {
608677 output. push ( '\n' ) ;
609678 }
610679 for _ in 0 ..marker_len {
@@ -625,6 +694,7 @@ fn output_result_bytes<'a, T: ?Sized>(
625694 merge : & [ MergeRange < T > ] ,
626695 marker_len : usize ,
627696 style : ConflictStyle ,
697+ incomplete_hunk_style : IncompleteHunkStyle ,
628698) -> Result < Vec < u8 > , Vec < u8 > > {
629699 let mut conflicts = 0 ;
630700 let mut output: Vec < u8 > = Vec :: new ( ) ;
@@ -637,23 +707,47 @@ fn output_result_bytes<'a, T: ?Sized>(
637707 . for_each ( |line| output. extend_from_slice ( line) ) ;
638708 }
639709 MergeRange :: Conflict ( ancestor_range, ours_range, theirs_range) => {
640- add_conflict_marker_bytes ( & mut output, b'<' , marker_len, Some ( b"ours" ) ) ;
710+ add_conflict_marker_bytes (
711+ & mut output,
712+ b'<' ,
713+ marker_len,
714+ Some ( b"ours" ) ,
715+ incomplete_hunk_style,
716+ ) ;
641717 ours[ ours_range. range ( ) ]
642718 . iter ( )
643719 . for_each ( |line| output. extend_from_slice ( line) ) ;
644720
645721 if let ConflictStyle :: Diff3 = style {
646- add_conflict_marker_bytes ( & mut output, b'|' , marker_len, Some ( b"original" ) ) ;
722+ add_conflict_marker_bytes (
723+ & mut output,
724+ b'|' ,
725+ marker_len,
726+ Some ( b"original" ) ,
727+ incomplete_hunk_style,
728+ ) ;
647729 ancestor[ ancestor_range. range ( ) ]
648730 . iter ( )
649731 . for_each ( |line| output. extend_from_slice ( line) ) ;
650732 }
651733
652- add_conflict_marker_bytes ( & mut output, b'=' , marker_len, None ) ;
734+ add_conflict_marker_bytes (
735+ & mut output,
736+ b'=' ,
737+ marker_len,
738+ None ,
739+ incomplete_hunk_style,
740+ ) ;
653741 theirs[ theirs_range. range ( ) ]
654742 . iter ( )
655743 . for_each ( |line| output. extend_from_slice ( line) ) ;
656- add_conflict_marker_bytes ( & mut output, b'>' , marker_len, Some ( b"theirs" ) ) ;
744+ add_conflict_marker_bytes (
745+ & mut output,
746+ b'>' ,
747+ marker_len,
748+ Some ( b"theirs" ) ,
749+ incomplete_hunk_style,
750+ ) ;
657751 conflicts += 1 ;
658752 }
659753 MergeRange :: Ours ( range) => {
@@ -686,8 +780,12 @@ fn add_conflict_marker_bytes(
686780 marker : u8 ,
687781 marker_len : usize ,
688782 filename : Option < & [ u8 ] > ,
783+ incomplete_hunk_style : IncompleteHunkStyle ,
689784) {
690- if !output. is_empty ( ) && output. last ( ) != Some ( & b'\n' ) {
785+ if matches ! ( incomplete_hunk_style, IncompleteHunkStyle :: Git )
786+ && !output. is_empty ( )
787+ && output. last ( ) != Some ( & b'\n' )
788+ {
691789 output. push ( b'\n' ) ;
692790 }
693791 for _ in 0 ..marker_len {
0 commit comments