{"thread":{"id":"66465","subject":"[PATCH] commit: warn when a new commit is dated before its parent","startedAt":"2026-10-05T15:09:58Z","lastAt":"2026-10-05T15:09:58Z","messageCount":1,"participants":["Devi Srinivas Vasamsetti via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"554184","messageId":"pull.2235.git.1791212998072.gitgitgadget@gmail.com","threadId":"66465","inReplyTo":null,"subject":"[PATCH] commit: warn when a new commit is dated before its parent","fromName":"Devi Srinivas Vasamsetti via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-10-05T15:09:58Z","receivedAt":"2026-10-05T15:09:58Z","isPatch":true,"body":"From: Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>\n\nGit writes whatever the clock says into the commit object. This can\nbe problematic because history traversal assumes commit dates are\nnon-decreasing. For example, \"git log --since\" stops walking at the\nfirst commit older than the cutoff, so an out-of-order date hides\nthe commits behind it.\n\nAdd a check gated by a new advice.clockSkew setting that warns when\nthe new commit's date precedes a parent's date. This allows users to\ncatch a wrong clock at commit time when it is still easy to fix.\n\nThis check only looks at the new commit being created and its parents.\nDates in replayed history or merges are not warned about.\n\nSigned-off-by: Devi Srinivas Vasamsetti <devisrinivas.vasamsetti@gmail.com>\n---\n    commit: warn when a new commit is dated before its parent\n    \n    What does this PR do? This PR adds a helpful warning if you try to make\n    a new commit with a date that is older than its parent commit.\n    \n    Why is this needed? Git just uses whatever time your computer's clock\n    says when creating a commit. If your system clock is incorrect, you\n    might accidentally create a commit dated in the past.\n    \n    This causes problems because commands like git log --since assume that\n    commit dates always move forward. If a date goes backward in history,\n    those commands can get confused and accidentally hide commits from the\n    log.\n    \n    What is fixed? We added a new configuration setting called\n    advice.clockSkew. Now, Git will warn you right away if your computer's\n    clock seems to be wrong when you are making a new commit. This gives you\n    a chance to fix your computer's time before you accidentally push\n    out-of-order dates!\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2235%2Fsrinivas1591%2Ffeature%2Fwarn-older-commits-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2235/srinivas1591/feature/warn-older-commits-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2235\n\n Documentation/config/advice.adoc |  7 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/commit.c                 | 46 ++++++++++++++++++++++++++++++++\n t/t7502-commit-porcelain.sh      | 27 +++++++++++++++++++\n 5 files changed, 82 insertions(+)\n\ndiff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc\nindex 81f80a9274..59b643d9d0 100644\n--- a/Documentation/config/advice.adoc\n+++ b/Documentation/config/advice.adoc\n@@ -38,6 +38,13 @@ all advice messages.\n \t\tconfiguration variable for how to set a given remote\n \t\tto be used by default in some situations where this\n \t\tadvice would be printed.\n+\tclockSkew::\n+\t\tShown by linkgit:git-commit[1] when the commit being\n+\t\tcreated is dated earlier than one of its parents, which\n+\t\tusually means the system clock is wrong. History\n+\t\ttraversal assumes commit dates do not decrease, so such\n+\t\ta commit can cause commands like `git log --since` to\n+\t\tskip the commits behind it.\n \tcommitBeforeMerge::\n \t\tShown when linkgit:git-merge[1] refuses to\n \t\tmerge to avoid overwriting local changes.\ndiff --git a/advice.c b/advice.c\nindex 63bf8b0c5f..3e14859de4 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -50,6 +50,7 @@ static struct {\n \t[ADVICE_AMBIGUOUS_FETCH_REFSPEC]\t\t= { \"ambiguousFetchRefspec\" },\n \t[ADVICE_AM_WORK_DIR] \t\t\t\t= { \"amWorkDir\" },\n \t[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] \t= { \"checkoutAmbiguousRemoteBranchName\" },\n+\t[ADVICE_CLOCK_SKEW]\t\t\t\t= { \"clockSkew\" },\n \t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\" },\n \t[ADVICE_DEFAULT_BRANCH_NAME]\t\t\t= { \"defaultBranchName\" },\n \t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\" },\ndiff --git a/advice.h b/advice.h\nindex 66f6cd6a77..43de2b19a2 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -17,6 +17,7 @@ enum advice_type {\n \tADVICE_AMBIGUOUS_FETCH_REFSPEC,\n \tADVICE_AM_WORK_DIR,\n \tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n+\tADVICE_CLOCK_SKEW,\n \tADVICE_COMMIT_BEFORE_MERGE,\n \tADVICE_DEFAULT_BRANCH_NAME, /* To be retired sometime after Git 3.0 */\n \tADVICE_DETACHED_HEAD,\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 205fbd57e3..78d1dde27e 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -11,6 +11,7 @@\n #include \"builtin.h\"\n #include \"advice.h\"\n #include \"config.h\"\n+#include \"date.h\"\n #include \"lockfile.h\"\n #include \"cache-tree.h\"\n #include \"color.h\"\n@@ -21,6 +22,7 @@\n #include \"commit.h\"\n #include \"add-interactive.h\"\n #include \"gettext.h\"\n+#include \"ident.h\"\n #include \"revision.h\"\n #include \"wt-status.h\"\n #include \"run-command.h\"\n@@ -1693,6 +1695,48 @@ struct repository *repo UNUSED)\n \treturn 0;\n }\n \n+static void warn_if_dated_before_parents(struct commit_list *parents)\n+{\n+\tstruct ident_split committer;\n+\tstruct strbuf ours = STRBUF_INIT;\n+\tconst char *info;\n+\ttimestamp_t date, newest = 0;\n+\n+\tif (!advice_enabled(ADVICE_CLOCK_SKEW))\n+\t\treturn;\n+\n+\tinfo = git_committer_info(IDENT_STRICT);\n+\tif (split_ident_line(&committer, info, strlen(info)) ||\n+\t    !committer.date_begin)\n+\t\treturn;\n+\tdate = parse_timestamp(committer.date_begin, NULL, 10);\n+\n+\tfor (; parents; parents = parents->next) {\n+\t\tstruct commit *parent = parents->item;\n+\n+\t\tif (repo_parse_commit(the_repository, parent))\n+\t\t\tcontinue;\n+\t\tif (parent->date > newest)\n+\t\t\tnewest = parent->date;\n+\t}\n+\n+\tif (!newest || date >= newest)\n+\t\treturn;\n+\n+\tstrbuf_addstr(&ours, show_date(date, atoi(committer.date_end + 1),\n+\t\t\t\t       DATE_MODE(ISO8601)));\n+\n+\tadvise_if_enabled(ADVICE_CLOCK_SKEW,\n+\t\t\t  _(\"the new commit is dated %s,\\n\"\n+\t\t\t    \"which is earlier than its parent, dated %s.\\n\"\n+\t\t\t    \"This usually means the system clock is wrong.\\n\"\n+\t\t\t    \"Commands that walk history in date order, such as\\n\"\n+\t\t\t    \"\\\"git log --since\\\", may skip commits as a result.\"),\n+\t\t\t  ours.buf,\n+\t\t\t  show_date(newest, 0, DATE_MODE(ISO8601)));\n+\tstrbuf_release(&ours);\n+}\n+\n static int git_commit_config(const char *k, const char *v,\n \t\t\t     const struct config_context *ctx, void *cb)\n {\n@@ -1962,6 +2006,8 @@ int cmd_commit(int argc,\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\twarn_if_dated_before_parents(parents);\n+\n \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n \t\t\t\t parents, &oid, author_ident.buf, NULL,\n \t\t\t\t sign_commit, extra)) {\ndiff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh\nindex 2adfe70b3d..fb611b191b 100755\n--- a/t/t7502-commit-porcelain.sh\n+++ b/t/t7502-commit-porcelain.sh\n@@ -1003,4 +1003,31 @@ test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'warn when a commit is dated before its parent' '\n+\ttest_when_finished \"git checkout main 2>/dev/null || git checkout master\" &&\n+\tgit checkout -b clock-skew &&\n+\ttest_commit --date \"2026-09-25T10:00:00+0000\" skew-parent &&\n+\techo skew >skew-child &&\n+\tgit add skew-child &&\n+\tGIT_COMMITTER_DATE=\"2026-09-13T06:00:00+0000\" \\\n+\t\tgit commit -m \"behind its parent\" 2>actual &&\n+\ttest_grep \"earlier than its parent\" actual\n+'\n+\n+test_expect_success 'no warning when commit dates increase' '\n+\techo forward >skew-forward &&\n+\tgit add skew-forward &&\n+\tGIT_COMMITTER_DATE=\"2026-09-26T06:00:00+0000\" \\\n+\t\tgit commit -m \"after its parent\" 2>actual &&\n+\ttest_grep ! \"earlier than its parent\" actual\n+'\n+\n+test_expect_success 'advice.clockSkew silences the warning' '\n+\techo quiet >skew-quiet &&\n+\tgit add skew-quiet &&\n+\tGIT_COMMITTER_DATE=\"2026-09-14T06:00:00+0000\" \\\n+\t\tgit -c advice.clockSkew=false commit -m quiet 2>actual &&\n+\ttest_grep ! \"earlier than its parent\" actual\n+'\n+\n test_done\n\nbase-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7\n-- \ngitgitgadget\n\n"}]}