diff: add --ignore-blank-lines option

The goal of the patch is to introduce the GNU diff -B/--ignore-blank-lines as closely as possible. The short option is not available because it's already used for "break-rewrites". When this option is used, git-diff will not create hunks that simply add or remove empty lines, but will still show empty lines addition/suppression if they are close enough to "valuable" changes. There are two differences between this option and GNU diff -B option: - GNU diff doesn't have "--inter-hunk-context", so this must be handled - The following sequence looks like a bug (context is displayed twice): $ seq 5 >file1 $ cat <<EOF >file2 change 1 2 3 4 5 change EOF $ diff -u -B file1 file2 --- file1 2013-06-08 22:13:04.471517834 +0200 +++ file2 2013-06-08 22:13:23.275517855 +0200 @@ -1,5 +1,7 @@ +change 1 2 + 3 4 5 @@ -3,3 +5,4 @@ 3 4 5 +change So here is a more thorough description of the option: - real changes are interesting - blank lines that are close enough (less than context size) to interesting changes are considered interesting (recursive definition) - "context" lines are used around each hunk of interesting changes - If two hunks are separated by less than "inter-hunk-context", they will be merged into one. The implementation does the "interesting changes selection" in a single pass. Signed-off-by: Antoine Pelisse <apelisse@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Antoine Pelisse committed Jun 19, 2013 at 20:46 UTC 36617af7ed594d1928554356d809bd611c642dd2
10 files changed +439 -8
Documentation/diff-options.txt
+3
@@ -439,6 +439,9 @@ endif::git-format-patch[]
439 differences even if one line has whitespace where the other
440 line has none.
441
442 +--ignore-blank-lines::
443 + Ignore changes whose lines are all blank.
444 +
445 --inter-hunk-context=<lines>::
446 Show the context between diff hunks, up to the specified number
447 of lines, thereby fusing hunks that are close to each other.
diff.c
+2
@@ -3593,6 +3593,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
3593 DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
3594 else if (!strcmp(arg, "--ignore-space-at-eol"))
3595 DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
3596 + else if (!strcmp(arg, "--ignore-blank-lines"))
3597 + DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
3598 else if (!strcmp(arg, "--patience"))
3599 options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
3600 else if (!strcmp(arg, "--histogram"))
t/t4015-diff-whitespace.sh
+345
@@ -142,6 +142,351 @@ EOF
142 git diff --ignore-space-at-eol > out
143 test_expect_success 'another test, with --ignore-space-at-eol' 'test_cmp expect out'
144
145 +test_expect_success 'ignore-blank-lines: only new lines' '
146 + test_seq 5 >x &&
147 + git update-index x &&
148 + test_seq 5 | sed "/3/i \\
149 +" >x &&
150 + git diff --ignore-blank-lines >out &&
151 + >expect &&
152 + test_cmp out expect
153 +'
154 +
155 +test_expect_success 'ignore-blank-lines: only new lines with space' '
156 + test_seq 5 >x &&
157 + git update-index x &&
158 + test_seq 5 | sed "/3/i \ " >x &&
159 + git diff -w --ignore-blank-lines >out &&
160 + >expect &&
161 + test_cmp out expect
162 +'
163 +
164 +test_expect_success 'ignore-blank-lines: after change' '
165 + cat <<-\EOF >x &&
166 + 1
167 + 2
168 +
169 + 3
170 + 4
171 + 5
172 +
173 + 6
174 + 7
175 + EOF
176 + git update-index x &&
177 + cat <<-\EOF >x &&
178 + change
179 +
180 + 1
181 + 2
182 + 3
183 + 4
184 + 5
185 + 6
186 +
187 + 7
188 + EOF
189 + git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
190 + cat <<-\EOF >expected &&
191 + diff --git a/x b/x
192 + --- a/x
193 + +++ b/x
194 + @@ -1,6 +1,7 @@
195 + +change
196 + +
197 + 1
198 + 2
199 + -
200 + 3
201 + 4
202 + 5
203 + EOF
204 + compare_diff_patch expected out.tmp
205 +'
206 +
207 +test_expect_success 'ignore-blank-lines: before change' '
208 + cat <<-\EOF >x &&
209 + 1
210 + 2
211 +
212 + 3
213 + 4
214 + 5
215 + 6
216 + 7
217 + EOF
218 + git update-index x &&
219 + cat <<-\EOF >x &&
220 +
221 + 1
222 + 2
223 + 3
224 + 4
225 + 5
226 +
227 + 6
228 + 7
229 + change
230 + EOF
231 + git diff --inter-hunk-context=100 --ignore-blank-lines >out.tmp &&
232 + cat <<-\EOF >expected &&
233 + diff --git a/x b/x
234 + --- a/x
235 + +++ b/x
236 + @@ -4,5 +4,7 @@
237 + 3
238 + 4
239 + 5
240 + +
241 + 6
242 + 7
243 + +change
244 + EOF
245 + compare_diff_patch expected out.tmp
246 +'
247 +
248 +test_expect_success 'ignore-blank-lines: between changes' '
249 + cat <<-\EOF >x &&
250 + 1
251 + 2
252 + 3
253 + 4
254 + 5
255 +
256 +
257 + 6
258 + 7
259 + 8
260 + 9
261 + 10
262 + EOF
263 + git update-index x &&
264 + cat <<-\EOF >x &&
265 + change
266 + 1
267 + 2
268 +
269 + 3
270 + 4
271 + 5
272 + 6
273 + 7
274 + 8
275 +
276 + 9
277 + 10
278 + change
279 + EOF
280 + git diff --ignore-blank-lines >out.tmp &&
281 + cat <<-\EOF >expected &&
282 + diff --git a/x b/x
283 + --- a/x
284 + +++ b/x
285 + @@ -1,5 +1,7 @@
286 + +change
287 + 1
288 + 2
289 + +
290 + 3
291 + 4
292 + 5
293 + @@ -8,5 +8,7 @@
294 + 6
295 + 7
296 + 8
297 + +
298 + 9
299 + 10
300 + +change
301 + EOF
302 + compare_diff_patch expected out.tmp
303 +'
304 +
305 +test_expect_success 'ignore-blank-lines: between changes (with interhunkctx)' '
306 + test_seq 10 >x &&
307 + git update-index x &&
308 + cat <<-\EOF >x &&
309 + change
310 + 1
311 + 2
312 +
313 + 3
314 + 4
315 + 5
316 +
317 + 6
318 + 7
319 + 8
320 + 9
321 +
322 + 10
323 + change
324 + EOF
325 + git diff --inter-hunk-context=2 --ignore-blank-lines >out.tmp &&
326 + cat <<-\EOF >expected &&
327 + diff --git a/x b/x
328 + --- a/x
329 + +++ b/x
330 + @@ -1,10 +1,15 @@
331 + +change
332 + 1
333 + 2
334 + +
335 + 3
336 + 4
337 + 5
338 + +
339 + 6
340 + 7
341 + 8
342 + 9
343 + +
344 + 10
345 + +change
346 + EOF
347 + compare_diff_patch expected out.tmp
348 +'
349 +
350 +test_expect_success 'ignore-blank-lines: scattered spaces' '
351 + test_seq 10 >x &&
352 + git update-index x &&
353 + cat <<-\EOF >x &&
354 + change
355 + 1
356 + 2
357 + 3
358 +
359 + 4
360 +
361 + 5
362 +
363 + 6
364 +
365 +
366 + 7
367 +
368 + 8
369 + 9
370 + 10
371 + change
372 + EOF
373 + git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
374 + cat <<-\EOF >expected &&
375 + diff --git a/x b/x
376 + --- a/x
377 + +++ b/x
378 + @@ -1,3 +1,4 @@
379 + +change
380 + 1
381 + 2
382 + 3
383 + @@ -8,3 +15,4 @@
384 + 8
385 + 9
386 + 10
387 + +change
388 + EOF
389 + compare_diff_patch expected out.tmp
390 +'
391 +
392 +test_expect_success 'ignore-blank-lines: spaces coalesce' '
393 + test_seq 6 >x &&
394 + git update-index x &&
395 + cat <<-\EOF >x &&
396 + change
397 + 1
398 + 2
399 + 3
400 +
401 + 4
402 +
403 + 5
404 +
405 + 6
406 + change
407 + EOF
408 + git diff --inter-hunk-context=4 --ignore-blank-lines >out.tmp &&
409 + cat <<-\EOF >expected &&
410 + diff --git a/x b/x
411 + --- a/x
412 + +++ b/x
413 + @@ -1,6 +1,11 @@
414 + +change
415 + 1
416 + 2
417 + 3
418 + +
419 + 4
420 + +
421 + 5
422 + +
423 + 6
424 + +change
425 + EOF
426 + compare_diff_patch expected out.tmp
427 +'
428 +
429 +test_expect_success 'ignore-blank-lines: mix changes and blank lines' '
430 + test_seq 16 >x &&
431 + git update-index x &&
432 + cat <<-\EOF >x &&
433 + change
434 + 1
435 + 2
436 +
437 + 3
438 + 4
439 + 5
440 + change
441 + 6
442 + 7
443 + 8
444 +
445 + 9
446 + 10
447 + 11
448 + change
449 + 12
450 + 13
451 + 14
452 +
453 + 15
454 + 16
455 + change
456 + EOF
457 + git diff --ignore-blank-lines >out.tmp &&
458 + cat <<-\EOF >expected &&
459 + diff --git a/x b/x
460 + --- a/x
461 + +++ b/x
462 + @@ -1,8 +1,11 @@
463 + +change
464 + 1
465 + 2
466 + +
467 + 3
468 + 4
469 + 5
470 + +change
471 + 6
472 + 7
473 + 8
474 + @@ -9,8 +13,11 @@
475 + 9
476 + 10
477 + 11
478 + +change
479 + 12
480 + 13
481 + 14
482 + +
483 + 15
484 + 16
485 + +change
486 + EOF
487 + compare_diff_patch expected out.tmp
488 +'
489 +
490 test_expect_success 'check mixed spaces and tabs in indent' '
491
492 # This is indented with SP HT SP.
xdiff/xdiff.h
+2
@@ -39,6 +39,8 @@ extern "C" {
39 #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
40 #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
41
42 +#define XDF_IGNORE_BLANK_LINES (1 << 7)
43 +
44 #define XDL_EMIT_FUNCNAMES (1 << 0)
45 #define XDL_EMIT_COMMON (1 << 1)
46 #define XDL_EMIT_FUNCCONTEXT (1 << 2)
xdiff/xdiffi.c
+28 -1
@@ -394,6 +394,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
394 xch->i2 = i2;
395 xch->chg1 = chg1;
396 xch->chg2 = chg2;
397 + xch->ignore = 0;
398
399 return xch;
400 }
@@ -544,7 +545,9 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
545 xdchange_t *xch, *xche;
546
547 for (xch = xscr; xch; xch = xche->next) {
547 - xche = xdl_get_hunk(xch, xecfg);
548 + xche = xdl_get_hunk(&xch, xecfg);
549 + if (!xch)
550 + break;
551 if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
552 xch->i2, xche->i2 + xche->chg2 - xch->i2,
553 ecb->priv) < 0)
@@ -553,6 +556,27 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
556 return 0;
557 }
558
559 +static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
560 +{
561 + xdchange_t *xch;
562 +
563 + for (xch = xscr; xch; xch = xch->next) {
564 + int ignore = 1;
565 + xrecord_t **rec;
566 + long i;
567 +
568 + rec = &xe->xdf1.recs[xch->i1];
569 + for (i = 0; i < xch->chg1 && ignore; i++)
570 + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
571 +
572 + rec = &xe->xdf2.recs[xch->i2];
573 + for (i = 0; i < xch->chg2 && ignore; i++)
574 + ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
575 +
576 + xch->ignore = ignore;
577 + }
578 +}
579 +
580 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
581 xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
582 xdchange_t *xscr;
@@ -571,6 +595,9 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
595 return -1;
596 }
597 if (xscr) {
598 + if (xpp->flags & XDF_IGNORE_BLANK_LINES)
599 + xdl_mark_ignorable(xscr, &xe, xpp->flags);
600 +
601 if (ef(&xe, xscr, ecb, xecfg) < 0) {
602
603 xdl_free_script(xscr);
xdiff/xdiffi.h
+1
@@ -41,6 +41,7 @@ typedef struct s_xdchange {
41 struct s_xdchange *next;
42 long i1, i2;
43 long chg1, chg2;
44 + int ignore;
45 } xdchange_t;
46
47
xdiff/xemit.c
+43 -6
@@ -56,16 +56,51 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *
56 /*
57 * Starting at the passed change atom, find the latest change atom to be included
58 * inside the differential hunk according to the specified configuration.
59 + * Also advance xscr if the first changes must be discarded.
60 */
60 -xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
61 - xdchange_t *xch, *xchp;
61 +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
62 +{
63 + xdchange_t *xch, *xchp, *lxch;
64 long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
65 + long max_ignorable = xecfg->ctxlen;
66 + unsigned long ignored = 0; /* number of ignored blank lines */
67 +
68 + /* remove ignorable changes that are too far before other changes */
69 + for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
70 + xch = xchp->next;
71 +
72 + if (xch == NULL ||
73 + xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable)
74 + *xscr = xch;
75 + }
76 +
77 + if (*xscr == NULL)
78 + return NULL;
79 +
80 + lxch = *xscr;
81
64 - for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
65 - if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
82 + for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) {
83 + long distance = xch->i1 - (xchp->i1 + xchp->chg1);
84 + if (distance > max_common)
85 break;
86
68 - return xchp;
87 + if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) {
88 + lxch = xch;
89 + ignored = 0;
90 + } else if (distance < max_ignorable && xch->ignore) {
91 + ignored += xch->chg2;
92 + } else if (lxch != xchp &&
93 + xch->i1 + ignored - (lxch->i1 + lxch->chg1) > max_common) {
94 + break;
95 + } else if (!xch->ignore) {
96 + lxch = xch;
97 + ignored = 0;
98 + } else {
99 + ignored += xch->chg2;
100 + }
101 + }
102 +
103 + return lxch;
104 }
105
106
@@ -139,7 +174,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
174 return xdl_emit_common(xe, xscr, ecb, xecfg);
175
176 for (xch = xscr; xch; xch = xche->next) {
142 - xche = xdl_get_hunk(xch, xecfg);
177 + xche = xdl_get_hunk(&xch, xecfg);
178 + if (!xch)
179 + break;
180
181 s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
182 s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
xdiff/xemit.h
+1 -1
@@ -27,7 +27,7 @@
27 typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
28 xdemitconf_t const *xecfg);
29
30 -xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg);
30 +xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg);
31 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
32 xdemitconf_t const *xecfg);
33
xdiff/xutils.c
+13
@@ -143,6 +143,19 @@ long xdl_guess_lines(mmfile_t *mf, long sample) {
143 return nl + 1;
144 }
145
146 +int xdl_blankline(const char *line, long size, long flags)
147 +{
148 + long i;
149 +
150 + if (!(flags & XDF_WHITESPACE_FLAGS))
151 + return (size <= 1);
152 +
153 + for (i = 0; i < size && XDL_ISSPACE(line[i]); i++)
154 + ;
155 +
156 + return (i == size);
157 +}
158 +
159 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
160 {
161 int i1, i2;
xdiff/xutils.h
+1
@@ -32,6 +32,7 @@ int xdl_cha_init(chastore_t *cha, long isize, long icount);
32 void xdl_cha_free(chastore_t *cha);
33 void *xdl_cha_alloc(chastore_t *cha);
34 long xdl_guess_lines(mmfile_t *mf, long sample);
35 +int xdl_blankline(const char *line, long size, long flags);
36 int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
37 unsigned long xdl_hash_record(char const **data, char const *top, long flags);
38 unsigned int xdl_hashbits(unsigned int size);