1 /* Produce a unidiff output from a diff_result. */
3 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #include <arraylist.h>
27 #include <diff_main.h>
28 #include <diff_output.h>
30 #include "diff_internal.h"
31 #include "diff_debug.h"
34 diff_chunk_get_left_start_pos(const struct diff_chunk
*c
)
36 return c
->left_start
->pos
;
40 diff_chunk_get_right_start_pos(const struct diff_chunk
*c
)
42 return c
->right_start
->pos
;
46 diff_chunk_context_empty(const struct diff_chunk_context
*cc
)
48 return diff_range_empty(&cc
->chunk
);
52 diff_chunk_get_left_start(const struct diff_chunk
*c
,
53 const struct diff_result
*r
, int context_lines
)
55 int left_start
= diff_atom_root_idx(r
->left
, c
->left_start
);
56 return MAX(0, left_start
- context_lines
);
60 diff_chunk_get_left_end(const struct diff_chunk
*c
,
61 const struct diff_result
*r
, int context_lines
)
63 int left_start
= diff_chunk_get_left_start(c
, r
, 0);
64 return MIN(r
->left
->atoms
.len
,
65 left_start
+ c
->left_count
+ context_lines
);
69 diff_chunk_get_right_start(const struct diff_chunk
*c
,
70 const struct diff_result
*r
, int context_lines
)
72 int right_start
= diff_atom_root_idx(r
->right
, c
->right_start
);
73 return MAX(0, right_start
- context_lines
);
77 diff_chunk_get_right_end(const struct diff_chunk
*c
,
78 const struct diff_result
*r
, int context_lines
)
80 int right_start
= diff_chunk_get_right_start(c
, r
, 0);
81 return MIN(r
->right
->atoms
.len
,
82 right_start
+ c
->right_count
+ context_lines
);
86 diff_chunk_get(const struct diff_result
*r
, int chunk_idx
)
88 return &r
->chunks
.head
[chunk_idx
];
92 diff_chunk_get_left_count(struct diff_chunk
*c
)
98 diff_chunk_get_right_count(struct diff_chunk
*c
)
100 return c
->right_count
;
104 diff_chunk_context_get(struct diff_chunk_context
*cc
, const struct diff_result
*r
,
105 int chunk_idx
, int context_lines
)
107 const struct diff_chunk
*c
= &r
->chunks
.head
[chunk_idx
];
108 int left_start
= diff_chunk_get_left_start(c
, r
, context_lines
);
109 int left_end
= diff_chunk_get_left_end(c
, r
, context_lines
);
110 int right_start
= diff_chunk_get_right_start(c
, r
, context_lines
);
111 int right_end
= diff_chunk_get_right_end(c
, r
, context_lines
);
113 *cc
= (struct diff_chunk_context
){
116 .end
= chunk_idx
+ 1,
123 .start
= right_start
,
130 diff_chunk_contexts_touch(const struct diff_chunk_context
*cc
,
131 const struct diff_chunk_context
*other
)
133 return diff_ranges_touch(&cc
->chunk
, &other
->chunk
)
134 || diff_ranges_touch(&cc
->left
, &other
->left
)
135 || diff_ranges_touch(&cc
->right
, &other
->right
);
139 diff_chunk_contexts_merge(struct diff_chunk_context
*cc
,
140 const struct diff_chunk_context
*other
)
142 diff_ranges_merge(&cc
->chunk
, &other
->chunk
);
143 diff_ranges_merge(&cc
->left
, &other
->left
);
144 diff_ranges_merge(&cc
->right
, &other
->right
);
148 diff_chunk_context_load_change(struct diff_chunk_context
*cc
,
150 struct diff_result
*result
,
155 int seen_minus
= 0, seen_plus
= 0;
160 for (i
= start_chunk_idx
; i
< result
->chunks
.len
; i
++) {
161 struct diff_chunk
*chunk
= &result
->chunks
.head
[i
];
162 enum diff_chunk_type t
= diff_chunk_type(chunk
);
163 struct diff_chunk_context next
;
165 if (t
!= CHUNK_MINUS
&& t
!= CHUNK_PLUS
) {
168 if (seen_minus
|| seen_plus
)
172 } else if (t
== CHUNK_MINUS
)
174 else if (t
== CHUNK_PLUS
)
177 if (diff_chunk_context_empty(cc
)) {
178 /* Note down the start point, any number of subsequent
179 * chunks may be joined up to this chunk by being
180 * directly adjacent. */
181 diff_chunk_context_get(cc
, result
, i
, context_lines
);
187 /* There already is a previous chunk noted down for being
188 * printed. Does it join up with this one? */
189 diff_chunk_context_get(&next
, result
, i
, context_lines
);
191 if (diff_chunk_contexts_touch(cc
, &next
)) {
192 /* This next context touches or overlaps the previous
194 diff_chunk_contexts_merge(cc
, &next
);
203 struct diff_output_unidiff_state
{
205 char prototype
[DIFF_FUNCTION_CONTEXT_SIZE
];
206 int last_prototype_idx
;
209 struct diff_output_unidiff_state
*
210 diff_output_unidiff_state_alloc(void)
212 struct diff_output_unidiff_state
*state
;
214 state
= calloc(1, sizeof(struct diff_output_unidiff_state
));
216 diff_output_unidiff_state_reset(state
);
221 diff_output_unidiff_state_reset(struct diff_output_unidiff_state
*state
)
223 state
->header_printed
= false;
224 memset(state
->prototype
, 0, sizeof(state
->prototype
));
225 state
->last_prototype_idx
= 0;
229 diff_output_unidiff_state_free(struct diff_output_unidiff_state
*state
)
235 output_unidiff_chunk(struct diff_output_info
*outinfo
, FILE *dest
,
236 struct diff_output_unidiff_state
*state
,
237 const struct diff_input_info
*info
,
238 const struct diff_result
*result
,
239 bool print_header
, bool show_function_prototypes
,
240 const struct diff_chunk_context
*cc
)
242 int rc
, left_start
, left_len
, right_start
, right_len
;
243 off_t outoff
= 0, *offp
;
246 if (diff_range_empty(&cc
->left
) && diff_range_empty(&cc
->right
))
249 if (outinfo
&& outinfo
->line_offsets
.len
> 0) {
250 unsigned int idx
= outinfo
->line_offsets
.len
- 1;
251 outoff
= outinfo
->line_offsets
.head
[idx
];
254 if (print_header
&& !(state
->header_printed
)) {
255 rc
= fprintf(dest
, "--- %s\n",
256 diff_output_get_label_left(info
));
260 ARRAYLIST_ADD(offp
, outinfo
->line_offsets
);
265 ARRAYLIST_ADD(typep
, outinfo
->line_types
);
268 *typep
= DIFF_LINE_MINUS
;
270 rc
= fprintf(dest
, "+++ %s\n",
271 diff_output_get_label_right(info
));
275 ARRAYLIST_ADD(offp
, outinfo
->line_offsets
);
280 ARRAYLIST_ADD(typep
, outinfo
->line_types
);
283 *typep
= DIFF_LINE_PLUS
;
285 state
->header_printed
= true;
288 left_len
= cc
->left
.end
- cc
->left
.start
;
289 if (result
->left
->atoms
.len
== 0)
291 else if (left_len
== 0 && cc
->left
.start
> 0)
292 left_start
= cc
->left
.start
;
294 left_start
= cc
->left
.start
+ 1;
296 right_len
= cc
->right
.end
- cc
->right
.start
;
297 if (result
->right
->atoms
.len
== 0)
299 else if (right_len
== 0 && cc
->right
.start
> 0)
300 right_start
= cc
->right
.start
;
302 right_start
= cc
->right
.start
+ 1;
304 if (show_function_prototypes
) {
305 rc
= diff_output_match_function_prototype(state
->prototype
,
306 sizeof(state
->prototype
), &state
->last_prototype_idx
,
312 if (left_len
== 1 && right_len
== 1) {
313 rc
= fprintf(dest
, "@@ -%d +%d @@%s%s\n",
314 left_start
, right_start
,
315 state
->prototype
[0] ? " " : "",
316 state
->prototype
[0] ? state
->prototype
: "");
317 } else if (left_len
== 1 && right_len
!= 1) {
318 rc
= fprintf(dest
, "@@ -%d +%d,%d @@%s%s\n",
319 left_start
, right_start
, right_len
,
320 state
->prototype
[0] ? " " : "",
321 state
->prototype
[0] ? state
->prototype
: "");
322 } else if (left_len
!= 1 && right_len
== 1) {
323 rc
= fprintf(dest
, "@@ -%d,%d +%d @@%s%s\n",
324 left_start
, left_len
, right_start
,
325 state
->prototype
[0] ? " " : "",
326 state
->prototype
[0] ? state
->prototype
: "");
328 rc
= fprintf(dest
, "@@ -%d,%d +%d,%d @@%s%s\n",
329 left_start
, left_len
, right_start
, right_len
,
330 state
->prototype
[0] ? " " : "",
331 state
->prototype
[0] ? state
->prototype
: "");
336 ARRAYLIST_ADD(offp
, outinfo
->line_offsets
);
341 ARRAYLIST_ADD(typep
, outinfo
->line_types
);
344 *typep
= DIFF_LINE_HUNK
;
347 /* Got the absolute line numbers where to start printing, and the index
348 * of the interesting (non-context) chunk.
349 * To print context lines above the interesting chunk, nipping on the
350 * previous chunk index may be necessary.
351 * It is guaranteed to be only context lines where left == right, so it
352 * suffices to look on the left. */
353 const struct diff_chunk
*first_chunk
;
354 int chunk_start_line
;
355 first_chunk
= &result
->chunks
.head
[cc
->chunk
.start
];
356 chunk_start_line
= diff_atom_root_idx(result
->left
,
357 first_chunk
->left_start
);
358 if (cc
->left
.start
< chunk_start_line
) {
359 rc
= diff_output_lines(outinfo
, dest
, " ",
360 &result
->left
->atoms
.head
[cc
->left
.start
],
361 chunk_start_line
- cc
->left
.start
);
366 /* Now write out all the joined chunks and contexts between them */
368 for (c_idx
= cc
->chunk
.start
; c_idx
< cc
->chunk
.end
; c_idx
++) {
369 const struct diff_chunk
*c
= &result
->chunks
.head
[c_idx
];
371 if (c
->left_count
&& c
->right_count
)
372 rc
= diff_output_lines(outinfo
, dest
,
373 c
->solved
? " " : "?",
374 c
->left_start
, c
->left_count
);
375 else if (c
->left_count
&& !c
->right_count
)
376 rc
= diff_output_lines(outinfo
, dest
,
377 c
->solved
? "-" : "?",
378 c
->left_start
, c
->left_count
);
379 else if (c
->right_count
&& !c
->left_count
)
380 rc
= diff_output_lines(outinfo
, dest
,
381 c
->solved
? "+" : "?",
382 c
->right_start
, c
->right_count
);
386 if (cc
->chunk
.end
== result
->chunks
.len
) {
387 rc
= diff_output_trailing_newline_msg(outinfo
, dest
, c
);
388 if (rc
!= DIFF_RC_OK
)
393 /* Trailing context? */
394 const struct diff_chunk
*last_chunk
;
396 last_chunk
= &result
->chunks
.head
[cc
->chunk
.end
- 1];
397 chunk_end_line
= diff_atom_root_idx(result
->left
,
398 last_chunk
->left_start
399 + last_chunk
->left_count
);
400 if (cc
->left
.end
> chunk_end_line
) {
401 rc
= diff_output_lines(outinfo
, dest
, " ",
402 &result
->left
->atoms
.head
[chunk_end_line
],
403 cc
->left
.end
- chunk_end_line
);
407 if (cc
->left
.end
== result
->left
->atoms
.len
) {
408 rc
= diff_output_trailing_newline_msg(outinfo
, dest
,
409 &result
->chunks
.head
[result
->chunks
.len
- 1]);
410 if (rc
!= DIFF_RC_OK
)
419 diff_output_unidiff_chunk(struct diff_output_info
**output_info
, FILE *dest
,
420 struct diff_output_unidiff_state
*state
,
421 const struct diff_input_info
*info
,
422 const struct diff_result
*result
,
423 const struct diff_chunk_context
*cc
)
425 struct diff_output_info
*outinfo
= NULL
;
426 int flags
= (result
->left
->root
->diff_flags
|
427 result
->right
->root
->diff_flags
);
428 bool show_function_prototypes
= (flags
& DIFF_FLAG_SHOW_PROTOTYPES
);
431 *output_info
= diff_output_info_alloc();
432 if (*output_info
== NULL
)
434 outinfo
= *output_info
;
437 return output_unidiff_chunk(outinfo
, dest
, state
, info
,
438 result
, false, show_function_prototypes
, cc
);
442 diff_output_unidiff(struct diff_output_info
**output_info
,
443 FILE *dest
, const struct diff_input_info
*info
,
444 const struct diff_result
*result
,
445 unsigned int context_lines
)
447 struct diff_output_unidiff_state
*state
;
448 struct diff_chunk_context cc
= {};
449 struct diff_output_info
*outinfo
= NULL
;
450 int atomizer_flags
= (result
->left
->atomizer_flags
|
451 result
->right
->atomizer_flags
);
452 int flags
= (result
->left
->root
->diff_flags
|
453 result
->right
->root
->diff_flags
);
454 bool show_function_prototypes
= (flags
& DIFF_FLAG_SHOW_PROTOTYPES
);
455 bool force_text
= (flags
& DIFF_FLAG_FORCE_TEXT_DATA
);
456 bool have_binary
= (atomizer_flags
& DIFF_ATOMIZER_FOUND_BINARY_DATA
);
457 off_t outoff
= 0, *offp
;
463 if (result
->rc
!= DIFF_RC_OK
)
467 *output_info
= diff_output_info_alloc();
468 if (*output_info
== NULL
)
470 outinfo
= *output_info
;
473 if (have_binary
&& !force_text
) {
474 for (i
= 0; i
< result
->chunks
.len
; i
++) {
475 struct diff_chunk
*c
= &result
->chunks
.head
[i
];
476 enum diff_chunk_type t
= diff_chunk_type(c
);
478 if (t
!= CHUNK_MINUS
&& t
!= CHUNK_PLUS
)
481 if (outinfo
&& outinfo
->line_offsets
.len
> 0) {
483 outinfo
->line_offsets
.len
- 1;
484 outoff
= outinfo
->line_offsets
.head
[idx
];
487 rc
= fprintf(dest
, "Binary files %s and %s differ\n",
488 diff_output_get_label_left(info
),
489 diff_output_get_label_right(info
));
491 ARRAYLIST_ADD(offp
, outinfo
->line_offsets
);
496 ARRAYLIST_ADD(typep
, outinfo
->line_types
);
499 *typep
= DIFF_LINE_NONE
;
507 state
= diff_output_unidiff_state_alloc();
510 diff_output_info_free(*output_info
);
517 unsigned int check_left_pos
, check_right_pos
;
520 for (i
= 0; i
< result
->chunks
.len
; i
++) {
521 struct diff_chunk
*c
= &result
->chunks
.head
[i
];
522 enum diff_chunk_type t
= diff_chunk_type(c
);
524 debug("[%d] %s lines L%d R%d @L %d @R %d\n",
525 i
, (t
== CHUNK_MINUS
? "minus" :
526 (t
== CHUNK_PLUS
? "plus" :
527 (t
== CHUNK_SAME
? "same" : "?"))),
530 c
->left_start
? diff_atom_root_idx(result
->left
, c
->left_start
) : -1,
531 c
->right_start
? diff_atom_root_idx(result
->right
, c
->right_start
) : -1);
532 assert(check_left_pos
== diff_atom_root_idx(result
->left
, c
->left_start
));
533 assert(check_right_pos
== diff_atom_root_idx(result
->right
, c
->right_start
));
534 check_left_pos
+= c
->left_count
;
535 check_right_pos
+= c
->right_count
;
538 assert(check_left_pos
== result
->left
->atoms
.len
);
539 assert(check_right_pos
== result
->right
->atoms
.len
);
542 for (i
= 0; i
< result
->chunks
.len
; i
++) {
543 struct diff_chunk
*c
= &result
->chunks
.head
[i
];
544 enum diff_chunk_type t
= diff_chunk_type(c
);
545 struct diff_chunk_context next
;
547 if (t
!= CHUNK_MINUS
&& t
!= CHUNK_PLUS
)
550 if (diff_chunk_context_empty(&cc
)) {
551 /* These are the first lines being printed.
552 * Note down the start point, any number of subsequent
553 * chunks may be joined up to this unidiff chunk by
554 * context lines or by being directly adjacent. */
555 diff_chunk_context_get(&cc
, result
, i
, context_lines
);
556 debug("new chunk to be printed:"
557 " chunk %d-%d left %d-%d right %d-%d\n",
558 cc
.chunk
.start
, cc
.chunk
.end
,
559 cc
.left
.start
, cc
.left
.end
,
560 cc
.right
.start
, cc
.right
.end
);
564 /* There already is a previous chunk noted down for being
565 * printed. Does it join up with this one? */
566 diff_chunk_context_get(&next
, result
, i
, context_lines
);
567 debug("new chunk to be printed:"
568 " chunk %d-%d left %d-%d right %d-%d\n",
569 next
.chunk
.start
, next
.chunk
.end
,
570 next
.left
.start
, next
.left
.end
,
571 next
.right
.start
, next
.right
.end
);
573 if (diff_chunk_contexts_touch(&cc
, &next
)) {
574 /* This next context touches or overlaps the previous
576 diff_chunk_contexts_merge(&cc
, &next
);
577 debug("new chunk to be printed touches previous chunk,"
578 " now: left %d-%d right %d-%d\n",
579 cc
.left
.start
, cc
.left
.end
,
580 cc
.right
.start
, cc
.right
.end
);
584 /* No touching, so the previous context is complete with a gap
585 * between it and this next one. Print the previous one and
586 * start fresh here. */
587 debug("new chunk to be printed does not touch previous chunk;"
588 " print left %d-%d right %d-%d\n",
589 cc
.left
.start
, cc
.left
.end
, cc
.right
.start
, cc
.right
.end
);
590 output_unidiff_chunk(outinfo
, dest
, state
, info
, result
,
591 true, show_function_prototypes
, &cc
);
593 debug("new unprinted chunk is left %d-%d right %d-%d\n",
594 cc
.left
.start
, cc
.left
.end
, cc
.right
.start
, cc
.right
.end
);
597 if (!diff_chunk_context_empty(&cc
))
598 output_unidiff_chunk(outinfo
, dest
, state
, info
, result
,
599 true, show_function_prototypes
, &cc
);
600 diff_output_unidiff_state_free(state
);