Skip to content
LogoLogo

Open a merge request

Merge requests let you propose changes from a fork descendant back to an upstream project without write access on the target.

Build the prerequisites interactively

Use the live docs flows to produce the source state that a merge request depends on:

Fork a project
Create a fork descendant anchored to a source commit.
Contribute to a project
Push commits and update refs on the fork before proposing changes upstream.

The merge-request flow itself remains protocol-documented here while the interactive fork and ref-update steps above give you the concrete source state needed for MERGE_REQUEST_ADD.

How it works

The full lifecycle is:

  1. fork the upstream project with FORK
  2. push commits and update refs on your fork
  3. open a merge request with MERGE_REQUEST_ADD, targeting the upstream project
  4. the upstream maintainer reviews the proposal
  5. the maintainer merges by submitting COMMIT_BUNDLE and REF_UPDATE on the target project, then closes the request with MERGE_REQUEST_REMOVE

You can also withdraw your own merge request at any time with MERGE_REQUEST_REMOVE.

The protocol records the proposal — merge resolution is application-layer. The protocol does not define a "merged" or "rejected" state.

Message body

MERGE_REQUEST_ADD

FieldConstraint
project_idTarget (upstream) project ID, 32 bytes
source_project_idSource fork-descendant project ID, 32 bytes
source_refBranch or ref name in the fork, 1–254 bytes, no null bytes
source_commit_hashHead commit of proposed changes, 32 bytes
target_refSuggested target ref in upstream, 1–254 bytes, no null bytes
titleShort description, 1–200 UTF-8 bytes

source_project_id must be in the target project's retained fork lineage within MAX_FORK_LINEAGE_DEPTH = 256 hops. source_project_id must not equal project_id — you cannot open a merge request from a project to itself.

source_ref must resolve exactly to source_commit_hash at execution time — the hash is authoritative, the ref name is advisory for humans.

target_ref is advisory only and does not constrain the maintainer performing the merge.

The merge request identity is content-addressed: request_id = Message.hash. Two messages with different timestamps produce different request_id values, so the same requester can open multiple merge requests against the same project.

MERGE_REQUEST_REMOVE

FieldConstraint
project_idTarget project ID, 32 bytes
request_idContent-addressed MR ID (original MERGE_REQUEST_ADD message hash), 32 bytes

Authorization

Opening a merge request

MERGE_REQUEST_ADD requires SIGNING scope. The target project must be Active (not Archived or Removed). The requester does not need any permission on the target project if it is public. Private targets require READ+ access.

The source project must not be Removed, and the requester must be the source owner or have READ+ access on a private source project.

Closing a merge request

MERGE_REQUEST_REMOVE has dual authorization — the first message type with this pattern:

  • Requester withdrawal — the original requester can close without any target-project membership
  • Maintainer closure — the target project owner or any collaborator with WRITE+ permission can close

The target project must not be Removed. Closure is allowed on Archived projects so maintainers can clean up.

V2 semantics

  • merge requests are a tombstone-backed 2P set — same pattern as collaborators, links, and reactions
  • merge request state is stored under the target project's namespace at prefix 0x1B, with a requester reverse index at 0x1C
  • ProjectState.merge_request_count tracks the number of active merge requests per project
  • the target project owner funds merge-request capacity: 20 + storage_units × 20 per project
  • both active entries and tombstones count toward quota; oldest entries are pruned first
  • closed merge requests return NOT_FOUND — closure attribution is available only from finalized MERGE_REQUEST_REMOVE message history, not from canonical state
  • MERGE_REQUEST_ADD and MERGE_REQUEST_REMOVE are storage-sensitive message types using tombstone-backed 2P-set semantics: remove wins on add/remove timestamp ties, and equal-timestamp add/add remains last-inclusion-wins

Learn more

Next steps