Back to Sapling

Hooks

eden/mononoke/docs/4.3-hooks.md

latest15.3 KB
Original Source

Hooks

This document explains Mononoke's repository hooks system. Hooks are configurable checks that execute during bookmark updates to enforce repository policies, validate commits, and prevent problematic changes from being accepted.

What Are Hooks?

Hooks are validation mechanisms that run when commits become ancestors of public bookmarks (branches). When a developer pushes commits or a bookmark is moved, Mononoke executes the configured hooks for that bookmark. If any hook rejects a commit, the entire push operation fails.

Hooks serve as the enforcement point for repository policies. They can validate commit messages, check file contents, enforce naming conventions, limit file sizes, verify permissions, and implement custom business logic. Unlike client-side hooks that can be bypassed, Mononoke's server-side hooks execute on every push.

Hooks vs. Pushrebase Hooks

Important: This document describes Mononoke's repository Hooks system (bookmark hooks, changeset hooks, and file hooks). There is a separate system called Pushrebase hooks that serves a different purpose. While the naming is similar, these are distinct systems:

  • Hooks (this document) - Policy enforcement hooks that validate commits during bookmark movements. They run before the bookmark is updated and can reject pushes that violate repository policies.
  • Pushrebase hooks (documented in Pushrebase) - Specialized hooks that run during the server-side rebase computation to add metadata like commit tracking information or GlobalRev numbers.

Both systems run during pushrebase operations, but they serve different purposes and execute at different points in the process.

Hook Types

Mononoke supports three types of hooks, each operating at a different granularity:

Bookmark Hooks

Bookmark hooks execute once per bookmark movement. They have access to the bookmark being updated and the target changeset. Bookmark hooks typically validate bookmark-specific policies such as ensuring certain bookmarks are only moved by authorized services or verifying that tags follow specific naming conventions.

The BookmarkHook trait is defined in repo_attributes/hook_manager/hook_manager/src/lib.rs.

Changeset Hooks

Changeset hooks execute once per changeset being pushed. They have access to the full changeset metadata, including author information, commit message, and the set of file changes. Changeset hooks typically validate commit message format, check for merge commits when linear history is required, verify author email domains, or enforce commit size limits.

The ChangesetHook trait is defined in repo_attributes/hook_manager/hook_manager/src/lib.rs.

File Hooks

File hooks execute once per file change (addition, modification, or deletion). They receive the file path and optionally the file content. File hooks typically enforce filename restrictions, check file content for prohibited patterns, limit individual file sizes, validate file permissions, or check for specific file types.

The FileHook trait is defined in repo_attributes/hook_manager/hook_manager/src/lib.rs.

Hook Manager Facet

The HookManager facet (repo_attributes/hook_manager/) manages hook registration, configuration, and execution. Like other repository facets, functions that need to run hooks declare their dependency on the hook manager through trait bounds.

The hook manager maintains:

  • Registered hooks and their configurations
  • Bookmark-to-hook mappings (which hooks run for which bookmarks)
  • Permission checkers for hook bypass authorization
  • Scuba logging for hook execution metrics

Hook execution is integrated into bookmark movement operations in repo_attributes/bookmarks/bookmarks_movement/. When a bookmark is moved (during a push, pushrebase, or other operation), the bookmark movement code invokes the hook manager to run all applicable hooks.

Hook Configuration

Hooks are configured per-repository in the repository's metadata configuration. Configuration includes:

Hook Registration Each hook has a name and a type (bookmark, changeset, or file). The hook implementation is specified, along with any hook-specific configuration options.

Bookmark Assignment Hooks are associated with bookmarks or bookmark patterns. A hook can be assigned to:

  • Specific bookmarks by name
  • Bookmarks matching a regular expression
  • Bookmarks not matching a regular expression (inverse regex)

This allows different branches to have different validation requirements. Production branches might have stricter hooks than development branches.

Hook Options Hooks can accept configuration parameters in JSON format or through typed configuration maps (strings, integers, lists). For example, a file size limit hook would be configured with the maximum allowed size, while a filename pattern hook would be configured with the patterns to allow or deny.

Log-Only Mode Hooks can be configured in log-only mode. In this mode, hook rejections are logged but do not prevent the push from succeeding. This is useful for testing new hooks before enforcing them or for gathering metrics on policy violations without disrupting developer workflows.

Bypass Settings Hooks can be configured with bypass conditions. For example, hooks might be bypassed for pushes authored by specific services rather than users, or for cross-repository sync operations.

The HookConfig struct in metaconfig/types/src/lib.rs defines the configuration structure.

Hook Execution

When a bookmark is updated, the hook execution process follows these steps:

Hook Selection The hook manager determines which hooks apply to the bookmark being moved. This includes hooks explicitly assigned to the bookmark and hooks matching the bookmark through regular expressions.

Changeset and File Enumeration For changeset and file hooks, the system identifies which changesets are new (not already ancestors of other public bookmarks). For each changeset, file changes are enumerated.

Hook Invocation Hooks are invoked asynchronously. Multiple hooks can run concurrently. Each hook receives:

  • Context (containing permissions, logging, tracing)
  • Repository access (through the HookRepo interface)
  • Changeset or file data
  • Metadata about the push source and author

Result Collection Each hook returns either acceptance or rejection. Rejections include a short description (for grouping similar failures) and a long description (for presenting to the user with guidance on fixing the issue).

Decision If any hook rejects, the entire bookmark movement fails. All rejections are collected and returned to the client. If all hooks accept (or are in log-only mode), the bookmark movement proceeds.

Hook execution is logged to Scuba for monitoring and analysis. Metrics include execution time, success/failure status, and any rejection messages.

Built-in Hook Implementations

Mononoke includes numerous hook implementations in features/hooks/src/implementations/. These provide common validation patterns:

File Validation

  • deny_files - Blocks files matching specific path patterns
  • block_files - Prevents specific files from being modified
  • block_content_pattern - Blocks file content matching specific patterns
  • no_bad_filenames - Rejects filenames with problematic characters
  • no_questionable_filenames - Validates filenames to prevent questionable characters
  • no_windows_filenames - Prevents filenames invalid on Windows
  • no_insecure_filenames - Blocks potentially dangerous filenames
  • block_invalid_symlinks - Validates symbolic link targets

Size Limits

  • limit_filesize - Enforces maximum individual file size
  • limit_commit_size - Limits total size of changes in a commit
  • limit_directory_size - Limits total size of a directory
  • limit_path_length - Enforces maximum path length
  • limit_subtree_op_size - Limits the size of subtree operations based on file count and path depth

Commit Message Validation

  • require_commit_message_pattern - Requires commit messages to match a pattern
  • block_commit_message_pattern - Rejects commit messages matching a pattern
  • limit_commit_message_length - Enforces commit message length limits

Commit Structure

  • block_merge_commits - Prevents merge commits (enforces linear history)
  • block_unclean_merge_commits - Allows only clean merges
  • block_empty_commit - Rejects empty commits

Repository Structure

  • block_accidental_new_bookmark_creation - Prevents unintended bookmark creation
  • block_new_bookmark_creations_by_name - Restricts bookmark creation by name pattern
  • limit_submodule_edits - Restricts changes to Git submodules

Binary and Executable Files

  • no_executable_binaries - Prevents executable binary files

Git-Specific

  • block_unannotated_tags - Requires annotated tags
  • limit_tag_updates - Restricts tag modifications

LFS

  • missing_lfsconfig - Validates Git LFS configuration

Testing and Debugging

  • always_fail_changeset - Always rejects commits (used for testing hook execution)

Additional implementations exist in features/hooks/src/facebook/implementations/ for Facebook-internal policies.

Hook Bypass

Under certain conditions, hooks can be bypassed:

Administrative Bypass Users who are members of the admin group or have the write_no_hooks permission can set the BYPASS_ALL_HOOKS pushvar to skip hook execution. This is used for emergency fixes or automated processes that need to bypass validation. Bypass usage is logged to Scuba for auditing.

Service Pushes Hooks can distinguish between pushes authored by users and pushes authored by services. Many hooks skip execution for service pushes, as service-originated commits are considered trusted. Hooks that still need to run for services (such as data integrity validators) can check the PushAuthoredBy parameter.

Cross-Repository Sync Hooks can distinguish between commits pushed directly to a repository and commits synchronized from another repository (push-redirected commits). Hooks may skip execution for synchronized commits, as they were already validated in the source repository.

The bypass logic is implemented in repo_attributes/bookmarks/bookmarks_movement/src/hook_running.rs.

Hook Development

New hooks are implemented by:

  1. Creating a struct that implements one of the hook traits (BookmarkHook, ChangesetHook, or FileHook)
  2. Implementing the run method to perform validation and return HookExecution::Accepted or HookExecution::Rejected
  3. Registering the hook with the hook manager during repository initialization
  4. Configuring the hook in repository metadata

Hook implementations receive a HookRepo interface providing access to repository data without requiring all repository facets. This interface is defined in repo_attributes/hook_manager/hook_manager/src/repo.rs and provides methods for fetching file contents, listing changesets, and accessing other data needed for validation.

Hooks should be stateless—all necessary state should be passed through configuration. This allows hook configuration to be changed without code deployment.

Integration with Repository Operations

Hooks are integrated into several repository operations:

Pushrebase When commits are landed via pushrebase (features/pushrebase/), hooks run before the server-side rebase is computed. Hooks validate the original pushed commits, and if they reject, the pushrebase is aborted before any rebasing occurs. If hooks pass, the server-side rebase proceeds, and then pushrebase hooks (a separate system) add metadata to the rebased commits before the bookmark is moved.

Bookmark Movement Direct bookmark updates (in repo_attributes/bookmarks/bookmarks_movement/) run hooks after validating other constraints (permissions, restrictions) but before updating the bookmark. This ensures all validations occur before the repository state changes.

Landing Service The land service (servers/land_service/) coordinates hook execution for landing operations, serializing execution when necessary to prevent conflicts.

Hooks execute within a transaction boundary—if they reject, no repository state is modified.

Observability

Hook execution is instrumented for monitoring and debugging:

Scuba Logging Each hook execution is logged with:

  • Hook name and type
  • Repository and bookmark
  • Changeset hash
  • Execution time
  • Success or rejection status
  • Rejection details

Bypassed Commits When hooks are bypassed (via admin override or configuration), the bypassed commits are logged separately to a Scuba table for security auditing.

Performance Metrics Hook execution time is tracked to identify slow hooks. Hooks that significantly impact push latency can be optimized or moved to asynchronous validation.

Relationship to Other Features

Hooks interact with several Mononoke features:

Pushrebase Hooks validate commits during pushrebase operations, but they run before the server-side rebase occurs. When a push triggers pushrebase, hooks execute on the original pushed commits before rebasing happens. If any hook rejects a commit, the pushrebase is aborted and the bookmark remains unchanged. This validation-before-rebase approach ensures that policy violations are caught early in the process.

After the regular hooks pass and the rebase computation completes, the separate pushrebase hooks system (documented in Pushrebase) runs to add metadata to the rebased commits.

Cross-Repository Sync Hooks can be configured to skip execution for commits synchronized from other repositories, avoiding duplicate validation when commits flow through multiple repositories.

Derived Data Hooks execute on the write path and do not wait for derived data. They operate on Bonsai changesets and file content, which are available immediately. If a hook needs indexed data, it must either compute it inline or rely on pre-existing derived data.

Permissions Hook bypass requires specific permissions (admin group membership or write_no_hooks action). The hook manager integrates with the permission system to verify bypass authorization.

Summary

Hooks provide server-side policy enforcement in Mononoke. The hook system:

Three Hook Types - Bookmark, changeset, and file hooks operate at different granularities.

Configurable Execution - Hooks are assigned to bookmarks or bookmark patterns, allowing different validation for different branches.

Rejection with Feedback - Hooks that reject provide descriptions to help developers fix issues.

Bypass Mechanisms - Administrative bypass and service push detection allow flexibility when needed.

Integrated Execution - Hooks execute during bookmark movement operations, running after other validations but before repository state changes.

Built-in Implementations - Mononoke includes many standard hooks for common validation patterns.

Hooks are implemented in features/hooks/ and managed by the HookManager facet in repo_attributes/hook_manager/. Integration with bookmark movement is in repo_attributes/bookmarks/bookmarks_movement/.