builtin-am: auto-detect mbox patches
[git/git.git] / builtin / am.c
CommitLineData
73c2779f
PT
1/*
2 * Builtin "git am"
3 *
4 * Based on git-am.sh by Junio C Hamano.
5 */
6#include "cache.h"
7#include "builtin.h"
8#include "exec_cmd.h"
8c3bd9e2
PT
9#include "parse-options.h"
10#include "dir.h"
11c2177f
PT
11#include "run-command.h"
12
c29807b2
PT
13/**
14 * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
15 */
16static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
17{
18 if (strbuf_getwholeline(sb, fp, '\n'))
19 return EOF;
20 if (sb->buf[sb->len - 1] == '\n') {
21 strbuf_setlen(sb, sb->len - 1);
22 if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
23 strbuf_setlen(sb, sb->len - 1);
24 }
25 return 0;
26}
27
11c2177f
PT
28enum patch_format {
29 PATCH_FORMAT_UNKNOWN = 0,
30 PATCH_FORMAT_MBOX
31};
8c3bd9e2
PT
32
33struct am_state {
34 /* state directory path */
35 char *dir;
36
37 /* current and last patch numbers, 1-indexed */
38 int cur;
39 int last;
11c2177f
PT
40
41 /* number of digits in patch filename */
42 int prec;
8c3bd9e2
PT
43};
44
45/**
46 * Initializes am_state with the default values. The state directory is set to
47 * dir.
48 */
49static void am_state_init(struct am_state *state, const char *dir)
50{
51 memset(state, 0, sizeof(*state));
52
53 assert(dir);
54 state->dir = xstrdup(dir);
11c2177f
PT
55
56 state->prec = 4;
8c3bd9e2
PT
57}
58
59/**
60 * Releases memory allocated by an am_state.
61 */
62static void am_state_release(struct am_state *state)
63{
64 free(state->dir);
65}
66
67/**
68 * Returns path relative to the am_state directory.
69 */
70static inline const char *am_path(const struct am_state *state, const char *path)
71{
72 return mkpath("%s/%s", state->dir, path);
73}
74
75/**
76 * Returns 1 if there is an am session in progress, 0 otherwise.
77 */
78static int am_in_progress(const struct am_state *state)
79{
80 struct stat st;
81
82 if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
83 return 0;
84 if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
85 return 0;
86 if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
87 return 0;
88 return 1;
89}
90
91/**
92 * Reads the contents of `file` in the `state` directory into `sb`. Returns the
93 * number of bytes read on success, -1 if the file does not exist. If `trim` is
94 * set, trailing whitespace will be removed.
95 */
96static int read_state_file(struct strbuf *sb, const struct am_state *state,
97 const char *file, int trim)
98{
99 strbuf_reset(sb);
100
101 if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
102 if (trim)
103 strbuf_trim(sb);
104
105 return sb->len;
106 }
107
108 if (errno == ENOENT)
109 return -1;
110
111 die_errno(_("could not read '%s'"), am_path(state, file));
112}
113
114/**
115 * Loads state from disk.
116 */
117static void am_load(struct am_state *state)
118{
119 struct strbuf sb = STRBUF_INIT;
120
121 if (read_state_file(&sb, state, "next", 1) < 0)
122 die("BUG: state file 'next' does not exist");
123 state->cur = strtol(sb.buf, NULL, 10);
124
125 if (read_state_file(&sb, state, "last", 1) < 0)
126 die("BUG: state file 'last' does not exist");
127 state->last = strtol(sb.buf, NULL, 10);
128
129 strbuf_release(&sb);
130}
131
132/**
133 * Removes the am_state directory, forcefully terminating the current am
134 * session.
135 */
136static void am_destroy(const struct am_state *state)
137{
138 struct strbuf sb = STRBUF_INIT;
139
140 strbuf_addstr(&sb, state->dir);
141 remove_dir_recursively(&sb, 0);
142 strbuf_release(&sb);
143}
144
c29807b2
PT
145/**
146 * Determines if the file looks like a piece of RFC2822 mail by grabbing all
147 * non-indented lines and checking if they look like they begin with valid
148 * header field names.
149 *
150 * Returns 1 if the file looks like a piece of mail, 0 otherwise.
151 */
152static int is_mail(FILE *fp)
153{
154 const char *header_regex = "^[!-9;-~]+:";
155 struct strbuf sb = STRBUF_INIT;
156 regex_t regex;
157 int ret = 1;
158
159 if (fseek(fp, 0L, SEEK_SET))
160 die_errno(_("fseek failed"));
161
162 if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
163 die("invalid pattern: %s", header_regex);
164
165 while (!strbuf_getline_crlf(&sb, fp)) {
166 if (!sb.len)
167 break; /* End of header */
168
169 /* Ignore indented folded lines */
170 if (*sb.buf == '\t' || *sb.buf == ' ')
171 continue;
172
173 /* It's a header if it matches header_regex */
174 if (regexec(&regex, sb.buf, 0, NULL, 0)) {
175 ret = 0;
176 goto done;
177 }
178 }
179
180done:
181 regfree(&regex);
182 strbuf_release(&sb);
183 return ret;
184}
185
186/**
187 * Attempts to detect the patch_format of the patches contained in `paths`,
188 * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
189 * detection fails.
190 */
191static int detect_patch_format(const char **paths)
192{
193 enum patch_format ret = PATCH_FORMAT_UNKNOWN;
194 struct strbuf l1 = STRBUF_INIT;
195 FILE *fp;
196
197 /*
198 * We default to mbox format if input is from stdin and for directories
199 */
200 if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
201 return PATCH_FORMAT_MBOX;
202
203 /*
204 * Otherwise, check the first few lines of the first patch, starting
205 * from the first non-blank line, to try to detect its format.
206 */
207
208 fp = xfopen(*paths, "r");
209
210 while (!strbuf_getline_crlf(&l1, fp)) {
211 if (l1.len)
212 break;
213 }
214
215 if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
216 ret = PATCH_FORMAT_MBOX;
217 goto done;
218 }
219
220 if (l1.len && is_mail(fp)) {
221 ret = PATCH_FORMAT_MBOX;
222 goto done;
223 }
224
225done:
226 fclose(fp);
227 strbuf_release(&l1);
228 return ret;
229}
230
11c2177f
PT
231/**
232 * Splits out individual email patches from `paths`, where each path is either
233 * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
234 */
235static int split_mail_mbox(struct am_state *state, const char **paths)
236{
237 struct child_process cp = CHILD_PROCESS_INIT;
238 struct strbuf last = STRBUF_INIT;
239
240 cp.git_cmd = 1;
241 argv_array_push(&cp.args, "mailsplit");
242 argv_array_pushf(&cp.args, "-d%d", state->prec);
243 argv_array_pushf(&cp.args, "-o%s", state->dir);
244 argv_array_push(&cp.args, "-b");
245 argv_array_push(&cp.args, "--");
246 argv_array_pushv(&cp.args, paths);
247
248 if (capture_command(&cp, &last, 8))
249 return -1;
250
251 state->cur = 1;
252 state->last = strtol(last.buf, NULL, 10);
253
254 return 0;
255}
256
257/**
258 * Splits a list of files/directories into individual email patches. Each path
259 * in `paths` must be a file/directory that is formatted according to
260 * `patch_format`.
261 *
262 * Once split out, the individual email patches will be stored in the state
263 * directory, with each patch's filename being its index, padded to state->prec
264 * digits.
265 *
266 * state->cur will be set to the index of the first mail, and state->last will
267 * be set to the index of the last mail.
268 *
269 * Returns 0 on success, -1 on failure.
270 */
271static int split_mail(struct am_state *state, enum patch_format patch_format,
272 const char **paths)
273{
274 switch (patch_format) {
275 case PATCH_FORMAT_MBOX:
276 return split_mail_mbox(state, paths);
277 default:
278 die("BUG: invalid patch_format");
279 }
280 return -1;
281}
282
8c3bd9e2
PT
283/**
284 * Setup a new am session for applying patches
285 */
11c2177f
PT
286static void am_setup(struct am_state *state, enum patch_format patch_format,
287 const char **paths)
8c3bd9e2 288{
c29807b2
PT
289 if (!patch_format)
290 patch_format = detect_patch_format(paths);
291
292 if (!patch_format) {
293 fprintf_ln(stderr, _("Patch format detection failed."));
294 exit(128);
295 }
296
8c3bd9e2
PT
297 if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
298 die_errno(_("failed to create directory '%s'"), state->dir);
299
11c2177f
PT
300 if (split_mail(state, patch_format, paths) < 0) {
301 am_destroy(state);
302 die(_("Failed to split patches."));
303 }
304
8c3bd9e2
PT
305 /*
306 * NOTE: Since the "next" and "last" files determine if an am_state
307 * session is in progress, they should be written last.
308 */
309
310 write_file(am_path(state, "next"), 1, "%d", state->cur);
311
312 write_file(am_path(state, "last"), 1, "%d", state->last);
313}
314
315/**
316 * Increments the patch pointer, and cleans am_state for the application of the
317 * next patch.
318 */
319static void am_next(struct am_state *state)
320{
321 state->cur++;
322 write_file(am_path(state, "next"), 1, "%d", state->cur);
323}
324
325/**
326 * Applies all queued mail.
327 */
328static void am_run(struct am_state *state)
329{
330 while (state->cur <= state->last) {
331
332 /* NEEDSWORK: Patch application not implemented yet */
333
334 am_next(state);
335 }
336
337 am_destroy(state);
338}
73c2779f 339
11c2177f
PT
340/**
341 * parse_options() callback that validates and sets opt->value to the
342 * PATCH_FORMAT_* enum value corresponding to `arg`.
343 */
344static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
345{
346 int *opt_value = opt->value;
347
348 if (!strcmp(arg, "mbox"))
349 *opt_value = PATCH_FORMAT_MBOX;
350 else
351 return error(_("Invalid value for --patch-format: %s"), arg);
352 return 0;
353}
354
73c2779f
PT
355int cmd_am(int argc, const char **argv, const char *prefix)
356{
8c3bd9e2 357 struct am_state state;
11c2177f 358 int patch_format = PATCH_FORMAT_UNKNOWN;
8c3bd9e2
PT
359
360 const char * const usage[] = {
361 N_("git am [options] [(<mbox>|<Maildir>)...]"),
362 NULL
363 };
364
365 struct option options[] = {
11c2177f
PT
366 OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
367 N_("format the patch(es) are in"),
368 parse_opt_patchformat),
8c3bd9e2
PT
369 OPT_END()
370 };
73c2779f
PT
371
372 /*
373 * NEEDSWORK: Once all the features of git-am.sh have been
374 * re-implemented in builtin/am.c, this preamble can be removed.
375 */
376 if (!getenv("_GIT_USE_BUILTIN_AM")) {
377 const char *path = mkpath("%s/git-am", git_exec_path());
378
379 if (sane_execvp(path, (char **)argv) < 0)
380 die_errno("could not exec %s", path);
381 } else {
382 prefix = setup_git_directory();
383 trace_repo_setup(prefix);
384 setup_work_tree();
385 }
386
8c3bd9e2
PT
387 git_config(git_default_config, NULL);
388
389 am_state_init(&state, git_path("rebase-apply"));
390
391 argc = parse_options(argc, argv, prefix, options, usage, 0);
392
393 if (am_in_progress(&state))
394 am_load(&state);
11c2177f
PT
395 else {
396 struct argv_array paths = ARGV_ARRAY_INIT;
397 int i;
398
399 for (i = 0; i < argc; i++) {
400 if (is_absolute_path(argv[i]) || !prefix)
401 argv_array_push(&paths, argv[i]);
402 else
403 argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
404 }
405
406 am_setup(&state, patch_format, paths.argv);
407
408 argv_array_clear(&paths);
409 }
8c3bd9e2
PT
410
411 am_run(&state);
412
413 am_state_release(&state);
414
73c2779f
PT
415 return 0;
416}