]> git.ipfire.org Git - thirdparty/git.git/blobdiff - sequencer.c
Merge branch 'pw/rebase-i-after-failure' into maint-2.42
[thirdparty/git.git] / sequencer.c
index f014c60cc8a8914a4ae8a42b217278b09cbbeffd..81e1ad5832b242054a9796cff9ac16c5fd4b6499 100644 (file)
@@ -1,14 +1,18 @@
-#include "cache.h"
+#include "git-compat-util.h"
 #include "abspath.h"
-#include "alloc.h"
+#include "advice.h"
 #include "config.h"
+#include "copy.h"
 #include "environment.h"
 #include "gettext.h"
 #include "hex.h"
 #include "lockfile.h"
 #include "dir.h"
-#include "object-store.h"
+#include "object-file.h"
+#include "object-name.h"
+#include "object-store-ll.h"
 #include "object.h"
+#include "pager.h"
 #include "commit.h"
 #include "sequencer.h"
 #include "tag.h"
 #include "utf8.h"
 #include "cache-tree.h"
 #include "diff.h"
+#include "path.h"
 #include "revision.h"
 #include "rerere.h"
+#include "merge.h"
 #include "merge-ort.h"
 #include "merge-ort-wrappers.h"
 #include "refs.h"
+#include "sparse-index.h"
 #include "strvec.h"
 #include "quote.h"
 #include "trailer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
 #include "branch.h"
-#include "wrapper.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
+/*
+ * To accommodate common filesystem limitations, where the loose refs' file
+ * names must not exceed `NAME_MAX`, the labels generated by `git rebase
+ * --rebase-merges` need to be truncated if the corresponding commit subjects
+ * are too long.
+ * Add some margin to stay clear from reaching `NAME_MAX`.
+ */
+#define GIT_MAX_LABEL_LENGTH ((NAME_MAX) - (LOCK_SUFFIX_LEN) - 16)
+
 static const char sign_off_header[] = "Signed-off-by: ";
 static const char cherry_picked_prefix[] = "(cherry picked from commit ";
 
@@ -219,7 +234,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)
        return rec;
 }
 
-static int git_sequencer_config(const char *k, const char *v, void *cb)
+static int git_sequencer_config(const char *k, const char *v,
+                               const struct config_context *ctx, void *cb)
 {
        struct replay_opts *opts = cb;
        int status;
@@ -274,7 +290,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)
        if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
                opts->commit_use_reference = git_config_bool(k, v);
 
-       return git_diff_basic_config(k, v, NULL);
+       return git_diff_basic_config(k, v, ctx, NULL);
 }
 
 void sequencer_init_config(struct replay_opts *opts)
@@ -660,11 +676,12 @@ void append_conflicts_hint(struct index_state *istate,
        }
 
        strbuf_addch(msgbuf, '\n');
-       strbuf_commented_addf(msgbuf, "Conflicts:\n");
+       strbuf_commented_addf(msgbuf, comment_line_char, "Conflicts:\n");
        for (i = 0; i < istate->cache_nr;) {
                const struct cache_entry *ce = istate->cache[i++];
                if (ce_stage(ce)) {
-                       strbuf_commented_addf(msgbuf, "\t%s\n", ce->name);
+                       strbuf_commented_addf(msgbuf, comment_line_char,
+                                             "\t%s\n", ce->name);
                        while (i < istate->cache_nr &&
                               !strcmp(ce->name, istate->cache[i]->name))
                                i++;
@@ -1143,7 +1160,8 @@ void cleanup_message(struct strbuf *msgbuf,
            cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
                strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len));
        if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
-               strbuf_stripspace(msgbuf, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+               strbuf_stripspace(msgbuf,
+                 cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
 }
 
 /*
@@ -1174,7 +1192,8 @@ int template_untouched(const struct strbuf *sb, const char *template_file,
        if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
                return 0;
 
-       strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+       strbuf_stripspace(&tmpl,
+         cleanup_mode == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if (!skip_prefix(sb->buf, tmpl.buf, &start))
                start = sb->buf;
        strbuf_release(&tmpl);
@@ -1546,7 +1565,8 @@ static int try_to_commit(struct repository *r,
                cleanup = opts->default_msg_cleanup;
 
        if (cleanup != COMMIT_MSG_CLEANUP_NONE)
-               strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
+               strbuf_stripspace(msg,
+                 cleanup == COMMIT_MSG_CLEANUP_ALL ? comment_line_char : '\0');
        if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
                res = 1; /* run 'git commit' to display error message */
                goto out;
@@ -1840,7 +1860,7 @@ static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
                s += count;
                len -= count;
        }
-       strbuf_add_commented_lines(buf, s, len);
+       strbuf_add_commented_lines(buf, s, len, comment_line_char);
 }
 
 /* Does the current fixup chain contain a squash command? */
@@ -1939,7 +1959,7 @@ static int append_squash_message(struct strbuf *buf, const char *body,
        strbuf_addf(buf, _(nth_commit_msg_fmt),
                    ++opts->current_fixup_count + 1);
        strbuf_addstr(buf, "\n\n");
-       strbuf_add_commented_lines(buf, body, commented_len);
+       strbuf_add_commented_lines(buf, body, commented_len, comment_line_char);
        /* buf->buf may be reallocated so store an offset into the buffer */
        fixup_off = buf->len;
        strbuf_addstr(buf, body + commented_len);
@@ -2029,7 +2049,8 @@ static int update_squash_messages(struct repository *r,
                              _(first_commit_msg_str));
                strbuf_addstr(&buf, "\n\n");
                if (is_fixup_flag(command, flag))
-                       strbuf_add_commented_lines(&buf, body, strlen(body));
+                       strbuf_add_commented_lines(&buf, body, strlen(body),
+                                                  comment_line_char);
                else
                        strbuf_addstr(&buf, body);
 
@@ -2048,7 +2069,8 @@ static int update_squash_messages(struct repository *r,
                strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
                            ++opts->current_fixup_count + 1);
                strbuf_addstr(&buf, "\n\n");
-               strbuf_add_commented_lines(&buf, body, strlen(body));
+               strbuf_add_commented_lines(&buf, body, strlen(body),
+                                          comment_line_char);
        } else
                return error(_("unknown command: %d"), command);
        repo_unuse_commit_buffer(r, commit, message);
@@ -2477,7 +2499,6 @@ void todo_list_release(struct todo_list *todo_list)
 static struct todo_item *append_new_todo(struct todo_list *todo_list)
 {
        ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);
-       todo_list->total_nr++;
        return todo_list->items + todo_list->nr++;
 }
 
@@ -2668,7 +2689,7 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf,
        char *p = buf, *next_p;
        int i, res = 0, fixup_okay = file_exists(rebase_path_done());
 
-       todo_list->current = todo_list->nr = 0;
+       todo_list->current = todo_list->nr = todo_list->total_nr = 0;
 
        for (i = 1; *p; i++, p = next_p) {
                char *eol = strchrnul(p, '\n');
@@ -2689,10 +2710,13 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf,
                        item->commit = NULL;
                }
 
+               if (item->command != TODO_COMMENT)
+                       todo_list->total_nr++;
+
                if (fixup_okay)
                        ; /* do nothing */
                else if (is_fixup(item->command))
-                       return error(_("cannot '%s' without a previous commit"),
+                       res = error(_("cannot '%s' without a previous commit"),
                                command_to_string(item->command));
                else if (!is_noop(item->command))
                        fixup_okay = 1;
@@ -2879,7 +2903,9 @@ static int git_config_string_dup(char **dest,
        return 0;
 }
 
-static int populate_opts_cb(const char *key, const char *value, void *data)
+static int populate_opts_cb(const char *key, const char *value,
+                           const struct config_context *ctx,
+                           void *data)
 {
        struct replay_opts *opts = data;
        int error_flag = 1;
@@ -2887,26 +2913,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
        if (!value)
                error_flag = 0;
        else if (!strcmp(key, "options.no-commit"))
-               opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+               opts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.edit"))
-               opts->edit = git_config_bool_or_int(key, value, &error_flag);
+               opts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty"))
                opts->allow_empty =
-                       git_config_bool_or_int(key, value, &error_flag);
+                       git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-empty-message"))
                opts->allow_empty_message =
-                       git_config_bool_or_int(key, value, &error_flag);
+                       git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.keep-redundant-commits"))
                opts->keep_redundant_commits =
-                       git_config_bool_or_int(key, value, &error_flag);
+                       git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.signoff"))
-               opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+               opts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.record-origin"))
-               opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+               opts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.allow-ff"))
-               opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+               opts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);
        else if (!strcmp(key, "options.mainline"))
-               opts->mainline = git_config_int(key, value);
+               opts->mainline = git_config_int(key, value, ctx->kvi);
        else if (!strcmp(key, "options.strategy"))
                git_config_string_dup(&opts->strategy, key, value);
        else if (!strcmp(key, "options.gpg-sign"))
@@ -2915,7 +2941,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
                strvec_push(&opts->xopts, value);
        } else if (!strcmp(key, "options.allow-rerere-auto"))
                opts->allow_rerere_auto =
-                       git_config_bool_or_int(key, value, &error_flag) ?
+                       git_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?
                                RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
        else if (!strcmp(key, "options.default-msg-cleanup")) {
                opts->explicit_cleanup = 1;
@@ -3626,14 +3652,14 @@ static int do_exec(struct repository *r, const char *command_line)
                          "  git rebase --continue\n"
                          "\n"),
                        command_line,
-                       dirty ? N_("and made changes to the index and/or the "
-                               "working tree\n") : "");
+                       dirty ? _("and made changes to the index and/or the "
+                               "working tree.\n") : "");
                if (status == 127)
                        /* command not found */
                        status = 1;
        } else if (dirty) {
                warning(_("execution succeeded: %s\nbut "
-                         "left changes to the index and/or the working tree\n"
+                         "left changes to the index and/or the working tree.\n"
                          "Commit or stash your changes, and then run\n"
                          "\n"
                          "  git rebase --continue\n"
@@ -4271,7 +4297,7 @@ void todo_list_filter_update_refs(struct repository *r,
                if (!is_null_oid(&rec->after))
                        continue;
 
-               for (j = 0; !found && j < todo_list->total_nr; j++) {
+               for (j = 0; !found && j < todo_list->nr; j++) {
                        struct todo_item *item = &todo_list->items[j];
                        const char *arg = todo_list->buf.buf + item->arg_offset;
 
@@ -4301,7 +4327,7 @@ void todo_list_filter_update_refs(struct repository *r,
         * For each todo_item, check if its ref is in the update_refs list.
         * If not, then add it as an un-updated ref.
         */
-       for (i = 0; i < todo_list->total_nr; i++) {
+       for (i = 0; i < todo_list->nr; i++) {
                struct todo_item *item = &todo_list->items[i];
                const char *arg = todo_list->buf.buf + item->arg_offset;
                int j, found = 0;
@@ -5047,19 +5073,31 @@ static int commit_staged_changes(struct repository *r,
                                 * We need to update the squash message to skip
                                 * the latest commit message.
                                 */
+                               int res = 0;
                                struct commit *commit;
+                               const char *msg;
                                const char *path = rebase_path_squash_msg();
                                const char *encoding = get_commit_output_encoding();
 
-                               if (parse_head(r, &commit) ||
-                                   !(p = repo_logmsg_reencode(r, commit, NULL, encoding)) ||
-                                   write_message(p, strlen(p), path, 0)) {
-                                       repo_unuse_commit_buffer(r, commit, p);
-                                       return error(_("could not write file: "
+                               if (parse_head(r, &commit))
+                                       return error(_("could not parse HEAD"));
+
+                               p = repo_logmsg_reencode(r, commit, NULL, encoding);
+                               if (!p)  {
+                                       res = error(_("could not parse commit %s"),
+                                                   oid_to_hex(&commit->object.oid));
+                                       goto unuse_commit_buffer;
+                               }
+                               find_commit_subject(p, &msg);
+                               if (write_message(msg, strlen(msg), path, 0)) {
+                                       res = error(_("could not write file: "
                                                       "'%s'"), path);
+                                       goto unuse_commit_buffer;
                                }
-                               repo_unuse_commit_buffer(r,
-                                                        commit, p);
+                       unuse_commit_buffer:
+                               repo_unuse_commit_buffer(r, commit, p);
+                               if (res)
+                                       return res;
                        }
                }
 
@@ -5339,6 +5377,7 @@ struct label_state {
        struct oidmap commit2label;
        struct hashmap labels;
        struct strbuf buf;
+       int max_label_length;
 };
 
 static const char *label_oid(struct object_id *oid, const char *label,
@@ -5395,6 +5434,8 @@ static const char *label_oid(struct object_id *oid, const char *label,
                }
        } else {
                struct strbuf *buf = &state->buf;
+               int label_is_utf8 = 1; /* start with this assumption */
+               size_t max_len = buf->len + state->max_label_length;
 
                /*
                 * Sanitize labels by replacing non-alpha-numeric characters
@@ -5403,14 +5444,34 @@ static const char *label_oid(struct object_id *oid, const char *label,
                 *
                 * Note that we retain non-ASCII UTF-8 characters (identified
                 * via the most significant bit). They should be all acceptable
-                * in file names. We do not validate the UTF-8 here, that's not
-                * the job of this function.
+                * in file names.
+                *
+                * As we will use the labels as names of (loose) refs, it is
+                * vital that the name not be longer than the maximum component
+                * size of the file system (`NAME_MAX`). We are careful to
+                * truncate the label accordingly, allowing for the `.lock`
+                * suffix and for the label to be UTF-8 encoded (i.e. we avoid
+                * truncating in the middle of a character).
                 */
-               for (; *label; label++)
-                       if ((*label & 0x80) || isalnum(*label))
+               for (; *label && buf->len + 1 < max_len; label++)
+                       if (isalnum(*label) ||
+                           (!label_is_utf8 && (*label & 0x80)))
                                strbuf_addch(buf, *label);
+                       else if (*label & 0x80) {
+                               const char *p = label;
+
+                               utf8_width(&p, NULL);
+                               if (p) {
+                                       if (buf->len + (p - label) > max_len)
+                                               break;
+                                       strbuf_add(buf, label, p - label);
+                                       label = p - 1;
+                               } else {
+                                       label_is_utf8 = 0;
+                                       strbuf_addch(buf, *label);
+                               }
                        /* avoid leading dash and double-dashes */
-                       else if (buf->len && buf->buf[buf->len - 1] != '-')
+                       else if (buf->len && buf->buf[buf->len - 1] != '-')
                                strbuf_addch(buf, '-');
                if (!buf->len) {
                        strbuf_addstr(buf, "rev-");
@@ -5472,7 +5533,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
        struct string_entry *entry;
        struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
                shown = OIDSET_INIT;
-       struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
+       struct label_state state =
+               { OIDMAP_INIT, { NULL }, STRBUF_INIT, GIT_MAX_LABEL_LENGTH };
 
        int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
        const char *cmd_pick = abbr ? "p" : "pick",
@@ -5480,6 +5542,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                *cmd_reset = abbr ? "t" : "reset",
                *cmd_merge = abbr ? "m" : "merge";
 
+       git_config_get_int("rebase.maxlabellength", &state.max_label_length);
+
        oidmap_init(&commit2todo, 0);
        oidmap_init(&state.commit2label, 0);
        hashmap_init(&state.labels, labels_cmp, NULL, 0);
@@ -6159,7 +6223,8 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
        todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0);
        strbuf_swap(&new_todo.buf, &buf2);
        strbuf_release(&buf2);
-       new_todo.total_nr -= new_todo.nr;
+       /* Nothing is done yet, and we're reparsing, so let's reset the count */
+       new_todo.total_nr = 0;
        if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0)
                BUG("invalid todo list after expanding IDs:\n%s",
                    new_todo.buf.buf);
@@ -6180,7 +6245,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
        if (checkout_onto(r, opts, onto_name, &oid, orig_head))
                goto cleanup;
 
-       if (require_clean_work_tree(r, "rebase", "", 1, 1))
+       if (require_clean_work_tree(r, "rebase", NULL, 1, 1))
                goto cleanup;
 
        todo_list_write_total_nr(&new_todo);