mdsmith
Esc
    v0.54.0 GitHub
    MDS048 structural ready

    MDS048: git-hook-sync

    Git artifacts must match the canonical glob-based template derived from .mdsmith.yml.

    The .gitattributes managed block and the pre-merge-commit hook must match a canonical template. That template is computed from the project’s .mdsmith.yml ignore patterns. Editing .mdsmith.yml keeps both git artifacts in sync.

    One limitation: ignore patterns that cannot appear verbatim in .gitattributes are dropped from the managed block. Negation patterns starting with ! and patterns containing whitespace fall in this bucket. mdsmith fix itself still honors them, so the merge-driver scope can diverge from mdsmith fix’s scope when the ignore: list relies on those forms.

    # Rationale

    Markdown files can contain generated sections like <?catalog?>, <?include?>, and <?toc?>. They need special handling during git merges to regenerate content and avoid conflicts. Two artifacts cooperate:

    • .gitattributes assigns the mdsmith merge driver to markdown files. The managed block uses globs (e.g. *.md merge=mdsmith) plus markdown-scoped exclude overrides (e.g. demo/**/*.md -merge) so the assignment scope tracks .mdsmith.yml ignore patterns rather than enumerating individual files. Each ignore pattern is intersected with the markdown include extensions, so an exclude line never disables git’s merge for a non-markdown file in a grandfathered tree.
    • The pre-merge-commit hook re-runs mdsmith fix . once every per-file merge has resolved, so generated sections reflect the final merged state. The hook script is glob-driven (no embedded file list), so its scope tracks the same ignore patterns automatically.

    This rule detects when either artifact drifts from the canonical template.

    # Settings

    This rule has no configuration settings beyond enabling/disabling it.

    # Config

    Enable (default: disabled):

    rules:
      git-hook-sync: true

    Disable:

    rules:
      git-hook-sync: false

    # How It Works

    The rule:

    1. Reads .mdsmith.yml ignore patterns and computes the canonical glob set: include patterns (default: *.md, *.markdown) followed by markdown-scoped exclude patterns for each ignore: entry (a demo/** ignore becomes demo/**/*.md and demo/**/*.markdown).
    2. If merge.mdsmith.driver is registered in git config, reads .gitattributes and compares the BEGIN/END managed block against the canonical render.
    3. Reads the pre-merge-commit hook (when it carries the mdsmith marker) and compares the script against the canonical glob-based template.
    4. Reports a warning if either artifact drifts.

    .gitattributes does not support negation patterns (!*.md is a syntax error there). The managed block exploits last-match-wins instead: include lines come first, then <exclude> -merge lines override them for matching paths.

    # Fix

    This rule is partially auto-fixable:

    • .gitattributes is auto-fixed by mdsmith fix when the merge driver is registered (merge.mdsmith.driver in git config). The fixer rewrites the managed block with the canonical globs, preserving any non-mdsmith entries surrounding it.
    • Pre-merge-commit hook is not auto-fixed (it is an executable script). You must manually re-run:
    mdsmith pre-merge-commit install

    # Why Auto-Fix .gitattributes?

    .gitattributes is a tracked file. It is not system configuration, and not executable. So it should be auto-fixable like other content issues. Build systems running mdsmith fix reconcile drift automatically.

    The pre-merge-commit hook remains manual-only because modifying executable files during automated fixes could be surprising or unsafe.

    When mdsmith fix updates .gitattributes, the fixer also runs git add -- .gitattributes. The regenerated file then lands in the index next to the markdown files the hook stages, so a merge commit produced by the pre-merge-commit hook flow includes both. If staging fails (for example, the index is locked by another git process), the on-disk fix is still applied and the failure is recorded. Check() keeps emitting a “staging failed” diagnostic until a later Fix() call re-runs git add successfully.

    # Manual Installation

    To register the merge driver, write the canonical .gitattributes block, and install the hook:

    mdsmith merge-driver install
    mdsmith pre-merge-commit install

    Both commands write the canonical glob-based content from the project’s .mdsmith.yml, so re-running them is idempotent.

    # Examples

    # Good

    .mdsmith.yml ignores test fixtures, and .gitattributes mirrors that exactly:

    # .mdsmith.yml
    ignore:
      - "demo/**"
      - "vendor/**"
    # .gitattributes
    # BEGIN mdsmith merge-driver
    *.md merge=mdsmith
    *.markdown merge=mdsmith
    demo/**/*.md -merge
    demo/**/*.markdown -merge
    vendor/**/*.md -merge
    vendor/**/*.markdown -merge
    # END mdsmith merge-driver

    # Bad

    .gitattributes still uses the old per-file format from a pre-glob install:

    # BEGIN mdsmith merge-driver
    README.md merge=mdsmith
    docs/index.md merge=mdsmith
    # END mdsmith merge-driver

    Diagnostic: .gitattributes managed block is out of sync. The message lists the installed include and exclude patterns and the canonical patterns the rule expected.

    # Meta-Information

    • ID: MDS048
    • Name: git-hook-sync
    • Status: ready
    • Default: disabled
    • Fixable: partial (.gitattributes is auto-fixed; hook requires manual mdsmith pre-merge-commit install)
    • Implementation: source
    • Category: structural