From 86efa215276aaeaef0c556c73f1993b6f36dcf91 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 27 Jan 2016 17:37:36 +0100 Subject: [PATCH 1/2] 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 Signed-off-by: Junio C Hamano --- t/t6023-merge-file.sh | 12 +++++++++ xdiff/xmerge.c | 61 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 190ee903cf..a082116501 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -346,4 +346,16 @@ test_expect_success 'conflict at EOF without LF resolved by --union' \ printf "line1\nline2\nline3x\nline3y" >expect.txt && test_cmp expect.txt output.txt' +test_expect_success 'conflict markers match existing line endings' ' + printf "1\\r\\n2\\r\\n3" >crlf-orig.txt && + printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt && + printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt && + test_must_fail git -c core.eol=crlf merge-file -p \ + crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt && + test $(tr "\015" Q ].*Q$" | wc -l) = 3 && + test_must_fail git -c core.eol=crlf merge-file -p \ + nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >nolf.txt && + test $(tr "\015" Q ].*Q$" | wc -l) = 0 +' + test_done diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 625198e058..7b21a6b2cf 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -143,6 +143,50 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); } +/* + * Returns 1 if the i'th line ends in CR/LF (if it is the last line and + * has no eol, the preceding line, if any), 0 if it ends in LF-only, and + * -1 if the line ending cannot be determined. + */ +static int is_eol_crlf(xdfile_t *file, int i) +{ + long size; + + if (i < file->nrec - 1) + /* All lines before the last *must* end in LF */ + return (size = file->recs[i]->size) > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!file->nrec) + /* Cannot determine eol style from empty file */ + return -1; + if ((size = file->recs[i]->size) && + file->recs[i]->ptr[size - 1] == '\n') + /* Last line; ends in LF; Is it CR/LF? */ + return size > 1 && + file->recs[i]->ptr[size - 2] == '\r'; + if (!i) + /* The only line has no eol */ + return -1; + /* Determine eol from second-to-last line */ + return (size = file->recs[i - 1]->size) > 1 && + file->recs[i - 1]->ptr[size - 2] == '\r'; +} + +static int is_cr_needed(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m) +{ + int needs_cr; + + /* Match post-images' preceding, or first, lines' end-of-line style */ + needs_cr = is_eol_crlf(&xe1->xdf2, m->i1 ? m->i1 - 1 : 0); + if (needs_cr) + needs_cr = is_eol_crlf(&xe2->xdf2, m->i2 ? m->i2 - 1 : 0); + /* Look at pre-image's first line, unless we already settled on LF */ + if (needs_cr) + needs_cr = is_eol_crlf(&xe1->xdf1, 0); + /* If still undecided, use LF-only */ + return needs_cr < 0 ? 0 : needs_cr; +} + static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, const char *name3, @@ -152,6 +196,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); int marker3_size = (name3 ? strlen(name3) + 1 : 0); + int needs_cr = is_cr_needed(xe1, xe2, m); if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; @@ -161,7 +206,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, dest ? dest + size : NULL); if (!dest) { - size += marker_size + 1 + marker1_size; + size += marker_size + 1 + needs_cr + marker1_size; } else { memset(dest + size, '<', marker_size); size += marker_size; @@ -170,6 +215,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name1, marker1_size - 1); size += marker1_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } @@ -180,7 +227,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, if (style == XDL_MERGE_DIFF3) { /* Shared preimage */ if (!dest) { - size += marker_size + 1 + marker3_size; + size += marker_size + 1 + needs_cr + marker3_size; } else { memset(dest + size, '|', marker_size); size += marker_size; @@ -189,6 +236,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name3, marker3_size - 1); size += marker3_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, @@ -196,10 +245,12 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } if (!dest) { - size += marker_size + 1; + size += marker_size + 1 + needs_cr; } else { memset(dest + size, '=', marker_size); size += marker_size; + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } @@ -207,7 +258,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, dest ? dest + size : NULL); if (!dest) { - size += marker_size + 1 + marker2_size; + size += marker_size + 1 + needs_cr + marker2_size; } else { memset(dest + size, '>', marker_size); size += marker_size; @@ -216,6 +267,8 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, memcpy(dest + size + 1, name2, marker2_size - 1); size += marker2_size; } + if (needs_cr) + dest[size++] = '\r'; dest[size++] = '\n'; } return size; From 15980deab9f41324aba5cc9536e463d204bafe63 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 27 Jan 2016 17:37:40 +0100 Subject: [PATCH 2/2] merge-file: ensure that conflict sections match eol style In the previous patch, we made sure that the conflict markers themselves match the end-of-line style of the input files. However, this still left out the conflicting text itself: if it lacks a trailing newline, we add one, and should add a carriage return when appropriate, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t6023-merge-file.sh | 3 ++- xdiff/xmerge.c | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index a082116501..20aee43f95 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -346,13 +346,14 @@ test_expect_success 'conflict at EOF without LF resolved by --union' \ printf "line1\nline2\nline3x\nline3y" >expect.txt && test_cmp expect.txt output.txt' -test_expect_success 'conflict markers match existing line endings' ' +test_expect_success 'conflict sections match existing line endings' ' printf "1\\r\\n2\\r\\n3" >crlf-orig.txt && printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt && printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt && test_must_fail git -c core.eol=crlf merge-file -p \ crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt && test $(tr "\015" Q ].*Q$" | wc -l) = 3 && + test $(tr "\015" Q nolf.txt && test $(tr "\015" Q ].*Q$" | wc -l) = 0 diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 7b21a6b2cf..d98f430c91 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -109,7 +109,7 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, return 0; } -static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { xrecord_t **recs; int size = 0; @@ -125,6 +125,12 @@ static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add if (add_nl) { i = recs[count - 1]->size; if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (needs_cr) { + if (dest) + dest[size] = '\r'; + size++; + } + if (dest) dest[size] = '\n'; size++; @@ -133,14 +139,14 @@ static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add return size; } -static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { - return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(0, xe, i, count, needs_cr, add_nl, dest); } -static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_nl, char *dest) { - return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); + return xdl_recs_copy_0(1, xe, i, count, needs_cr, add_nl, dest); } /* @@ -202,7 +208,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, marker_size = DEFAULT_CONFLICT_MARKER_SIZE; /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, dest ? dest + size : NULL); if (!dest) { @@ -221,7 +227,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } /* Postimage from side #1 */ - size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, 1, dest ? dest + size : NULL); if (style == XDL_MERGE_DIFF3) { @@ -240,7 +246,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, dest[size++] = '\r'; dest[size++] = '\n'; } - size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + size += xdl_orig_copy(xe1, m->i0, m->chg0, needs_cr, 1, dest ? dest + size : NULL); } @@ -255,7 +261,7 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } /* Postimage from side #2 */ - size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + size += xdl_recs_copy(xe2, m->i2, m->chg2, needs_cr, 1, dest ? dest + size : NULL); if (!dest) { size += marker_size + 1 + needs_cr + marker2_size; @@ -294,21 +300,24 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, marker_size); else if (m->mode & 3) { /* Before conflicting part */ - size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, 0, dest ? dest + size : NULL); /* Postimage from side #1 */ - if (m->mode & 1) - size += xdl_recs_copy(xe1, m->i1, m->chg1, (m->mode & 2), + if (m->mode & 1) { + int needs_cr = is_cr_needed(xe1, xe2, m); + + size += xdl_recs_copy(xe1, m->i1, m->chg1, needs_cr, (m->mode & 2), dest ? dest + size : NULL); + } /* Postimage from side #2 */ if (m->mode & 2) - size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, + size += xdl_recs_copy(xe2, m->i2, m->chg2, 0, 0, dest ? dest + size : NULL); } else continue; i = m->i1 + m->chg1; } - size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, dest ? dest + size : NULL); return size; }