From: "Devi Srinivas Vasamsetti via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>,
Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>
Subject: [PATCH] commit: warn when a new commit is dated before its parent
Date: Mon, 05 Oct 2026 15:09:58 +0000 [thread overview]
Message-ID: <pull.2235.git.1791212998072.gitgitgadget@gmail.com> (raw)
From: Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>
Git writes whatever the clock says into the commit object. This can
be problematic because history traversal assumes commit dates are
non-decreasing. For example, "git log --since" stops walking at the
first commit older than the cutoff, so an out-of-order date hides
the commits behind it.
Add a check gated by a new advice.clockSkew setting that warns when
the new commit's date precedes a parent's date. This allows users to
catch a wrong clock at commit time when it is still easy to fix.
This check only looks at the new commit being created and its parents.
Dates in replayed history or merges are not warned about.
Signed-off-by: Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>
---
commit: warn when a new commit is dated before its parent
What does this PR do? This PR adds a helpful warning if you try to make
a new commit with a date that is older than its parent commit.
Why is this needed? Git just uses whatever time your computer's clock
says when creating a commit. If your system clock is incorrect, you
might accidentally create a commit dated in the past.
This causes problems because commands like git log --since assume that
commit dates always move forward. If a date goes backward in history,
those commands can get confused and accidentally hide commits from the
log.
What is fixed? We added a new configuration setting called
advice.clockSkew. Now, Git will warn you right away if your computer's
clock seems to be wrong when you are making a new commit. This gives you
a chance to fix your computer's time before you accidentally push
out-of-order dates!
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2235%2Fsrinivas1591%2Ffeature%2Fwarn-older-commits-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2235/srinivas1591/feature/warn-older-commits-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2235
Documentation/config/advice.adoc | 7 +++++
advice.c | 1 +
advice.h | 1 +
builtin/commit.c | 46 ++++++++++++++++++++++++++++++++
t/t7502-commit-porcelain.sh | 27 +++++++++++++++++++
5 files changed, 82 insertions(+)
diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 81f80a9274..59b643d9d0 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -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.
diff --git a/advice.c b/advice.c
index 63bf8b0c5f..3e14859de4 100644
--- a/advice.c
+++ b/advice.c
@@ -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" },
diff --git a/advice.h b/advice.h
index 66f6cd6a77..43de2b19a2 100644
--- a/advice.h
+++ b/advice.h
@@ -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,
diff --git a/builtin/commit.c b/builtin/commit.c
index 205fbd57e3..78d1dde27e 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -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"
@@ -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"
@@ -1693,6 +1695,48 @@ struct repository *repo UNUSED)
return 0;
}
+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;
+
+ 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,
+ 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)
{
@@ -1962,6 +2006,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)) {
diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh
index 2adfe70b3d..fb611b191b 100755
--- a/t/t7502-commit-porcelain.sh
+++ b/t/t7502-commit-porcelain.sh
@@ -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
base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7
--
gitgitgadget
next reply other threads:[~2026-10-05 15:10 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-05 15:09 Devi Srinivas Vasamsetti via GitGitGadget [this message]
2026-10-06 14:37 ` [PATCH] commit: warn when a new commit is dated before its parent Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2235.git.1791212998072.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=devisrinivas.vasamsetti@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox