[v5,5/6] rebase -i: support --ignore-date
diff mbox series

Message ID 20191101140003.13960-6-rohit.ashiwal265@gmail.com
State New
Headers show
Series
  • rebase -i: support more options
Related show

Commit Message

Rohit Ashiwal Nov. 1, 2019, 2 p.m. UTC
rebase am already has this flag to "lie" about the author date
by changing it to the committer (current) date. Let's add the same
for interactive machinery.

Signed-off-by: Rohit Ashiwal <rohit.ashiwal265@gmail.com>
---
 Documentation/git-rebase.txt            |  6 +--
 builtin/rebase.c                        | 14 +++---
 sequencer.c                             | 60 +++++++++++++++++++++++--
 sequencer.h                             |  1 +
 t/t3433-rebase-options-compatibility.sh | 29 ++++++++++++
 5 files changed, 99 insertions(+), 11 deletions(-)

Comments

Junio C Hamano Nov. 2, 2019, 7:32 a.m. UTC | #1
Rohit Ashiwal <rohit.ashiwal265@gmail.com> writes:

>  --ignore-date::
> +	Instead of using the given author date, reset it to the
> +	current time. This implies --force-rebase.

The first sentence is unclear and puzzles me with "for what?".  IOW,
you are saying that by default the command uses the given (I presume
that you meant what is recorded in the original commits being
rebased---but we should find a way to explain it more clearly)
author date, but with this option what is used is reset to the
current time.  It is left unspecified what the command is using that
time for.

Perhaps (I am writing this paragraph after writing the rest of the
message, i.e. after reading the patch through):

	By default, the author date recorded in the commit being
	rebased as the author date is used as the author date of the
	rebased commits.  The option tells Git to use the current
	timestamp as the author date of the rebased commits instead.
	This implies `--force-rebase`.

The committer-date-is-author-date is indirectly also affected by
this option, because it (re)uses the "author date", but by making it
clear where the "author date" comes from in the description of this
option, it would make the interaction between these two options
obvious, hopefully ;-)

>  static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
>  static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
> +static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date")

It probably is a good idea to use dash not underscore for
cdate-is-adata and ignore-date, the two files you are adding.

It would be good to eventually fix the existing gpg-sign-opt to
follow suit, but not as a part of this series.

> +static void push_dates(struct child_process *child, int change_committer_date)
> +{
> +	time_t now = time(NULL);
> +	struct strbuf date = STRBUF_INIT;

I am tempted to suggest adding a /* NEEDSWORK */ comment to remind
us that we'd want to eventually use date.c::get_time() here, but not
as part of this series.  But grepping for "\<time(" is easy enough
so I can live without it.

> +	strbuf_addf(&date, "@%"PRIuMAX, (uintmax_t)now);
> +	argv_array_pushf(&child->env_array, "GIT_AUTHOR_DATE=%s", date.buf);

OK, so we get author-date of "now" (which we do not do normally)
when the caller decides to call us (which is, under
"--ignore-date").

> +	if (change_committer_date)
> +		argv_array_pushf(&child->env_array, "GIT_COMMITTER_DATE=%s", date.buf);

And when we are using --committer-date-is-author-date, we muck with
the committer-date as well.

Makes sense.

> @@ -958,7 +989,8 @@ static int run_git_commit(struct repository *r,
>  	                return -1;
>  
>  	        strbuf_addf(&datebuf, "@%s", date);
> -	        res = setenv("GIT_COMMITTER_DATE", datebuf.buf, 1);
> +		res = setenv("GIT_COMMITTER_DATE",
> +			     opts->ignore_date ? "" : datebuf.buf, 1);

This is the codepath that handles committer-date-is-author-date
option.  We read the author date into "date", and used to
unconditionally use it as the committer date.  With --ignore-date,
we behave differently.

It may happen that an empty string in GIT_COMMITTER_DATE is treated
the same way as missing GIT_COMMITTER_DATE, i.e. unspecified.

	if (opts->ignore_date)
		res = unsetenv("GIT_COMMITTER_DATE");
	else
        	res = setenv(...);

would be conceptually clearer.

But then we would notice that there is no reason to even read the
author-date into date if ignore-date is set, no?  So the whole thing
now becomes

	-	if (opts->committer_date_is_author_date) {
	+	if (!opts->ignore_date && opts->committer_date_is_author_date) {

which feels even cleaner.  Am I missing some subtleties (e.g. are we
worried about the case where the user has GIT_COMMITTER_DATE set and
exported to the environment of the shell that starts "git rebase", or
something like that?)???

> @@ -982,6 +1014,8 @@ static int run_git_commit(struct repository *r,
>  		argv_array_push(&cmd.args, "--amend");
>  	if (opts->gpg_sign)
>  		argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
> +	if (opts->ignore_date)
> +		push_dates(&cmd, opts->committer_date_is_author_date);
>  	if (defmsg)
>  		argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
>  	else if (!(flags & EDIT_MSG))
> @@ -1404,7 +1438,8 @@ static int try_to_commit(struct repository *r,
>  		strbuf_addf(&date, "@%.*s %.*s",
>  			    (int)(ident.date_end - ident.date_begin), ident.date_begin,
>  			    (int)(ident.tz_end - ident.tz_begin), ident.tz_begin);
> -		res = setenv("GIT_COMMITTER_DATE", date.buf, 1);
> +		res = setenv("GIT_COMMITTER_DATE",
> +			     opts->ignore_date ? "" : date.buf, 1);

The same comment as the early half of the above, on relying on the
empty date string instead of an explicit unsetenv().

> @@ -1454,6 +1489,15 @@ static int try_to_commit(struct repository *r,
>  
>  	reset_ident_date();
>  
> +	if (opts->ignore_date) {
> +		author = ignore_author_date(author);

The helper function called here begins with this comment:

"Construct a free()able author string with current time as the author date"

but without reading (and memorizing) it, ignore_author_date()
function sounds like a boolean that tells us "are we told to
ignore the author date?"

It sorely wants a better name to tell the reader that it is the
author ident with current timestamp.

> +		if (!author) {
> +			res = -1;
> +			goto out;
> +		}
> +		free(author_to_free);
> +		author_to_free = (char *)author;
> +	}

> @@ -2538,6 +2582,11 @@ static int read_populate_opts(struct replay_opts *opts)
>  			opts->committer_date_is_author_date = 1;
>  		}
>  
> +		if (file_exists(rebase_path_ignore_date())) {
> +			opts->allow_ff = 0;
> +			opts->ignore_date = 1;
> +		}

OK.

> @@ -3557,6 +3608,8 @@ static int do_merge(struct repository *r,
>  		argv_array_push(&cmd.args, git_path_merge_msg(r));
>  		if (opts->gpg_sign)
>  			argv_array_push(&cmd.args, opts->gpg_sign);
> +		if (opts->ignore_date)
> +			push_dates(&cmd, opts->committer_date_is_author_date);

OK, we've seen the callee above already and we know what this does.

> @@ -3830,7 +3883,8 @@ static int pick_commits(struct repository *r,
>  	if (opts->allow_ff)
>  		assert(!(opts->signoff || opts->no_commit ||
>  				opts->record_origin || opts->edit ||
> -				opts->committer_date_is_author_date));
> +				opts->committer_date_is_author_date ||
> +				opts->ignore_date));

We may want to switch this assert(...) and others to "if (!(...))
BUG()" someday, but not as part of this patch.
Junio C Hamano Nov. 2, 2019, 7:48 a.m. UTC | #2
Junio C Hamano <gitster@pobox.com> writes:

> Rohit Ashiwal <rohit.ashiwal265@gmail.com> writes:
>
>>  --ignore-date::
>> +	Instead of using the given author date, reset it to the
>> +	current time. This implies --force-rebase.
>
> The first sentence is unclear and puzzles me with "for what?".  IOW,
> you are saying that by default the command uses the given (I presume
> that you meant what is recorded in the original commits being
> rebased---but we should find a way to explain it more clearly)
> author date, but with this option what is used is reset to the
> current time.  It is left unspecified what the command is using that
> time for.
>
> Perhaps (I am writing this paragraph after writing the rest of the
> message, i.e. after reading the patch through):

Oops, too much re-editing without final proofreading.  Sorry.

> 	By default, the author date recorded in the commit being
> 	rebased as the author date is used as the author date of the
> 	rebased commits.  The option tells Git to use the current
> 	timestamp as the author date of the rebased commits instead.
> 	This implies `--force-rebase`.

	By default, the author date of the original commit is used
	as the author date for the resulting commit.  This option
	tells Git to use the current timestamp instead and implies
	`--force-rebase`.

would be much less redundant without losing clarity.

Patch
diff mbox series

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index e948a24433..3580447f2f 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -413,8 +413,8 @@  See also INCOMPATIBLE OPTIONS below.
 	as the committer date. This implies --force-rebase.
 
 --ignore-date::
-	This flag is passed to 'git am' to change the author date
-	of each rebased commit (see linkgit:git-am[1]).
+	Instead of using the given author date, reset it to the
+	current time. This implies --force-rebase.
 +
 See also INCOMPATIBLE OPTIONS below.
 
@@ -551,7 +551,6 @@  INCOMPATIBLE OPTIONS
 
 The following options:
 
- * --ignore-date
  * --whitespace
  * -C
 
@@ -577,6 +576,7 @@  In addition, the following pairs of options are incompatible:
  * --preserve-merges and --rebase-merges
  * --preserve-merges and --ignore-whitespace
  * --preserve-merges and --committer-date-is-author-date
+ * --preserve-merges and --ignore-date
  * --keep-base and --onto
  * --keep-base and --root
 
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 9227801ef6..7edae668f8 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -83,6 +83,7 @@  struct rebase_options {
 	char *gpg_sign_opt;
 	int autostash;
 	int committer_date_is_author_date;
+	int ignore_date;
 	char *cmd;
 	int allow_empty_message;
 	int rebase_merges, rebase_cousins;
@@ -117,6 +118,7 @@  static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.reschedule_failed_exec = opts->reschedule_failed_exec;
 	replay.committer_date_is_author_date =
 					opts->committer_date_is_author_date;
+	replay.ignore_date = opts->ignore_date;
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
 	replay.strategy = opts->strategy;
 
@@ -982,6 +984,8 @@  static int run_am(struct rebase_options *opts)
 		argv_array_push(&am.args, "--ignore-whitespace");
 	if (opts->committer_date_is_author_date)
 		argv_array_push(&opts->git_am_opts, "--committer-date-is-author-date");
+	if (opts->ignore_date)
+		argv_array_push(&opts->git_am_opts, "--ignore-date");
 	if (opts->action && !strcmp("continue", opts->action)) {
 		argv_array_push(&am.args, "--resolved");
 		argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
@@ -1451,8 +1455,8 @@  int cmd_rebase(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "committer-date-is-author-date",
 			 &options.committer_date_is_author_date,
 			 N_("make committer date match author date")),
-		OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL,
-				  N_("passed to 'git am'"), PARSE_OPT_NOARG),
+		OPT_BOOL(0, "ignore-date", &options.ignore_date,
+			 "ignore author date and use current date"),
 		OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"),
 				  N_("passed to 'git apply'"), 0),
 		OPT_BOOL(0, "ignore-whitespace", &options.ignore_whitespace,
@@ -1728,13 +1732,13 @@  int cmd_rebase(int argc, const char **argv, const char *prefix)
 		    state_dir_base, cmd_live_rebase, buf.buf);
 	}
 
-	if (options.committer_date_is_author_date)
+	if (options.committer_date_is_author_date ||
+	    options.ignore_date)
 		options.flags |= REBASE_FORCE;
 
 	for (i = 0; i < options.git_am_opts.argc; i++) {
 		const char *option = options.git_am_opts.argv[i], *p;
-		if (!strcmp(option, "--ignore-date") ||
-		    !strcmp(option, "--whitespace=fix") ||
+		if (!strcmp(option, "--whitespace=fix") ||
 		    !strcmp(option, "--whitespace=strip"))
 			options.flags |= REBASE_FORCE;
 		else if (skip_prefix(option, "-C", &p)) {
diff --git a/sequencer.c b/sequencer.c
index 34e45246f7..a4c6a47bea 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -148,6 +148,7 @@  static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
  */
 static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
 static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
+static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date")
 static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
 static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
 static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
@@ -890,6 +891,36 @@  static char *read_author_date_or_null(void)
 	return date;
 }
 
+/* Construct a free()able author string with current time as the author date */
+static char *ignore_author_date(const char *author)
+{
+	int len = strlen(author);
+	struct ident_split ident;
+	struct strbuf new_author = STRBUF_INIT;
+
+	if (split_ident_line(&ident, author, len) < 0) {
+		error(_("malformed ident line"));
+		return NULL;
+	}
+	len = ident.mail_end - ident.name_begin + 1;
+
+	strbuf_addf(&new_author, "%.*s ", len, ident.name_begin);
+	datestamp(&new_author);
+	return strbuf_detach(&new_author, NULL);
+}
+
+static void push_dates(struct child_process *child, int change_committer_date)
+{
+	time_t now = time(NULL);
+	struct strbuf date = STRBUF_INIT;
+
+	strbuf_addf(&date, "@%"PRIuMAX, (uintmax_t)now);
+	argv_array_pushf(&child->env_array, "GIT_AUTHOR_DATE=%s", date.buf);
+	if (change_committer_date)
+		argv_array_pushf(&child->env_array, "GIT_COMMITTER_DATE=%s", date.buf);
+	strbuf_release(&date);
+}
+
 static const char staged_changes_advice[] =
 N_("you have staged changes in your working tree\n"
 "If these changes are meant to be squashed into the previous commit, run:\n"
@@ -958,7 +989,8 @@  static int run_git_commit(struct repository *r,
 	                return -1;
 
 	        strbuf_addf(&datebuf, "@%s", date);
-	        res = setenv("GIT_COMMITTER_DATE", datebuf.buf, 1);
+		res = setenv("GIT_COMMITTER_DATE",
+			     opts->ignore_date ? "" : datebuf.buf, 1);
 
 		strbuf_release(&datebuf);
 	        free(date);
@@ -982,6 +1014,8 @@  static int run_git_commit(struct repository *r,
 		argv_array_push(&cmd.args, "--amend");
 	if (opts->gpg_sign)
 		argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+	if (opts->ignore_date)
+		push_dates(&cmd, opts->committer_date_is_author_date);
 	if (defmsg)
 		argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
 	else if (!(flags & EDIT_MSG))
@@ -1404,7 +1438,8 @@  static int try_to_commit(struct repository *r,
 		strbuf_addf(&date, "@%.*s %.*s",
 			    (int)(ident.date_end - ident.date_begin), ident.date_begin,
 			    (int)(ident.tz_end - ident.tz_begin), ident.tz_begin);
-		res = setenv("GIT_COMMITTER_DATE", date.buf, 1);
+		res = setenv("GIT_COMMITTER_DATE",
+			     opts->ignore_date ? "" : date.buf, 1);
 		strbuf_release(&date);
 
 		if (res)
@@ -1454,6 +1489,15 @@  static int try_to_commit(struct repository *r,
 
 	reset_ident_date();
 
+	if (opts->ignore_date) {
+		author = ignore_author_date(author);
+		if (!author) {
+			res = -1;
+			goto out;
+		}
+		free(author_to_free);
+		author_to_free = (char *)author;
+	}
 	if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
 				 oid, author, opts->gpg_sign, extra)) {
 		res = error(_("failed to write commit object"));
@@ -2538,6 +2582,11 @@  static int read_populate_opts(struct replay_opts *opts)
 			opts->committer_date_is_author_date = 1;
 		}
 
+		if (file_exists(rebase_path_ignore_date())) {
+			opts->allow_ff = 0;
+			opts->ignore_date = 1;
+		}
+
 		if (file_exists(rebase_path_reschedule_failed_exec()))
 			opts->reschedule_failed_exec = 1;
 
@@ -2622,6 +2671,8 @@  int write_basic_state(struct replay_opts *opts, const char *head_name,
 		write_file(rebase_path_signoff(), "--signoff\n");
 	if (opts->committer_date_is_author_date)
 		write_file(rebase_path_cdate_is_adate(), "%s", "");
+	if (opts->ignore_date)
+		write_file(rebase_path_ignore_date(), "%s", "");
 	if (opts->reschedule_failed_exec)
 		write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
@@ -3557,6 +3608,8 @@  static int do_merge(struct repository *r,
 		argv_array_push(&cmd.args, git_path_merge_msg(r));
 		if (opts->gpg_sign)
 			argv_array_push(&cmd.args, opts->gpg_sign);
+		if (opts->ignore_date)
+			push_dates(&cmd, opts->committer_date_is_author_date);
 
 		/* Add the tips to be merged */
 		for (j = to_merge; j; j = j->next)
@@ -3830,7 +3883,8 @@  static int pick_commits(struct repository *r,
 	if (opts->allow_ff)
 		assert(!(opts->signoff || opts->no_commit ||
 				opts->record_origin || opts->edit ||
-				opts->committer_date_is_author_date));
+				opts->committer_date_is_author_date ||
+				opts->ignore_date));
 	if (read_and_refresh_cache(r, opts))
 		return -1;
 
diff --git a/sequencer.h b/sequencer.h
index ffddf011cc..3e6275272a 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -44,6 +44,7 @@  struct replay_opts {
 	int quiet;
 	int reschedule_failed_exec;
 	int committer_date_is_author_date;
+	int ignore_date;
 
 	int mainline;
 
diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh
index a98cfe18b7..5166f158dd 100755
--- a/t/t3433-rebase-options-compatibility.sh
+++ b/t/t3433-rebase-options-compatibility.sh
@@ -99,4 +99,33 @@  test_expect_success '--committer-date-is-author-date works with rebase -r' '
 	done <rev_list
 '
 
+# Checking for +0000 in author time is enough since default
+# timezone is UTC, but the timezone used while committing
+# sets to +0530.
+test_expect_success '--ignore-date works with am backend' '
+	git commit --amend --date="$GIT_AUTHOR_DATE" &&
+	git rebase --ignore-date HEAD^ &&
+	git show HEAD --pretty="format:%ai" >authortime &&
+	grep "+0000" authortime
+'
+
+test_expect_success '--ignore-date works with interactive backend' '
+	git commit --amend --date="$GIT_AUTHOR_DATE" &&
+	git rebase --ignore-date -i HEAD^ &&
+	git show HEAD --pretty="format:%ai" >authortime &&
+	grep "+0000" authortime
+'
+
+test_expect_success '--ignore-date works with rebase -r' '
+	git checkout side &&
+	git merge --no-ff commit3 &&
+	git rebase -r --root --ignore-date &&
+	git rev-list HEAD >rev_list &&
+	while read HASH
+	do
+		git show $HASH --pretty="format:%ai" >authortime
+		grep "+0000" authortime
+	done <rev_list
+'
+
 test_done