]> git.ipfire.org Git - thirdparty/git.git/blob - apply.c
git-apply: improve error detection and messages
[thirdparty/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 }