diff --git a/distgit/hooks.py b/distgit/hooks.py
new file mode 100644
index 0000000..d057117
--- /dev/null
+++ b/distgit/hooks.py
@@ -0,0 +1,253 @@
+from enum import Enum
+from typing import Optional
+import pygit2
+
+
+class HookResult(Enum):
+    SUCCESS = 0
+    FAILURE = 1
+
+
+class Hook(object):
+    def __init__(self, repo: pygit2.Repository):
+        self.repo = repo
+
+
+"""
+Pre-Commit Hook (Client-side)
+Runs before a commit is made, before a commit message is written (if not supplied by -m).
+Use this hook to:
+- Check for uncommitted changes
+- Run tests, lints, security checks, etc.
+This can be bypassed with --no-verify by the user.
+"""
+class PreCommit(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Prepare-Commit-Msg Hook (Client-side)
+Runs right after the default log message is prepared and before the editor is started.
+Use this hook to:
+- Edit the message file in place (e.g. strip template comments)
+- Insert a standard prefix/suffix (e.g. branch name, ticket ID)
+- Add Signed-off-by from a template
+Takes 1–3 parameters: message file path, source (message|template|merge|squash|commit), and optionally commit hash for amend.
+"""
+class PrepareCommitMsg(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(
+        self,
+        message_file: str,
+        source: str = "",
+        commit_hash: Optional[str] = None,
+    ) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Commit-Msg Hook (Client-side)
+Runs after the commit message is prepared; can be bypassed with --no-verify.
+Use this hook to:
+- Enforce a project standard format (e.g. conventional commits)
+- Validate or normalize the message in place
+- Reject the commit (e.g. duplicate Signed-off-by, missing ticket reference)
+Takes one parameter: the path to the file holding the proposed commit log message.
+"""
+class CommitMsg(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, message_file: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Post-Commit Hook (Client-side)
+Runs after a commit is made. Cannot affect the outcome of git commit.
+Use this hook to:
+- Notify (e.g. log, webhook, chat)
+- Run post-commit checks or backups
+- Update external metadata or caches
+"""
+class PostCommit(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Pre-Merge-Commit Hook (Client-side)
+Runs after a merge has been carried out successfully and before the merge commit message is finalized; can be bypassed with --no-verify.
+Use this hook to:
+- Validate the merged tree (e.g. run tests on the result)
+- Inspect or adjust the merge commit message
+- Abort the merge commit if checks fail
+Takes no parameters.
+"""
+class PreMergeCommit(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Pre-Rebase Hook (Client-side)
+Called by git rebase; can be used to prevent a branch from being rebased.
+Use this hook to:
+- Block rebasing certain branches (e.g. main)
+- Run checks before rewriting history
+Takes one or two parameters: upstream ref, and optionally the branch being rebased (absent when rebasing the current branch).
+"""
+class PreRebase(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, upstream: str, branch: Optional[str] = None) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Post-Checkout Hook (Client-side)
+Runs after git checkout, git switch, or git clone (when a worktree is updated).
+Use this hook to:
+- Restore working tree metadata (e.g. permissions, ACLs)
+- Auto-display differences from the previous HEAD
+- Run repository validity checks or refresh generated files
+Takes three parameters: previous HEAD ref, new HEAD ref, and a flag (1 = branch checkout, 0 = file checkout).
+"""
+class PostCheckout(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, prev_head: str, new_head: str, branch_checkout: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Post-Merge Hook (Client-side)
+Runs after a successful git merge (e.g. after git pull). Cannot affect the outcome.
+Use this hook to:
+- Restore working tree metadata in conjunction with pre-commit
+- Run post-merge checks or notifications
+Takes one parameter: a status flag indicating whether the merge was a squash merge.
+"""
+class PostMerge(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, squash: str) -> HookResult:
+        return HookResult.SUCCESS
+"""
+Pre-Push Hook (Client-side)
+Called by git push; can be used to prevent a push.
+Use this hook to:
+- Run tests or lint before pushing
+- Enforce branch naming or ref permissions
+- Validate commits being pushed
+Takes two parameters: remote name and remote URL. Ref updates are provided on stdin.
+"""
+class PrePush(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, remote_name: str, remote_url: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+# -----------------------------------------------------------------------------
+# Server-side hooks (run in $GIT_DIR on receive-pack / push)
+# -----------------------------------------------------------------------------
+
+"""
+Update Hook (Server-side)
+Invoked by git-receive-pack once per ref being updated, before the ref is updated.
+Use this hook to:
+- Enforce fast-forward only (reject non-FF updates)
+- Implement per-ref access control
+- Log or validate old → new for specific refs
+Takes three parameters: ref name, old object name, new object name.
+"""
+class Update(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, ref_name: str, old_oid: str, new_oid: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Post-Update Hook (Server-side)
+Invoked by git-receive-pack once after all refs have been updated.
+Use this hook to:
+- Notify or trigger CI for updated refs
+- Run git update-server-info for dumb transports (e.g. HTTP)
+- Update caches or derived data
+Takes a variable number of parameters: the name of each ref that was updated.
+"""
+class PostUpdate(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, *ref_names: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Push-To-Checkout Hook (Server-side)
+Invoked when a push updates the currently checked-out branch and receive.denyCurrentBranch is updateInstead.
+Use this hook to:
+- Override how the working tree and index are updated to match the new commit
+- Run git read-tree -u -m to emulate a reverse fetch
+- Refuse the push by exiting non-zero (without modifying index or worktree)
+Takes one parameter: the commit object name the tip of the current branch will be updated to.
+"""
+class PushToCheckout(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self, new_commit: str) -> HookResult:
+        return HookResult.SUCCESS
+
+
+"""
+Pre-Auto-GC Hook
+Invoked by git gc --auto before automatic garbage collection runs.
+Use this hook to:
+- Prevent or delay gc when the repo is busy (e.g. long-running operations)
+- Run housekeeping or consistency checks before gc
+- Notify or log that auto-gc is about to run
+Takes no parameters. Exiting with non-zero status prevents gc from running.
+"""
+class PreAutoGc(Hook):
+    def __init__(self, repo: pygit2.Repository):
+        super().__init__(repo)
+
+    def run(self) -> HookResult:
+        return HookResult.SUCCESS
+
+
+# -----------------------------------------------------------------------------
+# Hooks skipped (not implemented in this module)
+# -----------------------------------------------------------------------------
+#
+# E-mail / git-am hooks (skipped by design):
+#   - applypatch-msg   (message file; used by git am)
+#   - pre-applypatch   (no params; used by git am)
+#   - post-applypatch  (no params; used by git am)
+#
+# Stdin-only or protocol hooks (skipped: no string parameters to pass to run()):
+#   - pre-receive         (no args; ref updates on stdin)
+#   - post-receive        (no args; ref updates on stdin)
+#   - reference-transaction (state string + ref updates on stdin)
+#   - proc-receive        (pkt-line protocol on stdin/stdout)
diff --git a/distgit/hooks_patterns.py b/distgit/hooks_patterns.py
new file mode 100644
index 0000000..73c2abe
--- /dev/null
+++ b/distgit/hooks_patterns.py
@@ -0,0 +1,44 @@
+from distgit.hooks import Hook, HookResult, CommitMsg, Update
+import re
+from sys import stderr
+import pygit2
+
+"""
+Commit-Msg Pattern Hook (Client-side)
+Validates the commit message against a regular expression.
+"""
+class CommitMsgPattern(CommitMsg):
+    def __init__(self, repo: pygit2.Repository, pattern: str):
+        super().__init__(repo)
+        self.exp = re.compile(pattern)
+
+    def run(self, message_file: str) -> HookResult:
+        with open(message_file, "r") as f:
+            message = f.read()
+        if not self.exp.match(message):
+            stderr.write(f"Commit message does not match pattern: {self.exp.pattern}\n")
+            return HookResult.FAILURE
+        return HookResult.SUCCESS
+
+"""
+Update Pattern Hook (Server-side)
+Invoked by git-receive-pack once per ref being updated, before the ref is updated.
+Use this hook to:
+- Enforce fast-forward only (reject non-FF updates)
+- Implement per-ref access control
+- Log or validate old → new for specific refs
+Takes three parameters: ref name, old object name, new object name.
+"""
+class UpdatePattern(Hook):
+    def __init__(self, repo: pygit2.Repository, ref_pattern: str, msg_pattern: str):
+        super().__init__(repo)
+        self.ref_exp = re.compile(ref_pattern)
+        self.msg_exp = re.compile(msg_pattern)
+
+    def run(self, ref_name: str, old_oid: str, new_oid: str) -> HookResult:
+        if self.ref_exp.match(ref_name):
+            commit = self.repo.revparse_single(new_oid).peel(pygit2.Commit)
+            if not self.msg_exp.match(commit.message):
+                stderr.write(f"Commit message does not match pattern: {self.msg_exp.pattern}\n")
+                return HookResult.FAILURE
+        return HookResult.SUCCESS
