1 /* Output all lines of a diff_result. */ 2 /* 3 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de> 4 * 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. 8 * 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. 16 */ 17 18 #include <errno.h> 19 #include <stdint.h> 20 #include <stdio.h> 21 #include <stdbool.h> 22 #include <stdlib.h> 23 24 #include <arraylist.h> 25 #include <diff_main.h> 26 #include <diff_output.h> 27 28 #include "diff_internal.h" 29 30 static int 31 output_plain_chunk(struct diff_output_info *outinfo, 32 FILE *dest, const struct diff_input_info *info, 33 const struct diff_result *result, 34 struct diff_chunk_context *cc, off_t *outoff, bool headers_only) 35 { 36 off_t *offp; 37 int left_start, left_len, right_start, right_len; 38 int rc; 39 bool change = false; 40 41 left_len = cc->left.end - cc->left.start; 42 if (left_len < 0) 43 return EINVAL; 44 else if (result->left->atoms.len == 0) 45 left_start = 0; 46 else if (left_len == 0 && cc->left.start > 0) 47 left_start = cc->left.start; 48 else if (cc->left.end > 0) 49 left_start = cc->left.start + 1; 50 else 51 left_start = cc->left.start; 52 53 right_len = cc->right.end - cc->right.start; 54 if (right_len < 0) 55 return EINVAL; 56 else if (result->right->atoms.len == 0) 57 right_start = 0; 58 else if (right_len == 0 && cc->right.start > 0) 59 right_start = cc->right.start; 60 else if (cc->right.end > 0) 61 right_start = cc->right.start + 1; 62 else 63 right_start = cc->right.start; 64 65 if (left_len == 0) { 66 /* addition */ 67 if (right_len == 1) { 68 rc = fprintf(dest, "%da%d\n", left_start, right_start); 69 } else { 70 rc = fprintf(dest, "%da%d,%d\n", left_start, 71 right_start, cc->right.end); 72 } 73 } else if (right_len == 0) { 74 /* deletion */ 75 if (left_len == 1) { 76 rc = fprintf(dest, "%dd%d\n", left_start, 77 right_start); 78 } else { 79 rc = fprintf(dest, "%d,%dd%d\n", left_start, 80 cc->left.end, right_start); 81 } 82 } else { 83 /* change */ 84 change = true; 85 if (left_len == 1 && right_len == 1) { 86 rc = fprintf(dest, "%dc%d\n", left_start, right_start); 87 } else if (left_len == 1) { 88 rc = fprintf(dest, "%dc%d,%d\n", left_start, 89 right_start, cc->right.end); 90 } else if (right_len == 1) { 91 rc = fprintf(dest, "%d,%dc%d\n", left_start, 92 cc->left.end, right_start); 93 } else { 94 rc = fprintf(dest, "%d,%dc%d,%d\n", left_start, 95 cc->left.end, right_start, cc->right.end); 96 } 97 } 98 if (rc < 0) 99 return errno; 100 if (outinfo) { 101 ARRAYLIST_ADD(offp, outinfo->line_offsets); 102 if (offp == NULL) 103 return ENOMEM; 104 *outoff += rc; 105 *offp = *outoff; 106 } 107 108 /* 109 * Now write out all the joined chunks. 110 * 111 * If the hunk denotes a change, it will come in the form of a deletion 112 * chunk followed by a addition chunk. Print a marker to break up the 113 * additions and deletions when this happens. 114 */ 115 int c_idx; 116 for (c_idx = cc->chunk.start; !headers_only && c_idx < cc->chunk.end; 117 c_idx++) { 118 const struct diff_chunk *c = &result->chunks.head[c_idx]; 119 if (c->left_count && !c->right_count) 120 rc = diff_output_lines(outinfo, dest, 121 c->solved ? "< " : "?", 122 c->left_start, c->left_count); 123 else if (c->right_count && !c->left_count) { 124 if (change) { 125 rc = fprintf(dest, "---\n"); 126 if (rc < 0) 127 return errno; 128 if (outinfo) { 129 ARRAYLIST_ADD(offp, 130 outinfo->line_offsets); 131 if (offp == NULL) 132 return ENOMEM; 133 *outoff += rc; 134 *offp = *outoff; 135 } 136 } 137 rc = diff_output_lines(outinfo, dest, 138 c->solved ? "> " : "?", 139 c->right_start, c->right_count); 140 } 141 if (rc) 142 return rc; 143 if (cc->chunk.end == result->chunks.len) { 144 rc = diff_output_trailing_newline_msg(outinfo, dest, c); 145 if (rc != DIFF_RC_OK) 146 return rc; 147 } 148 } 149 150 return DIFF_RC_OK; 151 } 152 153 int 154 diff_output_plain(struct diff_output_info **output_info, 155 FILE *dest, const struct diff_input_info *info, 156 const struct diff_result *result, int hunk_headers_only) 157 { 158 struct diff_output_info *outinfo = NULL; 159 struct diff_chunk_context cc = {}; 160 int atomizer_flags = (result->left->atomizer_flags| 161 result->right->atomizer_flags); 162 int flags = (result->left->root->diff_flags | 163 result->right->root->diff_flags); 164 bool force_text = (flags & DIFF_FLAG_FORCE_TEXT_DATA); 165 bool have_binary = (atomizer_flags & DIFF_ATOMIZER_FOUND_BINARY_DATA); 166 int i, rc; 167 off_t outoff = 0, *offp; 168 169 if (!result) 170 return EINVAL; 171 if (result->rc != DIFF_RC_OK) 172 return result->rc; 173 174 if (output_info) { 175 *output_info = diff_output_info_alloc(); 176 if (*output_info == NULL) 177 return ENOMEM; 178 outinfo = *output_info; 179 } 180 181 if (have_binary && !force_text) { 182 for (i = 0; i < result->chunks.len; i++) { 183 struct diff_chunk *c = &result->chunks.head[i]; 184 enum diff_chunk_type t = diff_chunk_type(c); 185 186 if (t != CHUNK_MINUS && t != CHUNK_PLUS) 187 continue; 188 189 rc = fprintf(dest, "Binary files %s and %s differ\n", 190 diff_output_get_label_left(info), 191 diff_output_get_label_right(info)); 192 if (rc < 0) 193 return errno; 194 if (outinfo) { 195 ARRAYLIST_ADD(offp, outinfo->line_offsets); 196 if (offp == NULL) 197 return ENOMEM; 198 outoff += rc; 199 *offp = outoff; 200 } 201 break; 202 } 203 204 return DIFF_RC_OK; 205 } 206 207 for (i = 0; i < result->chunks.len; i++) { 208 struct diff_chunk *chunk = &result->chunks.head[i]; 209 enum diff_chunk_type t = diff_chunk_type(chunk); 210 struct diff_chunk_context next; 211 212 if (t != CHUNK_MINUS && t != CHUNK_PLUS) 213 continue; 214 215 if (diff_chunk_context_empty(&cc)) { 216 /* Note down the start point, any number of subsequent 217 * chunks may be joined up to this chunk by being 218 * directly adjacent. */ 219 diff_chunk_context_get(&cc, result, i, 0); 220 continue; 221 } 222 223 /* There already is a previous chunk noted down for being 224 * printed. Does it join up with this one? */ 225 diff_chunk_context_get(&next, result, i, 0); 226 227 if (diff_chunk_contexts_touch(&cc, &next)) { 228 /* This next context touches or overlaps the previous 229 * one, join. */ 230 diff_chunk_contexts_merge(&cc, &next); 231 /* When we merge the last chunk we can end up with one 232 * hanging chunk and have to come back for it after the 233 * loop */ 234 continue; 235 } 236 rc = output_plain_chunk(outinfo, dest, info, result, &cc, 237 &outoff, hunk_headers_only); 238 if (rc != DIFF_RC_OK) 239 return rc; 240 cc = next; 241 } 242 if (!diff_chunk_context_empty(&cc)) 243 return output_plain_chunk(outinfo, dest, info, result, &cc, 244 &outoff, hunk_headers_only); 245 return DIFF_RC_OK; 246 } 247