12
12
#include "userdiff.h"
13
13
#include "apply.h"
14
14
#include "revision.h"
15
+ #include "dir.h"
15
16
16
17
struct patch_util {
17
18
/* For the search for an exact match */
@@ -26,6 +27,293 @@ struct patch_util {
26
27
struct object_id oid ;
27
28
};
28
29
30
+ static inline int strtost (char const * s , size_t * result , const char * * end )
31
+ {
32
+ unsigned long u ;
33
+ char * p ;
34
+
35
+ errno = 0 ;
36
+ /* negative values would be accepted by strtoul */
37
+ if (* s == '-' )
38
+ return -1 ;
39
+ u = strtoul (s , & p , 10 );
40
+ if (errno || p == s )
41
+ return -1 ;
42
+ if (result )
43
+ * result = u ;
44
+ * end = p ;
45
+
46
+ return 0 ;
47
+ }
48
+
49
+ static int parse_hunk_header (const char * p ,
50
+ size_t * old_count , size_t * new_count ,
51
+ const char * * end )
52
+ {
53
+ size_t o = 1 , n = 1 ;
54
+
55
+ if (!skip_prefix (p , "@@ -" , & p ) ||
56
+ strtost (p , NULL , & p ) ||
57
+ (* p != ' ' && (* p != ',' || strtost (p + 1 , & o , & p ))) ||
58
+ !skip_prefix (p , " +" , & p ) ||
59
+ strtost (p , NULL , & p ) ||
60
+ (* p != ' ' && (* p != ',' || strtost (p + 1 , & n , & p ))) ||
61
+ !skip_prefix (p , " @@" , & p ))
62
+ return -1 ;
63
+
64
+ * old_count = o ;
65
+ * new_count = n ;
66
+ * end = p ;
67
+
68
+ return 0 ;
69
+ }
70
+
71
+ static inline int find_eol (const char * line , size_t size )
72
+ {
73
+ char * eol ;
74
+
75
+ eol = memchr (line , '\n' , size );
76
+ if (!eol )
77
+ return size ;
78
+
79
+ if (eol != line && eol [-1 ] == '\r' )
80
+ eol [-1 ] = '\0' ;
81
+ else
82
+ * eol = '\0' ;
83
+
84
+ return eol + 1 - line ;
85
+ }
86
+
87
+ static int read_mbox (const char * path , struct string_list * list )
88
+ {
89
+ struct strbuf buf = STRBUF_INIT , contents = STRBUF_INIT ;
90
+ struct strbuf long_subject = STRBUF_INIT ;
91
+ struct patch_util * util = NULL ;
92
+ enum {
93
+ MBOX_BEFORE_HEADER ,
94
+ MBOX_IN_HEADER ,
95
+ MBOX_IN_COMMIT_MESSAGE ,
96
+ MBOX_AFTER_TRIPLE_DASH ,
97
+ MBOX_IN_DIFF
98
+ } state = MBOX_BEFORE_HEADER ;
99
+ char * line , * current_filename = NULL ;
100
+ int offset , len ;
101
+ size_t size , old_count = 0 , new_count = 0 ;
102
+ const char * author = NULL , * subject = NULL ;
103
+
104
+ if (!strcmp (path , "-" )) {
105
+ if (strbuf_read (& contents , STDIN_FILENO , 0 ) < 0 )
106
+ return error_errno (_ ("could not read stdin" ));
107
+ } else if (strbuf_read_file (& contents , path , 0 ) < 0 )
108
+ return error_errno (_ ("could not read '%s'" ), path );
109
+
110
+ line = contents .buf ;
111
+ size = contents .len ;
112
+ for (offset = 0 ; size > 0 ; offset += len , size -= len , line += len ) {
113
+ const char * p ;
114
+
115
+ len = find_eol (line , size );
116
+
117
+ if (state == MBOX_BEFORE_HEADER ) {
118
+ if (!skip_prefix (line , "From " , & p ))
119
+ continue ;
120
+
121
+ util = xcalloc (sizeof (* util ), 1 );
122
+ if (get_oid_hex (p , & util -> oid ) < 0 )
123
+ oidcpy (& util -> oid , null_oid ());
124
+ util -> matching = -1 ;
125
+ author = subject = NULL ;
126
+
127
+ state = MBOX_IN_HEADER ;
128
+ }
129
+
130
+ if (starts_with (line , "diff --git " )) {
131
+ struct patch patch = { 0 };
132
+ struct strbuf root = STRBUF_INIT ;
133
+ int linenr = 0 ;
134
+ int orig_len ;
135
+
136
+ state = MBOX_IN_DIFF ;
137
+ old_count = new_count = 0 ;
138
+ strbuf_addch (& buf , '\n' );
139
+ if (!util -> diff_offset )
140
+ util -> diff_offset = buf .len ;
141
+ line [len - 1 ] = '\n' ;
142
+ orig_len = len ;
143
+ len = parse_git_diff_header (& root , & linenr , 1 , line ,
144
+ len , size , & patch );
145
+ if (len < 0 ) {
146
+ error (_ ("could not parse git header '%.*s'" ),
147
+ orig_len , line );
148
+ free (util );
149
+ free (current_filename );
150
+ string_list_clear (list , 1 );
151
+ strbuf_release (& buf );
152
+ strbuf_release (& contents );
153
+ strbuf_release (& long_subject );
154
+ return -1 ;
155
+ }
156
+
157
+ if (patch .old_name )
158
+ skip_prefix (patch .old_name , "a/" ,
159
+ (const char * * )& patch .old_name );
160
+ if (patch .new_name )
161
+ skip_prefix (patch .new_name , "b/" ,
162
+ (const char * * )& patch .new_name );
163
+
164
+ strbuf_addstr (& buf , " ## " );
165
+ if (patch .is_new > 0 )
166
+ strbuf_addf (& buf , "%s (new)" , patch .new_name );
167
+ else if (patch .is_delete > 0 )
168
+ strbuf_addf (& buf , "%s (deleted)" , patch .old_name );
169
+ else if (patch .is_rename )
170
+ strbuf_addf (& buf , "%s => %s" , patch .old_name , patch .new_name );
171
+ else
172
+ strbuf_addstr (& buf , patch .new_name );
173
+
174
+ free (current_filename );
175
+ if (patch .is_delete > 0 )
176
+ current_filename = xstrdup (patch .old_name );
177
+ else
178
+ current_filename = xstrdup (patch .new_name );
179
+
180
+ if (patch .new_mode && patch .old_mode &&
181
+ patch .old_mode != patch .new_mode )
182
+ strbuf_addf (& buf , " (mode change %06o => %06o)" ,
183
+ patch .old_mode , patch .new_mode );
184
+
185
+ strbuf_addstr (& buf , " ##\n" );
186
+ util -> diffsize ++ ;
187
+ } else if (state == MBOX_IN_HEADER ) {
188
+ if (!line [0 ]) {
189
+ state = MBOX_IN_COMMIT_MESSAGE ;
190
+ /* Look for an in-body From: */
191
+ if (size > 5 && skip_prefix (line + 1 , "From: " , & p )) {
192
+ size -= p - line ;
193
+ line += p - line ;
194
+ len = find_eol (line , size );
195
+
196
+ while (isspace (* p ))
197
+ p ++ ;
198
+ author = p ;
199
+ }
200
+ strbuf_addstr (& buf , " ## Metadata ##\n" );
201
+ if (author )
202
+ strbuf_addf (& buf , "Author: %s\n" , author );
203
+ strbuf_addstr (& buf , "\n ## Commit message ##\n" );
204
+ if (subject )
205
+ strbuf_addf (& buf , " %s\n\n" , subject );
206
+ } else if (skip_prefix (line , "From: " , & p )) {
207
+ while (isspace (* p ))
208
+ p ++ ;
209
+ author = p ;
210
+ } else if (skip_prefix (line , "Subject: " , & p )) {
211
+ const char * q ;
212
+
213
+ while (isspace (* p ))
214
+ p ++ ;
215
+ subject = p ;
216
+
217
+ if (starts_with (p , "[PATCH" ) &&
218
+ (q = strchr (p , ']' ))) {
219
+ q ++ ;
220
+ while (isspace (* q ))
221
+ q ++ ;
222
+ subject = q ;
223
+ }
224
+
225
+ if (len < size && line [len ] == ' ' ) {
226
+ /* handle long subject */
227
+ strbuf_reset (& long_subject );
228
+ strbuf_addstr (& long_subject , subject );
229
+ while (len < size && line [len ] == ' ' ) {
230
+ line += len ;
231
+ size -= len ;
232
+ len = find_eol (line , size );
233
+ strbuf_addstr (& long_subject , line );
234
+ }
235
+ subject = long_subject .buf ;
236
+ }
237
+ }
238
+ } else if (state == MBOX_IN_COMMIT_MESSAGE ) {
239
+ if (!* line )
240
+ strbuf_addch (& buf , '\n' );
241
+ else if (strcmp (line , "---" )) {
242
+ int tabs = 0 ;
243
+
244
+ /* simulate tab expansion */
245
+ while (line [tabs ] == '\t' )
246
+ tabs ++ ;
247
+ strbuf_addf (& buf , "%*s%s\n" ,
248
+ 4 + 8 * tabs , "" , line + tabs );
249
+ } else {
250
+ /*
251
+ * Trim the trailing newline that is added
252
+ * by `format-patch`.
253
+ */
254
+ strbuf_trim_trailing_newline (& buf );
255
+ state = MBOX_AFTER_TRIPLE_DASH ;
256
+ }
257
+ } else if (state == MBOX_IN_DIFF ) {
258
+ switch (line [0 ]) {
259
+ case '\0' :
260
+ continue ; /* ignore empty lines after diff */
261
+ case '+' :
262
+ case '-' :
263
+ case ' ' :
264
+ if (!old_count && !new_count )
265
+ break ;
266
+ if (old_count && line [0 ] != '+' )
267
+ old_count -- ;
268
+ if (new_count && line [0 ] != '-' )
269
+ new_count -- ;
270
+ /* fallthrough */
271
+ case '\\' :
272
+ strbuf_addstr (& buf , line );
273
+ strbuf_addch (& buf , '\n' );
274
+ util -> diffsize ++ ;
275
+ continue ;
276
+ case '@' :
277
+ if (parse_hunk_header (line , & old_count ,
278
+ & new_count , & p ))
279
+ break ;
280
+
281
+ strbuf_addstr (& buf , "@@" );
282
+ if (current_filename && * p )
283
+ strbuf_addf (& buf , " %s:" ,
284
+ current_filename );
285
+ strbuf_addstr (& buf , p );
286
+ strbuf_addch (& buf , '\n' );
287
+ util -> diffsize ++ ;
288
+ continue ;
289
+ }
290
+
291
+ if (util ) {
292
+ string_list_append (list , buf .buf )-> util = util ;
293
+ strbuf_reset (& buf );
294
+ }
295
+ util = xcalloc (sizeof (* util ), 1 );
296
+ oidcpy (& util -> oid , null_oid ());
297
+ util -> matching = -1 ;
298
+ author = subject = NULL ;
299
+ state = MBOX_BEFORE_HEADER ;
300
+ }
301
+ }
302
+ strbuf_release (& contents );
303
+
304
+ if (util ) {
305
+ if (state == MBOX_IN_DIFF )
306
+ string_list_append (list , buf .buf )-> util = util ;
307
+ else
308
+ free (util );
309
+ }
310
+ strbuf_release (& buf );
311
+ strbuf_release (& long_subject );
312
+ free (current_filename );
313
+
314
+ return 0 ;
315
+ }
316
+
29
317
/*
30
318
* Reads the patches into a string list, with the `util` field being populated
31
319
* as struct object_id (will need to be free()d).
@@ -41,6 +329,10 @@ static int read_patches(const char *range, struct string_list *list,
41
329
ssize_t len ;
42
330
size_t size ;
43
331
int ret = -1 ;
332
+ const char * path ;
333
+
334
+ if (skip_prefix (range , "mbox:" , & path ))
335
+ return read_mbox (path , list );
44
336
45
337
strvec_pushl (& cp .args , "log" , "--no-color" , "-p" , "--no-merges" ,
46
338
"--reverse" , "--date-order" , "--decorate=no" ,
@@ -424,6 +716,19 @@ static void output_pair_header(struct diff_options *diffopt,
424
716
425
717
strbuf_addch (buf , ' ' );
426
718
pp_commit_easy (CMIT_FMT_ONELINE , commit , buf );
719
+ } else {
720
+ struct patch_util * util = b_util ? b_util : a_util ;
721
+ const char * needle = "\n ## Commit message ##\n" ;
722
+ const char * p = !util || !util -> patch ?
723
+ NULL : strstr (util -> patch , needle );
724
+ if (p ) {
725
+ if (status == '!' )
726
+ strbuf_addf (buf , "%s%s" , color_reset , color );
727
+
728
+ strbuf_addch (buf , ' ' );
729
+ p += strlen (needle );
730
+ strbuf_add (buf , p , strchrnul (p , '\n' ) - p );
731
+ }
427
732
}
428
733
strbuf_addf (buf , "%s\n" , color_reset );
429
734
@@ -554,6 +859,9 @@ int show_range_diff(const char *range1, const char *range2,
554
859
if (range_diff_opts -> left_only && range_diff_opts -> right_only )
555
860
res = error (_ ("options '%s' and '%s' cannot be used together" ), "--left-only" , "--right-only" );
556
861
862
+ if (!strcmp (range1 , "mbox:-" ) && !strcmp (range2 , "mbox:-" ))
863
+ res = error (_ ("only one mbox can be read from stdin" ));
864
+
557
865
if (!res && read_patches (range1 , & branch1 , range_diff_opts -> other_arg ))
558
866
res = error (_ ("could not parse log for '%s'" ), range1 );
559
867
if (!res && read_patches (range2 , & branch2 , range_diff_opts -> other_arg ))
@@ -575,10 +883,17 @@ int show_range_diff(const char *range1, const char *range2,
575
883
int is_range_diff_range (const char * arg )
576
884
{
577
885
char * copy = xstrdup (arg ); /* setup_revisions() modifies it */
578
- const char * argv [] = { "" , copy , "--" , NULL };
886
+ const char * argv [] = { "" , copy , "--" , NULL }, * path ;
579
887
int i , positive = 0 , negative = 0 ;
580
888
struct rev_info revs ;
581
889
890
+ if (skip_prefix (arg , "mbox:" , & path )) {
891
+ if (!strcmp (path , "-" ) || file_exists (path ))
892
+ return 1 ;
893
+ error_errno (_ ("not an mbox: '%s'" ), path );
894
+ return 0 ;
895
+ }
896
+
582
897
init_revisions (& revs , NULL );
583
898
if (setup_revisions (3 , argv , & revs , NULL ) == 1 ) {
584
899
for (i = 0 ; i < revs .pending .nr ; i ++ )
0 commit comments