notes.c: use designated initializers for clarity
[git/debian.git] / builtin / mailsplit.c
blob0b6193a0915a1fef572bb8a1148ae02f53ecf51c
1 /*
2 * Totally braindamaged mbox splitter program.
4 * It just splits a mbox into a list of files: "0001" "0002" ..
5 * so you can process them further from there.
6 */
7 #include "cache.h"
8 #include "builtin.h"
9 #include "gettext.h"
10 #include "string-list.h"
11 #include "strbuf.h"
13 static const char git_mailsplit_usage[] =
14 "git mailsplit [-d<prec>] [-f<n>] [-b] [--keep-cr] -o<directory> [(<mbox>|<Maildir>)...]";
16 static int is_from_line(const char *line, int len)
18 const char *colon;
20 if (len < 20 || memcmp("From ", line, 5))
21 return 0;
23 colon = line + len - 2;
24 line += 5;
25 for (;;) {
26 if (colon < line)
27 return 0;
28 if (*--colon == ':')
29 break;
32 if (!isdigit(colon[-4]) ||
33 !isdigit(colon[-2]) ||
34 !isdigit(colon[-1]) ||
35 !isdigit(colon[ 1]) ||
36 !isdigit(colon[ 2]))
37 return 0;
39 /* year */
40 if (strtol(colon+3, NULL, 10) <= 90)
41 return 0;
43 /* Ok, close enough */
44 return 1;
47 static struct strbuf buf = STRBUF_INIT;
48 static int keep_cr;
49 static int mboxrd;
51 static int is_gtfrom(const struct strbuf *buf)
53 size_t min = strlen(">From ");
54 size_t ngt;
56 if (buf->len < min)
57 return 0;
59 ngt = strspn(buf->buf, ">");
60 return ngt && starts_with(buf->buf + ngt, "From ");
63 /* Called with the first line (potentially partial)
64 * already in buf[] -- normally that should begin with
65 * the Unix "From " line. Write it into the specified
66 * file.
68 static int split_one(FILE *mbox, const char *name, int allow_bare)
70 FILE *output;
71 int fd;
72 int status = 0;
73 int is_bare = !is_from_line(buf.buf, buf.len);
75 if (is_bare && !allow_bare) {
76 fprintf(stderr, "corrupt mailbox\n");
77 exit(1);
79 fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
80 output = xfdopen(fd, "w");
82 /* Copy it out, while searching for a line that begins with
83 * "From " and having something that looks like a date format.
85 for (;;) {
86 if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' &&
87 buf.buf[buf.len-2] == '\r') {
88 strbuf_setlen(&buf, buf.len-2);
89 strbuf_addch(&buf, '\n');
92 if (mboxrd && is_gtfrom(&buf))
93 strbuf_remove(&buf, 0, 1);
95 if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
96 die_errno("cannot write output");
98 if (strbuf_getwholeline(&buf, mbox, '\n')) {
99 if (feof(mbox)) {
100 status = 1;
101 break;
103 die_errno("cannot read mbox");
105 if (!is_bare && is_from_line(buf.buf, buf.len))
106 break; /* done with one message */
108 fclose(output);
109 return status;
112 static int populate_maildir_list(struct string_list *list, const char *path)
114 DIR *dir;
115 struct dirent *dent;
116 char *name = NULL;
117 char *subs[] = { "cur", "new", NULL };
118 char **sub;
119 int ret = -1;
121 for (sub = subs; *sub; ++sub) {
122 free(name);
123 name = xstrfmt("%s/%s", path, *sub);
124 if (!(dir = opendir(name))) {
125 if (errno == ENOENT)
126 continue;
127 error_errno("cannot opendir %s", name);
128 goto out;
131 while ((dent = readdir(dir)) != NULL) {
132 if (dent->d_name[0] == '.')
133 continue;
134 free(name);
135 name = xstrfmt("%s/%s", *sub, dent->d_name);
136 string_list_insert(list, name);
139 closedir(dir);
142 ret = 0;
144 out:
145 free(name);
146 return ret;
149 static int maildir_filename_cmp(const char *a, const char *b)
151 while (*a && *b) {
152 if (isdigit(*a) && isdigit(*b)) {
153 long int na, nb;
154 na = strtol(a, (char **)&a, 10);
155 nb = strtol(b, (char **)&b, 10);
156 if (na != nb)
157 return na - nb;
158 /* strtol advanced our pointers */
160 else {
161 if (*a != *b)
162 return (unsigned char)*a - (unsigned char)*b;
163 a++;
164 b++;
167 return (unsigned char)*a - (unsigned char)*b;
170 static int split_maildir(const char *maildir, const char *dir,
171 int nr_prec, int skip)
173 char *file = NULL;
174 FILE *f = NULL;
175 int ret = -1;
176 int i;
177 struct string_list list = STRING_LIST_INIT_DUP;
179 list.cmp = maildir_filename_cmp;
181 if (populate_maildir_list(&list, maildir) < 0)
182 goto out;
184 for (i = 0; i < list.nr; i++) {
185 char *name;
187 free(file);
188 file = xstrfmt("%s/%s", maildir, list.items[i].string);
190 f = fopen(file, "r");
191 if (!f) {
192 error_errno("cannot open mail %s", file);
193 goto out;
196 if (strbuf_getwholeline(&buf, f, '\n')) {
197 error_errno("cannot read mail %s", file);
198 goto out;
201 name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
202 split_one(f, name, 1);
203 free(name);
205 fclose(f);
206 f = NULL;
209 ret = skip;
210 out:
211 if (f)
212 fclose(f);
213 free(file);
214 string_list_clear(&list, 1);
215 return ret;
218 static int split_mbox(const char *file, const char *dir, int allow_bare,
219 int nr_prec, int skip)
221 int ret = -1;
222 int peek;
224 FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
225 int file_done = 0;
227 if (isatty(fileno(f)))
228 warning(_("reading patches from stdin/tty..."));
230 if (!f) {
231 error_errno("cannot open mbox %s", file);
232 goto out;
235 do {
236 peek = fgetc(f);
237 if (peek == EOF) {
238 if (f == stdin)
239 /* empty stdin is OK */
240 ret = skip;
241 else {
242 fclose(f);
243 error(_("empty mbox: '%s'"), file);
245 goto out;
247 } while (isspace(peek));
248 ungetc(peek, f);
250 if (strbuf_getwholeline(&buf, f, '\n')) {
251 /* empty stdin is OK */
252 if (f != stdin) {
253 error("cannot read mbox %s", file);
254 goto out;
256 file_done = 1;
259 while (!file_done) {
260 char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
261 file_done = split_one(f, name, allow_bare);
262 free(name);
265 if (f != stdin)
266 fclose(f);
268 ret = skip;
269 out:
270 return ret;
273 int cmd_mailsplit(int argc, const char **argv, const char *prefix)
275 int nr = 0, nr_prec = 4, num = 0;
276 int allow_bare = 0;
277 const char *dir = NULL;
278 const char **argp;
279 static const char *stdin_only[] = { "-", NULL };
281 BUG_ON_NON_EMPTY_PREFIX(prefix);
283 for (argp = argv+1; *argp; argp++) {
284 const char *arg = *argp;
286 if (arg[0] != '-')
287 break;
288 /* do flags here */
289 if ( arg[1] == 'd' ) {
290 nr_prec = strtol(arg+2, NULL, 10);
291 if (nr_prec < 3 || 10 <= nr_prec)
292 usage(git_mailsplit_usage);
293 continue;
294 } else if ( arg[1] == 'f' ) {
295 nr = strtol(arg+2, NULL, 10);
296 } else if ( arg[1] == 'h' ) {
297 usage(git_mailsplit_usage);
298 } else if ( arg[1] == 'b' && !arg[2] ) {
299 allow_bare = 1;
300 } else if (!strcmp(arg, "--keep-cr")) {
301 keep_cr = 1;
302 } else if ( arg[1] == 'o' && arg[2] ) {
303 dir = arg+2;
304 } else if (!strcmp(arg, "--mboxrd")) {
305 mboxrd = 1;
306 } else if ( arg[1] == '-' && !arg[2] ) {
307 argp++; /* -- marks end of options */
308 break;
309 } else {
310 die("unknown option: %s", arg);
314 if ( !dir ) {
315 /* Backwards compatibility: if no -o specified, accept
316 <mbox> <dir> or just <dir> */
317 switch (argc - (argp-argv)) {
318 case 1:
319 dir = argp[0];
320 argp = stdin_only;
321 break;
322 case 2:
323 stdin_only[0] = argp[0];
324 dir = argp[1];
325 argp = stdin_only;
326 break;
327 default:
328 usage(git_mailsplit_usage);
330 } else {
331 /* New usage: if no more argument, parse stdin */
332 if ( !*argp )
333 argp = stdin_only;
336 while (*argp) {
337 const char *arg = *argp++;
338 struct stat argstat;
339 int ret = 0;
341 if (arg[0] == '-' && arg[1] == 0) {
342 ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
343 if (ret < 0) {
344 error("cannot split patches from stdin");
345 return 1;
347 num += (ret - nr);
348 nr = ret;
349 continue;
352 if (stat(arg, &argstat) == -1) {
353 error_errno("cannot stat %s", arg);
354 return 1;
357 if (S_ISDIR(argstat.st_mode))
358 ret = split_maildir(arg, dir, nr_prec, nr);
359 else
360 ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
362 if (ret < 0) {
363 error("cannot split patches from %s", arg);
364 return 1;
366 num += (ret - nr);
367 nr = ret;
370 printf("%d\n", num);
372 return 0;