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