Merge branch 'mm/gc-safety-doc'
[gitweb.git] / sequencer.c
index bd11db427378e75d03751f5c72b14ece5ef3faff..9adb7bbf1d4815d9e4f67b66e90c9ec327b2fb72 100644 (file)
@@ -16,6 +16,7 @@
 #include "refs.h"
 #include "argv-array.h"
 #include "quote.h"
+#include "trailer.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -27,6 +28,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
+static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
 
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
@@ -56,30 +58,6 @@ static const char *get_todo_path(const struct replay_opts *opts)
        return git_path_todo_file();
 }
 
-static int is_rfc2822_line(const char *buf, int len)
-{
-       int i;
-
-       for (i = 0; i < len; i++) {
-               int ch = buf[i];
-               if (ch == ':')
-                       return 1;
-               if (!isalnum(ch) && ch != '-')
-                       break;
-       }
-
-       return 0;
-}
-
-static int is_cherry_picked_from_line(const char *buf, int len)
-{
-       /*
-        * We only care that it looks roughly like (cherry picked from ...)
-        */
-       return len > strlen(cherry_picked_prefix) + 1 &&
-               starts_with(buf, cherry_picked_prefix) && buf[len - 1] == ')';
-}
-
 /*
  * Returns 0 for non-conforming footer
  * Returns 1 for conforming footer
@@ -89,49 +67,25 @@ static int is_cherry_picked_from_line(const char *buf, int len)
 static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
        int ignore_footer)
 {
-       char prev;
-       int i, k;
-       int len = sb->len - ignore_footer;
-       const char *buf = sb->buf;
-       int found_sob = 0;
-
-       /* footer must end with newline */
-       if (!len || buf[len - 1] != '\n')
-               return 0;
+       struct trailer_info info;
+       int i;
+       int found_sob = 0, found_sob_last = 0;
 
-       prev = '\0';
-       for (i = len - 1; i > 0; i--) {
-               char ch = buf[i];
-               if (prev == '\n' && ch == '\n') /* paragraph break */
-                       break;
-               prev = ch;
-       }
+       trailer_info_get(&info, sb->buf);
 
-       /* require at least one blank line */
-       if (prev != '\n' || buf[i] != '\n')
+       if (info.trailer_start == info.trailer_end)
                return 0;
 
-       /* advance to start of last paragraph */
-       while (i < len - 1 && buf[i] == '\n')
-               i++;
-
-       for (; i < len; i = k) {
-               int found_rfc2822;
-
-               for (k = i; k < len && buf[k] != '\n'; k++)
-                       ; /* do nothing */
-               k++;
+       for (i = 0; i < info.trailer_nr; i++)
+               if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {
+                       found_sob = 1;
+                       if (i == info.trailer_nr - 1)
+                               found_sob_last = 1;
+               }
 
-               found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
-               if (found_rfc2822 && sob &&
-                   !strncmp(buf + i, sob->buf, sob->len))
-                       found_sob = k;
+       trailer_info_release(&info);
 
-               if (!(found_rfc2822 ||
-                     is_cherry_picked_from_line(buf + i, k - i - 1)))
-                       return 0;
-       }
-       if (found_sob == i)
+       if (found_sob_last)
                return 3;
        if (found_sob)
                return 2;
@@ -168,7 +122,7 @@ int sequencer_remove_state(struct replay_opts *opts)
 
 static const char *action_name(const struct replay_opts *opts)
 {
-       return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+       return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick");
 }
 
 struct commit_message {
@@ -241,18 +195,18 @@ static int write_message(const void *buf, size_t len, const char *filename,
 
        int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
        if (msg_fd < 0)
-               return error_errno(_("Could not lock '%s'"), filename);
+               return error_errno(_("could not lock '%s'"), filename);
        if (write_in_full(msg_fd, buf, len) < 0) {
                rollback_lock_file(&msg_file);
-               return error_errno(_("Could not write to '%s'"), filename);
+               return error_errno(_("could not write to '%s'"), filename);
        }
        if (append_eol && write(msg_fd, "\n", 1) < 0) {
                rollback_lock_file(&msg_file);
-               return error_errno(_("Could not write eol to '%s"), filename);
+               return error_errno(_("could not write eol to '%s'"), filename);
        }
        if (commit_lock_file(&msg_file) < 0) {
                rollback_lock_file(&msg_file);
-               return error(_("Error wrapping up %s."), filename);
+               return error(_("failed to finalize '%s'."), filename);
        }
 
        return 0;
@@ -300,16 +254,30 @@ static struct tree *empty_tree(void)
 static int error_dirty_index(struct replay_opts *opts)
 {
        if (read_cache_unmerged())
-               return error_resolve_conflict(action_name(opts));
+               return error_resolve_conflict(_(action_name(opts)));
 
-       error(_("Your local changes would be overwritten by %s."),
-               action_name(opts));
+       error(_("your local changes would be overwritten by %s."),
+               _(action_name(opts)));
 
        if (advice_commit_before_merge)
-               advise(_("Commit your changes or stash them to proceed."));
+               advise(_("commit your changes or stash them to proceed."));
        return -1;
 }
 
+static void update_abort_safety_file(void)
+{
+       struct object_id head;
+
+       /* Do nothing on a single-pick */
+       if (!file_exists(git_path_seq_dir()))
+               return;
+
+       if (!get_oid("HEAD", &head))
+               write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head));
+       else
+               write_file(git_path_abort_safety_file(), "%s", "");
+}
+
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
                        int unborn, struct replay_opts *opts)
 {
@@ -321,7 +289,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
        if (checkout_fast_forward(from, to, 1))
                return -1; /* the callee should have complained already */
 
-       strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts));
+       strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
@@ -339,6 +307,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
        strbuf_release(&sb);
        strbuf_release(&err);
        ref_transaction_free(transaction);
+       update_abort_safety_file();
        return 0;
 }
 
@@ -370,7 +339,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        char **xopt;
        static struct lock_file index_lock;
 
-       hold_locked_index(&index_lock, 1);
+       hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
 
        read_cache();
 
@@ -397,7 +366,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
            write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
                /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
                return error(_("%s: Unable to write new index file"),
-                       action_name(opts));
+                       _(action_name(opts)));
        rollback_lock_file(&index_lock);
 
        if (opts->signoff)
@@ -415,7 +384,7 @@ static int is_index_unchanged(void)
        struct commit *head_commit;
 
        if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
-               return error(_("Could not resolve HEAD commit\n"));
+               return error(_("could not resolve HEAD commit\n"));
 
        head_commit = lookup_commit(head_sha1);
 
@@ -435,7 +404,7 @@ static int is_index_unchanged(void)
 
        if (!cache_tree_fully_valid(active_cache_tree))
                if (cache_tree_update(&the_index, 0))
-                       return error(_("Unable to update cache tree\n"));
+                       return error(_("unable to update cache tree\n"));
 
        return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
 }
@@ -479,6 +448,20 @@ static char **read_author_script(void)
        return env;
 }
 
+static const char staged_changes_advice[] =
+N_("you have staged changes in your working tree\n"
+"If these changes are meant to be squashed into the previous commit, run:\n"
+"\n"
+"  git commit --amend %s\n"
+"\n"
+"If they are meant to go into a new commit, run:\n"
+"\n"
+"  git commit %s\n"
+"\n"
+"In both cases, once you're done, continue with:\n"
+"\n"
+"  git rebase --continue\n");
+
 /*
  * If we are cherry-pick, and if the merge did not result in
  * hand-editing, we will hit this commit and inherit the original
@@ -505,16 +488,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                if (!env) {
                        const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
-                       return error("You have staged changes in your working "
-                               "tree. If these changes are meant to be\n"
-                               "squashed into the previous commit, run:\n\n"
-                               "  git commit --amend %s\n\n"
-                               "If they are meant to go into a new commit, "
-                               "run:\n\n"
-                               "  git commit %s\n\n"
-                               "In both cases, once you're done, continue "
-                               "with:\n\n"
-                               "  git rebase --continue\n", gpg_opt, gpg_opt);
+                       return error(_(staged_changes_advice),
+                                    gpg_opt, gpg_opt);
                }
        }
 
@@ -558,12 +533,12 @@ static int is_original_commit_empty(struct commit *commit)
        const unsigned char *ptree_sha1;
 
        if (parse_commit(commit))
-               return error(_("Could not parse commit %s\n"),
+               return error(_("could not parse commit %s\n"),
                             oid_to_hex(&commit->object.oid));
        if (commit->parents) {
                struct commit *parent = commit->parents->item;
                if (parse_commit(parent))
-                       return error(_("Could not parse parent commit %s\n"),
+                       return error(_("could not parse parent commit %s\n"),
                                oid_to_hex(&parent->object.oid));
                ptree_sha1 = parent->tree->object.oid.hash;
        } else {
@@ -623,7 +598,7 @@ static const char *todo_command_strings[] = {
 
 static const char *command_to_string(const enum todo_command command)
 {
-       if (command < ARRAY_SIZE(todo_command_strings))
+       if ((size_t)command < ARRAY_SIZE(todo_command_strings))
                return todo_command_strings[command];
        die("Unknown command: %d", command);
 }
@@ -647,12 +622,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                 * to work on.
                 */
                if (write_cache_as_tree(head, 0, NULL))
-                       return error(_("Your index file is unmerged."));
+                       return error(_("your index file is unmerged."));
        } else {
                unborn = get_sha1("HEAD", head);
                if (unborn)
                        hashcpy(head, EMPTY_TREE_SHA1_BIN);
-               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0))
+               if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
                        return error_dirty_index(opts);
        }
        discard_cache();
@@ -666,7 +641,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                struct commit_list *p;
 
                if (!opts->mainline)
-                       return error(_("Commit %s is a merge but no -m option was given."),
+                       return error(_("commit %s is a merge but no -m option was given."),
                                oid_to_hex(&commit->object.oid));
 
                for (cnt = 1, p = commit->parents;
@@ -674,11 +649,11 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                     cnt++)
                        p = p->next;
                if (cnt != opts->mainline || !p)
-                       return error(_("Commit %s does not have parent %d"),
+                       return error(_("commit %s does not have parent %d"),
                                oid_to_hex(&commit->object.oid), opts->mainline);
                parent = p->item;
        } else if (0 < opts->mainline)
-               return error(_("Mainline was specified but commit %s is not a merge."),
+               return error(_("mainline was specified but commit %s is not a merge."),
                        oid_to_hex(&commit->object.oid));
        else
                parent = commit->parents->item;
@@ -696,7 +671,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        oid_to_hex(&parent->object.oid));
 
        if (get_message(commit, &msg) != 0)
-               return error(_("Cannot get commit message for %s"),
+               return error(_("cannot get commit message for %s"),
                        oid_to_hex(&commit->object.oid));
 
        /*
@@ -807,6 +782,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
 
 leave:
        free_message(commit, &msg);
+       update_abort_safety_file();
 
        return res;
 }
@@ -835,14 +811,14 @@ static int read_and_refresh_cache(struct replay_opts *opts)
        if (read_index_preload(&the_index, NULL) < 0) {
                rollback_lock_file(&index_lock);
                return error(_("git %s: failed to read the index"),
-                       action_name(opts));
+                       _(action_name(opts)));
        }
        refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
        if (the_index.cache_changed && index_fd >= 0) {
                if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
                        rollback_lock_file(&index_lock);
                        return error(_("git %s: failed to refresh the index"),
-                               action_name(opts));
+                               _(action_name(opts)));
                }
        }
        rollback_lock_file(&index_lock);
@@ -935,13 +911,13 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
                item = append_new_todo(todo_list);
                item->offset_in_buf = p - todo_list->buf.buf;
                if (parse_insn_line(item, p, eol)) {
-                       res = error(_("Invalid line %d: %.*s"),
+                       res = error(_("invalid line %d: %.*s"),
                                i, (int)(eol - p), p);
                        item->command = -1;
                }
        }
        if (!todo_list->nr)
-               return error(_("No commits parsed."));
+               return error(_("no commits parsed."));
        return res;
 }
 
@@ -954,16 +930,16 @@ static int read_populate_todo(struct todo_list *todo_list,
        strbuf_reset(&todo_list->buf);
        fd = open(todo_file, O_RDONLY);
        if (fd < 0)
-               return error_errno(_("Could not open %s"), todo_file);
+               return error_errno(_("could not open '%s'"), todo_file);
        if (strbuf_read(&todo_list->buf, fd, 0) < 0) {
                close(fd);
-               return error(_("Could not read %s."), todo_file);
+               return error(_("could not read '%s'."), todo_file);
        }
        close(fd);
 
        res = parse_insn_buffer(todo_list->buf.buf, todo_list);
        if (res)
-               return error(_("Unusable instruction sheet: %s"), todo_file);
+               return error(_("unusable instruction sheet: '%s'"), todo_file);
 
        if (!is_rebase_i(opts)) {
                enum todo_command valid =
@@ -974,9 +950,9 @@ static int read_populate_todo(struct todo_list *todo_list,
                        if (valid == todo_list->items[i].command)
                                continue;
                        else if (valid == TODO_PICK)
-                               return error(_("Cannot cherry-pick during a revert."));
+                               return error(_("cannot cherry-pick during a revert."));
                        else
-                               return error(_("Cannot revert during a cherry-pick."));
+                               return error(_("cannot revert during a cherry-pick."));
        }
 
        return 0;
@@ -1019,10 +995,10 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
                ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
                opts->xopts[opts->xopts_nr++] = xstrdup(value);
        } else
-               return error(_("Invalid key: %s"), key);
+               return error(_("invalid key: %s"), key);
 
        if (!error_flag)
-               return error(_("Invalid value for %s: %s"), key, value);
+               return error(_("invalid value for %s: %s"), key, value);
 
        return 0;
 }
@@ -1054,7 +1030,7 @@ static int read_populate_opts(struct replay_opts *opts)
         * are pretty certain that it is syntactically correct.
         */
        if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0)
-               return error(_("Malformed options sheet: %s"),
+               return error(_("malformed options sheet: '%s'"),
                        git_path_opts_file());
        return 0;
 }
@@ -1097,7 +1073,7 @@ static int create_seq_dir(void)
                return -1;
        }
        else if (mkdir(git_path_seq_dir(), 0777) < 0)
-               return error_errno(_("Could not create sequencer directory %s"),
+               return error_errno(_("could not create sequencer directory '%s'"),
                                   git_path_seq_dir());
        return 0;
 }
@@ -1111,24 +1087,49 @@ static int save_head(const char *head)
        fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
        if (fd < 0) {
                rollback_lock_file(&head_lock);
-               return error_errno(_("Could not lock HEAD"));
+               return error_errno(_("could not lock HEAD"));
        }
        strbuf_addf(&buf, "%s\n", head);
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
                rollback_lock_file(&head_lock);
-               return error_errno(_("Could not write to %s"),
+               return error_errno(_("could not write to '%s'"),
                                   git_path_head_file());
        }
        if (commit_lock_file(&head_lock) < 0) {
                rollback_lock_file(&head_lock);
-               return error(_("Error wrapping up %s."), git_path_head_file());
+               return error(_("failed to finalize '%s'."), git_path_head_file());
        }
        return 0;
 }
 
+static int rollback_is_safe(void)
+{
+       struct strbuf sb = STRBUF_INIT;
+       struct object_id expected_head, actual_head;
+
+       if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
+               strbuf_trim(&sb);
+               if (get_oid_hex(sb.buf, &expected_head)) {
+                       strbuf_release(&sb);
+                       die(_("could not parse %s"), git_path_abort_safety_file());
+               }
+               strbuf_release(&sb);
+       }
+       else if (errno == ENOENT)
+               oidclr(&expected_head);
+       else
+               die_errno(_("could not read '%s'"), git_path_abort_safety_file());
+
+       if (get_oid("HEAD", &actual_head))
+               oidclr(&actual_head);
+
+       return !oidcmp(&actual_head, &expected_head);
+}
+
 static int reset_for_rollback(const unsigned char *sha1)
 {
        const char *argv[4];    /* reset --merge <arg> + NULL */
+
        argv[0] = "reset";
        argv[1] = "--merge";
        argv[2] = sha1_to_hex(sha1);
@@ -1166,9 +1167,9 @@ int sequencer_rollback(struct replay_opts *opts)
                return rollback_single_pick();
        }
        if (!f)
-               return error_errno(_("cannot open %s"), git_path_head_file());
+               return error_errno(_("cannot open '%s'"), git_path_head_file());
        if (strbuf_getline_lf(&buf, f)) {
-               error(_("cannot read %s: %s"), git_path_head_file(),
+               error(_("cannot read '%s': %s"), git_path_head_file(),
                      ferror(f) ?  strerror(errno) : _("unexpected end of file"));
                fclose(f);
                goto fail;
@@ -1183,6 +1184,12 @@ int sequencer_rollback(struct replay_opts *opts)
                error(_("cannot abort from a branch yet to be born"));
                goto fail;
        }
+
+       if (!rollback_is_safe()) {
+               /* Do not error, just do not rollback */
+               warning(_("You seem to have moved HEAD. "
+                         "Not rewinding, check your HEAD!"));
+       } else
        if (reset_for_rollback(sha1))
                goto fail;
        strbuf_release(&buf);
@@ -1200,14 +1207,14 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
 
        fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
        if (fd < 0)
-               return error_errno(_("Could not lock '%s'"), todo_path);
+               return error_errno(_("could not lock '%s'"), todo_path);
        offset = next < todo_list->nr ?
                todo_list->items[next].offset_in_buf : todo_list->buf.len;
        if (write_in_full(fd, todo_list->buf.buf + offset,
                        todo_list->buf.len - offset) < 0)
-               return error_errno(_("Could not write to '%s'"), todo_path);
+               return error_errno(_("could not write to '%s'"), todo_path);
        if (commit_lock_file(&todo_lock) < 0)
-               return error(_("Error wrapping up %s."), todo_path);
+               return error(_("failed to finalize '%s'."), todo_path);
        return 0;
 }
 
@@ -1306,7 +1313,7 @@ int sequencer_continue(struct replay_opts *opts)
                if (res)
                        goto release_todo_list;
        }
-       if (index_differs_from("HEAD", 0)) {
+       if (index_differs_from("HEAD", 0, 0)) {
                res = error_dirty_index(opts);
                goto release_todo_list;
        }
@@ -1382,11 +1389,12 @@ int sequencer_pick_revisions(struct replay_opts *opts)
                        create_seq_dir() < 0)
                return -1;
        if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT))
-               return error(_("Can't revert as initial commit"));
+               return error(_("can't revert as initial commit"));
        if (save_head(sha1_to_hex(sha1)))
                return -1;
        if (save_opts(opts))
                return -1;
+       update_abort_safety_file();
        res = pick_commits(&todo_list, opts);
        todo_list_release(&todo_list);
        return res;