This patch series completes the support for all rebase options in the builtin rebase. This converts the remaining command-line options. The previous patch series taught the builtin rebase to handle different actions, this patch series will continue adding functionality to builtin rebase by teaching it to handle options handled by original rebase i.e. `--signoff`, `rerere-autoupdate`, et al. It adds the functionality to the previous patch series, (https://public-inbox.org/git/20180808150622.9614-1-predatoramigo@gmail.com). This is the fourth patch series that brings us more closer to a builtin "git rebase". If you like to view the development branch, you can view (https://github.com/git/git/pull/505), where I have kept my commits up to date and leveraged Travis(there is sporadic failures in t5520 for macos gcc and isn't due to my patches) for extra testing other than my system. The next in line patch series which I'll be sending out today are: builtin rebase rest: The builtin rebase will convert all the remaining shell scripts from the original rebase to C. default to builtin rebase: This will turn on the feature-complete builtin rebase to on. These patch series are built on top of each other, i.e. they depend on this order. Pratik Karki (18): builtin rebase: allow selecting the rebase "backend" builtin rebase: support --signoff builtin rebase: support --rerere-autoupdate builtin rebase: support --committer-date-is-author-date builtin rebase: support `ignore-whitespace` option builtin rebase: support `ignore-date` option builtin rebase: support `keep-empty` option builtin rebase: support `--autosquash` builtin rebase: support `--gpg-sign` option builtin rebase: support `-C` and `--whitespace=<type>` builtin rebase: support `--autostash` option builtin rebase: support `--exec` builtin rebase: support `--allow-empty-message` option builtin rebase: support --rebase-merges[=[no-]rebase-cousins] merge-base --fork-point: extract libified function builtin rebase: support `fork-point` option builtin rebase: add support for custom merge strategies builtin rebase: support --root builtin/merge-base.c | 81 +------- builtin/rebase.c | 449 ++++++++++++++++++++++++++++++++++++++++++- commit.c | 81 ++++++++ commit.h | 2 + 4 files changed, 528 insertions(+), 85 deletions(-) -- 2.18.0
With this commit the builtin rebase supports selecting the "rebase backends" (or "type") `interactive`, `preserve-merges`, and `merge`. The `state_dir` was already handled according to the rebase type in a previous commit. Note that there is one quirk in the shell script: `--interactive` followed by `--merge` won't reset the type to "merge" but keeps the type as "interactive". And as t3418 tests this explicitly, we have to support it in the builtin rebase, too. Likewise, `--interactive` followed by `--preserve-merges` makes it an "explicitly interactive" rebase, i.e. a rebase that should show the todo list, while `--preserve-merges` alone is not interactive (and t5520 tests for this via `git pull --rebase=preserve`). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 75c4ac66e0..fc9b5a8a60 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -452,6 +452,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, return res && is_linear_history(onto, head); } +/* -i followed by -m is still -i */ +static int parse_opt_merge(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + if (!is_interactive(opts)) + opts->type = REBASE_MERGE; + + return 0; +} + +/* -i followed by -p is still explicitly interactive, but -p alone is not */ +static int parse_opt_interactive(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + opts->type = REBASE_INTERACTIVE; + opts->flags |= REBASE_INTERACTIVE_EXPLICIT; + + return 0; +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -510,6 +533,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "show-current-patch", &action, N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", &options, NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", &options, NULL, + N_("let the user edit the list of commits to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_interactive }, + OPT_SET_INT('p', "preserve-merges", &options.type, + N_("try to recreate merges instead of ignoring " + "them"), REBASE_PRESERVE_MERGES), OPT_END(), }; @@ -884,6 +918,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) diff_flush(&opts); } + if (is_interactive(&options)) + goto run_rebase; + /* Detach HEAD and reset the tree */ if (options.flags & REBASE_NO_QUIET) printf(_("First, rewinding head to replay your work on top of " -- 2.18.0
This commit adds support for `--signoff` which is used to add a `Signed-off-by` trailer to all the rebased commits. The actual handling is left to the rebase backends. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index fc9b5a8a60..a491481120 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { } flags; struct strbuf git_am_opt; const char *action; + int signoff; }; static int is_interactive(struct rebase_options *opts) @@ -168,6 +169,11 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("verbose", opts))) opts->flags |= REBASE_VERBOSE; + if (file_exists(state_dir_path("signoff", opts))) { + opts->signoff = 1; + opts->flags |= REBASE_FORCE; + } + strbuf_release(&buf); return 0; @@ -249,6 +255,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (opts->switch_to) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); + add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); switch (opts->type) { case REBASE_AM: @@ -513,6 +520,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by: line to each commit")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -745,6 +754,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.signoff) { + if (options.type == REBASE_PRESERVE_MERGES) + die("cannot combine '--signoff' with " + "'--preserve-merges'"); + strbuf_addstr(&options.git_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + if (!options.root) { if (argc < 1) die("TODO: handle @{upstream}"); -- 2.18.0
The `--rerere-autoupdate` option allows rerere to update the index with resolved conflicts. This commit follows closely the equivalent part of `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index a491481120..1729d2d9e2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { struct strbuf git_am_opt; const char *action; int signoff; + int allow_rerere_autoupdate; }; static int is_interactive(struct rebase_options *opts) @@ -174,6 +175,21 @@ static int read_basic_state(struct rebase_options *opts) opts->flags |= REBASE_FORCE; } + if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + &buf)) + return -1; + if (!strcmp(buf.buf, "--rerere-autoupdate")) + opts->allow_rerere_autoupdate = 1; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_autoupdate = 0; + else + warning(_("ignoring invalid allow_rerere_autoupdate: " + "'%s'"), buf.buf); + } else + opts->allow_rerere_autoupdate = -1; + strbuf_release(&buf); return 0; @@ -256,6 +272,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(&script_snippet, "allow_rerere_autoupdate", + opts->allow_rerere_autoupdate < 0 ? "" : + opts->allow_rerere_autoupdate ? + "--rerere-autoupdate" : "--no-rerere-autoupdate"); switch (opts->type) { case REBASE_AM: @@ -488,6 +508,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .type = REBASE_UNSPECIFIED, .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, + .allow_rerere_autoupdate = -1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -553,6 +574,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', "preserve-merges", &options.type, N_("try to recreate merges instead of ignoring " "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", + &options.allow_rerere_autoupdate, + N_("allow rerere to update index with resolved " + "conflict")), OPT_END(), }; -- 2.18.0
This option is simply handed down to `git am` by way of setting the `git_am_opt` variable that is handled by the `git-rebase--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1729d2d9e2..eef16206c2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -526,6 +526,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_EDIT_TODO, ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; + int committer_date_is_author_date = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -543,6 +544,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", + &committer_date_is_author_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -763,6 +767,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(&options.git_am_opt, " -q"); + if (committer_date_is_author_date) { + strbuf_addstr(&options.git_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
This commit adds support for the `--ignore-whitespace` option of the rebase command. This option is simply passed to the `--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index eef16206c2..7490d215ef 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -542,6 +543,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("passed to 'git apply'")), OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -773,6 +776,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (ignore_whitespace) + strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
This commit adds support for `--ignore-date` which is passed to `git am` to easily change the dates of the rebased commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7490d215ef..42ee040da3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_date = 0; int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -550,6 +551,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "committer-date-is-author-date", &committer_date_is_author_date, N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", &ignore_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -779,6 +782,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (ignore_whitespace) strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + if (ignore_date) { + strbuf_addstr(&options.git_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
The `--keep-empty` option can be used to keep the commits that do not change anything from its parents in the result. While the scripted version uses `interactive_rebase=implied` to indicate that the rebase needs to use the `git-rebase--interactive` backend in non-interactive mode as fallback when figuring out which backend to use, the C version needs to use a different route because the backend will already be chosen during the `parse_options()` call. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 42ee040da3..fd9ad8efae 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; + int keep_empty; }; static int is_interactive(struct rebase_options *opts) @@ -103,6 +104,23 @@ static int is_interactive(struct rebase_options *opts) opts->type == REBASE_PRESERVE_MERGES; } +static void imply_interactive(struct rebase_options *opts, const char *option) +{ + switch (opts->type) { + case REBASE_AM: + die(_("%s requires an interactive rebase"), option); + break; + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + break; + case REBASE_MERGE: + /* we silently *upgrade* --merge to --interactive if needed */ + default: + opts->type = REBASE_INTERACTIVE; /* implied */ + break; + } +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -276,6 +294,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate < 0 ? "" : opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); + add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); switch (opts->type) { case REBASE_AM: @@ -588,6 +607,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) &options.allow_rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), + OPT_BOOL(0, "keep-empty", &options.keep_empty, + N_("preserve empty commits during rebase")), OPT_END(), }; @@ -787,6 +808,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (options.keep_empty) + imply_interactive(&options, "--keep-empty"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
This commit adds support for the `--autosquash` option which is used to automatically squash the commits marked as `squash` or `fixup` in their messages. This is converted following `git-legacy-rebase.sh` closely. This option can also be configured via the Git config setting rebase.autosquash. To support this, we also add a custom rebase_config() function in this commit that will be used instead (and falls back to) git_default_config(). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index fd9ad8efae..79ba65fd75 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { int signoff; int allow_rerere_autoupdate; int keep_empty; + int autosquash; }; static int is_interactive(struct rebase_options *opts) @@ -295,6 +296,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -455,6 +457,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autosquash")) { + opts->autosquash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -609,6 +616,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "conflict")), OPT_BOOL(0, "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " + "squash!/fixup! under -i")), OPT_END(), }; -- 2.18.0
This commit introduces support for `--gpg-sign` option which is used to GPG-sign commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 79ba65fd75..cd9caf4841 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -97,6 +97,7 @@ struct rebase_options { int allow_rerere_autoupdate; int keep_empty; int autosquash; + char *gpg_sign_opt; }; static int is_interactive(struct rebase_options *opts) @@ -209,6 +210,15 @@ static int read_basic_state(struct rebase_options *opts) } else opts->allow_rerere_autoupdate = -1; + if (file_exists(state_dir_path("gpg_sign_opt", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("gpg_sign_opt", opts), + &buf)) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -297,6 +307,7 @@ static int run_specific_rebase(struct rebase_options *opts) "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); switch (opts->type) { case REBASE_AM: @@ -462,6 +473,13 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "commit.gpgsign")) { + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = git_config_bool(var, value) ? + xstrdup("-S") : NULL; + return 0; + } + return git_default_config(var, value, data); } @@ -555,6 +573,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int committer_date_is_author_date = 0; int ignore_date = 0; int ignore_whitespace = 0; + const char *gpg_sign = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -619,6 +638,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_STRING('S', "gpg-sign", &gpg_sign, + N_("gpg-sign?"), N_("GPG-sign commits")), OPT_END(), }; @@ -821,6 +842,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.keep_empty) imply_interactive(&options, "--keep-empty"); + if (gpg_sign) { + free(options.gpg_sign_opt); + options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1046,5 +1072,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) cleanup: strbuf_release(&revisions); free(options.head_name); + free(options.gpg_sign_opt); return ret; } -- 2.18.0
This commit converts more code from the shell script version to the builtin rebase. In this instance, we just have to be careful to keep support for passing multiple `--whitespace` options, as the shell script version does so, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index cd9caf4841..4437c86513 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -574,6 +574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ignore_date = 0; int ignore_whitespace = 0; const char *gpg_sign = NULL; + int opt_c = -1; + struct string_list whitespace = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -640,6 +642,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "squash!/fixup! under -i")), OPT_STRING('S', "gpg-sign", &gpg_sign, N_("gpg-sign?"), N_("GPG-sign commits")), + OPT_STRING_LIST(0, "whitespace", &whitespace, + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', 0, &opt_c, N_("passed to 'git apply'"), + REBASE_AM), OPT_END(), }; @@ -847,6 +853,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); } + if (opt_c >= 0) + strbuf_addf(&options.git_am_opt, " -C%d", opt_c); + + if (whitespace.nr) { + int i; + + for (i = 0; i < whitespace.nr; i++) { + const char *item = whitespace.items[i].string; + + strbuf_addf(&options.git_am_opt, " --whitespace=%s", + item); + + if ((!strcmp(item, "fix")) || (!strcmp(item, "strip"))) + options.flags |= REBASE_FORCE; + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
To support `--autostash` we introduce a function `apply_autostash()` just like in `git-legacy-rebase.sh`. Rather than refactoring and using the same function that exists in `sequencer.c`, we go a different route here, to avoid clashes with the sister GSoC project that turns the interactive rebase into a builtin. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 117 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 4437c86513..a6bfa73915 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -63,12 +63,6 @@ static int use_builtin_rebase(void) return ret; } -static int apply_autostash(void) -{ - warning("TODO"); - return 0; -} - struct rebase_options { enum rebase_type type; const char *state_dir; @@ -98,6 +92,7 @@ struct rebase_options { int keep_empty; int autosquash; char *gpg_sign_opt; + int autostash; }; static int is_interactive(struct rebase_options *opts) @@ -224,13 +219,56 @@ static int read_basic_state(struct rebase_options *opts) return 0; } +static int apply_autostash(struct rebase_options *opts) +{ + const char *path = state_dir_path("autostash", opts); + struct strbuf autostash = STRBUF_INIT; + struct child_process stash_apply = CHILD_PROCESS_INIT; + + if (!file_exists(path)) + return 0; + + if (read_one(state_dir_path("autostash", opts), &autostash)) + return error(_("Could not read '%s'"), path); + argv_array_pushl(&stash_apply.args, + "stash", "apply", autostash.buf, NULL); + stash_apply.git_cmd = 1; + stash_apply.no_stderr = stash_apply.no_stdout = + stash_apply.no_stdin = 1; + if (!run_command(&stash_apply)) + printf("Applied autostash.\n"); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(&args, + "stash", "store", "-m", "autostash", "-q", + autostash.buf, NULL); + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) + res = error(_("Cannot store %s"), autostash.buf); + argv_array_clear(&args); + strbuf_release(&autostash); + if (res) + return res; + + fprintf(stderr, + _("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(&autostash); + return 0; +} + static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; const char *argv_gc_auto[] = { "gc", "--auto", NULL }; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(); + apply_autostash(opts); close_all_packs(the_repository->objects); /* * We ignore errors in 'gc --auto', since the @@ -345,7 +383,7 @@ static int run_specific_rebase(struct rebase_options *opts) } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(); + apply_autostash(opts); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -480,6 +518,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autostash")) { + opts->autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -646,6 +689,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("whitespace"), N_("passed to 'git apply'")), OPT_SET_INT('C', 0, &opt_c, N_("passed to 'git apply'"), REBASE_AM), + OPT_BOOL(0, "autostash", &options.autostash, + N_("automatically stash/stash pop before and after")), OPT_END(), }; @@ -975,6 +1020,62 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_index(the_repository->index) < 0) die(_("could not read index")); + if (options.autostash) { + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = hold_locked_index(&lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + update_index_if_able(&the_index, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(0) || has_uncommitted_changes(0)) { + const char *autostash = + state_dir_path("autostash", &options); + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(autostash)) + die(_("Could not create directory for '%s'"), + options.state_dir); + write_file(autostash, "%s", buf.buf); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(&head->object.oid, "reset --hard", + NULL, 0) < 0) + die(_("could not reset --hard")); + printf(_("HEAD is now at %s"), + find_unique_abbrev(&head->object.oid, + DEFAULT_ABBREV)); + strbuf_reset(&buf); + pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); + if (buf.len > 0) + printf(" %s", buf.buf); + putchar('\n'); + + if (discard_index(the_repository->index) < 0 || + read_index(the_repository->index) < 0) + die(_("could not read index")); + } + } + if (require_clean_work_tree("rebase", _("Please commit or stash them."), 1, 1)) { ret = 1; -- 2.18.0
This commit adds support for the `--exec` option which takes a shell command-line as argument. This argument will be appended as an `exec <cmd>` command after each line in the todo list that creates a commit in the final history. commands. Note: while the shell script version of `git rebase` assigned the empty string to `cmd` by default, we *unset* it here because the code looks nicer and it does not change the behavior. The `--exec` option requires `--interactive` machinery. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index a6bfa73915..c9e992b526 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { int autosquash; char *gpg_sign_opt; int autostash; + char *cmd; }; static int is_interactive(struct rebase_options *opts) @@ -346,6 +347,7 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(&script_snippet, "cmd", opts->cmd); switch (opts->type) { case REBASE_AM: @@ -619,6 +621,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *gpg_sign = NULL; int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; + struct string_list exec = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -691,6 +694,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_AM), OPT_BOOL(0, "autostash", &options.autostash, N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "exec", &exec, N_("exec"), + N_("add exec lines after each commit of the " + "editable list")), OPT_END(), }; @@ -915,6 +921,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (exec.nr) { + int i; + + imply_interactive(&options, "--exec"); + + strbuf_reset(&buf); + for (i = 0; i < exec.nr; i++) + strbuf_addf(&buf, "exec %s\n", exec.items[i].string); + options.cmd = xstrdup(buf.buf); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1197,5 +1214,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_release(&revisions); free(options.head_name); free(options.gpg_sign_opt); + free(options.cmd); return ret; } -- 2.18.0
This commit introduces the `--allow-empty-message` option to `builtin/rebase.c`. The motivation behind this option is: if there are empty messages (which is not allowed in Git by default, but can be imported from different version control systems), the rebase will fail. Using `--allow-empty-message` overrides that behaviour which will allow the commits having empty messages to continue in rebase operation. Note: a very recent change made this the default in the shell scripted `git rebase`, therefore the builtin rebase does the same. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index c9e992b526..dfb1e6c25b 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { char *gpg_sign_opt; int autostash; char *cmd; + int allow_empty_message; }; static int is_interactive(struct rebase_options *opts) @@ -348,6 +349,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); + add_var(&script_snippet, "allow_empty_message", + opts->allow_empty_message ? "--allow-empty-message" : ""); switch (opts->type) { case REBASE_AM: @@ -598,6 +601,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, .allow_rerere_autoupdate = -1, + .allow_empty_message = 1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -697,6 +701,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + OPT_BOOL(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages")), OPT_END(), }; -- 2.18.0
The mode to rebase non-linear branches is now supported by the builtin rebase, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index dfb1e6c25b..0b94d2daaa 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { int autostash; char *cmd; int allow_empty_message; + int rebase_merges, rebase_cousins; }; static int is_interactive(struct rebase_options *opts) @@ -351,6 +352,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "cmd", opts->cmd); add_var(&script_snippet, "allow_empty_message", opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(&script_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(&script_snippet, "rebase_cousins", + opts->rebase_cousins ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -626,6 +631,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; + const char *rebase_merges = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -704,6 +710,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, + N_("mode"), + N_("try to rebase merges instead of skipping them"), + PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_END(), }; @@ -939,6 +949,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.cmd = xstrdup(buf.buf); } + if (rebase_merges) { + if (!*rebase_merges) + ; /* default mode; do nothing */ + else if (!strcmp("rebase-cousins", rebase_merges)) + options.rebase_cousins = 1; + else if (strcmp("no-rebase-cousins", rebase_merges)) + die(_("Unknown mode: %s"), rebase_merges); + options.rebase_merges = 1; + imply_interactive(&options, "--rebase-merges"); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
We need this functionality in the builtin rebase. Note: to make this function truly reusable, we have to switch the call get_merges_many_dirty() to get_merges_many() because we want the commit flags to be reset (otherwise, subsequent get_merge_bases() calls would obtain incorrect results). This did not matter when the function was called in `git rev-parse --fork-point` because in that command, the process definitely did not traverse any commits before exiting. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/merge-base.c | 81 ++++---------------------------------------- commit.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ commit.h | 2 ++ 3 files changed, 89 insertions(+), 75 deletions(-) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 08d91b1f0c..790ceaeed6 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -110,54 +110,12 @@ static int handle_is_ancestor(int argc, const char **argv) return 1; } -struct rev_collect { - struct commit **commit; - int nr; - int alloc; - unsigned int initial : 1; -}; - -static void add_one_commit(struct object_id *oid, struct rev_collect *revs) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return; - - commit = lookup_commit(the_repository, oid); - if (!commit || - (commit->object.flags & TMP_MARK) || - parse_commit(commit)) - return; - - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; - commit->object.flags |= TMP_MARK; -} - -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) -{ - struct rev_collect *revs = cbdata; - - if (revs->initial) { - revs->initial = 0; - add_one_commit(ooid, revs); - } - add_one_commit(noid, revs); - return 0; -} - static int handle_fork_point(int argc, const char **argv) { struct object_id oid; char *refname; + struct commit *derived, *fork_point; const char *commitname; - struct rev_collect revs; - struct commit *derived; - struct commit_list *bases; - int i, ret = 0; switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { case 0: @@ -173,41 +131,14 @@ static int handle_fork_point(int argc, const char **argv) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - memset(&revs, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); - if (!revs.nr && !get_oid(refname, &oid)) - add_one_commit(&oid, &revs); + fork_point = get_fork_point(refname, derived); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); - - /* - * There should be one and only one merge base, when we found - * a common ancestor among reflog entries. - */ - if (!bases || bases->next) { - ret = 1; - goto cleanup_return; - } - - /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(&bases->item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(&fork_point->object.oid)); + return 0; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/commit.c b/commit.c index 30d1af2b20..a3fc77a4eb 100644 --- a/commit.c +++ b/commit.c @@ -17,6 +17,7 @@ #include "sha1-lookup.h" #include "wt-status.h" #include "advice.h" +#include "refs.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -958,6 +959,86 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co return result; } +struct rev_collect { + struct commit **commit; + int nr; + int alloc; + unsigned int initial : 1; +}; + +static void add_one_commit(struct object_id *oid, struct rev_collect *revs) +{ + struct commit *commit; + + if (is_null_oid(oid)) + return; + + commit = lookup_commit(the_repository, oid); + if (!commit || + (commit->object.flags & TMP_MARK) || + parse_commit(commit)) + return; + + ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); + revs->commit[revs->nr++] = commit; + commit->object.flags |= TMP_MARK; +} + +static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *ident, timestamp_t timestamp, + int tz, const char *message, void *cbdata) +{ + struct rev_collect *revs = cbdata; + + if (revs->initial) { + revs->initial = 0; + add_one_commit(ooid, revs); + } + add_one_commit(noid, revs); + return 0; +} + +struct commit *get_fork_point(const char *refname, struct commit *commit) +{ + struct object_id oid; + struct rev_collect revs; + struct commit_list *bases; + int i; + struct commit *ret = NULL; + + memset(&revs, 0, sizeof(revs)); + revs.initial = 1; + for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + + if (!revs.nr && !get_oid(refname, &oid)) + add_one_commit(&oid, &revs); + + for (i = 0; i < revs.nr; i++) + revs.commit[i]->object.flags &= ~TMP_MARK; + + bases = get_merge_bases_many(commit, revs.nr, revs.commit); + + /* + * There should be one and only one merge base, when we found + * a common ancestor among reflog entries. + */ + if (!bases || bases->next) + goto cleanup_return; + + /* And the found one must be one of the reflog entries */ + for (i = 0; i < revs.nr; i++) + if (&bases->item->object == &revs.commit[i]->object) + break; /* found */ + if (revs.nr <= i) + goto cleanup_return; + + ret = bases->item; + +cleanup_return: + free_commit_list(bases); + return ret; +} + struct commit_list *get_octopus_merge_bases(struct commit_list *in) { struct commit_list *i, *j, *k, *ret = NULL; diff --git a/commit.h b/commit.h index da0db36eba..b34240017f 100644 --- a/commit.h +++ b/commit.h @@ -211,6 +211,8 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); /* To be used only when object flags after this call no longer matter */ extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos); +struct commit *get_fork_point(const char *refname, struct commit *commit); + /* largest positive number a signed 32-bit integer can contain */ #define INFINITE_DEPTH 0x7fffffff -- 2.18.0
This commit adds support for `--fork-point` and `--no-fork-point`. This is converted as-is from `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 0b94d2daaa..72e64868b2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -632,6 +632,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; + int fork_point = -1; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -714,6 +715,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("mode"), N_("try to rebase merges instead of skipping them"), PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + OPT_BOOL(0, "fork-point", &fork_point, + N_("use 'merge-base --fork-point' to refine upstream")), OPT_END(), }; @@ -1062,6 +1065,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); + if (fork_point > 0) { + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + if (read_index(the_repository->index) < 0) die(_("could not read index")); -- 2.18.0
When running a rebase in non-am mode, it uses the recursive merge to cherry-pick the commits, and the rebase command allows to configure the merge strategy to be used in this operation. This commit adds that support to the builtin rebase. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 72e64868b2..65e7be1c48 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; + char *strategy, *strategy_opts; }; static int is_interactive(struct rebase_options *opts) @@ -217,6 +218,22 @@ static int read_basic_state(struct rebase_options *opts) opts->gpg_sign_opt = xstrdup(buf.buf); } + if (file_exists(state_dir_path("strategy", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy", opts), &buf)) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy_opts", opts), &buf)) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -356,6 +373,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_merges ? "t" : ""); add_var(&script_snippet, "rebase_cousins", opts->rebase_cousins ? "t" : ""); + add_var(&script_snippet, "strategy", opts->strategy); + add_var(&script_snippet, "strategy_opts", opts->strategy_opts); switch (opts->type) { case REBASE_AM: @@ -633,6 +652,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; int fork_point = -1; + struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -717,6 +737,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_BOOL(0, "fork-point", &fork_point, N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", &options.strategy, + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", &strategy_options, + N_("option"), + N_("pass the argument through to the merge " + "strategy")), OPT_END(), }; @@ -963,6 +989,37 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) imply_interactive(&options, "--rebase-merges"); } + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(&buf); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(&buf, " --%s", + strategy_options.items[i].string); + options.strategy_opts = xstrdup(buf.buf); + } + + if (options.strategy) { + options.strategy = xstrdup(options.strategy); + switch (options.type) { + case REBASE_AM: + die(_("--strategy requires --merge or --interactive")); + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + /* compatible */ + break; + case REBASE_UNSPECIFIED: + options.type = REBASE_MERGE; + break; + default: + BUG("unhandled rebase type (%d)", options.type); + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
This option allows to rebase entire histories up to, and including, the root commit. The conversion from the shell script is straight-forward, apart from the fact that we do not have to write an empty tree in C. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> --- builtin/rebase.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 65e7be1c48..94abaaa890 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -76,6 +76,7 @@ struct rebase_options { const char *revisions; const char *switch_to; int root; + struct object_id *squash_onto; struct commit *restrict_revision; int dont_finish_rebase; enum { @@ -375,6 +376,9 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_cousins ? "t" : ""); add_var(&script_snippet, "strategy", opts->strategy); add_var(&script_snippet, "strategy_opts", opts->strategy_opts); + add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(&script_snippet, "squash_onto", + opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); switch (opts->type) { case REBASE_AM: @@ -653,6 +657,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *rebase_merges = NULL; int fork_point = -1; struct string_list strategy_options = STRING_LIST_INIT_NODUP; + struct object_id squash_onto; + char *squash_onto_name = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -743,6 +749,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("option"), N_("pass the argument through to the merge " "strategy")), + OPT_BOOL(0, "root", &options.root, + N_("rebase all reachable commits up to the root(s)")), OPT_END(), }; @@ -1020,6 +1028,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (options.root && !options.onto_name) + imply_interactive(&options, "--root without --onto"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1058,8 +1069,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); options.upstream_arg = options.upstream_name; - } else - die("TODO: upstream for --root"); + } else { + if (!options.onto_name) { + if (commit_tree("", 0, the_hash_algo->empty_tree, NULL, + &squash_onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = &squash_onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(&squash_onto)); + } + options.upstream_name = NULL; + options.upstream = NULL; + if (argc > 1) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + options.upstream_arg = "--root"; + } /* Make sure the branch to rebase onto is valid. */ if (!options.onto_name) @@ -1207,6 +1232,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) */ if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && !is_interactive(&options) && !options.restrict_revision && + options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { int flag; @@ -1311,5 +1337,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) free(options.head_name); free(options.gpg_sign_opt); free(options.cmd); + free(squash_onto_name); return ret; } -- 2.18.0
On Wed, Aug 8, 2018 at 5:26 PM Pratik Karki <predatoramigo@gmail.com> wrote: > @@ -224,13 +219,56 @@ static int read_basic_state(struct rebase_options *opts) > return 0; > } > > +static int apply_autostash(struct rebase_options *opts) > +{ > + const char *path = state_dir_path("autostash", opts); > + struct strbuf autostash = STRBUF_INIT; > + struct child_process stash_apply = CHILD_PROCESS_INIT; > + > + if (!file_exists(path)) > + return 0; > + > + if (read_one(state_dir_path("autostash", opts), &autostash)) > + return error(_("Could not read '%s'"), path); > + argv_array_pushl(&stash_apply.args, > + "stash", "apply", autostash.buf, NULL); > + stash_apply.git_cmd = 1; > + stash_apply.no_stderr = stash_apply.no_stdout = > + stash_apply.no_stdin = 1; > + if (!run_command(&stash_apply)) > + printf("Applied autostash.\n"); I think you need _() here. > + else { > + struct argv_array args = ARGV_ARRAY_INIT; > + int res = 0; > + > + argv_array_pushl(&args, > + "stash", "store", "-m", "autostash", "-q", > + autostash.buf, NULL); > + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) > + res = error(_("Cannot store %s"), autostash.buf); > + argv_array_clear(&args); > + strbuf_release(&autostash); > + if (res) > + return res; > + > + fprintf(stderr, > + _("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(&autostash); > + return 0; > +} > + > static int finish_rebase(struct rebase_options *opts) > { > struct strbuf dir = STRBUF_INIT; > const char *argv_gc_auto[] = { "gc", "--auto", NULL }; > > delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); > - apply_autostash(); > + apply_autostash(opts); > close_all_packs(the_repository->objects); > /* > * We ignore errors in 'gc --auto', since the -- Duy
Hi,
On Wed, 8 Aug 2018, Pratik Karki wrote:
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 42ee040da3..fd9ad8efae 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -588,6 +607,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> &options.allow_rerere_autoupdate,
> N_("allow rerere to update index with resolved "
> "conflict")),
> + OPT_BOOL(0, "keep-empty", &options.keep_empty,
> + N_("preserve empty commits during rebase")),
> OPT_END(),
This forgets the short option `-k`, I just noticed. I then looked at the
rest of the options, and they seem not to forget the short option
anywhere.
I will fix this before sending v2.
Ciao,
Dscho
Hi Duy, On Sat, 18 Aug 2018, Duy Nguyen wrote: > On Wed, Aug 8, 2018 at 5:26 PM Pratik Karki <predatoramigo@gmail.com> wrote: > > @@ -224,13 +219,56 @@ static int read_basic_state(struct rebase_options *opts) > > return 0; > > } > > > > +static int apply_autostash(struct rebase_options *opts) > > +{ > > + const char *path = state_dir_path("autostash", opts); > > + struct strbuf autostash = STRBUF_INIT; > > + struct child_process stash_apply = CHILD_PROCESS_INIT; > > + > > + if (!file_exists(path)) > > + return 0; > > + > > + if (read_one(state_dir_path("autostash", opts), &autostash)) > > + return error(_("Could not read '%s'"), path); > > + argv_array_pushl(&stash_apply.args, > > + "stash", "apply", autostash.buf, NULL); > > + stash_apply.git_cmd = 1; > > + stash_apply.no_stderr = stash_apply.no_stdout = > > + stash_apply.no_stdin = 1; > > + if (!run_command(&stash_apply)) > > + printf("Applied autostash.\n"); > > I think you need _() here. Good catch. Will fix before sending v2, Dscho > > > + else { > > + struct argv_array args = ARGV_ARRAY_INIT; > > + int res = 0; > > + > > + argv_array_pushl(&args, > > + "stash", "store", "-m", "autostash", "-q", > > + autostash.buf, NULL); > > + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) > > + res = error(_("Cannot store %s"), autostash.buf); > > + argv_array_clear(&args); > > + strbuf_release(&autostash); > > + if (res) > > + return res; > > + > > + fprintf(stderr, > > + _("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(&autostash); > > + return 0; > > +} > > + > > static int finish_rebase(struct rebase_options *opts) > > { > > struct strbuf dir = STRBUF_INIT; > > const char *argv_gc_auto[] = { "gc", "--auto", NULL }; > > > > delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); > > - apply_autostash(); > > + apply_autostash(opts); > > close_all_packs(the_repository->objects); > > /* > > * We ignore errors in 'gc --auto', since the > -- > Duy >
This patch series completes the support for all rebase options in the builtin rebase, e.g. --signoff, rerere-autoupdate, etc. It is based on pk/rebase -in-c-3-acts. Changes since v1: * Added the forgotten -k short option for --keep-empty (and verified that no other short options were forgotten). * The "Applied autostash" message is now translated (as was the original in the shell script version). Pratik Karki (18): builtin rebase: allow selecting the rebase "backend" builtin rebase: support --signoff builtin rebase: support --rerere-autoupdate builtin rebase: support --committer-date-is-author-date builtin rebase: support `ignore-whitespace` option builtin rebase: support `ignore-date` option builtin rebase: support `keep-empty` option builtin rebase: support `--autosquash` builtin rebase: support `--gpg-sign` option builtin rebase: support `-C` and `--whitespace=<type>` builtin rebase: support `--autostash` option builtin rebase: support `--exec` builtin rebase: support `--allow-empty-message` option builtin rebase: support --rebase-merges[=[no-]rebase-cousins] merge-base --fork-point: extract libified function builtin rebase: support `fork-point` option builtin rebase: add support for custom merge strategies builtin rebase: support --root builtin/merge-base.c | 81 +------- builtin/rebase.c | 449 ++++++++++++++++++++++++++++++++++++++++++- commit.c | 81 ++++++++ commit.h | 2 + 4 files changed, 528 insertions(+), 85 deletions(-) base-commit: e01f72be63e6cb58243124abb2491f9d5dbcdbef Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-33%2Fdscho%2Frebase-in-c-4-opts-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-33/dscho/rebase-in-c-4-opts-v2 Pull-Request: https://github.com/gitgitgadget/git/pull/33 Range-diff vs v1: 1: 22a65191f8 ! 1: 7f751c6ce1 builtin rebase: allow selecting the rebase "backend" @@ -19,6 +19,7 @@ tests for this via `git pull --rebase=preserve`). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 2: cb9129890b ! 2: 1e2ce4b34e builtin rebase: support --signoff @@ -7,6 +7,7 @@ handling is left to the rebase backends. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 3: 99ff1fc128 ! 3: a301ef6bbd builtin rebase: support --rerere-autoupdate @@ -7,6 +7,7 @@ `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 4: cb6596ea93 ! 4: 8f67a3969d builtin rebase: support --committer-date-is-author-date @@ -6,6 +6,7 @@ `git_am_opt` variable that is handled by the `git-rebase--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 5: bf3279c2d5 ! 5: f04394a500 builtin rebase: support `ignore-whitespace` option @@ -7,6 +7,7 @@ `--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 6: 804e1969c2 ! 6: 83bb277509 builtin rebase: support `ignore-date` option @@ -6,6 +6,7 @@ to easily change the dates of the rebased commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 7: fcf2243476 ! 7: a8f12bf01f builtin rebase: support `keep-empty` option @@ -12,6 +12,7 @@ already be chosen during the `parse_options()` call. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c @@ -60,7 +61,7 @@ &options.allow_rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), -+ OPT_BOOL(0, "keep-empty", &options.keep_empty, ++ OPT_BOOL('k', "keep-empty", &options.keep_empty, + N_("preserve empty commits during rebase")), OPT_END(), }; 8: b4ecffc8f1 ! 8: 1b6851e5e1 builtin rebase: support `--autosquash` @@ -12,6 +12,7 @@ falls back to) git_default_config(). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c @@ -46,7 +47,7 @@ @@ "conflict")), - OPT_BOOL(0, "keep-empty", &options.keep_empty, + OPT_BOOL('k', "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " 9: e2a26f10e5 ! 9: b8055fb258 builtin rebase: support `--gpg-sign` option @@ -6,6 +6,7 @@ to GPG-sign commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 10: a1f3245250 ! 10: 98908573ce builtin rebase: support `-C` and `--whitespace=<type>` @@ -8,6 +8,7 @@ shell script version does so, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 11: 7b3c0dbbd9 ! 11: 5e5e5cb758 builtin rebase: support `--autostash` option @@ -11,6 +11,7 @@ builtin. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c @@ -57,7 +58,7 @@ + stash_apply.no_stderr = stash_apply.no_stdout = + stash_apply.no_stdin = 1; + if (!run_command(&stash_apply)) -+ printf("Applied autostash.\n"); ++ printf(_("Applied autostash.\n")); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; 12: 6f6b3d9339 ! 12: 01e5cb3e91 builtin rebase: support `--exec` @@ -14,6 +14,7 @@ The `--exec` option requires `--interactive` machinery. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 13: fa643c4b3f ! 13: 1c723ad592 builtin rebase: support `--allow-empty-message` option @@ -14,6 +14,7 @@ `git rebase`, therefore the builtin rebase does the same. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 14: 00d7ccd356 ! 14: 1bebcde41c builtin rebase: support --rebase-merges[=[no-]rebase-cousins] @@ -6,6 +6,7 @@ rebase, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 15: 1cf16f416e ! 15: 05652bccfe merge-base --fork-point: extract libified function @@ -12,6 +12,7 @@ process definitely did not traverse any commits before exiting. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/merge-base.c b/builtin/merge-base.c --- a/builtin/merge-base.c 16: 78ffb7aaff ! 16: 25f6771947 builtin rebase: support `fork-point` option @@ -6,6 +6,7 @@ This is converted as-is from `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 17: 693b662542 ! 17: 10dc87d4ff builtin rebase: add support for custom merge strategies @@ -9,6 +9,7 @@ This commit adds that support to the builtin rebase. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c 18: 95679ccf0b ! 18: f155141023 builtin rebase: support --root @@ -9,6 +9,7 @@ the fact that we do not have to write an empty tree in C. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> + Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> With this commit the builtin rebase supports selecting the "rebase backends" (or "type") `interactive`, `preserve-merges`, and `merge`. The `state_dir` was already handled according to the rebase type in a previous commit. Note that there is one quirk in the shell script: `--interactive` followed by `--merge` won't reset the type to "merge" but keeps the type as "interactive". And as t3418 tests this explicitly, we have to support it in the builtin rebase, too. Likewise, `--interactive` followed by `--preserve-merges` makes it an "explicitly interactive" rebase, i.e. a rebase that should show the todo list, while `--preserve-merges` alone is not interactive (and t5520 tests for this via `git pull --rebase=preserve`). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 75c4ac66e0..fc9b5a8a60 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -452,6 +452,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, return res && is_linear_history(onto, head); } +/* -i followed by -m is still -i */ +static int parse_opt_merge(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + if (!is_interactive(opts)) + opts->type = REBASE_MERGE; + + return 0; +} + +/* -i followed by -p is still explicitly interactive, but -p alone is not */ +static int parse_opt_interactive(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + opts->type = REBASE_INTERACTIVE; + opts->flags |= REBASE_INTERACTIVE_EXPLICIT; + + return 0; +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -510,6 +533,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "show-current-patch", &action, N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", &options, NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", &options, NULL, + N_("let the user edit the list of commits to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_interactive }, + OPT_SET_INT('p', "preserve-merges", &options.type, + N_("try to recreate merges instead of ignoring " + "them"), REBASE_PRESERVE_MERGES), OPT_END(), }; @@ -884,6 +918,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) diff_flush(&opts); } + if (is_interactive(&options)) + goto run_rebase; + /* Detach HEAD and reset the tree */ if (options.flags & REBASE_NO_QUIET) printf(_("First, rewinding head to replay your work on top of " -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for `--signoff` which is used to add a `Signed-off-by` trailer to all the rebased commits. The actual handling is left to the rebase backends. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index fc9b5a8a60..a491481120 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { } flags; struct strbuf git_am_opt; const char *action; + int signoff; }; static int is_interactive(struct rebase_options *opts) @@ -168,6 +169,11 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("verbose", opts))) opts->flags |= REBASE_VERBOSE; + if (file_exists(state_dir_path("signoff", opts))) { + opts->signoff = 1; + opts->flags |= REBASE_FORCE; + } + strbuf_release(&buf); return 0; @@ -249,6 +255,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (opts->switch_to) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); + add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); switch (opts->type) { case REBASE_AM: @@ -513,6 +520,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by: line to each commit")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -745,6 +754,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.signoff) { + if (options.type == REBASE_PRESERVE_MERGES) + die("cannot combine '--signoff' with " + "'--preserve-merges'"); + strbuf_addstr(&options.git_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + if (!options.root) { if (argc < 1) die("TODO: handle @{upstream}"); -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> The `--rerere-autoupdate` option allows rerere to update the index with resolved conflicts. This commit follows closely the equivalent part of `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index a491481120..1729d2d9e2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { struct strbuf git_am_opt; const char *action; int signoff; + int allow_rerere_autoupdate; }; static int is_interactive(struct rebase_options *opts) @@ -174,6 +175,21 @@ static int read_basic_state(struct rebase_options *opts) opts->flags |= REBASE_FORCE; } + if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + &buf)) + return -1; + if (!strcmp(buf.buf, "--rerere-autoupdate")) + opts->allow_rerere_autoupdate = 1; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_autoupdate = 0; + else + warning(_("ignoring invalid allow_rerere_autoupdate: " + "'%s'"), buf.buf); + } else + opts->allow_rerere_autoupdate = -1; + strbuf_release(&buf); return 0; @@ -256,6 +272,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(&script_snippet, "allow_rerere_autoupdate", + opts->allow_rerere_autoupdate < 0 ? "" : + opts->allow_rerere_autoupdate ? + "--rerere-autoupdate" : "--no-rerere-autoupdate"); switch (opts->type) { case REBASE_AM: @@ -488,6 +508,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .type = REBASE_UNSPECIFIED, .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, + .allow_rerere_autoupdate = -1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -553,6 +574,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', "preserve-merges", &options.type, N_("try to recreate merges instead of ignoring " "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", + &options.allow_rerere_autoupdate, + N_("allow rerere to update index with resolved " + "conflict")), OPT_END(), }; -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This option is simply handed down to `git am` by way of setting the `git_am_opt` variable that is handled by the `git-rebase--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1729d2d9e2..eef16206c2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -526,6 +526,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_EDIT_TODO, ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; + int committer_date_is_author_date = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -543,6 +544,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", + &committer_date_is_author_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -763,6 +767,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(&options.git_am_opt, " -q"); + if (committer_date_is_author_date) { + strbuf_addstr(&options.git_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for the `--ignore-whitespace` option of the rebase command. This option is simply passed to the `--am` backend. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index eef16206c2..7490d215ef 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -542,6 +543,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("passed to 'git apply'")), OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -773,6 +776,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (ignore_whitespace) + strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for `--ignore-date` which is passed to `git am` to easily change the dates of the rebased commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7490d215ef..42ee040da3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_date = 0; int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -550,6 +551,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "committer-date-is-author-date", &committer_date_is_author_date, N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", &ignore_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -779,6 +782,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (ignore_whitespace) strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + if (ignore_date) { + strbuf_addstr(&options.git_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> The `--keep-empty` option can be used to keep the commits that do not change anything from its parents in the result. While the scripted version uses `interactive_rebase=implied` to indicate that the rebase needs to use the `git-rebase--interactive` backend in non-interactive mode as fallback when figuring out which backend to use, the C version needs to use a different route because the backend will already be chosen during the `parse_options()` call. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 42ee040da3..9c00f3568f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; + int keep_empty; }; static int is_interactive(struct rebase_options *opts) @@ -103,6 +104,23 @@ static int is_interactive(struct rebase_options *opts) opts->type == REBASE_PRESERVE_MERGES; } +static void imply_interactive(struct rebase_options *opts, const char *option) +{ + switch (opts->type) { + case REBASE_AM: + die(_("%s requires an interactive rebase"), option); + break; + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + break; + case REBASE_MERGE: + /* we silently *upgrade* --merge to --interactive if needed */ + default: + opts->type = REBASE_INTERACTIVE; /* implied */ + break; + } +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -276,6 +294,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate < 0 ? "" : opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); + add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); switch (opts->type) { case REBASE_AM: @@ -588,6 +607,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) &options.allow_rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), + OPT_BOOL('k', "keep-empty", &options.keep_empty, + N_("preserve empty commits during rebase")), OPT_END(), }; @@ -787,6 +808,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (options.keep_empty) + imply_interactive(&options, "--keep-empty"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for the `--autosquash` option which is used to automatically squash the commits marked as `squash` or `fixup` in their messages. This is converted following `git-legacy-rebase.sh` closely. This option can also be configured via the Git config setting rebase.autosquash. To support this, we also add a custom rebase_config() function in this commit that will be used instead (and falls back to) git_default_config(). Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 9c00f3568f..c80eebfbd2 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { int signoff; int allow_rerere_autoupdate; int keep_empty; + int autosquash; }; static int is_interactive(struct rebase_options *opts) @@ -295,6 +296,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -455,6 +457,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autosquash")) { + opts->autosquash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -609,6 +616,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "conflict")), OPT_BOOL('k', "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " + "squash!/fixup! under -i")), OPT_END(), }; -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit introduces support for `--gpg-sign` option which is used to GPG-sign commits. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index c80eebfbd2..0ab86b7c4c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -97,6 +97,7 @@ struct rebase_options { int allow_rerere_autoupdate; int keep_empty; int autosquash; + char *gpg_sign_opt; }; static int is_interactive(struct rebase_options *opts) @@ -209,6 +210,15 @@ static int read_basic_state(struct rebase_options *opts) } else opts->allow_rerere_autoupdate = -1; + if (file_exists(state_dir_path("gpg_sign_opt", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("gpg_sign_opt", opts), + &buf)) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -297,6 +307,7 @@ static int run_specific_rebase(struct rebase_options *opts) "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); switch (opts->type) { case REBASE_AM: @@ -462,6 +473,13 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "commit.gpgsign")) { + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = git_config_bool(var, value) ? + xstrdup("-S") : NULL; + return 0; + } + return git_default_config(var, value, data); } @@ -555,6 +573,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int committer_date_is_author_date = 0; int ignore_date = 0; int ignore_whitespace = 0; + const char *gpg_sign = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -619,6 +638,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_STRING('S', "gpg-sign", &gpg_sign, + N_("gpg-sign?"), N_("GPG-sign commits")), OPT_END(), }; @@ -821,6 +842,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.keep_empty) imply_interactive(&options, "--keep-empty"); + if (gpg_sign) { + free(options.gpg_sign_opt); + options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1046,5 +1072,6 @@ run_rebase: cleanup: strbuf_release(&revisions); free(options.head_name); + free(options.gpg_sign_opt); return ret; } -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit converts more code from the shell script version to the builtin rebase. In this instance, we just have to be careful to keep support for passing multiple `--whitespace` options, as the shell script version does so, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 0ab86b7c4c..63ffe5e1ff 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -574,6 +574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ignore_date = 0; int ignore_whitespace = 0; const char *gpg_sign = NULL; + int opt_c = -1; + struct string_list whitespace = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -640,6 +642,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "squash!/fixup! under -i")), OPT_STRING('S', "gpg-sign", &gpg_sign, N_("gpg-sign?"), N_("GPG-sign commits")), + OPT_STRING_LIST(0, "whitespace", &whitespace, + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', 0, &opt_c, N_("passed to 'git apply'"), + REBASE_AM), OPT_END(), }; @@ -847,6 +853,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); } + if (opt_c >= 0) + strbuf_addf(&options.git_am_opt, " -C%d", opt_c); + + if (whitespace.nr) { + int i; + + for (i = 0; i < whitespace.nr; i++) { + const char *item = whitespace.items[i].string; + + strbuf_addf(&options.git_am_opt, " --whitespace=%s", + item); + + if ((!strcmp(item, "fix")) || (!strcmp(item, "strip"))) + options.flags |= REBASE_FORCE; + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> To support `--autostash` we introduce a function `apply_autostash()` just like in `git-legacy-rebase.sh`. Rather than refactoring and using the same function that exists in `sequencer.c`, we go a different route here, to avoid clashes with the sister GSoC project that turns the interactive rebase into a builtin. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 117 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 63ffe5e1ff..1e35b82d9a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -63,12 +63,6 @@ static int use_builtin_rebase(void) return ret; } -static int apply_autostash(void) -{ - warning("TODO"); - return 0; -} - struct rebase_options { enum rebase_type type; const char *state_dir; @@ -98,6 +92,7 @@ struct rebase_options { int keep_empty; int autosquash; char *gpg_sign_opt; + int autostash; }; static int is_interactive(struct rebase_options *opts) @@ -224,13 +219,56 @@ static int read_basic_state(struct rebase_options *opts) return 0; } +static int apply_autostash(struct rebase_options *opts) +{ + const char *path = state_dir_path("autostash", opts); + struct strbuf autostash = STRBUF_INIT; + struct child_process stash_apply = CHILD_PROCESS_INIT; + + if (!file_exists(path)) + return 0; + + if (read_one(state_dir_path("autostash", opts), &autostash)) + return error(_("Could not read '%s'"), path); + argv_array_pushl(&stash_apply.args, + "stash", "apply", autostash.buf, NULL); + stash_apply.git_cmd = 1; + stash_apply.no_stderr = stash_apply.no_stdout = + stash_apply.no_stdin = 1; + if (!run_command(&stash_apply)) + printf(_("Applied autostash.\n")); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(&args, + "stash", "store", "-m", "autostash", "-q", + autostash.buf, NULL); + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) + res = error(_("Cannot store %s"), autostash.buf); + argv_array_clear(&args); + strbuf_release(&autostash); + if (res) + return res; + + fprintf(stderr, + _("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(&autostash); + return 0; +} + static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; const char *argv_gc_auto[] = { "gc", "--auto", NULL }; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(); + apply_autostash(opts); close_all_packs(the_repository->objects); /* * We ignore errors in 'gc --auto', since the @@ -345,7 +383,7 @@ static int run_specific_rebase(struct rebase_options *opts) } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(); + apply_autostash(opts); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -480,6 +518,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autostash")) { + opts->autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -646,6 +689,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("whitespace"), N_("passed to 'git apply'")), OPT_SET_INT('C', 0, &opt_c, N_("passed to 'git apply'"), REBASE_AM), + OPT_BOOL(0, "autostash", &options.autostash, + N_("automatically stash/stash pop before and after")), OPT_END(), }; @@ -975,6 +1020,62 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_index(the_repository->index) < 0) die(_("could not read index")); + if (options.autostash) { + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = hold_locked_index(&lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + update_index_if_able(&the_index, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(0) || has_uncommitted_changes(0)) { + const char *autostash = + state_dir_path("autostash", &options); + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(autostash)) + die(_("Could not create directory for '%s'"), + options.state_dir); + write_file(autostash, "%s", buf.buf); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(&head->object.oid, "reset --hard", + NULL, 0) < 0) + die(_("could not reset --hard")); + printf(_("HEAD is now at %s"), + find_unique_abbrev(&head->object.oid, + DEFAULT_ABBREV)); + strbuf_reset(&buf); + pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); + if (buf.len > 0) + printf(" %s", buf.buf); + putchar('\n'); + + if (discard_index(the_repository->index) < 0 || + read_index(the_repository->index) < 0) + die(_("could not read index")); + } + } + if (require_clean_work_tree("rebase", _("Please commit or stash them."), 1, 1)) { ret = 1; -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for the `--exec` option which takes a shell command-line as argument. This argument will be appended as an `exec <cmd>` command after each line in the todo list that creates a commit in the final history. commands. Note: while the shell script version of `git rebase` assigned the empty string to `cmd` by default, we *unset* it here because the code looks nicer and it does not change the behavior. The `--exec` option requires `--interactive` machinery. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1e35b82d9a..2547be9efe 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { int autosquash; char *gpg_sign_opt; int autostash; + char *cmd; }; static int is_interactive(struct rebase_options *opts) @@ -346,6 +347,7 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(&script_snippet, "cmd", opts->cmd); switch (opts->type) { case REBASE_AM: @@ -619,6 +621,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *gpg_sign = NULL; int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; + struct string_list exec = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -691,6 +694,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_AM), OPT_BOOL(0, "autostash", &options.autostash, N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "exec", &exec, N_("exec"), + N_("add exec lines after each commit of the " + "editable list")), OPT_END(), }; @@ -915,6 +921,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (exec.nr) { + int i; + + imply_interactive(&options, "--exec"); + + strbuf_reset(&buf); + for (i = 0; i < exec.nr; i++) + strbuf_addf(&buf, "exec %s\n", exec.items[i].string); + options.cmd = xstrdup(buf.buf); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1197,5 +1214,6 @@ cleanup: strbuf_release(&revisions); free(options.head_name); free(options.gpg_sign_opt); + free(options.cmd); return ret; } -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit introduces the `--allow-empty-message` option to `builtin/rebase.c`. The motivation behind this option is: if there are empty messages (which is not allowed in Git by default, but can be imported from different version control systems), the rebase will fail. Using `--allow-empty-message` overrides that behaviour which will allow the commits having empty messages to continue in rebase operation. Note: a very recent change made this the default in the shell scripted `git rebase`, therefore the builtin rebase does the same. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2547be9efe..3e37603da4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { char *gpg_sign_opt; int autostash; char *cmd; + int allow_empty_message; }; static int is_interactive(struct rebase_options *opts) @@ -348,6 +349,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); + add_var(&script_snippet, "allow_empty_message", + opts->allow_empty_message ? "--allow-empty-message" : ""); switch (opts->type) { case REBASE_AM: @@ -598,6 +601,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, .allow_rerere_autoupdate = -1, + .allow_empty_message = 1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -697,6 +701,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + OPT_BOOL(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages")), OPT_END(), }; -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> The mode to rebase non-linear branches is now supported by the builtin rebase, too. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 3e37603da4..bcacffda33 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { int autostash; char *cmd; int allow_empty_message; + int rebase_merges, rebase_cousins; }; static int is_interactive(struct rebase_options *opts) @@ -351,6 +352,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "cmd", opts->cmd); add_var(&script_snippet, "allow_empty_message", opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(&script_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(&script_snippet, "rebase_cousins", + opts->rebase_cousins ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -626,6 +631,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; + const char *rebase_merges = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -704,6 +710,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, + N_("mode"), + N_("try to rebase merges instead of skipping them"), + PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_END(), }; @@ -939,6 +949,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.cmd = xstrdup(buf.buf); } + if (rebase_merges) { + if (!*rebase_merges) + ; /* default mode; do nothing */ + else if (!strcmp("rebase-cousins", rebase_merges)) + options.rebase_cousins = 1; + else if (strcmp("no-rebase-cousins", rebase_merges)) + die(_("Unknown mode: %s"), rebase_merges); + options.rebase_merges = 1; + imply_interactive(&options, "--rebase-merges"); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> We need this functionality in the builtin rebase. Note: to make this function truly reusable, we have to switch the call get_merges_many_dirty() to get_merges_many() because we want the commit flags to be reset (otherwise, subsequent get_merge_bases() calls would obtain incorrect results). This did not matter when the function was called in `git rev-parse --fork-point` because in that command, the process definitely did not traverse any commits before exiting. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/merge-base.c | 81 ++++---------------------------------------- commit.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ commit.h | 2 ++ 3 files changed, 89 insertions(+), 75 deletions(-) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 08d91b1f0c..790ceaeed6 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -110,54 +110,12 @@ static int handle_is_ancestor(int argc, const char **argv) return 1; } -struct rev_collect { - struct commit **commit; - int nr; - int alloc; - unsigned int initial : 1; -}; - -static void add_one_commit(struct object_id *oid, struct rev_collect *revs) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return; - - commit = lookup_commit(the_repository, oid); - if (!commit || - (commit->object.flags & TMP_MARK) || - parse_commit(commit)) - return; - - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; - commit->object.flags |= TMP_MARK; -} - -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) -{ - struct rev_collect *revs = cbdata; - - if (revs->initial) { - revs->initial = 0; - add_one_commit(ooid, revs); - } - add_one_commit(noid, revs); - return 0; -} - static int handle_fork_point(int argc, const char **argv) { struct object_id oid; char *refname; + struct commit *derived, *fork_point; const char *commitname; - struct rev_collect revs; - struct commit *derived; - struct commit_list *bases; - int i, ret = 0; switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { case 0: @@ -173,41 +131,14 @@ static int handle_fork_point(int argc, const char **argv) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - memset(&revs, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); - if (!revs.nr && !get_oid(refname, &oid)) - add_one_commit(&oid, &revs); + fork_point = get_fork_point(refname, derived); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); - - /* - * There should be one and only one merge base, when we found - * a common ancestor among reflog entries. - */ - if (!bases || bases->next) { - ret = 1; - goto cleanup_return; - } - - /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(&bases->item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(&fork_point->object.oid)); + return 0; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/commit.c b/commit.c index 30d1af2b20..a3fc77a4eb 100644 --- a/commit.c +++ b/commit.c @@ -17,6 +17,7 @@ #include "sha1-lookup.h" #include "wt-status.h" #include "advice.h" +#include "refs.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -958,6 +959,86 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co return result; } +struct rev_collect { + struct commit **commit; + int nr; + int alloc; + unsigned int initial : 1; +}; + +static void add_one_commit(struct object_id *oid, struct rev_collect *revs) +{ + struct commit *commit; + + if (is_null_oid(oid)) + return; + + commit = lookup_commit(the_repository, oid); + if (!commit || + (commit->object.flags & TMP_MARK) || + parse_commit(commit)) + return; + + ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); + revs->commit[revs->nr++] = commit; + commit->object.flags |= TMP_MARK; +} + +static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *ident, timestamp_t timestamp, + int tz, const char *message, void *cbdata) +{ + struct rev_collect *revs = cbdata; + + if (revs->initial) { + revs->initial = 0; + add_one_commit(ooid, revs); + } + add_one_commit(noid, revs); + return 0; +} + +struct commit *get_fork_point(const char *refname, struct commit *commit) +{ + struct object_id oid; + struct rev_collect revs; + struct commit_list *bases; + int i; + struct commit *ret = NULL; + + memset(&revs, 0, sizeof(revs)); + revs.initial = 1; + for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + + if (!revs.nr && !get_oid(refname, &oid)) + add_one_commit(&oid, &revs); + + for (i = 0; i < revs.nr; i++) + revs.commit[i]->object.flags &= ~TMP_MARK; + + bases = get_merge_bases_many(commit, revs.nr, revs.commit); + + /* + * There should be one and only one merge base, when we found + * a common ancestor among reflog entries. + */ + if (!bases || bases->next) + goto cleanup_return; + + /* And the found one must be one of the reflog entries */ + for (i = 0; i < revs.nr; i++) + if (&bases->item->object == &revs.commit[i]->object) + break; /* found */ + if (revs.nr <= i) + goto cleanup_return; + + ret = bases->item; + +cleanup_return: + free_commit_list(bases); + return ret; +} + struct commit_list *get_octopus_merge_bases(struct commit_list *in) { struct commit_list *i, *j, *k, *ret = NULL; diff --git a/commit.h b/commit.h index da0db36eba..b34240017f 100644 --- a/commit.h +++ b/commit.h @@ -211,6 +211,8 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); /* To be used only when object flags after this call no longer matter */ extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos); +struct commit *get_fork_point(const char *refname, struct commit *commit); + /* largest positive number a signed 32-bit integer can contain */ #define INFINITE_DEPTH 0x7fffffff -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This commit adds support for `--fork-point` and `--no-fork-point`. This is converted as-is from `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index bcacffda33..6c101e1260 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -632,6 +632,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; + int fork_point = -1; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -714,6 +715,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("mode"), N_("try to rebase merges instead of skipping them"), PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + OPT_BOOL(0, "fork-point", &fork_point, + N_("use 'merge-base --fork-point' to refine upstream")), OPT_END(), }; @@ -1062,6 +1065,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); + if (fork_point > 0) { + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + if (read_index(the_repository->index) < 0) die(_("could not read index")); -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> When running a rebase in non-am mode, it uses the recursive merge to cherry-pick the commits, and the rebase command allows to configure the merge strategy to be used in this operation. This commit adds that support to the builtin rebase. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 6c101e1260..847c7daf1c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; + char *strategy, *strategy_opts; }; static int is_interactive(struct rebase_options *opts) @@ -217,6 +218,22 @@ static int read_basic_state(struct rebase_options *opts) opts->gpg_sign_opt = xstrdup(buf.buf); } + if (file_exists(state_dir_path("strategy", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy", opts), &buf)) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy_opts", opts), &buf)) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -356,6 +373,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_merges ? "t" : ""); add_var(&script_snippet, "rebase_cousins", opts->rebase_cousins ? "t" : ""); + add_var(&script_snippet, "strategy", opts->strategy); + add_var(&script_snippet, "strategy_opts", opts->strategy_opts); switch (opts->type) { case REBASE_AM: @@ -633,6 +652,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; int fork_point = -1; + struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -717,6 +737,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_BOOL(0, "fork-point", &fork_point, N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", &options.strategy, + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", &strategy_options, + N_("option"), + N_("pass the argument through to the merge " + "strategy")), OPT_END(), }; @@ -963,6 +989,37 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) imply_interactive(&options, "--rebase-merges"); } + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(&buf); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(&buf, " --%s", + strategy_options.items[i].string); + options.strategy_opts = xstrdup(buf.buf); + } + + if (options.strategy) { + options.strategy = xstrdup(options.strategy); + switch (options.type) { + case REBASE_AM: + die(_("--strategy requires --merge or --interactive")); + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + /* compatible */ + break; + case REBASE_UNSPECIFIED: + options.type = REBASE_MERGE; + break; + default: + BUG("unhandled rebase type (%d)", options.type); + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
From: Pratik Karki <predatoramigo@gmail.com> This option allows to rebase entire histories up to, and including, the root commit. The conversion from the shell script is straight-forward, apart from the fact that we do not have to write an empty tree in C. Signed-off-by: Pratik Karki <predatoramigo@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- builtin/rebase.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 847c7daf1c..71b92658ec 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -76,6 +76,7 @@ struct rebase_options { const char *revisions; const char *switch_to; int root; + struct object_id *squash_onto; struct commit *restrict_revision; int dont_finish_rebase; enum { @@ -375,6 +376,9 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_cousins ? "t" : ""); add_var(&script_snippet, "strategy", opts->strategy); add_var(&script_snippet, "strategy_opts", opts->strategy_opts); + add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(&script_snippet, "squash_onto", + opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); switch (opts->type) { case REBASE_AM: @@ -653,6 +657,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *rebase_merges = NULL; int fork_point = -1; struct string_list strategy_options = STRING_LIST_INIT_NODUP; + struct object_id squash_onto; + char *squash_onto_name = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -743,6 +749,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("option"), N_("pass the argument through to the merge " "strategy")), + OPT_BOOL(0, "root", &options.root, + N_("rebase all reachable commits up to the root(s)")), OPT_END(), }; @@ -1020,6 +1028,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (options.root && !options.onto_name) + imply_interactive(&options, "--root without --onto"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1058,8 +1069,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); options.upstream_arg = options.upstream_name; - } else - die("TODO: upstream for --root"); + } else { + if (!options.onto_name) { + if (commit_tree("", 0, the_hash_algo->empty_tree, NULL, + &squash_onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = &squash_onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(&squash_onto)); + } + options.upstream_name = NULL; + options.upstream = NULL; + if (argc > 1) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + options.upstream_arg = "--root"; + } /* Make sure the branch to rebase onto is valid. */ if (!options.onto_name) @@ -1207,6 +1232,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) */ if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && !is_interactive(&options) && !options.restrict_revision && + options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { int flag; @@ -1311,5 +1337,6 @@ cleanup: free(options.head_name); free(options.gpg_sign_opt); free(options.cmd); + free(squash_onto_name); return ret; } -- gitgitgadget
"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> This patch series completes the support for all rebase options in the
> builtin rebase, e.g. --signoff, rerere-autoupdate, etc.
>
> It is based on pk/rebase -in-c-3-acts.
... which in turn was based on pk/rebase-in-c-2-basic that just got
rerolled, so I would assume that you want pk/rebase-in-c-3-acts I
have rebased on top of the result of applying the updated 2-basic
series.
I've rebuilt the collection of topics up to pk/rebase-in-c-6-final
with these two updated series twice, once doing it manually, like I
did the last time, and another using "rebase -i -r" on top of the
updated pk/rebase-in-c-4-opts. The resulting trees match, of
course.
I did it twice to try out how it feels to use "rebase -i -r" because
I wanted to make sure what we are shipping in 'master' behaves
sensibly ;-)
Two things I noticed about the recreation of the merge ...
Reminder to bystanders. We need to merge ag/rebase-i-in-c
topic on top of pk/reabse-in-c-5-test topic before applying
a patch to adjust rebase to call rebase-i using the latter's
new calling convention. The topics look like
- pk/rebase-in-c has three patches on master
- pk/rebase-in-c-2-basic builds on it, and being replaced
- pk/rebase-in-c-3-acts builds on 2-basic (no update this time)
- pk/rebase-in-c-4-opts builds on 3-acts, and being replaced
- pk/rebase-in-c-5-test builds on 4-opts (no update this time)
- js/rebase-in-c-5.5 builds on 5-test and merges ag/rebase-in-c
topic before applying one patch on it (no update this time)
- pk/rebase-in-c-6-final builds on 5.5 (no update this time)
and we are replacing 2-basic with 11 patches and 4-opts with
18 patches.
... using "rebase -i -r" are that
(1) it rebuilt, or at least offered to rebuild, the entire side
branch, even though there is absolutely no need to. Leaving
"pick"s untouched, based on the correct fork point, resulted in
all picks fast forwarded, but it was somewhat alarming.
(2) "merge -C <original merge commit> ag/rebase-i-in-c" appeared as
the insn to merge in the (possibly rebuilt) side branch. And
just like "commit -C", it took the merge message from the
original merge commit, which means that the summary of the
merged side branch is kept stale. In this particular case, I
did not even want to see ag/rebase-i-in-c topic touched, so I
knew I want to keep the original merge summary, but if the user
took the offer to rewrite the side branch (e.g. with a "reword"
to retitle), using the original merge message would probably
disappoint the user.
I think (1) actually is a feature. Not everybody is an integrator
who does not want to touch any commit on the topic branch(es) while
rebuilding a single-strand-of-pearls that has many commits and an
occasional merge of the tip of another topic branch. It's just that
the feature does not suit the workflow I use when I am playing the
top-level integrator role.
I am not sure what should be the ideal behaviour for (2). I would
imagine that
- I do want to keep the original title the merge (e.g. "into
<target branch>", if left to "git merge" to come up with the
title during "rebase -i" session, would be lost and become "into
HEAD", which is not what we want);
- I do want to keep the original commentary in the merge (e.g. what
you would see in "git log --first-parent master..next" that gives
summary of each topic getting merged) so that I can update it as
needed; but
- I do want the topic summary fmt-merge-msg produces to be based on
the updated side branch.
I am not sure if the last item can reliably be filtered out of the
original and replaced with newly generated summary. If we can do
so, that would be ideal, I guess.
Another observation was that after rebuiding pk/rebase-in-c-6^0 on
top of the updated pk'/rebase-in-c-4 using "rebase -i -r", I of
course still needed to "branch -f" to update pk/rebase-in-c-5,
js/reabse-in-c-5.5, and pk/rebase-in-c-6 branches to point at
appropriate commits. I do not think it is a good idea to let
"rebase -i" munge these dependent branches by default, but it might
be worth considering it as an option. Since I want to be more in
control of what happens to the tips of topic branches, I did not
mind at all having to run "branch -f" and having the chance to run
"diff" before doing so, but at the same time, that means doing these
manually in steps building 5 on 4, 5.5 on 5 and then 6 on 5.5,
instead of building 6 on top of 4 using "rebase -i" and then tagging
the intermediate states, gives me more control without forcing me
more work.
I guess that is the answer to a question you asked earlier, which I
haven't answered so far because I didn't have a good grasp of where
my preference was coming from when it was asked. Now I know, so...
Junio C Hamano <gitster@pobox.com> writes: > I've rebuilt the collection of topics up to pk/rebase-in-c-6-final > with these two updated series twice, once doing it manually, like I > did the last time, and another using "rebase -i -r" on top of the > updated pk/rebase-in-c-4-opts. The resulting trees match, of > course. Just to clarify, "trees match" means "'rebase -i -r' gave us the correct result" ;-). > I guess that is the answer to a question you asked earlier, which I > haven't answered so far because I didn't have a good grasp of where > my preference was coming from when it was asked. Now I know, so... In case the question was unclear it was "is there a reason why you do not want to use rebase -ir?" If "cherry-pick" did not lose notes, I would have preferred to do this kind of rebuilding dependent chains of topics without using rebase at all, but that is a different story.
Hi Junio, On Thu, 6 Sep 2018, Junio C Hamano wrote: > "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com> > writes: > > > This patch series completes the support for all rebase options in the > > builtin rebase, e.g. --signoff, rerere-autoupdate, etc. > > > > It is based on pk/rebase -in-c-3-acts. > > ... which in turn was based on pk/rebase-in-c-2-basic that just got > rerolled, so I would assume that you want pk/rebase-in-c-3-acts I > have rebased on top of the result of applying the updated 2-basic > series. > > I've rebuilt the collection of topics up to pk/rebase-in-c-6-final > with these two updated series twice, once doing it manually, like I > did the last time, and another using "rebase -i -r" on top of the > updated pk/rebase-in-c-4-opts. The resulting trees match, of > course. > > I did it twice to try out how it feels to use "rebase -i -r" because > I wanted to make sure what we are shipping in 'master' behaves > sensibly ;-) > > Two things I noticed about the recreation of the merge ... > > Reminder to bystanders. We need to merge ag/rebase-i-in-c > topic on top of pk/reabse-in-c-5-test topic before applying > a patch to adjust rebase to call rebase-i using the latter's > new calling convention. The topics look like > > - pk/rebase-in-c has three patches on master > - pk/rebase-in-c-2-basic builds on it, and being replaced > - pk/rebase-in-c-3-acts builds on 2-basic (no update this time) > - pk/rebase-in-c-4-opts builds on 3-acts, and being replaced > - pk/rebase-in-c-5-test builds on 4-opts (no update this time) > - js/rebase-in-c-5.5 builds on 5-test and merges ag/rebase-in-c > topic before applying one patch on it (no update this time) > - pk/rebase-in-c-6-final builds on 5.5 (no update this time) > > and we are replacing 2-basic with 11 patches and 4-opts with > 18 patches. > > ... using "rebase -i -r" are that > > (1) it rebuilt, or at least offered to rebuild, the entire side > branch, even though there is absolutely no need to. Leaving > "pick"s untouched, based on the correct fork point, resulted in > all picks fast forwarded, but it was somewhat alarming. Right. But this is a legacy of our paradigm to script things in Unix shell script. It not only is slow, error-prone and hard to keep portable, it also encourages poor design, as you do not have the same expressive power as C has. In this case, it harmed us by making it impossible to essentially play out the rebase in memory and only fall back to writing things into the worktree upon failure. However, this is where we want to go. It is still a long way to go, though, as many code parts are safely in the "we use the worktree to play out the rebase in its entirety" place. The "skip_unnecessary_picks" trick is the best we could do so far. > (2) "merge -C <original merge commit> ag/rebase-i-in-c" appeared as > the insn to merge in the (possibly rebuilt) side branch. And > just like "commit -C", it took the merge message from the > original merge commit, which means that the summary of the > merged side branch is kept stale. In this particular case, I > did not even want to see ag/rebase-i-in-c topic touched, so I > knew I want to keep the original merge summary, but if the user > took the offer to rewrite the side branch (e.g. with a "reword" > to retitle), using the original merge message would probably > disappoint the user. Right. But the user would then also freely admit that they asked for the merge commit to be rebased, which is what `--rebase-merges` says. > I think (1) actually is a feature. Not everybody is an integrator > who does not want to touch any commit on the topic branch(es) while > rebuilding a single-strand-of-pearls that has many commits and an > occasional merge of the tip of another topic branch. It's just that > the feature does not suit the workflow I use when I am playing the > top-level integrator role. As I said. The ideal thing would be to invest quite a bit in refactoring especially the do_pick_commit() function, and then play out the rebase in memory, where one state variable knows what the "HEAD" is (but the worktree is left untouched, up until the point when an error occurs, in which case we want to write out the files). This would also need a major refactoring of the recursive merge, of course, which conflates the merge part with the writing of the merge conflicts to disk part. While I would love to see this happening, I don't think that I can spare enough time to drive this, at least for a couple of years. > I am not sure what should be the ideal behaviour for (2). I would > imagine that > > - I do want to keep the original title the merge (e.g. "into > <target branch>", if left to "git merge" to come up with the > title during "rebase -i" session, would be lost and become "into > HEAD", which is not what we want); > > - I do want to keep the original commentary in the merge (e.g. what > you would see in "git log --first-parent master..next" that gives > summary of each topic getting merged) so that I can update it as > needed; but > > - I do want the topic summary fmt-merge-msg produces to be based on > the updated side branch. > > I am not sure if the last item can reliably be filtered out of the > original and replaced with newly generated summary. If we can do > so, that would be ideal, I guess. I think what you want is not the `merge` command, but a custom script that you can then `exec`. This could even be automated to some extent, by introducing an option to `git rebase -i` that lets a script post-process the generated todo list, something I wanted for a long time. > Another observation was that after rebuiding pk/rebase-in-c-6^0 on > top of the updated pk'/rebase-in-c-4 using "rebase -i -r", I of > course still needed to "branch -f" to update pk/rebase-in-c-5, > js/reabse-in-c-5.5, and pk/rebase-in-c-6 branches to point at > appropriate commits. I do not think it is a good idea to let > "rebase -i" munge these dependent branches by default, but it might > be worth considering it as an option. Yes! Already years ago, I wanted to teach the shears to figure out that a branch (i.e. a second parent of a merge commit that mentions the branch name in its oneline) was updated by the rebase, and if the pre-rebase commit agrees with a local ref of that name, update said ref after the rebase finished successfully. There is one big caveat, though: what if one of those branches is checked out in a worktree? I think it can be done, and it should be hidden behind an opt-in config setting. #leftoverbits? > Since I want to be more in control of what happens to the tips of topic > branches, I did not mind at all having to run "branch -f" and having the > chance to run "diff" before doing so, but at the same time, that means > doing these manually in steps building 5 on 4, 5.5 on 5 and then 6 on > 5.5, instead of building 6 on top of 4 using "rebase -i" and then > tagging the intermediate states, gives me more control without forcing > me more work. Sure. This definitely gives you more control. I am not sure whether you want that control, or whether you *actually* want more safety guards. If it was me, I would prefer something that can stop/pause the process when something is obviously going wrong (it could be a script verifying that, e.g. looking at the length/contents of the range-diff and ringing an alarm when a commit other than a fixup! was dropped). Ciao, Dscho