From 2b71595d47a75031346d7bc0125da39a9bb10126 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 29 Dec 2018 17:03:58 +0100 Subject: [PATCH 01/18] sequencer: changes in parse_insn_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This clears the number of items of a todo_list before parsing it to allow to parse the same list multiple times without issues. As its items are not dynamically allocated, or don’t need to allocate memory, no additionnal memory management is required here. Furthermore, if a line is invalid, the type of the corresponding command is set to a garbage value, and its argument is defined properly. This will allow to recreate the text of a todo list from its commands, even if one of them is incorrect. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index b68bca0bef..d605199a54 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2141,6 +2141,8 @@ static int 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; + for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -2154,7 +2156,10 @@ static int parse_insn_buffer(struct repository *r, char *buf, if (parse_insn_line(r, item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = TODO_NOOP; + item->command = TODO_COMMENT + 1; + item->arg = p; + item->arg_len = (int)(eol - p); + item->commit = NULL; } if (fixup_okay) From 5d94d54564fb0dea1f3caf2f1dacb7701f4be25c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 29 Dec 2018 17:03:59 +0100 Subject: [PATCH 02/18] sequencer: make the todo_list structure public This makes the structures todo_list and todo_item, and the functions todo_list_release() and parse_insn_buffer(), accessible outside of sequencer.c. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 69 ++++++++++------------------------------------------- sequencer.h | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/sequencer.c b/sequencer.c index d605199a54..25cc7a9a91 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1510,32 +1510,6 @@ static int allow_empty(struct repository *r, return 1; } -/* - * Note that ordering matters in this enum. Not only must it match the mapping - * below, it is also divided into several sections that matter. When adding - * new commands, make sure you add it in the right section. - */ -enum todo_command { - /* commands that handle commits */ - TODO_PICK = 0, - TODO_REVERT, - TODO_EDIT, - TODO_REWORD, - TODO_FIXUP, - TODO_SQUASH, - /* commands that do something else than handling a single commit */ - TODO_EXEC, - TODO_BREAK, - TODO_LABEL, - TODO_RESET, - TODO_MERGE, - /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP, - TODO_DROP, - /* comments (not counted for reporting progress) */ - TODO_COMMENT -}; - static struct { char c; const char *str; @@ -2012,26 +1986,7 @@ enum todo_item_flags { TODO_EDIT_MERGE_MSG = 1 }; -struct todo_item { - enum todo_command command; - struct commit *commit; - unsigned int flags; - const char *arg; - int arg_len; - size_t offset_in_buf; -}; - -struct todo_list { - struct strbuf buf; - struct todo_item *items; - int nr, alloc, current; - int done_nr, total_nr; - struct stat_data stat; -}; - -#define TODO_LIST_INIT { STRBUF_INIT } - -static void todo_list_release(struct todo_list *todo_list) +void todo_list_release(struct todo_list *todo_list) { strbuf_release(&todo_list->buf); FREE_AND_NULL(todo_list->items); @@ -2134,8 +2089,8 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return !item->commit; } -static int parse_insn_buffer(struct repository *r, char *buf, - struct todo_list *todo_list) +int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; @@ -2234,7 +2189,7 @@ static int read_populate_todo(struct repository *r, return error(_("could not stat '%s'"), todo_file); fill_stat_data(&todo_list->stat, &st); - res = parse_insn_buffer(r, todo_list->buf.buf, todo_list); + res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list); if (res) { if (is_rebase_i(opts)) return error(_("please fix this using " @@ -2265,7 +2220,7 @@ static int read_populate_todo(struct repository *r, FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && - !parse_insn_buffer(r, done.buf.buf, &done)) + !todo_list_parse_insn_buffer(r, done.buf.buf, &done)) todo_list->done_nr = count_commands(&done); else todo_list->done_nr = 0; @@ -4556,7 +4511,7 @@ int sequencer_add_exec_commands(struct repository *r, if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4612,7 +4567,7 @@ int transform_todos(struct repository *r, unsigned flags) if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4698,7 +4653,7 @@ int check_todo_list(struct repository *r) goto leave_check; } advise_to_edit_todo = res = - parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) goto leave_check; @@ -4717,7 +4672,7 @@ int check_todo_list(struct repository *r) goto leave_check; } strbuf_release(&todo_file); - res = !!parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + res = !!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); /* Find commits in git-rebase-todo.backup yet unseen */ for (i = todo_list.nr - 1; i >= 0; i--) { @@ -4799,7 +4754,7 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { todo_list_release(&todo_list); return -1; } @@ -4887,7 +4842,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla if (strbuf_read_file(buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, buf->buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, buf->buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4995,7 +4950,7 @@ int rearrange_squash(struct repository *r) if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { todo_list_release(&todo_list); return -1; } diff --git a/sequencer.h b/sequencer.h index 9d83f0f3e9..c6360bac66 100644 --- a/sequencer.h +++ b/sequencer.h @@ -73,6 +73,56 @@ enum missing_commit_check_level { int write_message(const void *buf, size_t len, const char *filename, int append_eol); +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * of todo_command_info (in sequencer.c), it is also divided into several + * sections that matter. When adding new commands, make sure you add it in the + * right section. + */ +enum todo_command { + /* commands that handle commits */ + TODO_PICK = 0, + TODO_REVERT, + TODO_EDIT, + TODO_REWORD, + TODO_FIXUP, + TODO_SQUASH, + /* commands that do something else than handling a single commit */ + TODO_EXEC, + TODO_BREAK, + TODO_LABEL, + TODO_RESET, + TODO_MERGE, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP, + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT +}; + +struct todo_item { + enum todo_command command; + struct commit *commit; + unsigned int flags; + const char *arg; + int arg_len; + size_t offset_in_buf; +}; + +struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; + int done_nr, total_nr; + struct stat_data stat; +}; + +#define TODO_LIST_INIT { STRBUF_INIT } + +int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list); +void todo_list_release(struct todo_list *todo_list); + /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); int sequencer_pick_revisions(struct repository *repo, From 6ad656db9b2d2426a0a884b431e8adc9877101bc Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:46 +0100 Subject: [PATCH 03/18] sequencer: remove the 'arg' field from todo_item The 'arg' field of todo_item used to store the address of the first byte of the parameter of a command in a todo list. It was associated with the length of the parameter (the 'arg_len' field). This replaces the 'arg' field by 'arg_offset'. This new field does not store the address of the parameter, but the position of the first character of the parameter in the buffer. todo_item_get_arg() is added to return the address of the parameter of an item. This will prevent todo_list_add_exec_commands() from having to do awful pointer arithmetics when growing the todo list buffer. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 67 ++++++++++++++++++++++++++++++----------------------- sequencer.h | 6 +++-- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/sequencer.c b/sequencer.c index 25cc7a9a91..c844a9b7f3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1999,8 +1999,14 @@ static struct todo_item *append_new_todo(struct todo_list *todo_list) return todo_list->items + todo_list->nr++; } +const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item) +{ + return todo_list->buf.buf + item->arg_offset; +} + static int parse_insn_line(struct repository *r, struct todo_item *item, - const char *bol, char *eol) + const char *buf, const char *bol, char *eol) { struct object_id commit_oid; char *end_of_object_name; @@ -2014,7 +2020,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, if (bol == eol || *bol == '\r' || *bol == comment_line_char) { item->command = TODO_COMMENT; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@ -2041,7 +2047,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return error(_("%s does not accept arguments: '%s'"), command_to_string(item->command), bol); item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@ -2053,7 +2059,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, if (item->command == TODO_EXEC || item->command == TODO_LABEL || item->command == TODO_RESET) { item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@ -2067,7 +2073,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, } else { item->flags |= TODO_EDIT_MERGE_MSG; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@ -2079,8 +2085,9 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, status = get_oid(bol, &commit_oid); *end_of_object_name = saved; - item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); - item->arg_len = (int)(eol - item->arg); + bol = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_offset = bol - buf; + item->arg_len = (int)(eol - bol); if (status < 0) return -1; @@ -2108,11 +2115,11 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf, item = append_new_todo(todo_list); item->offset_in_buf = p - todo_list->buf.buf; - if (parse_insn_line(r, item, p, eol)) { + if (parse_insn_line(r, item, buf, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); item->command = TODO_COMMENT + 1; - item->arg = p; + item->arg_offset = p - buf; item->arg_len = (int)(eol - p); item->commit = NULL; } @@ -2452,7 +2459,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->command = command; item->commit = commit; - item->arg = NULL; + item->arg_offset = 0; item->arg_len = 0; item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); @@ -3491,6 +3498,8 @@ static int pick_commits(struct repository *r, 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); + if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { @@ -3542,10 +3551,9 @@ static int pick_commits(struct repository *r, fprintf(stderr, _("Stopped at %s... %.*s\n"), short_commit_name(commit), - item->arg_len, item->arg); + item->arg_len, arg); return error_with_patch(r, commit, - item->arg, item->arg_len, opts, res, - !res); + arg, item->arg_len, opts, res, !res); } if (is_rebase_i(opts) && !res) record_in_rewritten(&item->commit->object.oid, @@ -3554,7 +3562,7 @@ static int pick_commits(struct repository *r, if (res == 1) intend_to_amend(); return error_failed_squash(r, item->commit, opts, - item->arg_len, item->arg); + item->arg_len, arg); } else if (res && is_rebase_i(opts) && item->commit) { int to_amend = 0; struct object_id oid; @@ -3573,16 +3581,16 @@ static int pick_commits(struct repository *r, to_amend = 1; return res | error_with_patch(r, item->commit, - item->arg, item->arg_len, opts, + arg, item->arg_len, opts, res, to_amend); } } else if (item->command == TODO_EXEC) { - char *end_of_arg = (char *)(item->arg + item->arg_len); + char *end_of_arg = (char *)(arg + item->arg_len); int saved = *end_of_arg; struct stat st; *end_of_arg = '\0'; - res = do_exec(r, item->arg); + res = do_exec(r, arg); *end_of_arg = saved; /* Reread the todo file if it has changed. */ @@ -3599,14 +3607,14 @@ static int pick_commits(struct repository *r, todo_list->current = -1; } } else if (item->command == TODO_LABEL) { - if ((res = do_label(r, item->arg, item->arg_len))) + if ((res = do_label(r, arg, item->arg_len))) reschedule = 1; } else if (item->command == TODO_RESET) { - if ((res = do_reset(r, item->arg, item->arg_len, opts))) + if ((res = do_reset(r, arg, item->arg_len, opts))) reschedule = 1; } else if (item->command == TODO_MERGE) { if ((res = do_merge(r, item->commit, - item->arg, item->arg_len, + arg, item->arg_len, item->flags, opts)) < 0) reschedule = 1; else if (item->commit) @@ -3615,9 +3623,8 @@ static int pick_commits(struct repository *r, if (res > 0) /* failed with merge conflicts */ return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); @@ -3632,9 +3639,8 @@ static int pick_commits(struct repository *r, if (item->commit) return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } todo_list->current++; @@ -4575,7 +4581,8 @@ int transform_todos(struct repository *r, unsigned flags) for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { - strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg); + strbuf_addf(&buf, "%.*s\n", item->arg_len, + todo_item_get_arg(&todo_list, item)); continue; } @@ -4605,7 +4612,8 @@ int transform_todos(struct repository *r, unsigned flags) if (!item->arg_len) strbuf_addch(&buf, '\n'); else - strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg); + strbuf_addf(&buf, " %.*s\n", item->arg_len, + todo_item_get_arg(&todo_list, item)); } i = write_message(buf.buf, buf.len, todo_file, 0); @@ -4681,7 +4689,8 @@ int check_todo_list(struct repository *r) if (commit && !*commit_seen_at(&commit_seen, commit)) { strbuf_addf(&missing, " - %s %.*s\n", short_commit_name(commit), - item->arg_len, item->arg); + item->arg_len, + todo_item_get_arg(&todo_list, item)); *commit_seen_at(&commit_seen, commit) = 1; } } diff --git a/sequencer.h b/sequencer.h index c6360bac66..50d552429c 100644 --- a/sequencer.h +++ b/sequencer.h @@ -104,9 +104,9 @@ struct todo_item { enum todo_command command; struct commit *commit; unsigned int flags; - const char *arg; int arg_len; - size_t offset_in_buf; + /* The offset of the command and its argument in the strbuf */ + size_t offset_in_buf, arg_offset; }; struct todo_list { @@ -122,6 +122,8 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); void todo_list_release(struct todo_list *todo_list); +const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item); /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); From cbef27d61cd6ae4f1ecae054eb9df06e898148d8 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:47 +0100 Subject: [PATCH 04/18] sequencer: refactor transform_todos() to work on a todo_list This refactors transform_todos() to work on a todo_list. The function is renamed todo_list_transform(). As rebase -p still need to check the todo list from the disk, a new function is introduced, transform_todo_file(). It is still used by complete_action() and edit_todo_list() for now, but they will be replaced in a future commit. todo_list_transform() is not a static function, because it will be used by edit_todo_list() from rebase-interactive.c in a future commit. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 2 +- rebase-interactive.c | 4 +-- sequencer.c | 54 +++++++++++++++++++++++------------ sequencer.h | 4 ++- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index dd2a55ab1d..0898eb4c59 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -253,7 +253,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) } case SHORTEN_OIDS: case EXPAND_OIDS: - ret = transform_todos(the_repository, flags); + ret = transform_todo_file(the_repository, flags); break; case CHECK_TODO_LIST: ret = check_todo_list(the_repository); diff --git a/rebase-interactive.c b/rebase-interactive.c index 68aff1dac2..842fa07e7e 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -69,7 +69,7 @@ int edit_todo_list(struct repository *r, unsigned flags) strbuf_release(&buf); - transform_todos(r, flags | TODO_LIST_SHORTEN_IDS); + transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS); if (strbuf_read_file(&buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); @@ -85,7 +85,7 @@ int edit_todo_list(struct repository *r, unsigned flags) if (launch_sequence_editor(todo_file, NULL, NULL)) return -1; - transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS)); + transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS)); return 0; } diff --git a/sequencer.c b/sequencer.c index c844a9b7f3..346706029c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4562,27 +4562,18 @@ int sequencer_add_exec_commands(struct repository *r, return i; } -int transform_todos(struct repository *r, unsigned flags) +void todo_list_transform(struct repository *r, struct todo_list *todo_list, + unsigned flags) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; struct strbuf buf = STRBUF_INIT; struct todo_item *item; int i; - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + for (item = todo_list->items, i = 0; i < todo_list->nr; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { strbuf_addf(&buf, "%.*s\n", item->arg_len, - todo_item_get_arg(&todo_list, item)); + todo_item_get_arg(todo_list, item)); continue; } @@ -4613,12 +4604,39 @@ int transform_todos(struct repository *r, unsigned flags) strbuf_addch(&buf, '\n'); else strbuf_addf(&buf, " %.*s\n", item->arg_len, - todo_item_get_arg(&todo_list, item)); + todo_item_get_arg(todo_list, item)); } - i = write_message(buf.buf, buf.len, todo_file, 0); + strbuf_reset(&todo_list->buf); + strbuf_add(&todo_list->buf, buf.buf, buf.len); + strbuf_release(&buf); + + if (todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list)) + BUG("unusable todo list"); +} + +int transform_todo_file(struct repository *r, unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_transform(r, &todo_list, flags); + + res = write_message(todo_list.buf.buf, todo_list.buf.len, todo_file, 0); todo_list_release(&todo_list); - return i; + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; } enum missing_commit_check_level get_missing_commit_check_level(void) @@ -4880,7 +4898,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return error(_("could not copy '%s' to '%s'."), todo_file, rebase_path_todo_backup()); - if (transform_todos(r, flags | TODO_LIST_SHORTEN_IDS)) + if (transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS)) return error(_("could not transform the todo list")); strbuf_reset(buf); @@ -4909,7 +4927,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return -1; } - if (transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS))) + if (transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS))) return error(_("could not transform the todo list")); if (opts->allow_ff && skip_unnecessary_picks(r, &oid)) diff --git a/sequencer.h b/sequencer.h index 50d552429c..2ddb20cbc1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -121,6 +121,8 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); +void todo_list_transform(struct repository *r, struct todo_list *todo_list, + unsigned flags); void todo_list_release(struct todo_list *todo_list); const char *todo_item_get_arg(struct todo_list *todo_list, struct todo_item *item); @@ -148,7 +150,7 @@ int sequencer_make_script(struct repository *repo, FILE *out, unsigned flags); int sequencer_add_exec_commands(struct repository *r, const char *command); -int transform_todos(struct repository *r, unsigned flags); +int transform_todo_file(struct repository *r, unsigned flags); enum missing_commit_check_level get_missing_commit_check_level(void); int check_todo_list(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, From 616d7740cfbe65533af8ff1dabcf4e56f6baad5a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:48 +0100 Subject: [PATCH 05/18] sequencer: introduce todo_list_write_to_file() This introduces a new function to recreate the text of a todo list from its commands and write it to a file. This will be useful as the next few commits will change the use of the buffer in struct todo_list so it will no longer be a mirror of the file on disk. This functionality already exists in todo_list_transform(), but this function was made to replace the buffer of a todo list, which is not what we want here. Thus, the part of todo_list_transform() that replaces the buffer is dropped, and the function is renamed todo_list_to_strbuf(). It is called by todo_list_write_to_file() to fill the buffer to write to the disk. todo_list_write_to_file() can also take care of appending the help text to the buffer before writing it to the disk, or to write only the first n items of the list. This feature will be used by skip_unnecessary_picks(), which has to write done commands in a file. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 61 +++++++++++++++++++++++++++++++++++------------------ sequencer.h | 11 ++++++---- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/sequencer.c b/sequencer.c index 346706029c..4809b22ce4 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4562,26 +4562,28 @@ int sequencer_add_exec_commands(struct repository *r, return i; } -void todo_list_transform(struct repository *r, struct todo_list *todo_list, - unsigned flags) +static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, + struct strbuf *buf, int num, unsigned flags) { - struct strbuf buf = STRBUF_INIT; struct todo_item *item; - int i; + int i, max = todo_list->nr; - for (item = todo_list->items, i = 0; i < todo_list->nr; i++, item++) { + if (num > 0 && num < max) + max = num; + + for (item = todo_list->items, i = 0; i < max; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { - strbuf_addf(&buf, "%.*s\n", item->arg_len, + strbuf_addf(buf, "%.*s\n", item->arg_len, todo_item_get_arg(todo_list, item)); continue; } /* add command to the buffer */ if (flags & TODO_LIST_ABBREVIATE_CMDS) - strbuf_addch(&buf, command_to_char(item->command)); + strbuf_addch(buf, command_to_char(item->command)); else - strbuf_addstr(&buf, command_to_string(item->command)); + strbuf_addstr(buf, command_to_string(item->command)); /* add commit id */ if (item->commit) { @@ -4591,28 +4593,48 @@ void todo_list_transform(struct repository *r, struct todo_list *todo_list, if (item->command == TODO_MERGE) { if (item->flags & TODO_EDIT_MERGE_MSG) - strbuf_addstr(&buf, " -c"); + strbuf_addstr(buf, " -c"); else - strbuf_addstr(&buf, " -C"); + strbuf_addstr(buf, " -C"); } - strbuf_addf(&buf, " %s", oid); + strbuf_addf(buf, " %s", oid); } /* add all the rest */ if (!item->arg_len) - strbuf_addch(&buf, '\n'); + strbuf_addch(buf, '\n'); else - strbuf_addf(&buf, " %.*s\n", item->arg_len, + strbuf_addf(buf, " %.*s\n", item->arg_len, todo_item_get_arg(todo_list, item)); } +} - strbuf_reset(&todo_list->buf); - strbuf_add(&todo_list->buf, buf.buf, buf.len); +int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags) +{ + int edit_todo = !(shortrevisions && shortonto), res; + struct strbuf buf = STRBUF_INIT; + + todo_list_to_strbuf(r, todo_list, &buf, num, flags); + + if (flags & TODO_LIST_APPEND_TODO_HELP) { + int command_count = count_commands(todo_list); + if (!edit_todo) { + strbuf_addch(&buf, '\n'); + strbuf_commented_addf(&buf, Q_("Rebase %s onto %s (%d command)", + "Rebase %s onto %s (%d commands)", + command_count), + shortrevisions, shortonto, command_count); + } + append_todo_help(edit_todo, flags & TODO_LIST_KEEP_EMPTY, &buf); + } + + res = write_message(buf.buf, buf.len, file, 0); strbuf_release(&buf); - if (todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list)) - BUG("unusable todo list"); + return res; } int transform_todo_file(struct repository *r, unsigned flags) @@ -4629,9 +4651,8 @@ int transform_todo_file(struct repository *r, unsigned flags) return error(_("unusable todo list: '%s'"), todo_file); } - todo_list_transform(r, &todo_list, flags); - - res = write_message(todo_list.buf.buf, todo_list.buf.len, todo_file, 0); + res = todo_list_write_to_file(r, &todo_list, todo_file, + NULL, NULL, -1, flags); todo_list_release(&todo_list); if (res) diff --git a/sequencer.h b/sequencer.h index 2ddb20cbc1..7278f9675b 100644 --- a/sequencer.h +++ b/sequencer.h @@ -121,8 +121,9 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); -void todo_list_transform(struct repository *r, struct todo_list *todo_list, - unsigned flags); +int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags); void todo_list_release(struct todo_list *todo_list); const char *todo_item_get_arg(struct todo_list *todo_list, struct todo_item *item); @@ -145,8 +146,10 @@ int sequencer_remove_state(struct replay_opts *opts); * commits should be rebased onto the new base, this flag needs to be passed. */ #define TODO_LIST_REBASE_COUSINS (1U << 4) -int sequencer_make_script(struct repository *repo, FILE *out, - int argc, const char **argv, +#define TODO_LIST_APPEND_TODO_HELP (1U << 5) + +int sequencer_make_script(struct repository *r, FILE *out, int argc, + const char **argv, unsigned flags); int sequencer_add_exec_commands(struct repository *r, const char *command); From 6ca89c6f399b86983c7e93a3c5b918cad8292b47 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:49 +0100 Subject: [PATCH 06/18] sequencer: refactor check_todo_list() to work on a todo_list This refactors check_todo_list() to work on a todo_list to avoid redundant reads and writes to the disk. The function is renamed todo_list_check(). The parsing of the two todo lists is left to the caller. As rebase -p still need to check the todo list from the disk, a new function is introduced, check_todo_list_from_file(). It reads the file from the disk, parses it, pass the todo_list to todo_list_check(), and writes it back to the disk. As get_missing_commit_check_level() and the enum missing_commit_check_level are no longer needed inside of sequencer.c, they are moved to rebase-interactive.c, and made static again. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 2 +- rebase-interactive.c | 91 ++++++++++++++++++++++++- rebase-interactive.h | 2 + sequencer.c | 121 +++++++--------------------------- sequencer.h | 9 +-- 5 files changed, 117 insertions(+), 108 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 0898eb4c59..df19ccaeb9 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -256,7 +256,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = transform_todo_file(the_repository, flags); break; case CHECK_TODO_LIST: - ret = check_todo_list(the_repository); + ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: ret = rearrange_squash(the_repository); diff --git a/rebase-interactive.c b/rebase-interactive.c index 842fa07e7e..dfa6dd530f 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -1,8 +1,32 @@ #include "cache.h" #include "commit.h" -#include "rebase-interactive.h" #include "sequencer.h" +#include "rebase-interactive.h" #include "strbuf.h" +#include "commit-slab.h" +#include "config.h" + +enum missing_commit_check_level { + MISSING_COMMIT_CHECK_IGNORE = 0, + MISSING_COMMIT_CHECK_WARN, + MISSING_COMMIT_CHECK_ERROR +}; + +static enum missing_commit_check_level get_missing_commit_check_level(void) +{ + const char *value; + + if (git_config_get_value("rebase.missingcommitscheck", &value) || + !strcasecmp("ignore", value)) + return MISSING_COMMIT_CHECK_IGNORE; + if (!strcasecmp("warn", value)) + return MISSING_COMMIT_CHECK_WARN; + if (!strcasecmp("error", value)) + return MISSING_COMMIT_CHECK_ERROR; + warning(_("unrecognized setting %s for option " + "rebase.missingCommitsCheck. Ignoring."), value); + return MISSING_COMMIT_CHECK_IGNORE; +} void append_todo_help(unsigned edit_todo, unsigned keep_empty, struct strbuf *buf) @@ -89,3 +113,68 @@ int edit_todo_list(struct repository *r, unsigned flags) return 0; } + +define_commit_slab(commit_seen, unsigned char); +/* + * Check if the user dropped some commits by mistake + * Behaviour determined by rebase.missingCommitsCheck. + * Check if there is an unrecognized command or a + * bad SHA-1 in a command. + */ +int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo) +{ + enum missing_commit_check_level check_level = get_missing_commit_check_level(); + struct strbuf missing = STRBUF_INIT; + int res = 0, i; + struct commit_seen commit_seen; + + init_commit_seen(&commit_seen); + + if (check_level == MISSING_COMMIT_CHECK_IGNORE) + goto leave_check; + + /* Mark the commits in git-rebase-todo as seen */ + for (i = 0; i < new_todo->nr; i++) { + struct commit *commit = new_todo->items[i].commit; + if (commit) + *commit_seen_at(&commit_seen, commit) = 1; + } + + /* Find commits in git-rebase-todo.backup yet unseen */ + for (i = old_todo->nr - 1; i >= 0; i--) { + struct todo_item *item = old_todo->items + i; + struct commit *commit = item->commit; + if (commit && !*commit_seen_at(&commit_seen, commit)) { + strbuf_addf(&missing, " - %s %.*s\n", + find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV), + item->arg_len, + todo_item_get_arg(old_todo, item)); + *commit_seen_at(&commit_seen, commit) = 1; + } + } + + /* Warn about missing commits */ + if (!missing.len) + goto leave_check; + + if (check_level == MISSING_COMMIT_CHECK_ERROR) + res = 1; + + fprintf(stderr, + _("Warning: some commits may have been dropped accidentally.\n" + "Dropped commits (newer to older):\n")); + + /* Make the list user-friendly and display */ + fputs(missing.buf, stderr); + strbuf_release(&missing); + + fprintf(stderr, _("To avoid this message, use \"drop\" to " + "explicitly remove a commit.\n\n" + "Use 'git config rebase.missingCommitsCheck' to change " + "the level of warnings.\n" + "The possible behaviours are: ignore, warn, error.\n\n")); + +leave_check: + clear_commit_seen(&commit_seen); + return res; +} diff --git a/rebase-interactive.h b/rebase-interactive.h index 17b6c9f6d0..187b5032d6 100644 --- a/rebase-interactive.h +++ b/rebase-interactive.h @@ -3,9 +3,11 @@ struct strbuf; struct repository; +struct todo_list; void append_todo_help(unsigned edit_todo, unsigned keep_empty, struct strbuf *buf); int edit_todo_list(struct repository *r, unsigned flags); +int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo); #endif diff --git a/sequencer.c b/sequencer.c index 4809b22ce4..99e12c751e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4660,112 +4660,37 @@ int transform_todo_file(struct repository *r, unsigned flags) return 0; } -enum missing_commit_check_level get_missing_commit_check_level(void) -{ - const char *value; - - if (git_config_get_value("rebase.missingcommitscheck", &value) || - !strcasecmp("ignore", value)) - return MISSING_COMMIT_CHECK_IGNORE; - if (!strcasecmp("warn", value)) - return MISSING_COMMIT_CHECK_WARN; - if (!strcasecmp("error", value)) - return MISSING_COMMIT_CHECK_ERROR; - warning(_("unrecognized setting %s for option " - "rebase.missingCommitsCheck. Ignoring."), value); - return MISSING_COMMIT_CHECK_IGNORE; -} +static const char edit_todo_list_advice[] = +N_("You can fix this with 'git rebase --edit-todo' " +"and then run 'git rebase --continue'.\n" +"Or you can abort the rebase with 'git rebase" +" --abort'.\n"); -define_commit_slab(commit_seen, unsigned char); -/* - * Check if the user dropped some commits by mistake - * Behaviour determined by rebase.missingCommitsCheck. - * Check if there is an unrecognized command or a - * bad SHA-1 in a command. - */ -int check_todo_list(struct repository *r) +int check_todo_list_from_file(struct repository *r) { - enum missing_commit_check_level check_level = get_missing_commit_check_level(); - struct strbuf todo_file = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf missing = STRBUF_INIT; - int advise_to_edit_todo = 0, res = 0, i; - struct commit_seen commit_seen; - - init_commit_seen(&commit_seen); + struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT; + int res = 0; - strbuf_addstr(&todo_file, rebase_path_todo()); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { + if (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) { res = -1; - goto leave_check; - } - advise_to_edit_todo = res = - todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - - if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) - goto leave_check; - - /* Mark the commits in git-rebase-todo as seen */ - for (i = 0; i < todo_list.nr; i++) { - struct commit *commit = todo_list.items[i].commit; - if (commit) - *commit_seen_at(&commit_seen, commit) = 1; + goto out; } - todo_list_release(&todo_list); - strbuf_addstr(&todo_file, ".backup"); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { + if (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) { res = -1; - goto leave_check; - } - strbuf_release(&todo_file); - res = !!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - - /* Find commits in git-rebase-todo.backup yet unseen */ - for (i = todo_list.nr - 1; i >= 0; i--) { - struct todo_item *item = todo_list.items + i; - struct commit *commit = item->commit; - if (commit && !*commit_seen_at(&commit_seen, commit)) { - strbuf_addf(&missing, " - %s %.*s\n", - short_commit_name(commit), - item->arg_len, - todo_item_get_arg(&todo_list, item)); - *commit_seen_at(&commit_seen, commit) = 1; - } + goto out; } - /* Warn about missing commits */ - if (!missing.len) - goto leave_check; - - if (check_level == MISSING_COMMIT_CHECK_ERROR) - advise_to_edit_todo = res = 1; - - fprintf(stderr, - _("Warning: some commits may have been dropped accidentally.\n" - "Dropped commits (newer to older):\n")); - - /* Make the list user-friendly and display */ - fputs(missing.buf, stderr); - strbuf_release(&missing); - - fprintf(stderr, _("To avoid this message, use \"drop\" to " - "explicitly remove a commit.\n\n" - "Use 'git config rebase.missingCommitsCheck' to change " - "the level of warnings.\n" - "The possible behaviours are: ignore, warn, error.\n\n")); - -leave_check: - clear_commit_seen(&commit_seen); - strbuf_release(&todo_file); - todo_list_release(&todo_list); - - if (advise_to_edit_todo) - fprintf(stderr, - _("You can fix this with 'git rebase --edit-todo' " - "and then run 'git rebase --continue'.\n" - "Or you can abort the rebase with 'git rebase" - " --abort'.\n")); + res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo); + if (!res) + res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo); + if (!res) + res = todo_list_check(&old_todo, &new_todo); + if (res) + fprintf(stderr, _(edit_todo_list_advice)); +out: + todo_list_release(&old_todo); + todo_list_release(&new_todo); return res; } @@ -4943,7 +4868,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla todo_list_release(&todo_list); - if (check_todo_list(r)) { + if (check_todo_list_from_file(r)) { checkout_onto(opts, onto_name, onto, orig_head); return -1; } diff --git a/sequencer.h b/sequencer.h index 7278f9675b..217353e9f0 100644 --- a/sequencer.h +++ b/sequencer.h @@ -64,12 +64,6 @@ struct replay_opts { }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } -enum missing_commit_check_level { - MISSING_COMMIT_CHECK_IGNORE = 0, - MISSING_COMMIT_CHECK_WARN, - MISSING_COMMIT_CHECK_ERROR -}; - int write_message(const void *buf, size_t len, const char *filename, int append_eol); @@ -154,8 +148,7 @@ int sequencer_make_script(struct repository *r, FILE *out, int argc, int sequencer_add_exec_commands(struct repository *r, const char *command); int transform_todo_file(struct repository *r, unsigned flags); -enum missing_commit_check_level get_missing_commit_check_level(void); -int check_todo_list(struct repository *r); +int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, const char *cmd, From 683153a438f1b6b8e1a289a71f36244bde67e38f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:54 +0100 Subject: [PATCH 07/18] sequencer: refactor sequencer_add_exec_commands() to work on a todo_list This refactors sequencer_add_exec_commands() to work on a todo_list to avoid redundant reads and writes to the disk. Instead of inserting the `exec' commands between the other commands and re-parsing the buffer at the end, they are appended to the buffer once, and a new list of items is created. Items from the old list are copied across and new `exec' items are appended when necessary. This eliminates the need to reparse the buffer, but this also means we have to use todo_list_write_to_disk() to write the file. todo_list_add_exec_commands() and sequencer_add_exec_commands() are modified to take a string list instead of a string -- one item for each command. This makes it easier to insert a new command to the todo list for each command to execute. sequencer_add_exec_commands() still reads the todo list from the disk, as it is needed by rebase -p. complete_action() still uses sequencer_add_exec_commands() for now. This will be changed in a future commit. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 18 ++++-- sequencer.c | 114 ++++++++++++++++++++++------------ sequencer.h | 5 +- 3 files changed, 91 insertions(+), 46 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index df19ccaeb9..813bc34140 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -65,7 +65,7 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, const char *onto, const char *onto_name, const char *squash_onto, const char *head_name, const char *restrict_revision, char *raw_strategies, - const char *cmd, unsigned autosquash) + struct string_list *commands, unsigned autosquash) { int ret; const char *head_hash = NULL; @@ -116,7 +116,7 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, discard_cache(); ret = complete_action(the_repository, opts, flags, shortrevisions, onto_name, onto, - head_hash, cmd, autosquash); + head_hash, commands, autosquash); } free(revisions); @@ -139,6 +139,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL, *squash_onto = NULL, *upstream = NULL, *head_name = NULL, *switch_to = NULL, *cmd = NULL; + struct string_list commands = STRING_LIST_INIT_DUP; char *raw_strategies = NULL; enum { NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH, @@ -221,6 +222,14 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) warning(_("--[no-]rebase-cousins has no effect without " "--rebase-merges")); + if (cmd && *cmd) { + string_list_split(&commands, cmd, '\n', -1); + + /* rebase.c adds a new line to cmd after every command, + * so here the last command is always empty */ + string_list_remove_empty_items(&commands, 0); + } + switch (command) { case NONE: if (!onto && !upstream) @@ -228,7 +237,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto, onto_name, squash_onto, head_name, restrict_revision, - raw_strategies, cmd, autosquash); + raw_strategies, &commands, autosquash); break; case SKIP: { struct string_list merge_rr = STRING_LIST_INIT_DUP; @@ -262,11 +271,12 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = rearrange_squash(the_repository); break; case ADD_EXEC: - ret = sequencer_add_exec_commands(the_repository, cmd); + ret = sequencer_add_exec_commands(the_repository, &commands); break; default: BUG("invalid command '%d'", command); } + string_list_clear(&commands, 0); return !!ret; } diff --git a/sequencer.c b/sequencer.c index 99e12c751e..4c076bd183 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4505,61 +4505,95 @@ int sequencer_make_script(struct repository *r, FILE *out, * Add commands after pick and (series of) squash/fixup commands * in the todo list. */ -int sequencer_add_exec_commands(struct repository *r, - const char *commands) +static void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf *buf = &todo_list.buf; - size_t offset = 0, commands_len = strlen(commands); - int i, insert; + struct strbuf *buf = &todo_list->buf; + size_t base_offset = buf->len; + int i, insert, nr = 0, alloc = 0; + struct todo_item *items = NULL, *base_items = NULL; - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + base_items = xcalloc(commands->nr, sizeof(struct todo_item)); + for (i = 0; i < commands->nr; i++) { + size_t command_len = strlen(commands->items[i].string); - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); + strbuf_addstr(buf, commands->items[i].string); + strbuf_addch(buf, '\n'); + + base_items[i].command = TODO_EXEC; + base_items[i].offset_in_buf = base_offset; + base_items[i].arg_offset = base_offset + strlen("exec "); + base_items[i].arg_len = command_len - strlen("exec "); + + base_offset += command_len + 1; } /* * Insert after every pick. Here, fixup/squash chains * 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 + * 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 + * --keep-empty). So, it is safe to insert an exec command + * without looking at the command following a comment. */ - insert = -1; - for (i = 0; i < todo_list.nr; i++) { - enum todo_command command = todo_list.items[i].command; - - if (insert >= 0) { - /* skip fixup/squash chains */ - if (command == TODO_COMMENT) - continue; - else if (is_fixup(command)) { - insert = i + 1; - continue; - } - strbuf_insert(buf, - todo_list.items[insert].offset_in_buf + - offset, commands, commands_len); - offset += commands_len; - insert = -1; + insert = 0; + for (i = 0; i < todo_list->nr; i++) { + enum todo_command command = todo_list->items[i].command; + if (insert && !is_fixup(command)) { + ALLOC_GROW(items, nr + commands->nr, alloc); + COPY_ARRAY(items + nr, base_items, commands->nr); + nr += commands->nr; + + insert = 0; } + ALLOC_GROW(items, nr + 1, alloc); + items[nr++] = todo_list->items[i]; + if (command == TODO_PICK || command == TODO_MERGE) - insert = i + 1; + insert = 1; } /* insert or append final */ - if (insert >= 0 && insert < todo_list.nr) - strbuf_insert(buf, todo_list.items[insert].offset_in_buf + - offset, commands, commands_len); - else if (insert >= 0 || !offset) - strbuf_add(buf, commands, commands_len); + if (insert || nr == todo_list->nr) { + ALLOC_GROW(items, nr + commands->nr, alloc); + COPY_ARRAY(items + nr, base_items, commands->nr); + nr += commands->nr; + } + + free(base_items); + FREE_AND_NULL(todo_list->items); + todo_list->items = items; + todo_list->nr = nr; + todo_list->alloc = alloc; +} + +int sequencer_add_exec_commands(struct repository *r, + struct string_list *commands) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); - i = write_message(buf->buf, buf->len, todo_file, 0); + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_add_exec_commands(&todo_list, commands); + res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, 0); todo_list_release(&todo_list); - return i; + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; } static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, @@ -4790,7 +4824,7 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, - const char *onto, const char *orig_head, const char *cmd, + const char *onto, const char *orig_head, struct string_list *commands, unsigned autosquash) { const char *shortonto, *todo_file = rebase_path_todo(); @@ -4809,8 +4843,8 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla if (autosquash && rearrange_squash(r)) return -1; - if (cmd && *cmd) - sequencer_add_exec_commands(r, cmd); + if (commands->nr) + sequencer_add_exec_commands(r, commands); if (strbuf_read_file(buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); diff --git a/sequencer.h b/sequencer.h index 217353e9f0..87d04a3b9b 100644 --- a/sequencer.h +++ b/sequencer.h @@ -146,12 +146,13 @@ int sequencer_make_script(struct repository *r, FILE *out, int argc, const char **argv, unsigned flags); -int sequencer_add_exec_commands(struct repository *r, const char *command); +int sequencer_add_exec_commands(struct repository *r, + struct string_list *commands); int transform_todo_file(struct repository *r, unsigned flags); int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, - const char *onto, const char *orig_head, const char *cmd, + const char *onto, const char *orig_head, struct string_list *commands, unsigned autosquash); int rearrange_squash(struct repository *r); From f2a04904be6584f1ec783ed5d3c425026bcf908f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:55 +0100 Subject: [PATCH 08/18] sequencer: refactor rearrange_squash() to work on a todo_list This refactors rearrange_squash() to work on a todo_list to avoid redundant reads and writes. The function is renamed todo_list_rearrange_squash(). The old version created a new buffer, which was directly written to the disk. This new version creates a new item list by just copying items from the old item list, without creating a new buffer. This eliminates the need to reparse the todo list, but this also means its buffer cannot be directly written to the disk. As rebase -p still need to check the todo list from the disk, a new function is introduced, rearrange_squash_in_todo_file(). complete_action() still uses rearrange_squash_in_todo_file() for now. This will be changed in a future commit. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 2 +- sequencer.c | 92 ++++++++++++++++++----------------- sequencer.h | 2 +- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 813bc34140..60b15f9693 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -268,7 +268,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: - ret = rearrange_squash(the_repository); + ret = rearrange_squash_in_todo_file(the_repository); break; case ADD_EXEC: ret = sequencer_add_exec_commands(the_repository, &commands); diff --git a/sequencer.c b/sequencer.c index 4c076bd183..7798f93b23 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4840,7 +4840,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla write_message("noop\n", 5, todo_file, 0)) return -1; - if (autosquash && rearrange_squash(r)) + if (autosquash && rearrange_squash_in_todo_file(r)) return -1; if (commands->nr) @@ -4946,21 +4946,13 @@ define_commit_slab(commit_todo_item, struct todo_item *); * message will have to be retrieved from the commit (as the oneline in the * script cannot be trusted) in order to normalize the autosquash arrangement. */ -int rearrange_squash(struct repository *r) +static int todo_list_rearrange_squash(struct todo_list *todo_list) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; struct hashmap subject2item; - int res = 0, rearranged = 0, *next, *tail, i; + int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0; char **subjects; struct commit_todo_item commit_todo; - - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) - return -1; - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } + struct todo_item *items = NULL; init_commit_todo_item(&commit_todo); /* @@ -4973,13 +4965,13 @@ int rearrange_squash(struct repository *r) * be moved to appear after the i'th. */ hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, - NULL, todo_list.nr); - ALLOC_ARRAY(next, todo_list.nr); - ALLOC_ARRAY(tail, todo_list.nr); - ALLOC_ARRAY(subjects, todo_list.nr); - for (i = 0; i < todo_list.nr; i++) { + NULL, todo_list->nr); + ALLOC_ARRAY(next, todo_list->nr); + ALLOC_ARRAY(tail, todo_list->nr); + ALLOC_ARRAY(subjects, todo_list->nr); + for (i = 0; i < todo_list->nr; i++) { struct strbuf buf = STRBUF_INIT; - struct todo_item *item = todo_list.items + i; + struct todo_item *item = todo_list->items + i; const char *commit_buffer, *subject, *p; size_t subject_len; int i2 = -1; @@ -4992,7 +4984,6 @@ int rearrange_squash(struct repository *r) } if (is_fixup(item->command)) { - todo_list_release(&todo_list); clear_commit_todo_item(&commit_todo); return error(_("the script was already rearranged.")); } @@ -5027,7 +5018,7 @@ int rearrange_squash(struct repository *r) *commit_todo_item_at(&commit_todo, commit2)) /* found by commit name */ i2 = *commit_todo_item_at(&commit_todo, commit2) - - todo_list.items; + - todo_list->items; else { /* copy can be a prefix of the commit subject */ for (i2 = 0; i2 < i; i2++) @@ -5040,7 +5031,7 @@ int rearrange_squash(struct repository *r) } if (i2 >= 0) { rearranged = 1; - todo_list.items[i].command = + todo_list->items[i].command = starts_with(subject, "fixup!") ? TODO_FIXUP : TODO_SQUASH; if (next[i2] < 0) @@ -5058,10 +5049,8 @@ int rearrange_squash(struct repository *r) } if (rearranged) { - struct strbuf buf = STRBUF_INIT; - - for (i = 0; i < todo_list.nr; i++) { - enum todo_command command = todo_list.items[i].command; + for (i = 0; i < todo_list->nr; i++) { + enum todo_command command = todo_list->items[i].command; int cur = i; /* @@ -5072,37 +5061,50 @@ int rearrange_squash(struct repository *r) continue; while (cur >= 0) { - const char *bol = - get_item_line(&todo_list, cur); - const char *eol = - get_item_line(&todo_list, cur + 1); - - /* replace 'pick', by 'fixup' or 'squash' */ - command = todo_list.items[cur].command; - if (is_fixup(command)) { - strbuf_addstr(&buf, - todo_command_info[command].str); - bol += strcspn(bol, " \t"); - } - - strbuf_add(&buf, bol, eol - bol); - + ALLOC_GROW(items, nr + 1, alloc); + items[nr++] = todo_list->items[cur]; cur = next[cur]; } } - res = rewrite_file(todo_file, buf.buf, buf.len); - strbuf_release(&buf); + FREE_AND_NULL(todo_list->items); + todo_list->items = items; + todo_list->nr = nr; + todo_list->alloc = alloc; } free(next); free(tail); - for (i = 0; i < todo_list.nr; i++) + for (i = 0; i < todo_list->nr; i++) free(subjects[i]); free(subjects); hashmap_free(&subject2item, 1); - todo_list_release(&todo_list); clear_commit_todo_item(&commit_todo); - return res; + + return 0; +} + +int rearrange_squash_in_todo_file(struct repository *r) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) + return -1; + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { + todo_list_release(&todo_list); + return -1; + } + + res = todo_list_rearrange_squash(&todo_list); + if (!res) + res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, 0); + + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; } diff --git a/sequencer.h b/sequencer.h index 87d04a3b9b..add50f04f1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -154,7 +154,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, struct string_list *commands, unsigned autosquash); -int rearrange_squash(struct repository *r); +int rearrange_squash_in_todo_file(struct repository *r); extern const char sign_off_header[]; From d358fc286d1da690fb4acea629457faa9010944a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:56 +0100 Subject: [PATCH 09/18] sequencer: make sequencer_make_script() write its script to a strbuf This makes sequencer_make_script() write its script to a strbuf (ie. the buffer of a todo_list) instead of a FILE. This reduce the amount of read/write made by rebase interactive. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 13 ++++++----- sequencer.c | 41 +++++++++++++++-------------------- sequencer.h | 5 ++--- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 60b15f9693..b4190e58e1 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -71,7 +71,8 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, const char *head_hash = NULL; char *revisions = NULL, *shortrevisions = NULL; struct argv_array make_script_args = ARGV_ARRAY_INIT; - FILE *todo_list; + FILE *todo_list_file; + struct todo_list todo_list = TODO_LIST_INIT; if (prepare_branch_to_be_rebased(opts, switch_to)) return -1; @@ -93,8 +94,8 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, if (!upstream && squash_onto) write_file(path_squash_onto(), "%s\n", squash_onto); - todo_list = fopen(rebase_path_todo(), "w"); - if (!todo_list) { + todo_list_file = fopen(rebase_path_todo(), "w"); + if (!todo_list_file) { free(revisions); free(shortrevisions); @@ -105,10 +106,11 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, if (restrict_revision) argv_array_push(&make_script_args, restrict_revision); - ret = sequencer_make_script(the_repository, todo_list, + ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv, flags); - fclose(todo_list); + fputs(todo_list.buf.buf, todo_list_file); + fclose(todo_list_file); if (ret) error(_("could not generate todo list")); @@ -121,6 +123,7 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, free(revisions); free(shortrevisions); + todo_list_release(&todo_list); argv_array_clear(&make_script_args); return ret; diff --git a/sequencer.c b/sequencer.c index 7798f93b23..cf716fa5b8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4215,7 +4215,7 @@ static const char *label_oid(struct object_id *oid, const char *label, } static int make_script_with_merges(struct pretty_print_context *pp, - struct rev_info *revs, FILE *out, + struct rev_info *revs, struct strbuf *out, unsigned flags) { int keep_empty = flags & TODO_LIST_KEEP_EMPTY; @@ -4360,7 +4360,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, * gathering commits not yet shown, reversing the list on the fly, * then outputting that list (labeling revisions as needed). */ - fprintf(out, "%s onto\n", cmd_label); + strbuf_addf(out, "%s onto\n", cmd_label); for (iter = tips; iter; iter = iter->next) { struct commit_list *list = NULL, *iter2; @@ -4370,9 +4370,9 @@ static int make_script_with_merges(struct pretty_print_context *pp, entry = oidmap_get(&state.commit2label, &commit->object.oid); if (entry) - fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string); + strbuf_addf(out, "\n%c Branch %s\n", comment_line_char, entry->string); else - fprintf(out, "\n"); + strbuf_addch(out, '\n'); while (oidset_contains(&interesting, &commit->object.oid) && !oidset_contains(&shown, &commit->object.oid)) { @@ -4385,8 +4385,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, } if (!commit) - fprintf(out, "%s %s\n", cmd_reset, - rebase_cousins ? "onto" : "[new root]"); + strbuf_addf(out, "%s %s\n", cmd_reset, + rebase_cousins ? "onto" : "[new root]"); else { const char *to = NULL; @@ -4399,12 +4399,12 @@ static int make_script_with_merges(struct pretty_print_context *pp, &state); if (!to || !strcmp(to, "onto")) - fprintf(out, "%s onto\n", cmd_reset); + strbuf_addf(out, "%s onto\n", cmd_reset); else { strbuf_reset(&oneline); pretty_print_commit(pp, commit, &oneline); - fprintf(out, "%s %s # %s\n", - cmd_reset, to, oneline.buf); + strbuf_addf(out, "%s %s # %s\n", + cmd_reset, to, oneline.buf); } } @@ -4413,11 +4413,11 @@ static int make_script_with_merges(struct pretty_print_context *pp, entry = oidmap_get(&commit2todo, oid); /* only show if not already upstream */ if (entry) - fprintf(out, "%s\n", entry->string); + strbuf_addf(out, "%s\n", entry->string); entry = oidmap_get(&state.commit2label, oid); if (entry) - fprintf(out, "%s %s\n", - cmd_label, entry->string); + strbuf_addf(out, "%s %s\n", + cmd_label, entry->string); oidset_insert(&shown, oid); } @@ -4439,13 +4439,11 @@ static int make_script_with_merges(struct pretty_print_context *pp, return 0; } -int sequencer_make_script(struct repository *r, FILE *out, - int argc, const char **argv, - unsigned flags) +int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, + const char **argv, unsigned flags) { char *format = NULL; struct pretty_print_context pp = {0}; - struct strbuf buf = STRBUF_INIT; struct rev_info revs; struct commit *commit; int keep_empty = flags & TODO_LIST_KEEP_EMPTY; @@ -4488,16 +4486,13 @@ int sequencer_make_script(struct repository *r, FILE *out, if (!is_empty && (commit->object.flags & PATCHSAME)) continue; - strbuf_reset(&buf); if (!keep_empty && is_empty) - strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, "%s %s ", insn, + strbuf_addf(out, "%c ", comment_line_char); + strbuf_addf(out, "%s %s ", insn, oid_to_hex(&commit->object.oid)); - pretty_print_commit(&pp, commit, &buf); - strbuf_addch(&buf, '\n'); - fputs(buf.buf, out); + pretty_print_commit(&pp, commit, out); + strbuf_addch(out, '\n'); } - strbuf_release(&buf); return 0; } diff --git a/sequencer.h b/sequencer.h index add50f04f1..e25f5151d3 100644 --- a/sequencer.h +++ b/sequencer.h @@ -142,9 +142,8 @@ int sequencer_remove_state(struct replay_opts *opts); #define TODO_LIST_REBASE_COUSINS (1U << 4) #define TODO_LIST_APPEND_TODO_HELP (1U << 5) -int sequencer_make_script(struct repository *r, FILE *out, int argc, - const char **argv, - unsigned flags); +int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, + const char **argv, unsigned flags); int sequencer_add_exec_commands(struct repository *r, struct string_list *commands); From 94bcad797966b6a3490bc6806d3ee3eed54da9d9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:57 +0100 Subject: [PATCH 10/18] sequencer: change complete_action() to use the refactored functions complete_action() used functions that read the todo-list file, made some changes to it, and wrote it back to the disk. The previous commits were dedicated to separate the part that deals with the file from the actual logic of these functions. Now that this is done, we can call directly the "logic" functions to avoid useless file access. The parsing of the list has to be done by the caller. If the buffer of the todo list provided by the caller is empty, a `noop' command is directly added to the todo list, without touching the buffer. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 20 +++------ sequencer.c | 79 +++++++++++++++-------------------- sequencer.h | 2 +- 3 files changed, 41 insertions(+), 60 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index b4190e58e1..ffbe14cef5 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -71,7 +71,6 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, const char *head_hash = NULL; char *revisions = NULL, *shortrevisions = NULL; struct argv_array make_script_args = ARGV_ARRAY_INIT; - FILE *todo_list_file; struct todo_list todo_list = TODO_LIST_INIT; if (prepare_branch_to_be_rebased(opts, switch_to)) @@ -94,14 +93,6 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, if (!upstream && squash_onto) write_file(path_squash_onto(), "%s\n", squash_onto); - todo_list_file = fopen(rebase_path_todo(), "w"); - if (!todo_list_file) { - free(revisions); - free(shortrevisions); - - return error_errno(_("could not open %s"), rebase_path_todo()); - } - argv_array_pushl(&make_script_args, "", revisions, NULL); if (restrict_revision) argv_array_push(&make_script_args, restrict_revision); @@ -109,16 +100,17 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv, flags); - fputs(todo_list.buf.buf, todo_list_file); - fclose(todo_list_file); if (ret) error(_("could not generate todo list")); else { discard_cache(); - ret = complete_action(the_repository, opts, flags, - shortrevisions, onto_name, onto, - head_hash, commands, autosquash); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) + BUG("unusable todo list"); + + ret = complete_action(the_repository, opts, flags, shortrevisions, onto_name, + onto, head_hash, commands, autosquash, &todo_list); } free(revisions); diff --git a/sequencer.c b/sequencer.c index cf716fa5b8..1d5cd2fc27 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4817,93 +4817,82 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output return 0; } +static int todo_list_rearrange_squash(struct todo_list *todo_list); + int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, struct string_list *commands, - unsigned autosquash) + unsigned autosquash, struct todo_list *todo_list) { const char *shortonto, *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf *buf = &(todo_list.buf); + struct todo_list new_todo = TODO_LIST_INIT; + struct strbuf *buf = &todo_list->buf; struct object_id oid; - struct stat st; get_oid(onto, &oid); shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV); - if (!lstat(todo_file, &st) && st.st_size == 0 && - write_message("noop\n", 5, todo_file, 0)) - return -1; + if (buf->len == 0) { + struct todo_item *item = append_new_todo(todo_list); + item->command = TODO_NOOP; + item->commit = NULL; + item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0; + } - if (autosquash && rearrange_squash_in_todo_file(r)) + if (autosquash && todo_list_rearrange_squash(todo_list)) return -1; if (commands->nr) - sequencer_add_exec_commands(r, commands); + todo_list_add_exec_commands(todo_list, commands); - if (strbuf_read_file(buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(r, buf->buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - if (count_commands(&todo_list) == 0) { + if (count_commands(todo_list) == 0) { apply_autostash(opts); sequencer_remove_state(opts); - todo_list_release(&todo_list); return error(_("nothing to do")); } - strbuf_addch(buf, '\n'); - strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)", - "Rebase %s onto %s (%d commands)", - count_commands(&todo_list)), - shortrevisions, shortonto, count_commands(&todo_list)); - append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf); - - if (write_message(buf->buf, buf->len, todo_file, 0)) { - todo_list_release(&todo_list); - return -1; - } + if (todo_list_write_to_file(r, todo_list, todo_file, + shortrevisions, shortonto, -1, + flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) + return error_errno(_("could not write '%s'"), todo_file); if (copy_file(rebase_path_todo_backup(), todo_file, 0666)) return error(_("could not copy '%s' to '%s'."), todo_file, rebase_path_todo_backup()); - if (transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS)) - return error(_("could not transform the todo list")); - - strbuf_reset(buf); - - if (launch_sequence_editor(todo_file, buf, NULL)) { + if (launch_sequence_editor(todo_file, &new_todo.buf, NULL)) { apply_autostash(opts); sequencer_remove_state(opts); - todo_list_release(&todo_list); return -1; } - strbuf_stripspace(buf, 1); - if (buf->len == 0) { + strbuf_stripspace(&new_todo.buf, 1); + if (new_todo.buf.len == 0) { apply_autostash(opts); sequencer_remove_state(opts); - todo_list_release(&todo_list); + todo_list_release(&new_todo); return error(_("nothing to do")); } - todo_list_release(&todo_list); - - if (check_todo_list_from_file(r)) { + if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) || + todo_list_check(todo_list, &new_todo)) { + fprintf(stderr, _(edit_todo_list_advice)); checkout_onto(opts, onto_name, onto, orig_head); + todo_list_release(&new_todo); + return -1; } - if (transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS))) - return error(_("could not transform the todo list")); + if (todo_list_write_to_file(r, &new_todo, todo_file, NULL, NULL, -1, + flags & ~(TODO_LIST_SHORTEN_IDS))) { + todo_list_release(&new_todo); + return error_errno(_("could not write '%s'"), todo_file); + } + + todo_list_release(&new_todo); if (opts->allow_ff && skip_unnecessary_picks(r, &oid)) return error(_("could not skip unnecessary pick commands")); diff --git a/sequencer.h b/sequencer.h index e25f5151d3..7029b39cd6 100644 --- a/sequencer.h +++ b/sequencer.h @@ -152,7 +152,7 @@ int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, struct string_list *commands, - unsigned autosquash); + unsigned autosquash, struct todo_list *todo_list); int rearrange_squash_in_todo_file(struct repository *r); extern const char sign_off_header[]; From 1ba204de6924b171024638bb5f3f405a0e0946d0 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:58 +0100 Subject: [PATCH 11/18] rebase--interactive: move sequencer_add_exec_commands() As sequencer_add_exec_commands() is only needed inside of rebase--interactive.c for `rebase -p', it is moved there from sequencer.c. The parameter r (repository) is dropped along the way. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 27 ++++++++++++++++++++++++++- sequencer.c | 28 ++-------------------------- sequencer.h | 4 ++-- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index ffbe14cef5..3bf1da6940 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -13,6 +13,31 @@ static GIT_PATH_FUNC(path_state_dir, "rebase-merge/") static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto") static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive") +static int add_exec_commands(struct string_list *commands) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_add_exec_commands(&todo_list, commands); + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + static int get_revision_ranges(const char *upstream, const char *onto, const char **head_hash, char **revisions, char **shortrevisions) @@ -266,7 +291,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = rearrange_squash_in_todo_file(the_repository); break; case ADD_EXEC: - ret = sequencer_add_exec_commands(the_repository, &commands); + ret = add_exec_commands(&commands); break; default: BUG("invalid command '%d'", command); diff --git a/sequencer.c b/sequencer.c index 1d5cd2fc27..280d9bcce7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4500,8 +4500,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, * Add commands after pick and (series of) squash/fixup commands * in the todo list. */ -static void todo_list_add_exec_commands(struct todo_list *todo_list, - struct string_list *commands) +void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands) { struct strbuf *buf = &todo_list->buf; size_t base_offset = buf->len; @@ -4567,30 +4567,6 @@ static void todo_list_add_exec_commands(struct todo_list *todo_list, todo_list->alloc = alloc; } -int sequencer_add_exec_commands(struct repository *r, - struct string_list *commands) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - todo_list_add_exec_commands(&todo_list, commands); - res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, 0); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, struct strbuf *buf, int num, unsigned flags) { diff --git a/sequencer.h b/sequencer.h index 7029b39cd6..0c4b7c80f8 100644 --- a/sequencer.h +++ b/sequencer.h @@ -145,9 +145,9 @@ int sequencer_remove_state(struct replay_opts *opts); int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, const char **argv, unsigned flags); -int sequencer_add_exec_commands(struct repository *r, - struct string_list *commands); int transform_todo_file(struct repository *r, unsigned flags); +void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands); int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, From 79d7e883bb69803daf4a6f44692cb8fc0eee5b24 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:17:59 +0100 Subject: [PATCH 12/18] rebase--interactive: move rearrange_squash_in_todo_file() As rearrange_squash_in_todo_file() is only needed inside of rebase--interactive.c for `rebase -p', it is moved there from sequencer.c. The parameter r (repository) is dropped along the way, and the error handling is slightly improved. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 28 +++++++++++++++++++++++++++- sequencer.c | 28 +--------------------------- sequencer.h | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 3bf1da6940..ab2c6fcd99 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -38,6 +38,32 @@ static int add_exec_commands(struct string_list *commands) return 0; } +static int rearrange_squash_in_todo_file(void) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_rearrange_squash(&todo_list); + if (!res) + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + static int get_revision_ranges(const char *upstream, const char *onto, const char **head_hash, char **revisions, char **shortrevisions) @@ -288,7 +314,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: - ret = rearrange_squash_in_todo_file(the_repository); + ret = rearrange_squash_in_todo_file(); break; case ADD_EXEC: ret = add_exec_commands(&commands); diff --git a/sequencer.c b/sequencer.c index 280d9bcce7..ffa4334982 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4793,8 +4793,6 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output return 0; } -static int todo_list_rearrange_squash(struct todo_list *todo_list); - int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, struct string_list *commands, @@ -4906,7 +4904,7 @@ define_commit_slab(commit_todo_item, struct todo_item *); * message will have to be retrieved from the commit (as the oneline in the * script cannot be trusted) in order to normalize the autosquash arrangement. */ -static int todo_list_rearrange_squash(struct todo_list *todo_list) +int todo_list_rearrange_squash(struct todo_list *todo_list) { struct hashmap subject2item; int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0; @@ -5044,27 +5042,3 @@ static int todo_list_rearrange_squash(struct todo_list *todo_list) return 0; } - -int rearrange_squash_in_todo_file(struct repository *r) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res = 0; - - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) - return -1; - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } - - res = todo_list_rearrange_squash(&todo_list); - if (!res) - res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, 0); - - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} diff --git a/sequencer.h b/sequencer.h index 0c4b7c80f8..13c5676c24 100644 --- a/sequencer.h +++ b/sequencer.h @@ -153,7 +153,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, struct string_list *commands, unsigned autosquash, struct todo_list *todo_list); -int rearrange_squash_in_todo_file(struct repository *r); +int todo_list_rearrange_squash(struct todo_list *todo_list); extern const char sign_off_header[]; From 6bfeb7f1b503cf3fa0fd5690c0c98a9813c7c875 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:00 +0100 Subject: [PATCH 13/18] sequencer: refactor skip_unnecessary_picks() to work on a todo_list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This refactors skip_unnecessary_picks() to work on a todo_list. As this function is only called by complete_action() (and thus is not used by rebase -p), the file-handling logic is completely dropped here. Instead of truncating the todo list’s buffer, the items are moved to the beginning of the list, eliminating the need to reparse the list. This also means its buffer cannot be directly written to the disk. rewrite_file() is then removed, as it is now unused. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 82 +++++++++++++---------------------------------------- 1 file changed, 19 insertions(+), 63 deletions(-) diff --git a/sequencer.c b/sequencer.c index ffa4334982..894c7538d5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4700,52 +4700,22 @@ out: return res; } -static int rewrite_file(const char *path, const char *buf, size_t len) -{ - int rc = 0; - int fd = open(path, O_WRONLY | O_TRUNC); - if (fd < 0) - return error_errno(_("could not open '%s' for writing"), path); - if (write_in_full(fd, buf, len) < 0) - rc = error_errno(_("could not write to '%s'"), path); - if (close(fd) && !rc) - rc = error_errno(_("could not close '%s'"), path); - return rc; -} - /* skip picking commits whose parents are unchanged */ -static int skip_unnecessary_picks(struct repository *r, struct object_id *output_oid) +static int skip_unnecessary_picks(struct repository *r, + struct todo_list *todo_list, + struct object_id *base_oid) { - const char *todo_file = rebase_path_todo(); - struct strbuf buf = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; struct object_id *parent_oid; - int fd, i; - - if (!read_oneliner(&buf, rebase_path_onto(), 0)) - return error(_("could not read 'onto'")); - if (get_oid(buf.buf, output_oid)) { - strbuf_release(&buf); - return error(_("need a HEAD to fixup")); - } - strbuf_release(&buf); - - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) - return -1; - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } + int i; - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; + for (i = 0; i < todo_list->nr; i++) { + struct todo_item *item = todo_list->items + i; if (item->command >= TODO_NOOP) continue; if (item->command != TODO_PICK) break; if (parse_commit(item->commit)) { - todo_list_release(&todo_list); return error(_("could not parse commit '%s'"), oid_to_hex(&item->commit->object.oid)); } @@ -4754,42 +4724,26 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output if (item->commit->parents->next) break; /* merge commit */ parent_oid = &item->commit->parents->item->object.oid; - if (!oideq(parent_oid, output_oid)) + if (!oideq(parent_oid, base_oid)) break; - oidcpy(output_oid, &item->commit->object.oid); + oidcpy(base_oid, &item->commit->object.oid); } if (i > 0) { - int offset = get_item_line_offset(&todo_list, i); const char *done_path = rebase_path_done(); - fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - if (fd < 0) { - error_errno(_("could not open '%s' for writing"), - done_path); - todo_list_release(&todo_list); - return -1; - } - if (write_in_full(fd, todo_list.buf.buf, offset) < 0) { + if (todo_list_write_to_file(r, todo_list, done_path, NULL, NULL, i, 0)) { error_errno(_("could not write to '%s'"), done_path); - todo_list_release(&todo_list); - close(fd); return -1; } - close(fd); - if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset, - todo_list.buf.len - offset) < 0) { - todo_list_release(&todo_list); - return -1; - } + MOVE_ARRAY(todo_list->items, todo_list->items + i, todo_list->nr - i); + todo_list->nr -= i; + todo_list->current = 0; - todo_list.current = i; - if (is_fixup(peek_command(&todo_list, 0))) - record_in_rewritten(output_oid, peek_command(&todo_list, 0)); + if (is_fixup(peek_command(todo_list, 0))) + record_in_rewritten(base_oid, peek_command(todo_list, 0)); } - todo_list_release(&todo_list); - return 0; } @@ -4860,6 +4814,11 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return -1; } + if (opts->allow_ff && skip_unnecessary_picks(r, &new_todo, &oid)) { + todo_list_release(&new_todo); + return error(_("could not skip unnecessary pick commands")); + } + if (todo_list_write_to_file(r, &new_todo, todo_file, NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS))) { todo_list_release(&new_todo); @@ -4868,9 +4827,6 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla todo_list_release(&new_todo); - if (opts->allow_ff && skip_unnecessary_picks(r, &oid)) - return error(_("could not skip unnecessary pick commands")); - if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head)) return -1; From ddb81e50724002645d7ec7d9ffdb714d02a47759 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:01 +0100 Subject: [PATCH 14/18] rebase-interactive: use todo_list_write_to_file() in edit_todo_list() Just like complete_action(), edit_todo_list() used a function (transform_todo_file()) that read the todo list from the disk and wrote it back, resulting in useless disk accesses. This changes edit_todo_list() to call directly todo_list_write_to_file() instead. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- rebase-interactive.c | 38 ++++++++++++++++---------------------- sequencer.c | 4 ++-- sequencer.h | 3 --- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/rebase-interactive.c b/rebase-interactive.c index dfa6dd530f..d396ecc599 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -79,39 +79,33 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty, int edit_todo_list(struct repository *r, unsigned flags) { - struct strbuf buf = STRBUF_INIT; const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res = 0; - if (strbuf_read_file(&buf, todo_file, 0) < 0) + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); - strbuf_stripspace(&buf, 1); - if (write_message(buf.buf, buf.len, todo_file, 0)) { - strbuf_release(&buf); + strbuf_stripspace(&todo_list.buf, 1); + todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + if (todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, + flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) { + todo_list_release(&todo_list); return -1; } - strbuf_release(&buf); - - transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS); - - if (strbuf_read_file(&buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - append_todo_help(1, 0, &buf); - if (write_message(buf.buf, buf.len, todo_file, 0)) { - strbuf_release(&buf); + strbuf_reset(&todo_list.buf); + if (launch_sequence_editor(todo_file, &todo_list.buf, NULL)) { + todo_list_release(&todo_list); return -1; } - strbuf_release(&buf); + if (!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) + res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, + flags & ~(TODO_LIST_SHORTEN_IDS)); - if (launch_sequence_editor(todo_file, NULL, NULL)) - return -1; - - transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS)); - - return 0; + todo_list_release(&todo_list); + return res; } define_commit_slab(commit_seen, unsigned char); diff --git a/sequencer.c b/sequencer.c index 894c7538d5..b7289c93d4 100644 --- a/sequencer.c +++ b/sequencer.c @@ -383,8 +383,8 @@ static void print_advice(struct repository *r, int show_hint, } } -int write_message(const void *buf, size_t len, const char *filename, - int append_eol) +static int write_message(const void *buf, size_t len, const char *filename, + int append_eol) { struct lock_file msg_file = LOCK_INIT; diff --git a/sequencer.h b/sequencer.h index 13c5676c24..fb31a30d15 100644 --- a/sequencer.h +++ b/sequencer.h @@ -64,9 +64,6 @@ struct replay_opts { }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } -int write_message(const void *buf, size_t len, const char *filename, - int append_eol); - /* * Note that ordering matters in this enum. Not only must it match the mapping * of todo_command_info (in sequencer.c), it is also divided into several From af1fc3adc5bf0d831ee3c1c8e86c1b7ce59e070e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:02 +0100 Subject: [PATCH 15/18] rebase-interactive: append_todo_help() changes This moves the writing of the comment "Rebase $shortrevisions onto $shortonto ($command_count commands)" from todo_list_write_to_file() to append_todo_help(). shortrevisions, shortonto, and command_count are passed as parameters to append_todo_help(). During the initial edit of the todo list, shortrevisions and shortonto are not NULL. Therefore, if shortrevisions or shortonto is NULL, then edit_todo would be true, otherwise it would be false. Thus, edit_todo is removed from the parameters of append_todo_help(). Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- rebase-interactive.c | 12 +++++++++++- rebase-interactive.h | 3 ++- sequencer.c | 17 ++++------------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/rebase-interactive.c b/rebase-interactive.c index d396ecc599..807f8370db 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -28,7 +28,8 @@ static enum missing_commit_check_level get_missing_commit_check_level(void) return MISSING_COMMIT_CHECK_IGNORE; } -void append_todo_help(unsigned edit_todo, unsigned keep_empty, +void append_todo_help(unsigned keep_empty, int command_count, + const char *shortrevisions, const char *shortonto, struct strbuf *buf) { const char *msg = _("\nCommands:\n" @@ -48,6 +49,15 @@ void append_todo_help(unsigned edit_todo, unsigned keep_empty, ". specified). Use -c to reword the commit message.\n" "\n" "These lines can be re-ordered; they are executed from top to bottom.\n"); + unsigned edit_todo = !(shortrevisions && shortonto); + + if (!edit_todo) { + strbuf_addch(buf, '\n'); + strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)", + "Rebase %s onto %s (%d commands)", + command_count), + shortrevisions, shortonto, command_count); + } strbuf_add_commented_lines(buf, msg, strlen(msg)); diff --git a/rebase-interactive.h b/rebase-interactive.h index 187b5032d6..0e5925e3aa 100644 --- a/rebase-interactive.h +++ b/rebase-interactive.h @@ -5,7 +5,8 @@ struct strbuf; struct repository; struct todo_list; -void append_todo_help(unsigned edit_todo, unsigned keep_empty, +void append_todo_help(unsigned keep_empty, int command_count, + const char *shortrevisions, const char *shortonto, struct strbuf *buf); int edit_todo_list(struct repository *r, unsigned flags); int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo); diff --git a/sequencer.c b/sequencer.c index b7289c93d4..8f3836c479 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4619,22 +4619,13 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, const char *file, const char *shortrevisions, const char *shortonto, int num, unsigned flags) { - int edit_todo = !(shortrevisions && shortonto), res; + int res; struct strbuf buf = STRBUF_INIT; todo_list_to_strbuf(r, todo_list, &buf, num, flags); - - if (flags & TODO_LIST_APPEND_TODO_HELP) { - int command_count = count_commands(todo_list); - if (!edit_todo) { - strbuf_addch(&buf, '\n'); - strbuf_commented_addf(&buf, Q_("Rebase %s onto %s (%d command)", - "Rebase %s onto %s (%d commands)", - command_count), - shortrevisions, shortonto, command_count); - } - append_todo_help(edit_todo, flags & TODO_LIST_KEEP_EMPTY, &buf); - } + if (flags & TODO_LIST_APPEND_TODO_HELP) + append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list), + shortrevisions, shortonto, &buf); res = write_message(buf.buf, buf.len, file, 0); strbuf_release(&buf); From a930eb03a8299b5b29284dd9e3c253c38187167a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:03 +0100 Subject: [PATCH 16/18] rebase-interactive: rewrite edit_todo_list() to handle the initial edit edit_todo_list() is changed to work on a todo_list, and to handle the initial edition of the todo list (ie. making a backup of the todo list). It does not check for dropped commits yet, as todo_list_check() does not take the commits that have already been processed by the rebase (ie. the todo list is edited in the middle of a rebase session). Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 24 +++++++++++++++- rebase-interactive.c | 53 +++++++++++++++++++---------------- rebase-interactive.h | 4 ++- sequencer.c | 3 +- sequencer.h | 1 + 5 files changed, 57 insertions(+), 28 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index ab2c6fcd99..b277239f21 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -64,6 +64,28 @@ static int rearrange_squash_in_todo_file(void) return 0; } +static int edit_todo_file(unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT, + new_todo = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + strbuf_stripspace(&todo_list.buf, 1); + res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags); + if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file, + NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS))) + res = error_errno(_("could not write '%s'"), todo_file); + + todo_list_release(&todo_list); + todo_list_release(&new_todo); + + return res; +} + static int get_revision_ranges(const char *upstream, const char *onto, const char **head_hash, char **revisions, char **shortrevisions) @@ -295,7 +317,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) break; } case EDIT_TODO: - ret = edit_todo_list(the_repository, flags); + ret = edit_todo_file(flags); break; case SHOW_CURRENT_PATCH: { struct child_process cmd = CHILD_PROCESS_INIT; diff --git a/rebase-interactive.c b/rebase-interactive.c index 807f8370db..aa18ae82b7 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -87,35 +87,40 @@ void append_todo_help(unsigned keep_empty, int command_count, } } -int edit_todo_list(struct repository *r, unsigned flags) +int edit_todo_list(struct repository *r, struct todo_list *todo_list, + struct todo_list *new_todo, const char *shortrevisions, + const char *shortonto, unsigned flags) { const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res = 0; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - strbuf_stripspace(&todo_list.buf, 1); - todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - if (todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, - flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) { - todo_list_release(&todo_list); - return -1; - } + unsigned initial = shortrevisions && shortonto; - strbuf_reset(&todo_list.buf); - if (launch_sequence_editor(todo_file, &todo_list.buf, NULL)) { - todo_list_release(&todo_list); - return -1; - } + /* If the user is editing the todo list, we first try to parse + * it. If there is an error, we do not return, because the user + * might want to fix it in the first place. */ + if (!initial) + todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list); - if (!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) - res = todo_list_write_to_file(r, &todo_list, todo_file, NULL, NULL, -1, - flags & ~(TODO_LIST_SHORTEN_IDS)); + if (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto, + -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) + return error_errno(_("could not write '%s'"), todo_file); - todo_list_release(&todo_list); - return res; + if (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666)) + return error(_("could not copy '%s' to '%s'."), todo_file, + rebase_path_todo_backup()); + + if (launch_sequence_editor(todo_file, &new_todo->buf, NULL)) + return -2; + + strbuf_stripspace(&new_todo->buf, 1); + if (initial && new_todo->buf.len == 0) + return -3; + + /* For the initial edit, the todo list gets parsed in + * complete_action(). */ + if (!initial) + return todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo); + + return 0; } define_commit_slab(commit_seen, unsigned char); diff --git a/rebase-interactive.h b/rebase-interactive.h index 0e5925e3aa..44dbb06311 100644 --- a/rebase-interactive.h +++ b/rebase-interactive.h @@ -8,7 +8,9 @@ struct todo_list; void append_todo_help(unsigned keep_empty, int command_count, const char *shortrevisions, const char *shortonto, struct strbuf *buf); -int edit_todo_list(struct repository *r, unsigned flags); +int edit_todo_list(struct repository *r, struct todo_list *todo_list, + struct todo_list *new_todo, const char *shortrevisions, + const char *shortonto, unsigned flags); int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo); #endif diff --git a/sequencer.c b/sequencer.c index 8f3836c479..7d46f76a8b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -55,8 +55,7 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") -static GIT_PATH_FUNC(rebase_path_todo_backup, - "rebase-merge/git-rebase-todo.backup") +GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup") /* * The rebase command lines that have already been processed. A line diff --git a/sequencer.h b/sequencer.h index fb31a30d15..195891a267 100644 --- a/sequencer.h +++ b/sequencer.h @@ -10,6 +10,7 @@ struct repository; const char *git_path_commit_editmsg(void); const char *git_path_seq_dir(void); const char *rebase_path_todo(void); +const char *rebase_path_todo_backup(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) From 1451d0f616fb1a046349cc8cd94736bcfde5e4a5 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:04 +0100 Subject: [PATCH 17/18] sequencer: use edit_todo_list() in complete_action() This changes complete_action() to use edit_todo_list(), now that it can handle the initial edit of the todo list. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7d46f76a8b..c56c3add1a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4746,6 +4746,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla struct todo_list new_todo = TODO_LIST_INIT; struct strbuf *buf = &todo_list->buf; struct object_id oid; + int res; get_oid(onto, &oid); shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV); @@ -4770,24 +4771,16 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return error(_("nothing to do")); } - if (todo_list_write_to_file(r, todo_list, todo_file, - shortrevisions, shortonto, -1, - flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) - return error_errno(_("could not write '%s'"), todo_file); - - if (copy_file(rebase_path_todo_backup(), todo_file, 0666)) - return error(_("could not copy '%s' to '%s'."), todo_file, - rebase_path_todo_backup()); - - if (launch_sequence_editor(todo_file, &new_todo.buf, NULL)) { + res = edit_todo_list(r, todo_list, &new_todo, shortrevisions, + shortonto, flags); + if (res == -1) + return -1; + else if (res == -2) { apply_autostash(opts); sequencer_remove_state(opts); return -1; - } - - strbuf_stripspace(&new_todo.buf, 1); - if (new_todo.buf.len == 0) { + } else if (res == -3) { apply_autostash(opts); sequencer_remove_state(opts); todo_list_release(&new_todo); From ed35d18841130fee0109cbe40b659275a3f78cc9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 5 Mar 2019 20:18:05 +0100 Subject: [PATCH 18/18] rebase--interactive: move transform_todo_file() As transform_todo_file() is only needed inside of rebase--interactive.c for `rebase -p', it is moved there from sequencer.c. The parameter r (repository) is dropped along the way. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 26 +++++++++++++++++++++++++- sequencer.c | 23 ----------------------- sequencer.h | 1 - 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index b277239f21..4d9c1e62bb 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -64,6 +64,30 @@ static int rearrange_squash_in_todo_file(void) return 0; } +static int transform_todo_file(unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_write_to_file(the_repository, &todo_list, todo_file, + NULL, NULL, -1, flags); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + static int edit_todo_file(unsigned flags) { const char *todo_file = rebase_path_todo(); @@ -330,7 +354,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) } case SHORTEN_OIDS: case EXPAND_OIDS: - ret = transform_todo_file(the_repository, flags); + ret = transform_todo_file(flags); break; case CHECK_TODO_LIST: ret = check_todo_list_from_file(the_repository); diff --git a/sequencer.c b/sequencer.c index c56c3add1a..2a0fcb1cce 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4632,29 +4632,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, return res; } -int transform_todo_file(struct repository *r, unsigned flags) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int res; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - res = todo_list_write_to_file(r, &todo_list, todo_file, - NULL, NULL, -1, flags); - todo_list_release(&todo_list); - - if (res) - return error_errno(_("could not write '%s'."), todo_file); - return 0; -} - static const char edit_todo_list_advice[] = N_("You can fix this with 'git rebase --edit-todo' " "and then run 'git rebase --continue'.\n" diff --git a/sequencer.h b/sequencer.h index 195891a267..7cca49eff2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -143,7 +143,6 @@ int sequencer_remove_state(struct replay_opts *opts); int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, const char **argv, unsigned flags); -int transform_todo_file(struct repository *r, unsigned flags); void todo_list_add_exec_commands(struct todo_list *todo_list, struct string_list *commands); int check_todo_list_from_file(struct repository *r);