diff: --ignore-cr-at-eol

A new option --ignore-cr-at-eol tells the diff machinery to treat a carriage-return at the end of a (complete) line as if it does not exist. Just like other "--ignore-*" options to ignore various kinds of whitespace differences, this will help reviewing the real changes you made without getting distracted by spurious CRLF<->LF conversion made by your editor program. Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> [jch: squashed in command line completion by Dscho] Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Oct 26, 2017 at 15:32 UTC e9282f02b2f21118f3383608718e38efc3d967e1
8 files changed +78 -6
Documentation/diff-options.txt
+3
@@ -519,6 +519,9 @@ endif::git-format-patch[]
519 --text::
520 Treat all files as text.
521
522 +--ignore-cr-at-eol::
523 + Ignore carrige-return at the end of line when doing a comparison.
524 +
525 --ignore-space-at-eol::
526 Ignore changes in whitespace at EOL.
527
Documentation/merge-strategies.txt
+3 -2
@@ -57,11 +57,12 @@ diff-algorithm=[patience|minimal|histogram|myers];;
57 ignore-space-change;;
58 ignore-all-space;;
59 ignore-space-at-eol;;
60 +ignore-cr-at-eol;;
61 Treats lines with the indicated type of whitespace change as
62 unchanged for the sake of a three-way merge. Whitespace
63 changes mixed with other changes to a line are not ignored.
63 - See also linkgit:git-diff[1] `-b`, `-w`, and
64 - `--ignore-space-at-eol`.
64 + See also linkgit:git-diff[1] `-b`, `-w`,
65 + `--ignore-space-at-eol`, and `--ignore-cr-at-eol`.
66 +
67 * If 'their' version only introduces whitespace changes to a line,
68 'our' version is used;
contrib/completion/git-completion.bash
+1 -1
@@ -1399,7 +1399,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
1399 --patch-with-stat --name-only --name-status --color
1400 --no-color --color-words --no-renames --check
1401 --full-index --binary --abbrev --diff-filter=
1402 - --find-copies-harder
1402 + --find-copies-harder --ignore-cr-at-eol
1403 --text --ignore-space-at-eol --ignore-space-change
1404 --ignore-all-space --ignore-blank-lines --exit-code
1405 --quiet --ext-diff --no-ext-diff
diff.c
+2
@@ -3888,6 +3888,8 @@ int diff_opt_parse(struct diff_options *options,
3888 DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
3889 else if (!strcmp(arg, "--ignore-space-at-eol"))
3890 DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
3891 + else if (!strcmp(arg, "--ignore-cr-at-eol"))
3892 + DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
3893 else if (!strcmp(arg, "--ignore-blank-lines"))
3894 DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
3895 else if (!strcmp(arg, "--indent-heuristic"))
merge-recursive.c
+2
@@ -2214,6 +2214,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)
2214 DIFF_XDL_SET(o, IGNORE_WHITESPACE);
2215 else if (!strcmp(s, "ignore-space-at-eol"))
2216 DIFF_XDL_SET(o, IGNORE_WHITESPACE_AT_EOL);
2217 + else if (!strcmp(s, "ignore-cr-at-eol"))
2218 + DIFF_XDL_SET(o, IGNORE_CR_AT_EOL);
2219 else if (!strcmp(s, "renormalize"))
2220 o->renormalize = 1;
2221 else if (!strcmp(s, "no-renormalize"))
t/t4015-diff-whitespace.sh
+28
@@ -106,6 +106,8 @@ test_expect_success 'another test, without options' '
106 git diff -w -b --ignore-space-at-eol >out &&
107 test_cmp expect out &&
108
109 + git diff -w --ignore-cr-at-eol >out &&
110 + test_cmp expect out &&
111
112 tr "Q_" "\015 " <<-\EOF >expect &&
113 diff --git a/x b/x
@@ -128,6 +130,9 @@ test_expect_success 'another test, without options' '
130 git diff -b --ignore-space-at-eol >out &&
131 test_cmp expect out &&
132
133 + git diff -b --ignore-cr-at-eol >out &&
134 + test_cmp expect out &&
135 +
136 tr "Q_" "\015 " <<-\EOF >expect &&
137 diff --git a/x b/x
138 index d99af23..22d9f73 100644
@@ -145,6 +150,29 @@ test_expect_success 'another test, without options' '
150 CR at end
151 EOF
152 git diff --ignore-space-at-eol >out &&
153 + test_cmp expect out &&
154 +
155 + git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
156 + test_cmp expect out &&
157 +
158 + tr "Q_" "\015 " <<-\EOF >expect &&
159 + diff --git a/x b/x
160 + index_d99af23..22d9f73 100644
161 + --- a/x
162 + +++ b/x
163 + @@ -1,6 +1,6 @@
164 + -whitespace at beginning
165 + -whitespace change
166 + -whitespace in the middle
167 + -whitespace at end
168 + +_ whitespace at beginning
169 + +whitespace_ _change
170 + +white space in the middle
171 + +whitespace at end__
172 + unchanged line
173 + CR at end
174 + EOF
175 + git diff --ignore-cr-at-eol >out &&
176 test_cmp expect out
177 '
178
xdiff/xdiff.h
+3 -1
@@ -33,9 +33,11 @@ extern "C" {
33 #define XDF_IGNORE_WHITESPACE (1 << 1)
34 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2)
35 #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3)
36 +#define XDF_IGNORE_CR_AT_EOL (1 << 4)
37 #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \
38 XDF_IGNORE_WHITESPACE_CHANGE | \
38 - XDF_IGNORE_WHITESPACE_AT_EOL)
39 + XDF_IGNORE_WHITESPACE_AT_EOL | \
40 + XDF_IGNORE_CR_AT_EOL)
41
42 #define XDF_IGNORE_BLANK_LINES (1 << 7)
43
xdiff/xutils.c
+36 -2
@@ -156,6 +156,24 @@ int xdl_blankline(const char *line, long size, long flags)
156 return (i == size);
157 }
158
159 +/*
160 + * Have we eaten everything on the line, except for an optional
161 + * CR at the very end?
162 + */
163 +static int ends_with_optional_cr(const char *l, long s, long i)
164 +{
165 + int complete = s && l[s-1] == '\n';
166 +
167 + if (complete)
168 + s--;
169 + if (s == i)
170 + return 1;
171 + /* do not ignore CR at the end of an incomplete line */
172 + if (complete && s == i + 1 && l[i] == '\r')
173 + return 1;
174 + return 0;
175 +}
176 +
177 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
178 {
179 int i1, i2;
@@ -170,7 +188,8 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
188
189 /*
190 * -w matches everything that matches with -b, and -b in turn
173 - * matches everything that matches with --ignore-space-at-eol.
191 + * matches everything that matches with --ignore-space-at-eol,
192 + * which in turn matches everything that matches with --ignore-cr-at-eol.
193 *
194 * Each flavor of ignoring needs different logic to skip whitespaces
195 * while we have both sides to compare.
@@ -204,6 +223,14 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
223 i1++;
224 i2++;
225 }
226 + } else if (flags & XDF_IGNORE_CR_AT_EOL) {
227 + /* Find the first difference and see how the line ends */
228 + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
229 + i1++;
230 + i2++;
231 + }
232 + return (ends_with_optional_cr(l1, s1, i1) &&
233 + ends_with_optional_cr(l2, s2, i2));
234 }
235
236 /*
@@ -230,9 +257,16 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
257 char const *top, long flags) {
258 unsigned long ha = 5381;
259 char const *ptr = *data;
260 + int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
261
262 for (; ptr < top && *ptr != '\n'; ptr++) {
235 - if (XDL_ISSPACE(*ptr)) {
263 + if (cr_at_eol_only) {
264 + /* do not ignore CR at the end of an incomplete line */
265 + if (*ptr == '\r' &&
266 + (ptr + 1 < top && ptr[1] == '\n'))
267 + continue;
268 + }
269 + else if (XDL_ISSPACE(*ptr)) {
270 const char *ptr2 = ptr;
271 int at_eol;
272 while (ptr + 1 < top && XDL_ISSPACE(ptr[1])