From 957a0fe2e59343f10f10620fab787f7e5abf3ab8 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 5 Aug 2016 18:00:26 -0400 Subject: [PATCH 01/90] status: rename long-format print routines Rename the various wt_status_print*() routines to be wt_longstatus_print*() to make it clear that these routines are only concerned with the normal/long status output and reduce developer confusion as other status formats are added in the future. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- builtin/commit.c | 4 +- wt-status.c | 110 +++++++++++++++++++++++------------------------ wt-status.h | 2 +- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 1f6dbcd0d0..b80273b539 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -515,7 +515,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int break; case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: - wt_status_print(s); + wt_longstatus_print(s); break; } @@ -1403,7 +1403,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_LONG: s.verbose = verbose; s.ignore_submodule_arg = ignore_submodule_arg; - wt_status_print(&s); + wt_longstatus_print(&s); break; } return 0; diff --git a/wt-status.c b/wt-status.c index 19cbc3939c..961bbc7a21 100644 --- a/wt-status.c +++ b/wt-status.c @@ -139,7 +139,7 @@ void wt_status_prepare(struct wt_status *s) s->display_comment_prefix = 0; } -static void wt_status_print_unmerged_header(struct wt_status *s) +static void wt_longstatus_print_unmerged_header(struct wt_status *s) { int i; int del_mod_conflict = 0; @@ -191,7 +191,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s) status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_cached_header(struct wt_status *s) +static void wt_longstatus_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); @@ -207,9 +207,9 @@ static void wt_status_print_cached_header(struct wt_status *s) status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_dirty_header(struct wt_status *s, - int has_deleted, - int has_dirty_submodules) +static void wt_longstatus_print_dirty_header(struct wt_status *s, + int has_deleted, + int has_dirty_submodules) { const char *c = color(WT_STATUS_HEADER, s); @@ -226,9 +226,9 @@ static void wt_status_print_dirty_header(struct wt_status *s, status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_other_header(struct wt_status *s, - const char *what, - const char *how) +static void wt_longstatus_print_other_header(struct wt_status *s, + const char *what, + const char *how) { const char *c = color(WT_STATUS_HEADER, s); status_printf_ln(s, c, "%s:", what); @@ -238,7 +238,7 @@ static void wt_status_print_other_header(struct wt_status *s, status_printf_ln(s, c, "%s", ""); } -static void wt_status_print_trailer(struct wt_status *s) +static void wt_longstatus_print_trailer(struct wt_status *s) { status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } @@ -304,8 +304,8 @@ static int maxwidth(const char *(*label)(int), int minval, int maxval) return result; } -static void wt_status_print_unmerged_data(struct wt_status *s, - struct string_list_item *it) +static void wt_longstatus_print_unmerged_data(struct wt_status *s, + struct string_list_item *it) { const char *c = color(WT_STATUS_UNMERGED, s); struct wt_status_change_data *d = it->util; @@ -331,9 +331,9 @@ static void wt_status_print_unmerged_data(struct wt_status *s, strbuf_release(&onebuf); } -static void wt_status_print_change_data(struct wt_status *s, - int change_type, - struct string_list_item *it) +static void wt_longstatus_print_change_data(struct wt_status *s, + int change_type, + struct string_list_item *it) { struct wt_status_change_data *d = it->util; const char *c = color(change_type, s); @@ -378,7 +378,7 @@ static void wt_status_print_change_data(struct wt_status *s, status = d->worktree_status; break; default: - die("BUG: unhandled change_type %d in wt_status_print_change_data", + die("BUG: unhandled change_type %d in wt_longstatus_print_change_data", change_type); } @@ -627,7 +627,7 @@ void wt_status_collect(struct wt_status *s) wt_status_collect_untracked(s); } -static void wt_status_print_unmerged(struct wt_status *s) +static void wt_longstatus_print_unmerged(struct wt_status *s) { int shown_header = 0; int i; @@ -640,17 +640,17 @@ static void wt_status_print_unmerged(struct wt_status *s) if (!d->stagemask) continue; if (!shown_header) { - wt_status_print_unmerged_header(s); + wt_longstatus_print_unmerged_header(s); shown_header = 1; } - wt_status_print_unmerged_data(s, it); + wt_longstatus_print_unmerged_data(s, it); } if (shown_header) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } -static void wt_status_print_updated(struct wt_status *s) +static void wt_longstatus_print_updated(struct wt_status *s) { int shown_header = 0; int i; @@ -664,14 +664,14 @@ static void wt_status_print_updated(struct wt_status *s) d->index_status == DIFF_STATUS_UNMERGED) continue; if (!shown_header) { - wt_status_print_cached_header(s); + wt_longstatus_print_cached_header(s); s->commitable = 1; shown_header = 1; } - wt_status_print_change_data(s, WT_STATUS_UPDATED, it); + wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it); } if (shown_header) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } /* @@ -703,7 +703,7 @@ static int wt_status_check_worktree_changes(struct wt_status *s, return changes; } -static void wt_status_print_changed(struct wt_status *s) +static void wt_longstatus_print_changed(struct wt_status *s) { int i, dirty_submodules; int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules); @@ -711,7 +711,7 @@ static void wt_status_print_changed(struct wt_status *s) if (!worktree_changes) return; - wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules); + wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules); for (i = 0; i < s->change.nr; i++) { struct wt_status_change_data *d; @@ -721,12 +721,12 @@ static void wt_status_print_changed(struct wt_status *s) if (!d->worktree_status || d->worktree_status == DIFF_STATUS_UNMERGED) continue; - wt_status_print_change_data(s, WT_STATUS_CHANGED, it); + wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } -static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted) +static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted) { struct child_process sm_summary = CHILD_PROCESS_INIT; struct strbuf cmd_stdout = STRBUF_INIT; @@ -772,10 +772,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt strbuf_release(&summary); } -static void wt_status_print_other(struct wt_status *s, - struct string_list *l, - const char *what, - const char *how) +static void wt_longstatus_print_other(struct wt_status *s, + struct string_list *l, + const char *what, + const char *how) { int i; struct strbuf buf = STRBUF_INIT; @@ -785,7 +785,7 @@ static void wt_status_print_other(struct wt_status *s, if (!l->nr) return; - wt_status_print_other_header(s, what, how); + wt_longstatus_print_other_header(s, what, how); for (i = 0; i < l->nr; i++) { struct string_list_item *it; @@ -845,7 +845,7 @@ void wt_status_add_cut_line(FILE *fp) strbuf_release(&buf); } -static void wt_status_print_verbose(struct wt_status *s) +static void wt_longstatus_print_verbose(struct wt_status *s) { struct rev_info rev; struct setup_revision_opt opt; @@ -878,7 +878,7 @@ static void wt_status_print_verbose(struct wt_status *s) if (s->verbose > 1 && s->commitable) { /* print_updated() printed a header, so do we */ if (s->fp != stdout) - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); status_printf_ln(s, c, _("Changes to be committed:")); rev.diffopt.a_prefix = "c/"; rev.diffopt.b_prefix = "i/"; @@ -896,7 +896,7 @@ static void wt_status_print_verbose(struct wt_status *s) } } -static void wt_status_print_tracking(struct wt_status *s) +static void wt_longstatus_print_tracking(struct wt_status *s) { struct strbuf sb = STRBUF_INIT; const char *cp, *ep, *branch_name; @@ -959,7 +959,7 @@ static void show_merge_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git commit\" to conclude merge)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_am_in_progress(struct wt_status *s, @@ -980,7 +980,7 @@ static void show_am_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git am --abort\" to restore the original branch)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static char *read_line_from_git_path(const char *filename) @@ -1204,7 +1204,7 @@ static void show_rebase_in_progress(struct wt_status *s, _(" (use \"git rebase --continue\" once you are satisfied with your changes)")); } } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_cherry_pick_in_progress(struct wt_status *s, @@ -1223,7 +1223,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_revert_in_progress(struct wt_status *s, @@ -1242,7 +1242,7 @@ static void show_revert_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (use \"git revert --abort\" to cancel the revert operation)")); } - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } static void show_bisect_in_progress(struct wt_status *s, @@ -1259,7 +1259,7 @@ static void show_bisect_in_progress(struct wt_status *s, if (s->hints) status_printf_ln(s, color, _(" (use \"git bisect reset\" to get back to the original branch)")); - wt_status_print_trailer(s); + wt_longstatus_print_trailer(s); } /* @@ -1429,8 +1429,8 @@ void wt_status_get_state(struct wt_status_state *state, wt_status_get_detached_from(state); } -static void wt_status_print_state(struct wt_status *s, - struct wt_status_state *state) +static void wt_longstatus_print_state(struct wt_status *s, + struct wt_status_state *state) { const char *state_color = color(WT_STATUS_HEADER, s); if (state->merge_in_progress) @@ -1447,7 +1447,7 @@ static void wt_status_print_state(struct wt_status *s, show_bisect_in_progress(s, state, state_color); } -void wt_status_print(struct wt_status *s) +void wt_longstatus_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); const char *branch_status_color = color(WT_STATUS_HEADER, s); @@ -1484,10 +1484,10 @@ void wt_status_print(struct wt_status *s) status_printf_more(s, branch_status_color, "%s", on_what); status_printf_more(s, branch_color, "%s\n", branch_name); if (!s->is_initial) - wt_status_print_tracking(s); + wt_longstatus_print_tracking(s); } - wt_status_print_state(s, &state); + wt_longstatus_print_state(s, &state); free(state.branch); free(state.onto); free(state.detached_from); @@ -1498,19 +1498,19 @@ void wt_status_print(struct wt_status *s) status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } - wt_status_print_updated(s); - wt_status_print_unmerged(s); - wt_status_print_changed(s); + wt_longstatus_print_updated(s); + wt_longstatus_print_unmerged(s); + wt_longstatus_print_changed(s); if (s->submodule_summary && (!s->ignore_submodule_arg || strcmp(s->ignore_submodule_arg, "all"))) { - wt_status_print_submodule_summary(s, 0); /* staged */ - wt_status_print_submodule_summary(s, 1); /* unstaged */ + wt_longstatus_print_submodule_summary(s, 0); /* staged */ + wt_longstatus_print_submodule_summary(s, 1); /* unstaged */ } if (s->show_untracked_files) { - wt_status_print_other(s, &s->untracked, _("Untracked files"), "add"); + wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add"); if (s->show_ignored_files) - wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f"); + wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f"); if (advice_status_u_option && 2000 < s->untracked_in_ms) { status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); status_printf_ln(s, GIT_COLOR_NORMAL, @@ -1525,7 +1525,7 @@ void wt_status_print(struct wt_status *s) ? _(" (use -u option to show untracked files)") : ""); if (s->verbose) - wt_status_print_verbose(s); + wt_longstatus_print_verbose(s); if (!s->commitable) { if (s->amend) status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes")); diff --git a/wt-status.h b/wt-status.h index 2ca93f6957..2023a3cef6 100644 --- a/wt-status.h +++ b/wt-status.h @@ -99,7 +99,6 @@ struct wt_status_state { void wt_status_truncate_message_at_cut_line(struct strbuf *); void wt_status_add_cut_line(FILE *fp); void wt_status_prepare(struct wt_status *s); -void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); void wt_status_get_state(struct wt_status_state *state, int get_detached_from); int wt_status_check_rebase(const struct worktree *wt, @@ -107,6 +106,7 @@ int wt_status_check_rebase(const struct worktree *wt, int wt_status_check_bisect(const struct worktree *wt, struct wt_status_state *state); +void wt_longstatus_print(struct wt_status *s); void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); From be7e795efe35d3e50199af5452b218e5121b1713 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 5 Aug 2016 18:00:27 -0400 Subject: [PATCH 02/90] status: cleanup API to wt_status_print Refactor the API between builtin/commit.c and wt-status.[ch]. Hide the details of the various wt_*status_print() routines inside wt-status.c behind a single (new) wt_status_print() routine. Eliminate the switch statements from builtin/commit.c. Allow details of new status formats to be isolated within wt-status.c Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- builtin/commit.c | 51 +++++++++--------------------------------------- wt-status.c | 25 +++++++++++++++++++++--- wt-status.h | 16 +++++++++++---- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index b80273b539..a792deb9bd 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -142,14 +142,7 @@ static int show_ignored_in_status, have_option_m; static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; -static enum status_format { - STATUS_FORMAT_NONE = 0, - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, - - STATUS_FORMAT_UNSPECIFIED -} status_format = STATUS_FORMAT_UNSPECIFIED; +static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -500,24 +493,11 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->fp = fp; s->nowarn = nowarn; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + s->status_format = status_format; + s->ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(s); - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - wt_longstatus_print(s); - break; - } + wt_status_print(s); return s->commitable; } @@ -1099,7 +1079,7 @@ static const char *read_commit_message(const char *name) * is not in effect here. */ static struct status_deferred_config { - enum status_format status_format; + enum wt_status_format status_format; int show_branch; } status_deferred_config = { STATUS_FORMAT_UNSPECIFIED, @@ -1381,6 +1361,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.ignore_submodule_arg = ignore_submodule_arg; + s.status_format = status_format; + s.verbose = verbose; + wt_status_collect(&s); if (0 <= fd) @@ -1389,23 +1372,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (s.relative_paths) s.prefix = prefix; - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s); - break; - case STATUS_FORMAT_UNSPECIFIED: - die("BUG: finalize_deferred_config() should have been called"); - break; - case STATUS_FORMAT_NONE: - case STATUS_FORMAT_LONG: - s.verbose = verbose; - s.ignore_submodule_arg = ignore_submodule_arg; - wt_longstatus_print(&s); - break; - } + wt_status_print(&s); return 0; } diff --git a/wt-status.c b/wt-status.c index 961bbc7a21..e0dda24ae6 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1447,7 +1447,7 @@ static void wt_longstatus_print_state(struct wt_status *s, show_bisect_in_progress(s, state, state_color); } -void wt_longstatus_print(struct wt_status *s) +static void wt_longstatus_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); const char *branch_status_color = color(WT_STATUS_HEADER, s); @@ -1714,7 +1714,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) fputc(s->null_termination ? '\0' : '\n', s->fp); } -void wt_shortstatus_print(struct wt_status *s) +static void wt_shortstatus_print(struct wt_status *s) { int i; @@ -1746,7 +1746,7 @@ void wt_shortstatus_print(struct wt_status *s) } } -void wt_porcelain_print(struct wt_status *s) +static void wt_porcelain_print(struct wt_status *s) { s->use_color = 0; s->relative_paths = 0; @@ -1754,3 +1754,22 @@ void wt_porcelain_print(struct wt_status *s) s->no_gettext = 1; wt_shortstatus_print(s); } + +void wt_status_print(struct wt_status *s) +{ + switch (s->status_format) { + case STATUS_FORMAT_SHORT: + wt_shortstatus_print(s); + break; + case STATUS_FORMAT_PORCELAIN: + wt_porcelain_print(s); + break; + case STATUS_FORMAT_UNSPECIFIED: + die("BUG: finalize_deferred_config() should have been called"); + break; + case STATUS_FORMAT_NONE: + case STATUS_FORMAT_LONG: + wt_longstatus_print(s); + break; + } +} diff --git a/wt-status.h b/wt-status.h index 2023a3cef6..9389076f69 100644 --- a/wt-status.h +++ b/wt-status.h @@ -43,6 +43,15 @@ struct wt_status_change_data { unsigned new_submodule_commits : 1; }; +enum wt_status_format { + STATUS_FORMAT_NONE = 0, + STATUS_FORMAT_LONG, + STATUS_FORMAT_SHORT, + STATUS_FORMAT_PORCELAIN, + + STATUS_FORMAT_UNSPECIFIED +}; + struct wt_status { int is_initial; char *branch; @@ -66,6 +75,8 @@ struct wt_status { int show_branch; int hints; + enum wt_status_format status_format; + /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; @@ -99,6 +110,7 @@ struct wt_status_state { void wt_status_truncate_message_at_cut_line(struct strbuf *); void wt_status_add_cut_line(FILE *fp); void wt_status_prepare(struct wt_status *s); +void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); void wt_status_get_state(struct wt_status_state *state, int get_detached_from); int wt_status_check_rebase(const struct worktree *wt, @@ -106,10 +118,6 @@ int wt_status_check_rebase(const struct worktree *wt, int wt_status_check_bisect(const struct worktree *wt, struct wt_status_state *state); -void wt_longstatus_print(struct wt_status *s); -void wt_shortstatus_print(struct wt_status *s); -void wt_porcelain_print(struct wt_status *s); - __attribute__((format (printf, 3, 4))) void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) From c4f596b98ed305ceb594857fa34524fedc30a031 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 5 Aug 2016 18:00:28 -0400 Subject: [PATCH 03/90] status: support --porcelain[=] Update --porcelain argument to take optional version parameter to allow multiple porcelain formats to be supported in the future. The token "v1" is the default value and indicates the traditional porcelain format. (The token "1" is an alias for that.) Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 7 +++++-- builtin/commit.c | 21 ++++++++++++++++++--- t/t7060-wtstatus.sh | 21 +++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e1e8f57cdd..6b1454bd42 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -32,11 +32,14 @@ OPTIONS --branch:: Show the branch and tracking info even in short-format. ---porcelain:: +--porcelain[=]:: Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable across Git versions and regardless of user configuration. See below for details. ++ +The version parameter is used to specify the format version. +This is optional and defaults to the original version 'v1' format. --long:: Give the output in the long-format. This is the default. @@ -96,7 +99,7 @@ configuration variable documented in linkgit:git-config[1]. -z:: Terminate entries with NUL, instead of LF. This implies - the `--porcelain` output format if no other format is given. + the `--porcelain=v1` output format if no other format is given. --column[=]:: --no-column:: diff --git a/builtin/commit.c b/builtin/commit.c index a792deb9bd..185ac35d02 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -144,6 +144,21 @@ static struct strbuf message = STRBUF_INIT; static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) +{ + enum wt_status_format *value = (enum wt_status_format *)opt->value; + if (unset) + *value = STATUS_FORMAT_NONE; + else if (!arg) + *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v1") || !strcmp(arg, "1")) + *value = STATUS_FORMAT_PORCELAIN; + else + die("unsupported porcelain version '%s'", arg); + + return 0; +} + static int opt_parse_m(const struct option *opt, const char *arg, int unset) { struct strbuf *buf = opt->value; @@ -1316,9 +1331,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL('b', "branch", &s.show_branch, N_("show branch information")), - OPT_SET_INT(0, "porcelain", &status_format, - N_("machine-readable output"), - STATUS_FORMAT_PORCELAIN), + { OPTION_CALLBACK, 0, "porcelain", &status_format, + N_("version"), N_("machine-readable output"), + PARSE_OPT_OPTARG, opt_parse_porcelain }, OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 44bf1d84af..00e0cebff3 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -228,4 +228,25 @@ test_expect_success 'status --branch with detached HEAD' ' test_i18ncmp expected actual ' +## Duplicate the above test and verify --porcelain=v1 arg parsing. +test_expect_success 'status --porcelain=v1 --branch with detached HEAD' ' + git reset --hard && + git checkout master^0 && + git status --branch --porcelain=v1 >actual && + cat >expected <<-EOF && + ## HEAD (no branch) + ?? .gitconfig + ?? actual + ?? expect + ?? expected + ?? mdconflict/ + EOF + test_i18ncmp expected actual +' + +## Verify parser error on invalid --porcelain argument. +test_expect_success 'status --porcelain=bogus' ' + test_must_fail git status --porcelain=bogus +' + test_done From 1ecdecce621009a4d039d061d514056501d0ed8f Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 11 Aug 2016 10:45:57 -0400 Subject: [PATCH 04/90] status: collect per-file data for --porcelain=v2 Collect extra per-file data for porcelain V2 format. The output of `git status --porcelain` leaves out many details about the current status that clients might like to have. This can force them to be less efficient as they may need to launch secondary commands (and try to match the logic within git) to accumulate this extra information. For example, a GUI IDE might want the file mode to display the correct icon for a changed item (without having to stat it afterwards). Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- builtin/commit.c | 3 +++ wt-status.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++-- wt-status.h | 4 +++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 185ac35d02..3d222d3970 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -153,6 +153,8 @@ static int opt_parse_porcelain(const struct option *opt, const char *arg, int un *value = STATUS_FORMAT_PORCELAIN; else if (!strcmp(arg, "v1") || !strcmp(arg, "1")) *value = STATUS_FORMAT_PORCELAIN; + else if (!strcmp(arg, "v2") || !strcmp(arg, "2")) + *value = STATUS_FORMAT_PORCELAIN_V2; else die("unsupported porcelain version '%s'", arg); @@ -1104,6 +1106,7 @@ static struct status_deferred_config { static void finalize_deferred_config(struct wt_status *s) { int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN && + status_format != STATUS_FORMAT_PORCELAIN_V2 && !s->null_termination); if (s->null_termination) { diff --git a/wt-status.c b/wt-status.c index e0dda24ae6..dd0a1ab264 100644 --- a/wt-status.c +++ b/wt-status.c @@ -434,6 +434,31 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, if (S_ISGITLINK(p->two->mode)) d->new_submodule_commits = !!oidcmp(&p->one->oid, &p->two->oid); + + switch (p->status) { + case DIFF_STATUS_ADDED: + die("BUG: worktree status add???"); + break; + + case DIFF_STATUS_DELETED: + d->mode_index = p->one->mode; + oidcpy(&d->oid_index, &p->one->oid); + /* mode_worktree is zero for a delete. */ + break; + + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + case DIFF_STATUS_UNMERGED: + d->mode_index = p->one->mode; + d->mode_worktree = p->two->mode; + oidcpy(&d->oid_index, &p->one->oid); + break; + + case DIFF_STATUS_UNKNOWN: + die("BUG: worktree status unknown???"); + break; + } + } } @@ -479,12 +504,36 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, if (!d->index_status) d->index_status = p->status; switch (p->status) { + case DIFF_STATUS_ADDED: + /* Leave {mode,oid}_head zero for an add. */ + d->mode_index = p->two->mode; + oidcpy(&d->oid_index, &p->two->oid); + break; + case DIFF_STATUS_DELETED: + d->mode_head = p->one->mode; + oidcpy(&d->oid_head, &p->one->oid); + /* Leave {mode,oid}_index zero for a delete. */ + break; + case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: d->head_path = xstrdup(p->one->path); + d->score = p->score * 100 / MAX_SCORE; + /* fallthru */ + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + d->mode_head = p->one->mode; + d->mode_index = p->two->mode; + oidcpy(&d->oid_head, &p->one->oid); + oidcpy(&d->oid_index, &p->two->oid); break; case DIFF_STATUS_UNMERGED: d->stagemask = unmerged_mask(p->two->path); + /* + * Don't bother setting {mode,oid}_{head,index} since the print + * code will output the stage values directly and not use the + * values in these fields. + */ break; } } @@ -565,9 +614,17 @@ static void wt_status_collect_changes_initial(struct wt_status *s) if (ce_stage(ce)) { d->index_status = DIFF_STATUS_UNMERGED; d->stagemask |= (1 << (ce_stage(ce) - 1)); - } - else + /* + * Don't bother setting {mode,oid}_{head,index} since the print + * code will output the stage values directly and not use the + * values in these fields. + */ + } else { d->index_status = DIFF_STATUS_ADDED; + /* Leave {mode,oid}_head zero for adds. */ + d->mode_index = ce->ce_mode; + hashcpy(d->oid_index.hash, ce->sha1); + } } } @@ -1764,6 +1821,9 @@ void wt_status_print(struct wt_status *s) case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); break; + case STATUS_FORMAT_PORCELAIN_V2: + /* TODO */ + break; case STATUS_FORMAT_UNSPECIFIED: die("BUG: finalize_deferred_config() should have been called"); break; diff --git a/wt-status.h b/wt-status.h index 9389076f69..43fd3fccf6 100644 --- a/wt-status.h +++ b/wt-status.h @@ -38,6 +38,9 @@ struct wt_status_change_data { int worktree_status; int index_status; int stagemask; + int score; + int mode_head, mode_index, mode_worktree; + struct object_id oid_head, oid_index; char *head_path; unsigned dirty_submodule : 2; unsigned new_submodule_commits : 1; @@ -48,6 +51,7 @@ enum wt_status_format { STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, STATUS_FORMAT_PORCELAIN, + STATUS_FORMAT_PORCELAIN_V2, STATUS_FORMAT_UNSPECIFIED }; From 24959bad5dba353497323f10b3bf39f243c0f174 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 11 Aug 2016 10:45:58 -0400 Subject: [PATCH 05/90] status: print per-file porcelain v2 status data Print per-file information in porcelain v2 format. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- wt-status.c | 285 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 284 insertions(+), 1 deletion(-) diff --git a/wt-status.c b/wt-status.c index dd0a1ab264..bd9075b7ee 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1812,6 +1812,289 @@ static void wt_porcelain_print(struct wt_status *s) wt_shortstatus_print(s); } +/* + * Convert various submodule status values into a + * fixed-length string of characters in the buffer provided. + */ +static void wt_porcelain_v2_submodule_state( + struct wt_status_change_data *d, + char sub[5]) +{ + if (S_ISGITLINK(d->mode_head) || + S_ISGITLINK(d->mode_index) || + S_ISGITLINK(d->mode_worktree)) { + sub[0] = 'S'; + sub[1] = d->new_submodule_commits ? 'C' : '.'; + sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.'; + sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.'; + } else { + sub[0] = 'N'; + sub[1] = '.'; + sub[2] = '.'; + sub[3] = '.'; + } + sub[4] = 0; +} + +/* + * Fix-up changed entries before we print them. + */ +static void wt_porcelain_v2_fix_up_changed( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + + if (!d->index_status) { + /* + * This entry is unchanged in the index (relative to the head). + * Therefore, the collect_updated_cb was never called for this + * entry (during the head-vs-index scan) and so the head column + * fields were never set. + * + * We must have data for the index column (from the + * index-vs-worktree scan (otherwise, this entry should not be + * in the list of changes)). + * + * Copy index column fields to the head column, so that our + * output looks complete. + */ + assert(d->mode_head == 0); + d->mode_head = d->mode_index; + oidcpy(&d->oid_head, &d->oid_index); + } + + if (!d->worktree_status) { + /* + * This entry is unchanged in the worktree (relative to the index). + * Therefore, the collect_changed_cb was never called for this entry + * (during the index-vs-worktree scan) and so the worktree column + * fields were never set. + * + * We must have data for the index column (from the head-vs-index + * scan). + * + * Copy the index column fields to the worktree column so that + * our output looks complete. + * + * Note that we only have a mode field in the worktree column + * because the scan code tries really hard to not have to compute it. + */ + assert(d->mode_worktree == 0); + d->mode_worktree = d->mode_index; + } +} + +/* + * Print porcelain v2 info for tracked entries with changes. + */ +static void wt_porcelain_v2_print_changed_entry( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + struct strbuf buf_index = STRBUF_INIT; + struct strbuf buf_head = STRBUF_INIT; + const char *path_index = NULL; + const char *path_head = NULL; + char key[3]; + char submodule_token[5]; + char sep_char, eol_char; + + wt_porcelain_v2_fix_up_changed(it, s); + wt_porcelain_v2_submodule_state(d, submodule_token); + + key[0] = d->index_status ? d->index_status : '.'; + key[1] = d->worktree_status ? d->worktree_status : '.'; + key[2] = 0; + + if (s->null_termination) { + /* + * In -z mode, we DO NOT C-quote pathnames. Current path is ALWAYS first. + * A single NUL character separates them. + */ + sep_char = '\0'; + eol_char = '\0'; + path_index = it->string; + path_head = d->head_path; + } else { + /* + * Path(s) are C-quoted if necessary. Current path is ALWAYS first. + * The source path is only present when necessary. + * A single TAB separates them (because paths can contain spaces + * which are not escaped and C-quoting does escape TAB characters). + */ + sep_char = '\t'; + eol_char = '\n'; + path_index = quote_path(it->string, s->prefix, &buf_index); + if (d->head_path) + path_head = quote_path(d->head_path, s->prefix, &buf_head); + } + + if (path_head) + fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c", + key, submodule_token, + d->mode_head, d->mode_index, d->mode_worktree, + oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index), + key[0], d->score, + path_index, sep_char, path_head, eol_char); + else + fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c", + key, submodule_token, + d->mode_head, d->mode_index, d->mode_worktree, + oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index), + path_index, eol_char); + + strbuf_release(&buf_index); + strbuf_release(&buf_head); +} + +/* + * Print porcelain v2 status info for unmerged entries. + */ +static void wt_porcelain_v2_print_unmerged_entry( + struct string_list_item *it, + struct wt_status *s) +{ + struct wt_status_change_data *d = it->util; + const struct cache_entry *ce; + struct strbuf buf_index = STRBUF_INIT; + const char *path_index = NULL; + int pos, stage, sum; + struct { + int mode; + struct object_id oid; + } stages[3]; + char *key; + char submodule_token[5]; + char unmerged_prefix = 'u'; + char eol_char = s->null_termination ? '\0' : '\n'; + + wt_porcelain_v2_submodule_state(d, submodule_token); + + switch (d->stagemask) { + case 1: key = "DD"; break; /* both deleted */ + case 2: key = "AU"; break; /* added by us */ + case 3: key = "UD"; break; /* deleted by them */ + case 4: key = "UA"; break; /* added by them */ + case 5: key = "DU"; break; /* deleted by us */ + case 6: key = "AA"; break; /* both added */ + case 7: key = "UU"; break; /* both modified */ + default: + die("BUG: unhandled unmerged status %x", d->stagemask); + } + + /* + * Disregard d.aux.porcelain_v2 data that we accumulated + * for the head and index columns during the scans and + * replace with the actual stage data. + * + * Note that this is a last-one-wins for each the individual + * stage [123] columns in the event of multiple cache entries + * for same stage. + */ + memset(stages, 0, sizeof(stages)); + sum = 0; + pos = cache_name_pos(it->string, strlen(it->string)); + assert(pos < 0); + pos = -pos-1; + while (pos < active_nr) { + ce = active_cache[pos++]; + stage = ce_stage(ce); + if (strcmp(ce->name, it->string) || !stage) + break; + stages[stage - 1].mode = ce->ce_mode; + hashcpy(stages[stage - 1].oid.hash, ce->sha1); + sum |= (1 << (stage - 1)); + } + if (sum != d->stagemask) + die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask); + + if (s->null_termination) + path_index = it->string; + else + path_index = quote_path(it->string, s->prefix, &buf_index); + + fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c", + unmerged_prefix, key, submodule_token, + stages[0].mode, /* stage 1 */ + stages[1].mode, /* stage 2 */ + stages[2].mode, /* stage 3 */ + d->mode_worktree, + oid_to_hex(&stages[0].oid), /* stage 1 */ + oid_to_hex(&stages[1].oid), /* stage 2 */ + oid_to_hex(&stages[2].oid), /* stage 3 */ + path_index, + eol_char); + + strbuf_release(&buf_index); +} + +/* + * Print porcelain V2 status info for untracked and ignored entries. + */ +static void wt_porcelain_v2_print_other( + struct string_list_item *it, + struct wt_status *s, + char prefix) +{ + struct strbuf buf = STRBUF_INIT; + const char *path; + char eol_char; + + if (s->null_termination) { + path = it->string; + eol_char = '\0'; + } else { + path = quote_path(it->string, s->prefix, &buf); + eol_char = '\n'; + } + + fprintf(s->fp, "%c %s%c", prefix, path, eol_char); + + strbuf_release(&buf); +} + +/* + * Print porcelain V2 status. + * + * []* + * []* + * []* + * []* + * + */ +static void wt_porcelain_v2_print(struct wt_status *s) +{ + struct wt_status_change_data *d; + struct string_list_item *it; + int i; + + for (i = 0; i < s->change.nr; i++) { + it = &(s->change.items[i]); + d = it->util; + if (!d->stagemask) + wt_porcelain_v2_print_changed_entry(it, s); + } + + for (i = 0; i < s->change.nr; i++) { + it = &(s->change.items[i]); + d = it->util; + if (d->stagemask) + wt_porcelain_v2_print_unmerged_entry(it, s); + } + + for (i = 0; i < s->untracked.nr; i++) { + it = &(s->untracked.items[i]); + wt_porcelain_v2_print_other(it, s, '?'); + } + + for (i = 0; i < s->ignored.nr; i++) { + it = &(s->ignored.items[i]); + wt_porcelain_v2_print_other(it, s, '!'); + } +} + void wt_status_print(struct wt_status *s) { switch (s->status_format) { @@ -1822,7 +2105,7 @@ void wt_status_print(struct wt_status *s) wt_porcelain_print(s); break; case STATUS_FORMAT_PORCELAIN_V2: - /* TODO */ + wt_porcelain_v2_print(s); break; case STATUS_FORMAT_UNSPECIFIED: die("BUG: finalize_deferred_config() should have been called"); From d9fc746cd77910a7dec53abfec36df5c699b33c2 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 11 Aug 2016 10:45:59 -0400 Subject: [PATCH 06/90] status: print branch info with --porcelain=v2 --branch Expand porcelain v2 output to include branch and tracking branch information. This includes the commit id, the branch, the upstream branch, and the ahead and behind counts. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- builtin/commit.c | 5 +++ wt-status.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ wt-status.h | 1 + 3 files changed, 96 insertions(+) diff --git a/builtin/commit.c b/builtin/commit.c index 3d222d3970..5504afe53a 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -510,6 +510,8 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->fp = fp; s->nowarn = nowarn; s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; + if (!s->is_initial) + hashcpy(s->sha1_commit, sha1); s->status_format = status_format; s->ignore_submodule_arg = ignore_submodule_arg; @@ -1378,6 +1380,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) fd = hold_locked_index(&index_lock, 0); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; + if (!s.is_initial) + hashcpy(s.sha1_commit, sha1); + s.ignore_submodule_arg = ignore_submodule_arg; s.status_format = status_format; s.verbose = verbose; diff --git a/wt-status.c b/wt-status.c index bd9075b7ee..ebe4a1e247 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1812,6 +1812,92 @@ static void wt_porcelain_print(struct wt_status *s) wt_shortstatus_print(s); } +/* + * Print branch information for porcelain v2 output. These lines + * are printed when the '--branch' parameter is given. + * + * # branch.oid + * # branch.head + * [# branch.upstream + * [# branch.ab + -]] + * + * ::= the current commit hash or the the literal + * "(initial)" to indicate an initialized repo + * with no commits. + * + * ::= the current branch name or + * "(detached)" literal when detached head or + * "(unknown)" when something is wrong. + * + * ::= the upstream branch name, when set. + * + * ::= integer ahead value, when upstream set + * and the commit is present (not gone). + * + * ::= integer behind value, when upstream set + * and commit is present. + * + * + * The end-of-line is defined by the -z flag. + * + * ::= NUL when -z, + * LF when NOT -z. + * + */ +static void wt_porcelain_v2_print_tracking(struct wt_status *s) +{ + struct branch *branch; + const char *base; + const char *branch_name; + struct wt_status_state state; + int ab_info, nr_ahead, nr_behind; + char eol = s->null_termination ? '\0' : '\n'; + + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD")); + + fprintf(s->fp, "# branch.oid %s%c", + (s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)), + eol); + + if (!s->branch) + fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol); + else { + if (!strcmp(s->branch, "HEAD")) { + fprintf(s->fp, "# branch.head %s%c", "(detached)", eol); + + if (state.rebase_in_progress || state.rebase_interactive_in_progress) + branch_name = state.onto; + else if (state.detached_from) + branch_name = state.detached_from; + else + branch_name = ""; + } else { + branch_name = NULL; + skip_prefix(s->branch, "refs/heads/", &branch_name); + + fprintf(s->fp, "# branch.head %s%c", branch_name, eol); + } + + /* Lookup stats on the upstream tracking branch, if set. */ + branch = branch_get(branch_name); + base = NULL; + ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0); + if (base) { + base = shorten_unambiguous_ref(base, 0); + fprintf(s->fp, "# branch.upstream %s%c", base, eol); + free((char *)base); + + if (ab_info) + fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol); + } + } + + free(state.branch); + free(state.onto); + free(state.detached_from); +} + /* * Convert various submodule status values into a * fixed-length string of characters in the buffer provided. @@ -2058,6 +2144,7 @@ static void wt_porcelain_v2_print_other( /* * Print porcelain V2 status. * + * [] * []* * []* * []* @@ -2070,6 +2157,9 @@ static void wt_porcelain_v2_print(struct wt_status *s) struct string_list_item *it; int i; + if (s->show_branch) + wt_porcelain_v2_print_tracking(s); + for (i = 0; i < s->change.nr; i++) { it = &(s->change.items[i]); d = it->util; diff --git a/wt-status.h b/wt-status.h index 43fd3fccf6..e401837707 100644 --- a/wt-status.h +++ b/wt-status.h @@ -80,6 +80,7 @@ struct wt_status { int hints; enum wt_status_format status_format; + unsigned char sha1_commit[GIT_SHA1_RAWSZ]; /* when not Initial */ /* These are computed during processing of the individual sections */ int commitable; From 1cd828ddc8a61f6bec93303f4bdbcdbf1c261cb7 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 11 Aug 2016 10:46:00 -0400 Subject: [PATCH 07/90] git-status.txt: describe --porcelain=v2 format Update status manpage to include information about porcelain v2 format. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 126 +++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 6b1454bd42..725065ef2d 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -183,12 +183,12 @@ in which case `XY` are `!!`. If -b is used the short-format status is preceded by a line -## branchname tracking info + ## branchname tracking info -Porcelain Format -~~~~~~~~~~~~~~~~ +Porcelain Format Version 1 +~~~~~~~~~~~~~~~~~~~~~~~~~~ -The porcelain format is similar to the short format, but is guaranteed +Version 1 porcelain format is similar to the short format, but is guaranteed not to change in a backwards-incompatible way between Git versions or based on user configuration. This makes it ideal for parsing by scripts. The description of the short format above also describes the porcelain @@ -210,6 +210,124 @@ field from the first filename). Third, filenames containing special characters are not specially formatted; no quoting or backslash-escaping is performed. +Porcelain Format Version 2 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Version 2 format adds more detailed information about the state of +the worktree and changed items. Version 2 also defines an extensible +set of easy to parse optional headers. + +Header lines start with "#" and are added in response to specific +command line arguments. Parsers should ignore headers they +don't recognize. + +### Branch Headers + +If `--branch` is given, a series of header lines are printed with +information about the current branch. + + Line Notes + ------------------------------------------------------------ + # branch.oid | (initial) Current commit. + # branch.head | (detached) Current branch. + # branch.upstream If upstream is set. + # branch.ab + - If upstream is set and + the commit is present. + ------------------------------------------------------------ + +### Changed Tracked Entries + +Following the headers, a series of lines are printed for tracked +entries. One of three different line formats may be used to describe +an entry depending on the type of change. Tracked entries are printed +in an undefined order; parsers should allow for a mixture of the 3 +line types in any order. + +Ordinary changed entries have the following format: + + 1 + +Renamed or copied entries have the following format: + + 2 + + Field Meaning + -------------------------------------------------------- + A 2 character field containing the staged and + unstaged XY values described in the short format, + with unchanged indicated by a "." rather than + a space. + A 4 character field describing the submodule state. + "N..." when the entry is not a submodule. + "S" when the entry is a submodule. + is "C" if the commit changed; otherwise ".". + is "M" if it has tracked changes; otherwise ".". + is "U" if there are untracked changes; otherwise ".". + The octal file mode in HEAD. + The octal file mode in the index. + The octal file mode in the worktree. + The object name in HEAD. + The object name in the index. + The rename or copy score (denoting the percentage + of similarity between the source and target of the + move or copy). For example "R100" or "C75". + The pathname. In a renamed/copied entry, this + is the path in the index and in the working tree. + When the `-z` option is used, the 2 pathnames are separated + with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) + byte separates them. + The pathname in the commit at HEAD. This is only + present in a renamed/copied entry, and tells + where the renamed/copied contents came from. + -------------------------------------------------------- + +Unmerged entries have the following format; the first character is +a "u" to distinguish from ordinary changed entries. + + u

+ + Field Meaning + -------------------------------------------------------- + A 2 character field describing the conflict type + as described in the short format. + A 4 character field describing the submodule state + as described above. + The octal file mode in stage 1. + The octal file mode in stage 2. + The octal file mode in stage 3. + The octal file mode in the worktree. +

The object name in stage 1. +

The object name in stage 2. +

The object name in stage 3. + The pathname. + -------------------------------------------------------- + +### Other Items + +Following the tracked entries (and if requested), a series of +lines will be printed for untracked and then ignored items +found in the worktree. + +Untracked items have the following format: + + ? + +Ignored items have the following format: + + ! + +### Pathname Format Notes and -z + +When the `-z` option is given, pathnames are printed as is and +without any quoting and lines are terminated with a NUL (ASCII 0x00) +byte. + +Otherwise, all pathnames will be "C-quoted" if they contain any tab, +linefeed, double quote, or backslash characters. In C-quoting, these +characters will be replaced with the corresponding C-style escape +sequences and the resulting pathname will be double quoted. + + CONFIGURATION ------------- From b249e39f9934e020e1cf5e483c3f6f84d3638b31 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 11 Aug 2016 10:46:01 -0400 Subject: [PATCH 08/90] test-lib-functions.sh: add lf_to_nul helper Add lf_to_nul helper function to test-lib-functions. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- t/test-lib-functions.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 4f7eadb596..fdaeb3a96b 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -81,6 +81,10 @@ test_decode_color () { ' } +lf_to_nul () { + perl -pe 'y/\012/\000/' +} + nul_to_q () { perl -pe 'y/\000/Q/' } From 888525d7865fbf9a277ef5fb8c1fe57f2fb1a0c5 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 12 Aug 2016 11:44:52 -0400 Subject: [PATCH 09/90] status: unit tests for --porcelain=v2 Test porcelain v2 status format. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- t/t7064-wtstatus-pv2.sh | 593 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 593 insertions(+) create mode 100755 t/t7064-wtstatus-pv2.sh diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh new file mode 100755 index 0000000000..3012a4d7c0 --- /dev/null +++ b/t/t7064-wtstatus-pv2.sh @@ -0,0 +1,593 @@ +#!/bin/sh + +test_description='git status --porcelain=v2 + +This test exercises porcelain V2 output for git status.' + + +. ./test-lib.sh + + +test_expect_success setup ' + test_tick && + git config core.autocrlf false && + echo x >file_x && + echo y >file_y && + echo z >file_z && + mkdir dir1 && + echo a >dir1/file_a && + echo b >dir1/file_b +' + +test_expect_success 'before initial commit, nothing added, only untracked' ' + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + ? actual + ? dir1/ + ? expect + ? file_x + ? file_y + ? file_z + EOF + + git status --porcelain=v2 --branch --untracked-files=normal >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added' ' + git add file_x file_y file_z dir1 && + OID_A=$(git hash-object -t blob -- dir1/file_a) && + OID_B=$(git hash-object -t blob -- dir1/file_b) && + OID_X=$(git hash-object -t blob -- file_x) && + OID_Y=$(git hash-object -t blob -- file_y) && + OID_Z=$(git hash-object -t blob -- file_z) && + + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added (-z)' ' + lf_to_nul >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status -z --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'make first commit, comfirm HEAD oid and branch' ' + git commit -m initial && + H0=$(git rev-parse HEAD) && + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, create unstaged changes' ' + echo x >>file_x && + OID_X1=$(git hash-object -t blob -- file_x) && + rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit but omit untracked files and branch' ' + cat >expect <<-EOF && + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + EOF + + git status --porcelain=v2 --untracked-files=no >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, stage existing changes' ' + git add file_x && + git rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines' ' + git mv file_y renamed_y && + H0=$(git rev-parse HEAD) && + + q_to_tab >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines (-z)' ' + H0=$(git rev-parse HEAD) && + + ## Lines use NUL path separator and line terminator, so double transform here. + q_to_nul <<-EOF | lf_to_nul >expect && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all -z >actual && + test_cmp expect actual +' + +test_expect_success 'make second commit, confirm clean and new HEAD oid' ' + git commit -m second && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'confirm ignored files are not printed' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'ignored files are printed with --ignored' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + ! x.ign + EOF + + git status --porcelain=v2 --ignored --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'create and commit permanent ignore file' ' + cat >.gitignore <<-EOF && + actual* + expect* + EOF + + git add .gitignore && + git commit -m ignore_trash && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + EOF + + git status --porcelain=v2 --branch >actual && + test_cmp expect actual +' + +test_expect_success 'verify --intent-to-add output' ' + test_when_finished "git rm -f intent1.add intent2.add" && + touch intent1.add && + echo test >intent2.add && + + git add --intent-to-add intent1.add intent2.add && + + cat >expect <<-EOF && + 1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add + 1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add + EOF + + git status --porcelain=v2 >actual && + test_cmp expect actual +' + +test_expect_success 'verify AA (add-add) conflict' ' + test_when_finished "git reset --hard" && + + git branch AA_A master && + git checkout AA_A && + echo "Branch AA_A" >conflict.txt && + OID_AA_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_a" && + + git branch AA_B master && + git checkout AA_B && + echo "Branch AA_B" >conflict.txt && + OID_AA_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_b" && + + git branch AA_M AA_B && + git checkout AA_M && + test_must_fail git merge AA_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head AA_M + u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify UU (edit-edit) conflict' ' + test_when_finished "git reset --hard" && + + git branch UU_ANC master && + git checkout UU_ANC && + echo "Ancestor" >conflict.txt && + OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "UU_ANC" && + + git branch UU_A UU_ANC && + git checkout UU_A && + echo "Branch UU_A" >conflict.txt && + OID_UU_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_a" && + + git branch UU_B UU_ANC && + git checkout UU_B && + echo "Branch UU_B" >conflict.txt && + OID_UU_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_b" && + + git branch UU_M UU_B && + git checkout UU_M && + test_must_fail git merge UU_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head UU_M + u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify upstream fields in branch header' ' + git checkout master && + test_when_finished "rm -rf sub_repo" && + git clone . sub_repo && + ( + ## Confirm local master tracks remote master. + cd sub_repo && + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Test ahead/behind. + echo xyz >file_xyz && + git add file_xyz && + git commit -m xyz && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Repeat the above but without --branch. + cat >expect <<-EOF && + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual && + + ## Test upstream-gone case. Fake this by pointing origin/master at + ## a non-existing commit. + OLD=$(git rev-parse origin/master) && + NEW=$_z40 && + mv .git/packed-refs .git/old-packed-refs && + sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'create and add submodule, submodule appears clean (A. S...)' ' + git checkout master && + git clone . sub_repo && + git clone . super_repo && + ( cd super_repo && + git submodule add ../sub_repo sub1 && + + ## Confirm stage/add of clean submodule. + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 A. S... 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'untracked changes in added submodule (AM S..U)' ' + ( cd super_repo && + ## create untracked file in the submodule. + ( cd sub1 && + echo "xxxx" >file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged changes in added submodule (AM S.M.)' ' + ( cd super_repo && + ## stage the changes in the submodule. + ( cd sub1 && + git add file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and unstaged changes in added (AM S.M.)' ' + ( cd super_repo && + ( cd sub1 && + ## make additional unstaged changes (on the same file) in the submodule. + ## This does not cause us to get S.MU (because the submodule does not report + ## a "?" line for the unstaged changes). + echo "more changes" >>file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' ' + ( cd super_repo && + ( cd sub1 && + ## stage new changes in tracked file. + git add file_in_sub && + ## create new untracked file. + echo "yyyy" >>another_file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' ' + ( cd super_repo && + ( cd sub1 && + ## Make a new commit in the submodule. + git add file_in_sub && + rm -f another_file_in_sub && + git commit -m "new commit" + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'stage submodule in super and commit' ' + ( cd super_repo && + ## Stage the new submodule commit in the super. + git add sub1 && + ## Commit the super so that the sub no longer appears as added. + git commit -m "super commit" && + + HSUP=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' ' + ( cd super_repo && + ( cd sub1 && + echo "zzzz" >>file_in_sub + ) && + + HSUP=$(git rev-parse HEAD) && + HSUB=$(cd sub1 && git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + 1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_done From f6b78a771a1f4c980645f359fb222cc99a5b3b2a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 10/90] sequencer: lib'ify sequencer_pick_revisions() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The function sequencer_pick_revisions() has only two callers, cmd_revert() and cmd_cherry_pick(), both of which check the return value and react appropriately upon errors. So this is a safe conversion to make sequencer_pick_revisions() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3804fa931d..76b1c52f75 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1063,10 +1063,11 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (!get_sha1(name, sha1)) { if (!lookup_commit_reference_gently(sha1, 1)) { enum object_type type = sha1_object_info(sha1, NULL); - die(_("%s: can't cherry-pick a %s"), name, typename(type)); + return error(_("%s: can't cherry-pick a %s"), + name, typename(type)); } } else - die(_("%s: bad revision"), name); + return error(_("%s: bad revision"), name); } /* @@ -1082,10 +1083,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) !opts->revs->cmdline.rev->flags) { struct commit *cmit; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); cmit = get_revision(opts->revs); if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); + return error("BUG: expected exactly one commit from walk"); return single_pick(cmit, opts); } From bb2179c971909c4eab6130ac9072c9618485ccf8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Aug 2016 08:54:00 +0200 Subject: [PATCH 11/90] sequencer: do not die() in do_pick_commit() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The eventual caller of do_pick_commit() is sequencer_pick_revisions(), which already relays a reported error from its helper functions (including this one), and both of its two callers know how to react to a negative return correctly. So this makes do_pick_commit() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 76b1c52f75..baf6b40e7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -585,12 +585,14 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) && + update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) && + update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; if (res) { error(opts->action == REPLAY_REVERT From 57d69c8f84e894061712ccdeb41000c2db484c71 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 12/90] sequencer: lib'ify write_message() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of write_message(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make write_message() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index baf6b40e7a..ec85fe77b4 100644 --- a/sequencer.c +++ b/sequencer.c @@ -180,17 +180,20 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static void write_message(struct strbuf *msgbuf, const char *filename) +static int write_message(struct strbuf *msgbuf, const char *filename) { static struct lock_file msg_file; - int msg_fd = hold_lock_file_for_update(&msg_file, filename, - LOCK_DIE_ON_ERROR); + int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0); + if (msg_fd < 0) + return error_errno(_("Could not lock '%s'"), filename); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno(_("Could not write to %s"), filename); + return error_errno(_("Could not write to %s"), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) - die(_("Error wrapping up %s."), filename); + return error(_("Error wrapping up %s."), filename); + + return 0; } static struct tree *empty_tree(void) @@ -564,16 +567,16 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) head, &msgbuf, opts); if (res < 0) return res; - write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(&msgbuf, git_path_merge_msg()); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - write_message(&msgbuf, git_path_merge_msg()); + res = write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + res |= try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); From 6b7fbc814b7415e6f79a3221645220f43ac3fffb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 13/90] sequencer: lib'ify do_recursive_merge() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of do_recursive_merge(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make do_recursive_merge() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index ec85fe77b4..eb700913a1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -303,7 +303,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), action_name(opts)); + return error(_("%s: Unable to write new index file"), + action_name(opts)); rollback_lock_file(&index_lock); if (opts->signoff) From b20c5e994764e892df4acfdd268d7018a2a92741 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 14/90] sequencer: lib'ify do_pick_commit() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only two callers of do_pick_commit(), pick_commits() and single_pick() already check the return value and pass it on to their callers, so their callers must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make do_pick_commit() callable from new callers that want it not to die, without changing the external behaviour of anything existing. While at it, remove the superfluous space. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index eb700913a1..96b9ae1978 100644 --- a/sequencer.c +++ b/sequencer.c @@ -464,7 +464,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); + return error(_("Your index file is unmerged.")); } else { unborn = get_sha1("HEAD", head); if (unborn) From c2fd005878f435f6f0b6f4a06b798e9038bae310 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 15/90] sequencer: lib'ify walk_revs_populate_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The function sequencer_pick_revisions() is the only caller of walk_revs_populate_todo(), and it already returns errors appropriately, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make walk_revs_populate_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 96b9ae1978..ab599e04df 100644 --- a/sequencer.c +++ b/sequencer.c @@ -809,17 +809,19 @@ static void read_populate_opts(struct replay_opts **opts_ptr) die(_("Malformed options sheet: %s"), git_path_opts_file()); } -static void walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct commit *commit; struct commit_list **next; - prepare_revs(opts); + if (prepare_revs(opts)) + return -1; next = todo_list; while ((commit = get_revision(opts->revs))) next = commit_list_append(commit, next); + return 0; } static int create_seq_dir(void) @@ -1102,8 +1104,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) * progress */ - walk_revs_populate_todo(&todo_list, opts); - if (create_seq_dir() < 0) + if (walk_revs_populate_todo(&todo_list, opts) || + create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) return error(_("Can't revert as initial commit")); From 3b5f6403c941f054647ef22d1ff7ebeb4ebb1747 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 16/90] sequencer: lib'ify prepare_revs() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of prepare_revs(), walk_revs_populate_todo() was just taught to return errors, after verifying that its callers are prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make prepare_revs() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index ab599e04df..7fd0f99650 100644 --- a/sequencer.c +++ b/sequencer.c @@ -623,7 +623,7 @@ leave: return res; } -static void prepare_revs(struct replay_opts *opts) +static int prepare_revs(struct replay_opts *opts) { /* * picking (but not reverting) ranges (but not individual revisions) @@ -633,10 +633,11 @@ static void prepare_revs(struct replay_opts *opts) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); if (!opts->revs->commits) - die(_("empty commit set passed")); + return error(_("empty commit set passed")); + return 0; } static void read_and_refresh_cache(struct replay_opts *opts) From 7f4e48ce922d62b17621d8f38aea72adea15233d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 17/90] sequencer: lib'ify read_and_refresh_cache() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). There are two call sites of read_and_refresh_cache(), one of which is pick_commits(), whose callers were already prepared to do the right thing given an "error" return from it by an earlier patch, so the conversion is safe. The other one, sequencer_pick_revisions() was also prepared to relay an error return back to its caller in all remaining cases in an earlier patch. Signed-off-by: Johannes Schindelin --- sequencer.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7fd0f99650..631b75dabe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -640,18 +640,21 @@ static int prepare_revs(struct replay_opts *opts) return 0; } -static void read_and_refresh_cache(struct replay_opts *opts) +static int read_and_refresh_cache(struct replay_opts *opts) { static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); if (read_index_preload(&the_index, NULL) < 0) - die(_("git %s: failed to read the index"), action_name(opts)); + return error(_("git %s: failed to read the index"), + action_name(opts)); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - die(_("git %s: failed to refresh the index"), action_name(opts)); + return error(_("git %s: failed to refresh the index"), + action_name(opts)); } rollback_lock_file(&index_lock); + return 0; } static int format_todo(struct strbuf *buf, struct commit_list *todo_list, @@ -981,7 +984,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; for (cur = todo_list; cur; cur = cur->next) { save_todo(cur, opts); @@ -1045,7 +1049,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_NONE) assert(opts->revs); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; /* * Decide what to do depending on the arguments; a fresh From 546d99a423009280bd5e17297ded2d5d6f2b39c3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 18/90] sequencer: lib'ify read_populate_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of read_populate_todo(), sequencer_continue() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make read_populate_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 631b75dabe..c73cdfdac1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -748,7 +748,7 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, return 0; } -static void read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; @@ -756,18 +756,21 @@ static void read_populate_todo(struct commit_list **todo_list, fd = open(git_path_todo_file(), O_RDONLY); if (fd < 0) - die_errno(_("Could not open %s"), git_path_todo_file()); + return error_errno(_("Could not open %s"), + git_path_todo_file()); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - die(_("Could not read %s."), git_path_todo_file()); + return error(_("Could not read %s."), git_path_todo_file()); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - die(_("Unusable instruction sheet: %s"), git_path_todo_file()); + return error(_("Unusable instruction sheet: %s"), + git_path_todo_file()); + return 0; } static int populate_opts_cb(const char *key, const char *value, void *data) @@ -1019,7 +1022,8 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); + if (read_populate_todo(&todo_list, opts)) + return -1; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || From 128635ba72551b0699992b5d34a4c4133ced9bda Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 19/90] sequencer: lib'ify read_populate_opts() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of read_populate_opts(), sequencer_continue() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make read_populate_opts() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Note that the function git_config_from_file(), called from read_populate_opts(), can currently still die() (in git_parse_source(), because the do_config_from_file() function sets die_on_error = 1). We do not try to fix that here, as it would have larger ramifications on the config code, and we also assume that we write the opts file programmatically, hence any parse errors would be bugs. Signed-off-by: Johannes Schindelin --- sequencer.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index c73cdfdac1..1614efb8db 100644 --- a/sequencer.c +++ b/sequencer.c @@ -808,12 +808,20 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static void read_populate_opts(struct replay_opts **opts_ptr) +static int read_populate_opts(struct replay_opts **opts) { if (!file_exists(git_path_opts_file())) - return; - if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0) - die(_("Malformed options sheet: %s"), git_path_opts_file()); + return 0; + /* + * The function git_parse_source(), called from git_config_from_file(), + * may die() in case of a syntactically incorrect file. We do not care + * about this case, though, because we wrote that file ourselves, so we + * are pretty certain that it is syntactically correct. + */ + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0) + return error(_("Malformed options sheet: %s"), + git_path_opts_file()); + return 0; } static int walk_revs_populate_todo(struct commit_list **todo_list, @@ -1021,8 +1029,8 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); - read_populate_opts(&opts); - if (read_populate_todo(&todo_list, opts)) + if (read_populate_opts(&opts) || + read_populate_todo(&todo_list, opts)) return -1; /* Verify that the conflict has been resolved */ From 9b6a0a1a5b8bccc7187ebb248f062758ad2c8a3c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 20/90] sequencer: lib'ify create_seq_dir() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of create_seq_dir(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make create_seq_dir() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 1614efb8db..eb9c473328 100644 --- a/sequencer.c +++ b/sequencer.c @@ -847,8 +847,8 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), - git_path_seq_dir()); + return error_errno(_("Could not create sequencer directory %s"), + git_path_seq_dir()); return 0; } From 2ef766af07e8b6f9b9b954958f14ac1cb5482a72 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 21/90] sequencer: lib'ify save_head() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_head(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_head() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index eb9c473328..7a1561e6a0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -852,18 +852,28 @@ static int create_seq_dir(void) return 0; } -static void save_head(const char *head) +static int save_head(const char *head) { static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0); + if (fd < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not lock HEAD")); + } strbuf_addf(&buf, "%s\n", head); - if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), git_path_head_file()); - if (commit_lock_file(&head_lock) < 0) - die(_("Error wrapping up %s."), git_path_head_file()); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not write to %s"), + git_path_head_file()); + } + if (commit_lock_file(&head_lock) < 0) { + rollback_lock_file(&head_lock); + return error(_("Error wrapping up %s."), git_path_head_file()); + } + return 0; } static int reset_for_rollback(const unsigned char *sha1) @@ -1127,7 +1137,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) return error(_("Can't revert as initial commit")); - save_head(sha1_to_hex(sha1)); + if (save_head(sha1_to_hex(sha1))) + return -1; save_opts(opts); return pick_commits(todo_list, opts); } From f51cf724d8e741817bdb6e3c710171c896f79ccb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 22/90] sequencer: lib'ify save_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_todo(), pick_commits() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7a1561e6a0..32c53bb2b7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -943,24 +943,31 @@ fail: return -1; } -static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR); - if (format_todo(&buf, todo_list, opts) < 0) - die(_("Could not format %s."), git_path_todo_file()); + fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + if (fd < 0) + return error_errno(_("Could not lock '%s'"), + git_path_todo_file()); + if (format_todo(&buf, todo_list, opts) < 0) { + strbuf_release(&buf); + return error(_("Could not format %s."), git_path_todo_file()); + } if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); - die_errno(_("Could not write to %s"), git_path_todo_file()); + return error_errno(_("Could not write to %s"), + git_path_todo_file()); } if (commit_lock_file(&todo_lock) < 0) { strbuf_release(&buf); - die(_("Error wrapping up %s."), git_path_todo_file()); + return error(_("Error wrapping up %s."), git_path_todo_file()); } strbuf_release(&buf); + return 0; } static void save_opts(struct replay_opts *opts) @@ -1009,7 +1016,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) return -1; for (cur = todo_list; cur; cur = cur->next) { - save_todo(cur, opts); + if (save_todo(cur, opts)) + return -1; res = do_pick_commit(cur->item, opts); if (res) return res; From 5ae1f03f43339652f580de8b28236ae896c72863 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 23/90] sequencer: lib'ify save_opts() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_opts(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_opts() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index 32c53bb2b7..021ddf36d0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -970,37 +970,39 @@ static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) return 0; } -static void save_opts(struct replay_opts *opts) +static int save_opts(struct replay_opts *opts) { const char *opts_file = git_path_opts_file(); + int res = 0; if (opts->no_commit) - git_config_set_in_file(opts_file, "options.no-commit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.no-commit", "true"); if (opts->edit) - git_config_set_in_file(opts_file, "options.edit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.edit", "true"); if (opts->signoff) - git_config_set_in_file(opts_file, "options.signoff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.signoff", "true"); if (opts->record_origin) - git_config_set_in_file(opts_file, "options.record-origin", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.record-origin", "true"); if (opts->allow_ff) - git_config_set_in_file(opts_file, "options.allow-ff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.allow-ff", "true"); if (opts->mainline) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%d", opts->mainline); - git_config_set_in_file(opts_file, "options.mainline", buf.buf); + res |= git_config_set_in_file_gently(opts_file, "options.mainline", buf.buf); strbuf_release(&buf); } if (opts->strategy) - git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + res |= git_config_set_in_file_gently(opts_file, "options.strategy", opts->strategy); if (opts->gpg_sign) - git_config_set_in_file(opts_file, "options.gpg-sign", opts->gpg_sign); + res |= git_config_set_in_file_gently(opts_file, "options.gpg-sign", opts->gpg_sign); if (opts->xopts) { int i; for (i = 0; i < opts->xopts_nr; i++) - git_config_set_multivar_in_file(opts_file, + res |= git_config_set_multivar_in_file_gently(opts_file, "options.strategy-option", opts->xopts[i], "^$", 0); } + return res; } static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) @@ -1147,7 +1149,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) return error(_("Can't revert as initial commit")); if (save_head(sha1_to_hex(sha1))) return -1; - save_opts(opts); + if (save_opts(opts)) + return -1; return pick_commits(todo_list, opts); } From 0865df7116a6e717aff89e25026bd37aecf5c1ec Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 24/90] sequencer: lib'ify fast_forward_to() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of fast_forward_to(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make fast_forward_to() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 021ddf36d0..d92a632f29 100644 --- a/sequencer.c +++ b/sequencer.c @@ -226,7 +226,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, read_cache(); if (checkout_fast_forward(from, to, 1)) - exit(128); /* the callee should have complained already */ + return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); From ba2ead88d5dd4161f4d3700ced122f10aeaf5264 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 25/90] sequencer: lib'ify checkout_fast_forward() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only callers of checkout_fast_forward(), cmd_merge(), pull_into_void(), cmd_pull() and sequencer's fast_forward_to(), already check the return value and handle it appropriately. With this step, we make it notice an error return from this function. So this is a safe conversion to make checkout_fast_forward() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- merge.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/merge.c b/merge.c index 5db7d56b90..23866c9165 100644 --- a/merge.c +++ b/merge.c @@ -57,7 +57,8 @@ int checkout_fast_forward(const unsigned char *head, refresh_cache(REFRESH_QUIET); - hold_locked_index(lock_file, 1); + if (hold_locked_index(lock_file, 0) < 0) + return -1; memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); @@ -90,7 +91,9 @@ int checkout_fast_forward(const unsigned char *head, } if (unpack_trees(nr_trees, t, &opts)) return -1; - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) - die(_("unable to write new index file")); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) { + rollback_lock_file(lock_file); + return error(_("unable to write new index file")); + } return 0; } From ad2d946cb0da2e3bc7d9a0986a2bb0023722c389 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 30 Aug 2016 11:04:21 +0200 Subject: [PATCH 26/90] sequencer: ensure to release the lock when we could not read the index A future caller of read_and_refresh_cache() may want to do more than just print some helpful advice in case of failure. Suggested by Junio Hamano. Signed-off-by: Johannes Schindelin --- sequencer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index d92a632f29..eec8a60d6b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -644,14 +644,18 @@ static int read_and_refresh_cache(struct replay_opts *opts) { static struct lock_file index_lock; int index_fd = hold_locked_index(&index_lock, 0); - if (read_index_preload(&the_index, NULL) < 0) + if (read_index_preload(&the_index, NULL) < 0) { + rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), action_name(opts)); + } refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { - if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { + rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), action_name(opts)); + } } rollback_lock_file(&index_lock); return 0; From ee4f7d13a83ef46140c8d00db1d4fff35d5ef60a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 8 Feb 2016 17:16:19 +0100 Subject: [PATCH 27/90] sequencer: use static initializers for replay_opts This change is not completely faithful: instead of initializing all fields to 0, we choose to initialize command and subcommand to -1 (instead of defaulting to REPLAY_REVERT and REPLAY_NONE, respectively). Practically, it makes no difference at all, but future-proofs the code to require explicit assignments for both fields. Signed-off-by: Johannes Schindelin --- builtin/revert.c | 6 ++---- sequencer.h | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 4e693808b1..7365559c97 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -178,10 +178,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) int cmd_revert(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; opts.action = REPLAY_REVERT; @@ -195,10 +194,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix) int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); diff --git a/sequencer.h b/sequencer.h index 5ed5cb1d97..db425ad1a6 100644 --- a/sequencer.h +++ b/sequencer.h @@ -47,6 +47,7 @@ struct replay_opts { /* Only used by REPLAY_NONE */ struct rev_info *revs; }; +#define REPLAY_OPTS_INIT { -1, -1 } int sequencer_pick_revisions(struct replay_opts *opts); From 33d71aaed200bf173f58a3f0a25da8081d9eeb39 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:09:05 +0100 Subject: [PATCH 28/90] sequencer: use memoized sequencer directory path Signed-off-by: Johannes Schindelin --- builtin/commit.c | 2 +- sequencer.c | 11 ++++++----- sequencer.h | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 77e3dc8494..022119031a 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -173,7 +173,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path_cherry_pick_head())) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path(SEQ_DIR))) + if (file_exists(git_path_seq_dir())) sequencer_in_use = 1; } else diff --git a/sequencer.c b/sequencer.c index eec8a60d6b..cb16cbda35 100644 --- a/sequencer.c +++ b/sequencer.c @@ -21,10 +21,11 @@ const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; -static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE) -static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE) -static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR) -static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE) +GIT_PATH_FUNC(git_path_seq_dir, "sequencer") + +static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") +static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") +static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static int is_rfc2822_line(const char *buf, int len) { @@ -112,7 +113,7 @@ static void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT; - strbuf_addstr(&seq_dir, git_path(SEQ_DIR)); + strbuf_addstr(&seq_dir, git_path_seq_dir()); remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } diff --git a/sequencer.h b/sequencer.h index db425ad1a6..dd4d33a572 100644 --- a/sequencer.h +++ b/sequencer.h @@ -1,10 +1,7 @@ #ifndef SEQUENCER_H #define SEQUENCER_H -#define SEQ_DIR "sequencer" -#define SEQ_HEAD_FILE "sequencer/head" -#define SEQ_TODO_FILE "sequencer/todo" -#define SEQ_OPTS_FILE "sequencer/opts" +const char *git_path_seq_dir(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) From 929bcf196528d8561480a2bbeccd9e8cfa9951e2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:23:55 +0100 Subject: [PATCH 29/90] sequencer: avoid unnecessary indirection We really do not need the *pointer to a* pointer to the options in the read_populate_opts() function. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index cb16cbda35..c2fbf6f89c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -813,7 +813,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static int read_populate_opts(struct replay_opts **opts) +static int read_populate_opts(struct replay_opts *opts) { if (!file_exists(git_path_opts_file())) return 0; @@ -823,7 +823,7 @@ static int read_populate_opts(struct replay_opts **opts) * about this case, though, because we wrote that file ourselves, so we * are pretty certain that it is syntactically correct. */ - if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0) + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0) return error(_("Malformed options sheet: %s"), git_path_opts_file()); return 0; @@ -1054,7 +1054,7 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); - if (read_populate_opts(&opts) || + if (read_populate_opts(opts) || read_populate_todo(&todo_list, opts)) return -1; From 9e3e4dd3b422eb7e73751cd183ff8ab71757cc33 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 16:06:35 +0100 Subject: [PATCH 30/90] sequencer: future-proof remove_sequencer_state() In a couple of commits, we will teach the sequencer to handle the nitty gritty of the interactive rebase, which keeps its state in a different directory. Signed-off-by: Johannes Schindelin --- sequencer.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index c2fbf6f89c..8d272fbdca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -27,6 +27,11 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +static const char *get_dir(const struct replay_opts *opts) +{ + return git_path_seq_dir(); +} + static int is_rfc2822_line(const char *buf, int len) { int i; @@ -109,13 +114,13 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } -static void remove_sequencer_state(void) +static void remove_sequencer_state(const struct replay_opts *opts) { - struct strbuf seq_dir = STRBUF_INIT; + struct strbuf dir = STRBUF_INIT; - strbuf_addstr(&seq_dir, git_path_seq_dir()); - remove_dir_recursively(&seq_dir, 0); - strbuf_release(&seq_dir); + strbuf_addf(&dir, "%s", get_dir(opts)); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); } static const char *action_name(const struct replay_opts *opts) @@ -940,7 +945,7 @@ static int sequencer_rollback(struct replay_opts *opts) } if (reset_for_rollback(sha1)) goto fail; - remove_sequencer_state(); + remove_sequencer_state(opts); strbuf_release(&buf); return 0; fail: @@ -1034,7 +1039,7 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory */ - remove_sequencer_state(); + remove_sequencer_state(opts); return 0; } @@ -1095,7 +1100,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) * one that is being continued */ if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(); + remove_sequencer_state(opts); return 0; } if (opts->subcommand == REPLAY_ROLLBACK) From 8667c79ad7cf53388e84abfffb001d5d2444b2cf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 9 Jun 2016 09:43:42 +0200 Subject: [PATCH 31/90] sequencer: allow the sequencer to take custody of malloc()ed data The sequencer is our attempt to lib-ify cherry-pick. Yet it behaves like a one-shot command when it reads its configuration: memory is allocated and released only when the command exits. This is kind of okay for git-cherry-pick, which *is* a one-shot command. All the work to make the sequencer its work horse was done to allow using the functionality as a library function, though, including proper clean-up after use. This patch introduces an API to pass the responsibility of releasing certain memory to the sequencer. Example: const char *label = sequencer_entrust(opts, xstrfmt("From: %s", email)); The entrusted memory will remain valid until sequencer_remove_state() is called, or the program exits, whichever comes first. Signed-off-by: Johannes Schindelin --- sequencer.c | 13 +++++++++++++ sequencer.h | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index 8d272fbdca..8d56a05277 100644 --- a/sequencer.c +++ b/sequencer.c @@ -114,9 +114,22 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } +void *sequencer_entrust(struct replay_opts *opts, void *to_free) +{ + ALLOC_GROW(opts->owned, opts->owned_nr + 1, opts->owned_alloc); + opts->owned[opts->owned_nr++] = to_free; + + return to_free; +} + static void remove_sequencer_state(const struct replay_opts *opts) { struct strbuf dir = STRBUF_INIT; + int i; + + for (i = 0; i < opts->owned_nr; i++) + free(opts->owned[i]); + free(opts->owned); strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); diff --git a/sequencer.h b/sequencer.h index dd4d33a572..04892a9c20 100644 --- a/sequencer.h +++ b/sequencer.h @@ -43,9 +43,19 @@ struct replay_opts { /* Only used by REPLAY_NONE */ struct rev_info *revs; + + /* malloc()ed data entrusted to the sequencer */ + void **owned; + int owned_nr, owned_alloc; }; #define REPLAY_OPTS_INIT { -1, -1 } +/* + * Make it the duty of sequencer_remove_state() to release the memory; + * For ease of use, return the same pointer. + */ +void *sequencer_entrust(struct replay_opts *opts, void *to_free); + int sequencer_pick_revisions(struct replay_opts *opts); extern const char sign_off_header[]; From 877901f20ca283e9ee7f7d8976eaf6593698cc8b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 9 Jun 2016 09:59:52 +0200 Subject: [PATCH 32/90] sequencer: release memory that was allocated when reading options The sequencer reads options from disk and stores them in its struct for use during sequencer's operations. With this patch, the memory is released afterwards, plugging a memory leak. Signed-off-by: Johannes Schindelin --- sequencer.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 8d56a05277..3ca231f8f3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -131,6 +131,8 @@ static void remove_sequencer_state(const struct replay_opts *opts) free(opts->owned[i]); free(opts->owned); + free(opts->xopts); + strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -815,13 +817,18 @@ static int populate_opts_cb(const char *key, const char *value, void *data) opts->allow_ff = git_config_bool_or_int(key, value, &error_flag); else if (!strcmp(key, "options.mainline")) opts->mainline = git_config_int(key, value); - else if (!strcmp(key, "options.strategy")) + else if (!strcmp(key, "options.strategy")) { git_config_string(&opts->strategy, key, value); - else if (!strcmp(key, "options.gpg-sign")) + sequencer_entrust(opts, (char *) opts->strategy); + } + else if (!strcmp(key, "options.gpg-sign")) { git_config_string(&opts->gpg_sign, key, value); + sequencer_entrust(opts, (char *) opts->gpg_sign); + } else if (!strcmp(key, "options.strategy-option")) { ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); - opts->xopts[opts->xopts_nr++] = xstrdup(value); + opts->xopts[opts->xopts_nr++] = + sequencer_entrust(opts, xstrdup(value)); } else return error(_("Invalid key: %s"), key); From 728e02fdc9e81d144ef2fe1a1e3ad4e0a2b330d9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 13 Feb 2016 14:31:00 +0100 Subject: [PATCH 33/90] sequencer: future-proof read_populate_todo() Over the next commits, we will work on improving the sequencer to the point where it can process the edit script of an interactive rebase. To that end, we will need to teach the sequencer to read interactive rebase's todo file. In preparation, we consolidate all places where that todo file is needed to call a function that we will later extend. Signed-off-by: Johannes Schindelin --- sequencer.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3ca231f8f3..da6de229e7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -32,6 +32,11 @@ static const char *get_dir(const struct replay_opts *opts) return git_path_seq_dir(); } +static const char *get_todo_path(const struct replay_opts *opts) +{ + return git_path_todo_file(); +} + static int is_rfc2822_line(const char *buf, int len) { int i; @@ -776,25 +781,24 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, static int read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { + const char *todo_file = get_todo_path(opts); struct strbuf buf = STRBUF_INIT; int fd, res; - fd = open(git_path_todo_file(), O_RDONLY); + fd = open(todo_file, O_RDONLY); if (fd < 0) - return error_errno(_("Could not open %s"), - git_path_todo_file()); + return error_errno(_("Could not open %s"), todo_file); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - return error(_("Could not read %s."), git_path_todo_file()); + return error(_("Could not read %s."), todo_file); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - return error(_("Unusable instruction sheet: %s"), - git_path_todo_file()); + return error(_("Unusable instruction sheet: %s"), todo_file); return 0; } @@ -1077,7 +1081,7 @@ static int sequencer_continue(struct replay_opts *opts) { struct commit_list *todo_list = NULL; - if (!file_exists(git_path_todo_file())) + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts) || read_populate_todo(&todo_list, opts)) From f78f06ec7f76ab95ece7fcc1f6ecf111de60e380 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Feb 2016 19:21:31 +0100 Subject: [PATCH 34/90] sequencer: completely revamp the "todo" script parsing When we came up with the "sequencer" idea, we really wanted to have kind of a plumbing equivalent of the interactive rebase. Hence the choice of words: the "todo" script, a "pick", etc. However, when it came time to implement the entire shebang, somehow this idea got lost and the sequencer was used as working horse for cherry-pick and revert instead. So as not to interfere with the interactive rebase, it even uses a separate directory to store its state. Furthermore, it also is stupidly strict about the "todo" script it accepts: while it parses commands in a way that was *designed* to be similar to the interactive rebase, it then goes on to *error out* if the commands disagree with the overall action (cherry-pick or revert). Finally, the sequencer code chose to deviate from the interactive rebase code insofar that it *reformats* the "todo" script instead of just writing the part of the parsed script that were not yet processed. This is not only unnecessary churn, but might well lose information that is valuable to the user (i.e. comments after the commands). Let's just bite the bullet and rewrite the entire parser; the code now becomes not only more elegant: it allows us to go on and teach the sequencer how to parse *true* "todo" scripts as used by the interactive rebase itself. In a way, the sequencer is about to grow up to do its older brother's job. Better. In particular, we choose to maintain the list of commands in an array instead of a linked list: this is flexible enough to allow us later on to even implement rebase -i's reordering of fixup!/squash! commits very easily (and with a very nice speed bonus, at least on Windows). While at it, do not stop at the first problem, but list *all* of the problems. This will help the user when the sequencer will do `rebase -i`'s work by allowing to address all issues in one go rather than going back and forth until the todo list is valid. Signed-off-by: Johannes Schindelin --- sequencer.c | 275 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 159 insertions(+), 116 deletions(-) diff --git a/sequencer.c b/sequencer.c index da6de229e7..b971ad04fe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -473,7 +473,26 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +enum todo_command { + TODO_PICK = 0, + TODO_REVERT +}; + +static const char *todo_command_strings[] = { + "pick", + "revert" +}; + +static const char *command_to_string(const enum todo_command command) +{ + if (command < ARRAY_SIZE(todo_command_strings)) + return todo_command_strings[command]; + die("Unknown command: %d", command); +} + + +static int do_pick_commit(enum todo_command command, struct commit *commit, + struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -535,7 +554,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), - action_name(opts), oid_to_hex(&parent->object.oid)); + command_to_string(command), + oid_to_hex(&parent->object.oid)); if (get_message(commit, &msg) != 0) return error(_("Cannot get commit message for %s"), @@ -548,7 +568,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * reverse of it if we are revert. */ - if (opts->action == REPLAY_REVERT) { + if (command == TODO_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -589,7 +609,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) @@ -615,17 +635,17 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) && + if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) && update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) && + if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) && update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; if (res) { - error(opts->action == REPLAY_REVERT + error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), @@ -687,116 +707,121 @@ static int read_and_refresh_cache(struct replay_opts *opts) return 0; } -static int format_todo(struct strbuf *buf, struct commit_list *todo_list, - struct replay_opts *opts) -{ - struct commit_list *cur = NULL; - const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; +struct todo_item { + enum todo_command command; + struct commit *commit; + size_t offset_in_buf; +}; - for (cur = todo_list; cur; cur = cur->next) { - const char *commit_buffer = get_commit_buffer(cur->item, NULL); - sha1_abbrev = find_unique_abbrev(cur->item->object.oid.hash, DEFAULT_ABBREV); - subject_len = find_commit_subject(commit_buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - unuse_commit_buffer(cur->item, commit_buffer); - } - return 0; +struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; +}; + +#define TODO_LIST_INIT { STRBUF_INIT } + +static void todo_list_release(struct todo_list *todo_list) +{ + strbuf_release(&todo_list->buf); + free(todo_list->items); + todo_list->items = NULL; + todo_list->nr = todo_list->alloc = 0; } -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +struct todo_item *append_new_todo(struct todo_list *todo_list) +{ + ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc); + return todo_list->items + todo_list->nr++; +} + +static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) { unsigned char commit_sha1[20]; - enum replay_action action; char *end_of_object_name; - int saved, status, padding; + int i, saved, status, padding; - if (starts_with(bol, "pick")) { - action = REPLAY_PICK; - bol += strlen("pick"); - } else if (starts_with(bol, "revert")) { - action = REPLAY_REVERT; - bol += strlen("revert"); - } else - return NULL; + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) + if (skip_prefix(bol, todo_command_strings[i], &bol)) { + item->command = i; + break; + } + if (i >= ARRAY_SIZE(todo_command_strings)) + return -1; /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) - return NULL; + return -1; bol += padding; - end_of_object_name = bol + strcspn(bol, " \t\n"); + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if (action != opts->action) { - if (action == REPLAY_REVERT) - error((opts->action == REPLAY_REVERT) - ? _("Cannot revert during another revert.") - : _("Cannot revert during a cherry-pick.")); - else - error((opts->action == REPLAY_REVERT) - ? _("Cannot cherry-pick during a revert.") - : _("Cannot cherry-pick during another cherry-pick.")); - return NULL; - } - if (status < 0) - return NULL; + return -1; - return lookup_commit_reference(commit_sha1); + item->commit = lookup_commit_reference(commit_sha1); + return !item->commit; } -static int parse_insn_buffer(char *buf, struct commit_list **todo_list, - struct replay_opts *opts) +static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { - struct commit_list **next = todo_list; - struct commit *commit; + struct todo_item *item; char *p = buf; - int i; + int i, res = 0; for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) - return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); + + item = append_new_todo(todo_list); + item->offset_in_buf = p - todo_list->buf.buf; + if (parse_insn_line(item, p, eol)) { + res |= error(_("Invalid line %d: %.*s"), + i, (int)(eol - p), p); + item->command = -1; + } p = *eol ? eol + 1 : eol; } - if (!*todo_list) + if (!todo_list->nr) return error(_("No commits parsed.")); - return 0; + return res; } -static int read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { const char *todo_file = get_todo_path(opts); - struct strbuf buf = STRBUF_INIT; int fd, res; + strbuf_reset(&todo_list->buf); fd = open(todo_file, O_RDONLY); if (fd < 0) return error_errno(_("Could not open %s"), todo_file); - if (strbuf_read(&buf, fd, 0) < 0) { + if (strbuf_read(&todo_list->buf, fd, 0) < 0) { close(fd); - strbuf_release(&buf); return error(_("Could not read %s."), todo_file); } close(fd); - res = parse_insn_buffer(buf.buf, todo_list, opts); - strbuf_release(&buf); + res = parse_insn_buffer(todo_list->buf.buf, todo_list); + if (!res) { + enum todo_command valid = + opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; + int i; + + for (i = 0; i < todo_list->nr; i++) + if (valid == todo_list->items[i].command) + continue; + else if (valid == TODO_PICK) + return error(_("Cannot cherry-pick during a revert.")); + else + return error(_("Cannot revert during a cherry-pick.")); + } + if (res) return error(_("Unusable instruction sheet: %s"), todo_file); return 0; @@ -858,18 +883,33 @@ static int read_populate_opts(struct replay_opts *opts) return 0; } -static int walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { + enum todo_command command = opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT; + const char *command_string = todo_command_strings[command]; struct commit *commit; - struct commit_list **next; if (prepare_revs(opts)) return -1; - next = todo_list; - while ((commit = get_revision(opts->revs))) - next = commit_list_append(commit, next); + while ((commit = get_revision(opts->revs))) { + struct todo_item *item = append_new_todo(todo_list); + const char *commit_buffer = get_commit_buffer(commit, NULL); + const char *subject; + int subject_len; + + item->command = command; + item->commit = commit; + item->offset_in_buf = todo_list->buf.len; + subject_len = find_commit_subject(commit_buffer, &subject); + strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, + find_unique_abbrev(commit->object.oid.hash, + DEFAULT_ABBREV), + subject_len, subject); + unuse_commit_buffer(commit, commit_buffer); + } return 0; } @@ -977,30 +1017,22 @@ fail: return -1; } -static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; - struct strbuf buf = STRBUF_INIT; - int fd; + const char *todo_path = get_todo_path(opts); + int next = todo_list->current, offset, fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) - return error_errno(_("Could not lock '%s'"), - git_path_todo_file()); - if (format_todo(&buf, todo_list, opts) < 0) { - strbuf_release(&buf); - return error(_("Could not format %s."), git_path_todo_file()); - } - if (write_in_full(fd, buf.buf, buf.len) < 0) { - strbuf_release(&buf); - return error_errno(_("Could not write to %s"), - git_path_todo_file()); - } - if (commit_lock_file(&todo_lock) < 0) { - strbuf_release(&buf); - return error(_("Error wrapping up %s."), git_path_todo_file()); - } - strbuf_release(&buf); + return error_errno(_("Could not lock '%s'"), todo_path); + offset = next < todo_list->nr ? + todo_list->items[next].offset_in_buf : todo_list->buf.len; + if (write_in_full(fd, todo_list->buf.buf + offset, + todo_list->buf.len - offset) < 0) + return error_errno(_("Could not write to '%s'"), todo_path); + if (commit_lock_file(&todo_lock) < 0) + return error(_("Error wrapping up %s."), todo_path); return 0; } @@ -1039,9 +1071,8 @@ static int save_opts(struct replay_opts *opts) return res; } -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - struct commit_list *cur; int res; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); @@ -1051,10 +1082,12 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - for (cur = todo_list; cur; cur = cur->next) { - if (save_todo(cur, opts)) + while (todo_list->current < todo_list->nr) { + struct todo_item *item = todo_list->items + todo_list->current; + if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(cur->item, opts); + res = do_pick_commit(item->command, item->commit, opts); + todo_list->current++; if (res) return res; } @@ -1079,38 +1112,46 @@ static int continue_single_pick(void) static int sequencer_continue(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; + int res; if (!file_exists(get_todo_path(opts))) return continue_single_pick(); - if (read_populate_opts(opts) || - read_populate_todo(&todo_list, opts)) + if (read_populate_opts(opts)) return -1; + if ((res = read_populate_todo(&todo_list, opts))) + goto release_todo_list; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || file_exists(git_path_revert_head())) { - int ret = continue_single_pick(); - if (ret) - return ret; + res = continue_single_pick(); + if (res) + goto release_todo_list; } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); + if (index_differs_from("HEAD", 0)) { + res = error_dirty_index(opts); + goto release_todo_list; + } + todo_list.current++; + res = pick_commits(&todo_list, opts); +release_todo_list: + todo_list_release(&todo_list); + return res; } static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(cmit, opts); + return do_pick_commit(opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT, cmit, opts); } int sequencer_pick_revisions(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; unsigned char sha1[20]; - int i; + int i, res; if (opts->subcommand == REPLAY_NONE) assert(opts->revs); @@ -1185,7 +1226,9 @@ int sequencer_pick_revisions(struct replay_opts *opts) return -1; if (save_opts(opts)) return -1; - return pick_commits(todo_list, opts); + res = pick_commits(&todo_list, opts); + todo_list_release(&todo_list); + return res; } void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) From fc0b52b25d8032ed1e6c26567eeabc683dd4459e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 24 Feb 2016 14:57:22 +0100 Subject: [PATCH 35/90] sequencer: avoid completely different messages for different actions Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index b971ad04fe..7ba932bf0e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -232,11 +232,8 @@ static int error_dirty_index(struct replay_opts *opts) if (read_cache_unmerged()) return error_resolve_conflict(action_name(opts)); - /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) - error(_("Your local changes would be overwritten by cherry-pick.")); - else - error(_("Your local changes would be overwritten by revert.")); + error(_("Your local changes would be overwritten by %s."), + action_name(opts)); if (advice_commit_before_merge) advise(_("Commit your changes or stash them to proceed.")); From 1c79d6f18dfda5b127b2c661f56fabaf52c90d40 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 17:39:02 +0200 Subject: [PATCH 36/90] sequencer: get rid of the subcommand field The subcommands are used exactly once, at the very beginning of sequencer_pick_revisions(), to determine what to do. This is an unnecessary level of indirection: we can simply call the correct function to begin with. So let's do that. While at it, ensure that the subcommands return an error code so that they do not have to die() all over the place (bad practice for library functions...). Signed-off-by: Johannes Schindelin --- builtin/revert.c | 36 ++++++++++++++++-------------------- sequencer.c | 35 +++++++++++------------------------ sequencer.h | 13 ++++--------- 3 files changed, 31 insertions(+), 53 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 7365559c97..c9ae4dc13a 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static void parse_args(int argc, const char **argv, struct replay_opts *opts) +static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); @@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->keep_redundant_commits) opts->allow_empty = 1; - /* Set the subcommand */ - if (cmd == 'q') - opts->subcommand = REPLAY_REMOVE_STATE; - else if (cmd == 'c') - opts->subcommand = REPLAY_CONTINUE; - else if (cmd == 'a') - opts->subcommand = REPLAY_ROLLBACK; - else - opts->subcommand = REPLAY_NONE; - /* Check for incompatible command line arguments */ - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { char *this_operation; - if (opts->subcommand == REPLAY_REMOVE_STATE) + if (cmd == 'q') this_operation = "--quit"; - else if (opts->subcommand == REPLAY_CONTINUE) + else if (cmd == 'c') this_operation = "--continue"; else { - assert(opts->subcommand == REPLAY_ROLLBACK); + assert(cmd == 'a'); this_operation = "--abort"; } @@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) "--edit", opts->edit, NULL); - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { opts->revs = NULL; } else { struct setup_revision_opt s_r_opt; @@ -174,6 +164,14 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (argc > 1) usage_with_options(usage_str, options); + + if (cmd == 'q') + return sequencer_remove_state(opts); + if (cmd == 'c') + return sequencer_continue(opts); + if (cmd == 'a') + return sequencer_rollback(opts); + return sequencer_pick_revisions(opts); } int cmd_revert(int argc, const char **argv, const char *prefix) @@ -185,8 +183,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) opts.edit = 1; opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); return res; @@ -199,8 +196,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.action = REPLAY_PICK; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/sequencer.c b/sequencer.c index 7ba932bf0e..053e78ce8f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -127,7 +127,7 @@ void *sequencer_entrust(struct replay_opts *opts, void *to_free) return to_free; } -static void remove_sequencer_state(const struct replay_opts *opts) +int sequencer_remove_state(struct replay_opts *opts) { struct strbuf dir = STRBUF_INIT; int i; @@ -141,6 +141,8 @@ static void remove_sequencer_state(const struct replay_opts *opts) strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); strbuf_release(&dir); + + return 0; } static const char *action_name(const struct replay_opts *opts) @@ -971,7 +973,7 @@ static int rollback_single_pick(void) return reset_for_rollback(head_sha1); } -static int sequencer_rollback(struct replay_opts *opts) +int sequencer_rollback(struct replay_opts *opts) { FILE *f; unsigned char sha1[20]; @@ -1006,9 +1008,8 @@ static int sequencer_rollback(struct replay_opts *opts) } if (reset_for_rollback(sha1)) goto fail; - remove_sequencer_state(opts); strbuf_release(&buf); - return 0; + return sequencer_remove_state(opts); fail: strbuf_release(&buf); return -1; @@ -1093,8 +1094,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory */ - remove_sequencer_state(opts); - return 0; + return sequencer_remove_state(opts); } static int continue_single_pick(void) @@ -1107,11 +1107,14 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } -static int sequencer_continue(struct replay_opts *opts) +int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; int res; + if (read_and_refresh_cache(opts)) + return -1; + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) @@ -1150,26 +1153,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) unsigned char sha1[20]; int i, res; - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); - + assert(opts->revs); if (read_and_refresh_cache(opts)) return -1; - /* - * Decide what to do depending on the arguments; a fresh - * cherry-pick should be handled differently from an existing - * one that is being continued - */ - if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(opts); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - for (i = 0; i < opts->revs->pending.nr; i++) { unsigned char sha1[20]; const char *name = opts->revs->pending.objects[i].name; diff --git a/sequencer.h b/sequencer.h index 04892a9c20..0b3950d8fd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -10,16 +10,8 @@ enum replay_action { REPLAY_PICK }; -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - struct replay_opts { enum replay_action action; - enum replay_subcommand subcommand; /* Boolean options */ int edit; @@ -48,7 +40,7 @@ struct replay_opts { void **owned; int owned_nr, owned_alloc; }; -#define REPLAY_OPTS_INIT { -1, -1 } +#define REPLAY_OPTS_INIT { -1 } /* * Make it the duty of sequencer_remove_state() to release the memory; @@ -57,6 +49,9 @@ struct replay_opts { void *sequencer_entrust(struct replay_opts *opts, void *to_free); int sequencer_pick_revisions(struct replay_opts *opts); +int sequencer_continue(struct replay_opts *opts); +int sequencer_rollback(struct replay_opts *opts); +int sequencer_remove_state(struct replay_opts *opts); extern const char sign_off_header[]; From 5d0b0fcf6bd199462e3d8083c3202d6fe350a8c0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:21:16 +0100 Subject: [PATCH 37/90] sequencer: refactor the code to obtain a short commit name Not only does this DRY up the code (providing a better documentation what the code is about, as well as allowing to change the behavior in a single place), it also makes it substantially shorter to use the same functionality in functions to be introduced when we teach the sequencer to process interactive-rebase's git-rebase-todo file. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 053e78ce8f..0c8dec4992 100644 --- a/sequencer.c +++ b/sequencer.c @@ -157,13 +157,18 @@ struct commit_message { const char *message; }; +static const char *short_commit_name(struct commit *commit) +{ + return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); +} + static int get_message(struct commit *commit, struct commit_message *out) { const char *abbrev, *subject; int subject_len; out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding()); - abbrev = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); + abbrev = short_commit_name(commit); subject_len = find_commit_subject(out->message, &subject); @@ -647,8 +652,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), - msg.subject); + short_commit_name(commit), msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); goto leave; @@ -904,9 +908,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, - find_unique_abbrev(commit->object.oid.hash, - DEFAULT_ABBREV), - subject_len, subject); + short_commit_name(commit), subject_len, subject); unuse_commit_buffer(commit, commit_buffer); } return 0; From 5d5ee45c4e714591764fff02d3c4008be3918301 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 17:31:24 +0100 Subject: [PATCH 38/90] sequencer: remember the onelines when parsing the todo file The `git-rebase-todo` file contains a list of commands. Most of those commands have the form The is displayed primarily for the user's convenience, as rebase -i really interprets only the part. However, there are *some* places in interactive rebase where the is used to display messages, e.g. for reporting at which commit we stopped. So let's just remember it when parsing the todo file; we keep a copy of the entire todo file anyway (to write out the new `done` and `git-rebase-todo` file just before processing each command), so all we need to do is remember the begin offsets and lengths. As we will have to parse and remember the command-line for `exec` commands later, we do not call the field "oneline" but rather "arg" (and will reuse that for exec's command-line). Signed-off-by: Johannes Schindelin --- sequencer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sequencer.c b/sequencer.c index 0c8dec4992..ca1961c0f0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -713,6 +713,8 @@ static int read_and_refresh_cache(struct replay_opts *opts) struct todo_item { enum todo_command command; struct commit *commit; + const char *arg; + int arg_len; size_t offset_in_buf; }; @@ -764,6 +766,9 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; + item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_len = (int)(eol - item->arg); + if (status < 0) return -1; @@ -905,6 +910,8 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->command = command; item->commit = commit; + item->arg = NULL; + item->arg_len = 0; item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, From 62e174f98e1261c696e08bd4fdd8f63e8af8d8e4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 29 Feb 2016 17:17:47 +0100 Subject: [PATCH 39/90] sequencer: prepare for rebase -i's commit functionality In interactive rebases, we commit a little bit differently than the sequencer did so far: we heed the "author-script", the "message" and the "amend" files in the .git/rebase-merge/ subdirectory. Likewise, we may want to edit the commit message *even* when providing a file containing the suggested commit message. Therefore we change the code to not even provide a default message when we do not want any, and to call the editor explicitly. As interactive rebase's GPG settings are configured differently from how cherry-pick (and therefore sequencer) handles them, we will leave support for that to the next commit. Signed-off-by: Johannes Schindelin --- sequencer.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++------ sequencer.h | 3 ++ 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index ca1961c0f0..6c35fe80b9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -27,6 +27,19 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +/* + * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and + * GIT_AUTHOR_DATE that will be used for the commit that is currently + * being rebased. + */ +static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") + +/* We will introduce the 'interactive rebase' mode later */ +static inline int is_rebase_i(const struct replay_opts *opts) +{ + return 0; +} + static const char *get_dir(const struct replay_opts *opts) { return git_path_seq_dir(); @@ -377,20 +390,76 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static char **read_author_script(void) +{ + struct strbuf script = STRBUF_INIT; + int i, count = 0; + char *p, *p2, **env; + size_t env_size; + + if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) + return NULL; + + for (p = script.buf; *p; p++) + if (skip_prefix(p, "'\\\\''", (const char **)&p2)) + strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); + else if (*p == '\'') + strbuf_splice(&script, p-- - script.buf, 1, "", 0); + else if (*p == '\n') { + *p = '\0'; + count++; + } + + env_size = (count + 1) * sizeof(*env); + strbuf_grow(&script, env_size); + memmove(script.buf + env_size, script.buf, script.len); + p = script.buf + env_size; + env = (char **)strbuf_detach(&script, NULL); + + for (i = 0; i < count; i++) { + env[i] = p; + p += strlen(p) + 1; + } + env[count] = NULL; + + return env; +} + /* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original * author date and name. + * * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. + * + * An exception is when sequencer_commit() is called during an + * interactive rebase: in that case, we will want to retain the + * author metadata. */ -static int run_git_commit(const char *defmsg, struct replay_opts *opts, +int sequencer_commit(const char *defmsg, struct replay_opts *opts, int allow_empty) { + char **env = NULL; struct argv_array array; int rc; const char *value; + if (is_rebase_i(opts)) { + env = read_author_script(); + if (!env) + return error("You have staged changes in your working " + "tree. If these changes are meant to be\n" + "squashed into the previous commit, run:\n\n" + " git commit --amend $gpg_sign_opt_quoted\n\n" + "If they are meant to go into a new commit, " + "run:\n\n" + " git commit $gpg_sign_opt_quoted\n\n" + "In both cases, once you're done, continue " + "with:\n\n" + " git rebase --continue\n"); + } + argv_array_init(&array); argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); @@ -399,14 +468,13 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) argv_array_push(&array, "-s"); - if (!opts->edit) { - argv_array_push(&array, "-F"); - argv_array_push(&array, defmsg); - if (!opts->signoff && - !opts->record_origin && - git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); - } + if (defmsg) + argv_array_pushl(&array, "-F", defmsg, NULL); + if (opts->edit) + argv_array_push(&array, "-e"); + else if (!opts->signoff && !opts->record_origin && + git_config_get_value("commit.cleanup", &value)) + argv_array_push(&array, "--cleanup=verbatim"); if (allow_empty) argv_array_push(&array, "--allow-empty"); @@ -414,8 +482,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt(array.argv, RUN_GIT_CMD); + rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, + (const char *const *)env); argv_array_clear(&array); + free(env); + return rc; } @@ -664,7 +735,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(git_path_merge_msg(), opts, allow); + res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), + opts, allow); leave: free_message(commit, &msg); @@ -877,6 +949,9 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static int read_populate_opts(struct replay_opts *opts) { + if (is_rebase_i(opts)) + return 0; + if (!file_exists(git_path_opts_file())) return 0; /* diff --git a/sequencer.h b/sequencer.h index 0b3950d8fd..16deb6c85d 100644 --- a/sequencer.h +++ b/sequencer.h @@ -53,6 +53,9 @@ int sequencer_continue(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); +int sequencer_commit(const char *defmsg, struct replay_opts *opts, + int allow_empty); + extern const char sign_off_header[]; void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); From a1129872be3c71a0dbe9ed492f384ba45050b99f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 19 May 2016 09:00:44 +0200 Subject: [PATCH 40/90] sequencer: introduce a helper to read files written by scripts As we are slowly teaching the sequencer to perform the hard work for the interactive rebase, we need to read files that were written by shell scripts. These files typically contain a single line and are invariably ended by a line feed (and possibly a carriage return before that). Let's use a helper to read such files and to remove the line ending. Signed-off-by: Johannes Schindelin --- sequencer.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sequencer.c b/sequencer.c index 6c35fe80b9..086cd0b4be 100644 --- a/sequencer.c +++ b/sequencer.c @@ -242,6 +242,37 @@ static int write_message(struct strbuf *msgbuf, const char *filename) return 0; } +/* + * Reads a file that was presumably written by a shell script, i.e. + * with an end-of-line marker that needs to be stripped. + * + * Returns 1 if the file was read, 0 if it could not be read or does not exist. + */ +static int read_oneliner(struct strbuf *buf, + const char *path, int skip_if_empty) +{ + int orig_len = buf->len; + + if (!file_exists(path)) + return 0; + + if (strbuf_read_file(buf, path, 0) < 0) { + warning_errno("could not read '%s'", path); + return 0; + } + + if (buf->len > orig_len && buf->buf[buf->len - 1] == '\n') { + if (--buf->len > orig_len && buf->buf[buf->len - 1] == '\r') + --buf->len; + buf->buf[buf->len] = '\0'; + } + + if (skip_if_empty && buf->len == orig_len) + return 0; + + return 1; +} + static struct tree *empty_tree(void) { return lookup_tree(EMPTY_TREE_SHA1_BIN); From 6751e5b382890eedbd7919fd98e75b238bc25a46 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 18 May 2016 14:49:39 +0200 Subject: [PATCH 41/90] sequencer: prepare for rebase -i's GPG settings The rebase command sports a `--gpg-sign` option that is heeded by the interactive rebase. This patch teaches the sequencer that trick, as part of the bigger effort to make the sequencer the work horse of the interactive rebase. Signed-off-by: Johannes Schindelin --- sequencer.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 086cd0b4be..bf02565b4c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -15,6 +15,7 @@ #include "merge-recursive.h" #include "refs.h" #include "argv-array.h" +#include "quote.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -33,6 +34,11 @@ static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * The following files are written by git-rebase just after parsing the + * command-line (and are only consumed, not modified, by the sequencer). + */ +static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -132,6 +138,16 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } +static const char *gpg_sign_opt_quoted(struct replay_opts *opts) +{ + static struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + if (opts->gpg_sign) + sq_quotef(&buf, "-S%s", opts->gpg_sign); + return buf.buf; +} + void *sequencer_entrust(struct replay_opts *opts, void *to_free) { ALLOC_GROW(opts->owned, opts->owned_nr + 1, opts->owned_alloc); @@ -478,17 +494,20 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, if (is_rebase_i(opts)) { env = read_author_script(); - if (!env) + if (!env) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + return error("You have staged changes in your working " "tree. If these changes are meant to be\n" "squashed into the previous commit, run:\n\n" - " git commit --amend $gpg_sign_opt_quoted\n\n" + " git commit --amend %s\n\n" "If they are meant to go into a new commit, " "run:\n\n" - " git commit $gpg_sign_opt_quoted\n\n" + " git commit %s\n\n" "In both cases, once you're done, continue " "with:\n\n" - " git rebase --continue\n"); + " git rebase --continue\n", gpg_opt, gpg_opt); + } } argv_array_init(&array); @@ -980,8 +999,21 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static int read_populate_opts(struct replay_opts *opts) { - if (is_rebase_i(opts)) + if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + + if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) { + if (!starts_with(buf.buf, "-S")) + strbuf_reset(&buf); + else { + opts->gpg_sign = buf.buf + 2; + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + } + } + return 0; + } if (!file_exists(git_path_opts_file())) return 0; From fcffd092de4e432b161a78da63bdd4ab22b89680 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:13:26 +0100 Subject: [PATCH 42/90] sequencer: allow editing the commit message on a case-by-case basis In the upcoming commits, we will implement more and more of rebase -i's functionality. One particular feature of the commands to come is that some of them allow editing the commit message while others don't, i.e. we cannot define in the replay_opts whether the commit message should be edited or not. Let's add a new parameter to the sequencer_commit() function. Previously, it was the duty of the caller to ensure that the opts->edit setting indicates whether to let the user edit the commit message or not, indicating that it is an "all or nothing" setting, i.e. that the sequencer wants to let the user edit *all* commit message, or none at all. In the upcoming rebase -i mode, it will depend on the particular command that is currently executed, though. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++--- sequencer.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index bf02565b4c..6e9732c5a5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,7 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty) + int allow_empty, int edit) { char **env = NULL; struct argv_array array; @@ -520,7 +520,7 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "-s"); if (defmsg) argv_array_pushl(&array, "-F", defmsg, NULL); - if (opts->edit) + if (edit) argv_array_push(&array, "-e"); else if (!opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) @@ -786,7 +786,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow); + opts, allow, opts->edit); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index 16deb6c85d..7f5222f6c2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,7 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty); + int allow_empty, int edit); extern const char sign_off_header[]; From 13ff3687838f791c8f8b9b6ab45739251fcde453 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 15:01:48 +0100 Subject: [PATCH 43/90] sequencer: support amending commits This teaches the sequencer_commit() function to take an argument that will allow us to implement "todo" commands that need to amend the commit messages ("fixup", "squash" and "reword"). Signed-off-by: Johannes Schindelin --- sequencer.c | 6 ++++-- sequencer.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6e9732c5a5..60b522e3f0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,7 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit) + int allow_empty, int edit, int amend) { char **env = NULL; struct argv_array array; @@ -514,6 +514,8 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); + if (amend) + argv_array_push(&array, "--amend"); if (opts->gpg_sign) argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) @@ -786,7 +788,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit); + opts, allow, opts->edit, 0); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index 7f5222f6c2..c45f5c4d62 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,7 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit); + int allow_empty, int edit, int amend); extern const char sign_off_header[]; From 7be0fff8bb35b6122ca70982394fdea6198ecf55 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 15:01:48 +0100 Subject: [PATCH 44/90] sequencer: support cleaning up commit messages The sequencer_commit() function already knows how to amend commits, and with this new option, it can also clean up commit messages (i.e. strip out commented lines). This is needed to implement rebase -i's 'fixup' and 'squash' commands as sequencer commands. Signed-off-by: Johannes Schindelin --- sequencer.c | 10 +++++++--- sequencer.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 60b522e3f0..75772b808a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,8 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit, int amend) + int allow_empty, int edit, int amend, + int cleanup_commit_message) { char **env = NULL; struct argv_array array; @@ -522,9 +523,12 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "-s"); if (defmsg) argv_array_pushl(&array, "-F", defmsg, NULL); + if (cleanup_commit_message) + argv_array_push(&array, "--cleanup=strip"); if (edit) argv_array_push(&array, "-e"); - else if (!opts->signoff && !opts->record_origin && + else if (!cleanup_commit_message && + !opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) argv_array_push(&array, "--cleanup=verbatim"); @@ -788,7 +792,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0); + opts, allow, opts->edit, 0, 0); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index c45f5c4d62..688fff16dd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,8 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit, int amend); + int allow_empty, int edit, int amend, + int cleanup_commit_message); extern const char sign_off_header[]; From 03dcd1112a6c5a26edfd48e7c0e74cd006635f29 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 14:48:50 +0200 Subject: [PATCH 45/90] sequencer: remember do_recursive_merge()'s return value The return value of do_recursive_merge() may be positive (indicating merge conflicts), so let's OR later error conditions so as not to overwrite them with 0. This is not yet a problem, but preparing for the patches to come: we will teach the sequencer to do rebase -i's job. Signed-off-by: Johannes Schindelin --- sequencer.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 75772b808a..7953a054f8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -630,7 +630,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res = 0, unborn = 0, allow; if (opts->no_commit) { /* @@ -741,7 +741,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, + res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) return res; @@ -750,7 +750,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, struct commit_list *common = NULL; struct commit_list *remotes = NULL; - res = write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); @@ -787,11 +787,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, allow = allow_empty(opts, commit); if (allow < 0) { - res = allow; + res |= allow; goto leave; } if (!opts->no_commit) - res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), + res |= sequencer_commit(opts->edit ? + NULL : git_path_merge_msg(), opts, allow, opts->edit, 0, 0); leave: From e9747b8367cc054ef6d17edeefb177d920dd8c74 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 15 Apr 2016 17:41:32 +0200 Subject: [PATCH 46/90] sequencer: left-trim lines read from the script Interactive rebase's scripts may be indented; we need to handle this case, too, now that we prepare the sequencer to process interactive rebases. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7953a054f8..5e5d11388c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -875,6 +875,9 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) char *end_of_object_name; int i, saved, status, padding; + /* left-trim */ + bol += strspn(bol, " \t"); + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; From 4cb1f073190189caac08d1fc6154264c9efa8904 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Aug 2016 15:06:53 +0200 Subject: [PATCH 47/90] sequencer: refactor write_message() The write_message() function safely writes an strbuf to a file. Sometimes it is inconvenient to require an strbuf, though: the text to be written may not be stored in a strbuf, or the strbuf should not be released after writing. Let's refactor "safely writing string to a file" into write_with_lock_file(), and make write_message() use it. The new function makes it easy to create new convenience function write_file_gently(); as some of the upcoming callers of this new function would want to append a newline character, add a flag for it in write_file_gently(), and thus in write_with_lock_file(). Signed-off-by: Johannes Schindelin --- sequencer.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 5e5d11388c..aa949d45cf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -242,22 +242,37 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static int write_message(struct strbuf *msgbuf, const char *filename) +static int write_with_lock_file(const char *filename, + const void *buf, size_t len, int append_eol) { static struct lock_file msg_file; int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0); if (msg_fd < 0) return error_errno(_("Could not lock '%s'"), filename); - if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - return error_errno(_("Could not write to %s"), filename); - strbuf_release(msgbuf); + if (write_in_full(msg_fd, buf, len) < 0) + return error_errno(_("Could not write to '%s'"), filename); + if (append_eol && write(msg_fd, "\n", 1) < 0) + return error_errno(_("Could not write eol to '%s"), filename); if (commit_lock_file(&msg_file) < 0) return error(_("Error wrapping up %s."), filename); return 0; } +static int write_message(struct strbuf *msgbuf, const char *filename) +{ + int res = write_with_lock_file(filename, msgbuf->buf, msgbuf->len, 0); + strbuf_release(msgbuf); + return res; +} + +static int write_file_gently(const char *filename, + const char *text, int append_eol) +{ + return write_with_lock_file(filename, text, strlen(text), append_eol); +} + /* * Reads a file that was presumably written by a shell script, i.e. * with an end-of-line marker that needs to be stripped. From 490b79173b8f981fa2d39a8b4d09c5e36c729705 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Feb 2016 19:21:31 +0100 Subject: [PATCH 48/90] sequencer: remove overzealous assumption in rebase -i mode The sequencer was introduced to make the cherry-pick and revert functionality available as library function, with the original idea being to extend the sequencer to also implement the rebase -i functionality. The test to ensure that all of the commands in the script are identical to the overall operation does not mesh well with that. Therefore let's disable the test in rebase -i mode. While at it, error out early if the "instruction sheet" (i.e. the todo script) could not be parsed. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index aa949d45cf..514424567e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -963,7 +963,10 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (!res) { + if (res) + return error(_("Unusable instruction sheet: %s"), todo_file); + + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; int i; @@ -977,8 +980,6 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("Cannot revert during a cherry-pick.")); } - if (res) - return error(_("Unusable instruction sheet: %s"), todo_file); return 0; } From 9fb7506e23fb4821f4721a8dffa9a518d0cbaa1b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:20:26 +0200 Subject: [PATCH 49/90] sequencer: mark action_name() for translation The definition of this function goes back all the way to 043a449 (sequencer: factor code out of revert builtin, 2012-01-11), long before a serious effort was made to translate all the error messages. It is slightly out of the context of the current patch series (whose purpose it is to re-implement the performance critical parts of the interactive rebase in C) to make the error messages in the sequencer translatable, but what the heck. We'll just do it while we're looking at this part of the code. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 514424567e..1e7f29eaed 100644 --- a/sequencer.c +++ b/sequencer.c @@ -176,7 +176,7 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); } struct commit_message { @@ -312,10 +312,10 @@ static struct tree *empty_tree(void) static int error_dirty_index(struct replay_opts *opts) { if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); + return error_resolve_conflict(_(action_name(opts))); error(_("Your local changes would be overwritten by %s."), - action_name(opts)); + _(action_name(opts))); if (advice_commit_before_merge) advise(_("Commit your changes or stash them to proceed.")); @@ -333,7 +333,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (checkout_fast_forward(from, to, 1)) return -1; /* the callee should have complained already */ - strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); + strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); transaction = ref_transaction_begin(&err); if (!transaction || @@ -409,7 +409,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ return error(_("%s: Unable to write new index file"), - action_name(opts)); + _(action_name(opts))); rollback_lock_file(&index_lock); if (opts->signoff) @@ -840,14 +840,14 @@ static int read_and_refresh_cache(struct replay_opts *opts) if (read_index_preload(&the_index, NULL) < 0) { rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), - action_name(opts)); + _(action_name(opts))); } refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), - action_name(opts)); + _(action_name(opts))); } } rollback_lock_file(&index_lock); From 32645fc4f2c0c7ba615ed719dee981afca91bde2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:46:21 +0200 Subject: [PATCH 50/90] sequencer: quote filenames in error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the code consistent by fixing quite a couple of error messages. Suggested by Jakub Narębski. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index 1e7f29eaed..465e018913 100644 --- a/sequencer.c +++ b/sequencer.c @@ -255,7 +255,7 @@ static int write_with_lock_file(const char *filename, if (append_eol && write(msg_fd, "\n", 1) < 0) return error_errno(_("Could not write eol to '%s"), filename); if (commit_lock_file(&msg_file) < 0) - return error(_("Error wrapping up %s."), filename); + return error(_("Error wrapping up '%s'."), filename); return 0; } @@ -955,16 +955,16 @@ static int read_populate_todo(struct todo_list *todo_list, strbuf_reset(&todo_list->buf); fd = open(todo_file, O_RDONLY); if (fd < 0) - return error_errno(_("Could not open %s"), todo_file); + return error_errno(_("Could not open '%s'"), todo_file); if (strbuf_read(&todo_list->buf, fd, 0) < 0) { close(fd); - return error(_("Could not read %s."), todo_file); + return error(_("Could not read '%s'."), todo_file); } close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); if (res) - return error(_("Unusable instruction sheet: %s"), todo_file); + return error(_("Unusable instruction sheet: '%s'"), todo_file); if (!is_rebase_i(opts)) { enum todo_command valid = @@ -1050,7 +1050,7 @@ static int read_populate_opts(struct replay_opts *opts) * are pretty certain that it is syntactically correct. */ if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0) - return error(_("Malformed options sheet: %s"), + return error(_("Malformed options sheet: '%s'"), git_path_opts_file()); return 0; } @@ -1093,7 +1093,7 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - return error_errno(_("Could not create sequencer directory %s"), + return error_errno(_("Could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; } @@ -1112,12 +1112,12 @@ static int save_head(const char *head) strbuf_addf(&buf, "%s\n", head); if (write_in_full(fd, buf.buf, buf.len) < 0) { rollback_lock_file(&head_lock); - return error_errno(_("Could not write to %s"), + return error_errno(_("Could not write to '%s'"), git_path_head_file()); } if (commit_lock_file(&head_lock) < 0) { rollback_lock_file(&head_lock); - return error(_("Error wrapping up %s."), git_path_head_file()); + return error(_("Error wrapping up '%s'."), git_path_head_file()); } return 0; } @@ -1162,9 +1162,9 @@ int sequencer_rollback(struct replay_opts *opts) return rollback_single_pick(); } if (!f) - return error_errno(_("cannot open %s"), git_path_head_file()); + return error_errno(_("cannot open '%s'"), git_path_head_file()); if (strbuf_getline_lf(&buf, f)) { - error(_("cannot read %s: %s"), git_path_head_file(), + error(_("cannot read '%s': %s"), git_path_head_file(), ferror(f) ? strerror(errno) : _("unexpected end of file")); fclose(f); goto fail; @@ -1203,7 +1203,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) todo_list->buf.len - offset) < 0) return error_errno(_("Could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) - return error(_("Error wrapping up %s."), todo_path); + return error(_("Error wrapping up '%s'."), todo_path); return 0; } From 5e602e28be4550882931f6008c9180c909482e2f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:47:40 +0200 Subject: [PATCH 51/90] sequencer: remove bogus hint for translators When translating error messages, we need to be careful *not* to translate the todo commands such as "pick", "reword", etc because they are commands, and Git would not understand translated versions of those commands. Therefore, translating the commands in the error messages would simply be misleading. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 465e018913..cdff0f1954 100644 --- a/sequencer.c +++ b/sequencer.c @@ -697,8 +697,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, return fast_forward_to(commit->object.oid.hash, head, unborn, opts); if (parent && parse_commit(parent) < 0) - /* TRANSLATORS: The first %s will be "revert" or - "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), command_to_string(command), oid_to_hex(&parent->object.oid)); From 1a070fce7cbec1060e4f9136a55a3ddd5e633112 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 13:19:54 +0100 Subject: [PATCH 52/90] pull: drop confusing prefix parameter of die_on_unclean_work_tree() In cmd_pull(), when verifying that there are no changes preventing a rebasing pull, we diligently pass the prefix parameter to the die_on_unclean_work_tree() function which in turn diligently passes it to the has_unstaged_changes() and has_uncommitted_changes() functions. The casual reader might now be curious (as this developer was) whether that means that calling `git pull --rebase` in a subdirectory will ignore unstaged changes in other parts of the working directory. And be puzzled that `git pull --rebase` (correctly) complains about those changes outside of the current directory. The puzzle is easily resolved: while we take pains to pass around the prefix and even pass it to init_revisions(), the fact that no paths are passed to init_revisions() ensures that the prefix is simply ignored. That, combined with the fact that we will *always* want a *full* working directory check before running a rebasing pull, is reason enough to simply do away with the actual prefix parameter and to pass NULL instead, as if we were running this from the top-level working directory anyway. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index 398aae16c0..d4bd6350f0 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -328,12 +328,12 @@ static int git_pull_config(const char *var, const char *value, void *cb) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -static int has_unstaged_changes(const char *prefix) +static int has_unstaged_changes(void) { struct rev_info rev_info; int result; - init_revisions(&rev_info, prefix); + init_revisions(&rev_info, NULL); DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); diff_setup_done(&rev_info.diffopt); @@ -344,7 +344,7 @@ static int has_unstaged_changes(const char *prefix) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -static int has_uncommitted_changes(const char *prefix) +static int has_uncommitted_changes(void) { struct rev_info rev_info; int result; @@ -352,7 +352,7 @@ static int has_uncommitted_changes(const char *prefix) if (is_cache_unborn()) return 0; - init_revisions(&rev_info, prefix); + init_revisions(&rev_info, NULL); DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); add_head_to_pending(&rev_info); @@ -365,7 +365,7 @@ static int has_uncommitted_changes(const char *prefix) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -static void die_on_unclean_work_tree(const char *prefix) +static void die_on_unclean_work_tree(void) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); int do_die = 0; @@ -375,12 +375,12 @@ static void die_on_unclean_work_tree(const char *prefix) update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); - if (has_unstaged_changes(prefix)) { + if (has_unstaged_changes()) { error(_("Cannot pull with rebase: You have unstaged changes.")); do_die = 1; } - if (has_uncommitted_changes(prefix)) { + if (has_uncommitted_changes()) { if (do_die) error(_("Additionally, your index contains uncommitted changes.")); else @@ -875,7 +875,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - die_on_unclean_work_tree(prefix); + die_on_unclean_work_tree(); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); From 6c6055205538f29f05b46d228672f3bbdb8da7b0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 14:57:54 +0100 Subject: [PATCH 53/90] pull: make code more similar to the shell script again When converting the pull command to a builtin, the require_clean_work_tree() function was renamed and the pull-specific parts hard-coded. This makes it impossible to reuse the code, so let's modify the code to make it more similar to the original shell script again. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index d4bd6350f0..a3ed0541d2 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -365,10 +365,11 @@ static int has_uncommitted_changes(void) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -static void die_on_unclean_work_tree(void) +static int require_clean_work_tree(const char *action, const char *hint, + int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int do_die = 0; + int err = 0; hold_locked_index(lock_file, 0); refresh_cache(REFRESH_QUIET); @@ -376,20 +377,27 @@ static void die_on_unclean_work_tree(void) rollback_lock_file(lock_file); if (has_unstaged_changes()) { - error(_("Cannot pull with rebase: You have unstaged changes.")); - do_die = 1; + error(_("Cannot %s: You have unstaged changes."), _(action)); + err = 1; } if (has_uncommitted_changes()) { - if (do_die) + if (err) error(_("Additionally, your index contains uncommitted changes.")); else - error(_("Cannot pull with rebase: Your index contains uncommitted changes.")); - do_die = 1; + error(_("Cannot %s: Your index contains uncommitted changes."), + _(action)); + err = 1; } - if (do_die) - exit(1); + if (err) { + if (hint) + error("%s", hint); + if (!gently) + exit(err); + } + + return err; } /** @@ -875,7 +883,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - die_on_unclean_work_tree(); + require_clean_work_tree(N_("pull with rebase"), + "Please commit or stash them.", 0); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); From 7764cd86f84778ec781b32328a339ebc6fc32e46 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 25 Feb 2016 13:46:53 +0100 Subject: [PATCH 54/90] Make the require_clean_work_tree() function truly reusable It is remarkable that libgit.a did not sport this function yet... Let's move it into a more prominent (and into an actually reusable) spot: wt-status.[ch]. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 76 +------------------------------------------------- wt-status.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ wt-status.h | 3 ++ 3 files changed, 79 insertions(+), 75 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index a3ed0541d2..14ef8b5820 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -17,6 +17,7 @@ #include "revision.h" #include "tempfile.h" #include "lockfile.h" +#include "wt-status.h" enum rebase_type { REBASE_INVALID = -1, @@ -325,81 +326,6 @@ static int git_pull_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -/** - * Returns 1 if there are unstaged changes, 0 otherwise. - */ -static int has_unstaged_changes(void) -{ - struct rev_info rev_info; - int result; - - init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - diff_setup_done(&rev_info.diffopt); - result = run_diff_files(&rev_info, 0); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * Returns 1 if there are uncommitted changes, 0 otherwise. - */ -static int has_uncommitted_changes(void) -{ - struct rev_info rev_info; - int result; - - if (is_cache_unborn()) - return 0; - - init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - add_head_to_pending(&rev_info); - diff_setup_done(&rev_info.diffopt); - result = run_diff_index(&rev_info, 1); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * If the work tree has unstaged or uncommitted changes, dies with the - * appropriate message. - */ -static int require_clean_work_tree(const char *action, const char *hint, - int gently) -{ - struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int err = 0; - - hold_locked_index(lock_file, 0); - refresh_cache(REFRESH_QUIET); - update_index_if_able(&the_index, lock_file); - rollback_lock_file(lock_file); - - if (has_unstaged_changes()) { - error(_("Cannot %s: You have unstaged changes."), _(action)); - err = 1; - } - - if (has_uncommitted_changes()) { - if (err) - error(_("Additionally, your index contains uncommitted changes.")); - else - error(_("Cannot %s: Your index contains uncommitted changes."), - _(action)); - err = 1; - } - - if (err) { - if (hint) - error("%s", hint); - if (!gently) - exit(err); - } - - return err; -} - /** * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge * into merge_heads. diff --git a/wt-status.c b/wt-status.c index 539aac15a3..9ab9adc415 100644 --- a/wt-status.c +++ b/wt-status.c @@ -16,6 +16,7 @@ #include "strbuf.h" #include "utf8.h" #include "worktree.h" +#include "lockfile.h" static const char cut_line[] = "------------------------ >8 ------------------------\n"; @@ -2209,3 +2210,77 @@ void wt_status_print(struct wt_status *s) break; } } + +/** + * Returns 1 if there are unstaged changes, 0 otherwise. + */ +static int has_unstaged_changes(void) +{ + struct rev_info rev_info; + int result; + + init_revisions(&rev_info, NULL); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + diff_setup_done(&rev_info.diffopt); + result = run_diff_files(&rev_info, 0); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * Returns 1 if there are uncommitted changes, 0 otherwise. + */ +static int has_uncommitted_changes(void) +{ + struct rev_info rev_info; + int result; + + if (is_cache_unborn()) + return 0; + + init_revisions(&rev_info, NULL); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + add_head_to_pending(&rev_info); + diff_setup_done(&rev_info.diffopt); + result = run_diff_index(&rev_info, 1); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * If the work tree has unstaged or uncommitted changes, dies with the + * appropriate message. + */ +int require_clean_work_tree(const char *action, const char *hint, int gently) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); + int err = 0; + + hold_locked_index(lock_file, 0); + refresh_cache(REFRESH_QUIET); + update_index_if_able(&the_index, lock_file); + rollback_lock_file(lock_file); + + if (has_unstaged_changes()) { + error(_("Cannot %s: You have unstaged changes."), _(action)); + err = 1; + } + + if (has_uncommitted_changes()) { + if (err) + error(_("Additionally, your index contains uncommitted changes.")); + else + error(_("Cannot %s: Your index contains uncommitted changes."), + _(action)); + err = 1; + } + + if (err) { + if (hint) + error("%s", hint); + if (!gently) + exit(err); + } + + return err; +} diff --git a/wt-status.h b/wt-status.h index e401837707..03ecf53de0 100644 --- a/wt-status.h +++ b/wt-status.h @@ -128,4 +128,7 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, . __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); +/* The following function expect that the caller took care of reading the index. */ +int require_clean_work_tree(const char *action, const char *hint, int gently); + #endif /* STATUS_H */ From 903d399005e383b654d7c6d7a1efa7b6b37e0900 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 15:03:03 +0100 Subject: [PATCH 55/90] Export also the has_un{staged,committed}_changed() functions They will be used in the upcoming rebase helper. Signed-off-by: Johannes Schindelin --- wt-status.c | 4 ++-- wt-status.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wt-status.c b/wt-status.c index 9ab9adc415..f1120e6f62 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2214,7 +2214,7 @@ void wt_status_print(struct wt_status *s) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -static int has_unstaged_changes(void) +int has_unstaged_changes(void) { struct rev_info rev_info; int result; @@ -2230,7 +2230,7 @@ static int has_unstaged_changes(void) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -static int has_uncommitted_changes(void) +int has_uncommitted_changes(void) { struct rev_info rev_info; int result; diff --git a/wt-status.h b/wt-status.h index 03ecf53de0..68e367a235 100644 --- a/wt-status.h +++ b/wt-status.h @@ -128,7 +128,9 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, . __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); -/* The following function expect that the caller took care of reading the index. */ +/* The following functions expect that the caller took care of reading the index. */ +int has_unstaged_changes(void); +int has_uncommitted_changes(void); int require_clean_work_tree(const char *action, const char *hint, int gently); #endif /* STATUS_H */ From 6ab8a88714cc30c9b35f11c6b4368c4d745e6624 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 15:04:01 +0200 Subject: [PATCH 56/90] wt-status: teach has_{unstaged,uncommitted}_changes() about submodules Sometimes we are *actually* interested in those changes... For example when an interactive rebase wants to continue with a staged submodule update. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 2 +- wt-status.c | 16 +++++++++------- wt-status.h | 7 ++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index 14ef8b5820..c639167586 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -810,7 +810,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (!autostash) require_clean_work_tree(N_("pull with rebase"), - "Please commit or stash them.", 0); + "Please commit or stash them.", 1, 0); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); diff --git a/wt-status.c b/wt-status.c index f1120e6f62..086ae793f2 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2214,13 +2214,14 @@ void wt_status_print(struct wt_status *s) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -int has_unstaged_changes(void) +int has_unstaged_changes(int ignore_submodules) { struct rev_info rev_info; int result; init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); diff_setup_done(&rev_info.diffopt); result = run_diff_files(&rev_info, 0); @@ -2230,7 +2231,7 @@ int has_unstaged_changes(void) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -int has_uncommitted_changes(void) +int has_uncommitted_changes(int ignore_submodules) { struct rev_info rev_info; int result; @@ -2239,7 +2240,8 @@ int has_uncommitted_changes(void) return 0; init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); add_head_to_pending(&rev_info); diff_setup_done(&rev_info.diffopt); @@ -2251,7 +2253,7 @@ int has_uncommitted_changes(void) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -int require_clean_work_tree(const char *action, const char *hint, int gently) +int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); int err = 0; @@ -2261,12 +2263,12 @@ int require_clean_work_tree(const char *action, const char *hint, int gently) update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); - if (has_unstaged_changes()) { + if (has_unstaged_changes(ignore_submodules)) { error(_("Cannot %s: You have unstaged changes."), _(action)); err = 1; } - if (has_uncommitted_changes()) { + if (has_uncommitted_changes(ignore_submodules)) { if (err) error(_("Additionally, your index contains uncommitted changes.")); else diff --git a/wt-status.h b/wt-status.h index 68e367a235..54fec77032 100644 --- a/wt-status.h +++ b/wt-status.h @@ -129,8 +129,9 @@ __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); /* The following functions expect that the caller took care of reading the index. */ -int has_unstaged_changes(void); -int has_uncommitted_changes(void); -int require_clean_work_tree(const char *action, const char *hint, int gently); +int has_unstaged_changes(int ignore_submodules); +int has_uncommitted_changes(int ignore_submodules); +int require_clean_work_tree(const char *action, const char *hint, + int ignore_submodules, int gently); #endif /* STATUS_H */ From d22181b28722f4b28c21e2c2f02d28aeea9b5071 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:10:43 +0100 Subject: [PATCH 57/90] sequencer: support a new action: 'interactive rebase' This patch introduces a new action for the sequencer. It really does not do a whole lot of its own right now, but lays the ground work for patches to come. The intention, of course, is to finally make the sequencer the work horse of the interactive rebase (the original idea behind the "sequencer" concept). Signed-off-by: Johannes Schindelin --- sequencer.c | 32 +++++++++++++++++++++++++++++--- sequencer.h | 3 ++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index cdff0f1954..c89a0cffa1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -28,6 +28,14 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -43,16 +51,20 @@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } @@ -176,7 +188,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -407,7 +427,10 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* + * TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -1192,6 +1215,9 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("Could not lock '%s'"), todo_path); diff --git a/sequencer.h b/sequencer.h index 688fff16dd..edd7d4a8d1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { From efd0ff80a154dd06e5d89c582129fb1e6b07f9fb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:35 +0200 Subject: [PATCH 58/90] sequencer (rebase -i): implement the 'noop' command The 'noop' command is probably the most boring of all rebase -i commands to support in the sequencer. Which makes it an excellent candidate for this first stab to add support for rebase -i's commands to the sequencer. For the moment, let's also treat empty lines and commented-out lines as 'noop'; We will refine that handling later in this patch series. To make it easier to identify "classes" of todo_commands (such as: determine whether a command is pick-like, i.e. handles a single commit), let's enforce a certain order of said commands. Signed-off-by: Johannes Schindelin --- sequencer.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index c89a0cffa1..a148d3a9ad 100644 --- a/sequencer.c +++ b/sequencer.c @@ -642,14 +642,23 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) 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_REVERT, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP }; static const char *todo_command_strings[] = { "pick", - "revert" + "revert", + "noop" }; static const char *command_to_string(const enum todo_command command) @@ -914,6 +923,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_NOOP; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; @@ -922,6 +939,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= ARRAY_SIZE(todo_command_strings)) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) @@ -1281,7 +1305,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (item->command <= TODO_REVERT) + res = do_pick_commit(item->command, item->commit, + opts); + else if (item->command != TODO_NOOP) + return error("Unknown command %d", item->command); + todo_list->current++; if (res) return res; From 3cc96877b948adab7e3da23100ce096b83a1f826 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:47 +0200 Subject: [PATCH 59/90] sequencer (rebase -i): implement the 'edit' command This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin --- sequencer.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index a148d3a9ad..36a4c6b20b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,6 +16,7 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" +#include "log-tree.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -42,6 +43,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the long commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -651,6 +666,7 @@ enum todo_command { /* commands that handle commits */ TODO_PICK = 0, TODO_REVERT, + TODO_EDIT, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -658,6 +674,7 @@ enum todo_command { static const char *todo_command_strings[] = { "pick", "revert", + "edit", "noop" }; @@ -1290,9 +1307,85 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *commit_buffer = get_commit_buffer(commit, NULL), *subject; + int res = 0; + + if (write_file_gently(rebase_path_stopped_sha(), + short_commit_name(commit), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno("could not open '%s'", buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + find_commit_subject(commit_buffer, &subject); + res |= write_file_gently(buf.buf, subject, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot read HEAD"); + + return write_file_gently(rebase_path_amend(), sha1_to_hex(head), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } + else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1305,9 +1398,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_REVERT) + if (item->command <= TODO_EDIT) { res = do_pick_commit(item->command, item->commit, opts); + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning("Stopped at %s... %.*s", + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + } else if (item->command != TODO_NOOP) return error("Unknown command %d", item->command); @@ -1316,6 +1420,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return res; } + if (is_rebase_i(opts)) { + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory From 957836bbde516cc3a0fcd1b3b91971600972b0ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:01 +0200 Subject: [PATCH 60/90] sequencer (rebase -i): implement the 'exec' command The 'exec' command is a little special among rebase -i's commands, as it does *not* have a SHA-1 as first parameter. Instead, everything after the `exec` command is treated as command-line to execute. Let's reuse the arg/arg_len fields of the todo_item structure (which hold the oneline for pick/edit commands) to point to the command-line. Signed-off-by: Johannes Schindelin --- sequencer.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sequencer.c b/sequencer.c index 36a4c6b20b..166444c1f1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,7 @@ #include "argv-array.h" #include "quote.h" #include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -667,6 +668,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + /* commands that do something else than handling a single commit */ + TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -675,6 +678,7 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "exec", "noop" }; @@ -969,6 +973,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -1383,6 +1393,47 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("Could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning("Execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n", + command_line, + dirty ? "and made changes to the index and/or the " + "working tree\n" : ""); + if (status == 127) + /* command not found */ + status = 1; + } + else if (dirty) { + warning("Execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n", command_line); + status = 1; + } + + return status; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1412,6 +1463,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) !res); } } + else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; + } else if (item->command != TODO_NOOP) return error("Unknown command %d", item->command); From 1fc272ffee92254f96ff18b5bca032c73b5d67bb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 15:30:26 +0200 Subject: [PATCH 61/90] sequencer (rebase -i): learn about the 'verbose' mode When calling `git rebase -i -v`, the user wants to see some statistics after the commits were rebased. Let's show some. The strbuf we use to perform that task will be used for other things in subsequent commits, hence it is declared and initialized in a wider scope than strictly needed here. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 ++++++++++++++++++++++ sequencer.h | 1 + 2 files changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index 166444c1f1..d0578bbbeb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -63,6 +63,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1110,6 +1112,9 @@ static int read_populate_opts(struct replay_opts *opts) } } + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + return 0; } @@ -1480,9 +1485,26 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + + if (opts->verbose) { + const char *argv[] = { + "diff-tree", "--stat", NULL, NULL + }; + + if (!read_oneliner(&buf, rebase_path_orig_head(), 0)) + return error("Could not read %s", + rebase_path_orig_head()); + strbuf_addstr(&buf, "..HEAD"); + argv[2] = buf.buf; + run_command_v_opt(argv, RUN_GIT_CMD); + strbuf_reset(&buf); + } + strbuf_release(&buf); } /* diff --git a/sequencer.h b/sequencer.h index edd7d4a8d1..fd2a719e36 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; From 31a06ce5452988c7516676370e0b05df68941fae Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:18:50 +0100 Subject: [PATCH 62/90] sequencer (rebase -i): write the 'done' file In the interactive rebase, commands that were successfully processed are not simply discarded, but appended to the 'done' file instead. This is used e.g. to display the current state to the user in the output of `git status` or the progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sequencer.c b/sequencer.c index d0578bbbeb..b49d087191 100644 --- a/sequencer.c +++ b/sequencer.c @@ -38,6 +38,12 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -1284,6 +1290,20 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("Could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("Error wrapping up '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (offset > prev_offset && write_in_full(fd, + todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) + return error_errno(_("Could not write to '%s'"), + done_path); + close(fd); + } return 0; } From ab13304405e3dc0d78ef8668c0e6eecf6def6e65 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:49:51 +0200 Subject: [PATCH 63/90] sequencer (rebase -i): add support for the 'fixup' and 'squash' commands This is a huge patch, and at the same time a huge step forward to execute the performance-critical parts of the interactive rebase in a builtin command. Since 'fixup' and 'squash' are not only similar, but also need to know about each other (we want to reduce a series of fixups/squashes into a single, final commit message edit, from the user's point of view), we really have to implement them both at the same time. Most of the actual work is done by the existing code path that already handles the "pick" and the "edit" commands; We added support for other features (e.g. to amend the commit message) in the patches leading up to this one, yet there are still quite a few bits in this patch that simply would not make sense as individual patches (such as: determining whether there was anything to "fix up" in the "todo" script, etc). In theory, it would be possible to reuse the fast-forward code path also for the fixup and the squash code paths, but in practice this would make the code less readable. The end result cannot be fast-forwarded anyway, therefore let's just extend the cherry-picking code path for now. Since the sequencer parses the entire `git-rebase-todo` script in one go, fixup or squash commands without a preceding pick can be reported early (in git-rebase--interactive, we could only report such errors just before executing the fixup/squash). Signed-off-by: Johannes Schindelin --- sequencer.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 218 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index b49d087191..f6bc4318e1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -44,6 +44,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -676,6 +705,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_FIXUP, + TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ @@ -686,6 +717,8 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "fixup", + "squash", "exec", "noop" }; @@ -697,16 +730,116 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static int is_fixup(enum todo_command command) +{ + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + char *p, *p2; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("Could not read '%s'"), + rebase_path_squash_msg()); + + if (buf.buf[0] == '\n' || !skip_prefix(buf.buf + 1, + " This is a combination of ", + (const char **)&p)) + return error(_("Unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(strchrnul(buf.buf, '\n') - buf.buf), + buf.buf); + count = strtol(p, &p2, 10); + + if (count < 1 || *p2 != ' ') + return error(_("Invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(strchrnul(buf.buf, '\n') - buf.buf), + buf.buf); + + sprintf((char *)p, "%d", ++count); + if (!*p2) + *p2 = ' '; + else { + *(++p2) = 'c'; + strbuf_insert(&buf, p2 - buf.buf, " ", 1); + } + } + else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("Need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("Could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + return error(_("Could not read HEAD's commit message")); + + body = strstr(head_message, "\n\n"); + if (!body) + body = ""; + else + body = skip_blank_lines(body + 2); + if (write_file_gently(rebase_path_fixup_msg(), body, 0)) + return error(_("Cannot write '%s'"), + rebase_path_fixup_msg()); + + count = 2; + strbuf_addf(&buf, _("%c This is a combination of 2 commits.\n" + "%c The first commit's message is:\n\n%s"), + comment_line_char, comment_line_char, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("Could not read commit message of %s"), + oid_to_hex(&commit->object.oid)); + body = strstr(message, "\n\n"); + if (!body) + body = ""; + else + body = skip_blank_lines(body + 2); + + if (command == TODO_SQUASH) { + unlink(rebase_path_fixup_msg()); + strbuf_addf(&buf, _("\n%c This is the commit message #%d:\n" + "\n%s"), + comment_line_char, count, body); + } + else if (command == TODO_FIXUP) { + strbuf_addf(&buf, _("\n%c The commit message #%d " + "will be skipped:\n\n"), + comment_line_char, count); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } + else + return error(_("Unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + return write_message(&buf, rebase_path_squash_msg()); +} static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) + struct replay_opts *opts, int final_fixup) { + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res = 0, unborn = 0, allow; + int res = 0, unborn = 0, amend = 0, allow; if (opts->no_commit) { /* @@ -752,7 +885,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || (!parent && unborn))) return fast_forward_to(commit->object.oid.hash, head, unborn, opts); @@ -814,6 +947,28 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } + if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } + else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error("Could not rename %s to " + "%s", rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); @@ -865,9 +1020,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res |= sequencer_commit(opts->edit ? - NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); + res |= sequencer_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -1007,7 +1166,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); @@ -1017,8 +1176,15 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res |= error(_("Invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error("Cannot '%s' without a previous commit", + command_to_string(item->command)); + else if (item->command != TODO_NOOP) + fixup_okay = 1; p = *eol ? eol + 1 : eol; } if (!todo_list->nr) @@ -1418,6 +1584,20 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error("Could not rename %s to %s", + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error("Could not copy %s to %s", rebase_path_message(), + git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + static int do_exec(const char *command_line) { const char *child_argv[] = { NULL, NULL }; @@ -1459,6 +1639,21 @@ static int do_exec(const char *command_line) return status; } +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (todo_list->items[i].command < TODO_NOOP) + break; + return 1; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1474,9 +1669,15 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_EDIT) { + if (is_rebase_i(opts)) { + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { res = do_pick_commit(item->command, item->commit, - opts); + opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) @@ -1487,6 +1688,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); @@ -1581,7 +1788,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) From 6d2c4aba0f224971f81bb563fdf962705cfc25a7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:09 +0200 Subject: [PATCH 64/90] sequencer (rebase -i): implement the short commands For users' convenience, most rebase commands can be abbreviated, e.g. 'p' instead of 'pick' and 'x' instead of 'exec'. Let's teach the sequencer to handle those abbreviated commands just fine. Signed-off-by: Johannes Schindelin --- sequencer.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sequencer.c b/sequencer.c index f6bc4318e1..e0c63187e7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -713,20 +713,23 @@ enum todo_command { TODO_NOOP }; -static const char *todo_command_strings[] = { - "pick", - "revert", - "edit", - "fixup", - "squash", - "exec", - "noop" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" } }; static const char *command_to_string(const enum todo_command command) { - if (command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if (command < ARRAY_SIZE(todo_command_info)) + return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1119,12 +1122,17 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; + item->command = i; + break; + } + if (i >= ARRAY_SIZE(todo_command_info)) return -1; if (item->command == TODO_NOOP) { @@ -1309,7 +1317,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) From 22118668ccd309f311f1ce67fdce53e5d926b107 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:12 +0200 Subject: [PATCH 65/90] sequencer (rebase -i): write an author-script file When the interactive rebase aborts, it writes out an author-script file to record the author information for the current commit. As we are about to teach the sequencer how to perform the actions behind an interactive rebase, it needs to write those author-script files, too. Signed-off-by: Johannes Schindelin --- sequencer.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index e0c63187e7..5485c131cf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -528,6 +528,51 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } + else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\n"); + return write_message(&buf, rebase_path_author_script()); +} + static char **read_author_script(void) { struct strbuf script = STRBUF_INIT; @@ -972,7 +1017,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res |= -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) From c62d44a1d56373e068d7bf19eaa678a3e9849a0b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 17:12:20 +0100 Subject: [PATCH 66/90] sequencer (rebase -i): allow continuing with staged changes When an interactive rebase is interrupted, the user may stage changes before continuing, and we need to commit those changes in that case. Signed-off-by: Johannes Schindelin --- sequencer.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sequencer.c b/sequencer.c index 5485c131cf..2f0abe2e6b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1806,6 +1806,41 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("Cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) + return 0; + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot amend non-existing commit"); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error("Invalid file: %s", rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error("Invalid contents: %s", + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error("\nYou have uncommitted changes in your " + "working tree. Please, commit them\nfirst and " + "then run 'git rebase --continue' again."); + + strbuf_release(&rev); + amend = 1; + } + + if (sequencer_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error("Could not commit staged changes."); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1814,6 +1849,10 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) From 626d53682dac88e5d7092c839615fb53a63688a3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:29:21 +0200 Subject: [PATCH 67/90] sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed The scripted version of the interactive rebase already does that. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 2f0abe2e6b..e49e83d118 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1812,8 +1812,13 @@ static int commit_staged_changes(struct replay_opts *opts) if (has_unstaged_changes(1)) return error(_("Cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error("Could not remove CHERRY_PICK_HEAD"); return 0; + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; From b29541783a090e7940520066c3f9c2740b57895b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 31 Aug 2016 08:43:51 +0200 Subject: [PATCH 68/90] sequencer (rebase -i): skip some revert/cherry-pick specific code path When a cherry-pick continues without a "todo script", the intention is simply to pick a single commit. However, when an interactive rebase is continued without a "todo script", it means that the last command has been completed and that we now need to clean up. This commit guards the revert/cherry-pick specific steps so that they are not executed in rebase -i mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index e49e83d118..59819dc95b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1858,25 +1858,28 @@ int sequencer_continue(struct replay_opts *opts) if (commit_staged_changes(opts)) return -1; } - if (!file_exists(get_todo_path(opts))) + else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; } - if (index_differs_from("HEAD", 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); From 850d17b8721caa536138e7a0918b296eb83bf01c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:54 +0200 Subject: [PATCH 69/90] sequencer (rebase -i): the todo can be empty when continuing When the last command of an interactive rebase fails, the user needs to resolve the problem and then continue the interactive rebase. Naturally, the todo script is empty by then. So let's not complain about that! To that end, let's move that test out of the function that parses the todo script, and into the more high-level function read_populate_todo(). This is also necessary by now because the lower-level parse_insn_buffer() has no idea whether we are performing an interactive rebase or not. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 59819dc95b..c301253e9b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1242,8 +1242,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) fixup_okay = 1; p = *eol ? eol + 1 : eol; } - if (!todo_list->nr) - return error(_("No commits parsed.")); + return res; } @@ -1267,6 +1266,10 @@ static int read_populate_todo(struct todo_list *todo_list, if (res) return error(_("Unusable instruction sheet: '%s'"), todo_file); + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("No commits parsed.")); + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; From f46d14866d4024f1fec2f2bd91ab161444400978 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:12:54 +0200 Subject: [PATCH 70/90] sequencer (rebase -i): update refs after a successful rebase An interactive rebase operates on a detached HEAD (to keep the reflog of the original branch relatively clean), and updates the branch only at the end. Now that the sequencer learns to perform interactive rebases, it also needs to learn the trick to update the branch before removing the directory containing the state of the interactive rebase. Signed-off-by: Johannes Schindelin --- sequencer.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index c301253e9b..b9b952f2e1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -100,6 +100,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1770,12 +1772,39 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { - struct strbuf buf = STRBUF_INIT; + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + unsigned char head[20], orig[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot read HEAD"); + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) + return error("Could not read orig-head"); + strbuf_addf(&buf, "rebase -i (finish): %s onto ", + head_ref.buf); + if (!read_oneliner(&buf, rebase_path_onto(), 0)) + return error("Could not read 'onto'"); + if (update_ref(buf.buf, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + return error("Could not update %s", + head_ref.buf); + strbuf_reset(&buf); + strbuf_addf(&buf, + "rebase -i (finish): returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, buf.buf)) + return error("Could not update HEAD to %s", + head_ref.buf); + strbuf_reset(&buf); + } + if (opts->verbose) { const char *argv[] = { "diff-tree", "--stat", NULL, NULL @@ -1790,6 +1819,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) strbuf_reset(&buf); } strbuf_release(&buf); + strbuf_release(&head_ref); } /* From 76d0a9952df8aa363d4ac0e0d079ab4d13194474 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 21:47:32 +0100 Subject: [PATCH 71/90] sequencer (rebase -i): leave a patch upon error When doing an interactive rebase, we want to leave a 'patch' file for further inspection by the user (even if we never tried to actually apply that patch, since we're cherry-picking instead). Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index b9b952f2e1..cf49e5d6cc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1754,6 +1754,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return error_failed_squash(item->commit, opts, item->arg_len, item->arg); } + else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, 0); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); From f478ba71b73eeb0031204d7aa96e973e2f51da24 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:13:31 +0200 Subject: [PATCH 72/90] sequencer (rebase -i): implement the 'reword' command This is now trivial, as all the building blocks are in place: all we need to do is to flip the "edit" switch when committing. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index cf49e5d6cc..8f0a8cea42 100644 --- a/sequencer.c +++ b/sequencer.c @@ -752,6 +752,7 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_REWORD, TODO_FIXUP, TODO_SQUASH, /* commands that do something else than handling a single commit */ @@ -767,6 +768,7 @@ static struct { { 'p', "pick" }, { 0, "revert" }, { 'e', "edit" }, + { 'r', "reword" }, { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, @@ -997,7 +999,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (is_fixup(command)) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { if (update_squash_messages(command, commit, opts)) return -1; amend = 1; @@ -1756,7 +1760,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } else if (res && is_rebase_i(opts)) return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, 0); + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); From 0858a972410812bbac44704deb6382fb0b383a9b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:56:21 +0200 Subject: [PATCH 73/90] sequencer (rebase -i): allow fast-forwarding for edit/reword The sequencer already knew how to fast-forward instead of cherry-picking, if possible. We want to continue to do this, of course, but in case of the 'reword' command, we will need to call `git commit` after fast-forwarding. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 8f0a8cea42..be051764ff 100644 --- a/sequencer.c +++ b/sequencer.c @@ -891,7 +891,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res = 0, unborn = 0, amend = 0, allow; + int res = 0, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -937,20 +937,28 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; + if (get_message(commit, &msg) != 0) + return error(_("Cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); - + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res |= fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) return error(_("%s: cannot parse parent commit %s"), command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("Cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -1076,6 +1084,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) +fast_forward_edit: res |= sequencer_commit(msg_file, opts, allow, edit, amend, cleanup_commit_message); From da523720a38665c6eb6018195311309de5b4e4c4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:27:51 +0200 Subject: [PATCH 74/90] sequencer (rebase -i): refactor setting the reflog message This makes the code DRYer, with the obvious benefit that we can enhance the code further in a single place. We can also reuse the functionality elsewhere by calling this new function. Signed-off-by: Johannes Schindelin --- sequencer.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index be051764ff..7354ca3e6f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1727,6 +1727,26 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1797,6 +1817,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && starts_with(head_ref.buf, "refs/")) { + const char *msg; unsigned char head[20], orig[20]; if (get_sha1("HEAD", head)) @@ -1804,19 +1825,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || get_sha1_hex(buf.buf, orig)) return error("Could not read orig-head"); - strbuf_addf(&buf, "rebase -i (finish): %s onto ", - head_ref.buf); if (!read_oneliner(&buf, rebase_path_onto(), 0)) return error("Could not read 'onto'"); - if (update_ref(buf.buf, head_ref.buf, head, orig, + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) return error("Could not update %s", head_ref.buf); - strbuf_reset(&buf); - strbuf_addf(&buf, - "rebase -i (finish): returning to %s", + msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (create_symref("HEAD", head_ref.buf, buf.buf)) + if (create_symref("HEAD", head_ref.buf, msg)) return error("Could not update HEAD to %s", head_ref.buf); strbuf_reset(&buf); From 877628f258d2d5facca0d4894f7af0c84e947190 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:28:07 +0200 Subject: [PATCH 75/90] sequencer (rebase -i): set the reflog message consistently We already used the same reflog message as the scripted version of rebase -i when finishing. With this commit, we do that also for all the commands before that. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7354ca3e6f..d3e0b48bae 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1769,6 +1769,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_amend()); } if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { From e479d8becd389f6b1bec98a143d1a718a4c17b77 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 17:26:31 +0200 Subject: [PATCH 76/90] sequencer (rebase -i): copy commit notes at end When rebasing commits that have commit notes attached, the interactive rebase rewrites those notes faithfully at the end. The sequencer must do this, too, if it wishes to do interactive rebase's job. Signed-off-by: Johannes Schindelin --- sequencer.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/sequencer.c b/sequencer.c index d3e0b48bae..2f436cde44 100644 --- a/sequencer.c +++ b/sequencer.c @@ -93,6 +93,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") * the long commit name of the corresponding patch. */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -881,6 +890,43 @@ static int update_squash_messages(enum todo_command command, return write_message(&buf, rebase_path_squash_msg()); } +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} + static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts, int final_fixup) { @@ -1727,6 +1773,17 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_NOOP) + return todo_list->items[i].command; + + return -1; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1785,6 +1842,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); if (res && is_fixup(item->command)) { if (res == 1) intend_to_amend(); @@ -1814,6 +1874,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (is_rebase_i(opts)) { struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) @@ -1858,6 +1919,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) run_command_v_opt(argv, RUN_GIT_CMD); strbuf_reset(&buf); } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + } + strbuf_release(&buf); strbuf_release(&head_ref); } From 9fafae948aa3de87694344ed957e80e0a40a0169 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:30:25 +0200 Subject: [PATCH 77/90] sequencer (rebase -i): record interrupted commits in rewritten, too When continuing after a `pick` command failed, we want that commit to show up in the rewritten-list (and its notes to be rewritten), too. Signed-off-by: Johannes Schindelin --- sequencer.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sequencer.c b/sequencer.c index 2f436cde44..7ae4faa461 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2027,6 +2027,17 @@ int sequencer_continue(struct replay_opts *opts) } todo_list.current++; } + else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1)) { + if (!get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, + peek_command(&todo_list, 0)); + } + strbuf_release(&buf); + } res = pick_commits(&todo_list, opts); release_todo_list: From 9f83130f949af5a7e515d6f4a388457aae009e78 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:27:03 +0200 Subject: [PATCH 78/90] sequencer (rebase -i): run the post-rewrite hook, if needed Signed-off-by: Johannes Schindelin --- sequencer.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7ae4faa461..e93cf26aed 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1923,6 +1923,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -1931,6 +1933,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) argv_array_push(&child.args, "--for-rewrite=rebase"); /* we don't care if this copying failed */ run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } } strbuf_release(&buf); From b0d2235ce01a88be2f13c9b648051ca3d45f2382 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:05:06 +0200 Subject: [PATCH 79/90] sequencer (rebase -i): respect the rebase.autostash setting Git's `rebase` command inspects the `rebase.autostash` config setting to determine whether it should stash any uncommitted changes before rebasing and re-apply them afterwards. As we introduce more bits and pieces to let the sequencer act as interactive rebase's backend, here is the part that adds support for the autostash feature. Signed-off-by: Johannes Schindelin --- sequencer.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sequencer.c b/sequencer.c index e93cf26aed..e4bf0d615a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -111,6 +111,7 @@ static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1784,6 +1785,47 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("Cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1945,6 +1987,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) run_command(&hook); } } + apply_autostash(opts); strbuf_release(&buf); strbuf_release(&head_ref); From a35d5f2ace52d6b6e99d749a4dd48cff25ed1cbb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Mar 2016 17:58:55 +0200 Subject: [PATCH 80/90] sequencer (rebase -i): respect strategy/strategy_opts settings The sequencer already has an idea about using different merge strategies. We just piggy-back on top of that, using rebase -i's own settings, when running the sequencer in interactive rebase mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sequencer.c b/sequencer.c index e4bf0d615a..9ddc6734ca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -112,6 +112,8 @@ static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1407,6 +1409,26 @@ static int read_populate_opts(struct replay_opts *opts) if (file_exists(rebase_path_verbose())) opts->verbose = 1; + if (read_oneliner(&buf, rebase_path_strategy(), 0)) { + opts->strategy = + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + if (read_oneliner(&buf, + rebase_path_strategy_opts(), 0)) { + int i; + opts->xopts_nr = split_cmdline(buf.buf, + &opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) + skip_prefix(opts->xopts[i], "--", + &opts->xopts[i]); + if (opts->xopts_nr) + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + else + strbuf_release(&buf); + } + } + return 0; } From b8e29f82c913156459a59d542d94346a3deb0655 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Apr 2016 16:31:59 +0200 Subject: [PATCH 81/90] sequencer (rebase -i): allow rescheduling commands The interactive rebase has the very special magic that a cherry-pick that exits with a status different from 0 and 1 signifies a failure to even record that a cherry-pick was started. This can happen e.g. when a fast-forward fails because it would overwrite untracked files. In that case, we must reschedule the command that we thought we already had at least started successfully. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sequencer.c b/sequencer.c index 9ddc6734ca..d8a79d8384 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1896,6 +1896,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) From dc68c953b39715b73347ea41a49147b29d7c5d9c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 15:18:29 +0200 Subject: [PATCH 82/90] sequencer (rebase -i): implement the 'drop' command The parsing part of a 'drop' command is almost identical to parsing a 'pick', while the operation is the same as that of a 'noop'. Signed-off-by: Johannes Schindelin --- sequencer.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index d8a79d8384..6336c06c95 100644 --- a/sequencer.c +++ b/sequencer.c @@ -770,7 +770,8 @@ enum todo_command { /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP + TODO_NOOP, + TODO_DROP }; static struct { @@ -784,7 +785,8 @@ static struct { { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, - { 0, "noop" } + { 0, "noop" }, + { 'd', "drop" } }; static const char *command_to_string(const enum todo_command command) @@ -1302,7 +1304,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) else if (is_fixup(item->command)) return error("Cannot '%s' without a previous commit", command_to_string(item->command)); - else if (item->command != TODO_NOOP) + else if (item->command < TODO_NOOP) fixup_okay = 1; p = *eol ? eol + 1 : eol; } @@ -1801,7 +1803,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) int i; for (i = todo_list->current + offset; i < todo_list->nr; i++) - if (todo_list->items[i].command != TODO_NOOP) + if (todo_list->items[i].command < TODO_NOOP) return todo_list->items[i].command; return -1; @@ -1934,7 +1936,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) res = do_exec(item->arg); *end_of_arg = saved; } - else if (item->command != TODO_NOOP) + else if (item->command < TODO_NOOP) return error("Unknown command %d", item->command); todo_list->current++; From 79b9027c1ac8c0a52561a58c3e549d5952066acf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 08:45:17 +0200 Subject: [PATCH 83/90] sequencer (rebase -i): differentiate between comments and 'noop' In the upcoming patch, we will support rebase -i's progress reporting. The progress skips comments but counts 'noop's. Signed-off-by: Johannes Schindelin --- sequencer.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6336c06c95..b3818c84a5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -771,7 +771,9 @@ enum todo_command { TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, - TODO_DROP + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; static struct { @@ -786,12 +788,13 @@ static struct { { 's', "squash" }, { 'x', "exec" }, { 0, "noop" }, - { 'd', "drop" } + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if (command < ARRAY_SIZE(todo_command_info)) + if (command < TODO_COMMENT) return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1228,14 +1231,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) bol += strspn(bol, " \t"); if (bol == eol || *bol == '\r' || *bol == comment_line_char) { - item->command = TODO_NOOP; + item->command = TODO_COMMENT; item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + for (i = 0; i < TODO_COMMENT; i++) if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; @@ -1245,7 +1248,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_info)) + if (i >= TODO_COMMENT) return -1; if (item->command == TODO_NOOP) { From b9570f04023dd808fb237f0d3425fe7718a368f4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:54:20 +0200 Subject: [PATCH 84/90] run_command_opt(): optionally hide stderr when the command succeeds This will be needed to hide the output of `git commit` when the sequencer handles an interactive rebase's script. Signed-off-by: Johannes Schindelin --- run-command.c | 23 +++++++++++++++++++++++ run-command.h | 1 + 2 files changed, 24 insertions(+) diff --git a/run-command.c b/run-command.c index 5a4dbb66d7..921e43c4ea 100644 --- a/run-command.c +++ b/run-command.c @@ -575,6 +575,29 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0; cmd.dir = dir; cmd.env = env; + + if (opt & RUN_HIDE_STDERR_ON_SUCCESS) { + struct strbuf buf = STRBUF_INIT; + int res; + + cmd.err = -1; + if (start_command(&cmd) < 0) + return -1; + + if (strbuf_read(&buf, cmd.err, 0) < 0) { + close(cmd.err); + finish_command(&cmd); /* throw away exit code */ + return -1; + } + + close(cmd.err); + res = finish_command(&cmd); + if (res) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return res; + } + return run_command(&cmd); } diff --git a/run-command.h b/run-command.h index 50666497ae..f87d01a0d6 100644 --- a/run-command.h +++ b/run-command.h @@ -70,6 +70,7 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args); #define RUN_SILENT_EXEC_FAILURE 8 #define RUN_USING_SHELL 16 #define RUN_CLEAN_ON_EXIT 32 +#define RUN_HIDE_STDERR_ON_SUCCESS 64 int run_command_v_opt(const char **argv, int opt); /* From ffd343ba52ce208851a012547a439565f663087c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 85/90] sequencer (rebase -i): show only failed `git commit`'s output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index b3818c84a5..b44743fab5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -640,10 +640,15 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, { char **env = NULL; struct argv_array array; - int rc; + int opt = RUN_GIT_CMD, rc; const char *value; if (is_rebase_i(opts)) { + if (!edit) { + opt |= RUN_COMMAND_STDOUT_TO_STDERR; + opt |= RUN_HIDE_STDERR_ON_SUCCESS; + } + env = read_author_script(); if (!env) { const char *gpg_opt = gpg_sign_opt_quoted(opts); @@ -688,7 +693,7 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, + rc = run_command_v_opt_cd_env(array.argv, opt, NULL, (const char *const *)env); argv_array_clear(&array); free(env); From 68fbae82457b61d6ecda3f6c0686fd6cd35c3293 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 86/90] sequencer (rebase -i): show only failed cherry-picks' output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index b44743fab5..bbb583cadd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -477,6 +477,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; + if (is_rebase_i(opts)) + o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -488,6 +490,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); + if (is_rebase_i(opts) && clean <= 0) + fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; From 0a60eeba24c5ee5e8e71d9d703deda9199930248 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Apr 2016 12:51:50 +0200 Subject: [PATCH 87/90] sequencer (rebase -i): suggest --edit-todo upon unknown command This is the same behavior as known from `git rebase -i`. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index bbb583cadd..2938b27ef9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1341,8 +1341,12 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) + if (res) { + if (is_rebase_i(opts)) + return error(_("Please fix this using " + "'git rebase --edit-todo'.")); return error(_("Unusable instruction sheet: '%s'"), todo_file); + } if (!todo_list->nr && (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) From a63c964b5f53e8ad7f30d8cf6cfaa4ff93abc88f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 15:51:19 +0200 Subject: [PATCH 88/90] sequencer (rebase -i): show the progress The interactive rebase keeps the user informed about its progress. If the sequencer wants to do the grunt work of the interactive rebase, it also needs to show that progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sequencer.c b/sequencer.c index 2938b27ef9..e00016acd1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1212,6 +1212,7 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; + int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -1324,6 +1325,17 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) return res; } +static int count_commands(struct todo_list *todo_list) +{ + int count = 0, i; + + for (i = 0; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_COMMENT) + count++; + + return count; +} + static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -1366,6 +1378,21 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("Cannot revert during a cherry-pick.")); } + if (is_rebase_i(opts)) { + struct todo_list done = TODO_LIST_INIT; + + if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && + !parse_insn_buffer(done.buf.buf, &done)) + todo_list->done_nr = count_commands(&done); + else + todo_list->done_nr = 0; + + todo_list->total_nr = todo_list->done_nr + + count_commands(todo_list); + + todo_list_release(&done); + } + return 0; } @@ -1902,6 +1929,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { + if (item->command != TODO_COMMENT) + fprintf(stderr, "Rebasing (%d/%d)%s", + ++(todo_list->done_nr), + todo_list->total_nr, + opts->verbose ? "\n" : "\r"); unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From 97f88bbb0295556d3a5debee9b094414194fd8b9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:40:13 +0200 Subject: [PATCH 89/90] sequencer (rebase -i): write the progress into files For the benefit of e.g. the shell prompt, the interactive rebase not only displays the progress for the user to see, but also writes it into the msgnum/end files in the state directory. Teach the sequencer this new trick. Signed-off-by: Johannes Schindelin --- sequencer.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index e00016acd1..c3626283d6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -44,6 +44,16 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The file to keep track of how many commands were already processed (e.g. + * for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +/* + * The file to keep track of how many commands are to be processed in total + * (e.g. for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); /* * The commit message that is planned to be used for any changes that * need to be committed following a user interaction. @@ -1380,17 +1390,22 @@ static int read_populate_todo(struct todo_list *todo_list, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; + FILE *f = fopen(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && !parse_insn_buffer(done.buf.buf, &done)) todo_list->done_nr = count_commands(&done); else todo_list->done_nr = 0; + todo_list_release(&done); todo_list->total_nr = todo_list->done_nr + count_commands(todo_list); - todo_list_release(&done); + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } } return 0; @@ -1929,11 +1944,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { - if (item->command != TODO_COMMENT) + if (item->command != TODO_COMMENT) { + FILE *f = fopen(rebase_path_msgnum(), "w"); + + todo_list->done_nr++; + + if (f) { + fprintf(f, "%d\n", todo_list->done_nr); + fclose(f); + } fprintf(stderr, "Rebasing (%d/%d)%s", - ++(todo_list->done_nr), + todo_list->done_nr, todo_list->total_nr, opts->verbose ? "\n" : "\r"); + } unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From de74a6cc0d18b350946929871d7166f605f7a4e6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:28:04 +0200 Subject: [PATCH 90/90] sequencer (rebase -i): write out the final message The shell script version of the interactive rebase has a very specific final message. Teach the sequencer to print the same. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index c3626283d6..ba49c3f56f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2091,6 +2091,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } apply_autostash(opts); + fprintf(stderr, "Successfully rebased and updated %s.\n", + head_ref.buf); + strbuf_release(&buf); strbuf_release(&head_ref); }