Edit

thodg/got/lib/diff_output_unidiff.c

Branch :

  • Show log

    Commit

  • Author : Stefan Sperling
    Date : 2020-12-10 16:58:11
    Hash : f3b2b552
    Message : sync files from diff.git b3fd1fa284e6207b923bd3c887364d9eb93fb340

  • lib/diff_output_unidiff.c
  • /* Produce a unidiff output from a diff_result. */
    /*
     * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <errno.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    
    #include <arraylist.h>
    #include <diff_main.h>
    #include <diff_output.h>
    
    #include "diff_internal.h"
    #include "diff_debug.h"
    
    bool
    diff_chunk_context_empty(const struct diff_chunk_context *cc)
    {
    	return diff_range_empty(&cc->chunk);
    }
    
    int
    diff_chunk_get_left_start(const struct diff_chunk *c,
        const struct diff_result *r, int context_lines)
    {
    	int left_start = diff_atom_root_idx(r->left, c->left_start);
    	return MAX(0, left_start - context_lines);
    }
    
    int
    diff_chunk_get_left_end(const struct diff_chunk *c,
        const struct diff_result *r, int context_lines)
    {
    	int left_start = diff_chunk_get_left_start(c, r, 0);
    	return MIN(r->left->atoms.len,
    	    left_start + c->left_count + context_lines);
    }
    
    int
    diff_chunk_get_right_start(const struct diff_chunk *c,
        const struct diff_result *r, int context_lines)
    {
    	int right_start = diff_atom_root_idx(r->right, c->right_start);
    	return MAX(0, right_start - context_lines);
    }
    
    int
    diff_chunk_get_right_end(const struct diff_chunk *c,
        const struct diff_result *r, int context_lines)
    {
    	int right_start = diff_chunk_get_right_start(c, r, 0);
    	return MIN(r->right->atoms.len,
    	    right_start + c->right_count + context_lines);
    }
    
    struct diff_chunk *
    diff_chunk_get(const struct diff_result *r, int chunk_idx)
    {
    	return &r->chunks.head[chunk_idx];
    }
    
    int
    diff_chunk_get_left_count(struct diff_chunk *c)
    {
    	return c->left_count;
    }
    
    int
    diff_chunk_get_right_count(struct diff_chunk *c)
    {
    	return c->right_count;
    }
    
    void
    diff_chunk_context_get(struct diff_chunk_context *cc, const struct diff_result *r,
    		  int chunk_idx, int context_lines)
    {
    	const struct diff_chunk *c = &r->chunks.head[chunk_idx];
    	int left_start = diff_chunk_get_left_start(c, r, context_lines);
    	int left_end = diff_chunk_get_left_end(c, r, context_lines);
    	int right_start = diff_chunk_get_right_start(c, r, context_lines);
    	int right_end = diff_chunk_get_right_end(c, r,  context_lines);
    
    	*cc = (struct diff_chunk_context){
    		.chunk = {
    			.start = chunk_idx,
    			.end = chunk_idx + 1,
    		},
    		.left = {
    			.start = left_start,
    			.end = left_end,
    		},
    		.right = {
    			.start = right_start,
    			.end = right_end,
    		},
    	};
    }
    
    bool
    diff_chunk_contexts_touch(const struct diff_chunk_context *cc,
    			  const struct diff_chunk_context *other)
    {
    	return diff_ranges_touch(&cc->chunk, &other->chunk)
    		|| diff_ranges_touch(&cc->left, &other->left)
    		|| diff_ranges_touch(&cc->right, &other->right);
    }
    
    void
    diff_chunk_contexts_merge(struct diff_chunk_context *cc,
    			  const struct diff_chunk_context *other)
    {
    	diff_ranges_merge(&cc->chunk, &other->chunk);
    	diff_ranges_merge(&cc->left, &other->left);
    	diff_ranges_merge(&cc->right, &other->right);
    }
    
    void
    diff_chunk_context_load_change(struct diff_chunk_context *cc,
    			       int *nchunks_used,
    			       struct diff_result *result,
    			       int start_chunk_idx,
    			       int context_lines)
    {
    	int i;
    	int seen_minus = 0, seen_plus = 0;
    
    	if (nchunks_used)
    		*nchunks_used = 0;
    
    	for (i = start_chunk_idx; i < result->chunks.len; i++) {
    		struct diff_chunk *chunk = &result->chunks.head[i];
    		enum diff_chunk_type t = diff_chunk_type(chunk);
    		struct diff_chunk_context next;
    
    		if (t != CHUNK_MINUS && t != CHUNK_PLUS) {
    			if (nchunks_used)
    				(*nchunks_used)++;
    			if (seen_minus || seen_plus)
    				break;
    			else
    				continue;
    		} else if (t == CHUNK_MINUS)
    			seen_minus = 1;
    		else if (t == CHUNK_PLUS)
    			seen_plus = 1;
    
    		if (diff_chunk_context_empty(cc)) {
    			/* Note down the start point, any number of subsequent
    			 * chunks may be joined up to this chunk by being
    			 * directly adjacent. */
    			diff_chunk_context_get(cc, result, i, context_lines);
    			if (nchunks_used)
    				(*nchunks_used)++;
    			continue;
    		}
    
    		/* There already is a previous chunk noted down for being
    		 * printed. Does it join up with this one? */
    		diff_chunk_context_get(&next, result, i, context_lines);
    
    		if (diff_chunk_contexts_touch(cc, &next)) {
    			/* This next context touches or overlaps the previous
    			 * one, join. */
    			diff_chunk_contexts_merge(cc, &next);
    			if (nchunks_used)
    				(*nchunks_used)++;
    			continue;
    		} else
    			break;
    	}
    }
    
    struct diff_output_unidiff_state {
    	bool header_printed;
    	char prototype[DIFF_FUNCTION_CONTEXT_SIZE];
    	int last_prototype_idx;
    };
    
    struct diff_output_unidiff_state *
    diff_output_unidiff_state_alloc(void)
    {
    	struct diff_output_unidiff_state *state;
    
    	state = calloc(1, sizeof(struct diff_output_unidiff_state));
    	if (state != NULL)
    		diff_output_unidiff_state_reset(state);
    	return state;
    }
    
    void
    diff_output_unidiff_state_reset(struct diff_output_unidiff_state *state)
    {
    	state->header_printed = false;
    	memset(state->prototype, 0, sizeof(state->prototype));
    	state->last_prototype_idx = 0;
    }
    
    void
    diff_output_unidiff_state_free(struct diff_output_unidiff_state *state)
    {
    	free(state);
    }
    
    static int
    output_unidiff_chunk(struct diff_output_info *outinfo, FILE *dest,
    		     struct diff_output_unidiff_state *state,
    		     const struct diff_input_info *info,
    		     const struct diff_result *result,
    		     bool print_header, bool show_function_prototypes,
    		     const struct diff_chunk_context *cc)
    {
    	int rc, left_start, left_len, right_start, right_len;
    	off_t outoff = 0, *offp;
    
    	if (diff_range_empty(&cc->left) && diff_range_empty(&cc->right))
    		return DIFF_RC_OK;
    
    	if (outinfo && outinfo->line_offsets.len > 0) {
    		unsigned int idx = outinfo->line_offsets.len - 1;
    		outoff = outinfo->line_offsets.head[idx];
    	}
    
    	if (print_header && !(state->header_printed)) {
    		rc = fprintf(dest, "--- %s\n",
    		    diff_output_get_label_left(info));
    		if (rc < 0)
    			return errno;
    		if (outinfo) {
    			ARRAYLIST_ADD(offp, outinfo->line_offsets);
    			if (offp == NULL)
    				return ENOMEM;
    			outoff += rc;
    			*offp = outoff;
    
    		}
    		rc = fprintf(dest, "+++ %s\n",
    		    diff_output_get_label_right(info));
    		if (rc < 0)
    			return errno;
    		if (outinfo) {
    			ARRAYLIST_ADD(offp, outinfo->line_offsets);
    			if (offp == NULL)
    				return ENOMEM;
    			outoff += rc;
    			*offp = outoff;
    
    		}
    		state->header_printed = true;
    	}
    
    	left_len = cc->left.end - cc->left.start;
    	if (result->left->atoms.len == 0)
    		left_start = 0;
    	else if (left_len == 0 && cc->left.start > 0)
    		left_start = cc->left.start;
    	else
    		left_start = cc->left.start + 1;
    
    	right_len = cc->right.end - cc->right.start;
    	if (result->right->atoms.len == 0)
    		right_start = 0;
    	else if (right_len == 0 && cc->right.start > 0)
    		right_start = cc->right.start;
    	else
    		right_start = cc->right.start + 1;
    
    	if (show_function_prototypes) {
    		rc = diff_output_match_function_prototype(state->prototype,
    		    sizeof(state->prototype), &state->last_prototype_idx,
    		    result, cc);
    		if (rc)
    			return rc;
    	}
    
    	if (left_len == 1 && right_len == 1) {
    		rc = fprintf(dest, "@@ -%d +%d @@%s%s\n",
    			left_start, right_start,
    			state->prototype[0] ? " " : "",
    			state->prototype[0] ? state->prototype : "");
    	} else if (left_len == 1 && right_len != 1) {
    		rc = fprintf(dest, "@@ -%d +%d,%d @@%s%s\n",
    			left_start, right_start, right_len,
    			state->prototype[0] ? " " : "",
    			state->prototype[0] ? state->prototype : "");
    	} else if (left_len != 1 && right_len == 1) {
    		rc = fprintf(dest, "@@ -%d,%d +%d @@%s%s\n",
    			left_start, left_len, right_start,
    			state->prototype[0] ? " " : "",
    			state->prototype[0] ? state->prototype : "");
    	} else {
    		rc = fprintf(dest, "@@ -%d,%d +%d,%d @@%s%s\n",
    			left_start, left_len, right_start, right_len,
    			state->prototype[0] ? " " : "",
    			state->prototype[0] ? state->prototype : "");
    	}
    	if (rc < 0)
    		return errno;
    	if (outinfo) {
    		ARRAYLIST_ADD(offp, outinfo->line_offsets);
    		if (offp == NULL)
    			return ENOMEM;
    		outoff += rc;
    		*offp = outoff;
    
    	}
    
    	/* Got the absolute line numbers where to start printing, and the index
    	 * of the interesting (non-context) chunk.
    	 * To print context lines above the interesting chunk, nipping on the
    	 * previous chunk index may be necessary.
    	 * It is guaranteed to be only context lines where left == right, so it
    	 * suffices to look on the left. */
    	const struct diff_chunk *first_chunk;
    	int chunk_start_line;
    	first_chunk = &result->chunks.head[cc->chunk.start];
    	chunk_start_line = diff_atom_root_idx(result->left,
    					      first_chunk->left_start);
    	if (cc->left.start < chunk_start_line) {
    		rc = diff_output_lines(outinfo, dest, " ",
    				  &result->left->atoms.head[cc->left.start],
    				  chunk_start_line - cc->left.start);
    		if (rc)
    			return rc;
    	}
    
    	/* Now write out all the joined chunks and contexts between them */
    	int c_idx;
    	for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) {
    		const struct diff_chunk *c = &result->chunks.head[c_idx];
    
    		if (c->left_count && c->right_count)
    			rc = diff_output_lines(outinfo, dest,
    					  c->solved ? " " : "?",
    					  c->left_start, c->left_count);
    		else if (c->left_count && !c->right_count)
    			rc = diff_output_lines(outinfo, dest,
    					  c->solved ? "-" : "?",
    					  c->left_start, c->left_count);
    		else if (c->right_count && !c->left_count)
    			rc = diff_output_lines(outinfo, dest,
    					  c->solved ? "+" : "?",
    					  c->right_start, c->right_count);
    		if (rc)
    			return rc;
    
    		if (cc->chunk.end == result->chunks.len) {
    			rc = diff_output_trailing_newline_msg(outinfo, dest, c);
    			if (rc != DIFF_RC_OK)
    				return rc;
    		}
    	}
    
    	/* Trailing context? */
    	const struct diff_chunk *last_chunk;
    	int chunk_end_line;
    	last_chunk = &result->chunks.head[cc->chunk.end - 1];
    	chunk_end_line = diff_atom_root_idx(result->left,
    					    last_chunk->left_start
    					    + last_chunk->left_count);
    	if (cc->left.end > chunk_end_line) {
    		rc = diff_output_lines(outinfo, dest, " ",
    				  &result->left->atoms.head[chunk_end_line],
    				  cc->left.end - chunk_end_line);
    		if (rc)
    			return rc;
    	}
    
    	return DIFF_RC_OK;
    }
    
    int
    diff_output_unidiff_chunk(struct diff_output_info **output_info, FILE *dest,
    			  struct diff_output_unidiff_state *state,
    			  const struct diff_input_info *info,
    			  const struct diff_result *result,
    			  const struct diff_chunk_context *cc)
    {
    	struct diff_output_info *outinfo = NULL;
    	int flags = (result->left->root->diff_flags |
    	    result->right->root->diff_flags);
    	bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES);
    
    	if (output_info) {
    		*output_info = diff_output_info_alloc();
    		if (*output_info == NULL)
    			return ENOMEM;
    		outinfo = *output_info;
    	}
    
    	return output_unidiff_chunk(outinfo, dest, state, info,
    	    result, false, show_function_prototypes, cc);
    }
    
    int
    diff_output_unidiff(struct diff_output_info **output_info,
    		    FILE *dest, const struct diff_input_info *info,
    		    const struct diff_result *result,
    		    unsigned int context_lines)
    {
    	struct diff_output_unidiff_state *state;
    	struct diff_chunk_context cc = {};
    	struct diff_output_info *outinfo = NULL;
    	int atomizer_flags = (result->left->atomizer_flags|
    	    result->right->atomizer_flags);
    	int flags = (result->left->root->diff_flags |
    	    result->right->root->diff_flags);
    	bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES);
    	bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA);
    	bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA);
    	int i;
    
    	if (!result)
    		return EINVAL;
    	if (result->rc != DIFF_RC_OK)
    		return result->rc;
    
    	if (output_info) {
    		*output_info = diff_output_info_alloc();
    		if (*output_info == NULL)
    			return ENOMEM;
    		outinfo = *output_info;
    	}
    
    	if (have_binary && !force_text) {
    		for (i = 0; i < result->chunks.len; i++) {
    			struct diff_chunk *c = &result->chunks.head[i];
    			enum diff_chunk_type t = diff_chunk_type(c);
    
    			if (t != CHUNK_MINUS && t != CHUNK_PLUS)
    				continue;
    
    			fprintf(dest, "Binary files %s and %s differ\n",
    			    diff_output_get_label_left(info),
    			    diff_output_get_label_right(info));
    			break;
    		}
    
    		return DIFF_RC_OK;
    	}
    
    	state = diff_output_unidiff_state_alloc();
    	if (state == NULL) {
    		if (output_info) {
    			diff_output_info_free(*output_info);
    			*output_info = NULL;
    		}
    		return ENOMEM;
    	}
    
    #if DEBUG
    	unsigned int check_left_pos, check_right_pos;
    	check_left_pos = 0;
    	check_right_pos = 0;
    	for (i = 0; i < result->chunks.len; i++) {
    		struct diff_chunk *c = &result->chunks.head[i];
    		enum diff_chunk_type t = diff_chunk_type(c);
    
    		debug("[%d] %s lines L%d R%d @L %d @R %d\n",
    		      i, (t == CHUNK_MINUS ? "minus" :
    			  (t == CHUNK_PLUS ? "plus" :
    			   (t == CHUNK_SAME ? "same" : "?"))),
    		      c->left_count,
    		      c->right_count,
    		      c->left_start ? diff_atom_root_idx(result->left, c->left_start) : -1,
    		      c->right_start ? diff_atom_root_idx(result->right, c->right_start) : -1);
    		assert(check_left_pos == diff_atom_root_idx(result->left, c->left_start));
    		assert(check_right_pos == diff_atom_root_idx(result->right, c->right_start));
    		check_left_pos += c->left_count;
    		check_right_pos += c->right_count;
    
    	}
    	assert(check_left_pos == result->left->atoms.len);
    	assert(check_right_pos == result->right->atoms.len);
    #endif
    
    	for (i = 0; i < result->chunks.len; i++) {
    		struct diff_chunk *c = &result->chunks.head[i];
    		enum diff_chunk_type t = diff_chunk_type(c);
    		struct diff_chunk_context next;
    
    		if (t != CHUNK_MINUS && t != CHUNK_PLUS)
    			continue;
    
    		if (diff_chunk_context_empty(&cc)) {
    			/* These are the first lines being printed.
    			 * Note down the start point, any number of subsequent
    			 * chunks may be joined up to this unidiff chunk by
    			 * context lines or by being directly adjacent. */
    			diff_chunk_context_get(&cc, result, i, context_lines);
    			debug("new chunk to be printed:"
    			      " chunk %d-%d left %d-%d right %d-%d\n",
    			      cc.chunk.start, cc.chunk.end,
    			      cc.left.start, cc.left.end,
    			      cc.right.start, cc.right.end);
    			continue;
    		}
    
    		/* There already is a previous chunk noted down for being
    		 * printed. Does it join up with this one? */
    		diff_chunk_context_get(&next, result, i, context_lines);
    		debug("new chunk to be printed:"
    		      " chunk %d-%d left %d-%d right %d-%d\n",
    		      next.chunk.start, next.chunk.end,
    		      next.left.start, next.left.end,
    		      next.right.start, next.right.end);
    
    		if (diff_chunk_contexts_touch(&cc, &next)) {
    			/* This next context touches or overlaps the previous
    			 * one, join. */
    			diff_chunk_contexts_merge(&cc, &next);
    			debug("new chunk to be printed touches previous chunk,"
    			      " now: left %d-%d right %d-%d\n",
    			      cc.left.start, cc.left.end,
    			      cc.right.start, cc.right.end);
    			continue;
    		}
    
    		/* No touching, so the previous context is complete with a gap
    		 * between it and this next one. Print the previous one and
    		 * start fresh here. */
    		debug("new chunk to be printed does not touch previous chunk;"
    		      " print left %d-%d right %d-%d\n",
    		      cc.left.start, cc.left.end, cc.right.start, cc.right.end);
    		output_unidiff_chunk(outinfo, dest, state, info, result,
    		    true, show_function_prototypes, &cc);
    		cc = next;
    		debug("new unprinted chunk is left %d-%d right %d-%d\n",
    		      cc.left.start, cc.left.end, cc.right.start, cc.right.end);
    	}
    
    	if (!diff_chunk_context_empty(&cc))
    		output_unidiff_chunk(outinfo, dest, state, info, result,
    		    true, show_function_prototypes, &cc);
    	diff_output_unidiff_state_free(state);
    	return DIFF_RC_OK;
    }