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,313 @@ 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 (!isdigit (* 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
+ /* The range is -<start>[,<count>], defaulting to count = 1 */
58
+ !(* p == ' ' || (* p == ',' && !strtost (p + 1 , & o , & p ))) ||
59
+ !skip_prefix (p , " +" , & p ) ||
60
+ strtost (p , NULL , & p ) ||
61
+ /* The range is +<start>[,<count>], defaulting to count = 1 */
62
+ !(* p == ' ' || (* p == ',' && !strtost (p + 1 , & n , & p ))) ||
63
+ !skip_prefix (p , " @@" , & p ))
64
+ return -1 ;
65
+
66
+ * old_count = o ;
67
+ * new_count = n ;
68
+ * end = p ;
69
+
70
+ return 0 ;
71
+ }
72
+
73
+ /*
74
+ * This function finds the end of the line, replaces the newline character with
75
+ * a NUL, and returns the offset of the start of the next line.
76
+ *
77
+ * If no newline character was found, it returns the offset of the trailing NUL
78
+ * instead.
79
+ */
80
+ static inline int find_next_line (const char * line , size_t size )
81
+ {
82
+ char * eol ;
83
+
84
+ eol = memchr (line , '\n' , size );
85
+ if (!eol )
86
+ return size ;
87
+
88
+ * eol = '\0' ;
89
+
90
+ return eol + 1 - line ;
91
+ }
92
+
93
+ static int read_mbox (const char * path , struct string_list * list )
94
+ {
95
+ struct strbuf buf = STRBUF_INIT , contents = STRBUF_INIT ;
96
+ struct strbuf long_subject = STRBUF_INIT ;
97
+ struct patch_util * util = NULL ;
98
+ enum {
99
+ MBOX_BEFORE_HEADER ,
100
+ MBOX_IN_HEADER ,
101
+ MBOX_IN_COMMIT_MESSAGE ,
102
+ MBOX_AFTER_TRIPLE_DASH ,
103
+ MBOX_IN_DIFF
104
+ } state = MBOX_BEFORE_HEADER ;
105
+ char * line , * current_filename = NULL ;
106
+ int len ;
107
+ size_t size , old_count = 0 , new_count = 0 ;
108
+ const char * author = NULL , * subject = NULL ;
109
+
110
+ if (!strcmp (path , "-" )) {
111
+ if (strbuf_read (& contents , STDIN_FILENO , 0 ) < 0 )
112
+ return error_errno (_ ("could not read stdin" ));
113
+ } else if (strbuf_read_file (& contents , path , 0 ) < 0 )
114
+ return error_errno (_ ("could not read '%s'" ), path );
115
+
116
+ line = contents .buf ;
117
+ size = contents .len ;
118
+ for (; size ; size -= len , line += len ) {
119
+ const char * p ;
120
+
121
+ len = find_next_line (line , size );
122
+
123
+ if (state == MBOX_BEFORE_HEADER ) {
124
+ parse_from_delimiter :
125
+ if (!skip_prefix (line , "From " , & p ))
126
+ continue ;
127
+
128
+ if (util )
129
+ BUG ("util already allocated" );
130
+ util = xcalloc (1 , sizeof (* util ));
131
+ if (get_oid_hex (p , & util -> oid ) < 0 )
132
+ oidcpy (& util -> oid , null_oid ());
133
+ util -> matching = -1 ;
134
+ author = subject = NULL ;
135
+
136
+ state = MBOX_IN_HEADER ;
137
+ continue ;
138
+ }
139
+
140
+ if (starts_with (line , "diff --git " )) {
141
+ struct patch patch = { 0 };
142
+ struct strbuf root = STRBUF_INIT ;
143
+ int linenr = 0 ;
144
+ int orig_len ;
145
+
146
+ state = MBOX_IN_DIFF ;
147
+ old_count = new_count = 0 ;
148
+ strbuf_addch (& buf , '\n' );
149
+ if (!util -> diff_offset )
150
+ util -> diff_offset = buf .len ;
151
+
152
+ orig_len = len ;
153
+ /* `find_next_line()`'s replaced the LF with a NUL */
154
+ line [len - 1 ] = '\n' ;
155
+ len = len > 1 && line [len - 2 ] == '\r' ?
156
+ error (_ ("cannot handle diff headers with "
157
+ "CR/LF line endings" )) :
158
+ parse_git_diff_header (& root , & linenr , 1 , line ,
159
+ len , size , & patch );
160
+ if (len < 0 ) {
161
+ error (_ ("could not parse git header '%.*s'" ),
162
+ orig_len , line );
163
+ release_patch (& patch );
164
+ free (util );
165
+ free (current_filename );
166
+ string_list_clear (list , 1 );
167
+ strbuf_release (& buf );
168
+ strbuf_release (& contents );
169
+ strbuf_release (& long_subject );
170
+ return -1 ;
171
+ }
172
+
173
+ if (patch .old_name )
174
+ skip_prefix (patch .old_name , "a/" ,
175
+ (const char * * )& patch .old_name );
176
+ if (patch .new_name )
177
+ skip_prefix (patch .new_name , "b/" ,
178
+ (const char * * )& patch .new_name );
179
+
180
+ strbuf_addstr (& buf , " ## " );
181
+ if (patch .is_new )
182
+ strbuf_addf (& buf , "%s (new)" , patch .new_name );
183
+ else if (patch .is_delete )
184
+ strbuf_addf (& buf , "%s (deleted)" , patch .old_name );
185
+ else if (patch .is_rename )
186
+ strbuf_addf (& buf , "%s => %s" , patch .old_name , patch .new_name );
187
+ else
188
+ strbuf_addstr (& buf , patch .new_name );
189
+
190
+ free (current_filename );
191
+ if (patch .is_delete )
192
+ current_filename = xstrdup (patch .old_name );
193
+ else
194
+ current_filename = xstrdup (patch .new_name );
195
+
196
+ if (patch .new_mode && patch .old_mode &&
197
+ patch .old_mode != patch .new_mode )
198
+ strbuf_addf (& buf , " (mode change %06o => %06o)" ,
199
+ patch .old_mode , patch .new_mode );
200
+
201
+ strbuf_addstr (& buf , " ##\n" );
202
+ util -> diffsize ++ ;
203
+ release_patch (& patch );
204
+ } else if (state == MBOX_IN_HEADER ) {
205
+ if (!line [0 ]) {
206
+ state = MBOX_IN_COMMIT_MESSAGE ;
207
+ /* Look for an in-body From: */
208
+ if (skip_prefix (line + 1 , "From: " , & p )) {
209
+ size -= p - line ;
210
+ line += p - line ;
211
+ len = find_next_line (line , size );
212
+
213
+ while (isspace (* p ))
214
+ p ++ ;
215
+ author = p ;
216
+ }
217
+ strbuf_addstr (& buf , " ## Metadata ##\n" );
218
+ if (author )
219
+ strbuf_addf (& buf , "Author: %s\n" , author );
220
+ strbuf_addstr (& buf , "\n ## Commit message ##\n" );
221
+ if (subject )
222
+ strbuf_addf (& buf , " %s\n\n" , subject );
223
+ } else if (skip_prefix (line , "From: " , & p )) {
224
+ while (isspace (* p ))
225
+ p ++ ;
226
+ author = p ;
227
+ } else if (skip_prefix (line , "Subject: " , & p )) {
228
+ const char * q ;
229
+
230
+ while (isspace (* p ))
231
+ p ++ ;
232
+ subject = p ;
233
+
234
+ if (starts_with (p , "[PATCH" ) &&
235
+ (q = strchr (p , ']' ))) {
236
+ q ++ ;
237
+ while (isspace (* q ))
238
+ q ++ ;
239
+ subject = q ;
240
+ }
241
+
242
+ if (len < size && line [len ] == ' ' ) {
243
+ /* handle long subject */
244
+ strbuf_reset (& long_subject );
245
+ strbuf_addstr (& long_subject , subject );
246
+ while (len < size && line [len ] == ' ' ) {
247
+ line += len ;
248
+ size -= len ;
249
+ len = find_next_line (line , size );
250
+ strbuf_addstr (& long_subject , line );
251
+ }
252
+ subject = long_subject .buf ;
253
+ }
254
+ }
255
+ } else if (state == MBOX_IN_COMMIT_MESSAGE ) {
256
+ if (!line [0 ]) {
257
+ strbuf_addch (& buf , '\n' );
258
+ } else if (strcmp (line , "---" )) {
259
+ int tabs = 0 ;
260
+
261
+ /* simulate tab expansion */
262
+ while (line [tabs ] == '\t' )
263
+ tabs ++ ;
264
+ strbuf_addf (& buf , "%*s%s\n" ,
265
+ 4 + 8 * tabs , "" , line + tabs );
266
+ } else {
267
+ /*
268
+ * Trim the trailing newline that is added
269
+ * by `format-patch`.
270
+ */
271
+ strbuf_trim_trailing_newline (& buf );
272
+ state = MBOX_AFTER_TRIPLE_DASH ;
273
+ }
274
+ } else if (state == MBOX_IN_DIFF ) {
275
+ switch (line [0 ]) {
276
+ case '\0' : /* newer GNU diff, an empty context line */
277
+ case '+' :
278
+ case '-' :
279
+ case ' ' :
280
+ /* A `-- ` line indicates the end of a diff */
281
+ if (!old_count && !new_count )
282
+ break ;
283
+ if (old_count && line [0 ] != '+' )
284
+ old_count -- ;
285
+ if (new_count && line [0 ] != '-' )
286
+ new_count -- ;
287
+ /* fallthrough */
288
+ case '\\' :
289
+ strbuf_addstr (& buf , line );
290
+ strbuf_addch (& buf , '\n' );
291
+ util -> diffsize ++ ;
292
+ continue ;
293
+ case '@' :
294
+ if (parse_hunk_header (line , & old_count ,
295
+ & new_count , & p ))
296
+ break ;
297
+
298
+ strbuf_addstr (& buf , "@@" );
299
+ if (current_filename && * p )
300
+ strbuf_addf (& buf , " %s:" ,
301
+ current_filename );
302
+ strbuf_addstr (& buf , p );
303
+ strbuf_addch (& buf , '\n' );
304
+ util -> diffsize ++ ;
305
+ continue ;
306
+ default :
307
+ if (old_count || new_count )
308
+ warning (_ ("diff ended prematurely (-%d/+%d)" ),
309
+ (int )old_count , (int )new_count );
310
+ break ;
311
+ }
312
+
313
+ if (util ) {
314
+ string_list_append (list , buf .buf )-> util = util ;
315
+ util = NULL ;
316
+ strbuf_reset (& buf );
317
+ }
318
+ state = MBOX_BEFORE_HEADER ;
319
+ goto parse_from_delimiter ;
320
+ }
321
+ }
322
+ strbuf_release (& contents );
323
+
324
+ if (util ) {
325
+ if (state == MBOX_IN_DIFF )
326
+ string_list_append (list , buf .buf )-> util = util ;
327
+ else
328
+ free (util );
329
+ }
330
+ strbuf_release (& buf );
331
+ strbuf_release (& long_subject );
332
+ free (current_filename );
333
+
334
+ return 0 ;
335
+ }
336
+
29
337
/*
30
338
* Reads the patches into a string list, with the `util` field being populated
31
339
* as struct object_id (will need to be free()d).
@@ -41,6 +349,10 @@ static int read_patches(const char *range, struct string_list *list,
41
349
ssize_t len ;
42
350
size_t size ;
43
351
int ret = -1 ;
352
+ const char * path ;
353
+
354
+ if (skip_prefix (range , "mbox:" , & path ))
355
+ return read_mbox (path , list );
44
356
45
357
strvec_pushl (& cp .args , "log" , "--no-color" , "-p" , "--no-merges" ,
46
358
"--reverse" , "--date-order" , "--decorate=no" ,
@@ -424,6 +736,19 @@ static void output_pair_header(struct diff_options *diffopt,
424
736
425
737
strbuf_addch (buf , ' ' );
426
738
pp_commit_easy (CMIT_FMT_ONELINE , commit , buf );
739
+ } else {
740
+ struct patch_util * util = b_util ? b_util : a_util ;
741
+ const char * needle = "\n ## Commit message ##\n" ;
742
+ const char * p = !util || !util -> patch ?
743
+ NULL : strstr (util -> patch , needle );
744
+ if (p ) {
745
+ if (status == '!' )
746
+ strbuf_addf (buf , "%s%s" , color_reset , color );
747
+
748
+ strbuf_addch (buf , ' ' );
749
+ p += strlen (needle );
750
+ strbuf_add (buf , p , strchrnul (p , '\n' ) - p );
751
+ }
427
752
}
428
753
strbuf_addf (buf , "%s\n" , color_reset );
429
754
@@ -554,6 +879,9 @@ int show_range_diff(const char *range1, const char *range2,
554
879
if (range_diff_opts -> left_only && range_diff_opts -> right_only )
555
880
res = error (_ ("options '%s' and '%s' cannot be used together" ), "--left-only" , "--right-only" );
556
881
882
+ if (!strcmp (range1 , "mbox:-" ) && !strcmp (range2 , "mbox:-" ))
883
+ res = error (_ ("only one mbox can be read from stdin" ));
884
+
557
885
if (!res && read_patches (range1 , & branch1 , range_diff_opts -> other_arg ))
558
886
res = error (_ ("could not parse log for '%s'" ), range1 );
559
887
if (!res && read_patches (range2 , & branch2 , range_diff_opts -> other_arg ))
@@ -575,10 +903,18 @@ int show_range_diff(const char *range1, const char *range2,
575
903
int is_range_diff_range (const char * arg )
576
904
{
577
905
char * copy = xstrdup (arg ); /* setup_revisions() modifies it */
578
- const char * argv [] = { "" , copy , "--" , NULL };
906
+ const char * argv [] = { "" , copy , "--" , NULL }, * path ;
579
907
int i , positive = 0 , negative = 0 ;
580
908
struct rev_info revs ;
581
909
910
+ if (skip_prefix (arg , "mbox:" , & path )) {
911
+ free (copy );
912
+ if (!strcmp (path , "-" ) || file_exists (path ))
913
+ return 1 ;
914
+ error_errno (_ ("not an mbox: '%s'" ), path );
915
+ return 0 ;
916
+ }
917
+
582
918
init_revisions (& revs , NULL );
583
919
if (setup_revisions (3 , argv , & revs , NULL ) == 1 ) {
584
920
for (i = 0 ; i < revs .pending .nr ; i ++ )
0 commit comments