Rollup merge of #150443 - estebank:long-diff-markers, r=jackh726

Support long diff conflict markers

git can be configured to use more than 7 characters for conflict markers, and jj automatically uses longer conflict markers when the text contains any char sequence that could be confused with conflict markers. Ensure that we only point at markers that are consistent with the start marker's length.

Ensure that we only consider char sequences at the beginning of a line as a diff marker.

Fix https://github.com/rust-lang/rust/issues/150352.
This commit is contained in:
Stuart Cook 2026-02-08 16:58:23 +11:00 committed by GitHub
commit 55277add61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 74 additions and 11 deletions

View file

@ -3023,27 +3023,53 @@ impl<'a> Parser<'a> {
long_kind: &TokenKind,
short_kind: &TokenKind,
) -> bool {
(0..3).all(|i| self.look_ahead(i, |tok| tok == long_kind))
&& self.look_ahead(3, |tok| tok == short_kind)
if long_kind == short_kind {
// For conflict marker chars like `%` and `\`.
(0..7).all(|i| self.look_ahead(i, |tok| tok == long_kind))
} else {
// For conflict marker chars like `<` and `|`.
(0..3).all(|i| self.look_ahead(i, |tok| tok == long_kind))
&& self.look_ahead(3, |tok| tok == short_kind || tok == long_kind)
}
}
fn conflict_marker(&mut self, long_kind: &TokenKind, short_kind: &TokenKind) -> Option<Span> {
fn conflict_marker(
&mut self,
long_kind: &TokenKind,
short_kind: &TokenKind,
expected: Option<usize>,
) -> Option<(Span, usize)> {
if self.is_vcs_conflict_marker(long_kind, short_kind) {
let lo = self.token.span;
for _ in 0..4 {
self.bump();
if self.psess.source_map().span_to_margin(lo) != Some(0) {
return None;
}
return Some(lo.to(self.prev_token.span));
let mut len = 0;
while self.token.kind == *long_kind || self.token.kind == *short_kind {
if self.token.kind.break_two_token_op(1).is_some() {
len += 2;
} else {
len += 1;
}
self.bump();
if expected == Some(len) {
break;
}
}
if expected.is_some() && expected != Some(len) {
return None;
}
return Some((lo.to(self.prev_token.span), len));
}
None
}
pub(super) fn recover_vcs_conflict_marker(&mut self) {
// <<<<<<<
let Some(start) = self.conflict_marker(&TokenKind::Shl, &TokenKind::Lt) else {
let Some((start, len)) = self.conflict_marker(&TokenKind::Shl, &TokenKind::Lt, None) else {
return;
};
let mut spans = Vec::with_capacity(3);
let mut spans = Vec::with_capacity(2);
spans.push(start);
// |||||||
let mut middlediff3 = None;
@ -3055,13 +3081,19 @@ impl<'a> Parser<'a> {
if self.token == TokenKind::Eof {
break;
}
if let Some(span) = self.conflict_marker(&TokenKind::OrOr, &TokenKind::Or) {
if let Some((span, _)) =
self.conflict_marker(&TokenKind::OrOr, &TokenKind::Or, Some(len))
{
middlediff3 = Some(span);
}
if let Some(span) = self.conflict_marker(&TokenKind::EqEq, &TokenKind::Eq) {
if let Some((span, _)) =
self.conflict_marker(&TokenKind::EqEq, &TokenKind::Eq, Some(len))
{
middle = Some(span);
}
if let Some(span) = self.conflict_marker(&TokenKind::Shr, &TokenKind::Gt) {
if let Some((span, _)) =
self.conflict_marker(&TokenKind::Shr, &TokenKind::Gt, Some(len))
{
spans.push(span);
end = Some(span);
break;

View file

@ -0,0 +1,11 @@
enum E {
Foo {
<<<<<<<<< HEAD //~ ERROR encountered diff marker
x: u8,
|||||||
z: (),
=========
y: i8,
>>>>>>>>> branch
}
}

View file

@ -0,0 +1,20 @@
error: encountered diff marker
--> $DIR/long-conflict-markers.rs:3:1
|
LL | <<<<<<<<< HEAD
| ^^^^^^^^^ between this marker and `=======` is the code that you are merging into
...
LL | =========
| --------- between this marker and `>>>>>>>` is the incoming code
LL | y: i8,
LL | >>>>>>>>> branch
| ^^^^^^^^^ this marker concludes the conflict region
|
= note: conflict markers indicate that a merge was started but could not be completed due to merge conflicts
to resolve a conflict, keep only the code you want and then delete the lines containing conflict markers
= help: if you are in a merge, the top section is the code you already had checked out and the bottom section is the new code
if you are in a rebase, the top section is the code being rebased onto and the bottom section is the code you had checked out which is being rebased
= note: for an explanation on these markers from the `git` documentation, visit <https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_checking_out_conflicts>
error: aborting due to 1 previous error