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,309 @@ 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
+ (state == MBOX_IN_DIFF && line [0 ] == 'F' )) {
125
+ if (!skip_prefix (line , "From " , & p ))
126
+ continue ;
127
+
128
+ util = xcalloc (1 , sizeof (* util ));
129
+ if (get_oid_hex (p , & util -> oid ) < 0 )
130
+ oidcpy (& util -> oid , null_oid ());
131
+ util -> matching = -1 ;
132
+ author = subject = NULL ;
133
+
134
+ state = MBOX_IN_HEADER ;
135
+ continue ;
136
+ }
137
+
138
+ if (starts_with (line , "diff --git " )) {
139
+ struct patch patch = { 0 };
140
+ struct strbuf root = STRBUF_INIT ;
141
+ int linenr = 0 ;
142
+ int orig_len ;
143
+
144
+ state = MBOX_IN_DIFF ;
145
+ old_count = new_count = 0 ;
146
+ strbuf_addch (& buf , '\n' );
147
+ if (!util -> diff_offset )
148
+ util -> diff_offset = buf .len ;
149
+
150
+ orig_len = len ;
151
+ /* `find_next_line()`'s replaced the LF with a NUL */
152
+ line [len - 1 ] = '\n' ;
153
+ len = len > 1 && line [len - 2 ] == '\r' ?
154
+ error (_ ("cannot handle diff headers with "
155
+ "CR/LF line endings" )) :
156
+ parse_git_diff_header (& root , & linenr , 1 , line ,
157
+ len , size , & patch );
158
+ if (len < 0 ) {
159
+ error (_ ("could not parse git header '%.*s'" ),
160
+ orig_len , line );
161
+ release_patch (& patch );
162
+ free (util );
163
+ free (current_filename );
164
+ string_list_clear (list , 1 );
165
+ strbuf_release (& buf );
166
+ strbuf_release (& contents );
167
+ strbuf_release (& long_subject );
168
+ return -1 ;
169
+ }
170
+
171
+ if (patch .old_name )
172
+ skip_prefix (patch .old_name , "a/" ,
173
+ (const char * * )& patch .old_name );
174
+ if (patch .new_name )
175
+ skip_prefix (patch .new_name , "b/" ,
176
+ (const char * * )& patch .new_name );
177
+
178
+ strbuf_addstr (& buf , " ## " );
179
+ if (patch .is_new )
180
+ strbuf_addf (& buf , "%s (new)" , patch .new_name );
181
+ else if (patch .is_delete )
182
+ strbuf_addf (& buf , "%s (deleted)" , patch .old_name );
183
+ else if (patch .is_rename )
184
+ strbuf_addf (& buf , "%s => %s" , patch .old_name , patch .new_name );
185
+ else
186
+ strbuf_addstr (& buf , patch .new_name );
187
+
188
+ free (current_filename );
189
+ if (patch .is_delete )
190
+ current_filename = xstrdup (patch .old_name );
191
+ else
192
+ current_filename = xstrdup (patch .new_name );
193
+
194
+ if (patch .new_mode && patch .old_mode &&
195
+ patch .old_mode != patch .new_mode )
196
+ strbuf_addf (& buf , " (mode change %06o => %06o)" ,
197
+ patch .old_mode , patch .new_mode );
198
+
199
+ strbuf_addstr (& buf , " ##\n" );
200
+ util -> diffsize ++ ;
201
+ release_patch (& patch );
202
+ } else if (state == MBOX_IN_HEADER ) {
203
+ if (!line [0 ]) {
204
+ state = MBOX_IN_COMMIT_MESSAGE ;
205
+ /* Look for an in-body From: */
206
+ if (skip_prefix (line + 1 , "From: " , & p )) {
207
+ size -= p - line ;
208
+ line += p - line ;
209
+ len = find_next_line (line , size );
210
+
211
+ while (isspace (* p ))
212
+ p ++ ;
213
+ author = p ;
214
+ }
215
+ strbuf_addstr (& buf , " ## Metadata ##\n" );
216
+ if (author )
217
+ strbuf_addf (& buf , "Author: %s\n" , author );
218
+ strbuf_addstr (& buf , "\n ## Commit message ##\n" );
219
+ if (subject )
220
+ strbuf_addf (& buf , " %s\n\n" , subject );
221
+ } else if (skip_prefix (line , "From: " , & p )) {
222
+ while (isspace (* p ))
223
+ p ++ ;
224
+ author = p ;
225
+ } else if (skip_prefix (line , "Subject: " , & p )) {
226
+ const char * q ;
227
+
228
+ while (isspace (* p ))
229
+ p ++ ;
230
+ subject = p ;
231
+
232
+ if (starts_with (p , "[PATCH" ) &&
233
+ (q = strchr (p , ']' ))) {
234
+ q ++ ;
235
+ while (isspace (* q ))
236
+ q ++ ;
237
+ subject = q ;
238
+ }
239
+
240
+ if (len < size && line [len ] == ' ' ) {
241
+ /* handle long subject */
242
+ strbuf_reset (& long_subject );
243
+ strbuf_addstr (& long_subject , subject );
244
+ while (len < size && line [len ] == ' ' ) {
245
+ line += len ;
246
+ size -= len ;
247
+ len = find_next_line (line , size );
248
+ strbuf_addstr (& long_subject , line );
249
+ }
250
+ subject = long_subject .buf ;
251
+ }
252
+ }
253
+ } else if (state == MBOX_IN_COMMIT_MESSAGE ) {
254
+ if (!line [0 ]) {
255
+ strbuf_addch (& buf , '\n' );
256
+ } else if (strcmp (line , "---" )) {
257
+ int tabs = 0 ;
258
+
259
+ /* simulate tab expansion */
260
+ while (line [tabs ] == '\t' )
261
+ tabs ++ ;
262
+ strbuf_addf (& buf , "%*s%s\n" ,
263
+ 4 + 8 * tabs , "" , line + tabs );
264
+ } else {
265
+ /*
266
+ * Trim the trailing newline that is added
267
+ * by `format-patch`.
268
+ */
269
+ strbuf_trim_trailing_newline (& buf );
270
+ state = MBOX_AFTER_TRIPLE_DASH ;
271
+ }
272
+ } else if (state == MBOX_IN_DIFF ) {
273
+ switch (line [0 ]) {
274
+ case '\0' :
275
+ continue ; /* ignore empty lines after diff */
276
+ case '+' :
277
+ case '-' :
278
+ case ' ' :
279
+ /* A `-- ` line indicates the end of a diff */
280
+ if (!old_count && !new_count )
281
+ break ;
282
+ if (old_count && line [0 ] != '+' )
283
+ old_count -- ;
284
+ if (new_count && line [0 ] != '-' )
285
+ new_count -- ;
286
+ /* fallthrough */
287
+ case '\\' :
288
+ strbuf_addstr (& buf , line );
289
+ strbuf_addch (& buf , '\n' );
290
+ util -> diffsize ++ ;
291
+ continue ;
292
+ case '@' :
293
+ if (parse_hunk_header (line , & old_count ,
294
+ & new_count , & p ))
295
+ break ;
296
+
297
+ strbuf_addstr (& buf , "@@" );
298
+ if (current_filename && * p )
299
+ strbuf_addf (& buf , " %s:" ,
300
+ current_filename );
301
+ strbuf_addstr (& buf , p );
302
+ strbuf_addch (& buf , '\n' );
303
+ util -> diffsize ++ ;
304
+ continue ;
305
+ }
306
+
307
+ if (util ) {
308
+ string_list_append (list , buf .buf )-> util = util ;
309
+ strbuf_reset (& buf );
310
+ }
311
+ util = xcalloc (1 , sizeof (* util ));
312
+ oidcpy (& util -> oid , null_oid ());
313
+ util -> matching = -1 ;
314
+ author = subject = NULL ;
315
+ state = MBOX_BEFORE_HEADER ;
316
+ }
317
+ }
318
+ strbuf_release (& contents );
319
+
320
+ if (util ) {
321
+ if (state == MBOX_IN_DIFF )
322
+ string_list_append (list , buf .buf )-> util = util ;
323
+ else
324
+ free (util );
325
+ }
326
+ strbuf_release (& buf );
327
+ strbuf_release (& long_subject );
328
+ free (current_filename );
329
+
330
+ return 0 ;
331
+ }
332
+
29
333
/*
30
334
* Reads the patches into a string list, with the `util` field being populated
31
335
* as struct object_id (will need to be free()d).
@@ -41,6 +345,10 @@ static int read_patches(const char *range, struct string_list *list,
41
345
ssize_t len ;
42
346
size_t size ;
43
347
int ret = -1 ;
348
+ const char * path ;
349
+
350
+ if (skip_prefix (range , "mbox:" , & path ))
351
+ return read_mbox (path , list );
44
352
45
353
strvec_pushl (& cp .args , "log" , "--no-color" , "-p" , "--no-merges" ,
46
354
"--reverse" , "--date-order" , "--decorate=no" ,
@@ -424,6 +732,19 @@ static void output_pair_header(struct diff_options *diffopt,
424
732
425
733
strbuf_addch (buf , ' ' );
426
734
pp_commit_easy (CMIT_FMT_ONELINE , commit , buf );
735
+ } else {
736
+ struct patch_util * util = b_util ? b_util : a_util ;
737
+ const char * needle = "\n ## Commit message ##\n" ;
738
+ const char * p = !util || !util -> patch ?
739
+ NULL : strstr (util -> patch , needle );
740
+ if (p ) {
741
+ if (status == '!' )
742
+ strbuf_addf (buf , "%s%s" , color_reset , color );
743
+
744
+ strbuf_addch (buf , ' ' );
745
+ p += strlen (needle );
746
+ strbuf_add (buf , p , strchrnul (p , '\n' ) - p );
747
+ }
427
748
}
428
749
strbuf_addf (buf , "%s\n" , color_reset );
429
750
@@ -554,6 +875,9 @@ int show_range_diff(const char *range1, const char *range2,
554
875
if (range_diff_opts -> left_only && range_diff_opts -> right_only )
555
876
res = error (_ ("options '%s' and '%s' cannot be used together" ), "--left-only" , "--right-only" );
556
877
878
+ if (!strcmp (range1 , "mbox:-" ) && !strcmp (range2 , "mbox:-" ))
879
+ res = error (_ ("only one mbox can be read from stdin" ));
880
+
557
881
if (!res && read_patches (range1 , & branch1 , range_diff_opts -> other_arg ))
558
882
res = error (_ ("could not parse log for '%s'" ), range1 );
559
883
if (!res && read_patches (range2 , & branch2 , range_diff_opts -> other_arg ))
@@ -575,10 +899,17 @@ int show_range_diff(const char *range1, const char *range2,
575
899
int is_range_diff_range (const char * arg )
576
900
{
577
901
char * copy = xstrdup (arg ); /* setup_revisions() modifies it */
578
- const char * argv [] = { "" , copy , "--" , NULL };
902
+ const char * argv [] = { "" , copy , "--" , NULL }, * path ;
579
903
int i , positive = 0 , negative = 0 ;
580
904
struct rev_info revs ;
581
905
906
+ if (skip_prefix (arg , "mbox:" , & path )) {
907
+ if (!strcmp (path , "-" ) || file_exists (path ))
908
+ return 1 ;
909
+ error_errno (_ ("not an mbox: '%s'" ), path );
910
+ return 0 ;
911
+ }
912
+
582
913
init_revisions (& revs , NULL );
583
914
if (setup_revisions (3 , argv , & revs , NULL ) == 1 ) {
584
915
for (i = 0 ; i < revs .pending .nr ; i ++ )
0 commit comments