diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 368 |
1 files changed, 227 insertions, 141 deletions
diff --git a/sequencer.c b/sequencer.c index bceb6abcb6..9021cb8178 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,7 +1,6 @@ -#include "cache.h" +#include "git-compat-util.h" #include "abspath.h" #include "advice.h" -#include "alloc.h" #include "config.h" #include "copy.h" #include "environment.h" @@ -11,7 +10,7 @@ #include "dir.h" #include "object-file.h" #include "object-name.h" -#include "object-store.h" +#include "object-store-ll.h" #include "object.h" #include "pager.h" #include "commit.h" @@ -23,11 +22,14 @@ #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" @@ -46,10 +48,18 @@ #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 "; @@ -138,6 +148,11 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* + * When we stop for the user to resolve conflicts this file contains + * the patch of the commit that is being picked. + */ +static GIT_PATH_FUNC(rebase_path_patch, "rebase-merge/patch") +/* * For the post-rewrite hook, we make a list of rewritten commits and * their new sha1s. The rewritten-pending list keeps the sha1s of * commits that have been processed, but not committed yet, @@ -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) @@ -422,10 +438,9 @@ struct commit_message { const char *message; }; -static const char *short_commit_name(struct commit *commit) +static const char *short_commit_name(struct repository *r, struct commit *commit) { - return repo_find_unique_abbrev(the_repository, &commit->object.oid, - DEFAULT_ABBREV); + return repo_find_unique_abbrev(r, &commit->object.oid, DEFAULT_ABBREV); } static int get_message(struct commit *commit, struct commit_message *out) @@ -435,7 +450,7 @@ static int get_message(struct commit *commit, struct commit_message *out) out->message = repo_logmsg_reencode(the_repository, commit, NULL, get_commit_output_encoding()); - abbrev = short_commit_name(commit); + abbrev = short_commit_name(the_repository, commit); subject_len = find_commit_subject(out->message, &subject); @@ -660,11 +675,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 +1159,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 +1191,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 +1564,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 +1859,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 +1958,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 +2048,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 +2068,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); @@ -2241,6 +2262,8 @@ static int do_pick_commit(struct repository *r, */ if (command == TODO_REVERT) { + const char *orig_subject; + base = commit; base_label = msg.label; next = parent; @@ -2248,6 +2271,15 @@ static int do_pick_commit(struct repository *r, if (opts->commit_use_reference) { strbuf_addstr(&msgbuf, "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***"); + } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) && + /* + * We don't touch pre-existing repeated reverts, because + * theoretically these can be nested arbitrarily deeply, + * thus requiring excessive complexity to deal with. + */ + !starts_with(orig_subject, "Revert \"")) { + strbuf_addstr(&msgbuf, "Reapply \""); + strbuf_addstr(&msgbuf, orig_subject); } else { strbuf_addstr(&msgbuf, "Revert \""); strbuf_addstr(&msgbuf, msg.subject); @@ -2303,7 +2335,7 @@ static int do_pick_commit(struct repository *r, const char *dest = git_path_squash_msg(r); unlink(dest); if (copy_file(dest, rebase_path_squash_msg(), 0666)) { - res = error(_("could not rename '%s' to '%s'"), + res = error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), dest); goto leave; } @@ -2366,7 +2398,7 @@ static int do_pick_commit(struct repository *r, error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), - short_commit_name(commit), msg.subject); + short_commit_name(r, commit), msg.subject); print_advice(r, res == 1, opts); repo_rerere(r, opts->allow_rerere_auto); goto leave; @@ -2633,7 +2665,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return item->commit ? 0 : -1; } -int sequencer_get_last_command(struct repository *r, enum replay_action *action) +int sequencer_get_last_command(struct repository *r UNUSED, enum replay_action *action) { const char *todo_file, *bol; struct strbuf buf = STRBUF_INIT; @@ -2694,7 +2726,7 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf, 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; @@ -2881,7 +2913,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; @@ -2889,26 +2923,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")) @@ -2917,7 +2951,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; @@ -3153,7 +3187,8 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, - short_commit_name(commit), subject_len, subject); + short_commit_name(the_repository, commit), + subject_len, subject); repo_unuse_commit_buffer(the_repository, commit, commit_buffer); } @@ -3382,7 +3417,8 @@ give_advice: return -1; } -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts, + int reschedule) { struct lock_file todo_lock = LOCK_INIT; const char *todo_path = get_todo_path(opts); @@ -3392,7 +3428,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) * rebase -i writes "git-rebase-todo" without the currently executing * command, appending it to "done" instead. */ - if (is_rebase_i(opts)) + if (is_rebase_i(opts) && !reschedule) next++; fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); @@ -3405,7 +3441,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'"), todo_path); - if (is_rebase_i(opts) && next > 0) { + if (is_rebase_i(opts) && !reschedule && next > 0) { const char *done = rebase_path_done(); int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666); int ret = 0; @@ -3486,18 +3522,19 @@ static int make_patch(struct repository *r, struct commit *commit, struct replay_opts *opts) { - struct strbuf buf = STRBUF_INIT; struct rev_info log_tree_opt; const char *subject; char hex[GIT_MAX_HEXSZ + 1]; int res = 0; + if (!is_rebase_i(opts)) + BUG("make_patch should only be called when rebasing"); + oid_to_hex_r(hex, &commit->object.oid); if (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0) return -1; res |= write_rebase_head(&commit->object.oid); - strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); repo_init_revisions(r, &log_tree_opt, NULL); log_tree_opt.abbrev = 0; @@ -3505,28 +3542,26 @@ static int make_patch(struct repository *r, log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; log_tree_opt.disable_stdin = 1; log_tree_opt.no_commit_id = 1; - log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.file = fopen(rebase_path_patch(), "w"); log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; if (!log_tree_opt.diffopt.file) - res |= error_errno(_("could not open '%s'"), buf.buf); + res |= error_errno(_("could not open '%s'"), + rebase_path_patch()); else { res |= log_tree_commit(&log_tree_opt, commit); fclose(log_tree_opt.diffopt.file); } - strbuf_reset(&buf); - strbuf_addf(&buf, "%s/message", get_dir(opts)); - if (!file_exists(buf.buf)) { + if (!file_exists(rebase_path_message())) { const char *encoding = get_commit_output_encoding(); const char *commit_buffer = repo_logmsg_reencode(r, commit, NULL, encoding); find_commit_subject(commit_buffer, &subject); - res |= write_message(subject, strlen(subject), buf.buf, 1); + res |= write_message(subject, strlen(subject), rebase_path_message(), 1); repo_unuse_commit_buffer(r, commit, commit_buffer); } - strbuf_release(&buf); release_revisions(&log_tree_opt); return res; @@ -3574,7 +3609,7 @@ static int error_with_patch(struct repository *r, } else if (exit_code) { if (commit) fprintf_ln(stderr, _("Could not apply %s... %.*s"), - short_commit_name(commit), subject_len, subject); + short_commit_name(r, commit), subject_len, subject); else /* * We don't have the hash of the parent so @@ -4144,6 +4179,7 @@ static int do_merge(struct repository *r, if (ret < 0) { error(_("could not even attempt to merge '%.*s'"), merge_arg_len, arg); + unlink(git_path_merge_msg(r)); goto leave_merge; } /* @@ -4631,6 +4667,68 @@ N_("Could not execute the todo command\n" " git rebase --edit-todo\n" " git rebase --continue\n"); +static int pick_one_commit(struct repository *r, + struct todo_list *todo_list, + struct replay_opts *opts, + int *check_todo, int* reschedule) +{ + int res; + struct todo_item *item = todo_list->items + todo_list->current; + const char *arg = todo_item_get_arg(todo_list, item); + if (is_rebase_i(opts)) + opts->reflog_message = reflog_message( + opts, command_to_string(item->command), NULL); + + res = do_pick_commit(r, item, opts, is_final_fixup(todo_list), + check_todo); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + *reschedule = 1; + return -1; + } + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) { + if (!opts->verbose) + term_clear_line(); + fprintf(stderr, _("Stopped at %s... %.*s\n"), + short_commit_name(r, commit), item->arg_len, arg); + } + return error_with_patch(r, commit, + arg, item->arg_len, opts, res, !res); + } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(r, item->commit, opts, + item->arg_len, arg); + } else if (res && is_rebase_i(opts) && item->commit) { + int to_amend = 0; + struct object_id oid; + + /* + * If we are rewording and have either + * fast-forwarded already, or are about to + * create a new root commit, we want to amend, + * otherwise we do not. + */ + if (item->command == TODO_REWORD && + !repo_get_oid(r, "HEAD", &oid) && + (oideq(&item->commit->object.oid, &oid) || + (opts->have_squash_onto && + oideq(&opts->squash_onto, &oid)))) + to_amend = 1; + + return res | error_with_patch(r, item->commit, + arg, item->arg_len, opts, + res, to_amend); + } + return res; +} + static int pick_commits(struct repository *r, struct todo_list *todo_list, struct replay_opts *opts) @@ -4646,12 +4744,17 @@ static int pick_commits(struct repository *r, if (read_and_refresh_cache(r, opts)) return -1; + unlink(rebase_path_message()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + unlink(rebase_path_patch()); + while (todo_list->current < todo_list->nr) { struct todo_item *item = todo_list->items + todo_list->current; const char *arg = todo_item_get_arg(todo_list, item); int check_todo = 0; - if (save_todo(todo_list, opts)) + if (save_todo(todo_list, opts, reschedule)) return -1; if (is_rebase_i(opts)) { if (item->command != TODO_COMMENT) { @@ -4669,10 +4772,7 @@ static int pick_commits(struct repository *r, todo_list->total_nr, opts->verbose ? "\n" : "\r"); } - unlink(rebase_path_message()); unlink(rebase_path_author_script()); - unlink(rebase_path_stopped_sha()); - unlink(rebase_path_amend()); unlink(git_path_merge_head(r)); unlink(git_path_auto_merge(r)); delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); @@ -4684,66 +4784,10 @@ static int pick_commits(struct repository *r, } } if (item->command <= TODO_SQUASH) { - if (is_rebase_i(opts)) - opts->reflog_message = reflog_message(opts, - command_to_string(item->command), NULL); - - res = do_pick_commit(r, item, opts, - is_final_fixup(todo_list), - &check_todo); - if (is_rebase_i(opts) && res < 0) { - /* Reschedule */ - advise(_(rescheduled_advice), - get_item_line_length(todo_list, - todo_list->current), - get_item_line(todo_list, - todo_list->current)); - todo_list->current--; - if (save_todo(todo_list, opts)) - return -1; - } - if (item->command == TODO_EDIT) { - struct commit *commit = item->commit; - if (!res) { - if (!opts->verbose) - term_clear_line(); - fprintf(stderr, - _("Stopped at %s... %.*s\n"), - short_commit_name(commit), - item->arg_len, arg); - } - return error_with_patch(r, commit, - arg, item->arg_len, opts, res, !res); - } - if (is_rebase_i(opts) && !res) - record_in_rewritten(&item->commit->object.oid, - peek_command(todo_list, 1)); - if (res && is_fixup(item->command)) { - if (res == 1) - intend_to_amend(); - return error_failed_squash(r, item->commit, opts, - item->arg_len, arg); - } else if (res && is_rebase_i(opts) && item->commit) { - int to_amend = 0; - struct object_id oid; - - /* - * If we are rewording and have either - * fast-forwarded already, or are about to - * create a new root commit, we want to amend, - * otherwise we do not. - */ - if (item->command == TODO_REWORD && - !repo_get_oid(r, "HEAD", &oid) && - (oideq(&item->commit->object.oid, &oid) || - (opts->have_squash_onto && - oideq(&opts->squash_onto, &oid)))) - to_amend = 1; - - return res | error_with_patch(r, item->commit, - arg, item->arg_len, opts, - res, to_amend); - } + res = pick_one_commit(r, todo_list, opts, &check_todo, + &reschedule); + if (!res && item->command == TODO_EDIT) + return 0; } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(arg + item->arg_len); int saved = *end_of_arg; @@ -4791,22 +4835,19 @@ static int pick_commits(struct repository *r, get_item_line_length(todo_list, todo_list->current), get_item_line(todo_list, todo_list->current)); - todo_list->current--; - if (save_todo(todo_list, opts)) + if (save_todo(todo_list, opts, reschedule)) return -1; if (item->commit) - return error_with_patch(r, - item->commit, - arg, item->arg_len, - opts, res, 0); + write_rebase_head(&item->commit->object.oid); } else if (is_rebase_i(opts) && check_todo && !res && reread_todo_if_changed(r, todo_list, opts)) { return -1; } - todo_list->current++; if (res) return res; + + todo_list->current++; } if (is_rebase_i(opts)) { @@ -4959,6 +5000,11 @@ static int commit_staged_changes(struct repository *r, is_clean = !has_uncommitted_changes(r, 0); + if (!is_clean && !file_exists(rebase_path_message())) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + + return error(_(staged_changes_advice), gpg_opt, gpg_opt); + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; struct object_id head, to_amend; @@ -5038,19 +5084,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; } } @@ -5330,6 +5388,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, @@ -5386,6 +5445,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 @@ -5394,14 +5455,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-"); @@ -5463,7 +5544,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", @@ -5471,6 +5553,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); @@ -5507,7 +5591,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, if (!is_empty && (commit->object.flags & PATCHSAME)) { if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS) warning(_("skipped previously applied commit %s"), - short_commit_name(commit)); + short_commit_name(the_repository, commit)); skipped_commit = 1; continue; } @@ -5743,7 +5827,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, if (!is_empty && (commit->object.flags & PATCHSAME)) { if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS) warning(_("skipped previously applied commit %s"), - short_commit_name(commit)); + short_commit_name(r, commit)); skipped_commit = 1; continue; } @@ -5835,7 +5919,8 @@ static void todo_list_add_exec_commands(struct todo_list *todo_list, todo_list->alloc = alloc; } -static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, +static void todo_list_to_strbuf(struct repository *r, + struct todo_list *todo_list, struct strbuf *buf, int num, unsigned flags) { struct todo_item *item; @@ -5864,7 +5949,7 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis /* add commit id */ if (item->commit) { const char *oid = flags & TODO_LIST_SHORTEN_IDS ? - short_commit_name(item->commit) : + short_commit_name(r, item->commit) : oid_to_hex(&item->commit->object.oid); if (item->command == TODO_FIXUP) { @@ -6172,7 +6257,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); @@ -6223,7 +6308,7 @@ static int skip_fixupish(const char *subject, const char **p) { int todo_list_rearrange_squash(struct todo_list *todo_list) { struct hashmap subject2item; - int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0; + int rearranged = 0, *next, *tail, i, nr = 0; char **subjects; struct commit_todo_item commit_todo; struct todo_item *items = NULL; @@ -6335,6 +6420,8 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) } if (rearranged) { + ALLOC_ARRAY(items, todo_list->nr); + for (i = 0; i < todo_list->nr; i++) { enum todo_command command = todo_list->items[i].command; int cur = i; @@ -6347,16 +6434,15 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) continue; while (cur >= 0) { - ALLOC_GROW(items, nr + 1, alloc); items[nr++] = todo_list->items[cur]; cur = next[cur]; } } + assert(nr == todo_list->nr); + todo_list->alloc = nr; FREE_AND_NULL(todo_list->items); todo_list->items = items; - todo_list->nr = nr; - todo_list->alloc = alloc; } free(next); |