Skip to content

feat(git): add a hook that can enforce powerful commit selection policies - #2165

Open
FlippingBinary wants to merge 3 commits into
folke:mainfrom
FlippingBinary:feat/commit-hook
Open

FlippingBinary wants to merge 3 commits into
folke:mainfrom
FlippingBinary:feat/commit-hook

Conversation

@FlippingBinary

Copy link
Copy Markdown

Description

Resolves #2141 by introducing a CommitHook — a flexible extension point that lets users implement their own commit-selection logic (such as a minimum age requirement) without forcing a built-in policy on everyone.

The original issue asked for a built-in minimum commit age feature to reduce churn from broken commits. There was resistance to shipping such a feature directly: aging is one of many possible rules users might want (such as skipping CI bot commits, commits with a rapid followup, fresh commits, etc.), and baking a specific policy into core would either impose one user's preferences on everyone or require a pile of configuration that overlaps with this generic mechanism.

This change provides the generic mechanism instead and lets users decide what to enforce.

What it does:

  • Adds a commit field to defaults and allows it or the per-spec commit to contain a CommitHook function.
  • The hook receives a GitTarget describing the commit that would otherwise be selected (resolved tag, version, or latest on the branch) and returns either:
    • a commit hash string, or
    • a GitTarget (e.g. the target itself, or one of its parents via target:parent()).
  • GitTarget exposes methods useful for writing rules: :date(), :age(), :short(), :message(), :author(), :parent().
  • A per-spec commit function wins over the global default when both are set.

Examples:

Skip commits newer than 7 days (the original use case from the issue):

require("lazy").setup({
  defaults = {
    commit = function(target)
      ---@type GitTarget?
      local curr = target
      while curr and curr:age() < 7 do
        curr = curr:parent()
      end
      return curr
    end,
  },
})

Skip commits followed up within 3 days:

require("lazy").setup({
  defaults = {
    commit = function(target)
      local last_age = 0
      ---@type GitTarget?
      local curr = target
      while curr and curr:age() - last_age <= 3 do
        last_age = curr:age()
        curr = curr:parent()
      end
      return curr
    end
  },
})

Skip commits authored by Github Copilot:

require("lazy").setup({
  defaults = {
    commit = function(target)
      ---@type GitTarget?
      local curr = target
      while curr and curr:author():match("copilot") do
        curr = curr:parent()
      end
      return curr
    end
  },
})

Notes:

  • The commit field remains backward compatible when set to a string.
  • git.lua was refactored to share the target-building path between the hook and the unchanged string-commit case; the special-cased branch in manage/task/git.lua was removed in favor of resolving through get_target.
  • Tests added in tests/manage/git_spec.lua cover both the hook behavior and the unchanged paths. The tests are significantly slower than the other tests, which may explain why there were no tests covering git behavior before. They are standalone and easily removed.

As an aside, this pull request includes a commit that modifies the test framework. Under nvim -l, mini.test's scheduler-based queue was being torn down by Neovim's exit sequence before tests drained, which set vim.v.exiting mid-flight and caused lazy's async executor to hang. The test entrypoint now blocks on MiniTest.is_executing() so mini.test's own quit_on_finish runs cleanly. If desired, that commit can be spun off or removed entirely. I just needed it for the tests to work, but there probably is a better way to solve that particular problem.

Related Issue(s)

Fixes #2141

Screenshots

N/A

@github-actions github-actions Bot added the size/xl Extra large PR (100+ lines changed) label Jun 20, 2026
@FlippingBinary FlippingBinary changed the title Add a hook that can enforce powerful commit selection policies feat(git): add a hook that can enforce powerful commit selection policies Jun 20, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl Extra large PR (100+ lines changed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: minimum release age / update cooldown for security

1 participant