21
21
static int merge_patch = 1 ;
22
22
static const char apply_usage [] = "git-apply <patch>" ;
23
23
24
+ /*
25
+ * Various "current state", notably line numbers and what
26
+ * file (and how) we're patching right now.. The "is_xxxx"
27
+ * things are flags, where -1 means "don't know yet".
28
+ */
24
29
static int linenr = 1 ;
30
+ static int old_mode , new_mode ;
31
+ static char * old_name , * new_name , * def_name ;
32
+ static int is_rename , is_copy , is_new , is_delete ;
25
33
26
34
#define CHUNKSIZE (8192)
35
+ #define SLOP (16)
27
36
28
37
static void * read_patch_file (int fd , unsigned long * sizep )
29
38
{
@@ -48,6 +57,15 @@ static void *read_patch_file(int fd, unsigned long *sizep)
48
57
size += nr ;
49
58
}
50
59
* sizep = size ;
60
+
61
+ /*
62
+ * Make sure that we have some slop in the buffer
63
+ * so that we can do speculative "memcmp" etc, and
64
+ * see to it that it is NUL-filled.
65
+ */
66
+ if (alloc < size + SLOP )
67
+ buffer = xrealloc (buffer , size + SLOP );
68
+ memset (buffer + size , 0 , SLOP );
51
69
return buffer ;
52
70
}
53
71
@@ -62,48 +80,200 @@ static unsigned long linelen(char *buffer, unsigned long size)
62
80
return len ;
63
81
}
64
82
65
- static int match_word (const char * line , const char * match )
83
+ static int is_dev_null (const char * str )
84
+ {
85
+ return !memcmp ("/dev/null" , str , 9 ) && isspace (str [9 ]);
86
+ }
87
+
88
+ static char * find_name (const char * line , char * def , int p_value )
66
89
{
90
+ int len ;
91
+ const char * start = line ;
92
+ char * name ;
93
+
67
94
for (;;) {
68
- char c = * match ++ ;
69
- if (! c )
95
+ char c = * line ;
96
+ if (isspace ( c ) )
70
97
break ;
71
- if (* line ++ != c )
72
- return 0 ;
98
+ line ++ ;
99
+ if (c == '/' && !-- p_value )
100
+ start = line ;
101
+ }
102
+ if (!start )
103
+ return def ;
104
+ len = line - start ;
105
+ if (!len )
106
+ return def ;
107
+
108
+ /*
109
+ * Generally we prefer the shorter name, especially
110
+ * if the other one is just a variation of that with
111
+ * something else tacked on to the end (ie "file.orig"
112
+ * or "file~").
113
+ */
114
+ if (def ) {
115
+ int deflen = strlen (def );
116
+ if (deflen < len && !strncmp (start , def , deflen ))
117
+ return def ;
73
118
}
74
- return * line == ' ' ;
119
+
120
+ name = xmalloc (len + 1 );
121
+ memcpy (name , start , len );
122
+ name [len ] = 0 ;
123
+ free (def );
124
+ return name ;
125
+ }
126
+
127
+ /*
128
+ * Get the name etc info from the --/+++ lines of a traditional patch header
129
+ *
130
+ * NOTE! This hardcodes "-p1" behaviour in filename detection.
131
+ */
132
+ static int parse_traditional_patch (const char * first , const char * second )
133
+ {
134
+ int p_value = 1 ;
135
+ char * name ;
136
+
137
+ first += 4 ; // skip "--- "
138
+ second += 4 ; // skip "+++ "
139
+ if (is_dev_null (first )) {
140
+ is_new = 1 ;
141
+ name = find_name (second , def_name , p_value );
142
+ } else if (is_dev_null (second )) {
143
+ is_delete = 1 ;
144
+ name = find_name (first , def_name , p_value );
145
+ } else {
146
+ name = find_name (first , def_name , p_value );
147
+ name = find_name (second , name , p_value );
148
+ }
149
+ if (!name )
150
+ die ("unable to find filename in patch at line %d" , linenr );
151
+ old_name = name ;
152
+ new_name = name ;
153
+ }
154
+
155
+ static int gitdiff_hdrend (const char * line )
156
+ {
157
+ return -1 ;
158
+ }
159
+
160
+ static int gitdiff_oldname (const char * line )
161
+ {
162
+ if (!old_name )
163
+ old_name = find_name (line , NULL , 1 );
164
+ return 0 ;
165
+ }
166
+
167
+ static int gitdiff_newname (const char * line )
168
+ {
169
+ if (!new_name )
170
+ new_name = find_name (line , NULL , 1 );
171
+ return 0 ;
172
+ }
173
+
174
+ static int gitdiff_oldmode (const char * line )
175
+ {
176
+ old_mode = strtoul (line , NULL , 8 );
177
+ return 0 ;
178
+ }
179
+
180
+ static int gitdiff_newmode (const char * line )
181
+ {
182
+ new_mode = strtoul (line , NULL , 8 );
183
+ return 0 ;
184
+ }
185
+
186
+ static int gitdiff_delete (const char * line )
187
+ {
188
+ is_delete = 1 ;
189
+ return gitdiff_oldmode (line );
190
+ }
191
+
192
+ static int gitdiff_newfile (const char * line )
193
+ {
194
+ is_new = 1 ;
195
+ return gitdiff_newmode (line );
196
+ }
197
+
198
+ static int gitdiff_copysrc (const char * line )
199
+ {
200
+ is_copy = 1 ;
201
+ old_name = find_name (line , NULL , 0 );
202
+ return 0 ;
203
+ }
204
+
205
+ static int gitdiff_copydst (const char * line )
206
+ {
207
+ is_copy = 1 ;
208
+ new_name = find_name (line , NULL , 0 );
209
+ return 0 ;
210
+ }
211
+
212
+ static int gitdiff_renamesrc (const char * line )
213
+ {
214
+ is_rename = 1 ;
215
+ old_name = find_name (line , NULL , 0 );
216
+ return 0 ;
217
+ }
218
+
219
+ static int gitdiff_renamedst (const char * line )
220
+ {
221
+ is_rename = 1 ;
222
+ new_name = find_name (line , NULL , 0 );
223
+ return 0 ;
224
+ }
225
+
226
+ static int gitdiff_similarity (const char * line )
227
+ {
228
+ return 0 ;
75
229
}
76
230
77
231
/* Verify that we recognize the lines following a git header */
78
- static int parse_git_header (char * line , unsigned int size )
232
+ static int parse_git_header (char * line , int len , unsigned int size )
79
233
{
80
- unsigned long offset , len ;
234
+ unsigned long offset ;
235
+
236
+ /* A git diff has explicit new/delete information, so we don't guess */
237
+ is_new = 0 ;
238
+ is_delete = 0 ;
239
+
240
+ line += len ;
241
+ size -= len ;
242
+ linenr ++ ;
243
+ for (offset = len ; size > 0 ; offset += len , size -= len , line += len , linenr ++ ) {
244
+ static const struct opentry {
245
+ const char * str ;
246
+ int (* fn )(const char * );
247
+ } optable [] = {
248
+ { "@@ -" , gitdiff_hdrend },
249
+ { "--- " , gitdiff_oldname },
250
+ { "+++ " , gitdiff_newname },
251
+ { "old mode " , gitdiff_oldmode },
252
+ { "new mode " , gitdiff_newmode },
253
+ { "deleted file mode " , gitdiff_delete },
254
+ { "new file mode " , gitdiff_newfile },
255
+ { "copy from " , gitdiff_copysrc },
256
+ { "copy to " , gitdiff_copydst },
257
+ { "rename from " , gitdiff_renamesrc },
258
+ { "rename to " , gitdiff_renamedst },
259
+ { "similarity index " , gitdiff_similarity },
260
+ };
261
+ int i ;
81
262
82
- for (offset = 0 ; size > 0 ; offset += len , size -= len , line += len , linenr ++ ) {
83
263
len = linelen (line , size );
84
- if (!len )
85
- break ;
86
- if (line [len - 1 ] != '\n' )
87
- return -1 ;
88
- if (len < 4 )
264
+ if (!len || line [len - 1 ] != '\n' )
89
265
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 ;
266
+ for (i = 0 ; i < sizeof (optable ) / sizeof (optable [0 ]); i ++ ) {
267
+ const struct opentry * p = optable + i ;
268
+ int oplen = strlen (p -> str );
269
+ if (len < oplen || memcmp (p -> str , line , oplen ))
270
+ continue ;
271
+ if (p -> fn (line + oplen ) < 0 )
272
+ return offset ;
273
+ }
103
274
}
104
275
105
- /* We want either a patch _or_ something real */
106
- return offset ? :-1 ;
276
+ return offset ;
107
277
}
108
278
109
279
static int parse_num (const char * line , int len , int offset , const char * expect , unsigned long * p )
@@ -159,6 +329,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize)
159
329
{
160
330
unsigned long offset , len ;
161
331
332
+ is_rename = is_copy = 0 ;
333
+ is_new = is_delete = -1 ;
334
+ old_mode = new_mode = -1 ;
335
+ def_name = old_name = new_name = NULL ;
162
336
for (offset = 0 ; size > 0 ; offset += len , size -= len , line += len , linenr ++ ) {
163
337
unsigned long nextlen ;
164
338
@@ -190,11 +364,11 @@ static int find_header(char *line, unsigned long size, int *hdrsize)
190
364
* or mode change, so we handle that specially
191
365
*/
192
366
if (!memcmp ("diff --git " , line , 11 )) {
193
- int git_hdr_len = parse_git_header (line + len , size - len );
367
+ int git_hdr_len = parse_git_header (line , len , size );
194
368
if (git_hdr_len < 0 )
195
369
continue ;
196
370
197
- * hdrsize = len + git_hdr_len ;
371
+ * hdrsize = git_hdr_len ;
198
372
return offset ;
199
373
}
200
374
@@ -212,6 +386,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize)
212
386
continue ;
213
387
214
388
/* Ok, we'll consider it a patch */
389
+ parse_traditional_patch (line , line + len );
215
390
* hdrsize = len + nextlen ;
216
391
linenr += 2 ;
217
392
return offset ;
@@ -237,6 +412,11 @@ static int apply_fragment(char *line, unsigned long size)
237
412
oldlines = pos [1 ];
238
413
newlines = pos [3 ];
239
414
415
+ if (is_new < 0 && (pos [0 ] || oldlines ))
416
+ is_new = 0 ;
417
+ if (is_delete < 0 && (pos [1 ] || newlines ))
418
+ is_delete = 0 ;
419
+
240
420
/* Parse the thing.. */
241
421
line += len ;
242
422
size -= len ;
@@ -294,6 +474,12 @@ static int apply_chunk(char *buffer, unsigned long size)
294
474
header = buffer + offset ;
295
475
296
476
printf ("Found header:\n%.*s\n\n" , hdrsize , header );
477
+ printf ("Rename: %d\n" , is_rename );
478
+ printf ("Copy: %d\n" , is_copy );
479
+ printf ("New: %d\n" , is_new );
480
+ printf ("Delete: %d\n" , is_delete );
481
+ printf ("Mode: %o->%o\n" , old_mode , new_mode );
482
+ printf ("Name: '%s'->'%s'\n" , old_name , new_name );
297
483
298
484
patch = header + hdrsize ;
299
485
patchsize = apply_single_patch (patch , size - offset - hdrsize );
0 commit comments