git-apply: improve error detection and messages
[git.git] / apply.c
1 /*
2  * apply.c
3  *
4  * Copyright (C) Linus Torvalds, 2005
5  *
6  * This applies patches on top of some (arbitrary) version of the SCM.
7  *
8  * NOTE! It does all its work in the index file, and only cares about
9  * the files in the working directory if you tell it to "merge" the
10  * patch apply.
11  *
12  * Even when merging it always takes the source from the index, and
13  * uses the working tree as a "branch" for a 3-way merge.
14  */
15 #include <ctype.h>
16
17 #include "cache.h"
18
19 // We default to the merge behaviour, since that's what most people would
20 // expect
21 static int merge_patch = 1;
22 static const char apply_usage[] = "git-apply <patch>";
23
24 static int linenr = 1;
25
26 #define CHUNKSIZE (8192)
27
28 static void *read_patch_file(int fd, unsigned long *sizep)
29 {
30         unsigned long size = 0, alloc = CHUNKSIZE;
31         void *buffer = xmalloc(alloc);
32
33         for (;;) {
34                 int nr = alloc - size;
35                 if (nr < 1024) {
36                         alloc += CHUNKSIZE;
37                         buffer = xrealloc(buffer, alloc);
38                         nr = alloc - size;
39                 }
40                 nr = read(fd, buffer + size, nr);
41                 if (!nr)
42                         break;
43                 if (nr < 0) {
44                         if (errno == EAGAIN)
45                                 continue;
46                         die("git-apply: read returned %s", strerror(errno));
47                 }
48                 size += nr;
49         }
50         *sizep = size;
51         return buffer;
52 }
53
54 static unsigned long linelen(char *buffer, unsigned long size)
55 {
56         unsigned long len = 0;
57         while (size--) {
58                 len++;
59                 if (*buffer++ == '\n')
60                         break;
61         }
62         return len;
63 }
64
65 static int match_word(const char *line, const char *match)
66 {
67         for (;;) {
68                 char c = *match++;
69                 if (!c)
70                         break;
71                 if (*line++ != c)
72                         return 0;
73         }
74         return *line == ' ';
75 }
76
77 /* Verify that we recognize the lines following a git header */
78 static int parse_git_header(char *line, unsigned int size)
79 {
80         unsigned long offset, len;
81
82         for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
83                 len = linelen(line, size);
84                 if (!len)
85                         break;
86                 if (line[len-1] != '\n')
87                         return -1;
88                 if (len < 4)
89                         break;
90                 if (!memcmp(line, "@@ -", 4))
91                         return offset;
92                 if (match_word(line, "new file mode"))
93                         continue;
94                 if (match_word(line, "deleted file mode"))
95                         continue;
96                 if (match_word(line, "copy"))
97                         continue;
98                 if (match_word(line, "rename"))
99                         continue;
100                 if (match_word(line, "similarity index"))
101                         continue;
102                 break;
103         }
104
105         /* We want either a patch _or_ something real */
106         return offset ? :-1;
107 }
108
109 static int parse_num(const char *line, int len, int offset, const char *expect, unsigned long *p)
110 {
111         char *ptr;
112         int digits, ex;
113
114         if (offset < 0 || offset >= len)
115                 return -1;
116         line += offset;
117         len -= offset;
118
119         if (!isdigit(*line))
120                 return -1;
121         *p = strtoul(line, &ptr, 10);
122
123         digits = ptr - line;
124
125         offset += digits;
126         line += digits;
127         len -= digits;
128
129         ex = strlen(expect);
130         if (ex > len)
131                 return -1;
132         if (memcmp(line, expect, ex))
133                 return -1;
134
135         return offset + ex;
136 }
137
138 /*
139  * Parse a unified diff fragment header of the
140  * form "@@ -a,b +c,d @@"
141  */
142 static int parse_fragment_header(char *line, int len, unsigned long *pos)
143 {
144         int offset;
145
146         if (!len || line[len-1] != '\n')
147                 return -1;
148
149         /* Figure out the number of lines in a fragment */
150         offset = parse_num(line, len, 4, ",", pos);
151         offset = parse_num(line, len, offset, " +", pos+1);
152         offset = parse_num(line, len, offset, ",", pos+2);
153         offset = parse_num(line, len, offset, " @@", pos+3);
154
155         return offset;
156 }
157
158 static int find_header(char *line, unsigned long size, int *hdrsize)
159 {
160         unsigned long offset, len;
161
162         for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
163                 unsigned long nextlen;
164
165                 len = linelen(line, size);
166                 if (!len)
167                         break;
168
169                 /* Testing this early allows us to take a few shortcuts.. */
170                 if (len < 6)
171                         continue;
172
173                 /*
174                  * Make sure we don't find any unconnected patch fragmants.
175                  * That's a sign that we didn't find a header, and that a
176                  * patch has become corrupted/broken up.
177                  */
178                 if (!memcmp("@@ -", line, 4)) {
179                         unsigned long pos[4];
180                         if (parse_fragment_header(line, len, pos) < 0)
181                                 continue;
182                         error("patch fragment without header at line %d: %.*s", linenr, len-1, line);
183                 }
184
185                 if (size < len + 6)
186                         break;
187
188                 /*
189                  * Git patch? It might not have a real patch, just a rename
190                  * or mode change, so we handle that specially
191                  */
192                 if (!memcmp("diff --git ", line, 11)) {
193                         int git_hdr_len = parse_git_header(line + len, size - len);
194                         if (git_hdr_len < 0)
195                                 continue;
196
197                         *hdrsize = len + git_hdr_len;
198                         return offset;
199                 }
200
201                 /** --- followed by +++ ? */
202                 if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
203                         continue;
204
205                 /*
206                  * We only accept unified patches, so we want it to
207                  * at least have "@@ -a,b +c,d @@\n", which is 14 chars
208                  * minimum
209                  */
210                 nextlen = linelen(line + len, size - len);
211                 if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
212                         continue;
213
214                 /* Ok, we'll consider it a patch */
215                 *hdrsize = len + nextlen;
216                 linenr += 2;
217                 return offset;
218         }
219         return -1;
220 }
221
222 /*
223  * Parse a unified diff. Note that this really needs
224  * to parse each fragment separately, since the only
225  * way to know the difference between a "---" that is
226  * part of a patch, and a "---" that starts the next
227  * patch is to look at the line counts..
228  */
229 static int apply_fragment(char *line, unsigned long size)
230 {
231         int len = linelen(line, size), offset;
232         unsigned long pos[4], oldlines, newlines;
233
234         offset = parse_fragment_header(line, len, pos);
235         if (offset < 0)
236                 return -1;
237         oldlines = pos[1];
238         newlines = pos[3];
239
240         /* Parse the thing.. */
241         line += len;
242         size -= len;
243         linenr++;
244         for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
245                 if (!oldlines && !newlines)
246                         break;
247                 len = linelen(line, size);
248                 if (!len || line[len-1] != '\n')
249                         return -1;
250                 switch (*line) {
251                 default:
252                         return -1;
253                 case ' ':
254                         oldlines--;
255                         newlines--;
256                         break;
257                 case '-':
258                         oldlines--;
259                         break;
260                 case '+':
261                         newlines--;
262                         break;
263                 }
264         }
265         return offset;
266 }
267
268 static int apply_single_patch(char *line, unsigned long size)
269 {
270         unsigned long offset = 0;
271
272         while (size > 4 && !memcmp(line, "@@ -", 4)) {
273                 int len = apply_fragment(line, size);
274                 if (len <= 0)
275                         die("corrupt patch at line %d", linenr);
276
277 printf("applying fragment:\n%.*s\n\n", len, line);
278
279                 offset += len;
280                 line += len;
281                 size -= len;
282         }
283         return offset;
284 }
285
286 static int apply_chunk(char *buffer, unsigned long size)
287 {
288         int hdrsize, patchsize;
289         int offset = find_header(buffer, size, &hdrsize);
290         char *header, *patch;
291
292         if (offset < 0)
293                 return offset;
294         header = buffer + offset;
295
296 printf("Found header:\n%.*s\n\n", hdrsize, header);
297
298         patch = header + hdrsize;
299         patchsize = apply_single_patch(patch, size - offset - hdrsize);
300
301         return offset + hdrsize + patchsize;
302 }
303
304 static int apply_patch(int fd)
305 {
306         unsigned long offset, size;
307         char *buffer = read_patch_file(fd, &size);
308
309         if (!buffer)
310                 return -1;
311         offset = 0;
312         while (size > 0) {
313                 int nr = apply_chunk(buffer + offset, size);
314                 if (nr < 0)
315                         break;
316                 offset += nr;
317                 size -= nr;
318         }
319         free(buffer);
320         return 0;
321 }
322
323 int main(int argc, char **argv)
324 {
325         int i;
326
327         if (read_cache() < 0)
328                 die("unable to read index file");
329
330         for (i = 1; i < argc; i++) {
331                 const char *arg = argv[i];
332                 int fd;
333
334                 if (!strcmp(arg, "-")) {
335                         apply_patch(0);
336                         continue;
337                 }
338                 if (!strcmp(arg, "--no-merge")) {
339                         merge_patch = 0;
340                         continue;
341                 }
342                 fd = open(arg, O_RDONLY);
343                 if (fd < 0)
344                         usage(apply_usage);
345                 apply_patch(fd);
346                 close(fd);
347         }
348         return 0;
349 }