1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 Tobias Kortkamp <tobik@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "config.h"
30 
31 #include <sys/param.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "array.h"
37 #include "color.h"
38 #include "diff.h"
39 #include "diffutil.h"
40 #include "flow.h"
41 #include "mempool.h"
42 #include "str.h"
43 
44 struct Hunk {
45 	size_t start;
46 	size_t end;
47 };
48 
49 // Prototypes
50 DECLARE_COMPARE(by_start);
51 static struct Array *get_hunks(struct Mempool *, struct diff *, size_t);
52 
DEFINE_COMPARE(by_start,struct Hunk,void)53 DEFINE_COMPARE(by_start, struct Hunk, void)
54 {
55 	if (a->start < b->start) {
56 		return -1;
57 	} else if (a->start > b->start) {
58 		return 1;
59 	} else {
60 		return 0;
61 	}
62 }
63 
64 struct Array *
get_hunks(struct Mempool * pool,struct diff * p,size_t context)65 get_hunks(struct Mempool *pool, struct diff *p, size_t context)
66 {
67 	panic_unless(p->sessz > 0, "shortest common subsequence length is 0");
68 	if (context == 0) {
69 		struct Array *hunks = mempool_array(pool);
70 		struct Hunk *h = mempool_alloc(pool, sizeof(struct Hunk));
71 		h->start = 0;
72 		h->end = p->sessz - 1;
73 		array_append(hunks, h);
74 		return hunks;
75 	}
76 
77 	struct Array *edit_ranges = mempool_array(pool);
78 	struct Hunk *h = mempool_alloc(pool, sizeof(struct Hunk));
79 	bool first = true;
80 	bool last_common = true;
81 	for (size_t i = 0; i < p->sessz; i++) {
82 		switch (p->ses[i].type) {
83 		case DIFF_COMMON:
84 			if (!last_common) {
85 				array_append(edit_ranges, h);
86 				h = mempool_alloc(pool, sizeof(struct Hunk));
87 			}
88 			last_common = true;
89 			first = true;
90 			break;
91 		case DIFF_ADD:
92 		case DIFF_DELETE:
93 			last_common = false;
94 			if (first) {
95 				h->start = i;
96 				first = false;
97 			}
98 			h->end = i;
99 			break;
100 		}
101 	}
102 	if (!first) {
103 		array_append(edit_ranges, h);
104 	}
105 
106 	ARRAY_FOREACH(edit_ranges, struct Hunk *, h) {
107 		if (h->start > context) {
108 			h->start -= context;
109 		}
110 		h->end = MIN(h->end + context, p->sessz - 1);
111 	}
112 	array_sort(edit_ranges, by_start, NULL);
113 
114 	struct Array *hunks = mempool_array(pool);
115 	struct Hunk *last = NULL;
116 	ARRAY_FOREACH(edit_ranges, struct Hunk *, h) {
117 		unless (last) {
118 			last = h;
119 			continue;
120 		} else {
121 			if (h->start <= last->end) {
122 				if (h->end > last->end) {
123 					last->end = h->end;
124 				}
125 			} else if (h->start > last->end) {
126 				array_append(hunks, last);
127 				last = h;
128 			}
129 		}
130 	}
131 	if (last) {
132 		array_append(hunks, last);
133 	}
134 
135 	panic_unless(array_len(hunks) > 0, "no hunks found");
136 	return hunks;
137 }
138 
139 
140 char *
diff_to_patch(struct diff * p,struct Mempool * extpool,TostringFn tostring,void * tostring_userdata,size_t context,bool color)141 diff_to_patch(struct diff *p, struct Mempool *extpool, TostringFn tostring, void *tostring_userdata, size_t context, bool color)
142 {
143 	if (p->sessz == 0) {
144 		return NULL;
145 	}
146 
147 	SCOPE_MEMPOOL(pool);
148 	const char *color_add = ANSI_COLOR_GREEN;
149 	const char *color_context = ANSI_COLOR_CYAN;
150 	const char *color_delete = ANSI_COLOR_RED;
151 	const char *color_reset = ANSI_COLOR_RESET;
152 	if (!color) {
153 		color_add = color_context = color_delete = color_reset = "";
154 	}
155 
156 	struct Array *hunks = get_hunks(pool, p, context);
157 	struct Array *result = mempool_array(pool);
158 	ARRAY_FOREACH(hunks, struct Hunk *, h) {
159 		size_t origin_len = 0;
160 		size_t target_len = 0;
161 		for (size_t i = h->start; i <= h->end; i++) {
162 			switch (p->ses[i].type) {
163 			case DIFF_ADD:
164 				target_len++;
165 				break;
166 			case DIFF_COMMON:
167 				origin_len++;
168 				target_len++;
169 				break;
170 			case DIFF_DELETE:
171 				origin_len++;
172 				break;
173 			}
174 		}
175 		size_t origin_start = p->ses[h->start].originIdx;
176 		if (origin_start == 0) {
177 			origin_start = 1;
178 		}
179 		size_t target_start = p->ses[h->start].targetIdx;
180 		char *buf;
181 		if (origin_len > 1) {
182 			buf = str_printf(pool, "%s@@ -%zu,%zu", color_context, origin_start, origin_len);
183 		} else {
184 			buf = str_printf(pool, "%s@@ -%zu", color_context, origin_start);
185 		}
186 		array_append(result, buf);
187 		if (target_len > 1) {
188 			buf = str_printf(pool, " +%zu,%zu @@%s\n", target_start, target_len, color_reset);
189 		} else {
190 			buf = str_printf(pool, " +%zu @@%s\n", target_start, color_reset);
191 		}
192 		array_append(result, buf);
193 		for (size_t i = h->start; i <= h->end; i++) {
194 			char *line;
195 			if (tostring) {
196 				line = mempool_take(pool, tostring(*(void **)p->ses[i].e, tostring_userdata));
197 			} else {
198 				line = *(void **)p->ses[i].e;
199 			}
200 			switch (p->ses[i].type) {
201 			case DIFF_ADD:
202 				buf = str_printf(pool, "%s+%s%s\n", color_add, line, color_reset);
203 				break;
204 			case DIFF_COMMON:
205 				buf = str_printf(pool, " %s\n", line);
206 				break;
207 			case DIFF_DELETE:
208 				buf = str_printf(pool, "%s-%s%s\n", color_delete, line, color_reset);
209 				break;
210 			}
211 			array_append(result, buf);
212 		}
213 	}
214 
215 	return str_join(extpool, result, "");
216 }
217