Start implementing "git-apply"
[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 #define CHUNKSIZE (8192)
25
26 static void *read_patch_file(int fd, unsigned long *sizep)
27 {
28         unsigned long size = 0, alloc = CHUNKSIZE;
29         void *buffer = xmalloc(alloc);
30
31         for (;;) {
32                 int nr = alloc - size;
33                 if (nr < 1024) {
34                         alloc += CHUNKSIZE;
35                         buffer = xrealloc(buffer, alloc);
36                         nr = alloc - size;
37                 }
38                 nr = read(fd, buffer + size, nr);
39                 if (!nr)
40                         break;
41                 if (nr < 0) {
42                         if (errno == EAGAIN)
43                                 continue;
44                         die("git-apply: read returned %s", strerror(errno));
45                 }
46                 size += nr;
47         }
48         *sizep = size;
49         return buffer;
50 }
51
52 static unsigned long linelen(char *buffer, unsigned long size)
53 {
54         unsigned long len = 0;
55         while (size--) {
56                 len++;
57                 if (*buffer++ == '\n')
58                         break;
59         }
60         return len;
61 }
62
63 static int match_word(const char *line, const char *match)
64 {
65         for (;;) {
66                 char c = *match++;
67                 if (!c)
68                         break;
69                 if (*line++ != c)
70                         return 0;
71         }
72         return *line == ' ';
73 }
74
75 /* Verify that we recognize the lines following a git header */
76 static int parse_git_header(char *line, unsigned int size)
77 {
78         unsigned long offset, len;
79
80         for (offset = 0 ; size > 0 ; offset += len, size -= len, line += len) {
81                 len = linelen(line, size);
82                 if (!len)
83                         break;
84                 if (line[len-1] != '\n')
85                         return -1;
86                 if (len < 4)
87                         break;
88                 if (!memcmp(line, "@@ -", 4))
89                         return offset;
90                 if (match_word(line, "new file mode"))
91                         continue;
92                 if (match_word(line, "deleted file mode"))
93                         continue;
94                 if (match_word(line, "copy"))
95                         continue;
96                 if (match_word(line, "rename"))
97                         continue;
98                 if (match_word(line, "similarity index"))
99                         continue;
100                 break;
101         }
102
103         /* We want either a patch _or_ something real */
104         return offset ? :-1;
105 }
106
107 static int find_header(char *line, unsigned long size, int *hdrsize)
108 {
109         unsigned long offset, len;
110
111         for (offset = 0; size > 0; offset += len, size -= len, line += len) {
112                 unsigned long nextlen;
113
114                 len = linelen(line, size);
115                 if (!len)
116                         break;
117
118                 /* Testing this early allows us to take a few shortcuts.. */
119                 if (len < 6)
120                         continue;
121                 if (size < len + 6)
122                         break;
123
124                 /*
125                  * Git patch? It might not have a real patch, just a rename
126                  * or mode change, so we handle that specially
127                  */
128                 if (!memcmp("diff --git ", line, 11)) {
129                         int git_hdr_len = parse_git_header(line + len, size - len);
130                         if (git_hdr_len < 0)
131                                 continue;
132
133                         *hdrsize = len + git_hdr_len;
134                         return offset;
135                 }
136
137                 /** --- followed by +++ ? */
138                 if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
139                         continue;
140
141                 /*
142                  * We only accept unified patches, so we want it to
143                  * at least have "@@ -a,b +c,d @@\n", which is 14 chars
144                  * minimum
145                  */
146                 nextlen = linelen(line + len, size - len);
147                 if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
148                         continue;
149
150                 /* Ok, we'll consider it a patch */
151                 *hdrsize = len + nextlen;
152                 return offset;
153         }
154         return -1;
155 }
156
157 static int parse_num(const char *line, int len, int offset, const char *expect, unsigned long *p)
158 {
159         char *ptr;
160         int digits, ex;
161
162         if (offset < 0 || offset >= len)
163                 return -1;
164         line += offset;
165         len -= offset;
166
167         if (!isdigit(*line))
168                 return -1;
169         *p = strtoul(line, &ptr, 10);
170
171         digits = ptr - line;
172
173         offset += digits;
174         line += digits;
175         len -= digits;
176
177         ex = strlen(expect);
178         if (ex > len)
179                 return -1;
180         if (memcmp(line, expect, ex))
181                 return -1;
182
183         return offset + ex;
184 }
185
186 /*
187  * Parse a unified diff. Note that this really needs
188  * to parse each fragment separately, since the only
189  * way to know the difference between a "---" that is
190  * part of a patch, and a "---" that starts the next
191  * patch is to look at the line counts..
192  */
193 static int apply_fragment(char *line, unsigned long size)
194 {
195         int len = linelen(line, size), offset;
196         unsigned long oldpos, oldlines, newpos, newlines;
197
198         if (!len || line[len-1] != '\n')
199                 return -1;
200
201         /* Figure out the number of lines in a fragment */
202         offset = parse_num(line, len, 4, ",", &oldpos);
203         offset = parse_num(line, len, offset, " +", &oldlines);
204         offset = parse_num(line, len, offset, ",", &newpos);
205         offset = parse_num(line, len, offset, " @@", &newlines);
206         if (offset < 0)
207                 return -1;
208
209         /* Parse the thing.. */
210         line += len;
211         size -= len;
212         for (offset = len; size > 0; offset += len, size -= len, line += len) {
213                 if (!oldlines && !newlines)
214                         break;
215                 len = linelen(line, size);
216                 if (!len || line[len-1] != '\n')
217                         return -1;
218                 switch (*line) {
219                 default:
220                         return -1;
221                 case ' ':
222                         oldlines--;
223                         newlines--;
224                         break;
225                 case '-':
226                         oldlines--;
227                         break;
228                 case '+':
229                         newlines--;
230                         break;
231                 }
232         }
233         return offset;
234 }
235
236 static int apply_single_patch(char *line, unsigned long size)
237 {
238         unsigned long offset = 0;
239
240         while (size > 4 && !memcmp(line, "@@ -", 4)) {
241                 int len = apply_fragment(line, size);
242                 if (len <= 0)
243                         break;
244
245 printf("applying fragment:\n%.*s\n\n", len, line);
246
247                 offset += len;
248                 line += len;
249                 size -= len;
250         }
251         return offset;
252 }
253
254 static int apply_chunk(char *buffer, unsigned long size)
255 {
256         int hdrsize, patchsize;
257         int offset = find_header(buffer, size, &hdrsize);
258         char *header, *patch;
259
260         if (offset < 0)
261                 return offset;
262         header = buffer + offset;
263
264 printf("Found header:\n%.*s\n\n", hdrsize, header);
265
266         patch = header + hdrsize;
267         patchsize = apply_single_patch(patch, size - offset - hdrsize);
268
269         return offset + hdrsize + patchsize;
270 }
271
272 static int apply_patch(int fd)
273 {
274         unsigned long offset, size;
275         char *buffer = read_patch_file(fd, &size);
276
277         if (!buffer)
278                 return -1;
279         offset = 0;
280         while (size > 0) {
281                 int nr = apply_chunk(buffer + offset, size);
282                 if (nr < 0)
283                         break;
284                 offset += nr;
285                 size -= nr;
286         }
287         free(buffer);
288         return 0;
289 }
290
291 int main(int argc, char **argv)
292 {
293         int i;
294
295         if (read_cache() < 0)
296                 die("unable to read index file");
297
298         for (i = 1; i < argc; i++) {
299                 const char *arg = argv[i];
300                 int fd;
301
302                 if (!strcmp(arg, "-")) {
303                         apply_patch(0);
304                         continue;
305                 }
306                 if (!strcmp(arg, "--no-merge")) {
307                         merge_patch = 0;
308                         continue;
309                 }
310                 fd = open(arg, O_RDONLY);
311                 if (fd < 0)
312                         usage(apply_usage);
313                 apply_patch(fd);
314                 close(fd);
315         }
316         return 0;
317 }