eden/mononoke/docs/4.1-pushrebase.md
This document explains pushrebase, a server-side rebasing mechanism that maintains linear commit history in high-throughput repositories. Pushrebase is a key feature used by both Sapling and Git clients when pushing commits to Mononoke.
Pushrebase is a server-side operation that automatically rebases commits during a push when the target bookmark has moved forward since the client's base commit. Rather than requiring clients to pull, rebase locally, and retry their push, the server performs the rebase operation automatically for conflict-free changes.
When a client pushes commits to a bookmark that has moved forward, Mononoke detects whether the changes can be safely rebased. If the pushed commits modify different files than the commits that moved the bookmark forward, the server rebases the pushed commits on top of the current bookmark position and updates the bookmark atomically.
The result is a linear commit history on the target bookmark without requiring coordination between clients or manual rebase operations.
In repositories with high commit rates and many concurrent developers, bookmark contention becomes a problem. Without pushrebase, when multiple developers attempt to push to the same bookmark:
This retry loop increases push latency and client-side complexity. Pushrebase addresses this by moving the rebase operation to the server, which can perform it atomically during the bookmark update. Clients pushing non-conflicting changes succeed on the first attempt.
From the client's perspective, pushrebase is transparent. A client pushes commits to a bookmark using standard Sapling push, or via the Git pushrebase tool provided by Mononoke. The client's base commit may be behind the current bookmark position on the server.
When Mononoke receives a push to a bookmark configured for pushrebase:
1. Identify the Rebased Set
The server identifies which commits need to be rebased:
2. Conflict Detection
The server checks whether the rebase can be performed automatically:
A conflict exists if:
3. Rebase Execution
If no conflicts are detected, the server performs the rebase:
4. Hook Execution
Before updating the bookmark, pushrebase runs configured hooks:
5. Bookmark Update
If hooks pass, the server updates the bookmark:
6. Response to Client
The server returns the rebased commits to the client:
If the bookmark moves during the pushrebase operation (due to another concurrent push), the server retries the rebase with the new bookmark position. After a configured number of retries (typically 100), the server fails the push and the client must retry.
Pushrebase only handles conflict-free rebases where no file-level merging is required. This is a conservative approach that ensures correctness - if file contents conflict, human intervention is required.
The conflict detection is performed at the path level, not the content level. Two commits that modify the same file are considered conflicting even if the changes are to different lines. This conservative approach avoids potential semantic conflicts and unexpected behavior.
If a push contains conflicts, the server rejects it with a conflict error. The client must:
Pushrebase behavior is controlled by repository configuration in metaconfig/:
Per-Repository Settings:
pushrebase.block_merges - Reject pushes containing merge commitspushrebase.casefolding_check - Check for case-folding conflicts (important for case-insensitive filesystems)pushrebase.rewritedates - Update commit timestamps during rebasepushrebase.recursion_limit - Maximum commit stack depthpushrebase.forbid_p2_root_rebases - Disallow rebasing certain stacks based on merge commitsPer-Bookmark Settings:
Pushrebase has a hook mechanism that allows additional optional operations to happen during pushrebase. This is separate from Mononoke's bookmark hook system (documented in 4.3-hooks.md) although bookmarks hooks are also run during pushrebase. Pushrebase hooks run during rebase computation and before the bookmark is updated, allowing:
Pushrebase hooks are defined in features/pushrebase/pushrebase_hook/ (the trait) and features/pushrebase/pushrebase_hooks/ (hook implementations). The get_pushrebase_hooks() function in features/pushrebase/pushrebase_hooks/ constructs the set of hooks to run based on repository configuration.
Pushrebase is implemented across several components:
Core Pushrebase (features/pushrebase/src/):
do_pushrebase_bonsai() - Main entry point for pushrebase operationBookmark Movement (repo_attributes/bookmarks/bookmarks_movement/):
pushrebase_onto.rs - High-level pushrebase operationPushrebase Client (features/pushrebase/client/):
local.rs - Direct in-process pushrebasehybrid.rs - Client that can use local or remote pushrebasefacebook/land_service.rs - Pushrebase via land service microservicePushrebase Hooks:
features/pushrebase/pushrebase_hook/ - Hook trait definitionsfeatures/pushrebase/pushrebase_hooks/ - Hook implementations and registryThe pushrebase operation is a feature (as described in the Architecture Overview) that composes multiple repository facets:
Bookmarks - Reading and updating bookmarksCommitGraph - Ancestry queries and graph traversalRepoBlobstore - Storing rebased changesetsHookManager - Running hooksRepoDerivedData - Triggering derived data computationSapling clients use pushrebase as the default push mode for most bookmarks:
Push Operation:
sl push --to master
If the local base is behind the server's master bookmark, the server automatically rebases the commits. The client receives the rebased commits and updates its local state.
Mutation Information: Sapling tracks commit rewrites (mutations) caused by pushrebase. When the server rebases commits, it returns obsmarkers that map the original commit hashes to the rebased hashes. This allows Sapling to understand that the local commits and server commits are related.
Commit Cloud Integration: Pushrebase does not currently integrate with Commit Cloud (Sapling's feature for sharing uncommitted work). When commits are pushrebased, the mutation information is not synchronized across a developer's machines, but rather each separate machine will discover that the commits have been landed. This is an opportunity for improvement.
Git clients can also use pushrebase when pushing to Mononoke:
Push with Pushrebase: Git clients push using the standard Git protocol. If the server is configured for pushrebase on the target branch, the push operation triggers a pushrebase.
Mapping to Git: When rebased commits are created:
bonsai_git_mappingDifferences from Sapling: Git does not have a native mutation tracking mechanism like Sapling's obsmarkers. Git clients receive the new commit hashes but do not have a built-in way to track the relationship between original and rebased commits.
Mononoke supports two primary push modes:
Fast-Forward Push:
Pushrebase:
The choice between these modes is configured per-bookmark in repository configuration.
The PushrebaseMutationMapping facet (in repo_attributes/pushrebase_mutation_mapping/) tracks the relationship between original commits and their rebased versions:
This mapping is populated by a pushrebase transaction hook and is accessible through the SCS API (Source Control Service) for programmatic queries.
Pushrebase operations are instrumented with metrics and logging:
Metrics:
pushrebase.critical_section_success_duration_us - Time spent in successful critical sectionpushrebase.critical_section_failure_duration_us - Time spent in failed critical sectionpushrebase.critical_section_retries_failed - Count of retry failurespushrebase.commits_rebased - Number of commits rebasedLogging:
Debugging: The admin tool provides commands for testing and debugging pushrebase:
admin commit pushrebase --help
Merge Commits:
Pushrebase can be configured to reject merge commits (block_merges configuration). Repositories that maintain strict linear history use this setting.
Large Stacks:
Rebasing very large commit stacks can be slow. The recursion_limit configuration prevents unbounded stack depth.
Concurrent Updates: If a bookmark is updated very frequently, pushrebase may exhaust retries. The client must retry the push operation.
Case Conflicts: On case-insensitive filesystems, pushrebase can detect potential case conflicts that might cause issues for some clients.
Hook Failures: If any hook fails during pushrebase, the entire operation is rolled back. The client receives the hook error message.
The pushrebase implementation is in features/pushrebase/ and its integration with bookmark movement is in repo_attributes/bookmarks/bookmarks_movement/src/pushrebase_onto.rs.