githooks

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package githooks manages CodeGraph's marker-fenced git sync hooks (post-commit/post-merge/post-checkout) — a verbatim port of TS sync/git-hooks.js (D-01/D-02). Every write funnels through internal/fsatomic.WriteFile (D-05); hooks-dir resolution and the git-repo probe come from internal/gitmeta (IsGitRepo/HooksDir) — this package never shells out to git directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HookStatus

type HookStatus struct {
	Name       string
	Installed  bool
	Executable bool
}

HookStatus is one hook's install state, as reported by Status. Installed means the file exists and contains the begin marker (TS-parity text-only check, D-03). Executable additionally reports whether the file's exec bit is set (IN-03) — fsatomic.WriteFile's atomic rename and the subsequent best-effort os.Chmod are two separate, non-atomic steps, so a crash between them (or an external `chmod -x`) can leave a hook that's Installed but not actually runnable by git. Executable is only meaningful when Installed is true.

type InstallResult

type InstallResult struct {
	Installed []string
	HooksDir  string
	Skipped   string
	Errors    []error
}

InstallResult reports the outcome of Install. Installed lists the hooks actually written, in the fixed defaultSyncHooks order. Skipped is set (and Installed left empty) when the target isn't a git repository or the hooks directory couldn't be created — never an error, per D-04's clean-skip contract. Errors accumulates one entry per hook that failed its individual write (e.g. unwritable file, read-only mount, disk full) so callers can surface *why* Installed came back short of all three hooks instead of failing silently (WR-01).

func Install

func Install(ctx context.Context, projectRoot string) InstallResult

Install writes the marker-fenced sync-hook block into each of post-commit/post-merge/post-checkout, in that fixed order (verbatim port of TS installGitSyncHook, sync/git-hooks.js:155-186, D-02/D-05). For each hook file: any existing content has a prior codegraph block stripped and trailing whitespace trimmed; if what remains is non-empty, the current block is appended after a blank-line separator; otherwise (no existing file, or an effectively-empty base) the file is seeded with "#!/bin/sh\n" + block. This is strip-then-append-at-end, not in-place replacement (Pitfall 2). Every write goes through fsatomic.WriteFile; chmod 0755 is best-effort (Pitfall 4, TS swallows chmod errors too). In a non-repo, returns Skipped "not a git repository" and writes nothing.

Malformed existing content (CR-01): if stripMarkerBlock reports the existing hook file's marker pairing can't be trusted (unterminated begin, nested begin, or dangling end), Install does NOT write to that file at all — appending a fresh block after the untrustworthy raw content would leave a dangling marker in place that a later Install/Remove call could misinterpret as closed by the new block, silently deleting everything in between. Instead the hook is skipped, an error is accumulated in InstallResult.Errors naming the hook and asking the user to fix or delete the malformed block manually, and the file is left byte-for-byte untouched — matching Remove's existing ok==false handling.

Note (verbatim TS quirk, confirmed against sync/git-hooks.js): the very first install of a fresh hook file seeds "#!/bin/sh\n"+block (no blank-line separator), but the moment that file is read back on a second install, the surviving "#!/bin/sh" line is treated as non-empty base content, so the round-tripped form becomes "#!/bin/sh\n\n"+block (one blank line inserted). From that second install onward the round-tripped form is a stable fixed point — re-installing again never changes it. Only the very first-vs-second install transition adds that one blank line; this is TS's real behavior, faithfully reproduced here, not a Go-side bug.

Concurrency (WR-02): each individual hook write is atomic and crash-safe via fsatomic.WriteFile, but the surrounding read-modify-write sequence (read existing content, compute the new body, write it back) is not. Install is not safe to call concurrently against the same projectRoot — two overlapping Install/Remove invocations (or Install racing init's advisory path) can race on the same hook file and produce a lost update, with neither caller aware anything raced. Callers that need concurrent-safety must serialize their own calls (e.g. a lockfile around the hooks-dir mutation).

type RemoveResult

type RemoveResult struct {
	Removed  []string
	HooksDir string
	Skipped  string
	Errors   []error
}

RemoveResult reports the outcome of Remove. Removed lists the hooks that had a codegraph block stripped (file deleted or rewritten). Uses the Go-idiomatic field name Removed rather than TS's `{installed: removed}` naming quirk (RESEARCH.md note on removeGitSyncHook's result shape). Errors accumulates one entry per hook that failed its individual delete/write (WR-01) — the loop still continues past a failure, but the failure is no longer silently discarded.

func Remove

func Remove(ctx context.Context, projectRoot string) RemoveResult

Remove strips codegraph's marker block from each of post-commit/post-merge/post-checkout (verbatim port of TS removeGitSyncHook, sync/git-hooks.js:192-216, D-02/D-05). Only files that actually contain the begin marker are touched — a hook never installed by codegraph, or an absent file, is skipped with no error. If the remainder after stripping is effectively empty (isEffectivelyEmpty), the file is deleted entirely via os.Remove; otherwise the trimmed remainder + a trailing newline is written via fsatomic.WriteFile and re-chmod'd 0755 (best-effort). Running Remove twice is a no-op on the second run (files already gone or already clean). In a non-repo, returns Skipped "not a git repository".

Concurrency (WR-02): same caveat as Install — the read-modify-write sequence around each hook file is not atomic as a whole, only the individual fsatomic.WriteFile call is. Remove is not safe to call concurrently against the same projectRoot, including racing an Install against the same hooks directory.

type StatusResult

type StatusResult struct {
	Hooks    []HookStatus
	HooksDir string
	Skipped  string
}

StatusResult reports per-hook install state for all three sync hooks.

func Status

func Status(ctx context.Context, projectRoot string) StatusResult

Status reports per-hook install state for all three sync hooks (extends TS isSyncHookInstalled's aggregate some() with per-hook detail, D-11). A hook is Installed when its file exists and contains the begin marker — this includes hooks installed by TS CodeGraph itself, since the markers are byte-identical (D-03). Executable additionally reports the file's exec bit (IN-03, a Go-only robustness addition beyond TS parity — TS's isSyncHookInstalled never checks executability). In a non-repo, returns Skipped "not a git repository".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL