c12566ab4ab0d51ef95387b302c7e30622f0990e
   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"
   9#include "parse-options.h"
  10#include "dir.h"
  11#include "run-command.h"
  12
  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
  28enum patch_format {
  29        PATCH_FORMAT_UNKNOWN = 0,
  30        PATCH_FORMAT_MBOX
  31};
  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;
  40
  41        /* number of digits in patch filename */
  42        int prec;
  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);
  55
  56        state->prec = 4;
  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
 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
 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
 283/**
 284 * Setup a new am session for applying patches
 285 */
 286static void am_setup(struct am_state *state, enum patch_format patch_format,
 287                        const char **paths)
 288{
 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
 297        if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
 298                die_errno(_("failed to create directory '%s'"), state->dir);
 299
 300        if (split_mail(state, patch_format, paths) < 0) {
 301                am_destroy(state);
 302                die(_("Failed to split patches."));
 303        }
 304
 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}
 339
 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
 355int cmd_am(int argc, const char **argv, const char *prefix)
 356{
 357        struct am_state state;
 358        int patch_format = PATCH_FORMAT_UNKNOWN;
 359
 360        const char * const usage[] = {
 361                N_("git am [options] [(<mbox>|<Maildir>)...]"),
 362                NULL
 363        };
 364
 365        struct option options[] = {
 366                OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
 367                        N_("format the patch(es) are in"),
 368                        parse_opt_patchformat),
 369                OPT_END()
 370        };
 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
 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);
 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        }
 410
 411        am_run(&state);
 412
 413        am_state_release(&state);
 414
 415        return 0;
 416}