Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Documentation/config/advice.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ all advice messages.
configuration variable for how to set a given remote
to be used by default in some situations where this
advice would be printed.
clockSkew::
Shown by linkgit:git-commit[1] when the commit being
created is dated earlier than one of its parents, which
usually means the system clock is wrong. History
traversal assumes commit dates do not decrease, so such
a commit can cause commands like `git log --since` to
skip the commits behind it.
commitBeforeMerge::
Shown when linkgit:git-merge[1] refuses to
merge to avoid overwriting local changes.
Expand Down
1 change: 1 addition & 0 deletions advice.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static struct {
[ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec" },
[ADVICE_AM_WORK_DIR] = { "amWorkDir" },
[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
[ADVICE_CLOCK_SKEW] = { "clockSkew" },
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
[ADVICE_DEFAULT_BRANCH_NAME] = { "defaultBranchName" },
[ADVICE_DETACHED_HEAD] = { "detachedHead" },
Expand Down
1 change: 1 addition & 0 deletions advice.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum advice_type {
ADVICE_AMBIGUOUS_FETCH_REFSPEC,
ADVICE_AM_WORK_DIR,
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
ADVICE_CLOCK_SKEW,
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DEFAULT_BRANCH_NAME, /* To be retired sometime after Git 3.0 */
ADVICE_DETACHED_HEAD,
Expand Down
61 changes: 61 additions & 0 deletions builtin/commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "builtin.h"
#include "advice.h"
#include "config.h"
#include "date.h"
#include "lockfile.h"
#include "cache-tree.h"
#include "color.h"
Expand All @@ -21,6 +22,7 @@
#include "commit.h"
#include "add-interactive.h"
#include "gettext.h"
#include "ident.h"
#include "revision.h"
#include "wt-status.h"
#include "run-command.h"
Expand Down Expand Up @@ -1666,6 +1668,63 @@ struct repository *repo UNUSED)
return 0;
}

/*
* Warn when the commit we are about to write is dated earlier than a parent.
*
* Git stores whatever the clock says, and history traversal assumes commit
* dates do not decrease: "git log --since", for one, stops walking at the
* first commit older than the cutoff, so an out-of-order date silently hides
* the commits behind it. Only the person committing can tell whether their
* clock or the parent's is the wrong one, so warn rather than refuse.
*
* This deliberately looks at nothing but the commit being created and its
* parents. Skew between machines is normal in a distributed system and is not
* something to complain about at commit time.
*/
static void warn_if_dated_before_parents(struct commit_list *parents)
{
struct ident_split committer;
struct strbuf ours = STRBUF_INIT;
const char *info;
timestamp_t date, newest = 0;

if (!advice_enabled(ADVICE_CLOCK_SKEW))
return;

info = git_committer_info(IDENT_STRICT);
if (split_ident_line(&committer, info, strlen(info)) ||
!committer.date_begin)
return;
date = parse_timestamp(committer.date_begin, NULL, 10);

for (; parents; parents = parents->next) {
struct commit *parent = parents->item;

if (repo_parse_commit(the_repository, parent))
continue;
if (parent->date > newest)
newest = parent->date;
}

if (!newest || date >= newest)
return;

/* show_date() reuses one buffer, so keep a copy of the first result. */
strbuf_addstr(&ours, show_date(date, atoi(committer.date_end + 1),
DATE_MODE(ISO8601)));

advise_if_enabled(ADVICE_CLOCK_SKEW,
_("the new commit is dated %s,\n"
"which is earlier than its parent, dated %s.\n"
"This usually means the system clock is wrong.\n"
"Commands that walk history in date order, such as\n"
"\"git log --since\", may skip commits as a result."),
ours.buf,
/* A parsed commit keeps no timezone, so show UTC. */
show_date(newest, 0, DATE_MODE(ISO8601)));
strbuf_release(&ours);
}

static int git_commit_config(const char *k, const char *v,
const struct config_context *ctx, void *cb)
{
Expand Down Expand Up @@ -1935,6 +1994,8 @@ int cmd_commit(int argc,
append_merge_tag_headers(parents, &tail);
}

warn_if_dated_before_parents(parents);

if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
parents, &oid, author_ident.buf, NULL,
sign_commit, extra)) {
Expand Down
27 changes: 27 additions & 0 deletions t/t7502-commit-porcelain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1003,4 +1003,31 @@ test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '
test_cmp expect actual
'

test_expect_success 'warn when a commit is dated before its parent' '
test_when_finished "git checkout main 2>/dev/null || git checkout master" &&
git checkout -b clock-skew &&
test_commit --date "2026-09-25T10:00:00+0000" skew-parent &&
echo skew >skew-child &&
git add skew-child &&
GIT_COMMITTER_DATE="2026-09-13T06:00:00+0000" \
git commit -m "behind its parent" 2>actual &&
test_grep "earlier than its parent" actual
'

test_expect_success 'no warning when commit dates increase' '
echo forward >skew-forward &&
git add skew-forward &&
GIT_COMMITTER_DATE="2026-09-26T06:00:00+0000" \
git commit -m "after its parent" 2>actual &&
test_grep ! "earlier than its parent" actual
'

test_expect_success 'advice.clockSkew silences the warning' '
echo quiet >skew-quiet &&
git add skew-quiet &&
GIT_COMMITTER_DATE="2026-09-14T06:00:00+0000" \
git -c advice.clockSkew=false commit -m quiet 2>actual &&
test_grep ! "earlier than its parent" actual
'

test_done
Loading