-#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 ";
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;
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)
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)
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);
}
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++;
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');
}
/*
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);
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;
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? */
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);
_(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);
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);
*/
if (command == TODO_REVERT) {
+ const char *orig_subject;
+
base = commit;
base_label = msg.label;
next = parent;
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);
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;
}
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;
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++;
}
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;
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');
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;
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;
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"))
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;
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);
}
} 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
" 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"
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;
* 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;
if (!opts->verbose)
term_clear_line();
fprintf(stderr, _("Stopped at %s... %.*s\n"),
- short_commit_name(commit), item->arg_len, arg);
+ short_commit_name(r, commit), item->arg_len, arg);
}
return error_with_patch(r, commit,
arg, item->arg_len, opts, res, !res);
* 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;
}
}
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,
}
} 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
*
* 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-");
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",
*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);
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;
}
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;
}
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;
/* 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) {
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);
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);
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;
}
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;
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);