summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c666
1 files changed, 459 insertions, 207 deletions
diff --git a/sequencer.c b/sequencer.c
index 1d206fd224..8a51c9042b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -131,7 +131,7 @@ static GIT_PATH_FUNC(rebase_path_rewritten_pending,
"rebase-merge/rewritten-pending")
/*
- * The path of the file containig the OID of the "squash onto" commit, i.e.
+ * The path of the file containing the OID of the "squash onto" commit, i.e.
* the dummy commit used for `reset [new root]`.
*/
static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
@@ -147,6 +147,8 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
* command-line.
*/
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
+static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
+static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date")
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
@@ -586,7 +588,7 @@ static int do_recursive_merge(struct repository *r,
struct replay_opts *opts)
{
struct merge_options o;
- struct tree *result, *next_tree, *base_tree, *head_tree;
+ struct tree *next_tree, *base_tree, *head_tree;
int clean;
char **xopt;
struct lock_file index_lock = LOCK_INIT;
@@ -613,11 +615,10 @@ static int do_recursive_merge(struct repository *r,
clean = merge_trees(&o,
head_tree,
- next_tree, base_tree, &result);
+ next_tree, base_tree);
if (is_rebase_i(opts) && clean <= 0)
fputs(o.obuf.buf, stdout);
strbuf_release(&o.obuf);
- diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
if (clean < 0) {
rollback_lock_file(&index_lock);
return clean;
@@ -770,7 +771,7 @@ static int parse_key_value_squoted(char *buf, struct string_list *list)
* GIT_AUTHOR_DATE='$author_date'
*
* where $author_name, $author_email and $author_date are quoted. We are strict
- * with our parsing, as the file was meant to be eval'd in the old
+ * with our parsing, as the file was meant to be eval'd in the now-removed
* git-am.sh/git-rebase--interactive.sh scripts, and thus if the file differs
* from what this function expects, it is better to bail out than to do
* something that the user does not expect.
@@ -824,9 +825,19 @@ int read_author_script(const char *path, char **name, char **email, char **date,
error(_("missing 'GIT_AUTHOR_DATE'"));
if (date_i < 0 || email_i < 0 || date_i < 0 || err)
goto finish;
- *name = kv.items[name_i].util;
- *email = kv.items[email_i].util;
- *date = kv.items[date_i].util;
+
+ if (name)
+ *name = kv.items[name_i].util;
+ else
+ free(kv.items[name_i].util);
+ if (email)
+ *email = kv.items[email_i].util;
+ else
+ free(kv.items[email_i].util);
+ if (date)
+ *date = kv.items[date_i].util;
+ else
+ free(kv.items[date_i].util);
retval = 0;
finish:
string_list_clear(&kv, !!retval);
@@ -869,32 +880,45 @@ static char *get_author(const char *message)
return NULL;
}
-/* Read author-script and return an ident line (author <email> timestamp) */
-static const char *read_author_ident(struct strbuf *buf)
+/* Returns a "date" string that needs to be free()'d by the caller */
+static char *read_author_date_or_null(void)
{
- struct strbuf out = STRBUF_INIT;
- char *name, *email, *date;
+ char *date;
if (read_author_script(rebase_path_author_script(),
- &name, &email, &date, 0))
+ NULL, NULL, &date, 0))
return NULL;
+ return date;
+}
+
+/* Construct a free()able author string with current time as the author date */
+static char *ignore_author_date(const char *author)
+{
+ int len = strlen(author);
+ struct ident_split ident;
+ struct strbuf new_author = STRBUF_INIT;
- /* validate date since fmt_ident() will die() on bad value */
- if (parse_date(date, &out)){
- warning(_("invalid date format '%s' in '%s'"),
- date, rebase_path_author_script());
- strbuf_release(&out);
+ if (split_ident_line(&ident, author, len) < 0) {
+ error(_("malformed ident line"));
return NULL;
}
+ len = ident.mail_end - ident.name_begin + 1;
- strbuf_reset(&out);
- strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0));
- strbuf_swap(buf, &out);
- strbuf_release(&out);
- free(name);
- free(email);
- free(date);
- return buf->buf;
+ strbuf_addf(&new_author, "%.*s ", len, ident.name_begin);
+ datestamp(&new_author);
+ return strbuf_detach(&new_author, NULL);
+}
+
+static void push_dates(struct child_process *child, int change_committer_date)
+{
+ time_t now = time(NULL);
+ struct strbuf date = STRBUF_INIT;
+
+ strbuf_addf(&date, "@%"PRIuMAX, (uintmax_t)now);
+ argv_array_pushf(&child->env_array, "GIT_AUTHOR_DATE=%s", date.buf);
+ if (change_committer_date)
+ argv_array_pushf(&child->env_array, "GIT_COMMITTER_DATE=%s", date.buf);
+ strbuf_release(&date);
}
static const char staged_changes_advice[] =
@@ -954,49 +978,27 @@ static int run_git_commit(struct repository *r,
{
struct child_process cmd = CHILD_PROCESS_INIT;
- if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
- struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
- const char *author = NULL;
- struct object_id root_commit, *cache_tree_oid;
- int res = 0;
-
- if (is_rebase_i(opts)) {
- author = read_author_ident(&script);
- if (!author) {
- strbuf_release(&script);
- return -1;
- }
- }
+ cmd.git_cmd = 1;
- if (!defmsg)
- BUG("root commit without message");
+ if (opts->committer_date_is_author_date) {
+ int res = -1;
+ struct strbuf datebuf = STRBUF_INIT;
+ char *date = read_author_date_or_null();
- if (!(cache_tree_oid = get_cache_tree_oid(r->index)))
- res = -1;
+ if (!date)
+ return -1;
- if (!res)
- res = strbuf_read_file(&msg, defmsg, 0);
+ strbuf_addf(&datebuf, "@%s", date);
+ res = setenv("GIT_COMMITTER_DATE",
+ opts->ignore_date ? "" : datebuf.buf, 1);
- if (res <= 0)
- res = error_errno(_("could not read '%s'"), defmsg);
- else
- res = commit_tree(msg.buf, msg.len, cache_tree_oid,
- NULL, &root_commit, author,
- opts->gpg_sign);
+ strbuf_release(&datebuf);
+ free(date);
- strbuf_release(&msg);
- strbuf_release(&script);
- if (!res) {
- update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
- res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
- UPDATE_REFS_MSG_ON_ERR);
- }
- return res < 0 ? error(_("writing root commit")) : 0;
+ if (res)
+ return -1;
}
- cmd.git_cmd = 1;
-
if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
@@ -1012,6 +1014,8 @@ static int run_git_commit(struct repository *r,
argv_array_push(&cmd.args, "--amend");
if (opts->gpg_sign)
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+ if (opts->ignore_date)
+ push_dates(&cmd, opts->committer_date_is_author_date);
if (defmsg)
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
else if (!(flags & EDIT_MSG))
@@ -1196,25 +1200,22 @@ static int run_prepare_commit_msg_hook(struct repository *r,
struct strbuf *msg,
const char *commit)
{
- struct argv_array hook_env = ARGV_ARRAY_INIT;
- int ret;
- const char *name;
+ int ret = 0;
+ const char *name, *arg1 = NULL, *arg2 = NULL;
name = git_path_commit_editmsg();
if (write_message(msg->buf, msg->len, name, 0))
return -1;
- argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", r->index_file);
- argv_array_push(&hook_env, "GIT_EDITOR=:");
- if (commit)
- ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
- "commit", commit, NULL);
- else
- ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
- "message", NULL);
- if (ret)
+ if (commit) {
+ arg1 = "commit";
+ arg2 = commit;
+ } else {
+ arg1 = "message";
+ }
+ if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name,
+ arg1, arg2, NULL))
ret = error(_("'prepare-commit-msg' hook failed"));
- argv_array_clear(&hook_env);
return ret;
}
@@ -1378,19 +1379,18 @@ static int try_to_commit(struct repository *r,
struct object_id *oid)
{
struct object_id tree;
- struct commit *current_head;
+ struct commit *current_head = NULL;
struct commit_list *parents = NULL;
struct commit_extra_header *extra = NULL;
struct strbuf err = STRBUF_INIT;
struct strbuf commit_msg = STRBUF_INIT;
- char *amend_author = NULL;
+ char *author_to_free = NULL;
const char *hook_commit = NULL;
enum commit_msg_cleanup_mode cleanup;
int res = 0;
if (parse_head(r, &current_head))
return -1;
-
if (flags & AMEND_MSG) {
const char *exclude_gpgsig[] = { "gpgsig", NULL };
const char *out_enc = get_commit_output_encoding();
@@ -1405,7 +1405,7 @@ static int try_to_commit(struct repository *r,
strbuf_addstr(msg, orig_message);
hook_commit = "HEAD";
}
- author = amend_author = get_author(message);
+ author = author_to_free = get_author(message);
unuse_commit_buffer(current_head, message);
if (!author) {
res = error(_("unable to parse commit author"));
@@ -1413,20 +1413,62 @@ static int try_to_commit(struct repository *r,
}
parents = copy_commit_list(current_head->parents);
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
- } else if (current_head) {
+ } else if (current_head &&
+ (!(flags & CREATE_ROOT_COMMIT) || (flags & AMEND_MSG))) {
commit_list_insert(current_head, &parents);
}
+ if (opts->committer_date_is_author_date) {
+ int len = strlen(author);
+ struct ident_split ident;
+ struct strbuf date = STRBUF_INIT;
+
+ if (split_ident_line(&ident, author, len) < 0) {
+ res = error(_("malformed ident line"));
+ goto out;
+ }
+ if (!ident.date_begin) {
+ res = error(_("corrupted author without date information"));
+ goto out;
+ }
+
+ strbuf_addf(&date, "@%.*s %.*s",
+ (int)(ident.date_end - ident.date_begin), ident.date_begin,
+ (int)(ident.tz_end - ident.tz_begin), ident.tz_begin);
+ res = setenv("GIT_COMMITTER_DATE",
+ opts->ignore_date ? "" : date.buf, 1);
+ strbuf_release(&date);
+
+ if (res)
+ goto out;
+ }
+
if (write_index_as_tree(&tree, r->index, r->index_file, 0, NULL)) {
res = error(_("git write-tree failed to write a tree"));
goto out;
}
- if (!(flags & ALLOW_EMPTY) && oideq(current_head ?
- get_commit_tree_oid(current_head) :
- the_hash_algo->empty_tree, &tree)) {
- res = 1; /* run 'git commit' to display error message */
- goto out;
+ if (!(flags & ALLOW_EMPTY)) {
+ struct commit *first_parent = current_head;
+
+ if (flags & AMEND_MSG) {
+ if (current_head->parents) {
+ first_parent = current_head->parents->item;
+ if (repo_parse_commit(r, first_parent)) {
+ res = error(_("could not parse HEAD commit"));
+ goto out;
+ }
+ } else {
+ first_parent = NULL;
+ }
+ }
+ if (oideq(first_parent
+ ? get_commit_tree_oid(first_parent)
+ : the_hash_algo->empty_tree,
+ &tree)) {
+ res = 1; /* run 'git commit' to display error message */
+ goto out;
+ }
}
if (find_hook("prepare-commit-msg")) {
@@ -1460,6 +1502,15 @@ static int try_to_commit(struct repository *r,
reset_ident_date();
+ if (opts->ignore_date) {
+ author = ignore_author_date(author);
+ if (!author) {
+ res = -1;
+ goto out;
+ }
+ free(author_to_free);
+ author_to_free = (char *)author;
+ }
if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
oid, author, opts->gpg_sign, extra)) {
res = error(_("failed to write commit object"));
@@ -1472,6 +1523,7 @@ static int try_to_commit(struct repository *r,
goto out;
}
+ run_commit_hook(0, r->index_file, "post-commit", NULL);
if (flags & AMEND_MSG)
commit_post_rewrite(r, current_head, oid);
@@ -1479,7 +1531,7 @@ out:
free_commit_extra_headers(extra);
strbuf_release(&err);
strbuf_release(&commit_msg);
- free(amend_author);
+ free(author_to_free);
return res;
}
@@ -1490,8 +1542,7 @@ static int do_commit(struct repository *r,
{
int res = 1;
- if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
- !(flags & CREATE_ROOT_COMMIT)) {
+ if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
struct object_id oid;
struct strbuf sb = STRBUF_INIT;
@@ -1646,6 +1697,7 @@ static int update_squash_messages(struct repository *r,
struct strbuf buf = STRBUF_INIT;
int res;
const char *message, *body;
+ const char *encoding = get_commit_output_encoding();
if (opts->current_fixup_count > 0) {
struct strbuf header = STRBUF_INIT;
@@ -1672,7 +1724,7 @@ static int update_squash_messages(struct repository *r,
return error(_("need a HEAD to fixup"));
if (!(head_commit = lookup_commit_reference(r, &head)))
return error(_("could not read HEAD"));
- if (!(head_message = get_commit_buffer(head_commit, NULL)))
+ if (!(head_message = logmsg_reencode(head_commit, NULL, encoding)))
return error(_("could not read HEAD's commit message"));
find_commit_subject(head_message, &body);
@@ -1693,7 +1745,7 @@ static int update_squash_messages(struct repository *r,
unuse_commit_buffer(head_commit, head_message);
}
- if (!(message = get_commit_buffer(commit, NULL)))
+ if (!(message = logmsg_reencode(commit, NULL, encoding)))
return error(_("could not read commit message of %s"),
oid_to_hex(&commit->object.oid));
find_commit_subject(message, &body);
@@ -1775,7 +1827,7 @@ static int do_pick_commit(struct repository *r,
enum todo_command command,
struct commit *commit,
struct replay_opts *opts,
- int final_fixup)
+ int final_fixup, int *check_todo)
{
unsigned int flags = opts->edit ? EDIT_MSG : 0;
const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r);
@@ -1785,7 +1837,7 @@ static int do_pick_commit(struct repository *r,
char *author = NULL;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
- int res, unborn = 0, allow;
+ int res, unborn = 0, reword = 0, allow;
if (opts->no_commit) {
/*
@@ -1855,7 +1907,7 @@ static int do_pick_commit(struct repository *r,
opts);
if (res || command != TODO_REWORD)
goto leave;
- flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG;
+ reword = 1;
msg_file = NULL;
goto fast_forward_edit;
}
@@ -1913,7 +1965,7 @@ static int do_pick_commit(struct repository *r,
}
if (command == TODO_REWORD)
- flags |= EDIT_MSG | VERIFY_MSG;
+ reword = 1;
else if (is_fixup(command)) {
if (update_squash_messages(r, command, commit, opts))
return -1;
@@ -1997,13 +2049,21 @@ static int do_pick_commit(struct repository *r,
} else if (allow)
flags |= ALLOW_EMPTY;
if (!opts->no_commit) {
-fast_forward_edit:
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
res = do_commit(r, msg_file, author, opts, flags);
else
res = error(_("unable to parse commit author"));
+ *check_todo = !!(flags & EDIT_MSG);
+ if (!res && reword) {
+fast_forward_edit:
+ res = run_git_commit(r, NULL, opts, EDIT_MSG |
+ VERIFY_MSG | AMEND_MSG |
+ (flags & ALLOW_EMPTY));
+ *check_todo = 1;
+ }
}
+
if (!res && final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
@@ -2079,6 +2139,18 @@ const char *todo_item_get_arg(struct todo_list *todo_list,
return todo_list->buf.buf + item->arg_offset;
}
+static int is_command(enum todo_command command, const char **bol)
+{
+ const char *str = todo_command_info[command].str;
+ const char nick = todo_command_info[command].c;
+ const char *p = *bol + 1;
+
+ return skip_prefix(*bol, str, bol) ||
+ ((nick && **bol == nick) &&
+ (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
+ (*bol = p));
+}
+
static int parse_insn_line(struct repository *r, struct todo_item *item,
const char *buf, const char *bol, char *eol)
{
@@ -2100,12 +2172,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
}
for (i = 0; i < TODO_COMMENT; i++)
- if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
- item->command = i;
- break;
- } else if ((bol + 1 == eol || bol[1] == ' ') &&
- *bol == todo_command_info[i].c) {
- bol++;
+ if (is_command(i, &bol)) {
item->command = i;
break;
}
@@ -2173,34 +2240,26 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
int sequencer_get_last_command(struct repository *r, enum replay_action *action)
{
- struct todo_item item;
- char *eol;
- const char *todo_file;
+ const char *todo_file, *bol;
struct strbuf buf = STRBUF_INIT;
- int ret = -1;
+ int ret = 0;
todo_file = git_path_todo_file();
if (strbuf_read_file(&buf, todo_file, 0) < 0) {
- if (errno == ENOENT)
+ if (errno == ENOENT || errno == ENOTDIR)
return -1;
else
return error_errno("unable to open '%s'", todo_file);
}
- eol = strchrnul(buf.buf, '\n');
- if (buf.buf != eol && eol[-1] == '\r')
- eol--; /* strip Carriage Return */
- if (parse_insn_line(r, &item, buf.buf, buf.buf, eol))
- goto fail;
- if (item.command == TODO_PICK)
+ bol = buf.buf + strspn(buf.buf, " \t\r\n");
+ if (is_command(TODO_PICK, &bol) && (*bol == ' ' || *bol == '\t'))
*action = REPLAY_PICK;
- else if (item.command == TODO_REVERT)
+ else if (is_command(TODO_REVERT, &bol) &&
+ (*bol == ' ' || *bol == '\t'))
*action = REPLAY_REVERT;
else
- goto fail;
-
- ret = 0;
+ ret = -1;
- fail:
strbuf_release(&buf);
return ret;
@@ -2314,19 +2373,21 @@ static int have_finished_the_last_pick(void)
return ret;
}
-void sequencer_post_commit_cleanup(struct repository *r)
+void sequencer_post_commit_cleanup(struct repository *r, int verbose)
{
struct replay_opts opts = REPLAY_OPTS_INIT;
int need_cleanup = 0;
if (file_exists(git_path_cherry_pick_head(r))) {
- unlink(git_path_cherry_pick_head(r));
+ if (!unlink(git_path_cherry_pick_head(r)) && verbose)
+ warning(_("cancelling a cherry picking in progress"));
opts.action = REPLAY_PICK;
need_cleanup = 1;
}
if (file_exists(git_path_revert_head(r))) {
- unlink(git_path_revert_head(r));
+ if (!unlink(git_path_revert_head(r)) && verbose)
+ warning(_("cancelling a revert in progress"));
opts.action = REPLAY_REVERT;
need_cleanup = 1;
}
@@ -2531,6 +2592,16 @@ static int read_populate_opts(struct replay_opts *opts)
opts->signoff = 1;
}
+ if (file_exists(rebase_path_cdate_is_adate())) {
+ opts->allow_ff = 0;
+ opts->committer_date_is_author_date = 1;
+ }
+
+ if (file_exists(rebase_path_ignore_date())) {
+ opts->allow_ff = 0;
+ opts->ignore_date = 1;
+ }
+
if (file_exists(rebase_path_reschedule_failed_exec()))
opts->reschedule_failed_exec = 1;
@@ -2613,6 +2684,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
if (opts->signoff)
write_file(rebase_path_signoff(), "--signoff\n");
+ if (opts->committer_date_is_author_date)
+ write_file(rebase_path_cdate_is_adate(), "%s", "");
+ if (opts->ignore_date)
+ write_file(rebase_path_ignore_date(), "%s", "");
if (opts->reschedule_failed_exec)
write_file(rebase_path_reschedule_failed_exec(), "%s", "");
@@ -2625,14 +2700,17 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
enum todo_command command = opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT;
const char *command_string = todo_command_info[command].str;
+ const char *encoding;
struct commit *commit;
if (prepare_revs(opts))
return -1;
+ encoding = get_log_output_encoding();
+
while ((commit = get_revision(opts->revs))) {
struct todo_item *item = append_new_todo(todo_list);
- const char *commit_buffer = get_commit_buffer(commit, NULL);
+ const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
const char *subject;
int subject_len;
@@ -2653,15 +2731,41 @@ static int walk_revs_populate_todo(struct todo_list *todo_list,
return 0;
}
-static int create_seq_dir(void)
+static int create_seq_dir(struct repository *r)
{
- if (file_exists(git_path_seq_dir())) {
- error(_("a cherry-pick or revert is already in progress"));
- advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
+ enum replay_action action;
+ const char *in_progress_error = NULL;
+ const char *in_progress_advice = NULL;
+ unsigned int advise_skip = file_exists(git_path_revert_head(r)) ||
+ file_exists(git_path_cherry_pick_head(r));
+
+ if (!sequencer_get_last_command(r, &action)) {
+ switch (action) {
+ case REPLAY_REVERT:
+ in_progress_error = _("revert is already in progress");
+ in_progress_advice =
+ _("try \"git revert (--continue | %s--abort | --quit)\"");
+ break;
+ case REPLAY_PICK:
+ in_progress_error = _("cherry-pick is already in progress");
+ in_progress_advice =
+ _("try \"git cherry-pick (--continue | %s--abort | --quit)\"");
+ break;
+ default:
+ BUG("unexpected action in create_seq_dir");
+ }
+ }
+ if (in_progress_error) {
+ error("%s", in_progress_error);
+ if (advice_sequencer_in_use)
+ advise(in_progress_advice,
+ advise_skip ? "--skip | " : "");
return -1;
- } else if (mkdir(git_path_seq_dir(), 0777) < 0)
+ }
+ if (mkdir(git_path_seq_dir(), 0777) < 0)
return error_errno(_("could not create sequencer directory '%s'"),
git_path_seq_dir());
+
return 0;
}
@@ -2712,15 +2816,20 @@ static int rollback_is_safe(void)
return oideq(&actual_head, &expected_head);
}
-static int reset_for_rollback(const struct object_id *oid)
+static int reset_merge(const struct object_id *oid)
{
- const char *argv[4]; /* reset --merge <arg> + NULL */
+ int ret;
+ struct argv_array argv = ARGV_ARRAY_INIT;
- argv[0] = "reset";
- argv[1] = "--merge";
- argv[2] = oid_to_hex(oid);
- argv[3] = NULL;
- return run_command_v_opt(argv, RUN_GIT_CMD);
+ argv_array_pushl(&argv, "reset", "--merge", NULL);
+
+ if (!is_null_oid(oid))
+ argv_array_push(&argv, oid_to_hex(oid));
+
+ ret = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+ argv_array_clear(&argv);
+
+ return ret;
}
static int rollback_single_pick(struct repository *r)
@@ -2734,7 +2843,16 @@ static int rollback_single_pick(struct repository *r)
return error(_("cannot resolve HEAD"));
if (is_null_oid(&head_oid))
return error(_("cannot abort from a branch yet to be born"));
- return reset_for_rollback(&head_oid);
+ return reset_merge(&head_oid);
+}
+
+static int skip_single_pick(void)
+{
+ struct object_id head;
+
+ if (read_ref_full("HEAD", 0, &head, NULL))
+ return error(_("cannot resolve HEAD"));
+ return reset_merge(&head);
}
int sequencer_rollback(struct repository *r, struct replay_opts *opts)
@@ -2777,7 +2895,7 @@ int sequencer_rollback(struct repository *r, struct replay_opts *opts)
warning(_("You seem to have moved HEAD. "
"Not rewinding, check your HEAD!"));
} else
- if (reset_for_rollback(&oid))
+ if (reset_merge(&oid))
goto fail;
strbuf_release(&buf);
return sequencer_remove_state(opts);
@@ -2786,6 +2904,70 @@ fail:
return -1;
}
+int sequencer_skip(struct repository *r, struct replay_opts *opts)
+{
+ enum replay_action action = -1;
+ sequencer_get_last_command(r, &action);
+
+ /*
+ * Check whether the subcommand requested to skip the commit is actually
+ * in progress and that it's safe to skip the commit.
+ *
+ * opts->action tells us which subcommand requested to skip the commit.
+ * If the corresponding .git/<ACTION>_HEAD exists, we know that the
+ * action is in progress and we can skip the commit.
+ *
+ * Otherwise we check that the last instruction was related to the
+ * particular subcommand we're trying to execute and barf if that's not
+ * the case.
+ *
+ * Finally we check that the rollback is "safe", i.e., has the HEAD
+ * moved? In this case, it doesn't make sense to "reset the merge" and
+ * "skip the commit" as the user already handled this by committing. But
+ * we'd not want to barf here, instead give advice on how to proceed. We
+ * only need to check that when .git/<ACTION>_HEAD doesn't exist because
+ * it gets removed when the user commits, so if it still exists we're
+ * sure the user can't have committed before.
+ */
+ switch (opts->action) {
+ case REPLAY_REVERT:
+ if (!file_exists(git_path_revert_head(r))) {
+ if (action != REPLAY_REVERT)
+ return error(_("no revert in progress"));
+ if (!rollback_is_safe())
+ goto give_advice;
+ }
+ break;
+ case REPLAY_PICK:
+ if (!file_exists(git_path_cherry_pick_head(r))) {
+ if (action != REPLAY_PICK)
+ return error(_("no cherry-pick in progress"));
+ if (!rollback_is_safe())
+ goto give_advice;
+ }
+ break;
+ default:
+ BUG("unexpected action in sequencer_skip");
+ }
+
+ if (skip_single_pick())
+ return error(_("failed to skip the commit"));
+ if (!is_directory(git_path_seq_dir()))
+ return 0;
+
+ return sequencer_continue(r, opts);
+
+give_advice:
+ error(_("there is nothing to skip"));
+
+ if (advice_resolve_conflict) {
+ advise(_("have you committed already?\n"
+ "try \"git %s --continue\""),
+ action == REPLAY_REVERT ? "revert" : "cherry-pick");
+ }
+ return -1;
+}
+
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
{
struct lock_file todo_lock = LOCK_INIT;
@@ -2925,7 +3107,8 @@ static int make_patch(struct repository *r,
strbuf_addf(&buf, "%s/message", get_dir(opts));
if (!file_exists(buf.buf)) {
- const char *commit_buffer = get_commit_buffer(commit, NULL);
+ const char *encoding = get_commit_output_encoding();
+ const char *commit_buffer = logmsg_reencode(commit, NULL, encoding);
find_commit_subject(commit_buffer, &subject);
res |= write_message(subject, strlen(subject), buf.buf, 1);
unuse_commit_buffer(commit, commit_buffer);
@@ -3197,7 +3380,7 @@ static int do_reset(struct repository *r,
return error_resolve_conflict(_(action_name(opts)));
}
- if (!fill_tree_descriptor(&desc, &oid)) {
+ if (!fill_tree_descriptor(r, &desc, &oid)) {
error(_("failed to find tree of %s"), oid_to_hex(&oid));
rollback_lock_file(&lock);
free((void *)desc.buffer);
@@ -3259,6 +3442,9 @@ static int do_merge(struct repository *r,
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
struct commit_list *to_merge = NULL, **tail = &to_merge;
+ const char *strategy = !opts->xopts_nr &&
+ (!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
+ NULL : opts->strategy;
struct merge_options o;
int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
static struct lock_file lock;
@@ -3324,7 +3510,8 @@ static int do_merge(struct repository *r,
}
if (commit) {
- const char *message = get_commit_buffer(commit, NULL);
+ const char *encoding = get_commit_output_encoding();
+ const char *message = logmsg_reencode(commit, NULL, encoding);
const char *body;
int len;
@@ -3404,10 +3591,14 @@ static int do_merge(struct repository *r,
rollback_lock_file(&lock);
ret = fast_forward_to(r, &commit->object.oid,
&head_commit->object.oid, 0, opts);
+ if (flags & TODO_EDIT_MERGE_MSG) {
+ run_commit_flags |= AMEND_MSG;
+ goto fast_forward_edit;
+ }
goto leave_merge;
}
- if (to_merge->next) {
+ if (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -3421,7 +3612,14 @@ static int do_merge(struct repository *r,
cmd.git_cmd = 1;
argv_array_push(&cmd.args, "merge");
argv_array_push(&cmd.args, "-s");
- argv_array_push(&cmd.args, "octopus");
+ if (!strategy)
+ argv_array_push(&cmd.args, "octopus");
+ else {
+ argv_array_push(&cmd.args, strategy);
+ for (k = 0; k < opts->xopts_nr; k++)
+ argv_array_pushf(&cmd.args,
+ "-X%s", opts->xopts[k]);
+ }
argv_array_push(&cmd.args, "--no-edit");
argv_array_push(&cmd.args, "--no-ff");
argv_array_push(&cmd.args, "--no-log");
@@ -3430,6 +3628,8 @@ static int do_merge(struct repository *r,
argv_array_push(&cmd.args, git_path_merge_msg(r));
if (opts->gpg_sign)
argv_array_push(&cmd.args, opts->gpg_sign);
+ if (opts->ignore_date)
+ push_dates(&cmd, opts->committer_date_is_author_date);
/* Add the tips to be merged */
for (j = to_merge; j; j = j->next)
@@ -3459,7 +3659,7 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
- write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
+ write_message(oid_to_hex(&merge_commit->object.oid), the_hash_algo->hexsz,
git_path_merge_head(r), 0);
write_message("no-ff", 5, git_path_merge_mode(r), 0);
@@ -3507,6 +3707,7 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
+ fast_forward_edit:
ret = !!run_git_commit(r, git_path_merge_msg(r), opts,
run_commit_flags);
@@ -3701,13 +3902,16 @@ static int pick_commits(struct repository *r,
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
- opts->record_origin || opts->edit));
+ opts->record_origin || opts->edit ||
+ opts->committer_date_is_author_date ||
+ opts->ignore_date));
if (read_and_refresh_cache(r, opts))
return -1;
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))
return -1;
@@ -3731,7 +3935,7 @@ static int pick_commits(struct repository *r,
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
- unlink(git_path_merge_head(the_repository));
+ unlink(git_path_merge_head(r));
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK) {
@@ -3746,7 +3950,8 @@ static int pick_commits(struct repository *r,
command_to_string(item->command), NULL),
1);
res = do_pick_commit(r, item->command, item->commit,
- opts, is_final_fixup(todo_list));
+ opts, is_final_fixup(todo_list),
+ &check_todo);
if (is_rebase_i(opts) && res < 0) {
/* Reschedule */
advise(_(rescheduled_advice),
@@ -3803,7 +4008,6 @@ static int pick_commits(struct repository *r,
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(arg + item->arg_len);
int saved = *end_of_arg;
- struct stat st;
if (!opts->verbose)
term_clear_line();
@@ -3814,17 +4018,8 @@ static int pick_commits(struct repository *r,
if (res) {
if (opts->reschedule_failed_exec)
reschedule = 1;
- } else if (stat(get_todo_path(opts), &st))
- res = error_errno(_("could not stat '%s'"),
- get_todo_path(opts));
- else if (match_stat_data(&todo_list->stat, &st)) {
- /* Reread the todo file if it has changed. */
- todo_list_release(todo_list);
- if (read_populate_todo(r, todo_list, opts))
- res = -1; /* message was printed */
- /* `current` will be incremented below */
- todo_list->current = -1;
}
+ check_todo = 1;
} else if (item->command == TODO_LABEL) {
if ((res = do_label(r, arg, item->arg_len)))
reschedule = 1;
@@ -3860,6 +4055,20 @@ static int pick_commits(struct repository *r,
item->commit,
arg, item->arg_len,
opts, res, 0);
+ } else if (is_rebase_i(opts) && check_todo && !res) {
+ struct stat st;
+
+ if (stat(get_todo_path(opts), &st)) {
+ res = error_errno(_("could not stat '%s'"),
+ get_todo_path(opts));
+ } else if (match_stat_data(&todo_list->stat, &st)) {
+ /* Reread the todo file if it has changed. */
+ todo_list_release(todo_list);
+ if (read_populate_todo(r, todo_list, opts))
+ res = -1; /* message was printed */
+ /* `current` will be incremented below */
+ todo_list->current = -1;
+ }
}
todo_list->current++;
@@ -4087,9 +4296,10 @@ static int commit_staged_changes(struct repository *r,
*/
struct commit *commit;
const char *path = rebase_path_squash_msg();
+ const char *encoding = get_commit_output_encoding();
if (parse_head(r, &commit) ||
- !(p = get_commit_buffer(commit, NULL)) ||
+ !(p = logmsg_reencode(commit, NULL, encoding)) ||
write_message(p, strlen(p), path, 0)) {
unuse_commit_buffer(commit, p);
return error(_("could not write file: "
@@ -4116,7 +4326,7 @@ static int commit_staged_changes(struct repository *r,
opts, flags))
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
- unlink(git_path_merge_head(the_repository));
+ unlink(git_path_merge_head(r));
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
@@ -4186,9 +4396,12 @@ static int single_pick(struct repository *r,
struct commit *cmit,
struct replay_opts *opts)
{
+ int check_todo;
+
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
return do_pick_commit(r, opts->action == REPLAY_PICK ?
- TODO_PICK : TODO_REVERT, cmit, opts, 0);
+ TODO_PICK : TODO_REVERT, cmit, opts, 0,
+ &check_todo);
}
int sequencer_pick_revisions(struct repository *r,
@@ -4251,7 +4464,7 @@ int sequencer_pick_revisions(struct repository *r,
*/
if (walk_revs_populate_todo(&todo_list, opts) ||
- create_seq_dir() < 0)
+ create_seq_dir(r) < 0)
return -1;
if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
return error(_("can't revert as initial commit"));
@@ -4330,9 +4543,14 @@ struct labels_entry {
char label[FLEX_ARRAY];
};
-static int labels_cmp(const void *fndata, const struct labels_entry *a,
- const struct labels_entry *b, const void *key)
+static int labels_cmp(const void *fndata, const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key, const void *key)
{
+ const struct labels_entry *a, *b;
+
+ a = container_of(eptr, const struct labels_entry, entry);
+ b = container_of(entry_or_key, const struct labels_entry, entry);
+
return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
}
@@ -4353,7 +4571,6 @@ static const char *label_oid(struct object_id *oid, const char *label,
struct labels_entry *labels_entry;
struct string_entry *string_entry;
struct object_id dummy;
- size_t len;
int i;
string_entry = oidmap_get(&state->commit2label, oid);
@@ -4373,11 +4590,11 @@ static const char *label_oid(struct object_id *oid, const char *label,
* abbreviation for any uninteresting commit's names that does not
* clash with any other label.
*/
+ strbuf_reset(&state->buf);
if (!label) {
char *p;
- strbuf_reset(&state->buf);
- strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
+ strbuf_grow(&state->buf, GIT_MAX_HEXSZ);
label = p = state->buf.buf;
find_unique_abbrev_r(p, oid, default_abbrev);
@@ -4390,7 +4607,7 @@ static const char *label_oid(struct object_id *oid, const char *label,
size_t i = strlen(p) + 1;
oid_to_hex_r(p, oid);
- for (; i < GIT_SHA1_HEXSZ; i++) {
+ for (; i < the_hash_algo->hexsz; i++) {
char save = p[i];
p[i] = '\0';
if (!hashmap_get_from_hash(&state->labels,
@@ -4399,37 +4616,60 @@ static const char *label_oid(struct object_id *oid, const char *label,
p[i] = save;
}
}
- } else if (((len = strlen(label)) == the_hash_algo->hexsz &&
- !get_oid_hex(label, &dummy)) ||
- (len == 1 && *label == '#') ||
- hashmap_get_from_hash(&state->labels,
- strihash(label), label)) {
+ } else {
+ struct strbuf *buf = &state->buf;
+
/*
- * If the label already exists, or if the label is a valid full
- * OID, or the label is a '#' (which we use as a separator
- * between merge heads and oneline), we append a dash and a
- * number to make it unique.
+ * Sanitize labels by replacing non-alpha-numeric characters
+ * (including white-space ones) by dashes, as they might be
+ * illegal in file names (and hence in ref names).
+ *
+ * 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.
*/
- struct strbuf *buf = &state->buf;
+ for (; *label; label++)
+ if ((*label & 0x80) || isalnum(*label))
+ strbuf_addch(buf, *label);
+ /* avoid leading dash and double-dashes */
+ else if (buf->len && buf->buf[buf->len - 1] != '-')
+ strbuf_addch(buf, '-');
+ if (!buf->len) {
+ strbuf_addstr(buf, "rev-");
+ strbuf_add_unique_abbrev(buf, oid, default_abbrev);
+ }
+ label = buf->buf;
- strbuf_reset(buf);
- strbuf_add(buf, label, len);
+ if ((buf->len == the_hash_algo->hexsz &&
+ !get_oid_hex(label, &dummy)) ||
+ (buf->len == 1 && *label == '#') ||
+ hashmap_get_from_hash(&state->labels,
+ strihash(label), label)) {
+ /*
+ * If the label already exists, or if the label is a
+ * valid full OID, or the label is a '#' (which we use
+ * as a separator between merge heads and oneline), we
+ * append a dash and a number to make it unique.
+ */
+ size_t len = buf->len;
- for (i = 2; ; i++) {
- strbuf_setlen(buf, len);
- strbuf_addf(buf, "-%d", i);
- if (!hashmap_get_from_hash(&state->labels,
- strihash(buf->buf),
- buf->buf))
- break;
- }
+ for (i = 2; ; i++) {
+ strbuf_setlen(buf, len);
+ strbuf_addf(buf, "-%d", i);
+ if (!hashmap_get_from_hash(&state->labels,
+ strihash(buf->buf),
+ buf->buf))
+ break;
+ }
- label = buf->buf;
+ label = buf->buf;
+ }
}
FLEX_ALLOC_STR(labels_entry, label, label);
- hashmap_entry_init(labels_entry, strihash(label));
- hashmap_add(&state->labels, labels_entry);
+ hashmap_entry_init(&labels_entry->entry, strihash(label));
+ hashmap_add(&state->labels, &labels_entry->entry);
FLEX_ALLOC_STR(string_entry, string, label);
oidcpy(&string_entry->entry.oid, oid);
@@ -4444,6 +4684,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
{
int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+ int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -4463,14 +4704,19 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidmap_init(&commit2todo, 0);
oidmap_init(&state.commit2label, 0);
- hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
+ hashmap_init(&state.labels, labels_cmp, NULL, 0);
strbuf_init(&state.buf, 32);
if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
+ struct labels_entry *onto_label_entry;
struct object_id *oid = &revs->cmdline.rev[0].item->oid;
FLEX_ALLOC_STR(entry, string, "onto");
oidcpy(&entry->entry.oid, oid);
oidmap_put(&state.commit2label, entry);
+
+ FLEX_ALLOC_STR(onto_label_entry, label, "onto");
+ hashmap_entry_init(&onto_label_entry->entry, strihash("onto"));
+ hashmap_add(&state.labels, &onto_label_entry->entry);
}
/*
@@ -4525,10 +4771,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,
else
strbuf_addbuf(&label, &oneline);
- for (p1 = label.buf; *p1; p1++)
- if (isspace(*p1))
- *(char *)p1 = '-';
-
strbuf_reset(&buf);
strbuf_addf(&buf, "%s -C %s",
cmd_merge, oid_to_hex(&commit->object.oid));
@@ -4571,7 +4813,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
label_oid(oid, "branch-point", &state);
}
- /* Add HEAD as implict "tip of branch" */
+ /* Add HEAD as implicit "tip of branch" */
if (!iter->next)
tips_tail = &commit_list_insert(iter->item,
tips_tail)->next;
@@ -4610,7 +4852,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
if (!commit)
strbuf_addf(out, "%s %s\n", cmd_reset,
- rebase_cousins ? "onto" : "[new root]");
+ rebase_cousins || root_with_onto ?
+ "onto" : "[new root]");
else {
const char *to = NULL;
@@ -4657,7 +4900,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidmap_free(&commit2todo, 1);
oidmap_free(&state.commit2label, 1);
- hashmap_free(&state.labels, 1);
+ hashmap_free_entries(&state.labels, struct labels_entry, entry);
strbuf_release(&state.buf);
return 0;
@@ -4752,7 +4995,7 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
* are considered part of the pick, so we insert the commands *after*
* those chains if there are any.
*
- * As we insert the exec commands immediatly after rearranging
+ * As we insert the exec commands immediately after rearranging
* any fixups and before the user edits the list, a fixup chain
* can never contain comments (any comments are empty picks that
* have been commented out because the user did not specify
@@ -5028,9 +5271,15 @@ struct subject2item_entry {
};
static int subject2item_cmp(const void *fndata,
- const struct subject2item_entry *a,
- const struct subject2item_entry *b, const void *key)
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *key)
{
+ const struct subject2item_entry *a, *b;
+
+ a = container_of(eptr, const struct subject2item_entry, entry);
+ b = container_of(entry_or_key, const struct subject2item_entry, entry);
+
return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
}
@@ -5063,8 +5312,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
* In that case, last[i] will indicate the index of the latest item to
* be moved to appear after the i'th.
*/
- hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp,
- NULL, todo_list->nr);
+ hashmap_init(&subject2item, subject2item_cmp, NULL, todo_list->nr);
ALLOC_ARRAY(next, todo_list->nr);
ALLOC_ARRAY(tail, todo_list->nr);
ALLOC_ARRAY(subjects, todo_list->nr);
@@ -5090,7 +5338,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
*commit_todo_item_at(&commit_todo, item->commit) = item;
parse_commit(item->commit);
- commit_buffer = get_commit_buffer(item->commit, NULL);
+ commit_buffer = logmsg_reencode(item->commit, NULL, "UTF-8");
find_commit_subject(commit_buffer, &subject);
format_subject(&buf, subject, " ");
subject = subjects[i] = strbuf_detach(&buf, &subject_len);
@@ -5107,8 +5355,11 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
break;
}
- if ((entry = hashmap_get_from_hash(&subject2item,
- strhash(p), p)))
+ entry = hashmap_get_entry_from_hash(&subject2item,
+ strhash(p), p,
+ struct subject2item_entry,
+ entry);
+ if (entry)
/* found by title */
i2 = entry->i;
else if (!strchr(p, ' ') &&
@@ -5142,8 +5393,9 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
strhash(subject), subject)) {
FLEX_ALLOC_MEM(entry, subject, subject, subject_len);
entry->i = i;
- hashmap_entry_init(entry, strhash(entry->subject));
- hashmap_put(&subject2item, entry);
+ hashmap_entry_init(&entry->entry,
+ strhash(entry->subject));
+ hashmap_put(&subject2item, &entry->entry);
}
}
@@ -5177,7 +5429,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
for (i = 0; i < todo_list->nr; i++)
free(subjects[i]);
free(subjects);
- hashmap_free(&subject2item, 1);
+ hashmap_free_entries(&subject2item, struct subject2item_entry, entry);
clear_commit_todo_item(&commit_todo);