threads / patch / 49071

patch, 11 partsA minimal builtin rebase

Subject: [GSoC] [PATCH 00/11] A minimal builtin rebase

## tl;dr

42 messages between Aug 8, 2018 and Sep 10, 2018. Diffs are folded; open one to read it.

replies: 41people: 6as markdown or json

Pratik Karki· Aug 8, 2018, 13:48 UTC · lore

This patch series provides the bare minimum to run more than the trivial rebase (i.e. `git rebase <upstream>`).

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 <upstream>", 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 <upstream> <switch-to>`
 builtin/rebase.c | 333 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 320 insertions(+), 13 deletions(-)
-- 
2.18.0
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 35 ++++++++++++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 5 deletions(-)
Show changes to builtin/rebase.c +30 −5
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 <cmd>] [--onto <newbase>] "
+		"[<upstream>] [<branch>]"),
+	N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
+		"--root [<branch>]"),
+	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", &options.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 <base>"), 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/<that-branch> 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
Junio C Hamano· Aug 8, 2018, 19:02 UTC · re: Pratik Karki · lore

Re: [PATCH 01/11] builtin rebase: support --onto

Pratik Karki <predatoramigo@gmail.com> writes:
> 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").
Is there anything unimportant?  A rhetorical question, of course.

The quite casual and natural use of "to rebase" as a verb in the first sentence contradicts with what the parenthetical "its odd name" comment says. Perhaps drop everything after "(which..."?

i.e.
	The `--onto` option allows to rebase a range of commits onto
	a different base commit.  Port the support for the option to
	the C re-implementation.
> This commit introduces options parsing so that different options can
> be added in future commits.

We usually do not say "This commit does X", or (worse) "I do X in this commit". Instead, order the codebase to be like so, e.g. "Support command line options by adding a call to parse_options(); later commits will add more options by building on top." or something like that.

Show 22 quoted lines
> @@ -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 <base>"), 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);
OK.  This correctly calls the parser after repository setup.
Show 24 quoted lines
>  	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/<that-branch> 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. */
Johannes Schindelin· Aug 24, 2018, 16:21 UTC · re: Junio C Hamano · lore

Re: [PATCH 01/11] builtin rebase: support --onto

Hi Junio,
On Wed, 8 Aug 2018, Junio C Hamano wrote:
Show 7 quoted lines
> Pratik Karki <predatoramigo@gmail.com> writes:
> 
> > 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").
> 
> Is there anything unimportant?  A rhetorical question, of course.

You might think it is a rhetorical question, but obviously it is not, as your reaction testifies.

But certainly there are more important options and less important options! The most important options are those that are frequently used.

Show 9 quoted lines
> The quite casual and natural use of "to rebase" as a verb in the
> first sentence contradicts with what the parenthetical "its odd
> name" comment says.  Perhaps drop everything after "(which..."?
> 
> i.e.
> 
> 	The `--onto` option allows to rebase a range of commits onto
> 	a different base commit.  Port the support for the option to
> 	the C re-implementation.
I'd rather keep it.

Remember, a story is easier to read than a dull academic treatise. I want to have a little bit of a personal touch when I stumble over these commit messages again. And I know I will.

Show 5 quoted lines
> > This commit introduces options parsing so that different options can
> > be added in future commits.
> 
> We usually do not say "This commit does X", or (worse) "I do X in
> this commit".
Oh, don't we now? ;-)

(This *was* a rhetorical question, as *I* use this tense all the time, and unless you have quietly rewritten my commit messages without my knowledge nor consent, the Git commit history is full of these instances.)

> Instead, order the codebase to be like so, e.g.  "Support command line
> options by adding a call to parse_options(); later commits will add more
> options by building on top." or something like that.

To be quite frank with you, I hoped for a review that would focus a teeny tiny bit on the correctness of the code.

If you want to continue to nit-pick the commit messages, that's fine, of course, but do understand that I am not really prepared to change a whole lot there, unless you point out outright errors or false statements. Those naturally need fixing.

Also, please note that I will now *definitely* focus on bug fixes, as I am really eager to get those speed improvements into Git for Windows v2.19.0.

And I don't know whether I have said this publicly yet: I will send the next iterations of Pratik's patch series. He is busy with exams (GSoC really caters for US schedules, students who are in countries with very different university schedules are a bit out of luck here), and I really want these patches.

Ciao, Dscho

Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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. `<rev1>...<rev2>`.

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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
Show changes to builtin/rebase.c +7 −1
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 <cmd>] [--onto <newbase>] "
@@ -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", &options.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, &merge_base) < 0)
+			die(_("'%s': need exactly one merge base"),
+			    options.onto_name);
+		options.onto = lookup_commit_or_die(&merge_base,
+						    options.onto_name);
 	} else {
 		options.onto = peel_committish(options.onto_name);
 		if (!options.onto)
-- 
2.18.0
Junio C Hamano· Aug 8, 2018, 19:12 UTC · re: Pratik Karki · lore

Re: [PATCH 02/11] builtin rebase: support `git rebase --onto A...B`

Pratik Karki <predatoramigo@gmail.com> writes:
Show 7 quoted lines
> This commit implements support for an --onto argument that is actually a
> "symmetric range" i.e. `<rev1>...<rev2>`.
>
> 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

Sorry, but it is unclear what you mean by "than it is of current." Do you mean we leave it broken at this step in the series for now for expediency, with the intention to later revisit and fix it, or do you mean something else?

Show 15 quoted lines
> 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 <predatoramigo@gmail.com>
> ...
> @@ -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, &merge_base) < 0)
> +			die(_("'%s': need exactly one merge base"),
> +			    options.onto_name);
> +		options.onto = lookup_commit_or_die(&merge_base,
> +						    options.onto_name);
The original is slightly sloppy in that it will misparse
	rebase --onto 'master^{/log ... message}'

and this shares the same, which I think is probably OK. When this actually becomes problematic, the original can easily be salvaged by making it to fall back to the same peel_committish in its else clause; I am not sure if this C rewrite is as easily be fixed the same way, though.

>  	} else {
>  		options.onto = peel_committish(options.onto_name);
>  		if (!options.onto)
Johannes Schindelin· Aug 26, 2018, 18:36 UTC · re: Junio C Hamano · lore

Re: [PATCH 02/11] builtin rebase: support `git rebase --onto A...B`

Hi Junio,
On Wed, 8 Aug 2018, Junio C Hamano wrote:
Show 14 quoted lines
> Pratik Karki <predatoramigo@gmail.com> writes:
> 
> > This commit implements support for an --onto argument that is actually a
> > "symmetric range" i.e. `<rev1>...<rev2>`.
> >
> > 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
> 
> Sorry, but it is unclear what you mean by "than it is of current."
> Do you mean we leave it broken at this step in the series for now
> for expediency, with the intention to later revisit and fix it, or
> do you mean something else?

I suggested to drop the distinction, in favor of simpler code. Not for the time being, but for good.

I reworded the commit message thusly:
    builtin rebase: support `git rebase --onto A...B`
    This commit implements support for an --onto argument that is actually a
    "symmetric range" i.e. `<rev1>...<rev2>`.
    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.
Show 16 quoted lines
> > @@ -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, &merge_base) < 0)
> > +			die(_("'%s': need exactly one merge base"),
> > +			    options.onto_name);
> > +		options.onto = lookup_commit_or_die(&merge_base,
> > +						    options.onto_name);
> 
> The original is slightly sloppy in that it will misparse
> 
> 	rebase --onto 'master^{/log ... message}'
> 
> and this shares the same, which I think is probably OK.

I did run into this recently, but not with an `--onto` option. I forgot the details (I meant to write it down, and forgot that, too).

Sorry for musing, back on the topic. Yes, it shares the same, and *that* makes it okay. Remember: this patch series is not about improving `git rebase` at all. It is about converting from shell script to builtin.

> When this actually becomes problematic, the original can easily be
> salvaged by making it to fall back to the same peel_committish in its
> else clause; I am not sure if this C rewrite is as easily be fixed the
> same way, though.
I will make a note so that I hopefully won't forget.

Thanks, Dscho

Show 5 quoted lines
> 
> >  	} else {
> >  		options.onto = peel_committish(options.onto_name);
> >  		if (!options.onto)
> 
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/rebase.c +11 −0
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", &options.onto_name,
 			   N_("revision"),
 			   N_("rebase onto given branch instead of upstream")),
+		OPT_BOOL(0, "no-verify", &ok_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(&msg, "rebase: checkout %s", options.onto_name);
 	if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
 		die(_("Could not detach HEAD"));
-- 
2.18.0
Junio C Hamano· Aug 8, 2018, 19:32 UTC · re: Pratik Karki · lore

Re: [PATCH 03/11] builtin rebase: handle the pre-rebase hook (and add --no-verify)

Pratik Karki <predatoramigo@gmail.com> writes:
Show 6 quoted lines
> 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 <predatoramigo@gmail.com>
> ---

Introduction of upstream_arg in this step looked a bit surprising, but the hook invocation is the only thing that uses it, so it is understandable.

"rebase: handle the re-rebase hook and --no-verify" would have been sufficient, without "add" or parentheses.

Show 29 quoted lines
>  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", &options.onto_name,
>  			   N_("revision"),
>  			   N_("rebase onto given branch instead of upstream")),
> +		OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
> +			 N_("allow pre-rebase hook to run")),

Do we need to catch "--no-no-verify" ourselves with NONEG bit, or is this sufficient to tell parse_options() machinery to take care of it?

Show 24 quoted lines
>  		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(&msg, "rebase: checkout %s", options.onto_name);
>  	if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
>  		die(_("Could not detach HEAD"));
Johannes Schindelin· Aug 27, 2018, 12:15 UTC · re: Junio C Hamano · lore

Re: [PATCH 03/11] builtin rebase: handle the pre-rebase hook (and add --no-verify)

Hi Junio,
On Wed, 8 Aug 2018, Junio C Hamano wrote:
Show 12 quoted lines
> Pratik Karki <predatoramigo@gmail.com> writes:
> 
> > 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 <predatoramigo@gmail.com>
> > ---
> 
> Introduction of upstream_arg in this step looked a bit
> surprising, but the hook invocation is the only thing that uses it,
> so it is understandable.
Yep, that's literally all that `upstream_arg` is used for:

$ git grep upstream_arg v2.19.0-rc0 v2.19.0-rc0:git-rebase.sh: upstream_arg="$upstream_name" v2.19.0-rc0:git-rebase.sh: upstream_arg=--root v2.19.0-rc0:git-rebase.sh:run_pre_rebase_hook "$upstream_arg" "$@"

> "rebase: handle the re-rebase hook and --no-verify" would have been
> sufficient, without "add" or parentheses.
Fixed.
Show 14 quoted lines
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index 38c496dd10..b79f9b0a9f 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -317,6 +319,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >  		OPT_STRING(0, "onto", &options.onto_name,
> >  			   N_("revision"),
> >  			   N_("rebase onto given branch instead of upstream")),
> > +		OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
> > +			 N_("allow pre-rebase hook to run")),
> 
> Do we need to catch "--no-no-verify" ourselves with NONEG bit, or is
> this sufficient to tell parse_options() machinery to take care of
> it?
I just issued
	$ ./git rebase --verify --no-no-verify --xyz
and it showed
	error: unknown option `xyz'
	[... usage ...]

I vaguely remembered that the parse_options() machinery special-cases "no-" prefixes, and my test seems to confirm it.

Holler if you want a more in-depth analysis.

Ciao, Dscho

Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
Show changes to builtin/rebase.c +15 −0
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(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
+	add_var(&script_snippet, "GIT_QUIET",
+		opts->flags & REBASE_NO_QUIET ? "" : "t");
+	add_var(&script_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", &ok_to_skip_pre_rebase,
 			 N_("allow pre-rebase hook to run")),
+		OPT_NEGBIT('q', "quiet", &options.flags,
+			   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(&options.git_am_opt, " -q");
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
-- 
2.18.0
Stefan Beller· Aug 8, 2018, 18:31 UTC · re: Pratik Karki · lore

Re: [PATCH 04/11] builtin rebase: support --quiet

On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
Show 13 quoted lines
>
> 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
  --quote in the
(in case a resend is needed)
Junio C Hamano· Aug 8, 2018, 19:37 UTC · re: Stefan Beller · lore

Re: [PATCH 04/11] builtin rebase: support --quiet

Stefan Beller <sbeller@google.com> writes:
Show 10 quoted lines
> On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
>>
>> 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.

Why? Is it for completeness (i.e. does the scripted version take such an option and addition of --no-quiet makes the C rewrite behave the same)?

Show 7 quoted lines
>> 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
>
>   --quote in the
>
> (in case a resend is needed)
Meaning --quiet?
Johannes Schindelin· Aug 27, 2018, 12:31 UTC · re: Junio C Hamano · lore

Re: [PATCH 04/11] builtin rebase: support --quiet

Hi Junio,
On Wed, 8 Aug 2018, Junio C Hamano wrote:
Show 16 quoted lines
> Stefan Beller <sbeller@google.com> writes:
> 
> > On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
> >>
> >> 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.
> 
> Why?  Is it for completeness (i.e. does the scripted version take
> such an option and addition of --no-quiet makes the C rewrite behave
> the same)?

Ah. I mentioned that an explanation for this is needed in the commit message, and I guess that it is a bit too subtle. The part you clipped from your quoted text says:

	[... `--quiet`] 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().

I agree that this is a pretty convoluted way to express the issue. See below for an attempt at a clearer commit message.

Show 9 quoted lines
> >> 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
> >
> >   --quote in the
> >
> > (in case a resend is needed)
> 
> Meaning --quiet?
Yep. I should have paid more attention in my pre-submission review, sorry.
I changed the commit message to read like this:
	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.
	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.

Ciao, Dscho

Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +58 −2
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 <cmd>] [--onto <newbase>] "
@@ -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(&script_snippet, "GIT_QUIET",
 		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf);
+	add_var(&script_snippet, "verbose",
+		opts->flags & REBASE_VERBOSE ? "t" : "");
+	add_var(&script_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", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET),
+			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+		OPT_BIT('v', "verbose", &options.flags,
+			N_("display a diffstat of what changed upstream"),
+			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
+		{OPTION_NEGBIT, 'n', "no-stat", &options.flags, 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, &options);
+
 	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(&merge_base),
+				oid_to_hex(&options.onto->object.oid));
+
+		/* We want color (if set), but no pager */
+		diff_setup(&opts);
+		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(&opts);
+		diff_tree_oid(&merge_base, &options.onto->object.oid,
+			      "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* 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(&msg, "rebase: checkout %s", options.onto_name);
 	if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
 		die(_("Could not detach HEAD"));
-- 
2.18.0
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/rebase.c +11 −0
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 <cmd>] [--onto <newbase>] "
@@ -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(&options);
 
+cleanup:
 	strbuf_release(&revisions);
 	free(options.head_name);
 	return ret;
-- 
2.18.0
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
Show changes to builtin/rebase.c +72 −0
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 <cmd>] [--onto <newbase>] "
@@ -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, &merge_bases->item->object.oid);
+		res = !oidcmp(merge_base, &onto->object.oid);
+	} else {
+		oidcpy(merge_base, &null_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, &options.orig_head, &merge_base) &&
+	    !is_interactive(&options) && !options.restrict_revision &&
+	    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
+		int flag;
+
+		if (!(options.flags & REBASE_NO_QUIET))
+			; /* be quiet */
+		else if (!strcmp(branch_name, "HEAD") &&
+			resolve_ref_unsafe("HEAD", 0, NULL, &flag))
+			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
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +22 −2
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(&script_snippet, "diffstat",
 		opts->flags & REBASE_DIFFSTAT ? "t" : "");
+	add_var(&script_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", &options.flags, NULL,
 			N_("do not show diffstat of what changed upstream"),
 			PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
+		OPT_BIT('f', "force-rebase", &options.flags,
+			N_("cherry-pick all commits, even if unchanged"),
+			REBASE_FORCE),
+		OPT_BIT(0, "no-ff", &options.flags,
+			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(&options.upstream->object.oid, &options.onto->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, &flag))
+				puts(_("HEAD is up to date."));
+			else
+				printf(_("Current branch %s is up to date.\n"),
+				       branch_name);
+			ret = !!finish_rebase(&options);
+			goto cleanup;
+		} else if (!(options.flags & REBASE_NO_QUIET))
 			; /* be quiet */
 		else if (!strcmp(branch_name, "HEAD") &&
-			resolve_ref_unsafe("HEAD", 0, NULL, &flag))
+			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
 			puts(_("HEAD is up to date, rebase forced."));
 		else
 			printf(_("Current branch %s is up to date, rebase "
-- 
2.18.0
Stefan Beller· Aug 8, 2018, 18:51 UTC · re: Pratik Karki · lore

Re: [PATCH 08/11] builtin rebase: support --force-rebase

On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
> @@ -551,10 +560,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
[...]
>                         ; /* be quiet */
>                 else if (!strcmp(branch_name, "HEAD") &&
> -                       resolve_ref_unsafe("HEAD", 0, NULL, &flag))
> +                        resolve_ref_unsafe("HEAD", 0, NULL, &flag))

This line is changing only the indentation whitespace? Would it make sense to have it in the previous patch?

Johannes Schindelin· Aug 24, 2018, 16:10 UTC · re: Stefan Beller · lore

Re: [PATCH 08/11] builtin rebase: support --force-rebase

Hi Stefan,
On Wed, 8 Aug 2018, Stefan Beller wrote:
Show 11 quoted lines
> On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
> 
> > @@ -551,10 +560,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> [...]
> >                         ; /* be quiet */
> >                 else if (!strcmp(branch_name, "HEAD") &&
> > -                       resolve_ref_unsafe("HEAD", 0, NULL, &flag))
> > +                        resolve_ref_unsafe("HEAD", 0, NULL, &flag))
> 
> This line is changing only the indentation whitespace?
> Would it make sense to have it in the previous patch?

Correct. I will fix this before sending the next iteration, Dscho

Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)
Show changes to builtin/rebase.c +47 −1
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", &options.onto_name,
@@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	git_config(rebase_config, &options);
 
+	if (is_directory(apply_dir())) {
+		options.type = REBASE_AM;
+		options.state_dir = apply_dir();
+	} else if (is_directory(merge_dir())) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%s/rewritten", merge_dir());
+		if (is_directory(buf.buf)) {
+			options.type = REBASE_PRESERVE_MERGES;
+			options.flags |= REBASE_INTERACTIVE_EXPLICIT;
+		} else {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%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(&buf);
+		strbuf_addf(&buf, "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(&options.git_am_opt, " -q");
 
-- 
2.18.0
Stefan Beller· Aug 8, 2018, 18:59 UTC · re: Pratik Karki · lore

Re: [PATCH 09/11] builtin rebase: start a new rebase only if none is in progress

On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
Show 71 quoted lines
>
> 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 <predatoramigo@gmail.com>
> ---
>  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", &options.onto_name,
> @@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>
>         git_config(rebase_config, &options);
>
> +       if (is_directory(apply_dir())) {
> +               options.type = REBASE_AM;
> +               options.state_dir = apply_dir();
> +       } else if (is_directory(merge_dir())) {
> +               strbuf_reset(&buf);
> +               strbuf_addf(&buf, "%s/rewritten", merge_dir());
> +               if (is_directory(buf.buf)) {
> +                       options.type = REBASE_PRESERVE_MERGES;
> +                       options.flags |= REBASE_INTERACTIVE_EXPLICIT;
> +               } else {
> +                       strbuf_reset(&buf);
> +                       strbuf_addf(&buf, "%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 */

The faithful conversion doesn't even stop at the comments. ;-) I shortly wondered if this is the best place for this comment, but let's just keep it here to have the 1:1 rewrite.

> +       if (in_progress) {
[...]
> +                   state_dir_base, cmd_live_rebase,buf.buf);

In case a resend is needed, add a whitespace after the comma and buf.buf, please.

So far I have not seen anything major that would warrant a resend.

Thanks, Stefan

Johannes Schindelin· Aug 24, 2018, 16:13 UTC · re: Stefan Beller · lore

Re: [PATCH 09/11] builtin rebase: start a new rebase only if none is in progress

Hi Stefan,
On Wed, 8 Aug 2018, Stefan Beller wrote:
Show 13 quoted lines
> On Wed, Aug 8, 2018 at 6:51 AM Pratik Karki <predatoramigo@gmail.com> wrote:
> >
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index 8a7bf3d468..a261f552f1 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -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 */
> 
> The faithful conversion doesn't even stop at the comments. ;-)
Yes, I insisted on it.

TBH it is a bit of a shame that we cannot fix all those error messages going to stdout, but... you know... One step after the other.

> I shortly wondered if this is the best place for this comment,
> but let's just keep it here to have the 1:1 rewrite.

It should probably be inside the conditional block, but as you say: the original had it in a funny spot, and so does the converted code.

Show 6 quoted lines
> > +       if (in_progress) {
> [...]
> > +                   state_dir_base, cmd_live_rebase,buf.buf);
> 
> In case a resend is needed, add a whitespace after the
> comma and buf.buf, please.
I will fix this before sending the next iteration.

Thanks for the review! Dscho

Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
Show changes to builtin/rebase.c +8 −3
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(&script_snippet, "upstream_name", opts->upstream_name);
 	add_var(&script_snippet, "upstream",
 				 oid_to_hex(&opts->upstream->object.oid));
-	add_var(&script_snippet, "head_name", opts->head_name);
+	add_var(&script_snippet, "head_name",
+		opts->head_name ? opts->head_name : "detached HEAD");
 	add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
 	add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid));
 	add_var(&script_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, 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/<that-branch> or "detached HEAD"
+	 * head_name -- refs/heads/<that-branch> 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", &options.orig_head))
-- 
2.18.0
Pratik Karki· Aug 8, 2018, 13:48 UTC · re: Pratik Karki · lore

[PATCH 11/11] builtin rebase: support `git rebase <upstream> <switch-to>`

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 <predatoramigo@gmail.com>
---
 builtin/rebase.c | 48 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 44 insertions(+), 4 deletions(-)
Show changes to builtin/rebase.c +44 −4
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(&script_snippet, "force_rebase",
 		opts->flags & REBASE_FORCE ? "t" : "");
+	if (opts->switch_to)
+		add_var(&script_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/<that-branch> 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(&buf);
+		strbuf_addf(&buf, "refs/heads/%s", branch_name);
+		if (!read_ref(buf.buf, &options.orig_head))
+			options.head_name = xstrdup(buf.buf);
+		/* If not is it a valid ref (branch or commit)? */
+		else if (!get_oid(branch_name, &options.orig_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", &options.orig_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, &oid) < 0) {
+					ret = !!error(_("could not parse '%s'"),
+						      options.switch_to);
+					goto cleanup;
+				}
+
+				strbuf_reset(&buf);
+				strbuf_addf(&buf, "rebase: checkout %s",
+					    options.switch_to);
+				if (reset_head(&oid, "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
Duy Nguyen· Aug 8, 2018, 16:03 UTC · re: Pratik Karki · lore

Re: [PATCH 11/11] builtin rebase: support `git rebase <upstream> <switch-to>`

(not really a review, this patch just happens to catch my eyes)
On Wed, Aug 8, 2018 at 3:55 PM Pratik Karki <predatoramigo@gmail.com> wrote:
Show 53 quoted lines
>
> 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 <predatoramigo@gmail.com>
> ---
>  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(&script_snippet, "force_rebase",
>                 opts->flags & REBASE_FORCE ? "t" : "");
> +       if (opts->switch_to)
> +               add_var(&script_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/<that-branch> 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(&buf);
> +               strbuf_addf(&buf, "refs/heads/%s", branch_name);
> +               if (!read_ref(buf.buf, &options.orig_head))
> +                       options.head_name = xstrdup(buf.buf);
> +               /* If not is it a valid ref (branch or commit)? */
> +               else if (!get_oid(branch_name, &options.orig_head))
> +                       options.head_name = NULL;
> +               else
> +                       die(_("fatal: no such branch/commit '%s'"),
die() automatically adds "fatal:" so you should not add it yourself here
Show 12 quoted lines
> +                           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", &options.orig_head))
>                         die(_("Could not resolve HEAD to a revision"));
> -       }
> +       } else
> +               BUG("unexpected number of arguments left to parse");

Does this mean "git base one two three" triggers this BUG? If so, this should be a die() instead. I did not real the full source code, so maybe this case is already caught higher up.

-- 
Duy
Johannes Schindelin· Aug 8, 2018, 18:52 UTC · re: Duy Nguyen · lore

Re: [PATCH 11/11] builtin rebase: support `git rebase <upstream> <switch-to>`

Hi Duy,
On Wed, 8 Aug 2018, Duy Nguyen wrote:
Show 17 quoted lines
> On Wed, Aug 8, 2018 at 3:55 PM Pratik Karki <predatoramigo@gmail.com> wrote:
> >
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index 63634210c0..b2ddfa8dbf 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -585,7 +602,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
> >                 }
> >                 if (get_oid("HEAD", &options.orig_head))
> >                         die(_("Could not resolve HEAD to a revision"));
> > -       }
> > +       } else
> > +               BUG("unexpected number of arguments left to parse");
> 
> Does this mean "git base one two three" triggers this BUG? If so, this
> should be a die() instead. I did not real the full source code, so
> maybe this case is already caught higher up.
As you can see from
	https://github.com/git/git/blob/v2.18.0/git-rebase.sh#L615

the original, Unix shell script version of `git rebase` also says "BUG" here. And if you care to look at

	https://github.com/git/git/blob/3358abdcb/builtin/rebase.c#L870-L872

you will see that there is a proper check for the correct amount of command-line parameters.

So at this point, it would indeed indicate a bug if the `argc` had an unexpected value.

Ciao, Dscho

Johannes Schindelin via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Pratik Karki · lore

[PATCH v2 00/11] A minimal builtin rebase

This patch series provides the bare minimum to run more than just the trivial rebase (i.e. git rebase <upstream>): it implements the most common options such as --onto.

It is based the latest iteration of pk/rebase-in-c.

This is the second patch series that brings us more closer to a builtin "git rebase".

Changes since v1:
 * Many commit messages were reworded.
 * An indentation fix was folded into the commit that introduces the
   incorrect indentation.
 * A missing space after a comma was inserted.
Pratik Karki (11):
  builtin rebase: support --onto
  builtin rebase: support `git rebase --onto A...B`
  builtin rebase: handle the pre-rebase hook and --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 <upstream> <switch-to>`
 builtin/rebase.c | 333 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 320 insertions(+), 13 deletions(-)
base-commit: ac7f467fef8b836084afdce5eded047c79a6858d
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-32%2Fdscho%2Frebase-in-c-2-basic-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-32/dscho/rebase-in-c-2-basic-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/32
Range-diff vs v1:
  1:  c5f67c35ea !  1:  fba1b3e2a9 builtin rebase: support --onto
     @@ -15,6 +15,7 @@
          command name, but to the first (non-option) command-line parameter.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  2:  35d141c32a !  2:  f9826ab58f builtin rebase: support `git rebase --onto A...B`
     @@ -7,12 +7,18 @@
      
          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.
     +    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 <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  3:  e223f2209d !  3:  7100820def builtin rebase: handle the pre-rebase hook (and add --no-verify)
     @@ -1,12 +1,13 @@
      Author: Pratik Karki <predatoramigo@gmail.com>
      
     -    builtin rebase: handle the pre-rebase hook (and add --no-verify)
     +    builtin rebase: handle the pre-rebase hook and --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 <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  4:  19919e7e24 !  4:  5034f53024 builtin rebase: support --quiet
     @@ -7,19 +7,23 @@
          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.
     +    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.
      
     -    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().
     +    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 <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  5:  cbf318d0de !  5:  ce1e1f266a builtin rebase: support the `verbose` and `diffstat` options
     @@ -11,6 +11,7 @@
          calling) git_default_config().
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  6:  b440bf9884 !  6:  f11f21d5c6 builtin rebase: require a clean worktree
     @@ -6,6 +6,7 @@
          whether the repository is ready for rebase.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
  7:  0efe9b41f0 !  7:  2ec0b744bf builtin rebase: try to fast forward when possible
     @@ -11,6 +11,7 @@
          rebase).
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
     @@ -105,7 +106,7 @@
      +		if (!(options.flags & REBASE_NO_QUIET))
      +			; /* be quiet */
      +		else if (!strcmp(branch_name, "HEAD") &&
     -+			resolve_ref_unsafe("HEAD", 0, NULL, &flag))
     ++			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
      +			puts(_("HEAD is up to date, rebase forced."));
      +		else
      +			printf(_("Current branch %s is up to date, rebase "
  8:  ae019dec3f !  8:  78d90e67de builtin rebase: support --force-rebase
     @@ -10,6 +10,7 @@
          fast-forward even if it could.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
     @@ -63,8 +64,4 @@
      +		} else if (!(options.flags & REBASE_NO_QUIET))
       			; /* be quiet */
       		else if (!strcmp(branch_name, "HEAD") &&
     --			resolve_ref_unsafe("HEAD", 0, NULL, &flag))
     -+			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
     - 			puts(_("HEAD is up to date, rebase forced."));
     - 		else
     - 			printf(_("Current branch %s is up to date, rebase "
     + 			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
  9:  d58d504c03 !  9:  b639bfa5a8 builtin rebase: start a new rebase only if none is in progress
     @@ -7,6 +7,7 @@
          ongoing rebase operation completes or is terminated.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
     @@ -84,7 +85,7 @@
      +		      "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);
     ++		    state_dir_base, cmd_live_rebase, buf.buf);
      +	}
      +
       	if (!(options.flags & REBASE_NO_QUIET))
 10:  ef468bf3d7 ! 10:  aab01f0b8e builtin rebase: only store fully-qualified refs in `options.head_name`
     @@ -12,6 +12,7 @@
          "detached HEAD" for display only. Make it so.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
 11:  9a26fc3fac ! 11:  e64190d8ed builtin rebase: support `git rebase <upstream> <switch-to>`
     @@ -7,6 +7,7 @@
          `git-legacy-rebase.sh` is converted to builtin `rebase.c`.
      
          Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
     +    Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      
      diff --git a/builtin/rebase.c b/builtin/rebase.c
      --- a/builtin/rebase.c
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 01/11] builtin rebase: support --onto

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 35 ++++++++++++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 5 deletions(-)
Show changes to builtin/rebase.c +30 −5
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 <cmd>] [--onto <newbase>] "
+		"[<upstream>] [<branch>]"),
+	N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
+		"--root [<branch>]"),
+	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", &options.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 <base>"), 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/<that-branch> 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
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 02/11] builtin rebase: support `git rebase --onto A...B`

From: Pratik Karki <predatoramigo@gmail.com>

This commit implements support for an --onto argument that is actually a "symmetric range" i.e. `<rev1>...<rev2>`.

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
Show changes to builtin/rebase.c +7 −1
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 <cmd>] [--onto <newbase>] "
@@ -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", &options.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, &merge_base) < 0)
+			die(_("'%s': need exactly one merge base"),
+			    options.onto_name);
+		options.onto = lookup_commit_or_die(&merge_base,
+						    options.onto_name);
 	} else {
 		options.onto = peel_committish(options.onto_name);
 		if (!options.onto)
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 03/11] builtin rebase: handle the pre-rebase hook and --no-verify

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/rebase.c +11 −0
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", &options.onto_name,
 			   N_("revision"),
 			   N_("rebase onto given branch instead of upstream")),
+		OPT_BOOL(0, "no-verify", &ok_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(&msg, "rebase: checkout %s", options.onto_name);
 	if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
 		die(_("Could not detach HEAD"));
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 04/11] builtin rebase: support --quiet

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
Show changes to builtin/rebase.c +15 −0
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(&script_snippet, "revisions", opts->revisions);
 	add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
 		oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
+	add_var(&script_snippet, "GIT_QUIET",
+		opts->flags & REBASE_NO_QUIET ? "" : "t");
+	add_var(&script_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", &ok_to_skip_pre_rebase,
 			 N_("allow pre-rebase hook to run")),
+		OPT_NEGBIT('q', "quiet", &options.flags,
+			   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(&options.git_am_opt, " -q");
+
 	switch (options.type) {
 	case REBASE_MERGE:
 	case REBASE_INTERACTIVE:
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 05/11] builtin rebase: support the `verbose` and `diffstat` options

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)
Show changes to builtin/rebase.c +58 −2
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 <cmd>] [--onto <newbase>] "
@@ -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(&script_snippet, "GIT_QUIET",
 		opts->flags & REBASE_NO_QUIET ? "" : "t");
 	add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf);
+	add_var(&script_snippet, "verbose",
+		opts->flags & REBASE_VERBOSE ? "t" : "");
+	add_var(&script_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", &options.flags,
 			   N_("be quiet. implies --no-stat"),
-			   REBASE_NO_QUIET),
+			   REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+		OPT_BIT('v', "verbose", &options.flags,
+			N_("display a diffstat of what changed upstream"),
+			REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
+		{OPTION_NEGBIT, 'n', "no-stat", &options.flags, 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, &options);
+
 	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(&merge_base),
+				oid_to_hex(&options.onto->object.oid));
+
+		/* We want color (if set), but no pager */
+		diff_setup(&opts);
+		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(&opts);
+		diff_tree_oid(&merge_base, &options.onto->object.oid,
+			      "", &opts);
+		diffcore_std(&opts);
+		diff_flush(&opts);
+	}
+
+	/* 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(&msg, "rebase: checkout %s", options.onto_name);
 	if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
 		die(_("Could not detach HEAD"));
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 06/11] builtin rebase: require a clean worktree

From: Pratik Karki <predatoramigo@gmail.com>

This commit reads the index of the repository for rebase and checks whether the repository is ready for rebase.

Signed-off-by: Pratik Karki <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
Show changes to builtin/rebase.c +11 −0
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 <cmd>] [--onto <newbase>] "
@@ -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(&options);
 
+cleanup:
 	strbuf_release(&revisions);
 	free(options.head_name);
 	return ret;
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 07/11] builtin rebase: try to fast forward when possible

From: Pratik Karki <predatoramigo@gmail.com>
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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
Show changes to builtin/rebase.c +72 −0
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 <cmd>] [--onto <newbase>] "
@@ -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, &merge_bases->item->object.oid);
+		res = !oidcmp(merge_base, &onto->object.oid);
+	} else {
+		oidcpy(merge_base, &null_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, &options.orig_head, &merge_base) &&
+	    !is_interactive(&options) && !options.restrict_revision &&
+	    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
+		int flag;
+
+		if (!(options.flags & REBASE_NO_QUIET))
+			; /* be quiet */
+		else if (!strcmp(branch_name, "HEAD") &&
+			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
+			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
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 08/11] builtin rebase: support --force-rebase

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)
Show changes to builtin/rebase.c +21 −1
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(&script_snippet, "diffstat",
 		opts->flags & REBASE_DIFFSTAT ? "t" : "");
+	add_var(&script_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", &options.flags, NULL,
 			N_("do not show diffstat of what changed upstream"),
 			PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
+		OPT_BIT('f', "force-rebase", &options.flags,
+			N_("cherry-pick all commits, even if unchanged"),
+			REBASE_FORCE),
+		OPT_BIT(0, "no-ff", &options.flags,
+			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(&options.upstream->object.oid, &options.onto->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, &flag))
+				puts(_("HEAD is up to date."));
+			else
+				printf(_("Current branch %s is up to date.\n"),
+				       branch_name);
+			ret = !!finish_rebase(&options);
+			goto cleanup;
+		} else if (!(options.flags & REBASE_NO_QUIET))
 			; /* be quiet */
 		else if (!strcmp(branch_name, "HEAD") &&
 			 resolve_ref_unsafe("HEAD", 0, NULL, &flag))
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 09/11] builtin rebase: start a new rebase only if none is in progress

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)
Show changes to builtin/rebase.c +47 −1
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", &options.onto_name,
@@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	git_config(rebase_config, &options);
 
+	if (is_directory(apply_dir())) {
+		options.type = REBASE_AM;
+		options.state_dir = apply_dir();
+	} else if (is_directory(merge_dir())) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%s/rewritten", merge_dir());
+		if (is_directory(buf.buf)) {
+			options.type = REBASE_PRESERVE_MERGES;
+			options.flags |= REBASE_INTERACTIVE_EXPLICIT;
+		} else {
+			strbuf_reset(&buf);
+			strbuf_addf(&buf, "%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(&buf);
+		strbuf_addf(&buf, "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(&options.git_am_opt, " -q");
 
-- 
gitgitgadget
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
Show changes to builtin/rebase.c +8 −3
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(&script_snippet, "upstream_name", opts->upstream_name);
 	add_var(&script_snippet, "upstream",
 				 oid_to_hex(&opts->upstream->object.oid));
-	add_var(&script_snippet, "head_name", opts->head_name);
+	add_var(&script_snippet, "head_name",
+		opts->head_name ? opts->head_name : "detached HEAD");
 	add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
 	add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid));
 	add_var(&script_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, 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/<that-branch> or "detached HEAD"
+	 * head_name -- refs/heads/<that-branch> 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", &options.orig_head))
-- 
gitgitgadget
SZEDER Gábor· Sep 8, 2018, 08:52 UTC · re: Pratik Karki via GitGitGadget · lore

Re: [PATCH v2 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`

On Tue, Sep 04, 2018 at 02:27:20PM -0700, Pratik Karki via GitGitGadget wrote:
Show 21 quoted lines
> From: Pratik Karki <predatoramigo@gmail.com>
> 
> 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 <predatoramigo@gmail.com>
> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>  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
Show 7 quoted lines
> @@ -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;
Please use FREE_AND_NULL(options.head_name) here.
Show 6 quoted lines
>  			branch_name = "HEAD";
>  		}
>  		if (get_oid("HEAD", &options.orig_head))
> -- 
> gitgitgadget
> 
Junio C Hamano· Sep 10, 2018, 16:55 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`

SZEDER Gábor <szeder.dev@gmail.com> writes:
Show 6 quoted lines
>>  		} else {
>> -			options.head_name = xstrdup("detached HEAD");
>> +			free(options.head_name);
>> +			options.head_name = NULL;
>
> Please use FREE_AND_NULL(options.head_name) here.
Good; did contrib/coccinelle/free.cocci catch this?
Show 7 quoted lines
>
>>  			branch_name = "HEAD";
>>  		}
>>  		if (get_oid("HEAD", &options.orig_head))
>> -- 
>> gitgitgadget
>> 
SZEDER Gábor· Sep 10, 2018, 20:25 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 10/11] builtin rebase: only store fully-qualified refs in `options.head_name`

On Mon, Sep 10, 2018 at 09:55:30AM -0700, Junio C Hamano wrote:
Show 10 quoted lines
> SZEDER Gábor <szeder.dev@gmail.com> writes:
> 
> >>  		} else {
> >> -			options.head_name = xstrdup("detached HEAD");
> >> +			free(options.head_name);
> >> +			options.head_name = NULL;
> >
> > Please use FREE_AND_NULL(options.head_name) here.
> 
> Good; did contrib/coccinelle/free.cocci catch this?
Yes.

But now that you mention it, I see that it didn't catch it in 'pu' or in most of the later 'pk/rebase-in-c-X-...' branches, even though they all have these lines. It bisects to 0073df2bd3 (builtin rebase: support --rebase-merges[=[no-]rebase-cousins], 2018-09-04), which doesn't touch these lines at all.

Strange; I have no idea what's going on.
Pratik Karki via GitGitGadget· Sep 4, 2018, 21:27 UTC · re: Johannes Schindelin via GitGitGadget · lore

[PATCH v2 11/11] builtin rebase: support `git rebase <upstream> <switch-to>`

From: Pratik Karki <predatoramigo@gmail.com>

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 <predatoramigo@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin/rebase.c | 48 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 44 insertions(+), 4 deletions(-)
Show changes to builtin/rebase.c +44 −4
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(&script_snippet, "force_rebase",
 		opts->flags & REBASE_FORCE ? "t" : "");
+	if (opts->switch_to)
+		add_var(&script_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/<that-branch> 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(&buf);
+		strbuf_addf(&buf, "refs/heads/%s", branch_name);
+		if (!read_ref(buf.buf, &options.orig_head))
+			options.head_name = xstrdup(buf.buf);
+		/* If not is it a valid ref (branch or commit)? */
+		else if (!get_oid(branch_name, &options.orig_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", &options.orig_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, &oid) < 0) {
+					ret = !!error(_("could not parse '%s'"),
+						      options.switch_to);
+					goto cleanup;
+				}
+
+				strbuf_reset(&buf);
+				strbuf_addf(&buf, "rebase: checkout %s",
+					    options.switch_to);
+				if (reset_head(&oid, "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

← back to recent threads