Skip to content

Commit 86efa21

Browse files
dschogitster
authored andcommitted
merge-file: let conflict markers match end-of-line style of the context
When merging files with CR/LF line endings, the conflict markers should match those, lest the output file has mixed line endings. This is particularly of interest on Windows, where some editors get *really* confused by mixed line endings. The original version of this patch by Beat Bolli respected core.eol, and a subsequent improvement by this developer also respected gitattributes. This approach was suboptimal, though: `git merge-file` was invented as a drop-in replacement for GNU merge and as such has no problem operating outside of any repository at all! Another problem with the original approach was pointed out by Junio Hamano: legacy repositories might have their text files committed using CR/LF line endings (and core.eol and the gitattributes would give us a false impression there). Therefore, the much superior approach is to simply match the context's line endings, if any. We actually do not have to look at the *entire* context at all: if the files are all LF-only, or if they all have CR/LF line endings, it is sufficient to look at just a *single* line to match that style. And if the line endings are mixed anyway, it is *still* okay to imitate just a single line's eol: we will just add to the pile of mixed line endings, and there is nothing we can do about that. So what we do is: we look at the line preceding the conflict, falling back to the line preceding that in case it was the last line and had no line ending, falling back to the first line, first in the first post-image, then the second post-image, and finally the pre-image. If we find consistent CR/LF (or undecided) end-of-line style, we match that, otherwise we use LF-only line endings for the conflict markers. Note that while it is true that there have to be at least two lines we can look at (otherwise there would be no conflict), the same is not true for line *endings*: the three files in question could all consist of a single line without any line ending, each. In this case we fall back to using LF-only. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 833e482 commit 86efa21

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed

t/t6023-merge-file.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,4 +346,16 @@ test_expect_success 'conflict at EOF without LF resolved by --union' \
346346
printf "line1\nline2\nline3x\nline3y" >expect.txt &&
347347
test_cmp expect.txt output.txt'
348348

349+
test_expect_success 'conflict markers match existing line endings' '
350+
printf "1\\r\\n2\\r\\n3" >crlf-orig.txt &&
351+
printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt &&
352+
printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt &&
353+
test_must_fail git -c core.eol=crlf merge-file -p \
354+
crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt &&
355+
test $(tr "\015" Q <crlf.txt | grep "^[<=>].*Q$" | wc -l) = 3 &&
356+
test_must_fail git -c core.eol=crlf merge-file -p \
357+
nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >nolf.txt &&
358+
test $(tr "\015" Q <nolf.txt | grep "^[<=>].*Q$" | wc -l) = 0
359+
'
360+
349361
test_done

xdiff/xmerge.c

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,50 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
143143
return xdl_recs_copy_0(1, xe, i, count, add_nl, dest);
144144
}
145145

146+
/*
147+
* Returns 1 if the i'th line ends in CR/LF (if it is the last line and
148+
* has no eol, the preceding line, if any), 0 if it ends in LF-only, and
149+
* -1 if the line ending cannot be determined.
150+
*/
151+
static int is_eol_crlf(xdfile_t *file, int i)
152+
{
153+
long size;
154+
155+
if (i < file->nrec - 1)
156+
/* All lines before the last *must* end in LF */
157+
return (size = file->recs[i]->size) > 1 &&
158+
file->recs[i]->ptr[size - 2] == '\r';
159+
if (!file->nrec)
160+
/* Cannot determine eol style from empty file */
161+
return -1;
162+
if ((size = file->recs[i]->size) &&
163+
file->recs[i]->ptr[size - 1] == '\n')
164+
/* Last line; ends in LF; Is it CR/LF? */
165+
return size > 1 &&
166+
file->recs[i]->ptr[size - 2] == '\r';
167+
if (!i)
168+
/* The only line has no eol */
169+
return -1;
170+
/* Determine eol from second-to-last line */
171+
return (size = file->recs[i - 1]->size) > 1 &&
172+
file->recs[i - 1]->ptr[size - 2] == '\r';
173+
}
174+
175+
static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m)
176+
{
177+
int needs_cr;
178+
179+
/* Match post-images' preceding, or first, lines' end-of-line style */
180+
needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0);
181+
if (needs_cr)
182+
needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0);
183+
/* Look at pre-image's first line, unless we already settled on LF */
184+
if (needs_cr)
185+
needs_cr = is_eol_crlf(&xe1->xdf1, 0);
186+
/* If still undecided, use LF-only */
187+
return needs_cr < 0 ? 0 : needs_cr;
188+
}
189+
146190
static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
147191
xdfenv_t *xe2, const char *name2,
148192
const char *name3,
@@ -152,6 +196,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
152196
int marker1_size = (name1 ? strlen(name1) + 1 : 0);
153197
int marker2_size = (name2 ? strlen(name2) + 1 : 0);
154198
int marker3_size = (name3 ? strlen(name3) + 1 : 0);
199+
int needs_cr = is_cr_needed(xe1, xe2, m);
155200

156201
if (marker_size <= 0)
157202
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@@ -161,7 +206,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
161206
dest ? dest + size : NULL);
162207

163208
if (!dest) {
164-
size += marker_size + 1 + marker1_size;
209+
size += marker_size + 1 + needs_cr + marker1_size;
165210
} else {
166211
memset(dest + size, '<', marker_size);
167212
size += marker_size;
@@ -170,6 +215,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
170215
memcpy(dest + size + 1, name1, marker1_size - 1);
171216
size += marker1_size;
172217
}
218+
if (needs_cr)
219+
dest[size++] = '\r';
173220
dest[size++] = '\n';
174221
}
175222

@@ -180,7 +227,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
180227
if (style == XDL_MERGE_DIFF3) {
181228
/* Shared preimage */
182229
if (!dest) {
183-
size += marker_size + 1 + marker3_size;
230+
size += marker_size + 1 + needs_cr + marker3_size;
184231
} else {
185232
memset(dest + size, '|', marker_size);
186233
size += marker_size;
@@ -189,25 +236,29 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
189236
memcpy(dest + size + 1, name3, marker3_size - 1);
190237
size += marker3_size;
191238
}
239+
if (needs_cr)
240+
dest[size++] = '\r';
192241
dest[size++] = '\n';
193242
}
194243
size += xdl_orig_copy(xe1, m->i0, m->chg0, 1,
195244
dest ? dest + size : NULL);
196245
}
197246

198247
if (!dest) {
199-
size += marker_size + 1;
248+
size += marker_size + 1 + needs_cr;
200249
} else {
201250
memset(dest + size, '=', marker_size);
202251
size += marker_size;
252+
if (needs_cr)
253+
dest[size++] = '\r';
203254
dest[size++] = '\n';
204255
}
205256

206257
/* Postimage from side #2 */
207258
size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
208259
dest ? dest + size : NULL);
209260
if (!dest) {
210-
size += marker_size + 1 + marker2_size;
261+
size += marker_size + 1 + needs_cr + marker2_size;
211262
} else {
212263
memset(dest + size, '>', marker_size);
213264
size += marker_size;
@@ -216,6 +267,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
216267
memcpy(dest + size + 1, name2, marker2_size - 1);
217268
size += marker2_size;
218269
}
270+
if (needs_cr)
271+
dest[size++] = '\r';
219272
dest[size++] = '\n';
220273
}
221274
return size;

0 commit comments

Comments
 (0)