Path: blob/main/contrib/libdiff/lib/diff_output_unidiff.c
35065 views
/* Produce a unidiff output from a diff_result. */1/*2* Copyright (c) 2020 Neels Hofmeyr <[email protected]>3*4* Permission to use, copy, modify, and distribute this software for any5* purpose with or without fee is hereby granted, provided that the above6* copyright notice and this permission notice appear in all copies.7*8* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES9* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF10* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR11* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES12* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN13* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF14* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.15*/1617#include <errno.h>18#include <stdbool.h>19#include <stdint.h>20#include <stdio.h>21#include <stdlib.h>22#include <string.h>23#include <assert.h>2425#include <arraylist.h>26#include <diff_main.h>27#include <diff_output.h>2829#include "diff_internal.h"30#include "diff_debug.h"3132off_t33diff_chunk_get_left_start_pos(const struct diff_chunk *c)34{35return c->left_start->pos;36}3738off_t39diff_chunk_get_right_start_pos(const struct diff_chunk *c)40{41return c->right_start->pos;42}4344bool45diff_chunk_context_empty(const struct diff_chunk_context *cc)46{47return diff_range_empty(&cc->chunk);48}4950int51diff_chunk_get_left_start(const struct diff_chunk *c,52const struct diff_result *r, int context_lines)53{54int left_start = diff_atom_root_idx(r->left, c->left_start);55return MAX(0, left_start - context_lines);56}5758int59diff_chunk_get_left_end(const struct diff_chunk *c,60const struct diff_result *r, int context_lines)61{62int left_start = diff_chunk_get_left_start(c, r, 0);63return MIN(r->left->atoms.len,64left_start + c->left_count + context_lines);65}6667int68diff_chunk_get_right_start(const struct diff_chunk *c,69const struct diff_result *r, int context_lines)70{71int right_start = diff_atom_root_idx(r->right, c->right_start);72return MAX(0, right_start - context_lines);73}7475int76diff_chunk_get_right_end(const struct diff_chunk *c,77const struct diff_result *r, int context_lines)78{79int right_start = diff_chunk_get_right_start(c, r, 0);80return MIN(r->right->atoms.len,81right_start + c->right_count + context_lines);82}8384struct diff_chunk *85diff_chunk_get(const struct diff_result *r, int chunk_idx)86{87return &r->chunks.head[chunk_idx];88}8990int91diff_chunk_get_left_count(struct diff_chunk *c)92{93return c->left_count;94}9596int97diff_chunk_get_right_count(struct diff_chunk *c)98{99return c->right_count;100}101102void103diff_chunk_context_get(struct diff_chunk_context *cc, const struct diff_result *r,104int chunk_idx, int context_lines)105{106const struct diff_chunk *c = &r->chunks.head[chunk_idx];107int left_start = diff_chunk_get_left_start(c, r, context_lines);108int left_end = diff_chunk_get_left_end(c, r, context_lines);109int right_start = diff_chunk_get_right_start(c, r, context_lines);110int right_end = diff_chunk_get_right_end(c, r, context_lines);111112*cc = (struct diff_chunk_context){113.chunk = {114.start = chunk_idx,115.end = chunk_idx + 1,116},117.left = {118.start = left_start,119.end = left_end,120},121.right = {122.start = right_start,123.end = right_end,124},125};126}127128bool129diff_chunk_contexts_touch(const struct diff_chunk_context *cc,130const struct diff_chunk_context *other)131{132return diff_ranges_touch(&cc->chunk, &other->chunk)133|| diff_ranges_touch(&cc->left, &other->left)134|| diff_ranges_touch(&cc->right, &other->right);135}136137void138diff_chunk_contexts_merge(struct diff_chunk_context *cc,139const struct diff_chunk_context *other)140{141diff_ranges_merge(&cc->chunk, &other->chunk);142diff_ranges_merge(&cc->left, &other->left);143diff_ranges_merge(&cc->right, &other->right);144}145146void147diff_chunk_context_load_change(struct diff_chunk_context *cc,148int *nchunks_used,149struct diff_result *result,150int start_chunk_idx,151int context_lines)152{153int i;154int seen_minus = 0, seen_plus = 0;155156if (nchunks_used)157*nchunks_used = 0;158159for (i = start_chunk_idx; i < result->chunks.len; i++) {160struct diff_chunk *chunk = &result->chunks.head[i];161enum diff_chunk_type t = diff_chunk_type(chunk);162struct diff_chunk_context next;163164if (t != CHUNK_MINUS && t != CHUNK_PLUS) {165if (nchunks_used)166(*nchunks_used)++;167if (seen_minus || seen_plus)168break;169else170continue;171} else if (t == CHUNK_MINUS)172seen_minus = 1;173else if (t == CHUNK_PLUS)174seen_plus = 1;175176if (diff_chunk_context_empty(cc)) {177/* Note down the start point, any number of subsequent178* chunks may be joined up to this chunk by being179* directly adjacent. */180diff_chunk_context_get(cc, result, i, context_lines);181if (nchunks_used)182(*nchunks_used)++;183continue;184}185186/* There already is a previous chunk noted down for being187* printed. Does it join up with this one? */188diff_chunk_context_get(&next, result, i, context_lines);189190if (diff_chunk_contexts_touch(cc, &next)) {191/* This next context touches or overlaps the previous192* one, join. */193diff_chunk_contexts_merge(cc, &next);194if (nchunks_used)195(*nchunks_used)++;196continue;197} else198break;199}200}201202struct diff_output_unidiff_state {203bool header_printed;204char prototype[DIFF_FUNCTION_CONTEXT_SIZE];205int last_prototype_idx;206};207208struct diff_output_unidiff_state *209diff_output_unidiff_state_alloc(void)210{211struct diff_output_unidiff_state *state;212213state = calloc(1, sizeof(struct diff_output_unidiff_state));214if (state != NULL)215diff_output_unidiff_state_reset(state);216return state;217}218219void220diff_output_unidiff_state_reset(struct diff_output_unidiff_state *state)221{222state->header_printed = false;223memset(state->prototype, 0, sizeof(state->prototype));224state->last_prototype_idx = 0;225}226227void228diff_output_unidiff_state_free(struct diff_output_unidiff_state *state)229{230free(state);231}232233static int234output_unidiff_chunk(struct diff_output_info *outinfo, FILE *dest,235struct diff_output_unidiff_state *state,236const struct diff_input_info *info,237const struct diff_result *result,238bool print_header, bool show_function_prototypes,239const struct diff_chunk_context *cc)240{241int rc, left_start, left_len, right_start, right_len;242off_t outoff = 0, *offp;243uint8_t *typep;244245if (diff_range_empty(&cc->left) && diff_range_empty(&cc->right))246return DIFF_RC_OK;247248if (outinfo && outinfo->line_offsets.len > 0) {249unsigned int idx = outinfo->line_offsets.len - 1;250outoff = outinfo->line_offsets.head[idx];251}252253if (print_header && !(state->header_printed)) {254rc = fprintf(dest, "--- %s\n",255diff_output_get_label_left(info));256if (rc < 0)257return errno;258if (outinfo) {259ARRAYLIST_ADD(offp, outinfo->line_offsets);260if (offp == NULL)261return ENOMEM;262outoff += rc;263*offp = outoff;264ARRAYLIST_ADD(typep, outinfo->line_types);265if (typep == NULL)266return ENOMEM;267*typep = DIFF_LINE_MINUS;268}269rc = fprintf(dest, "+++ %s\n",270diff_output_get_label_right(info));271if (rc < 0)272return errno;273if (outinfo) {274ARRAYLIST_ADD(offp, outinfo->line_offsets);275if (offp == NULL)276return ENOMEM;277outoff += rc;278*offp = outoff;279ARRAYLIST_ADD(typep, outinfo->line_types);280if (typep == NULL)281return ENOMEM;282*typep = DIFF_LINE_PLUS;283}284state->header_printed = true;285}286287left_len = cc->left.end - cc->left.start;288if (result->left->atoms.len == 0)289left_start = 0;290else if (left_len == 0 && cc->left.start > 0)291left_start = cc->left.start;292else293left_start = cc->left.start + 1;294295right_len = cc->right.end - cc->right.start;296if (result->right->atoms.len == 0)297right_start = 0;298else if (right_len == 0 && cc->right.start > 0)299right_start = cc->right.start;300else301right_start = cc->right.start + 1;302303/* Got the absolute line numbers where to start printing, and the index304* of the interesting (non-context) chunk.305* To print context lines above the interesting chunk, nipping on the306* previous chunk index may be necessary.307* It is guaranteed to be only context lines where left == right, so it308* suffices to look on the left. */309const struct diff_chunk *first_chunk;310int chunk_start_line;311first_chunk = &result->chunks.head[cc->chunk.start];312chunk_start_line = diff_atom_root_idx(result->left,313first_chunk->left_start);314if (show_function_prototypes) {315rc = diff_output_match_function_prototype(state->prototype,316sizeof(state->prototype), &state->last_prototype_idx,317result, chunk_start_line);318if (rc)319return rc;320}321322if (left_len == 1 && right_len == 1) {323rc = fprintf(dest, "@@ -%d +%d @@%s%s\n",324left_start, right_start,325state->prototype[0] ? " " : "",326state->prototype[0] ? state->prototype : "");327} else if (left_len == 1 && right_len != 1) {328rc = fprintf(dest, "@@ -%d +%d,%d @@%s%s\n",329left_start, right_start, right_len,330state->prototype[0] ? " " : "",331state->prototype[0] ? state->prototype : "");332} else if (left_len != 1 && right_len == 1) {333rc = fprintf(dest, "@@ -%d,%d +%d @@%s%s\n",334left_start, left_len, right_start,335state->prototype[0] ? " " : "",336state->prototype[0] ? state->prototype : "");337} else {338rc = fprintf(dest, "@@ -%d,%d +%d,%d @@%s%s\n",339left_start, left_len, right_start, right_len,340state->prototype[0] ? " " : "",341state->prototype[0] ? state->prototype : "");342}343if (rc < 0)344return errno;345if (outinfo) {346ARRAYLIST_ADD(offp, outinfo->line_offsets);347if (offp == NULL)348return ENOMEM;349outoff += rc;350*offp = outoff;351ARRAYLIST_ADD(typep, outinfo->line_types);352if (typep == NULL)353return ENOMEM;354*typep = DIFF_LINE_HUNK;355}356357if (cc->left.start < chunk_start_line) {358rc = diff_output_lines(outinfo, dest, " ",359&result->left->atoms.head[cc->left.start],360chunk_start_line - cc->left.start);361if (rc)362return rc;363}364365/* Now write out all the joined chunks and contexts between them */366int c_idx;367for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) {368const struct diff_chunk *c = &result->chunks.head[c_idx];369370if (c->left_count && c->right_count)371rc = diff_output_lines(outinfo, dest,372c->solved ? " " : "?",373c->left_start, c->left_count);374else if (c->left_count && !c->right_count)375rc = diff_output_lines(outinfo, dest,376c->solved ? "-" : "?",377c->left_start, c->left_count);378else if (c->right_count && !c->left_count)379rc = diff_output_lines(outinfo, dest,380c->solved ? "+" : "?",381c->right_start, c->right_count);382if (rc)383return rc;384385if (cc->chunk.end == result->chunks.len) {386rc = diff_output_trailing_newline_msg(outinfo, dest, c);387if (rc != DIFF_RC_OK)388return rc;389}390}391392/* Trailing context? */393const struct diff_chunk *last_chunk;394int chunk_end_line;395last_chunk = &result->chunks.head[cc->chunk.end - 1];396chunk_end_line = diff_atom_root_idx(result->left,397last_chunk->left_start398+ last_chunk->left_count);399if (cc->left.end > chunk_end_line) {400rc = diff_output_lines(outinfo, dest, " ",401&result->left->atoms.head[chunk_end_line],402cc->left.end - chunk_end_line);403if (rc)404return rc;405406if (cc->left.end == result->left->atoms.len) {407rc = diff_output_trailing_newline_msg(outinfo, dest,408&result->chunks.head[result->chunks.len - 1]);409if (rc != DIFF_RC_OK)410return rc;411}412}413414return DIFF_RC_OK;415}416417int418diff_output_unidiff_chunk(struct diff_output_info **output_info, FILE *dest,419struct diff_output_unidiff_state *state,420const struct diff_input_info *info,421const struct diff_result *result,422const struct diff_chunk_context *cc)423{424struct diff_output_info *outinfo = NULL;425int flags = (result->left->root->diff_flags |426result->right->root->diff_flags);427bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES);428429if (output_info) {430*output_info = diff_output_info_alloc();431if (*output_info == NULL)432return ENOMEM;433outinfo = *output_info;434}435436return output_unidiff_chunk(outinfo, dest, state, info,437result, false, show_function_prototypes, cc);438}439440int441diff_output_unidiff(struct diff_output_info **output_info,442FILE *dest, const struct diff_input_info *info,443const struct diff_result *result,444unsigned int context_lines)445{446struct diff_output_unidiff_state *state;447struct diff_chunk_context cc = {};448struct diff_output_info *outinfo = NULL;449int atomizer_flags = (result->left->atomizer_flags|450result->right->atomizer_flags);451int flags = (result->left->root->diff_flags |452result->right->root->diff_flags);453bool show_function_prototypes = (flags & DIFF_FLAG_SHOW_PROTOTYPES);454bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA);455bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA);456off_t outoff = 0, *offp;457uint8_t *typep;458int rc, i;459460if (!result)461return EINVAL;462if (result->rc != DIFF_RC_OK)463return result->rc;464465if (output_info) {466*output_info = diff_output_info_alloc();467if (*output_info == NULL)468return ENOMEM;469outinfo = *output_info;470}471472if (have_binary && !force_text) {473for (i = 0; i < result->chunks.len; i++) {474struct diff_chunk *c = &result->chunks.head[i];475enum diff_chunk_type t = diff_chunk_type(c);476477if (t != CHUNK_MINUS && t != CHUNK_PLUS)478continue;479480if (outinfo && outinfo->line_offsets.len > 0) {481unsigned int idx =482outinfo->line_offsets.len - 1;483outoff = outinfo->line_offsets.head[idx];484}485486rc = fprintf(dest, "Binary files %s and %s differ\n",487diff_output_get_label_left(info),488diff_output_get_label_right(info));489if (outinfo) {490ARRAYLIST_ADD(offp, outinfo->line_offsets);491if (offp == NULL)492return ENOMEM;493outoff += rc;494*offp = outoff;495ARRAYLIST_ADD(typep, outinfo->line_types);496if (typep == NULL)497return ENOMEM;498*typep = DIFF_LINE_NONE;499}500break;501}502503return DIFF_RC_OK;504}505506state = diff_output_unidiff_state_alloc();507if (state == NULL) {508if (output_info) {509diff_output_info_free(*output_info);510*output_info = NULL;511}512return ENOMEM;513}514515#if DEBUG516unsigned int check_left_pos, check_right_pos;517check_left_pos = 0;518check_right_pos = 0;519for (i = 0; i < result->chunks.len; i++) {520struct diff_chunk *c = &result->chunks.head[i];521enum diff_chunk_type t = diff_chunk_type(c);522523debug("[%d] %s lines L%d R%d @L %d @R %d\n",524i, (t == CHUNK_MINUS ? "minus" :525(t == CHUNK_PLUS ? "plus" :526(t == CHUNK_SAME ? "same" : "?"))),527c->left_count,528c->right_count,529c->left_start ? diff_atom_root_idx(result->left, c->left_start) : -1,530c->right_start ? diff_atom_root_idx(result->right, c->right_start) : -1);531assert(check_left_pos == diff_atom_root_idx(result->left, c->left_start));532assert(check_right_pos == diff_atom_root_idx(result->right, c->right_start));533check_left_pos += c->left_count;534check_right_pos += c->right_count;535536}537assert(check_left_pos == result->left->atoms.len);538assert(check_right_pos == result->right->atoms.len);539#endif540541for (i = 0; i < result->chunks.len; i++) {542struct diff_chunk *c = &result->chunks.head[i];543enum diff_chunk_type t = diff_chunk_type(c);544struct diff_chunk_context next;545546if (t != CHUNK_MINUS && t != CHUNK_PLUS)547continue;548549if (diff_chunk_context_empty(&cc)) {550/* These are the first lines being printed.551* Note down the start point, any number of subsequent552* chunks may be joined up to this unidiff chunk by553* context lines or by being directly adjacent. */554diff_chunk_context_get(&cc, result, i, context_lines);555debug("new chunk to be printed:"556" chunk %d-%d left %d-%d right %d-%d\n",557cc.chunk.start, cc.chunk.end,558cc.left.start, cc.left.end,559cc.right.start, cc.right.end);560continue;561}562563/* There already is a previous chunk noted down for being564* printed. Does it join up with this one? */565diff_chunk_context_get(&next, result, i, context_lines);566debug("new chunk to be printed:"567" chunk %d-%d left %d-%d right %d-%d\n",568next.chunk.start, next.chunk.end,569next.left.start, next.left.end,570next.right.start, next.right.end);571572if (diff_chunk_contexts_touch(&cc, &next)) {573/* This next context touches or overlaps the previous574* one, join. */575diff_chunk_contexts_merge(&cc, &next);576debug("new chunk to be printed touches previous chunk,"577" now: left %d-%d right %d-%d\n",578cc.left.start, cc.left.end,579cc.right.start, cc.right.end);580continue;581}582583/* No touching, so the previous context is complete with a gap584* between it and this next one. Print the previous one and585* start fresh here. */586debug("new chunk to be printed does not touch previous chunk;"587" print left %d-%d right %d-%d\n",588cc.left.start, cc.left.end, cc.right.start, cc.right.end);589output_unidiff_chunk(outinfo, dest, state, info, result,590true, show_function_prototypes, &cc);591cc = next;592debug("new unprinted chunk is left %d-%d right %d-%d\n",593cc.left.start, cc.left.end, cc.right.start, cc.right.end);594}595596if (!diff_chunk_context_empty(&cc))597output_unidiff_chunk(outinfo, dest, state, info, result,598true, show_function_prototypes, &cc);599diff_output_unidiff_state_free(state);600return DIFF_RC_OK;601}602603604