/* * Builtin "git am" * * Based on git-am.sh by Junio C Hamano. */ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" #include "dir.h" #include "run-command.h" #include "quote.h" #include "cache-tree.h" #include "refs.h" #include "commit.h" #include "lockfile.h" #include "diff.h" #include "diffcore.h" #include "unpack-trees.h" #include "branch.h" #include "sequencer.h" #include "merge-recursive.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. */ static int is_empty_file(const char *filename) { struct stat st; if (stat(filename, &st) < 0) { if (errno == ENOENT) return 1; die_errno(_("could not stat %s"), filename); } return !st.st_size; } /** * Returns the first line of msg */ static const char *firstline(const char *msg) { static struct strbuf sb = STRBUF_INIT; strbuf_reset(&sb); strbuf_add(&sb, msg, strchrnul(msg, '\n') - msg); return sb.buf; } enum patch_format { PATCH_FORMAT_UNKNOWN = 0, PATCH_FORMAT_MBOX }; struct am_state { /* state directory path */ struct strbuf dir; /* current and last patch numbers, 1-indexed */ int cur; int last; /* commit message and metadata */ struct strbuf author_name; struct strbuf author_email; struct strbuf author_date; struct strbuf msg; /* number of digits in patch filename */ int prec; int quiet; /* override error message when patch failure occurs */ const char *resolvemsg; int sign; int threeway; }; /** * Initializes am_state with the default values. */ static void am_state_init(struct am_state *state) { const char *quiet; memset(state, 0, sizeof(*state)); strbuf_init(&state->dir, 0); strbuf_init(&state->author_name, 0); strbuf_init(&state->author_email, 0); strbuf_init(&state->author_date, 0); strbuf_init(&state->msg, 0); state->prec = 4; quiet = getenv("GIT_QUIET"); if (quiet && *quiet) state->quiet = 1; } /** * Release memory allocated by an am_state. */ static void am_state_release(struct am_state *state) { strbuf_release(&state->dir); strbuf_release(&state->author_name); strbuf_release(&state->author_email); strbuf_release(&state->author_date); strbuf_release(&state->msg); } /** * Returns path relative to the am_state directory. */ static inline const char *am_path(const struct am_state *state, const char *path) { return mkpath("%s/%s", state->dir.buf, path); } /** * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline * at the end. */ static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (!state->quiet) { vfprintf(fp, fmt, ap); putc('\n', fp); } va_end(ap); } /** * Returns 1 if there is an am session in progress, 0 otherwise. */ static int am_in_progress(const struct am_state *state) { struct stat st; if (lstat(state->dir.buf, &st) < 0 || !S_ISDIR(st.st_mode)) return 0; if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode)) return 0; if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode)) return 0; return 1; } /** * Reads the contents of `file`. The third argument can be used to give a hint * about the file size, to avoid reallocs. Returns number of bytes read on * success, -1 if the file does not exist. If trim is set, trailing whitespace * will be removed from the file contents. */ static int read_state_file(struct strbuf *sb, const char *file, size_t hint, int trim) { strbuf_reset(sb); if (strbuf_read_file(sb, file, hint) >= 0) { if (trim) strbuf_rtrim(sb); return sb->len; } if (errno == ENOENT) return -1; die_errno(_("could not read '%s'"), file); } /** * Parses the "author script" `filename`, and sets state->author_name, * state->author_email and state->author_date accordingly. We are strict with * our parsing, as the author script is supposed to be eval'd, and loosely * parsing it may not give the results the user expects. * * The author script is of the format: * * GIT_AUTHOR_NAME='$author_name' * GIT_AUTHOR_EMAIL='$author_email' * GIT_AUTHOR_DATE='$author_date' * * where $author_name, $author_email and $author_date are quoted. */ static int read_author_script(struct am_state *state) { char *value; struct strbuf sb = STRBUF_INIT; const char *filename = am_path(state, "author-script"); FILE *fp = fopen(filename, "r"); if (!fp) { if (errno == ENOENT) return 0; die_errno(_("could not open '%s' for reading"), filename); } if (strbuf_getline(&sb, fp, '\n')) return -1; if (!skip_prefix(sb.buf, "GIT_AUTHOR_NAME=", (const char**) &value)) return -1; value = sq_dequote(value); if (!value) return -1; strbuf_reset(&state->author_name); strbuf_addstr(&state->author_name, value); if (strbuf_getline(&sb, fp, '\n')) return -1; if (!skip_prefix(sb.buf, "GIT_AUTHOR_EMAIL=", (const char**) &value)) return -1; value = sq_dequote(value); if (!value) return -1; strbuf_reset(&state->author_email); strbuf_addstr(&state->author_email, value); if (strbuf_getline(&sb, fp, '\n')) return -1; if (!skip_prefix(sb.buf, "GIT_AUTHOR_DATE=", (const char**) &value)) return -1; value = sq_dequote(value); if (!value) return -1; strbuf_reset(&state->author_date); strbuf_addstr(&state->author_date, value); if (fgetc(fp) != EOF) return -1; fclose(fp); strbuf_release(&sb); return 0; } /** * Saves state->author_name, state->author_email and state->author_date in * `filename` as an "author script", which is the format used by git-am.sh. */ static void write_author_script(const struct am_state *state) { static const char fmt[] = "GIT_AUTHOR_NAME=%s\n" "GIT_AUTHOR_EMAIL=%s\n" "GIT_AUTHOR_DATE=%s\n"; struct strbuf author_name = STRBUF_INIT; struct strbuf author_email = STRBUF_INIT; struct strbuf author_date = STRBUF_INIT; sq_quote_buf(&author_name, state->author_name.buf); sq_quote_buf(&author_email, state->author_email.buf); sq_quote_buf(&author_date, state->author_date.buf); write_file(am_path(state, "author-script"), 1, fmt, author_name.buf, author_email.buf, author_date.buf); strbuf_release(&author_name); strbuf_release(&author_email); strbuf_release(&author_date); } /** * Loads state from disk. */ static void am_load(struct am_state *state) { struct strbuf sb = STRBUF_INIT; read_state_file(&sb, am_path(state, "next"), 8, 1); state->cur = strtol(sb.buf, NULL, 10); read_state_file(&sb, am_path(state, "last"), 8, 1); state->last = strtol(sb.buf, NULL, 10); if (read_author_script(state) < 0) die(_("could not parse author script")); read_state_file(&state->msg, am_path(state, "final-commit"), 0, 0); read_state_file(&sb, am_path(state, "quiet"), 2, 1); state->quiet = !strcmp(sb.buf, "t"); read_state_file(&sb, am_path(state, "sign"), 2, 1); state->sign = !strcmp(sb.buf, "t"); strbuf_release(&sb); } /** * Remove the am_state directory. */ static void am_destroy(const struct am_state *state) { struct strbuf sb = STRBUF_INIT; strbuf_addstr(&sb, state->dir.buf); remove_dir_recursively(&sb, 0); strbuf_release(&sb); } /* * Returns 1 if the file looks like a piece of email a-la RFC2822, 0 otherwise. * We check this by grabbing all the non-indented lines and seeing if they look * like they begin with valid header field names. */ static int is_email(const char *filename) { struct strbuf sb = STRBUF_INIT; FILE *fp = xfopen(filename, "r"); int ret = 1; while (!strbuf_getline(&sb, fp, '\n')) { const char *x; strbuf_rtrim(&sb); if (!sb.len) break; /* End of header */ /* Ignore indented folded lines */ if (*sb.buf == '\t' || *sb.buf == ' ') continue; /* It's a header if it matches the regexp "^[!-9;-~]+:" */ for (x = sb.buf; *x; x++) { if (('!' <= *x && *x <= '9') || (';' <= *x && *x <= '~')) continue; if (*x == ':' && x != sb.buf) break; ret = 0; goto done; } } done: fclose(fp); strbuf_release(&sb); return ret; } /** * Attempts to detect the patch_format of the patches contained in `paths`, * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if * detection fails. */ static int detect_patch_format(struct string_list *paths) { enum patch_format ret = PATCH_FORMAT_UNKNOWN; struct strbuf l1 = STRBUF_INIT; struct strbuf l2 = STRBUF_INIT; struct strbuf l3 = STRBUF_INIT; FILE *fp; /* * We default to mbox format if input is from stdin and for directories */ if (!paths->nr || !strcmp(paths->items->string, "-") || is_directory(paths->items->string)) { ret = PATCH_FORMAT_MBOX; goto done; } /* * Otherwise, check the first 3 lines of the first patch, starting * from the first non-blank line, to try to detect its format. */ fp = xfopen(paths->items->string, "r"); while (!strbuf_getline(&l1, fp, '\n')) { strbuf_trim(&l1); if (l1.len) break; } strbuf_getline(&l2, fp, '\n'); strbuf_trim(&l2); strbuf_getline(&l3, fp, '\n'); strbuf_trim(&l3); fclose(fp); if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) ret = PATCH_FORMAT_MBOX; else if (l1.len && l2.len && l3.len && is_email(paths->items->string)) ret = PATCH_FORMAT_MBOX; done: strbuf_release(&l1); strbuf_release(&l2); strbuf_release(&l3); return ret; } /** * Splits out individual patches from `paths`, where each path is either a mbox * file or a Maildir. Return 0 on success, -1 on failure. */ static int split_patches_mbox(struct am_state *state, struct string_list *paths) { struct child_process cp = CHILD_PROCESS_INIT; struct string_list_item *item; struct strbuf last = STRBUF_INIT; cp.git_cmd = 1; argv_array_push(&cp.args, "mailsplit"); argv_array_pushf(&cp.args, "-d%d", state->prec); argv_array_pushf(&cp.args, "-o%s", state->dir.buf); argv_array_push(&cp.args, "-b"); argv_array_push(&cp.args, "--"); for_each_string_list_item(item, paths) argv_array_push(&cp.args, item->string); if (capture_command(&cp, &last, 8)) return -1; state->cur = 1; state->last = strtol(last.buf, NULL, 10); return 0; } /** * Splits out individual patches, of patch_format, contained within paths. * These patches will be stored in the state directory, with each patch's * filename being its index, padded to state->prec digits. state->cur will be * set to the index of the first patch, and state->last will be set to the * index of the last patch. Returns 0 on success, -1 on failure. */ static int split_patches(struct am_state *state, enum patch_format patch_format, struct string_list *paths) { switch (patch_format) { case PATCH_FORMAT_MBOX: return split_patches_mbox(state, paths); default: die("BUG: invalid patch_format"); } return -1; } /** * Setup a new am session for applying patches */ static void am_setup(struct am_state *state, enum patch_format patch_format, struct string_list *paths) { unsigned char curr_head[20]; if (!patch_format) patch_format = detect_patch_format(paths); if (!patch_format) { fprintf_ln(stderr, _("Patch format detection failed.")); exit(128); } if (mkdir(state->dir.buf, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir.buf); if (split_patches(state, patch_format, paths) < 0) { am_destroy(state); die(_("Failed to split patches.")); } write_file(am_path(state, "next"), 1, "%d", state->cur); write_file(am_path(state, "last"), 1, "%d", state->last); write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f"); write_file(am_path(state, "sign"), 1, state->sign ? "t" : "f"); if (!get_sha1("HEAD", curr_head)) { write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head)); update_ref("am", "ORIG_HEAD", curr_head, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } else { write_file(am_path(state, "abort-safety"), 1, "%s", ""); delete_ref("ORIG_HEAD", NULL, 0); } } /** * Increments the patch pointer, and cleans am_state for the application of the * next patch. */ static void am_next(struct am_state *state) { unsigned char head[GIT_SHA1_RAWSZ]; state->cur++; write_file(am_path(state, "next"), 1, "%d", state->cur); strbuf_reset(&state->author_name); strbuf_reset(&state->author_email); strbuf_reset(&state->author_date); unlink(am_path(state, "author-script")); strbuf_reset(&state->msg); unlink(am_path(state, "final-commit")); if (!get_sha1("HEAD", head)) write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head)); else write_file(am_path(state, "abort-safety"), 1, "%s", ""); } /** * Returns the filename of the current patch. */ static const char *msgnum(const struct am_state *state) { static struct strbuf sb = STRBUF_INIT; strbuf_reset(&sb); strbuf_addf(&sb, "%0*d", state->prec, state->cur); return sb.buf; } /** * Refresh and write index. */ static void refresh_and_write_cache(void) { static struct lock_file lock_file; hold_locked_index(&lock_file, 1); refresh_cache(REFRESH_QUIET); if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write index file")); rollback_lock_file(&lock_file); } /** * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn * branch, returns 1 if there are entries in the index, 0 otherwise. If an * strbuf is provided, the space-separated list of files that differ will be * appended to it. */ static int index_has_changes(struct strbuf *sb) { unsigned char head[GIT_SHA1_RAWSZ]; int i; if (!get_sha1_tree("HEAD", head)) { struct diff_options opt; diff_setup(&opt); DIFF_OPT_SET(&opt, EXIT_WITH_STATUS); if (!sb) DIFF_OPT_SET(&opt, QUICK); do_diff_cache(head, &opt); diffcore_std(&opt); for (i = 0; sb && i < diff_queued_diff.nr; i++) { if (i) strbuf_addch(sb, ' '); strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); } diff_flush(&opt); return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0; } else { for (i = 0; sb && i < active_nr; i++) { if (i) strbuf_addch(sb, ' '); strbuf_addstr(sb, active_cache[i]->name); } return !!active_nr; } } /** * Parses `patch` using git-mailinfo. state->msg will be set to the patch * message. state->author_name, state->author_email, state->author_date will be * set to the patch author's name, email and date respectively. The patch's * body will be written to "$state_dir/patch", where $state_dir is the state * directory. * * Returns 1 if the patch should be skipped, 0 otherwise. */ static int parse_patch(struct am_state *state, const char *patch) { FILE *fp; struct child_process cp = CHILD_PROCESS_INIT; struct strbuf sb = STRBUF_INIT; cp.git_cmd = 1; cp.in = xopen(patch, O_RDONLY, 0); cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777); argv_array_push(&cp.args, "mailinfo"); argv_array_push(&cp.args, am_path(state, "msg")); argv_array_push(&cp.args, am_path(state, "patch")); if (run_command(&cp) < 0) die("could not parse patch"); close(cp.in); close(cp.out); /* Extract message and author information */ fp = xfopen(am_path(state, "info"), "r"); while (!strbuf_getline(&sb, fp, '\n')) { const char *x; if (skip_prefix(sb.buf, "Subject: ", &x)) { if (state->msg.len) strbuf_addch(&state->msg, '\n'); strbuf_addstr(&state->msg, x); } else if (skip_prefix(sb.buf, "Author: ", &x)) { if (state->author_name.len) strbuf_addch(&state->author_name, '\n'); strbuf_addstr(&state->author_name, x); } else if (skip_prefix(sb.buf, "Email: ", &x)) { if (state->author_email.len) strbuf_addch(&state->author_email, '\n'); strbuf_addstr(&state->author_email, x); } else if (skip_prefix(sb.buf, "Date: ", &x)) { if (state->author_date.len) strbuf_addch(&state->author_date, '\n'); strbuf_addstr(&state->author_date, x); } } fclose(fp); /* Skip pine's internal folder data */ if (!strcmp(state->author_name.buf, "Mail System Internal Data")) return 1; if (is_empty_file(am_path(state, "patch"))) die(_("Patch is empty. Was it split wrong?\n" "If you would prefer to skip this patch, instead run \"git am --skip\".\n" "To restore the original branch and stop patching run \"git am --abort\".")); strbuf_addstr(&state->msg, "\n\n"); if (strbuf_read_file(&state->msg, am_path(state, "msg"), 0) < 0) die_errno(_("could not read '%s'"), am_path(state, "msg")); stripspace(&state->msg, 0); if (state->sign) append_signoff(&state->msg, 0, 0); return 0; } /** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ static void NORETURN die_user_resolve(const struct am_state *state) { if (state->resolvemsg) printf_ln("%s", state->resolvemsg); else printf_ln(_("When you have resolved this problem, run \"git am --continue\".\n" "If you prefer to skip this patch, run \"git am --skip\" instead.\n" "To restore the original branch and stop patching, run \"git am --abort\".")); exit(128); } /* * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If * index_file is not NULL, the patch will be applied to that index. */ static int run_apply(const struct am_state *state, const char *index_file) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; if (index_file) argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file); /* * If we are allowed to fall back on 3-way merge, don't give false * errors during the initial attempt. */ if (state->threeway && !index_file) { cp.no_stdout = 1; cp.no_stderr = 1; } argv_array_push(&cp.args, "apply"); if (index_file) argv_array_push(&cp.args, "--cached"); else argv_array_push(&cp.args, "--index"); argv_array_push(&cp.args, am_path(state, "patch")); if (run_command(&cp)) return -1; /* Reload index as git-apply will have modified it. */ discard_cache(); read_cache_from(index_file ? index_file : get_index_file()); return 0; } /* * Builds a index that contains just the blobs needed for a 3way merge. */ static int build_fake_ancestor(const struct am_state *state, const char *index_file) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; argv_array_push(&cp.args, "apply"); argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); argv_array_push(&cp.args, am_path(state, "patch")); if (run_command(&cp)) return -1; return 0; } /** * Attempt a threeway merge, using index_path as the temporary index. */ static int fall_back_threeway(const struct am_state *state, const char *index_path) { unsigned char orig_tree[20], his_tree[20], our_tree[20]; const unsigned char *bases[1] = {orig_tree}; struct merge_options o; struct commit *result; if (get_sha1("HEAD", our_tree) < 0) hashcpy(our_tree, EMPTY_TREE_SHA1_BIN); if (build_fake_ancestor(state, index_path)) return error("could not build fake ancestor"); discard_cache(); read_cache_from(index_path); if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL)) return error(_("Repository lacks necessary blobs to fall back on 3-way merge.")); say(state, stdout, _("Using index info to reconstruct a base tree...")); if (!state->quiet) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_path); argv_array_pushl(&cp.args, "diff-index", "--cached", "--diff-filter=AM", "--name-status", "HEAD", NULL); run_command(&cp); } if (run_apply(state, index_path)) return error(_("Did you hand edit your patch?\n" "It does not apply to blobs recorded in its index.")); if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL)) return error("could not write tree"); say(state, stdout, _("Falling back to patching base and 3-way merge...")); discard_cache(); read_cache(); /* * This is not so wrong. Depending on which base we picked, orig_tree * may be wildly different from ours, but his_tree has the same set of * wildly different changes in parts the patch did not touch, so * recursive ends up canceling them, saying that we reverted all those * changes. */ init_merge_options(&o); o.branch1 = "HEAD"; o.branch2 = firstline(state->msg.buf); if (state->quiet) o.verbosity = 0; if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) return error(_("Failed to merge in the changes.")); return 0; } /** * Commits the current index with state->msg as the commit message and * state->author_name, state->author_email and state->author_date as the author * information. */ static void do_commit(const struct am_state *state) { unsigned char tree[20], parent[20], commit[20]; unsigned char *ptr; struct commit_list *parents = NULL; const char *reflog_msg, *author; struct strbuf sb = STRBUF_INIT; if (write_cache_as_tree(tree, 0, NULL)) die(_("git write-tree failed to write a tree")); if (!get_sha1_commit("HEAD", parent)) { ptr = parent; commit_list_insert(lookup_commit(parent), &parents); } else { ptr = NULL; say(state, stderr, _("applying to an empty history")); } author = fmt_ident(state->author_name.buf, state->author_email.buf, state->author_date.buf, IDENT_STRICT); if (commit_tree(state->msg.buf, state->msg.len, tree, parents, commit, author, NULL)) die(_("failed to write commit object")); reflog_msg = getenv("GIT_REFLOG_ACTION"); if (!reflog_msg) reflog_msg = "am"; strbuf_addf(&sb, "%s: %s", reflog_msg, firstline(state->msg.buf)); update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR); strbuf_release(&sb); } /** * Applies all queued patches. */ static void am_run(struct am_state *state) { struct strbuf sb = STRBUF_INIT; unlink(am_path(state, "dirtyindex")); refresh_and_write_cache(); if (index_has_changes(&sb)) { write_file(am_path(state, "dirtyindex"), 1, "t"); die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf); } strbuf_release(&sb); while (state->cur <= state->last) { const char *patch = am_path(state, msgnum(state)); int apply_status; if (!file_exists(patch)) goto next; if (parse_patch(state, patch)) goto next; /* patch should be skipped */ write_author_script(state); write_file(am_path(state, "final-commit"), 1, "%s", state->msg.buf); say(state, stdout, _("Applying: %s"), firstline(state->msg.buf)); apply_status = run_apply(state, NULL); if (apply_status && state->threeway) { struct strbuf sb = STRBUF_INIT; strbuf_addstr(&sb, am_path(state, "patch-merge-index")); apply_status = fall_back_threeway(state, sb.buf); strbuf_release(&sb); /* * Applying the patch to an earlier tree and merging * the result may have produced the same tree as ours. */ if (!apply_status && !index_has_changes(NULL)) { say(state, stdout, _("No changes -- Patch already applied.")); goto next; } } if (apply_status) { int value; printf_ln(_("Patch failed at %s %s"), msgnum(state), firstline(state->msg.buf)); if (!git_config_get_bool("advice.amworkdir", &value) && !value) printf_ln(_("The copy of the patch that failed is found in: %s"), am_path(state, "patch")); die_user_resolve(state); } do_commit(state); next: am_next(state); } am_destroy(state); } /** * Resume the current am session after patch application failure. The user did * all the hard work, and we do not have to do any patch application. Just * trust and commit what the user has in the index and working tree. */ static void am_resolve(struct am_state *state) { say(state, stdout, _("Applying: %s"), firstline(state->msg.buf)); if (!index_has_changes(NULL)) { printf_ln(_("No changes - did you forget to use 'git add'?\n" "If there is nothing left to stage, chances are that something else\n" "already introduced the same changes; you might want to skip this patch.")); die_user_resolve(state); } if (unmerged_cache()) { printf_ln(_("You still have unmerged paths in your index.\n" "Did you forget to use 'git add'?")); die_user_resolve(state); } do_commit(state); am_next(state); am_run(state); } /** * Performs a checkout fast-forward from `head` to `remote`. If `reset` is * true, any unmerged entries will be discarded. Returns 0 on success, -1 on * failure. */ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) { struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); struct unpack_trees_options opts; struct tree_desc t[2]; if (parse_tree(head) || parse_tree(remote)) return -1; hold_locked_index(lock_file, 1); refresh_cache(REFRESH_QUIET); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.src_index = &the_index; opts.dst_index = &the_index; opts.update = 1; opts.merge = 1; opts.reset = reset; opts.fn = twoway_merge; init_tree_desc(&t[0], head->buffer, head->size); init_tree_desc(&t[1], remote->buffer, remote->size); if (unpack_trees(2, t, &opts)) { rollback_lock_file(lock_file); return -1; } if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; } /** * Clean the index without touching entries that are not modified between * `head` and `remote`. */ static int clean_index(const unsigned char *head, const unsigned char *remote) { struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); struct tree *head_tree, *remote_tree, *index_tree; unsigned char index[GIT_SHA1_RAWSZ]; struct pathspec pathspec; head_tree = parse_tree_indirect(head); if (!head_tree) return error(_("Could not parse object '%s'."), sha1_to_hex(head)); remote_tree = parse_tree_indirect(remote); if (!remote_tree) return error(_("Could not parse object '%s'."), sha1_to_hex(remote)); read_cache_unmerged(); if (fast_forward_to(head_tree, head_tree, 1)) return -1; if (write_cache_as_tree(index, 0, NULL)) return -1; index_tree = parse_tree_indirect(index); if (!index_tree) return error(_("Could not parse object '%s'."), sha1_to_hex(index)); if (fast_forward_to(index_tree, remote_tree, 0)) return -1; memset(&pathspec, 0, sizeof(pathspec)); hold_locked_index(lock_file, 1); if (read_tree(remote_tree, 0, &pathspec)) { rollback_lock_file(lock_file); return -1; } if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); remove_branch_state(); return 0; } /** * Resume the current am session by skipping the current patch. */ static void am_skip(struct am_state *state) { unsigned char head[GIT_SHA1_RAWSZ]; if (get_sha1("HEAD", head)) hashcpy(head, EMPTY_TREE_SHA1_BIN); if (clean_index(head, head)) die(_("failed to clean index")); am_next(state); am_run(state); } static int safe_to_abort(const struct am_state *state) { struct strbuf sb = STRBUF_INIT; unsigned char abort_safety[20], head[20]; if (file_exists(am_path(state, "dirtyindex"))) return 0; if (read_state_file(&sb, am_path(state, "abort-safety"), 40, 1) > 0) { if (get_sha1_hex(sb.buf, abort_safety)) die(_("could not parse %s"), am_path(state, "abort_safety")); } else hashclr(abort_safety); if (get_sha1("HEAD", head)) hashclr(head); if (!hashcmp(head, abort_safety)) return 1; error(_("You seem to have moved HEAD since the last 'am' failure.\n" "Not rewinding to ORIG_HEAD")); return 0; } /** * Aborts the current am session if it is safe to do so. */ static void am_abort(struct am_state *state) { unsigned char curr_head[20], orig_head[20]; int has_curr_head, has_orig_head; const char *curr_branch; if (!safe_to_abort(state)) { am_destroy(state); return; } curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL); has_curr_head = !is_null_sha1(curr_head); if (!has_curr_head) hashcpy(curr_head, EMPTY_TREE_SHA1_BIN); has_orig_head = !get_sha1("ORIG_HEAD", orig_head); if (!has_orig_head) hashcpy(orig_head, EMPTY_TREE_SHA1_BIN); clean_index(curr_head, orig_head); if (has_orig_head) update_ref("am --abort", "HEAD", orig_head, has_curr_head ? curr_head : NULL, 0, UPDATE_REFS_DIE_ON_ERR); else if (curr_branch) delete_ref(curr_branch, NULL, REF_NODEREF); am_destroy(state); } /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. */ static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset) { int *opt_value = opt->value; if (!strcmp(arg, "mbox")) *opt_value = PATCH_FORMAT_MBOX; else return -1; return 0; } enum resume_mode { RESUME_FALSE = 0, RESUME_RESOLVED, RESUME_SKIP, RESUME_ABORT }; static struct am_state state; static int opt_patch_format; static enum resume_mode opt_resume; static const char * const am_usage[] = { N_("git am [options] [(|)...]"), N_("git am [options] (--continue | --skip | --abort)"), NULL }; static struct option am_options[] = { OPT_BOOL('3', "3way", &state.threeway, N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), OPT_BOOL('s', "signoff", &state.sign, N_("add a Signed-off-by line to the commit message")), OPT_CALLBACK(0, "patch-format", &opt_patch_format, N_("format"), N_("format the patch(es) are in"), parse_opt_patchformat), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), OPT_CMDMODE(0, "continue", &opt_resume, N_("continue applying patches after resolving a conflict"), RESUME_RESOLVED), OPT_CMDMODE('r', "resolved", &opt_resume, N_("synonyms for --continue"), RESUME_RESOLVED), OPT_CMDMODE(0, "skip", &opt_resume, N_("skip the current patch"), RESUME_SKIP), OPT_CMDMODE(0, "abort", &opt_resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), OPT_END() }; int cmd_am(int argc, const char **argv, const char *prefix) { if (!getenv("_GIT_USE_BUILTIN_AM")) { const char *path = mkpath("%s/git-am", git_exec_path()); if (sane_execvp(path, (char**) argv) < 0) die_errno("could not exec %s", path); } git_config(git_default_config, NULL); am_state_init(&state); strbuf_addstr(&state.dir, git_path("rebase-apply")); argc = parse_options(argc, argv, prefix, am_options, am_usage, 0); if (read_index_preload(&the_index, NULL) < 0) die(_("failed to read the index")); if (am_in_progress(&state)) am_load(&state); else { struct string_list paths = STRING_LIST_INIT_DUP; int i; for (i = 0; i < argc; i++) { if (is_absolute_path(argv[i]) || !prefix) string_list_append(&paths, argv[i]); else string_list_append(&paths, mkpath("%s/%s", prefix, argv[i])); } am_setup(&state, opt_patch_format, &paths); string_list_clear(&paths, 0); } switch (opt_resume) { case RESUME_FALSE: am_run(&state); break; case RESUME_RESOLVED: am_resolve(&state); break; case RESUME_SKIP: am_skip(&state); break; case RESUME_ABORT: am_abort(&state); break; default: die("BUG: invalid resume value"); } am_state_release(&state); return 0; }