1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2001-2006, 2008, 2012 Peter Miller
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see
17 // <http://www.gnu.org/licenses/>.
18 //
19
20 #include <common/ac/string.h>
21
22 #include <common/error.h>
23 #include <libaegis/patch.h>
24 #include <libaegis/patch/context.h>
25 #include <libaegis/patch/format/uni.h>
26 #include <common/trace.h>
27
28
29 static int
starts_with(string_ty * line,const char * prefix)30 starts_with(string_ty *line, const char *prefix)
31 {
32 size_t pfxlen;
33
34 pfxlen = strlen(prefix);
35 return
36 (
37 line->str_length > pfxlen + 1
38 &&
39 0 == memcmp(line->str_text, prefix, pfxlen)
40 &&
41 (
42 line->str_text[pfxlen] == ' '
43 ||
44 line->str_text[pfxlen] == '\t'
45 )
46 );
47 }
48
49
50 static string_ty *
second_word(string_ty * line)51 second_word(string_ty *line)
52 {
53 const char *cp;
54 const char *ep;
55
56 cp = line->str_text;
57 while (*cp && *cp != ' ' && *cp != '\t')
58 ++cp;
59 while (*cp && (*cp == ' ' || *cp == '\t'))
60 ++cp;
61 ep = cp;
62 while (*ep && *ep != ' ' && *ep != '\t')
63 ++ep;
64 return str_n_from_c(cp, ep - cp);
65 }
66
67
68 static patch_ty *
uni_diff_header(patch_context_ty * context)69 uni_diff_header(patch_context_ty *context)
70 {
71 string_ty *line;
72 int idx;
73 patch_ty *result;
74 string_ty *s;
75
76 trace(("uni_diff_header()\n{\n"));
77 result = patch_new();
78
79 //
80 // Look for the optional index line.
81 //
82 line = patch_context_getline(context, 0);
83 if (!line)
84 {
85 oops:
86 patch_delete(result);
87 trace(("return 0\n"));
88 trace(("}\n"));
89 return 0;
90 }
91 idx = 0;
92 if (starts_with(line, "Index:"))
93 {
94 s = second_word(line);
95 result->name.push_back(s);
96 str_free(s);
97 idx++;
98 }
99
100 //
101 // Look for the optional before line.
102 //
103 line = patch_context_getline(context, idx);
104 if (!line)
105 goto oops;
106 static string_ty *dev_null;
107 if (!dev_null)
108 dev_null = str_from_c("/dev/null");
109 if (starts_with(line, "---"))
110 {
111 s = second_word(line);
112 if (!str_equal(s, dev_null))
113 result->name.push_back(s);
114 str_free(s);
115 idx++;
116 }
117
118 //
119 // Look for the optional after line.
120 //
121 line = patch_context_getline(context, idx);
122 if (!line)
123 goto oops;
124 if (starts_with(line, "+++"))
125 {
126 s = second_word(line);
127 if (!str_equal(s, dev_null))
128 result->name.push_back(s);
129 str_free(s);
130 idx++;
131 }
132
133 //
134 // If there are no names at all,
135 // this isn't one of our files.
136 //
137 if (result->name.nstrings == 0)
138 goto oops;
139
140 //
141 // Look for a line which starts with "@@"
142 //
143 line = patch_context_getline(context, idx);
144 if (!starts_with(line, "@@"))
145 goto oops;
146
147 //
148 // Discard all of the header lines, except @@ line
149 // (it's actually part of the first hunk).
150 //
151 patch_context_discard(context, idx);
152 trace(("return %p\n", result));
153 trace(("}\n"));
154 return result;
155 }
156
157
158 static const char *
getnum(const char * cp,int * np)159 getnum(const char *cp, int *np)
160 {
161 int n;
162
163 switch (*cp)
164 {
165 default:
166 return 0;
167
168 case '0': case '1': case '2': case '3': case '4':
169 case '5': case '6': case '7': case '8': case '9':
170 break;
171 }
172 n = 0;
173 for (;;)
174 {
175 n = n * 10 + *cp++ - '0';
176 switch (*cp)
177 {
178 default:
179 break;
180
181 case '0': case '1': case '2': case '3': case '4':
182 case '5': case '6': case '7': case '8': case '9':
183 continue;
184 }
185 break;
186 }
187 *np = n;
188 return cp;
189 }
190
191
192 static int
range(string_ty * line,int * b1,int * b2,int * a1,int * a2)193 range(string_ty *line, int *b1, int *b2, int *a1, int *a2)
194 {
195 const char *cp;
196
197 //
198 // The lines are of the form
199 // @@ -N[,N] +N[,N] @@
200 //
201 // The first pair of numbers is a start line and a line count
202 // (the count defaults to 1 if not given) of the first file,
203 // the second pair of numbers applies to the second file.
204 //
205 trace(("range(line = \"%s\")\n{\n", line->str_text));
206 if (line->str_length < 11)
207 {
208 oops:
209 trace(("return 0;\n}\n"));
210 return 0;
211 }
212 cp = line->str_text;
213 if (*cp++ != '@')
214 goto oops;
215 if (*cp++ != '@')
216 goto oops;
217 if (*cp++ != ' ')
218 goto oops;
219 if (*cp++ != '-')
220 goto oops;
221
222 cp = getnum(cp, b1);
223 if (!cp)
224 goto oops;
225 if (*cp == ',')
226 {
227 ++cp;
228 cp = getnum(cp, b2);
229 if (!cp)
230 goto oops;
231 }
232 else
233 *b2 = 1;
234
235 if (*cp++ != ' ')
236 goto oops;
237 if (*cp++ != '+')
238 goto oops;
239
240 cp = getnum(cp, a1);
241 if (!cp)
242 goto oops;
243 if (*cp == ',')
244 {
245 ++cp;
246 cp = getnum(cp, a2);
247 if (!cp)
248 goto oops;
249 }
250 else
251 *a2 = 1;
252
253 if (*cp++ != ' ')
254 goto oops;
255 if (*cp++ != '@')
256 goto oops;
257 if (*cp++ != '@')
258 goto oops;
259 if (*cp)
260 goto oops;
261 trace(("return 1;\n}\n"));
262 return 1;
263 }
264
265
266 static patch_hunk_ty *
uni_diff_hunk(patch_context_ty * context)267 uni_diff_hunk(patch_context_ty *context)
268 {
269 int before1, num_before;
270 int after1, num_after;
271 string_ty *line;
272 patch_hunk_ty *php;
273 int idx;
274
275 trace(("uni_diff_hunk()\n{\n"));
276 line = patch_context_getline(context, 0);
277 if (!line)
278 {
279 oops:
280 trace(("return 0;\n}\n"));
281 return 0;
282 }
283 if (!range(line, &before1, &num_before, &after1, &num_after))
284 goto oops;
285 idx = 1;
286
287 php = patch_hunk_new();
288 php->before.start_line_number = before1;
289 php->after.start_line_number = after1;
290
291 while (num_before > 0 || num_after > 0)
292 {
293 patch_line_type type;
294 string_ty *value;
295
296 trace(("num_before=%d\n", num_before));
297 trace(("num_after =%d\n", num_after));
298 line = patch_context_getline(context, idx++);
299 if (!line)
300 goto oops;
301 trace(("line = \"%s\"\n", line->str_text));
302 type = patch_line_type_unchanged;
303 switch (line->str_text[0])
304 {
305 default:
306 goto oops;
307
308 case ' ':
309 break;
310
311 case '-':
312 type = patch_line_type_deleted;
313 break;
314
315 case '+':
316 type = patch_line_type_inserted;
317 break;
318 }
319 value = str_n_from_c(line->str_text + 1, line->str_length - 1);
320 if (type != patch_line_type_inserted)
321 {
322 if (num_before <= 0)
323 goto oops;
324 patch_line_list_append(&php->before, type, value);
325 --num_before;
326 }
327 if (type != patch_line_type_deleted)
328 {
329 if (num_after <= 0)
330 goto oops;
331 patch_line_list_append(&php->after, type, value);
332 --num_after;
333 }
334 str_free(value);
335 trace(("mark\n"));
336 }
337
338 //
339 // In the limiting case, using the diff -U0 flag, inserts
340 // and deletes are off by one. They mean "append after" and
341 // "delete after", but we need them to mean "insert before" and
342 // "delete before".
343 //
344 if (php->before.start_line_number && php->before.length == 0)
345 php->before.start_line_number++;
346 if (php->after.start_line_number && php->after.length == 0)
347 php->after.start_line_number++;
348
349 //
350 // We have a viable hunk, take them out of the context because
351 // we won't need to backtrack them any more.
352 //
353 trace(("mark\n"));
354 patch_context_discard(context, idx);
355
356 trace(("mark\n"));
357 trace(("return %p\n", php));
358 trace(("}\n"));
359 return php;
360 }
361
362
363 patch_format_ty patch_format_uni =
364 {
365 "uni diff",
366 uni_diff_header,
367 uni_diff_hunk,
368 };
369
370
371 // vim: set ts=8 sw=4 et :
372