git-apply: improve error detection and messages
[git/git.git] / apply.c
CommitLineData
c1bb9350
LT
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
21static int merge_patch = 1;
22static const char apply_usage[] = "git-apply <patch>";
23
46979f56
LT
24static int linenr = 1;
25
c1bb9350
LT
26#define CHUNKSIZE (8192)
27
28static 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
54static 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
65static 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 */
78static int parse_git_header(char *line, unsigned int size)
79{
80 unsigned long offset, len;
81
46979f56 82 for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
c1bb9350
LT
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
46979f56
LT
109static 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 */
142static 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
c1bb9350
LT
158static int find_header(char *line, unsigned long size, int *hdrsize)
159{
160 unsigned long offset, len;
161
46979f56 162 for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
c1bb9350
LT
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;
46979f56
LT
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
c1bb9350
LT
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;
46979f56 216 linenr += 2;
c1bb9350
LT
217 return offset;
218 }
219 return -1;
220}
221
c1bb9350
LT
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 */
229static int apply_fragment(char *line, unsigned long size)
230{
231 int len = linelen(line, size), offset;
46979f56 232 unsigned long pos[4], oldlines, newlines;
c1bb9350 233
46979f56 234 offset = parse_fragment_header(line, len, pos);
c1bb9350
LT
235 if (offset < 0)
236 return -1;
46979f56
LT
237 oldlines = pos[1];
238 newlines = pos[3];
c1bb9350
LT
239
240 /* Parse the thing.. */
241 line += len;
242 size -= len;
46979f56
LT
243 linenr++;
244 for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
c1bb9350
LT
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
268static 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)
46979f56 275 die("corrupt patch at line %d", linenr);
c1bb9350
LT
276
277printf("applying fragment:\n%.*s\n\n", len, line);
278
279 offset += len;
280 line += len;
281 size -= len;
282 }
283 return offset;
284}
285
286static 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
296printf("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
304static 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
323int 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}