[PATCH v2 12/18] builtin rebase: support `--exec`
From: Pratik Karki This commit adds support for the `--exec` option which takes a shell command-line as argument. This argument will be appended as an `exec ` 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 Signed-off-by: Johannes Schindelin --- 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(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(_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", _name, N_("revision"), @@ -691,6 +694,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_AM), OPT_BOOL(0, "autostash", , N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "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(, "--exec"); + + strbuf_reset(); + for (i = 0; i < exec.nr; i++) + strbuf_addf(, "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(); free(options.head_name); free(options.gpg_sign_opt); + free(options.cmd); return ret; } -- gitgitgadget
[PATCH v2 17/18] builtin rebase: add support for custom merge strategies
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(); + if (read_one(state_dir_path("strategy", opts), )) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(); + if (read_one(state_dir_path("strategy_opts", opts), )) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + strbuf_release(); return 0; @@ -356,6 +373,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_merges ? "t" : ""); add_var(_snippet, "rebase_cousins", opts->rebase_cousins ? "t" : ""); + add_var(_snippet, "strategy", opts->strategy); + add_var(_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", _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", _point, N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", , + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", _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(, "--rebase-merges"); } + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(, " --%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
[PATCH v2 16/18] builtin rebase: support `fork-point` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", _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", _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, + _head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + if (read_index(the_repository->index) < 0) die(_("could not read index")); -- gitgitgadget
[PATCH v2 11/18] builtin rebase: support `--autostash` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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), )) + return error(_("Could not read '%s'"), path); + argv_array_pushl(_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(_apply)) + printf(_("Applied autostash.\n")); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(, +"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(); + strbuf_release(); + 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(); + 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(, opts->state_dir); remove_dir_recursively(, 0); strbuf_release(); @@ -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, _c, N_("passed to 'git apply'"), REBASE_AM), + OPT_BOOL(0, "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
[PATCH v2 13/18] builtin rebase: support `--allow-empty-message` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(_snippet, "cmd", opts->cmd); + add_var(_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", , N_("exec"), N_("add exec lines after each commit of the " "editable list")), + OPT_BOOL(0, "allow-empty-message", +_empty_message, +N_("allow rebasing commits with empty messages")), OPT_END(), }; -- gitgitgadget
[PATCH v2 18/18] builtin rebase: support --root
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(_snippet, "strategy", opts->strategy); add_var(_snippet, "strategy_opts", opts->strategy_opts); + add_var(_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(_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", _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", , +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(, "--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, + _onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = _onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(_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, _head, _base) && !is_interactive() && !options.restrict_revision && + options.upstream && !oidcmp(>object.oid, >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
[PATCH v2 09/18] builtin rebase: support `--gpg-sign` option
From: Pratik Karki This commit introduces support for `--gpg-sign` option which is used to GPG-sign commits. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- 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(); + if (read_one(state_dir_path("gpg_sign_opt", opts), + )) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + strbuf_release(); return 0; @@ -297,6 +307,7 @@ static int run_specific_rebase(struct rebase_options *opts) "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(_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", _name, N_("revision"), @@ -619,6 +638,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", , N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_STRING('S', "gpg-sign", _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(, "--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(); free(options.head_name); + free(options.gpg_sign_opt); return ret; } -- gitgitgadget
[PATCH v2 15/18] merge-base --fork-point: extract libified function
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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]), , )) { 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, ); - memset(, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, ); - if (!revs.nr && !get_oid(refname, )) - add_one_commit(, ); + 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 (>item->object == [i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(>item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(_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; + + co
[PATCH v2 10/18] builtin rebase: support `-C` and `--whitespace=`
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", _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", _sign, N_("gpg-sign?"), N_("GPG-sign commits")), + OPT_STRING_LIST(0, "whitespace", , + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', 0, _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(_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(_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
[PATCH v2 14/18] builtin rebase: support --rebase-merges[=[no-]rebase-cousins]
From: Pratik Karki The mode to rebase non-linear branches is now supported by the builtin rebase, too. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- 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(_snippet, "cmd", opts->cmd); add_var(_snippet, "allow_empty_message", opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(_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", _name, N_("revision"), @@ -704,6 +710,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-empty-message", _empty_message, N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", _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(, "--rebase-merges"); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 08/18] builtin rebase: support `--autosquash`
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(_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", _empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", , +N_("move commits that begin with " + "squash!/fixup! under -i")), OPT_END(), }; -- gitgitgadget
[PATCH v2 07/18] builtin rebase: support `keep-empty` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(_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) _rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), + OPT_BOOL('k', "keep-empty", _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(, "--keep-empty"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 06/18] builtin rebase: support `ignore-date` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", _name, @@ -550,6 +551,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "committer-date-is-author-date", _date_is_author_date, N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", _date, +N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " --ignore-whitespace"); + if (ignore_date) { + strbuf_addstr(_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 02/18] builtin rebase: support --signoff
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(); return 0; @@ -249,6 +255,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (opts->switch_to) add_var(_snippet, "switch_to", opts->switch_to); add_var(_snippet, "action", opts->action ? opts->action : ""); + add_var(_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", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", , +N_("add a Signed-off-by: line to each commit")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + if (!options.root) { if (argc < 1) die("TODO: handle @{upstream}"); -- gitgitgadget
[PATCH v2 05/18] builtin rebase: support `ignore-whitespace` option
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", _name, N_("revision"), @@ -542,6 +543,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "ignore-whitespace", _whitespace, +N_("passed to 'git apply'")), OPT_BOOL(0, "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(_am_opt, " --ignore-whitespace"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 03/18] builtin rebase: support --rerere-autoupdate
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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(); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + )) + 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(); return 0; @@ -256,6 +272,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "switch_to", opts->switch_to); add_var(_snippet, "action", opts->action ? opts->action : ""); add_var(_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(_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", , N_("try to recreate merges instead of ignoring " "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", +_rerere_autoupdate, +N_("allow rerere to update index with resolved " + "conflict")), OPT_END(), }; -- gitgitgadget
[PATCH v2 04/18] builtin rebase: support --committer-date-is-author-date
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", _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", , N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", +_date_is_author_date, +N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " -q"); + if (committer_date_is_author_date) { + strbuf_addstr(_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 01/18] builtin rebase: allow selecting the rebase "backend"
From: Pratik Karki 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 Signed-off-by: Johannes Schindelin --- 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", , N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", , NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", , 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", , + 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(); } + if (is_interactive()) + 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
[PATCH v2 07/11] builtin rebase: try to fast forward when possible
From: Pratik Karki In this commit, we add support to fast forward. Note: we will need the merge base later, therefore the call to can_fast_forward() really needs to be the first one when testing whether we can skip the rebase entirely (otherwise, it would make more sense to skip the possibly expensive operation if, say, running an interactive rebase). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 72 1 file changed, 72 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index afef0b0046..d67df28efc 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -20,6 +20,7 @@ #include "commit.h" #include "diff.h" #include "wt-status.h" +#include "revision.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -89,6 +90,12 @@ struct rebase_options { struct strbuf git_am_opt; }; +static int is_interactive(struct rebase_options *opts) +{ + return opts->type == REBASE_INTERACTIVE || + opts->type == REBASE_PRESERVE_MERGES; +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -334,6 +341,46 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +/* + * Determines whether the commits in from..to are linear, i.e. contain + * no merge commits. This function *expects* `from` to be an ancestor of + * `to`. + */ +static int is_linear_history(struct commit *from, struct commit *to) +{ + while (to && to != from) { + parse_commit(to); + if (!to->parents) + return 1; + if (to->parents->next) + return 0; + to = to->parents->item; + } + return 1; +} + +static int can_fast_forward(struct commit *onto, struct object_id *head_oid, + struct object_id *merge_base) +{ + struct commit *head = lookup_commit(the_repository, head_oid); + struct commit_list *merge_bases; + int res; + + if (!head) + return 0; + + merge_bases = get_merge_bases(onto, head); + if (merge_bases && !merge_bases->next) { + oidcpy(merge_base, _bases->item->object.oid); + res = !oidcmp(merge_base, >object.oid); + } else { + oidcpy(merge_base, _oid); + res = 0; + } + free_commit_list(merge_bases); + return res && is_linear_history(onto, head); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -489,6 +536,31 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } + /* +* Now we are rebasing commits upstream..orig_head (or with --root, +* everything leading up to orig_head) on top of onto. +*/ + + /* +* Check if we are already based on onto with linear history, +* but this should be done only when upstream and onto are the same +* and if this is not an interactive rebase. +*/ + if (can_fast_forward(options.onto, _head, _base) && + !is_interactive() && !options.restrict_revision && + !oidcmp(>object.oid, >object.oid)) { + int flag; + + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && +resolve_ref_unsafe("HEAD", 0, NULL, )) + puts(_("HEAD is up to date, rebase forced.")); + else + printf(_("Current branch %s is up to date, rebase " +"forced.\n"), branch_name); + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, -- gitgitgadget
[PATCH v2 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`
From: Pratik Karki When running a rebase on a detached HEAD, we currently store the string "detached HEAD" in options.head_name. That is a faithful translation of the shell script version, and we still kind of need it for the purposes of the scripted backends. It is poor style for C, though, where we would really only want a valid, fully-qualified ref name as value, and NULL for detached HEADs, using "detached HEAD" for display only. Make it so. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 11 --- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index d45f8f9008..afc75fe731 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -169,7 +169,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "upstream_name", opts->upstream_name); add_var(_snippet, "upstream", oid_to_hex(>upstream->object.oid)); - add_var(_snippet, "head_name", opts->head_name); + add_var(_snippet, "head_name", + opts->head_name ? opts->head_name : "detached HEAD"); add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); add_var(_snippet, "onto_name", opts->onto_name); @@ -251,6 +252,9 @@ static int reset_head(struct object_id *oid, const char *action, *old_orig = NULL, oid_old_orig; int ret = 0; + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) return -1; @@ -558,7 +562,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * branch_name -- branch/commit being rebased, or *HEAD (already detached) * orig_head -- commit object name of tip of the branch before rebasing -* head_name -- refs/heads/ or "detached HEAD" +* head_name -- refs/heads/ or NULL (detached HEAD) */ if (argc > 0) die("TODO: handle switch_to"); @@ -575,7 +579,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) branch_name = options.head_name; } else { - options.head_name = xstrdup("detached HEAD"); + free(options.head_name); + options.head_name = NULL; branch_name = "HEAD"; } if (get_oid("HEAD", _head)) -- gitgitgadget
[PATCH v2 04/11] builtin rebase: support --quiet
From: Pratik Karki This commit introduces a rebase option `--quiet`. While `--quiet` is commonly perceived as opposite to `--verbose`, this is not the case for the rebase command: both `--quiet` and `--verbose` default to `false` if neither `--quiet` nor `--verbose` is present. Despite the default being `false` for both verbose and quiet mode, passing the `--quiet` option will turn off verbose mode, and `--verbose` will turn off quiet mode. This patch introduces the `flags` bit field, with `REBASE_NO_QUIET` as first user (with many more to come). We do *not* use `REBASE_QUIET` here for an important reason: To keep the implementation simple, this commit introduces `--no-quiet` instead of `--quiet`, so that a single `OPT_NEGBIT()` can turn on quiet mode and turn off verbose and diffstat mode at the same time. Likewise, the companion commit which will introduce support for `--verbose` will have a single `OPT_BIT()` that turns off quiet mode and turns on verbose and diffstat mode at the same time. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 15 +++ 1 file changed, 15 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index b79f9b0a9f..19fa4d3fc4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,10 @@ struct rebase_options { int root; struct commit *restrict_revision; int dont_finish_rebase; + enum { + REBASE_NO_QUIET = 1<<0, + } flags; + struct strbuf git_am_opt; }; /* Returns the filename prefixed by the state_dir */ @@ -159,6 +163,9 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "revisions", opts->revisions); add_var(_snippet, "restrict_revision", opts->restrict_revision ? oid_to_hex(>restrict_revision->object.oid) : NULL); + add_var(_snippet, "GIT_QUIET", + opts->flags & REBASE_NO_QUIET ? "" : "t"); + add_var(_snippet, "git_am_opt", opts->git_am_opt.buf); switch (opts->type) { case REBASE_AM: @@ -308,6 +315,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { .type = REBASE_UNSPECIFIED, + .flags = REBASE_NO_QUIET, + .git_am_opt = STRBUF_INIT, }; const char *branch_name; int ret, flags; @@ -321,6 +330,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("rebase onto given branch instead of upstream")), OPT_BOOL(0, "no-verify", _to_skip_pre_rebase, N_("allow pre-rebase hook to run")), + OPT_NEGBIT('q', "quiet", , + N_("be quiet. implies --no-stat"), + REBASE_NO_QUIET), OPT_END(), }; @@ -357,6 +369,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (!(options.flags & REBASE_NO_QUIET)) + strbuf_addstr(_am_opt, " -q"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- gitgitgadget
[PATCH v2 02/11] builtin rebase: support `git rebase --onto A...B`
From: Pratik Karki This commit implements support for an --onto argument that is actually a "symmetric range" i.e. `...`. The equivalent shell script version of the code offers two different error messages for the cases where there is no merge base vs more than one merge base. Though it would be nice to retain this distinction, dropping it makes it possible to simply use the `get_oid_mb()` function. Besides, it happens rarely in real-world scenarios. Therefore, in the interest of keeping the code less complex, let's just use that function, and live with an error message that does not distinguish between those two error conditions. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 8 +++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 742ed31498..38c496dd10 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -17,6 +17,7 @@ #include "unpack-trees.h" #include "lockfile.h" #include "parse-options.h" +#include "commit.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -311,6 +312,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, N_("revision"), @@ -387,7 +389,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - die("TODO"); + if (get_oid_mb(options.onto_name, _base) < 0) + die(_("'%s': need exactly one merge base"), + options.onto_name); + options.onto = lookup_commit_or_die(_base, + options.onto_name); } else { options.onto = peel_committish(options.onto_name); if (!options.onto) -- gitgitgadget
[PATCH v2 11/11] builtin rebase: support `git rebase `
From: Pratik Karki This commit adds support for `switch-to` which is used to switch to the target branch if needed. The equivalent codes found in shell script `git-legacy-rebase.sh` is converted to builtin `rebase.c`. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 48 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index afc75fe731..e817956d96 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,7 @@ struct rebase_options { struct commit *onto; const char *onto_name; const char *revisions; + const char *switch_to; int root; struct commit *restrict_revision; int dont_finish_rebase; @@ -186,6 +187,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_DIFFSTAT ? "t" : ""); add_var(_snippet, "force_rebase", opts->flags & REBASE_FORCE ? "t" : ""); + if (opts->switch_to) + add_var(_snippet, "switch_to", opts->switch_to); switch (opts->type) { case REBASE_AM: @@ -564,9 +567,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or NULL (detached HEAD) */ - if (argc > 0) -die("TODO: handle switch_to"); - else { + if (argc == 1) { + /* Is it "rebase other branchname" or "rebase other commit"? */ + branch_name = argv[0]; + options.switch_to = argv[0]; + + /* Is it a local branch? */ + strbuf_reset(); + strbuf_addf(, "refs/heads/%s", branch_name); + if (!read_ref(buf.buf, _head)) + options.head_name = xstrdup(buf.buf); + /* If not is it a valid ref (branch or commit)? */ + else if (!get_oid(branch_name, _head)) + options.head_name = NULL; + else + die(_("fatal: no such branch/commit '%s'"), + branch_name); + } else if (argc == 0) { /* Do not need to switch branches, we are already on it. */ options.head_name = xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, @@ -585,7 +602,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (get_oid("HEAD", _head)) die(_("Could not resolve HEAD to a revision")); - } + } else + BUG("unexpected number of arguments left to parse"); if (read_index(the_repository->index) < 0) die(_("could not read index")); @@ -612,6 +630,28 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int flag; if (!(options.flags & REBASE_FORCE)) { + /* Lazily switch to the target branch if needed... */ + if (options.switch_to) { + struct object_id oid; + + if (get_oid(options.switch_to, ) < 0) { + ret = !!error(_("could not parse '%s'"), + options.switch_to); + goto cleanup; + } + + strbuf_reset(); + strbuf_addf(, "rebase: checkout %s", + options.switch_to); + if (reset_head(, "checkout", + options.head_name, 0) < 0) { + ret = !!error(_("could not switch to " + "%s"), + options.switch_to); + goto cleanup; + } + } + if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && -- gitgitgadget
[PATCH v2 09/11] builtin rebase: start a new rebase only if none is in progress
From: Pratik Karki To run a new rebase, there needs to be a check to assure that no other rebase is in progress. New rebase operation cannot start until an ongoing rebase operation completes or is terminated. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 48 +++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 8a7bf3d468..d45f8f9008 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -87,6 +87,7 @@ struct rebase_options { REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, REBASE_FORCE = 1<<3, + REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; struct strbuf git_am_opt; }; @@ -392,10 +393,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, }; const char *branch_name; - int ret, flags; + int ret, flags, in_progress = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) git_config(rebase_config, ); + if (is_directory(apply_dir())) { + options.type = REBASE_AM; + options.state_dir = apply_dir(); + } else if (is_directory(merge_dir())) { + strbuf_reset(); + strbuf_addf(, "%s/rewritten", merge_dir()); + if (is_directory(buf.buf)) { + options.type = REBASE_PRESERVE_MERGES; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else { + strbuf_reset(); + strbuf_addf(, "%s/interactive", merge_dir()); + if(file_exists(buf.buf)) { + options.type = REBASE_INTERACTIVE; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else + options.type = REBASE_MERGE; + } + options.state_dir = merge_dir(); + } + + if (options.type != REBASE_UNSPECIFIED) + in_progress = 1; + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -455,6 +481,26 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + /* Make sure no rebase is in progress */ + if (in_progress) { + const char *last_slash = strrchr(options.state_dir, '/'); + const char *state_dir_base = + last_slash ? last_slash + 1 : options.state_dir; + const char *cmd_live_rebase = + "git rebase (--continue | --abort | --skip)"; + strbuf_reset(); + strbuf_addf(, "rm -fr \"%s\"", options.state_dir); + die(_("It seems that there is already a %s directory, and\n" + "I wonder if you are in the middle of another rebase. " + "If that is the\n" + "case, please try\n\t%s\n" + "If that is not the case, please\n\t%s\n" + "and run me again. I am stopping in case you still " + "have something\n" + "valuable there.\n"), + state_dir_base, cmd_live_rebase, buf.buf); + } + if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(_am_opt, " -q"); -- gitgitgadget
[PATCH v2 03/11] builtin rebase: handle the pre-rebase hook and --no-verify
From: Pratik Karki This commit converts the equivalent part of the shell script `git-legacy-rebase.sh` to run the pre-rebase hook (unless disabled), and to interrupt the rebase with error if the hook fails. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 11 +++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 38c496dd10..b79f9b0a9f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -70,6 +70,7 @@ struct rebase_options { const char *state_dir; struct commit *upstream; const char *upstream_name; + const char *upstream_arg; char *head_name; struct object_id orig_head; struct commit *onto; @@ -310,6 +311,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags; + int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; struct object_id merge_base; @@ -317,6 +319,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", _name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "no-verify", _to_skip_pre_rebase, +N_("allow pre-rebase hook to run")), OPT_END(), }; @@ -382,6 +386,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.upstream = peel_committish(options.upstream_name); if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); + options.upstream_arg = options.upstream_name; } else die("TODO: upstream for --root"); @@ -430,6 +435,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + /* If a hook exists, give it a chance to interrupt*/ + if (!ok_to_skip_pre_rebase && + run_hook_le(NULL, "pre-rebase", options.upstream_arg, + argc ? argv[0] : NULL, NULL)) + die(_("The pre-rebase hook refused to rebase.")); + strbuf_addf(, "rebase: checkout %s", options.onto_name); if (reset_head(>object.oid, "checkout", NULL, 1)) die(_("Could not detach HEAD")); -- gitgitgadget
[PATCH v2 01/11] builtin rebase: support --onto
From: Pratik Karki The `--onto` option is important, as it allows to rebase a range of commits onto a different base commit (which gave the command its odd name: "rebase"). This commit introduces options parsing so that different options can be added in future commits. Note: As this commit introduces to the parse_options() call (which "eats" argv[0]), the argc is now expected to be lower by one after this patch, compared to before this patch: argv[0] no longer refers to the command name, but to the first (non-option) command-line parameter. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 35 ++- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index e695d8a430..742ed31498 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -16,6 +16,16 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "lockfile.h" +#include "parse-options.h" + +static char const * const builtin_rebase_usage[] = { + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "[] []"), + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "--root []"), + N_("git rebase --continue | --abort | --skip | --edit-todo"), + NULL +}; static GIT_PATH_FUNC(apply_dir, "rebase-apply") static GIT_PATH_FUNC(merge_dir, "rebase-merge") @@ -301,6 +311,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct option builtin_rebase_options[] = { + OPT_STRING(0, "onto", _name, + N_("revision"), + N_("rebase onto given branch instead of upstream")), + OPT_END(), + }; /* * NEEDSWORK: Once the builtin rebase has been tested enough @@ -318,13 +334,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) BUG("sane_execvp() returned???"); } - if (argc != 2) - die(_("Usage: %s "), argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, +builtin_rebase_options, +builtin_rebase_usage, 0); + + if (argc > 2) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); switch (options.type) { case REBASE_MERGE: @@ -343,10 +368,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (!options.root) { - if (argc < 2) + if (argc < 1) die("TODO: handle @{upstream}"); else { - options.upstream_name = argv[1]; + options.upstream_name = argv[0]; argc--; argv++; if (!strcmp(options.upstream_name, "-")) @@ -377,7 +402,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or "detached HEAD" */ - if (argc > 1) + if (argc > 0) die("TODO: handle switch_to"); else { /* Do not need to switch branches, we are already on it. */ -- gitgitgadget
[PATCH v2 05/11] builtin rebase: support the `verbose` and `diffstat` options
From: Pratik Karki This commit introduces support for the `-v` and `--stat` options of rebase. The --stat option can also be configured via the Git config setting rebase.stat. To support this, we also add a custom rebase_config() function in this commit that will be used instead of (and falls back to calling) git_default_config(). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 60 ++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 19fa4d3fc4..2d3f1d65fb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -18,6 +18,7 @@ #include "lockfile.h" #include "parse-options.h" #include "commit.h" +#include "diff.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -81,6 +82,8 @@ struct rebase_options { int dont_finish_rebase; enum { REBASE_NO_QUIET = 1<<0, + REBASE_VERBOSE = 1<<1, + REBASE_DIFFSTAT = 1<<2, } flags; struct strbuf git_am_opt; }; @@ -166,6 +169,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "GIT_QUIET", opts->flags & REBASE_NO_QUIET ? "" : "t"); add_var(_snippet, "git_am_opt", opts->git_am_opt.buf); + add_var(_snippet, "verbose", + opts->flags & REBASE_VERBOSE ? "t" : ""); + add_var(_snippet, "diffstat", + opts->flags & REBASE_DIFFSTAT ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -311,6 +318,21 @@ static int reset_head(struct object_id *oid, const char *action, return ret; } +static int rebase_config(const char *var, const char *value, void *data) +{ + struct rebase_options *opts = data; + + if (!strcmp(var, "rebase.stat")) { + if (git_config_bool(var, value)) + opts->flags |= REBASE_DIFFSTAT; + else + opts->flags &= !REBASE_DIFFSTAT; + return 0; + } + + return git_default_config(var, value, data); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -332,7 +354,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", , N_("be quiet. implies --no-stat"), - REBASE_NO_QUIET), + REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT), + OPT_BIT('v', "verbose", , + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + {OPTION_NEGBIT, 'n', "no-stat", , NULL, + N_("do not show diffstat of what changed upstream"), + PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_END(), }; @@ -360,7 +388,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - git_config(git_default_config, NULL); + git_config(rebase_config, ); + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -456,6 +485,33 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); + if (options.flags & REBASE_DIFFSTAT) { + struct diff_options opts; + + if (options.flags & REBASE_VERBOSE) + printf(_("Changes from %s to %s:\n"), + oid_to_hex(_base), + oid_to_hex(>object.oid)); + + /* We want color (if set), but no pager */ + diff_setup(); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ + opts.output_format |= + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + opts.detect_rename = DIFF_DETECT_RENAME; + diff_setup_done(); + diff_tree_oid(_base, >object.oid, + "", ); + diffcore_std(); + diff_flush(); + } + + /* Detach HEAD and reset the tree *
[PATCH v2 08/11] builtin rebase: support --force-rebase
From: Pratik Karki In this commit, we add support to `--force-rebase` option. The equivalent part of the shell script found in `git-legacy-rebase.sh` is converted as faithfully as possible to C. The --force-rebase option ensures that the rebase does not simply fast-forward even if it could. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 22 +- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index d67df28efc..8a7bf3d468 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -86,6 +86,7 @@ struct rebase_options { REBASE_NO_QUIET = 1<<0, REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, + REBASE_FORCE = 1<<3, } flags; struct strbuf git_am_opt; }; @@ -181,6 +182,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_VERBOSE ? "t" : ""); add_var(_snippet, "diffstat", opts->flags & REBASE_DIFFSTAT ? "t" : ""); + add_var(_snippet, "force_rebase", + opts->flags & REBASE_FORCE ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -409,6 +412,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BIT('f', "force-rebase", , + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), + OPT_BIT(0, "no-ff", , + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), OPT_END(), }; @@ -551,7 +560,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) !oidcmp(>object.oid, >object.oid)) { int flag; - if (!(options.flags & REBASE_NO_QUIET)) + if (!(options.flags & REBASE_FORCE)) { + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && +resolve_ref_unsafe("HEAD", 0, NULL, )) + puts(_("HEAD is up to date.")); + else + printf(_("Current branch %s is up to date.\n"), + branch_name); + ret = !!finish_rebase(); + goto cleanup; + } else if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && resolve_ref_unsafe("HEAD", 0, NULL, )) -- gitgitgadget
[PATCH v2 06/11] builtin rebase: require a clean worktree
From: Pratik Karki This commit reads the index of the repository for rebase and checks whether the repository is ready for rebase. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 11 +++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2d3f1d65fb..afef0b0046 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -19,6 +19,7 @@ #include "parse-options.h" #include "commit.h" #include "diff.h" +#include "wt-status.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -479,6 +480,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + if (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; + goto cleanup; + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, @@ -528,6 +538,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ret = !!run_specific_rebase(); +cleanup: strbuf_release(); free(options.head_name); return ret; -- gitgitgadget
[GSoC] [PATCH 0/1] default to builtin rebase
Since, all the conversion of shell script version of `git rebase` is done as can be seen from the previous patch series, now we can default to the builtin one. The builtin rebase can now handle every task done by the original shell script `git rebase`. So, in this patch series, we turn the builtin rebase `on` so that the rebase operations can be handled it by it. This patch series is the continuation of (https://public-inbox.org/git/20180808153635.19944-1-predatoram...@gmail.com/). This is the sixth and final patch series for 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. These patch series are built on top of each other, i.e. they depend on this order. Pratik Karki (1): rebase: default to using the builtin rebase builtin/rebase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -- 2.18.0
[PATCH 1/1] rebase: default to using the builtin rebase
Now that the builtin rebase is feature-complete, we should use it by default. Let's keep the legacy scripted version around for the time being; Once the builtin rebase is well-tested enough, we can remove `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki --- builtin/rebase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 4e69458161..c8d632b6f4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -54,7 +54,7 @@ static int use_builtin_rebase(void) cp.git_cmd = 1; if (capture_command(, , 6)) { strbuf_release(); - return 0; + return 1; } strbuf_trim(); -- 2.18.0
[PATCH 5/6] builtin rebase: use no-op editor when interactive is "implied"
Some options are only handled by the git-rebase--interactive backend, even if run non-interactively. For this awkward situation (run non-interactively, but use the interactive backend), the shell scripted version of `git rebase` introduced the concept of an "implied interactive rebase". All it does is to replace the editor by a dummy one (`:` is the Unix command that takes arbitrary command-line parameters, ignores them and simply exits with success). Signed-off-by: Pratik Karki --- builtin/rebase.c | 7 +++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 04974dff6e..fb8ab5a177 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -383,6 +383,13 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "git_format_patch_opt", opts->git_format_patch_opt.buf); + if (is_interactive(opts) && + !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + strbuf_addstr(_snippet, + "GIT_EDITOR=:; export GIT_EDITOR; "); + opts->autosquash = 0; + } + switch (opts->type) { case REBASE_AM: backend = "git-rebase--am"; -- 2.18.0
[PATCH 3/6] builtin rebase: fast-forward to onto if it is a proper descendant
When trying to rebase onto a direct descendant of HEAD, we can take a shortcut and fast-forward instead. This commit makes it so. Signed-off-by: Pratik Karki --- builtin/rebase.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index c51b9d288a..1bb64e7cd7 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1367,6 +1367,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not detach HEAD")); strbuf_release(); + /* +* If the onto is a proper descendant of the tip of the branch, then +* we just fast-forwarded. +*/ + strbuf_reset(); + if (!oidcmp(_base, _head)) { + printf(_("Fast-forwarded %s to %s. \n"), + branch_name, options.onto_name); + strbuf_addf(, "rebase finished: %s onto %s", + options.head_name ? options.head_name : "detached HEAD", + oid_to_hex(>object.oid)); + reset_head(NULL, "Fast-forwarded", options.head_name, 0, + "HEAD", msg.buf); + strbuf_release(); + ret = !!finish_rebase(); + goto cleanup; + } + strbuf_addf(, "%s..%s", options.root ? oid_to_hex(>object.oid) : (options.restrict_revision ? -- 2.18.0
[PATCH 1/6] builtin rebase: optionally auto-detect the upstream
The `git rebase` command, when called without the `` command-line argument, automatically looks for the upstream branch configured for the current branch. With this commit, the builtin rebase learned that trick, too. Signed-off-by: Pratik Karki --- builtin/rebase.c | 44 +--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 94abaaa890..c5b2534717 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -622,6 +622,36 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, return 0; } +static void NORETURN error_on_missing_default_upstream(void) +{ + struct branch *current_branch = branch_get(NULL); + + printf(_("%s\n" +"Please specify which branch you want to rebase against.\n" +"See git-rebase(1) for details.\n" +"\n" +"git rebase ''\n" +"\n"), + current_branch ? _("There is no tracking information for " + "the current branch.") : + _("You are not currently on a branch.")); + + if (current_branch) { + const char *remote = current_branch->remote_name; + + if (!remote) + remote = _(""); + + printf(_("If you wish to set tracking information for this " +"branch you can do so with:\n" +"\n" +"git branch --set-upstream-to=%s/ %s\n" +"\n"), + remote, current_branch->name); + } + exit(1); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -1056,9 +1086,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (!options.root) { - if (argc < 1) - die("TODO: handle @{upstream}"); - else { + if (argc < 1) { + struct branch *branch; + + branch = branch_get(NULL); + options.upstream_name = branch_get_upstream(branch, + NULL); + if (!options.upstream_name) + error_on_missing_default_upstream(); + if (fork_point < 0) + fork_point = 1; + } else { options.upstream_name = argv[0]; argc--; argv++; -- 2.18.0
[PATCH 6/6] builtin rebase: error out on incompatible option/mode combinations
While working on the GSoC project to convert the rebase command to a builtin, the rebase command learned to error out on certain command-line option combinations that cannot work, such as --whitespace=fix with --interactive. This commit converts that code. Signed-off-by: Pratik Karki --- builtin/rebase.c | 41 + 1 file changed, 41 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index fb8ab5a177..4e69458161 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1098,6 +1098,28 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.git_am_opt.len) { + const char *p; + + /* all am options except -q are compatible only with --am */ + strbuf_reset(); + strbuf_addbuf(, _am_opt); + strbuf_addch(, ' '); + while ((p = strstr(buf.buf, " -q "))) + strbuf_splice(, p - buf.buf, 4, " ", 1); + strbuf_trim(); + + if (is_interactive() && buf.len) + die(_("error: cannot combine interactive options " + "(--interactive, --exec, --rebase-merges, " + "--preserve-merges, --keep-empty, --root + " + "--onto) with am options (%s)"), buf.buf); + if (options.type == REBASE_MERGE && buf.len) + die(_("error: cannot combine merge options (--merge, " + "--strategy, --strategy-option) with am options " + "(%s)"), buf.buf); + } + if (options.signoff) { if (options.type == REBASE_PRESERVE_MERGES) die("cannot combine '--signoff' with " @@ -1106,6 +1128,25 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (options.type == REBASE_PRESERVE_MERGES) + /* +* Note: incompatibility with --signoff handled in signoff block above +* Note: incompatibility with --interactive is just a strong warning; +* git-rebase.txt caveats with "unless you know what you are doing" +*/ + if (options.rebase_merges) + die(_("error: cannot combine '--preserve_merges' with " + "'--rebase-merges'")); + + if (options.rebase_merges) { + if (strategy_options.nr) + die(_("error: cannot combine '--rebase_merges' with " + "'--strategy-option'")); + if (options.strategy) + die(_("error: cannot combine '--rebase_merges' with " + "'--strategy'")); + } + if (!options.root) { if (argc < 1) { struct branch *branch; -- 2.18.0
[GSoC] [PATCH 0/6] builtin rebase rest
This patch pretty much converts all what is remaining from the shell-script version of `git rebase` to the builtin version. After the conversion of actions and different options which are important for builtin rebase and have been organized to make a patch series of their own. There were still some leftover shell scripts on the original rebase and hence, this patch series addresses those by converting them and this completes the builtin rebase. It adds the functionality to the previous patch series, (https://public-inbox.org/git/20180808152140.14585-1-predatoram...@gmail.com/). This is the fifth 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: 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 (6): builtin rebase: optionally auto-detect the upstream builtin rebase: optionally pass custom reflogs to reset_head() builtin rebase: fast-forward to onto if it is a proper descendant builtin rebase: show progress when connected to a terminal builtin rebase: use no-op editor when interactive is "implied" builtin rebase: error out on incompatible option/mode combinations builtin/rebase.c | 150 ++- 1 file changed, 135 insertions(+), 15 deletions(-) -- 2.18.0
[PATCH 4/6] builtin rebase: show progress when connected to a terminal
In this commit, we pass `--progress` to the `format-patch` command if stderr is connected to an interactive terminal, unless we're in quiet mode. This `--progress` option will be used in `format-patch` to show progress reports on stderr as patches are generated. Signed-off-by: Pratik Karki --- builtin/rebase.c | 7 +++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1bb64e7cd7..04974dff6e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -98,6 +98,7 @@ struct rebase_options { int allow_empty_message; int rebase_merges, rebase_cousins; char *strategy, *strategy_opts; + struct strbuf git_format_patch_opt; }; static int is_interactive(struct rebase_options *opts) @@ -379,6 +380,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "rebase_root", opts->root ? "t" : ""); add_var(_snippet, "squash_onto", opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); + add_var(_snippet, "git_format_patch_opt", + opts->git_format_patch_opt.buf); switch (opts->type) { case REBASE_AM: @@ -667,6 +670,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, .allow_rerere_autoupdate = -1, .allow_empty_message = 1, + .git_format_patch_opt = STRBUF_INIT, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -1068,6 +1072,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.root && !options.onto_name) imply_interactive(, "--root without --onto"); + if (isatty(2) && options.flags & REBASE_NO_QUIET) + strbuf_addstr(_format_patch_opt, " --progress"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[PATCH 2/6] builtin rebase: optionally pass custom reflogs to reset_head()
In the next patch, we will make use of that in the code that fast-forwards to `onto` whenever possible. Signed-off-by: Pratik Karki --- builtin/rebase.c | 33 + 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c5b2534717..c51b9d288a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -431,7 +431,8 @@ static int run_specific_rebase(struct rebase_options *opts) #define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, int detach_head) + const char *switch_to_branch, int detach_head, + const char *reflog_orig_head, const char *reflog_head) { struct object_id head_oid; struct tree_desc desc; @@ -506,20 +507,26 @@ static int reset_head(struct object_id *oid, const char *action, old_orig = _old_orig; if (!get_oid("HEAD", _orig)) { orig = _orig; - strbuf_addstr(, "updating ORIG_HEAD"); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + if (!reflog_orig_head) { + strbuf_addstr(, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - strbuf_setlen(, prefix_len); - strbuf_addstr(, "updating HEAD"); + if (!reflog_head) { + strbuf_setlen(, prefix_len); + strbuf_addstr(, "updating HEAD"); + reflog_head = msg.buf; + } if (!switch_to_branch) - ret = update_ref(msg.buf, "HEAD", oid, orig, REF_NO_DEREF, + ret = update_ref(reflog_head, "HEAD", oid, orig, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR); else { ret = create_symref("HEAD", switch_to_branch, msg.buf); if (!ret) - ret = update_ref(msg.buf, "HEAD", oid, NULL, 0, + ret = update_ref(reflog_head, "HEAD", oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); } @@ -899,7 +906,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(_rr); string_list_clear(_rr, 1); - if (reset_head(NULL, "reset", NULL, 0) < 0) + if (reset_head(NULL, "reset", NULL, 0, NULL, NULL) < 0) die(_("could not discard worktree changes")); if (read_basic_state()) exit(1); @@ -915,7 +922,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state()) exit(1); if (reset_head(_head, "reset", - options.head_name, 0) < 0) + options.head_name, 0, NULL, NULL) < 0) die(_("could not move back to %s"), oid_to_hex(_head)); ret = finish_rebase(); @@ -1235,7 +1242,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) write_file(autostash, "%s", buf.buf); printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(>object.oid, "reset --hard", - NULL, 0) < 0) + NULL, 0, NULL, NULL) < 0) die(_("could not reset --hard")); printf(_("HEAD is now at %s"), find_unique_abbrev(>object.oid, @@ -1289,7 +1296,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(, "rebase: checkout %s", options.switch_to); if (reset_head(, "checkout", - options.head_name, 0) < 0) { + options.head_name, 0, + NULL, NULL) < 0) { ret = !!error(_("could not switch to " "%s"), options.switch_to); @@ -1354,7 +1362,8 @@ int cmd_rebase(int argc, const
[PATCH 16/18] builtin rebase: support `fork-point` option
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 --- 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", _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", _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, + _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
[PATCH 14/18] builtin rebase: support --rebase-merges[=[no-]rebase-cousins]
The mode to rebase non-linear branches is now supported by the builtin rebase, too. Signed-off-by: Pratik Karki --- 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(_snippet, "cmd", opts->cmd); add_var(_snippet, "allow_empty_message", opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(_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", _name, N_("revision"), @@ -704,6 +710,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-empty-message", _empty_message, N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", _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(, "--rebase-merges"); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[PATCH 17/18] builtin rebase: add support for custom merge strategies
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 --- 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(); + if (read_one(state_dir_path("strategy", opts), )) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(); + if (read_one(state_dir_path("strategy_opts", opts), )) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + strbuf_release(); return 0; @@ -356,6 +373,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_merges ? "t" : ""); add_var(_snippet, "rebase_cousins", opts->rebase_cousins ? "t" : ""); + add_var(_snippet, "strategy", opts->strategy); + add_var(_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", _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", _point, N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", , + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", _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(, "--rebase-merges"); } + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(, " --%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
[PATCH 15/18] merge-base --fork-point: extract libified function
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 --- 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]), , )) { 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, ); - memset(, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, ); - if (!revs.nr && !get_oid(refname, )) - add_one_commit(, ); + 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 (>item->object == [i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(>item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(_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 ||
[PATCH 18/18] builtin rebase: support --root
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 --- 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(_snippet, "strategy", opts->strategy); add_var(_snippet, "strategy_opts", opts->strategy_opts); + add_var(_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(_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", _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", , +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(, "--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, + _onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = _onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(_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, _head, _base) && !is_interactive() && !options.restrict_revision && + options.upstream && !oidcmp(>object.oid, >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
[PATCH 13/18] builtin rebase: support `--allow-empty-message` option
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 --- 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(_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(_snippet, "cmd", opts->cmd); + add_var(_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", , N_("exec"), N_("add exec lines after each commit of the " "editable list")), + OPT_BOOL(0, "allow-empty-message", +_empty_message, +N_("allow rebasing commits with empty messages")), OPT_END(), }; -- 2.18.0
[PATCH 11/18] builtin rebase: support `--autostash` option
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 --- 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), )) + return error(_("Could not read '%s'"), path); + argv_array_pushl(_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(_apply)) + printf("Applied autostash.\n"); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(, +"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(); + strbuf_release(); + 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(); + 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(, opts->state_dir); remove_dir_recursively(, 0); strbuf_release(); @@ -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, _c, N_("passed to 'git apply'"), REBASE_AM), + OPT_BOOL(0, "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(_file, 0)
[PATCH 12/18] builtin rebase: support `--exec`
This commit adds support for the `--exec` option which takes a shell command-line as argument. This argument will be appended as an `exec ` 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 --- 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(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(_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", _name, N_("revision"), @@ -691,6 +694,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_AM), OPT_BOOL(0, "autostash", , N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "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(, "--exec"); + + strbuf_reset(); + for (i = 0; i < exec.nr; i++) + strbuf_addf(, "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(); free(options.head_name); free(options.gpg_sign_opt); + free(options.cmd); return ret; } -- 2.18.0
[PATCH 09/18] builtin rebase: support `--gpg-sign` option
This commit introduces support for `--gpg-sign` option which is used to GPG-sign commits. Signed-off-by: Pratik Karki --- 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(); + if (read_one(state_dir_path("gpg_sign_opt", opts), + )) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + strbuf_release(); return 0; @@ -297,6 +307,7 @@ static int run_specific_rebase(struct rebase_options *opts) "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(_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", _name, N_("revision"), @@ -619,6 +638,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", , N_("move commits that begin with " "squash!/fixup! under -i")), + OPT_STRING('S', "gpg-sign", _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(, "--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(); free(options.head_name); + free(options.gpg_sign_opt); return ret; } -- 2.18.0
[PATCH 10/18] builtin rebase: support `-C` and `--whitespace=`
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 --- 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", _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", _sign, N_("gpg-sign?"), N_("GPG-sign commits")), + OPT_STRING_LIST(0, "whitespace", , + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', 0, _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(_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(_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
[PATCH 08/18] builtin rebase: support `--autosquash`
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 --- 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(_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(_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", _empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", , +N_("move commits that begin with " + "squash!/fixup! under -i")), OPT_END(), }; -- 2.18.0
[PATCH 07/18] builtin rebase: support `keep-empty` option
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 --- 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(_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) _rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), + OPT_BOOL(0, "keep-empty", _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(, "--keep-empty"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[PATCH 05/18] builtin rebase: support `ignore-whitespace` option
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 --- 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", _name, N_("revision"), @@ -542,6 +543,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "ignore-whitespace", _whitespace, +N_("passed to 'git apply'")), OPT_BOOL(0, "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(_am_opt, " --ignore-whitespace"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[PATCH 04/18] builtin rebase: support --committer-date-is-author-date
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 --- 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", _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", , N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", +_date_is_author_date, +N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " -q"); + if (committer_date_is_author_date) { + strbuf_addstr(_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[PATCH 02/18] builtin rebase: support --signoff
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 --- 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(); return 0; @@ -249,6 +255,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (opts->switch_to) add_var(_snippet, "switch_to", opts->switch_to); add_var(_snippet, "action", opts->action ? opts->action : ""); + add_var(_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", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", , +N_("add a Signed-off-by: line to each commit")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + if (!options.root) { if (argc < 1) die("TODO: handle @{upstream}"); -- 2.18.0
[PATCH 01/18] builtin rebase: allow selecting the rebase "backend"
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 --- 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", , N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", , NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", , 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", , + 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(); } + if (is_interactive()) + 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
[PATCH 03/18] builtin rebase: support --rerere-autoupdate
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 --- 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(); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + )) + 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(); return 0; @@ -256,6 +272,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "switch_to", opts->switch_to); add_var(_snippet, "action", opts->action ? opts->action : ""); add_var(_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(_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", , N_("try to recreate merges instead of ignoring " "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", +_rerere_autoupdate, +N_("allow rerere to update index with resolved " + "conflict")), OPT_END(), }; -- 2.18.0
[PATCH 06/18] builtin rebase: support `ignore-date` option
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 --- 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", _name, @@ -550,6 +551,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "committer-date-is-author-date", _date_is_author_date, N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", _date, +N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", , 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(_am_opt, " --ignore-whitespace"); + if (ignore_date) { + strbuf_addstr(_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[GSoC] [PATCH 00/18] builtin rebase options
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-predatoram...@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=` 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
[PATCH 7/7] builtin rebase: stop if `git am` is in progress
This commit checks for the file `applying` used by `git am` in `rebase-apply/` and if the file is present it means `git am` is in progress so it errors out. Signed-off-by: Pratik Karki --- builtin/rebase.c | 5 + 1 file changed, 5 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1344e071f3..75c4ac66e0 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -539,6 +539,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) git_config(rebase_config, ); + strbuf_reset(); + strbuf_addf(, "%s/applying", apply_dir()); + if(file_exists(buf.buf)) + die(_("It looks like 'git am' is in progress. Cannot rebase.")); + if (is_directory(apply_dir())) { options.type = REBASE_AM; options.state_dir = apply_dir(); -- 2.18.0
[PATCH 6/7] builtin rebase: actions require a rebase in progress
This commit prevents actions (such as --continue, --skip) from running when there is no rebase in progress. Signed-off-by: Pratik Karki --- builtin/rebase.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index e3dd2f511e..1344e071f3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -577,6 +577,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (action != NO_ACTION && !in_progress) + die(_("No rebase in progress?")); + if (action == ACTION_EDIT_TODO && !is_interactive()) die(_("The --edit-todo action can only be used during " "interactive rebase.")); -- 2.18.0
[PATCH 5/7] builtin rebase: support --edit-todo and --show-current-patch
While these sub-commands are very different in spirit, their implementation is almost identical, so we convert them in one go. And since those are the last sub-commands that needed to be converted, now we can also turn that `default:` case into a bug (because we should now handle all the actions). Signed-off-by: Pratik Karki --- builtin/rebase.c | 23 ++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 0d805480a1..e3dd2f511e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -472,6 +472,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SKIP, ACTION_ABORT, ACTION_QUIT, + ACTION_EDIT_TODO, + ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -503,6 +505,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_ABORT), OPT_CMDMODE(0, "quit", , N_("abort but keep HEAD where it is"), ACTION_QUIT), + OPT_CMDMODE(0, "edit-todo", , N_("edit the todo list " + "during an interactive rebase"), ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", , + N_("show the patch file being applied or merged"), + ACTION_SHOW_CURRENT_PATCH), OPT_END(), }; @@ -570,6 +577,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (action == ACTION_EDIT_TODO && !is_interactive()) + die(_("The --edit-todo action can only be used during " + "interactive rebase.")); + switch (action) { case ACTION_CONTINUE: { struct object_id head; @@ -639,8 +650,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not remove '%s'"), options.state_dir); goto cleanup; } + case ACTION_EDIT_TODO: + options.action = "edit-todo"; + options.dont_finish_rebase = 1; + goto run_rebase; + case ACTION_SHOW_CURRENT_PATCH: + options.action = "show-current-patch"; + options.dont_finish_rebase = 1; + goto run_rebase; + case NO_ACTION: + break; default: - die("TODO"); + BUG("action: %d", action); } /* Make sure no rebase is in progress */ -- 2.18.0
[PATCH 1/7] builtin rebase: support --continue
This commit adds the option `--continue` which is used to resume rebase after merge conflicts. The code tries to stay as close to the equivalent shell scripts found in `git-legacy-rebase.sh` as possible. When continuing a rebase, the state variables are read from state_dir. Some of the state variables are not actually stored there, such as `upstream`. The shell script version simply does not set them, but for consistency, we unset them in the builtin version. Signed-off-by: Pratik Karki --- builtin/rebase.c | 115 +-- strbuf.c | 9 strbuf.h | 3 ++ 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index b2ddfa8dbf..10da4c978b 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -91,6 +91,7 @@ struct rebase_options { REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; struct strbuf git_am_opt; + const char *action; }; static int is_interactive(struct rebase_options *opts) @@ -115,6 +116,62 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o return path.buf; } +/* Read one file, then strip line endings */ +static int read_one(const char *path, struct strbuf *buf) +{ + if (strbuf_read_file(buf, path, 0) < 0) + return error_errno(_("could not read '%s'"), path); + strbuf_trim_trailing_newline(buf); + return 0; +} + +/* Initialize the rebase options from the state directory. */ +static int read_basic_state(struct rebase_options *opts) +{ + struct strbuf head_name = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_one(state_dir_path("head-name", opts), _name) || + read_one(state_dir_path("onto", opts), )) + return -1; + opts->head_name = starts_with(head_name.buf, "refs/") ? + xstrdup(head_name.buf) : NULL; + strbuf_release(_name); + if (get_oid(buf.buf, )) + return error(_("could not get 'onto': '%s'"), buf.buf); + opts->onto = lookup_commit_or_die(, buf.buf); + + /* +* We always write to orig-head, but interactive rebase used to write to +* head. Fall back to reading from head to cover for the case that the +* user upgraded git with an ongoing interactive rebase. +*/ + strbuf_reset(); + if (file_exists(state_dir_path("orig-head", opts))) { + if (read_one(state_dir_path("orig-head", opts), )) + return -1; + } else if (read_one(state_dir_path("head", opts), )) + return -1; + if (get_oid(buf.buf, >orig_head)) + return error(_("invalid orig-head: '%s'"), buf.buf); + + strbuf_reset(); + if (read_one(state_dir_path("quiet", opts), )) + return -1; + if (buf.len) + opts->flags &= ~REBASE_NO_QUIET; + else + opts->flags |= REBASE_NO_QUIET; + + if (file_exists(state_dir_path("verbose", opts))) + opts->flags |= REBASE_VERBOSE; + + strbuf_release(); + + return 0; +} + static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; @@ -168,12 +225,13 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "state_dir", opts->state_dir); add_var(_snippet, "upstream_name", opts->upstream_name); - add_var(_snippet, "upstream", -oid_to_hex(>upstream->object.oid)); + add_var(_snippet, "upstream", opts->upstream ? + oid_to_hex(>upstream->object.oid) : NULL); add_var(_snippet, "head_name", opts->head_name ? opts->head_name : "detached HEAD"); add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); - add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); + add_var(_snippet, "onto", opts->onto ? + oid_to_hex(>onto->object.oid) : NULL); add_var(_snippet, "onto_name", opts->onto_name); add_var(_snippet, "revisions", opts->revisions); add_var(_snippet, "restrict_revision", opts->restrict_revision ? @@ -189,6 +247,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_FORCE ? "t" : ""); if (opts->switch_to) add_var(_snippet, "switch_to", opts->switch_to); + add_var(_snippet, "action", opts->action ? opts->action : ""); switch (opts->type) { case REB
[GSoC] [PATCH 0/7] builtin rebase actions
The previous patch series implemented essential options and neccessary configurations. This patch series teaches all the rebase actions like `--continue`, `--skip`, `--abort`, et al. to builtin rebase. These actions are important to operation of `git rebase` hence, are kept in a patch series of their own. It adds the rebase actions to the previous patch series, (https://public-inbox.org/git/20180808134830.19949-1-predatoram...@gmail.com/). This is the third 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 options: The builtin rebase will add all the options supported by original rebase. 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 (7): builtin rebase: support --continue builtin rebase: support --skip builtin rebase: support --abort builtin rebase: support --quit builtin rebase: support --edit-todo and --show-current-patch builtin rebase: actions require a rebase in progress builtin rebase: stop if `git am` is in progress builtin/rebase.c | 193 ++- strbuf.c | 9 +++ strbuf.h | 3 + 3 files changed, 201 insertions(+), 4 deletions(-) -- 2.18.0
[PATCH 3/7] builtin rebase: support --abort
This commit teaches the builtin rebase the "abort" action, which a user can call to roll back a rebase that is in progress. Signed-off-by: Pratik Karki --- builtin/rebase.c | 20 1 file changed, 20 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7a903838b1..8e94f6cc85 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -470,6 +470,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) NO_ACTION, ACTION_CONTINUE, ACTION_SKIP, + ACTION_ABORT, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -496,6 +497,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_CONTINUE), OPT_CMDMODE(0, "skip", , N_("skip current patch and continue"), ACTION_SKIP), + OPT_CMDMODE(0, "abort", , + N_("abort and check out the original branch"), + ACTION_ABORT), OPT_END(), }; @@ -608,6 +612,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) exit(1); goto run_rebase; } + case ACTION_ABORT: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + options.action = "abort"; + + rerere_clear(_rr); + string_list_clear(_rr, 1); + + if (read_basic_state()) + exit(1); + if (reset_head(_head, "reset", + options.head_name, 0) < 0) + die(_("could not move back to %s"), + oid_to_hex(_head)); + ret = finish_rebase(); + goto cleanup; + } default: die("TODO"); } -- 2.18.0
[PATCH 2/7] builtin rebase: support --skip
This commit adds the option `--skip` which is used to restart rebase after skipping the current patch. Signed-off-by: Pratik Karki --- builtin/rebase.c | 18 ++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 10da4c978b..7a903838b1 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -21,6 +21,7 @@ #include "diff.h" #include "wt-status.h" #include "revision.h" +#include "rerere.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -468,6 +469,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) enum { NO_ACTION, ACTION_CONTINUE, + ACTION_SKIP, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -492,6 +494,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_FORCE), OPT_CMDMODE(0, "continue", , N_("continue"), ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", , + N_("skip current patch and continue"), ACTION_SKIP), OPT_END(), }; @@ -590,6 +594,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) exit(1); goto run_rebase; } + case ACTION_SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + options.action = "skip"; + + rerere_clear(_rr); + string_list_clear(_rr, 1); + + if (reset_head(NULL, "reset", NULL, 0) < 0) + die(_("could not discard worktree changes")); + if (read_basic_state()) + exit(1); + goto run_rebase; + } default: die("TODO"); } -- 2.18.0
[PATCH 4/7] builtin rebase: support --quit
With this patch, the builtin rebase handles the `--quit` action which can be used to abort a rebase without rolling back any changes performed during the rebase (this is useful when a user forgot that they were in the middle of a rebase and continued working normally). Signed-off-by: Pratik Karki --- builtin/rebase.c | 11 +++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 8e94f6cc85..0d805480a1 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -471,6 +471,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_CONTINUE, ACTION_SKIP, ACTION_ABORT, + ACTION_QUIT, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -500,6 +501,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", , N_("abort and check out the original branch"), ACTION_ABORT), + OPT_CMDMODE(0, "quit", , + N_("abort but keep HEAD where it is"), ACTION_QUIT), OPT_END(), }; @@ -628,6 +631,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ret = finish_rebase(); goto cleanup; } + case ACTION_QUIT: { + strbuf_reset(); + strbuf_addstr(, options.state_dir); + ret = !!remove_dir_recursively(, 0); + if (ret) + die(_("could not remove '%s'"), options.state_dir); + goto cleanup; + } default: die("TODO"); } -- 2.18.0
[PATCH 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`
When running a rebase on a detached HEAD, we currently store the string "detached HEAD" in options.head_name. That is a faithful translation of the shell script version, and we still kind of need it for the purposes of the scripted backends. It is poor style for C, though, where we would really only want a valid, fully-qualified ref name as value, and NULL for detached HEADs, using "detached HEAD" for display only. Make it so. Signed-off-by: Pratik Karki --- builtin/rebase.c | 11 --- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index a261f552f1..63634210c0 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -169,7 +169,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "upstream_name", opts->upstream_name); add_var(_snippet, "upstream", oid_to_hex(>upstream->object.oid)); - add_var(_snippet, "head_name", opts->head_name); + add_var(_snippet, "head_name", + opts->head_name ? opts->head_name : "detached HEAD"); add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); add_var(_snippet, "onto_name", opts->onto_name); @@ -251,6 +252,9 @@ static int reset_head(struct object_id *oid, const char *action, *old_orig = NULL, oid_old_orig; int ret = 0; + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) return -1; @@ -558,7 +562,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * branch_name -- branch/commit being rebased, or *HEAD (already detached) * orig_head -- commit object name of tip of the branch before rebasing -* head_name -- refs/heads/ or "detached HEAD" +* head_name -- refs/heads/ or NULL (detached HEAD) */ if (argc > 0) die("TODO: handle switch_to"); @@ -575,7 +579,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) branch_name = options.head_name; } else { - options.head_name = xstrdup("detached HEAD"); + free(options.head_name); + options.head_name = NULL; branch_name = "HEAD"; } if (get_oid("HEAD", _head)) -- 2.18.0
[PATCH 09/11] builtin rebase: start a new rebase only if none is in progress
To run a new rebase, there needs to be a check to assure that no other rebase is in progress. New rebase operation cannot start until an ongoing rebase operation completes or is terminated. Signed-off-by: Pratik Karki --- builtin/rebase.c | 48 +++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 8a7bf3d468..a261f552f1 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -87,6 +87,7 @@ struct rebase_options { REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, REBASE_FORCE = 1<<3, + REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; struct strbuf git_am_opt; }; @@ -392,10 +393,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, }; const char *branch_name; - int ret, flags; + int ret, flags, in_progress = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, @@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) git_config(rebase_config, ); + if (is_directory(apply_dir())) { + options.type = REBASE_AM; + options.state_dir = apply_dir(); + } else if (is_directory(merge_dir())) { + strbuf_reset(); + strbuf_addf(, "%s/rewritten", merge_dir()); + if (is_directory(buf.buf)) { + options.type = REBASE_PRESERVE_MERGES; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else { + strbuf_reset(); + strbuf_addf(, "%s/interactive", merge_dir()); + if(file_exists(buf.buf)) { + options.type = REBASE_INTERACTIVE; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else + options.type = REBASE_MERGE; + } + options.state_dir = merge_dir(); + } + + if (options.type != REBASE_UNSPECIFIED) + in_progress = 1; + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -455,6 +481,26 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + /* Make sure no rebase is in progress */ + if (in_progress) { + const char *last_slash = strrchr(options.state_dir, '/'); + const char *state_dir_base = + last_slash ? last_slash + 1 : options.state_dir; + const char *cmd_live_rebase = + "git rebase (--continue | --abort | --skip)"; + strbuf_reset(); + strbuf_addf(, "rm -fr \"%s\"", options.state_dir); + die(_("It seems that there is already a %s directory, and\n" + "I wonder if you are in the middle of another rebase. " + "If that is the\n" + "case, please try\n\t%s\n" + "If that is not the case, please\n\t%s\n" + "and run me again. I am stopping in case you still " + "have something\n" + "valuable there.\n"), + state_dir_base, cmd_live_rebase,buf.buf); + } + if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(_am_opt, " -q"); -- 2.18.0
[PATCH 11/11] builtin rebase: support `git rebase `
This commit adds support for `switch-to` which is used to switch to the target branch if needed. The equivalent codes found in shell script `git-legacy-rebase.sh` is converted to builtin `rebase.c`. Signed-off-by: Pratik Karki --- builtin/rebase.c | 48 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 63634210c0..b2ddfa8dbf 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,7 @@ struct rebase_options { struct commit *onto; const char *onto_name; const char *revisions; + const char *switch_to; int root; struct commit *restrict_revision; int dont_finish_rebase; @@ -186,6 +187,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_DIFFSTAT ? "t" : ""); add_var(_snippet, "force_rebase", opts->flags & REBASE_FORCE ? "t" : ""); + if (opts->switch_to) + add_var(_snippet, "switch_to", opts->switch_to); switch (opts->type) { case REBASE_AM: @@ -564,9 +567,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or NULL (detached HEAD) */ - if (argc > 0) -die("TODO: handle switch_to"); - else { + if (argc == 1) { + /* Is it "rebase other branchname" or "rebase other commit"? */ + branch_name = argv[0]; + options.switch_to = argv[0]; + + /* Is it a local branch? */ + strbuf_reset(); + strbuf_addf(, "refs/heads/%s", branch_name); + if (!read_ref(buf.buf, _head)) + options.head_name = xstrdup(buf.buf); + /* If not is it a valid ref (branch or commit)? */ + else if (!get_oid(branch_name, _head)) + options.head_name = NULL; + else + die(_("fatal: no such branch/commit '%s'"), + branch_name); + } else if (argc == 0) { /* Do not need to switch branches, we are already on it. */ options.head_name = xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, @@ -585,7 +602,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (get_oid("HEAD", _head)) die(_("Could not resolve HEAD to a revision")); - } + } else + BUG("unexpected number of arguments left to parse"); if (read_index(the_repository->index) < 0) die(_("could not read index")); @@ -612,6 +630,28 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int flag; if (!(options.flags & REBASE_FORCE)) { + /* Lazily switch to the target branch if needed... */ + if (options.switch_to) { + struct object_id oid; + + if (get_oid(options.switch_to, ) < 0) { + ret = !!error(_("could not parse '%s'"), + options.switch_to); + goto cleanup; + } + + strbuf_reset(); + strbuf_addf(, "rebase: checkout %s", + options.switch_to); + if (reset_head(, "checkout", + options.head_name, 0) < 0) { + ret = !!error(_("could not switch to " + "%s"), + options.switch_to); + goto cleanup; + } + } + if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && -- 2.18.0
[PATCH 07/11] builtin rebase: try to fast forward when possible
In this commit, we add support to fast forward. Note: we will need the merge base later, therefore the call to can_fast_forward() really needs to be the first one when testing whether we can skip the rebase entirely (otherwise, it would make more sense to skip the possibly expensive operation if, say, running an interactive rebase). Signed-off-by: Pratik Karki --- builtin/rebase.c | 72 1 file changed, 72 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index afef0b0046..52a218cd18 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -20,6 +20,7 @@ #include "commit.h" #include "diff.h" #include "wt-status.h" +#include "revision.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -89,6 +90,12 @@ struct rebase_options { struct strbuf git_am_opt; }; +static int is_interactive(struct rebase_options *opts) +{ + return opts->type == REBASE_INTERACTIVE || + opts->type == REBASE_PRESERVE_MERGES; +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -334,6 +341,46 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +/* + * Determines whether the commits in from..to are linear, i.e. contain + * no merge commits. This function *expects* `from` to be an ancestor of + * `to`. + */ +static int is_linear_history(struct commit *from, struct commit *to) +{ + while (to && to != from) { + parse_commit(to); + if (!to->parents) + return 1; + if (to->parents->next) + return 0; + to = to->parents->item; + } + return 1; +} + +static int can_fast_forward(struct commit *onto, struct object_id *head_oid, + struct object_id *merge_base) +{ + struct commit *head = lookup_commit(the_repository, head_oid); + struct commit_list *merge_bases; + int res; + + if (!head) + return 0; + + merge_bases = get_merge_bases(onto, head); + if (merge_bases && !merge_bases->next) { + oidcpy(merge_base, _bases->item->object.oid); + res = !oidcmp(merge_base, >object.oid); + } else { + oidcpy(merge_base, _oid); + res = 0; + } + free_commit_list(merge_bases); + return res && is_linear_history(onto, head); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -489,6 +536,31 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } + /* +* Now we are rebasing commits upstream..orig_head (or with --root, +* everything leading up to orig_head) on top of onto. +*/ + + /* +* Check if we are already based on onto with linear history, +* but this should be done only when upstream and onto are the same +* and if this is not an interactive rebase. +*/ + if (can_fast_forward(options.onto, _head, _base) && + !is_interactive() && !options.restrict_revision && + !oidcmp(>object.oid, >object.oid)) { + int flag; + + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && + resolve_ref_unsafe("HEAD", 0, NULL, )) + puts(_("HEAD is up to date, rebase forced.")); + else + printf(_("Current branch %s is up to date, rebase " +"forced.\n"), branch_name); + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, -- 2.18.0
[PATCH 08/11] builtin rebase: support --force-rebase
In this commit, we add support to `--force-rebase` option. The equivalent part of the shell script found in `git-legacy-rebase.sh` is converted as faithfully as possible to C. The --force-rebase option ensures that the rebase does not simply fast-forward even if it could. Signed-off-by: Pratik Karki --- builtin/rebase.c | 24 ++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 52a218cd18..8a7bf3d468 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -86,6 +86,7 @@ struct rebase_options { REBASE_NO_QUIET = 1<<0, REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, + REBASE_FORCE = 1<<3, } flags; struct strbuf git_am_opt; }; @@ -181,6 +182,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_VERBOSE ? "t" : ""); add_var(_snippet, "diffstat", opts->flags & REBASE_DIFFSTAT ? "t" : ""); + add_var(_snippet, "force_rebase", + opts->flags & REBASE_FORCE ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -409,6 +412,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", , NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BIT('f', "force-rebase", , + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), + OPT_BIT(0, "no-ff", , + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), OPT_END(), }; @@ -551,10 +560,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) !oidcmp(>object.oid, >object.oid)) { int flag; - if (!(options.flags & REBASE_NO_QUIET)) + if (!(options.flags & REBASE_FORCE)) { + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && +resolve_ref_unsafe("HEAD", 0, NULL, )) + puts(_("HEAD is up to date.")); + else + printf(_("Current branch %s is up to date.\n"), + branch_name); + ret = !!finish_rebase(); + goto cleanup; + } else if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && - resolve_ref_unsafe("HEAD", 0, NULL, )) +resolve_ref_unsafe("HEAD", 0, NULL, )) puts(_("HEAD is up to date, rebase forced.")); else printf(_("Current branch %s is up to date, rebase " -- 2.18.0
[GSoC] [PATCH 00/11] A minimal builtin rebase
This patch series provides the bare minimum to run more than the trivial rebase (i.e. `git rebase `). Here, I have implemented essential options needed to make this a builtin rebase. Ofcourse, to accomplish the task of builtin rebase, I had to do essential optimizations and add certain shield which weren't present in original rebase. It is based the latest iteration of pk/rebase-in-c, i.e. ac7f467fef8b (builtin/rebase: support running "git rebase ", 2018-08-07). This is the second 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. I plan on submitting the next patch series today, in this order: bultin rebase actions: The builtin rebase will add all the rebase actions. builtin rebase options: The builtin rebase will add all the options supported by original rebase. 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. The motivation to organize these patch series is to make review easier and for pleasant read with the help of my GSoC mentors since, this is the final week of GSoC and other GSoC students most likely have submitted their respective works which will need lots of review. Pratik Karki (11): builtin rebase: support --onto builtin rebase: support `git rebase --onto A...B` builtin rebase: handle the pre-rebase hook (and add --no-verify) builtin rebase: support --quiet builtin rebase: support the `verbose` and `diffstat` options builtin rebase: require a clean worktree builtin rebase: try to fast forward when possible builtin rebase: support --force-rebase builtin rebase: start a new rebase only if none is in progress builtin rebase: only store fully-qualified refs in `options.head_name` builtin rebase: support `git rebase ` builtin/rebase.c | 333 +-- 1 file changed, 320 insertions(+), 13 deletions(-) -- 2.18.0
[PATCH 05/11] builtin rebase: support the `verbose` and `diffstat` options
This commit introduces support for the `-v` and `--stat` options of rebase. The --stat option can also be configured via the Git config setting rebase.stat. To support this, we also add a custom rebase_config() function in this commit that will be used instead of (and falls back to calling) git_default_config(). Signed-off-by: Pratik Karki --- builtin/rebase.c | 60 ++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 19fa4d3fc4..2d3f1d65fb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -18,6 +18,7 @@ #include "lockfile.h" #include "parse-options.h" #include "commit.h" +#include "diff.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -81,6 +82,8 @@ struct rebase_options { int dont_finish_rebase; enum { REBASE_NO_QUIET = 1<<0, + REBASE_VERBOSE = 1<<1, + REBASE_DIFFSTAT = 1<<2, } flags; struct strbuf git_am_opt; }; @@ -166,6 +169,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "GIT_QUIET", opts->flags & REBASE_NO_QUIET ? "" : "t"); add_var(_snippet, "git_am_opt", opts->git_am_opt.buf); + add_var(_snippet, "verbose", + opts->flags & REBASE_VERBOSE ? "t" : ""); + add_var(_snippet, "diffstat", + opts->flags & REBASE_DIFFSTAT ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -311,6 +318,21 @@ static int reset_head(struct object_id *oid, const char *action, return ret; } +static int rebase_config(const char *var, const char *value, void *data) +{ + struct rebase_options *opts = data; + + if (!strcmp(var, "rebase.stat")) { + if (git_config_bool(var, value)) + opts->flags |= REBASE_DIFFSTAT; + else + opts->flags &= !REBASE_DIFFSTAT; + return 0; + } + + return git_default_config(var, value, data); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -332,7 +354,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", , N_("be quiet. implies --no-stat"), - REBASE_NO_QUIET), + REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT), + OPT_BIT('v', "verbose", , + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + {OPTION_NEGBIT, 'n', "no-stat", , NULL, + N_("do not show diffstat of what changed upstream"), + PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_END(), }; @@ -360,7 +388,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - git_config(git_default_config, NULL); + git_config(rebase_config, ); + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -456,6 +485,33 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); + if (options.flags & REBASE_DIFFSTAT) { + struct diff_options opts; + + if (options.flags & REBASE_VERBOSE) + printf(_("Changes from %s to %s:\n"), + oid_to_hex(_base), + oid_to_hex(>object.oid)); + + /* We want color (if set), but no pager */ + diff_setup(); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ + opts.output_format |= + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + opts.detect_rename = DIFF_DETECT_RENAME; + diff_setup_done(); + diff_tree_oid(_base, >object.oid, + "", ); + diffcore_std(); + diff_flush(); + } + + /* Detach HEAD and reset the tree */ + if (options.flags & REBASE_NO_QUIET) + printf(_("First, rewinding head to replay your work on top of " +"it...\n")); + strbuf_addf(, "rebase: checkout %s", options.onto_name); if (reset_head(>object.oid, "checkout", NULL, 1)) die(_("Could not detach HEAD")); -- 2.18.0
[PATCH 02/11] builtin rebase: support `git rebase --onto A...B`
This commit implements support for an --onto argument that is actually a "symmetric range" i.e. `...`. The equivalent shell script version of the code offers two different error messages for the cases where there is no merge base vs more than one merge base. Though following the similar approach would be nice, this would create more complexity than it is of current. Currently, for simple convenience, the `get_oid_mb()` function is used whose return value does not discern between those two error conditions. Signed-off-by: Pratik Karki --- builtin/rebase.c | 8 +++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 742ed31498..38c496dd10 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -17,6 +17,7 @@ #include "unpack-trees.h" #include "lockfile.h" #include "parse-options.h" +#include "commit.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -311,6 +312,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", _name, N_("revision"), @@ -387,7 +389,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - die("TODO"); + if (get_oid_mb(options.onto_name, _base) < 0) + die(_("'%s': need exactly one merge base"), + options.onto_name); + options.onto = lookup_commit_or_die(_base, + options.onto_name); } else { options.onto = peel_committish(options.onto_name); if (!options.onto) -- 2.18.0
[PATCH 01/11] builtin rebase: support --onto
The `--onto` option is important, as it allows to rebase a range of commits onto a different base commit (which gave the command its odd name: "rebase"). This commit introduces options parsing so that different options can be added in future commits. Note: As this commit introduces to the parse_options() call (which "eats" argv[0]), the argc is now expected to be lower by one after this patch, compared to before this patch: argv[0] no longer refers to the command name, but to the first (non-option) command-line parameter. Signed-off-by: Pratik Karki --- builtin/rebase.c | 35 ++- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index e695d8a430..742ed31498 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -16,6 +16,16 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "lockfile.h" +#include "parse-options.h" + +static char const * const builtin_rebase_usage[] = { + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "[] []"), + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "--root []"), + N_("git rebase --continue | --abort | --skip | --edit-todo"), + NULL +}; static GIT_PATH_FUNC(apply_dir, "rebase-apply") static GIT_PATH_FUNC(merge_dir, "rebase-merge") @@ -301,6 +311,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct option builtin_rebase_options[] = { + OPT_STRING(0, "onto", _name, + N_("revision"), + N_("rebase onto given branch instead of upstream")), + OPT_END(), + }; /* * NEEDSWORK: Once the builtin rebase has been tested enough @@ -318,13 +334,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) BUG("sane_execvp() returned???"); } - if (argc != 2) - die(_("Usage: %s "), argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, +builtin_rebase_options, +builtin_rebase_usage, 0); + + if (argc > 2) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); switch (options.type) { case REBASE_MERGE: @@ -343,10 +368,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (!options.root) { - if (argc < 2) + if (argc < 1) die("TODO: handle @{upstream}"); else { - options.upstream_name = argv[1]; + options.upstream_name = argv[0]; argc--; argv++; if (!strcmp(options.upstream_name, "-")) @@ -377,7 +402,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or "detached HEAD" */ - if (argc > 1) + if (argc > 0) die("TODO: handle switch_to"); else { /* Do not need to switch branches, we are already on it. */ -- 2.18.0
[PATCH 06/11] builtin rebase: require a clean worktree
This commit reads the index of the repository for rebase and checks whether the repository is ready for rebase. Signed-off-by: Pratik Karki --- builtin/rebase.c | 11 +++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2d3f1d65fb..afef0b0046 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -19,6 +19,7 @@ #include "parse-options.h" #include "commit.h" #include "diff.h" +#include "wt-status.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -479,6 +480,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + if (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; + goto cleanup; + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, @@ -528,6 +538,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ret = !!run_specific_rebase(); +cleanup: strbuf_release(); free(options.head_name); return ret; -- 2.18.0
[PATCH 03/11] builtin rebase: handle the pre-rebase hook (and add --no-verify)
This commit converts the equivalent part of the shell script `git-legacy-rebase.sh` to run the pre-rebase hook (unless disabled), and to interrupt the rebase with error if the hook fails. Signed-off-by: Pratik Karki --- builtin/rebase.c | 11 +++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 38c496dd10..b79f9b0a9f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -70,6 +70,7 @@ struct rebase_options { const char *state_dir; struct commit *upstream; const char *upstream_name; + const char *upstream_arg; char *head_name; struct object_id orig_head; struct commit *onto; @@ -310,6 +311,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags; + int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; struct object_id merge_base; @@ -317,6 +319,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", _name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "no-verify", _to_skip_pre_rebase, +N_("allow pre-rebase hook to run")), OPT_END(), }; @@ -382,6 +386,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.upstream = peel_committish(options.upstream_name); if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); + options.upstream_arg = options.upstream_name; } else die("TODO: upstream for --root"); @@ -430,6 +435,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + /* If a hook exists, give it a chance to interrupt*/ + if (!ok_to_skip_pre_rebase && + run_hook_le(NULL, "pre-rebase", options.upstream_arg, + argc ? argv[0] : NULL, NULL)) + die(_("The pre-rebase hook refused to rebase.")); + strbuf_addf(, "rebase: checkout %s", options.onto_name); if (reset_head(>object.oid, "checkout", NULL, 1)) die(_("Could not detach HEAD")); -- 2.18.0
[PATCH 04/11] builtin rebase: support --quiet
This commit introduces a rebase option `--quiet`. While `--quiet` is commonly perceived as opposite to `--verbose`, this is not the case for the rebase command: both `--quiet` and `--verbose` default to `false` if neither `--quiet` nor `--verbose` is present. This commit goes further and introduces `--no-quiet` which is the contrary of `--quiet` and it's introduction doesn't modify any behaviour. Note: The `flags` field in `rebase_options` will accumulate more bits in subsequent commits, in particular a verbose and a diffstat flag. And as --quoet inthe shell scripted version of the rebase command switches off --verbose and --stat, and as --verbose switches off --quiet, we use the (negated) REBASE_NO_QUIET instead of REBASE_QUIET: this allows us to turn off the quiet mode and turn on the verbose and diffstat mode in a single OPT_BIT(), and the opposite in a single OPT_NEGBIT(). Signed-off-by: Pratik Karki --- builtin/rebase.c | 15 +++ 1 file changed, 15 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index b79f9b0a9f..19fa4d3fc4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,10 @@ struct rebase_options { int root; struct commit *restrict_revision; int dont_finish_rebase; + enum { + REBASE_NO_QUIET = 1<<0, + } flags; + struct strbuf git_am_opt; }; /* Returns the filename prefixed by the state_dir */ @@ -159,6 +163,9 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(_snippet, "revisions", opts->revisions); add_var(_snippet, "restrict_revision", opts->restrict_revision ? oid_to_hex(>restrict_revision->object.oid) : NULL); + add_var(_snippet, "GIT_QUIET", + opts->flags & REBASE_NO_QUIET ? "" : "t"); + add_var(_snippet, "git_am_opt", opts->git_am_opt.buf); switch (opts->type) { case REBASE_AM: @@ -308,6 +315,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { .type = REBASE_UNSPECIFIED, + .flags = REBASE_NO_QUIET, + .git_am_opt = STRBUF_INIT, }; const char *branch_name; int ret, flags; @@ -321,6 +330,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("rebase onto given branch instead of upstream")), OPT_BOOL(0, "no-verify", _to_skip_pre_rebase, N_("allow pre-rebase hook to run")), + OPT_NEGBIT('q', "quiet", , + N_("be quiet. implies --no-stat"), + REBASE_NO_QUIET), OPT_END(), }; @@ -357,6 +369,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (!(options.flags & REBASE_NO_QUIET)) + strbuf_addstr(_am_opt, " -q"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: -- 2.18.0
[GSoC] [PATCH v6 2/3] rebase: refactor common shell functions into their own file
The functions present in `git-legacy-rebase.sh` are used by the rebase backends as they are implemented as shell script functions in the `git-rebase--` files. To make the `builtin/rebase.c` work, we have to provide support via a Unix shell script snippet that uses these functions and so, we want to use the rebase backends *directly* from the builtin rebase without going through `git-legacy-rebase.sh`. This commit extracts the functions to a separate file, `git-rebase--common`, that will be read by `git-legacy-rebase.sh` and by the shell script snippets which will be used extensively in the following commits. Signed-off-by: Pratik Karki --- Unchanged since v5. .gitignore| 1 + Makefile | 1 + git-legacy-rebase.sh | 69 ++- git-rebase--common.sh | 68 ++ 4 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 git-rebase--common.sh diff --git a/.gitignore b/.gitignore index ec23959014..824141cba1 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common /git-rebase--helper /git-rebase--interactive /git-rebase--merge diff --git a/Makefile b/Makefile index c311532791..c210681a1d 100644 --- a/Makefile +++ b/Makefile @@ -619,6 +619,7 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 7973447645..af2cdfef03 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -57,12 +57,7 @@ cd_to_toplevel LF=' ' ok_to_skip_pre_rebase= -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm ", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" + squash_onto= unset onto unset restrict_revision @@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +. git-rebase--common read_basic_state () { test -f "$state_dir/head-name" && @@ -132,67 +128,6 @@ read_basic_state () { } } -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - echo "$GIT_QUIET" > "$state_dir"/quiet && - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -move_to_original_branch () { - case "$head_name" in - refs/*) - message="rebase finished: $head_name onto $onto" - git update-ref -m "$message" \ - $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref \ - -m "rebase finished: returning to $head_name" \ - HEAD $head_name || - die "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m &q
[GSoC] [PATCH v6 1/3] rebase: start implementing it as a builtin
This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. In the original difftool conversion, if sane_execvp() that attempts to run the legacy scripted version returned with non-negative status, the command silently exited without doing anything with success, but sane_execvp() should not return with non-negative status in the first place, so we use die() to notice such an abnormal case. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki --- Unchanged since v5. .gitignore| 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 58 +++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index 3284a1e9b1..ec23959014 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index bc4fc8eeab..c311532791 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 0362f1ce25..44651a447f 100644 --- a/builtin.h +++ b/builtin.h @@ -202,6 +202,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 00..c44addb2a4 --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,58 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(, +"config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(, , 6)) { + strbuf_release(); + return 0; + } + + strbuf_trim(); + ret = !strcmp("true", out.buf); + strbuf_release(); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* +* NEEDSWORK: Once the builtin rebase has been tested enough +* and git-legacy-rebase.sh is retired to contrib/, this preamble +* can be removed. +*/ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno(_("could not exec %s"), path); + else + BUG("sane_execvp() returned???"); + } + + if (argc != 2) + die(_("Usage: %s ")
[GSoC] [PATCH v6 0/3] rebase: rewrite rebase in C
As a GSoC project, I have been working on the builtin rebase. The motivation behind the rewrite of rebase i.e. from shell script to C are for following reasons: 1. Writing shell scripts and getting it to production is much faster than doing the equivalent in C but lacks in performance and extra workarounds are needed for non-POSIX platforms. 2. Git for Windows is at loss as the installer size increases due to addition of extra dependencies for the shell scripts which are usually available in POSIX compliant platforms. This series of patches serves to demonstrate a minimal builtin rebase which supports running `git rebase ` and also serves to ask for reviews. This is only the first patch series, with many more to come. I have finished the conversion, but I want to organize the patches in a neat patch series to make review process more pleasant, and you can see the progress at <https://github.com/git/git/pull/505>. The organization of patches is also almost done. After the follow-up patches, the rebase operation will be handled completely by this `builtin/rebase.c`. Changes since v5: - Fix `builtin/rebase: support running "git rebase "` as it does not need to switch to another branch and only needs to detach the `HEAD` for now. - The `reset_head()` function is introduced in `builtin/rebase: support running "git rebase "` which is only used to detach the `HEAD` to `onto` for starting the rebase, but upcoming patches will add more functionality like moving or updating to original branch. - Lots of changes that fix bugs discovered while getting the test suite to pass with the patch series. Pratik Karki (3): rebase: start implementing it as a builtin rebase: refactor common shell functions into their own file builtin/rebase: support running "git rebase " .gitignore| 2 + Makefile | 4 +- builtin.h | 1 + builtin/rebase.c | 421 ++ git-rebase.sh => git-legacy-rebase.sh | 69 + git-rebase--common.sh | 68 + git.c | 6 + 7 files changed, 503 insertions(+), 68 deletions(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (90%) create mode 100644 git-rebase--common.sh -- 2.18.0
[GSoC] [PATCH v6 3/3] builtin/rebase: support running "git rebase "
This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. On Windows, some of the tests fail merely due to core.fileMode not being heeded that's why the core.*config variables is parsed here. The `reset_head()` function is currently only used to detach the HEAD to onto by way of starting the rebase, but it offers additional functionality that subsequent patches will need like moving to the original branch (possibly updating it) and also to do the equivalent of `git reset --hard`. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki --- builtin/rebase.c | 365 ++- 1 file changed, 364 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c44addb2a4..e695d8a430 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,24 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "lockfile.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply") +static GIT_PATH_FUNC(merge_dir, "rebase-merge") + +enum rebase_type { + REBASE_UNSPECIFIED = -1, + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -30,8 +48,260 @@ 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; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + int root; + struct commit *restrict_revision; + int dont_finish_rebase; +}; + +/* Returns the filename prefixed by the state_dir */ +static const char *state_dir_path(const char *filename, struct rebase_options *opts) +{ + static struct strbuf path = STRBUF_INIT; + static size_t prefix_len; + + if (!prefix_len) { + strbuf_addf(, "%s/", opts->state_dir); + prefix_len = path.len; + } + + strbuf_setlen(, prefix_len); + strbuf_addstr(, filename); + return path.buf; +} + +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(); + close_all_packs(the_repository->objects); + /* +* We ignore errors in 'gc --auto', since the +* user should see them. +*/ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(, opts->state_dir); + remove_dir_recursively(, 0); + strbuf_release(); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, )) + return NULL; + obj = parse_object(the_repository, ); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + if (!value) + strbuf_addf(buf, "unset %s; ", name); + else { + strbuf_addf(buf, "%s=", name); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); + } +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(_snippet, "GIT_DIR", absolute_path(get_git_dir())); + add_var(_snippet, "state_dir", opts->state_dir); + + add_var(_snippet, "upstream_name", opts->upstream_name
Re: What's cooking in git.git (Aug 2018, #01; Thu, 2)
Hi Junio, On Fri, Aug 3, 2018 at 4:47 AM Junio C Hamano wrote: > > * pk/rebase-in-c (2018-07-30) 3 commits > - builtin/rebase: support running "git rebase " > - rebase: refactor common shell functions into their own file > - rebase: start implementing it as a builtin > > Rewrite of the "rebase" machinery in C. > > Will merge to 'next'. On a recent mail[1], I had suggested stalling this iteration in `pu` and wait for another iteration to merge in `next`. [1]: https://public-inbox.org/git/CAOZc8M_FJmMCEB1MkJrBRpLiFjy8OTEg_MxoNQTh5-brHxR-=a...@mail.gmail.com/ Cheers, Pratik
Re: [GSoC] [PATCH v5 0/3] rebase: rewrite rebase in C
Hi Junio, During recent development, I found out that `v5` has some issues and shouldn't be merged into `next`. I implemented more options and ran a couple of regression tests from which I figured out that certain choices I made in those commits need to be reconsidered. During recent development, my working branch `wip-rebase` has passing `t3400` and for which I have made some changes to the code already in v5. Cheers, Pratik
[PATCH v5 3/3] builtin/rebase: support running "git rebase "
This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. On Windows, some of the tests fail merely due to core.fileMode not being heeded that's why the core.*config variables is parsed here. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki --- builtin/rebase.c | 350 ++- 1 file changed, 349 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c44addb2a4..5863139204 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,24 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "lockfile.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply") +static GIT_PATH_FUNC(merge_dir, "rebase-merge") + +enum rebase_type { + REBASE_UNSPECIFIED = -1, + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -30,8 +48,243 @@ 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; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + const char *root; + const char *restrict_revision; +}; + +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(); + close_all_packs(the_repository->objects); + /* +* We ignore errors in 'gc --auto', since the +* user should see them. +*/ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(, opts->state_dir); + remove_dir_recursively(, 0); + strbuf_release(); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, )) + return NULL; + obj = parse_object(); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + strbuf_addstr(buf, name); + if (value) { + strbuf_addstr(buf, "="); + sq_quote_buf(buf, value); + } + strbuf_addstr(buf, "; "); +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(_snippet, "GIT_DIR", absolute_path(get_git_dir())); + add_var(_snippet, "state_dir", opts->state_dir); + + add_var(_snippet, "upstream_name", opts->upstream_name); + add_var(_snippet, "upstream", +oid_to_hex(>upstream->object.oid)); + add_var(_snippet, "head_name", opts->head_name); + add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); + add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); + add_var(_snippet, "onto_name", opts->onto_name); + add_var(_snippet, "revisions", opts->revisions); + if (opts->restrict_revision == NULL) + add_var(_snippet, "restrict_revision", ""); + else + add_var(_snippet, "restrict_revision", + opts->restrict_revision); + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"
[PATCH v5 2/3] rebase: refactor common shell functions into their own file
The functions present in `git-legacy-rebase.sh` are used by the rebase backends as they are implemented as shell script functions in the `git-rebase--` files. To make the `builtin/rebase.c` work, we have to provide support via a Unix shell script snippet that uses these functions and so, we want to use the rebase backends *directly* from the builtin rebase without going through `git-legacy-rebase.sh`. This commit extracts the functions to a separate file, `git-rebase--common`, that will be read by `git-legacy-rebase.sh` and by the shell script snippets which will be used extensively in the following commits. Signed-off-by: Pratik Karki --- Unchanged since v4. .gitignore| 1 + Makefile | 1 + git-legacy-rebase.sh | 69 ++- git-rebase--common.sh | 68 ++ 4 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 git-rebase--common.sh diff --git a/.gitignore b/.gitignore index ec23959014..824141cba1 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common /git-rebase--helper /git-rebase--interactive /git-rebase--merge diff --git a/Makefile b/Makefile index a0c410b7d6..c59e2f64a1 100644 --- a/Makefile +++ b/Makefile @@ -619,6 +619,7 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 7973447645..af2cdfef03 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -57,12 +57,7 @@ cd_to_toplevel LF=' ' ok_to_skip_pre_rebase= -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm ", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" + squash_onto= unset onto unset restrict_revision @@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +. git-rebase--common read_basic_state () { test -f "$state_dir/head-name" && @@ -132,67 +128,6 @@ read_basic_state () { } } -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - echo "$GIT_QUIET" > "$state_dir"/quiet && - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -move_to_original_branch () { - case "$head_name" in - refs/*) - message="rebase finished: $head_name onto $onto" - git update-ref -m "$message" \ - $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref \ - -m "rebase finished: returning to $head_name" \ - HEAD $head_name || - die "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m &q
[PATCH v5 1/3] rebase: start implementing it as a builtin
This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. In the original difftool conversion, if sane_execvp() that attempts to run the legacy scripted version returned with non-negative status, the command silently exited without doing anything with success, but sane_execvp() should not return with non-negative status in the first place, so we use die() to notice such an abnormal case. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki --- .gitignore| 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 58 +++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index 3284a1e9b1..ec23959014 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 08e5c54549..a0c410b7d6 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 0362f1ce25..44651a447f 100644 --- a/builtin.h +++ b/builtin.h @@ -202,6 +202,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 00..c44addb2a4 --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,58 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(, +"config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(, , 6)) { + strbuf_release(); + return 0; + } + + strbuf_trim(); + ret = !strcmp("true", out.buf); + strbuf_release(); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* +* NEEDSWORK: Once the builtin rebase has been tested enough +* and git-legacy-rebase.sh is retired to contrib/, this preamble +* can be removed. +*/ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno(_("could not exec %s"), path); + else + BUG("sane_execvp() returned???"); + } + + if (argc != 2) + die(_("Usage: %s "), argv[0]); +
[GSoC] [PATCH v5 0/3] rebase: rewrite rebase in C
As a GSoC project, I have been working on the builtin rebase. The motivation behind the rewrite of rebase i.e. from shell script to C are for following reasons: 1. Writing shell scripts and getting it to production is much faster than doing the equivalent in C but lacks in performance and extra workarounds are needed for non-POSIX platforms. 2. Git for Windows is at loss as the installer size increases due to addition of extra dependencies for the shell scripts which are usually available in POSIX compliant platforms. This series of patches serves to demonstrate a minimal builtin rebase which supports running `git rebase ` and also serves to ask for reviews. Changes since v4: - Remove the `do_reset()` refactored function from sequencer. In other words `sequencer: refactor the code to detach HEAD to checkout.c` patch was dropped to introduce a new function `reset_hard()` for `rebase.c` (as suggested by Johannes). - Fix a case of leak in `rebase: start implementing it as a builtin`. (as pointed out by Andrei Rybak and Eric Sunshine). - Wrap the user visible comments in `_()` and used `BUG()` depending on the scenarios (as pointed out by Duy Nguyen). - Fix the macro `GIT_PATH_FUNC` which expands to function definition and doesn't require semicolons (as pointed out by Beat Bolli). Pratik Karki (3): rebase: start implementing it as a builtin rebase: refactor common shell functions into their own file builtin/rebase: support running "git rebase " .gitignore| 2 + Makefile | 4 +- builtin.h | 1 + builtin/rebase.c | 406 ++ git-rebase.sh => git-legacy-rebase.sh | 69 + git-rebase--common.sh | 68 + git.c | 6 + 7 files changed, 488 insertions(+), 68 deletions(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (90%) create mode 100644 git-rebase--common.sh -- 2.18.0
Re: [PATCH] fixup! builtin/rebase: support running "git rebase "
Hi, On Sat, Jul 21, 2018 at 6:36 PM Beat Bolli wrote: > > The macro GIT_PATH_FUNC expands to a function definition that ends with > a closing brace. The extra semicolon produces a warning when compiling > with -pedantic. > > Signed-off-by: Beat Bolli > --- > > Junio, this applies on top of pk/rebase-in-c. > > Thanks, Beat > > builtin/rebase.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index e38ea80874..6aaae2436f 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -14,8 +14,8 @@ > #include "refs.h" > #include "quote.h" > > -static GIT_PATH_FUNC(apply_dir, "rebase-apply"); > -static GIT_PATH_FUNC(merge_dir, "rebase-merge"); > +static GIT_PATH_FUNC(apply_dir, "rebase-apply") > +static GIT_PATH_FUNC(merge_dir, "rebase-merge") > First of all, I apologize for not replying earlier to your review. I have already fixed this macro in my local repository. The new iteration of my patches was being held due to fixup of `detach_head_to()` and I will soon submit a new iteration of patch within a day or two. Thank you for pointing this out. Cheers, Pratik Karki
Re: [GSoC] GSoC with git, week 10
Hi, On Tue, Jul 10, 2018 at 10:05 PM Alban Gruin wrote: > > Hi, > > I published a new blog post: > > https://blog.pa1ch.fr/posts/2018/07/10/en/gsoc2018-week-10.html > Nice work. Sorry for late notification, my blog is up too. https://prertik.github.io/post/week-10 Cheers, Pratik
Re: [PATCH v4 3/4] sequencer: refactor the code to detach HEAD to checkout.c
Hi, On Mon, Jul 9, 2018 at 3:16 AM Johannes Schindelin wrote: > > Hi Pratik, > > On Sun, 8 Jul 2018, Pratik Karki wrote: > > > diff --git a/checkout.c b/checkout.c > > index bdefc888ba..da68915fd7 100644 > > --- a/checkout.c > > +++ b/checkout.c > > @@ -2,6 +2,11 @@ > > #include "remote.h" > > #include "refspec.h" > > #include "checkout.h" > > +#include "unpack-trees.h" > > +#include "lockfile.h" > > +#include "refs.h" > > +#include "tree.h" > > +#include "cache-tree.h" > > > > struct tracking_name_data { > > /* const */ char *src_ref; > > @@ -42,3 +47,62 @@ const char *unique_tracking_name(const char *name, > > struct object_id *oid) > > free(cb_data.dst_ref); > > return NULL; > > } > > + > > +int detach_head_to(struct object_id *oid, const char *action, > > +const char *reflog_message) > > +{ > > + struct strbuf ref_name = STRBUF_INIT; > > + struct tree_desc desc; > > + struct lock_file lock = LOCK_INIT; > > + struct unpack_trees_options unpack_tree_opts; > > + struct tree *tree; > > + int ret = 0; > > + > > + if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) > > + return -1; > > + > > + memset(_tree_opts, 0, sizeof(unpack_tree_opts)); > > + setup_unpack_trees_porcelain(_tree_opts, action); > > + unpack_tree_opts.head_idx = 1; > > + unpack_tree_opts.src_index = _index; > > + unpack_tree_opts.dst_index = _index; > > + unpack_tree_opts.fn = oneway_merge; > > + unpack_tree_opts.merge = 1; > > + unpack_tree_opts.update = 1; > > + > > + if (read_cache_unmerged()) { > > + rollback_lock_file(); > > + strbuf_release(_name); > > + return error_resolve_conflict(_(action)); > > + } > > + > > + if (!fill_tree_descriptor(, oid)) { > > + error(_("failed to find tree of %s"), oid_to_hex(oid)); > > + rollback_lock_file(); > > + free((void *)desc.buffer); > > + strbuf_release(_name); > > + return -1; > > + } > > + > > + if (unpack_trees(1, , _tree_opts)) { > > + rollback_lock_file(); > > + free((void *)desc.buffer); > > + strbuf_release(_name); > > + return -1; > > + } > > + > > + tree = parse_tree_indirect(oid); > > + prime_cache_tree(_index, tree); > > + > > + if (write_locked_index(_index, , COMMIT_LOCK) < 0) > > + ret = error(_("could not write index")); > > + free((void *)desc.buffer); > > + > > + if (!ret) > > + ret = update_ref(reflog_message, "HEAD", oid, > > + NULL, 0, UPDATE_REFS_MSG_ON_ERR); > > I noticed that this does not actually detach the HEAD. That is my fault, > of course, as I should have not only suggested refactoring the > `do_reset()` function from `sequencer.c`, but I should also have > remembered that that function has the benefit of *always* acting on a > detached HEAD (because it runs during an interactive rebase), and > therefore does not need to detach it explicitly. > > In light of the `reset_hard()` function that you added in a `wip` (see > https://github.com/git/git/pull/505/files#diff-c7361e406139e8cd3a300b80b8f8cc8dR296), > I could imagine that it might be better, after all, to leave `do_reset()` > alone and implement a `reset_hard()` function that also optionally > detaches the `HEAD` (I *think* that the flag `REF_NO_DEREF` would do that > for you). Yes. I think this will be better. Thanks. > Alternatively, just update the code in `do_reset()` to use that flag > first, and only *then* extract the code to `checkout.c`. > > (I could not resist, and made this quick change on top of your > `wip-rebase`, and together with a couple more, obvious fixups, this lets > t3403 pass. It still needs some things that you have not yet sent to the > mailing list, such as support for `--skip`.) Thank you for taking the time to review.
Re: [PATCH v4 3/4] sequencer: refactor the code to detach HEAD to checkout.c
On Mon, Jul 9, 2018 at 10:27 PM Junio C Hamano wrote: > > Pratik Karki writes: > > > In the upcoming builtin rebase, we will have to start by detaching > > the HEAD, just like shell script version does. Essentially, we have > > to do the same thing as `git checkout -q ^0 --`, in pure C. > > > > The aforementioned functionality was already present in `sequencer.c` > > in `do_reset()` function. But `do_reset()` performs more than detaching > > the HEAD, and performs action specific to `sequencer.c`. > > > > So this commit refactors out that part from `do_reset()`, and moves it > > to a new function called `detach_head_to()`. As this function has > > nothing to do with the sequencer, and everything to do with what `git > > checkout -q ^0 --` does, we move that function to checkout.c. > > > > This refactoring actually introduces a slight change in behavior: > > previously, the index was locked before parsing the argument to the > > todo command `reset`, while it now gets locked *after* that, in the > > `detach_head_to()` function. > > > > It does not make a huge difference, and the upside is that this closes > > a few (unlikely) code paths where the index would not be unlocked upon > > error. > > > > Signed-off-by: Pratik Karki > > --- > > Here is a place to say "unchanged since v3" for a change like this > one that changes neither the proposed log message above nor the > patch below to help reviewers who have seen the previous round (they > can stop reading here). Hopefully there are more reviewers than you > who write new code, so spending a bit more time to help them spend > less would be an overall win for the project. Thanks for the review. I'll keep this in mind, the next time.
Re: [PATCH v4 1/4] rebase: start implementing it as a builtin
Hi, On Mon, Jul 9, 2018 at 2:21 PM Eric Sunshine wrote: > > On Mon, Jul 9, 2018 at 3:59 AM Andrei Rybak wrote: > > On 2018-07-08 20:01, Pratik Karki wrote: > > > +static int use_builtin_rebase(void) > > > +{ > > > + struct child_process cp = CHILD_PROCESS_INIT; > > > + struct strbuf out = STRBUF_INIT; > > > + int ret; > > > + > > > + argv_array_pushl(, > > > + "config", "--bool", "rebase.usebuiltin", NULL); > > > + cp.git_cmd = 1; > > > + if (capture_command(, , 6)) > > > + return 0; > > > > Does strbuf out leak on return here? > > Good catch. This _is_ a potential leak. Here is an excerpt from the > documentation of pipe_command(), which is called by capture_command(): > > Any output collected in the buffers is kept even if the > command returns a non-zero exit. > > So, yes, this needs a strbuf_release() before returning. Hmm. This seems to be a problem. Thanks for reviewing.
[PATCH v4 4/4] builtin/rebase: support running "git rebase "
This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki --- builtin/rebase.c | 238 ++- 1 file changed, 237 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index fab7fca37e..e38ea80874 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,20 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "checkout.h" +#include "refs.h" +#include "quote.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply"); +static GIT_PATH_FUNC(merge_dir, "rebase-merge"); + +enum rebase_type { + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -28,8 +42,136 @@ 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; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + const char *root; +}; + +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(); + close_all_packs(the_repository->objects); + /* +* We ignore errors in 'gc --auto', since the +* user should see them. +*/ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(, opts->state_dir); + remove_dir_recursively(, 0); + strbuf_release(); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, )) + return NULL; + obj = parse_object(); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + strbuf_addstr(buf, name); + strbuf_addstr(buf, "="); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(_snippet, "GIT_DIR", absolute_path(get_git_dir())); + + add_var(_snippet, "upstream_name", opts->upstream_name); + add_var(_snippet, "upstream", +oid_to_hex(>upstream->object.oid)); + add_var(_snippet, "head_name", opts->head_name); + add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); + add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); + add_var(_snippet, "onto_name", opts->onto_name); + add_var(_snippet, "revisions", opts->revisions); + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"; + backend_func = "git_rebase__am"; + break; + case REBASE_INTERACTIVE: + backend = "git-rebase--interactive"; + backend_func = "git_rebase__interactive"; + break; + case REBASE_MERGE: + backend = "git-rebase--merge"; + backend_func = "git_rebase__merge"; + break; + case REBASE_PRESERVE_MERGES: + backend = "git-rebase--preserve-merges"; + backend_func = "git_rebase__preserve_merges"; + break; + default: + BUG("Unha
[PATCH v4 3/4] sequencer: refactor the code to detach HEAD to checkout.c
In the upcoming builtin rebase, we will have to start by detaching the HEAD, just like shell script version does. Essentially, we have to do the same thing as `git checkout -q ^0 --`, in pure C. The aforementioned functionality was already present in `sequencer.c` in `do_reset()` function. But `do_reset()` performs more than detaching the HEAD, and performs action specific to `sequencer.c`. So this commit refactors out that part from `do_reset()`, and moves it to a new function called `detach_head_to()`. As this function has nothing to do with the sequencer, and everything to do with what `git checkout -q ^0 --` does, we move that function to checkout.c. This refactoring actually introduces a slight change in behavior: previously, the index was locked before parsing the argument to the todo command `reset`, while it now gets locked *after* that, in the `detach_head_to()` function. It does not make a huge difference, and the upside is that this closes a few (unlikely) code paths where the index would not be unlocked upon error. Signed-off-by: Pratik Karki --- checkout.c | 64 + checkout.h | 3 +++ sequencer.c | 58 +--- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/checkout.c b/checkout.c index bdefc888ba..da68915fd7 100644 --- a/checkout.c +++ b/checkout.c @@ -2,6 +2,11 @@ #include "remote.h" #include "refspec.h" #include "checkout.h" +#include "unpack-trees.h" +#include "lockfile.h" +#include "refs.h" +#include "tree.h" +#include "cache-tree.h" struct tracking_name_data { /* const */ char *src_ref; @@ -42,3 +47,62 @@ const char *unique_tracking_name(const char *name, struct object_id *oid) free(cb_data.dst_ref); return NULL; } + +int detach_head_to(struct object_id *oid, const char *action, + const char *reflog_message) +{ + struct strbuf ref_name = STRBUF_INIT; + struct tree_desc desc; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + int ret = 0; + + if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + memset(_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = _index; + unpack_tree_opts.dst_index = _index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.merge = 1; + unpack_tree_opts.update = 1; + + if (read_cache_unmerged()) { + rollback_lock_file(); + strbuf_release(_name); + return error_resolve_conflict(_(action)); + } + + if (!fill_tree_descriptor(, oid)) { + error(_("failed to find tree of %s"), oid_to_hex(oid)); + rollback_lock_file(); + free((void *)desc.buffer); + strbuf_release(_name); + return -1; + } + + if (unpack_trees(1, , _tree_opts)) { + rollback_lock_file(); + free((void *)desc.buffer); + strbuf_release(_name); + return -1; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(_index, tree); + + if (write_locked_index(_index, , COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (!ret) + ret = update_ref(reflog_message, "HEAD", oid, +NULL, 0, UPDATE_REFS_MSG_ON_ERR); + + strbuf_release(_name); + return ret; + +} diff --git a/checkout.h b/checkout.h index 9980711179..6ce46cf4e8 100644 --- a/checkout.h +++ b/checkout.h @@ -10,4 +10,7 @@ */ extern const char *unique_tracking_name(const char *name, struct object_id *oid); +int detach_head_to(struct object_id *oid, const char *action, + const char *reflog_message); + #endif /* CHECKOUT_H */ diff --git a/sequencer.c b/sequencer.c index 5354d4d51e..106d518f4d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -29,6 +29,7 @@ #include "oidset.h" #include "commit-slab.h" #include "alias.h" +#include "checkout.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -2756,14 +2757,7 @@ static int do_reset(const char *name, int len, struct replay_opts *opts) { struct strbuf ref_name = STRBUF_INIT; struct object_id oid; - struct lock_file lock = LOCK_INIT; - struct tree_desc desc; - struct tree *tree; - struct unpack_trees_options unpack_tree_opts; - int ret = 0, i; - - if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) - return -1; + int i; if (len == 10 && !st
[PATCH v4 2/4] rebase: refactor common shell functions into their own file
The functions present in `git-legacy-rebase.sh` are used by the rebase backends as they are implemented as shell script functions in the `git-rebase--` files. To make the `builtin/rebase.c` work, we have to provide support via a Unix shell script snippet that uses these functions and so, we want to use the rebase backends *directly* from the builtin rebase without going through `git-legacy-rebase.sh`. This commit extracts the functions to a separate file, `git-rebase--common`, that will be read by `git-legacy-rebase.sh` and by the shell script snippets which will be used extensively in the following commits. Signed-off-by: Pratik Karki --- .gitignore| 1 + Makefile | 1 + git-legacy-rebase.sh | 69 ++- git-rebase--common.sh | 68 ++ 4 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 git-rebase--common.sh diff --git a/.gitignore b/.gitignore index ec23959014..824141cba1 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common /git-rebase--helper /git-rebase--interactive /git-rebase--merge diff --git a/Makefile b/Makefile index e88fe2e5fb..163e52ad1a 100644 --- a/Makefile +++ b/Makefile @@ -619,6 +619,7 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 19bdebb480..fbaf16420d 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -57,12 +57,7 @@ cd_to_toplevel LF=' ' ok_to_skip_pre_rebase= -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm ", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" + squash_onto= unset onto unset restrict_revision @@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +. git-rebase--common read_basic_state () { test -f "$state_dir/head-name" && @@ -132,67 +128,6 @@ read_basic_state () { } } -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - echo "$GIT_QUIET" > "$state_dir"/quiet && - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -move_to_original_branch () { - case "$head_name" in - refs/*) - message="rebase finished: $head_name onto $onto" - git update-ref -m "$message" \ - $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref \ - -m "rebase finished: returning to $head_name" \ - HEAD $head_name || - die "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m "autostash"
[PATCH v4 1/4] rebase: start implementing it as a builtin
This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. In the original difftool conversion, if sane_execvp() that attempts to run the legacy scripted version returned with non-negative status, the command silently exited without doing anything with success, but sane_execvp() should not retun with non-negative status in the first place, so we use die() to notice such an abnormal case. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki --- .gitignore| 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 56 +++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index 3284a1e9b1..ec23959014 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 0cb6590f24..e88fe2e5fb 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 0362f1ce25..44651a447f 100644 --- a/builtin.h +++ b/builtin.h @@ -202,6 +202,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 00..fab7fca37e --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,56 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(, +"config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(, , 6)) + return 0; + + strbuf_trim(); + ret = !strcmp("true", out.buf); + strbuf_release(); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* +* NEEDSWORK: Once the builtin rebase has been tested enough +* and git-legacy-rebase.sh is retired to contrib/, this preamble +* can be removed. +*/ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + else + die("sane_execvp() returned???"); + } + + if (argc != 2) + die("Usage: %s ", argv[0]); + prefix = setup_git_directory(); + trace_repo_se
[GSoC] [PATCH v4 0/4] rebase: rewrite rebase in C
As a GSoC project, I have been working on the builtin rebase. The motivation behind the rewrite of rebase i.e. from shell script to C are for following reasons: 1. Writing shell scripts and getting it to production is much faster than doing the equivalent in C but lacks in performance and extra workarounds are needed for non-POSIX platforms. 2. Git for Windows is at loss as the installer size increases due to addition of extra dependencies for the shell scripts which are usually available in POSIX compliant platforms. This series of patches serves to demonstrate a minimal builtin rebase which supports running `git rebase ` and also serves to ask for reviews. Changes since v3: - Fix commit message of `rebase: start implementing it as a builtin`. - Acknowledge Junio's style reviews. - Acknowledge Johannes Schindelin's review. Pratik Karki (4): rebase: start implementing it as a builtin rebase: refactor common shell functions into their own file sequencer: refactor the code to detach HEAD to checkout.c builtin/rebase: support running "git rebase " .gitignore| 2 + Makefile | 4 +- builtin.h | 1 + builtin/rebase.c | 292 ++ checkout.c| 64 ++ checkout.h| 3 + git-rebase.sh => git-legacy-rebase.sh | 69 +- git-rebase--common.sh | 68 ++ git.c | 6 + sequencer.c | 58 + 10 files changed, 446 insertions(+), 121 deletions(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (89%) create mode 100644 git-rebase--common.sh -- 2.18.0
[PATCH v3 4/4] builtin/rebase: support running "git rebase "
This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki --- builtin/rebase.c | 237 ++- 1 file changed, 236 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index fab7fca37e..71f06f0789 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,20 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "checkout.h" +#include "refs.h" +#include "quote.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply"); +static GIT_PATH_FUNC(merge_dir, "rebase-merge"); + +enum rebase_type { + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -28,8 +42,136 @@ 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; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + const char *root; +}; + +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(); + close_all_packs(the_repository->objects); + /* +* We ignore errors in 'gc --auto', since the +* user should see them. +*/ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(, opts->state_dir); + remove_dir_recursively(, 0); + strbuf_release(); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, )) + return NULL; + obj = parse_object(); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + strbuf_addstr(buf, name); + strbuf_addstr(buf, "="); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(_snippet, "GIT_DIR", absolute_path(get_git_dir())); + + add_var(_snippet, "upstream_name", opts->upstream_name); + add_var(_snippet, "upstream", +oid_to_hex(>upstream->object.oid)); + add_var(_snippet, "head_name", opts->head_name); + add_var(_snippet, "orig_head", oid_to_hex(>orig_head)); + add_var(_snippet, "onto", oid_to_hex(>onto->object.oid)); + add_var(_snippet, "onto_name", opts->onto_name); + add_var(_snippet, "revisions", opts->revisions); + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"; + backend_func = "git_rebase__am"; + break; + case REBASE_INTERACTIVE: + backend = "git-rebase--interactive"; + backend_func = "git_rebase__interactive"; + break; + case REBASE_MERGE: + backend = "git-rebase--merge"; + backend_func = "git_rebase__merge"; + break; + case REBASE_PRESERVE_MERGES: + backend = "git-rebase--preserve-merges"; + backend_func = "git_rebase__preserve_merges"; + break; + default: + BUG("Unha
[PATCH v3 3/4] sequencer: refactor the code to detach HEAD to checkout.c
In the upcoming builtin rebase, we will have to start by detaching the HEAD, just like shell script version does. Essentially, we have to do the same thing as `git checkout -q ^0 --`, in pure C. The aforementioned functionality was already present in `sequencer.c` in `do_reset()` function. But `do_reset()` performs more than detaching the HEAD, and performs action specific to `sequencer.c`. So this commit refactors out that part from `do_reset()`, and moves it to a new function called `detach_head_to()`. As this function has nothing to do with the sequencer, and everything to do with what `git checkout -q ^0 --` does, we move that function to checkout.c. This refactoring actually introduces a slight change in behavior: previously, the index was locked before parsing the argument to the todo command `reset`, while it now gets locked *after* that, in the `detach_head_to()` function. It does not make a huge difference, and the upside is that this closes a few (unlikely) code paths where the index would not be unlocked upon error. Signed-off-by: Pratik Karki --- checkout.c | 64 + checkout.h | 3 +++ sequencer.c | 58 +--- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/checkout.c b/checkout.c index bdefc888ba..da68915fd7 100644 --- a/checkout.c +++ b/checkout.c @@ -2,6 +2,11 @@ #include "remote.h" #include "refspec.h" #include "checkout.h" +#include "unpack-trees.h" +#include "lockfile.h" +#include "refs.h" +#include "tree.h" +#include "cache-tree.h" struct tracking_name_data { /* const */ char *src_ref; @@ -42,3 +47,62 @@ const char *unique_tracking_name(const char *name, struct object_id *oid) free(cb_data.dst_ref); return NULL; } + +int detach_head_to(struct object_id *oid, const char *action, + const char *reflog_message) +{ + struct strbuf ref_name = STRBUF_INIT; + struct tree_desc desc; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + int ret = 0; + + if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + memset(_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = _index; + unpack_tree_opts.dst_index = _index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.merge = 1; + unpack_tree_opts.update = 1; + + if (read_cache_unmerged()) { + rollback_lock_file(); + strbuf_release(_name); + return error_resolve_conflict(_(action)); + } + + if (!fill_tree_descriptor(, oid)) { + error(_("failed to find tree of %s"), oid_to_hex(oid)); + rollback_lock_file(); + free((void *)desc.buffer); + strbuf_release(_name); + return -1; + } + + if (unpack_trees(1, , _tree_opts)) { + rollback_lock_file(); + free((void *)desc.buffer); + strbuf_release(_name); + return -1; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(_index, tree); + + if (write_locked_index(_index, , COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (!ret) + ret = update_ref(reflog_message, "HEAD", oid, +NULL, 0, UPDATE_REFS_MSG_ON_ERR); + + strbuf_release(_name); + return ret; + +} diff --git a/checkout.h b/checkout.h index 9980711179..6ce46cf4e8 100644 --- a/checkout.h +++ b/checkout.h @@ -10,4 +10,7 @@ */ extern const char *unique_tracking_name(const char *name, struct object_id *oid); +int detach_head_to(struct object_id *oid, const char *action, + const char *reflog_message); + #endif /* CHECKOUT_H */ diff --git a/sequencer.c b/sequencer.c index 5354d4d51e..106d518f4d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -29,6 +29,7 @@ #include "oidset.h" #include "commit-slab.h" #include "alias.h" +#include "checkout.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -2756,14 +2757,7 @@ static int do_reset(const char *name, int len, struct replay_opts *opts) { struct strbuf ref_name = STRBUF_INIT; struct object_id oid; - struct lock_file lock = LOCK_INIT; - struct tree_desc desc; - struct tree *tree; - struct unpack_trees_options unpack_tree_opts; - int ret = 0, i; - - if (hold_locked_index(, LOCK_REPORT_ON_ERROR) < 0) - return -1; + int i; if (len == 10 && !st
[PATCH v3 1/4] rebase: start implementing it as a builtin
This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki --- .gitignore| 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 56 +++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index 3284a1e9b1..ec23959014 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 0cb6590f24..e88fe2e5fb 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1059,6 +1059,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 0362f1ce25..44651a447f 100644 --- a/builtin.h +++ b/builtin.h @@ -202,6 +202,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 00..fab7fca37e --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,56 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(, +"config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(, , 6)) + return 0; + + strbuf_trim(); + ret = !strcmp("true", out.buf); + strbuf_release(); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* +* NEEDSWORK: Once the builtin rebase has been tested enough +* and git-legacy-rebase.sh is retired to contrib/, this preamble +* can be removed. +*/ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno("could not exec %s", path); + else + die("sane_execvp() returned???"); + } + + if (argc != 2) + die("Usage: %s ", argv[0]); + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-rebase.sh b/git-legacy-rebase.sh similarity index 100% rename from git-rebase.sh rename to git-legacy-rebase.sh diff --git a/git.c b/git.c index 9dbe6ffaa7..0e35632464 100644 --- a/git.c +++ b/git.c @@ -518,6 +518,12 @@ static struct cm