builtin-am: split out mbox/maildir patches with git-mailsplit

git-am.sh supports mbox, stgit and mercurial patches. Re-implement support for splitting out mbox/maildirs using git-mailsplit, while also implementing the framework required to support other patch formats in the future. Re-implement support for the --patch-format option (since a5a6755 (git-am foreign patch support: introduce patch_format, 2009-05-27)) to allow the user to choose between the different patch formats. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Paul Tan <pyokagan@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul Tan committed Aug 4, 2015 at 21:51 UTC 11c2177f2c264513f8d3abe64666fa05bcd84a57
1 file changed +104 -3
builtin/am.c
+104 -3
@@ -8,6 +8,12 @@
8 #include "exec_cmd.h"
9 #include "parse-options.h"
10 #include "dir.h"
11 +#include "run-command.h"
12 +
13 +enum patch_format {
14 + PATCH_FORMAT_UNKNOWN = 0,
15 + PATCH_FORMAT_MBOX
16 +};
17
18 struct am_state {
19 /* state directory path */
@@ -16,6 +22,9 @@ struct am_state {
22 /* current and last patch numbers, 1-indexed */
23 int cur;
24 int last;
25 +
26 + /* number of digits in patch filename */
27 + int prec;
28 };
29
30 /**
@@ -28,6 +37,8 @@ static void am_state_init(struct am_state *state, const char *dir)
37
38 assert(dir);
39 state->dir = xstrdup(dir);
40 +
41 + state->prec = 4;
42 }
43
44 /**
@@ -116,14 +127,72 @@ static void am_destroy(const struct am_state *state)
127 strbuf_release(&sb);
128 }
129
130 +/**
131 + * Splits out individual email patches from `paths`, where each path is either
132 + * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
133 + */
134 +static int split_mail_mbox(struct am_state *state, const char **paths)
135 +{
136 + struct child_process cp = CHILD_PROCESS_INIT;
137 + struct strbuf last = STRBUF_INIT;
138 +
139 + cp.git_cmd = 1;
140 + argv_array_push(&cp.args, "mailsplit");
141 + argv_array_pushf(&cp.args, "-d%d", state->prec);
142 + argv_array_pushf(&cp.args, "-o%s", state->dir);
143 + argv_array_push(&cp.args, "-b");
144 + argv_array_push(&cp.args, "--");
145 + argv_array_pushv(&cp.args, paths);
146 +
147 + if (capture_command(&cp, &last, 8))
148 + return -1;
149 +
150 + state->cur = 1;
151 + state->last = strtol(last.buf, NULL, 10);
152 +
153 + return 0;
154 +}
155 +
156 +/**
157 + * Splits a list of files/directories into individual email patches. Each path
158 + * in `paths` must be a file/directory that is formatted according to
159 + * `patch_format`.
160 + *
161 + * Once split out, the individual email patches will be stored in the state
162 + * directory, with each patch's filename being its index, padded to state->prec
163 + * digits.
164 + *
165 + * state->cur will be set to the index of the first mail, and state->last will
166 + * be set to the index of the last mail.
167 + *
168 + * Returns 0 on success, -1 on failure.
169 + */
170 +static int split_mail(struct am_state *state, enum patch_format patch_format,
171 + const char **paths)
172 +{
173 + switch (patch_format) {
174 + case PATCH_FORMAT_MBOX:
175 + return split_mail_mbox(state, paths);
176 + default:
177 + die("BUG: invalid patch_format");
178 + }
179 + return -1;
180 +}
181 +
182 /**
183 * Setup a new am session for applying patches
184 */
122 -static void am_setup(struct am_state *state)
185 +static void am_setup(struct am_state *state, enum patch_format patch_format,
186 + const char **paths)
187 {
188 if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
189 die_errno(_("failed to create directory '%s'"), state->dir);
190
191 + if (split_mail(state, patch_format, paths) < 0) {
192 + am_destroy(state);
193 + die(_("Failed to split patches."));
194 + }
195 +
196 /*
197 * NOTE: Since the "next" and "last" files determine if an am_state
198 * session is in progress, they should be written last.
@@ -159,9 +228,25 @@ static void am_run(struct am_state *state)
228 am_destroy(state);
229 }
230
231 +/**
232 + * parse_options() callback that validates and sets opt->value to the
233 + * PATCH_FORMAT_* enum value corresponding to `arg`.
234 + */
235 +static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
236 +{
237 + int *opt_value = opt->value;
238 +
239 + if (!strcmp(arg, "mbox"))
240 + *opt_value = PATCH_FORMAT_MBOX;
241 + else
242 + return error(_("Invalid value for --patch-format: %s"), arg);
243 + return 0;
244 +}
245 +
246 int cmd_am(int argc, const char **argv, const char *prefix)
247 {
248 struct am_state state;
249 + int patch_format = PATCH_FORMAT_UNKNOWN;
250
251 const char * const usage[] = {
252 N_("git am [options] [(<mbox>|<Maildir>)...]"),
@@ -169,6 +254,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
254 };
255
256 struct option options[] = {
257 + OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
258 + N_("format the patch(es) are in"),
259 + parse_opt_patchformat),
260 OPT_END()
261 };
262
@@ -195,8 +283,21 @@ int cmd_am(int argc, const char **argv, const char *prefix)
283
284 if (am_in_progress(&state))
285 am_load(&state);
198 - else
199 - am_setup(&state);
286 + else {
287 + struct argv_array paths = ARGV_ARRAY_INIT;
288 + int i;
289 +
290 + for (i = 0; i < argc; i++) {
291 + if (is_absolute_path(argv[i]) || !prefix)
292 + argv_array_push(&paths, argv[i]);
293 + else
294 + argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
295 + }
296 +
297 + am_setup(&state, patch_format, paths.argv);
298 +
299 + argv_array_clear(&paths);
300 + }
301
302 am_run(&state);
303