Skip to content

Commit 7573cec

Browse files
phillipwoodgitster
authored andcommitted
rebase -i: support --committer-date-is-author-date
Rebase is implemented with two different backends - 'apply' and 'merge' each of which support a different set of options. In particular the apply backend supports a number of options implemented by 'git am' that are not implemented in the merge backend. This means that the available options are different depending on which backend is used which is confusing. This patch adds support for the --committer-date-is-author-date option to the merge backend. This option uses the author date of the commit that is being rewritten as the committer date when the new commit is created. Original-patch-by: Rohit Ashiwal <[email protected]> Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e8cbe21 commit 7573cec

File tree

6 files changed

+162
-13
lines changed

6 files changed

+162
-13
lines changed

Documentation/git-rebase.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,9 +445,13 @@ if the other side had no changes that conflicted.
445445
See also INCOMPATIBLE OPTIONS below.
446446

447447
--committer-date-is-author-date::
448+
Instead of using the current time as the committer date, use
449+
the author date of the commit being rebased as the committer
450+
date. This option implies `--force-rebase`.
451+
448452
--ignore-date::
449-
These flags are passed to 'git am' to easily change the dates
450-
of the rebased commits (see linkgit:git-am[1]).
453+
This flag is passed to 'git am' to change the author date
454+
of each rebased commit (see linkgit:git-am[1]).
451455
+
452456
See also INCOMPATIBLE OPTIONS below.
453457

@@ -585,7 +589,6 @@ INCOMPATIBLE OPTIONS
585589
The following options:
586590

587591
* --apply
588-
* --committer-date-is-author-date
589592
* --ignore-date
590593
* --whitespace
591594
* -C
@@ -613,6 +616,7 @@ In addition, the following pairs of options are incompatible:
613616
* --preserve-merges and --rebase-merges
614617
* --preserve-merges and --empty=
615618
* --preserve-merges and --ignore-whitespace
619+
* --preserve-merges and --committer-date-is-author-date
616620
* --keep-base and --onto
617621
* --keep-base and --root
618622

builtin/rebase.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct rebase_options {
8888
int autosquash;
8989
char *gpg_sign_opt;
9090
int autostash;
91+
int committer_date_is_author_date;
9192
char *cmd;
9293
int allow_empty_message;
9394
int rebase_merges, rebase_cousins;
@@ -124,6 +125,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
124125
replay.quiet = !(opts->flags & REBASE_NO_QUIET);
125126
replay.verbose = opts->flags & REBASE_VERBOSE;
126127
replay.reschedule_failed_exec = opts->reschedule_failed_exec;
128+
replay.committer_date_is_author_date =
129+
opts->committer_date_is_author_date;
127130
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
128131
replay.strategy = opts->strategy;
129132

@@ -1497,9 +1500,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
14971500
PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
14981501
OPT_BOOL(0, "signoff", &options.signoff,
14991502
N_("add a Signed-off-by: line to each commit")),
1500-
OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date",
1501-
&options.git_am_opts, NULL,
1502-
N_("passed to 'git am'"), PARSE_OPT_NOARG),
1503+
OPT_BOOL(0, "committer-date-is-author-date",
1504+
&options.committer_date_is_author_date,
1505+
N_("make committer date match author date")),
15031506
OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL,
15041507
N_("passed to 'git am'"), PARSE_OPT_NOARG),
15051508
OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"),
@@ -1794,11 +1797,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
17941797
options.autosquash) {
17951798
allow_preemptive_ff = 0;
17961799
}
1800+
if (options.committer_date_is_author_date)
1801+
options.flags |= REBASE_FORCE;
17971802

17981803
for (i = 0; i < options.git_am_opts.argc; i++) {
17991804
const char *option = options.git_am_opts.argv[i], *p;
1800-
if (!strcmp(option, "--committer-date-is-author-date") ||
1801-
!strcmp(option, "--ignore-date") ||
1805+
if (!strcmp(option, "--ignore-date") ||
18021806
!strcmp(option, "--whitespace=fix") ||
18031807
!strcmp(option, "--whitespace=strip"))
18041808
allow_preemptive_ff = 0;
@@ -1855,6 +1859,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
18551859
if (ignore_whitespace)
18561860
argv_array_push(&options.git_am_opts,
18571861
"--ignore-whitespace");
1862+
if (options.committer_date_is_author_date)
1863+
argv_array_push(&options.git_am_opts,
1864+
"--committer-date-is-author-date");
18581865
} else {
18591866
/* REBASE_MERGE and PRESERVE_MERGES */
18601867
if (ignore_whitespace) {

sequencer.c

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
149149
* command-line.
150150
*/
151151
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
152+
static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
152153
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
153154
static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
154155
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
@@ -302,6 +303,8 @@ int sequencer_remove_state(struct replay_opts *opts)
302303
}
303304
}
304305

306+
free(opts->committer_name);
307+
free(opts->committer_email);
305308
free(opts->gpg_sign);
306309
free(opts->strategy);
307310
for (i = 0; i < opts->xopts_nr; i++)
@@ -872,6 +875,22 @@ static char *get_author(const char *message)
872875
return NULL;
873876
}
874877

878+
static const char *author_date_from_env_array(const struct argv_array *env)
879+
{
880+
int i;
881+
const char *date;
882+
883+
for (i = 0; i < env->argc; i++)
884+
if (skip_prefix(env->argv[i],
885+
"GIT_AUTHOR_DATE=", &date))
886+
return date;
887+
/*
888+
* If GIT_AUTHOR_DATE is missing we should have already errored out when
889+
* reading the script
890+
*/
891+
BUG("GIT_AUTHOR_DATE missing from author script");
892+
}
893+
875894
static const char staged_changes_advice[] =
876895
N_("you have staged changes in your working tree\n"
877896
"If these changes are meant to be squashed into the previous commit, run:\n"
@@ -938,6 +957,10 @@ static int run_git_commit(struct repository *r,
938957
gpg_opt, gpg_opt);
939958
}
940959

960+
if (opts->committer_date_is_author_date)
961+
argv_array_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
962+
author_date_from_env_array(&cmd.env_array));
963+
941964
argv_array_push(&cmd.args, "commit");
942965

943966
if (!(flags & VERIFY_MSG))
@@ -1315,6 +1338,7 @@ static int try_to_commit(struct repository *r,
13151338
struct strbuf err = STRBUF_INIT;
13161339
struct strbuf commit_msg = STRBUF_INIT;
13171340
char *amend_author = NULL;
1341+
const char *committer = NULL;
13181342
const char *hook_commit = NULL;
13191343
enum commit_msg_cleanup_mode cleanup;
13201344
int res = 0;
@@ -1406,10 +1430,32 @@ static int try_to_commit(struct repository *r,
14061430
goto out;
14071431
}
14081432

1409-
reset_ident_date();
1433+
if (opts->committer_date_is_author_date) {
1434+
struct ident_split id;
1435+
struct strbuf date = STRBUF_INIT;
1436+
1437+
if (split_ident_line(&id, author, (int)strlen(author)) < 0) {
1438+
res = error(_("invalid author identity '%s'"), author);
1439+
goto out;
1440+
}
1441+
if (!id.date_begin) {
1442+
res = error(_("corrupt author: missing date information"));
1443+
goto out;
1444+
}
1445+
strbuf_addf(&date, "@%.*s %.*s",
1446+
(int)(id.date_end - id.date_begin), id.date_begin,
1447+
(int)(id.tz_end - id.tz_begin), id.tz_begin);
1448+
committer = fmt_ident(opts->committer_name,
1449+
opts->committer_email,
1450+
WANT_COMMITTER_IDENT, date.buf,
1451+
IDENT_STRICT);
1452+
strbuf_release(&date);
1453+
} else {
1454+
reset_ident_date();
1455+
}
14101456

14111457
if (commit_tree_extended(msg->buf, msg->len, &tree, parents, oid,
1412-
author, NULL, opts->gpg_sign, extra)) {
1458+
author, committer, opts->gpg_sign, extra)) {
14131459
res = error(_("failed to write commit object"));
14141460
goto out;
14151461
}
@@ -2532,6 +2578,11 @@ static int read_populate_opts(struct replay_opts *opts)
25322578
opts->signoff = 1;
25332579
}
25342580

2581+
if (file_exists(rebase_path_cdate_is_adate())) {
2582+
opts->allow_ff = 0;
2583+
opts->committer_date_is_author_date = 1;
2584+
}
2585+
25352586
if (file_exists(rebase_path_reschedule_failed_exec()))
25362587
opts->reschedule_failed_exec = 1;
25372588

@@ -2622,6 +2673,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
26222673
write_file(rebase_path_drop_redundant_commits(), "%s", "");
26232674
if (opts->keep_redundant_commits)
26242675
write_file(rebase_path_keep_redundant_commits(), "%s", "");
2676+
if (opts->committer_date_is_author_date)
2677+
write_file(rebase_path_cdate_is_adate(), "%s", "");
26252678
if (opts->reschedule_failed_exec)
26262679
write_file(rebase_path_reschedule_failed_exec(), "%s", "");
26272680

@@ -3542,6 +3595,10 @@ static int do_merge(struct repository *r,
35423595
goto leave_merge;
35433596
}
35443597

3598+
if (opts->committer_date_is_author_date)
3599+
argv_array_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
3600+
author_date_from_env_array(&cmd.env_array));
3601+
35453602
cmd.git_cmd = 1;
35463603
argv_array_push(&cmd.args, "merge");
35473604
argv_array_push(&cmd.args, "-s");
@@ -3819,7 +3876,8 @@ static int pick_commits(struct repository *r,
38193876
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
38203877
if (opts->allow_ff)
38213878
assert(!(opts->signoff || opts->no_commit ||
3822-
opts->record_origin || opts->edit));
3879+
opts->record_origin || opts->edit ||
3880+
opts->committer_date_is_author_date));
38233881
if (read_and_refresh_cache(r, opts))
38243882
return -1;
38253883

@@ -4258,6 +4316,22 @@ static int commit_staged_changes(struct repository *r,
42584316
return 0;
42594317
}
42604318

4319+
static int init_committer(struct replay_opts *opts)
4320+
{
4321+
struct ident_split id;
4322+
const char *committer;
4323+
4324+
committer = git_committer_info(IDENT_STRICT);
4325+
if (split_ident_line(&id, committer, strlen(committer)) < 0)
4326+
return error(_("invalid committer '%s'"), committer);
4327+
opts->committer_name =
4328+
xmemdupz(id.name_begin, id.name_end - id.name_begin);
4329+
opts->committer_email =
4330+
xmemdupz(id.mail_begin, id.mail_end - id.mail_end);
4331+
4332+
return 0;
4333+
}
4334+
42614335
int sequencer_continue(struct repository *r, struct replay_opts *opts)
42624336
{
42634337
struct todo_list todo_list = TODO_LIST_INIT;
@@ -4269,6 +4343,9 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
42694343
if (read_populate_opts(opts))
42704344
return -1;
42714345
if (is_rebase_i(opts)) {
4346+
if (opts->committer_date_is_author_date && init_committer(opts))
4347+
return -1;
4348+
42724349
if ((res = read_populate_todo(r, &todo_list, opts)))
42734350
goto release_todo_list;
42744351

@@ -5145,6 +5222,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
51455222

51465223
res = -1;
51475224

5225+
if (opts->committer_date_is_author_date && init_committer(opts))
5226+
goto cleanup;
5227+
51485228
if (checkout_onto(r, opts, onto_name, &oid, orig_head))
51495229
goto cleanup;
51505230

sequencer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ struct replay_opts {
4545
int verbose;
4646
int quiet;
4747
int reschedule_failed_exec;
48+
int committer_date_is_author_date;
4849

4950
int mainline;
5051

52+
char *committer_name;
53+
char *committer_email;
5154
char *gpg_sign;
5255
enum commit_msg_cleanup_mode default_msg_cleanup;
5356
int explicit_cleanup;

t/t3422-rebase-incompatible-options.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ test_rebase_am_only () {
6161
}
6262

6363
test_rebase_am_only --whitespace=fix
64-
test_rebase_am_only --committer-date-is-author-date
6564
test_rebase_am_only -C4
6665

6766
test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' '

t/t3436-rebase-more-options.sh

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ test_description='tests to ensure compatibility between am and interactive backe
99

1010
. "$TEST_DIRECTORY"/lib-rebase.sh
1111

12+
GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
13+
export GIT_AUTHOR_DATE
14+
1215
# This is a special case in which both am and interactive backends
1316
# provide the same output. It was done intentionally because
1417
# both the backends fall short of optimal behaviour.
@@ -21,11 +24,20 @@ test_expect_success 'setup' '
2124
test_write_lines "line 1" "new line 2" "line 3" >file &&
2225
git commit -am "update file" &&
2326
git tag side &&
27+
test_commit commit1 foo foo1 &&
28+
test_commit commit2 foo foo2 &&
29+
test_commit commit3 foo foo3 &&
2430
2531
git checkout --orphan master &&
32+
rm foo &&
2633
test_write_lines "line 1" " line 2" "line 3" >file &&
2734
git commit -am "add file" &&
28-
git tag main
35+
git tag main &&
36+
37+
mkdir test-bin &&
38+
write_script test-bin/git-merge-test <<-\EOF
39+
exec git-merge-recursive "$@"
40+
EOF
2941
'
3042

3143
test_expect_success '--ignore-whitespace works with apply backend' '
@@ -52,6 +64,50 @@ test_expect_success '--ignore-whitespace is remembered when continuing' '
5264
git diff --exit-code side
5365
'
5466

67+
test_ctime_is_atime () {
68+
git log $1 --format=%ai >authortime &&
69+
git log $1 --format=%ci >committertime &&
70+
test_cmp authortime committertime
71+
}
72+
73+
test_expect_success '--committer-date-is-author-date works with apply backend' '
74+
GIT_AUTHOR_DATE="@1234 +0300" git commit --amend --reset-author &&
75+
git rebase --apply --committer-date-is-author-date HEAD^ &&
76+
test_ctime_is_atime -1
77+
'
78+
79+
test_expect_success '--committer-date-is-author-date works with merge backend' '
80+
GIT_AUTHOR_DATE="@1234 +0300" git commit --amend --reset-author &&
81+
git rebase -m --committer-date-is-author-date HEAD^ &&
82+
test_ctime_is_atime -1
83+
'
84+
85+
test_expect_success '--committer-date-is-author-date works with rebase -r' '
86+
git checkout side &&
87+
GIT_AUTHOR_DATE="@1234 +0300" git merge --no-ff commit3 &&
88+
git rebase -r --root --committer-date-is-author-date &&
89+
test_ctime_is_atime
90+
'
91+
92+
test_expect_success '--committer-date-is-author-date works when forking merge' '
93+
git checkout side &&
94+
GIT_AUTHOR_DATE="@1234 +0300" git merge --no-ff commit3 &&
95+
PATH="./test-bin:$PATH" git rebase -r --root --strategy=test \
96+
--committer-date-is-author-date &&
97+
test_ctime_is_atime
98+
'
99+
100+
test_expect_success '--committer-date-is-author-date works when committing conflict resolution' '
101+
git checkout commit2 &&
102+
GIT_AUTHOR_DATE="@1980 +0000" git commit --amend --only --reset-author &&
103+
test_must_fail git rebase -m --committer-date-is-author-date \
104+
--onto HEAD^^ HEAD^ &&
105+
echo resolved > foo &&
106+
git add foo &&
107+
git rebase --continue &&
108+
test_ctime_is_atime -1
109+
'
110+
55111
# This must be the last test in this file
56112
test_expect_success '$EDITOR and friends are unchanged' '
57113
test_editor_unchanged

0 commit comments

Comments
 (0)