Skip to content

Instantly share code, notes, and snippets.

@NagariaHussain
Created February 17, 2026 01:52
Show Gist options
  • Select an option

  • Save NagariaHussain/ad099bfddf6856d7eea46a9a1ed537b8 to your computer and use it in GitHub Desktop.

Select an option

Save NagariaHussain/ad099bfddf6856d7eea46a9a1ed537b8 to your computer and use it in GitHub Desktop.
Wiki Contributions: Drafts & Merges Flow Guide

Wiki Contributions: Drafts & Merges Guide

A guide for junior engineers on how the contribution system works.


The Big Picture

There are 3 main doctypes that power the contribution system:

Doctype Purpose
Wiki Document The actual live wiki page (what visitors see)
Wiki Change Request (CR) A "pull request" — a container for someone's proposed edits
Wiki Revision A snapshot of the entire wiki tree at a point in time

Think of it like Git: a Wiki Revision is a commit, a Wiki Change Request is a branch/PR, and Wiki Documents are the working tree.


1. How a Draft is Created

When a user clicks "Edit" on a wiki page:

sequenceDiagram
    participant U as User
    participant API as Backend API
    participant CR as Wiki Change Request
    participant Rev as Wiki Revision

    U->>API: Clicks "Edit Page"
    API->>API: get_or_create_draft_change_request()

    alt User already has a Draft CR
        API->>CR: Return existing Draft CR
    else No existing draft
        API->>Rev: Create OVERLAY revision<br/>(empty, points to main_revision)
        API->>CR: Create new CR<br/>base=main_revision, head=overlay
        CR-->>U: Draft CR ready
    end
Loading

Key concept — Overlay Revisions:

graph TD
    subgraph "Before Refactor (slow)"
        A1[main_revision<br/>500 items] -->|CLONE ALL 500| B1[head_revision<br/>500 items copied]
    end

    subgraph "After Refactor — Phase 4 (fast)"
        A2[main_revision<br/>500 items] -->|points to| B2[head_revision OVERLAY<br/>0 items initially]
        B2 -->|inherits all items<br/>from parent| A2
    end

    style B2 fill:#d4edda,stroke:#28a745
    style B1 fill:#f8d7da,stroke:#dc3545
Loading

An overlay revision starts empty. It inherits everything from its parent (main_revision). Only when you actually edit a page does that page get copied into the overlay — this is called copy-on-write.


2. Editing Pages Inside a Draft

When a user edits, creates, or deletes a page inside their CR:

sequenceDiagram
    participant U as User
    participant API as Backend
    participant Overlay as Head Revision (Overlay)
    participant Base as Base Revision (Parent)

    U->>API: Save page edit
    API->>Overlay: ensure_overlay_item(doc_key)

    alt Item NOT in overlay yet
        Overlay->>Base: Copy item from parent into overlay
    end

    API->>Overlay: Update the item (new content, title, etc.)
    API->>Overlay: mark_hashes_stale()
    API-->>U: Page saved in draft
Loading
graph LR
    subgraph "Base Revision (500 pages)"
        P1[Page A]
        P2[Page B]
        P3[Page C]
        P4[Page D]
        P5[...]
    end

    subgraph "Overlay (only edits)"
        O2[Page B modified]
        O5[Page E new]
    end

    Overlay -->|inherits unchanged pages| P1
    Overlay -->|inherits unchanged pages| P3
    Overlay -->|inherits unchanged pages| P4

    style O2 fill:#fff3cd,stroke:#ffc107
    style O5 fill:#d4edda,stroke:#28a745
Loading

The overlay only stores what changed. Reading the full tree means: "take base items, then apply overlay items on top."


3. Content Storage (Deduplication)

Content is stored in Wiki Content Blob — deduplicated by SHA-256 hash:

graph LR
    RevA_Item1[Revision A: Page X] -->|content_blob| Blob1["Blob abc123<br/>(SHA-256 of content)"]
    RevB_Item1[Revision B: Page X] -->|content_blob| Blob1
    RevC_Item1[Revision C: Page X<br/>EDITED] -->|content_blob| Blob2["Blob def456<br/>(new content hash)"]

    style Blob1 fill:#e2e3f1,stroke:#6c757d
    style Blob2 fill:#d4edda,stroke:#28a745
Loading

If two revisions have the same content for a page, they share the same blob. Saves tons of storage.


4. The Review Flow

stateDiagram-v2
    [*] --> Draft: User creates CR

    Draft --> InReview: request_review(reviewers)

    InReview --> Approved: All reviewers approve
    InReview --> ChangesRequested: Any reviewer requests changes

    ChangesRequested --> InReview: Author edits & resubmits

    Approved --> Merged: Wiki Manager merges

    Draft --> Archived: Stale/abandoned

    Merged --> [*]
    Archived --> [*]
Loading

5. The Merge Flow

When a Wiki Manager clicks "Merge", the system decides between two paths:

flowchart TD
    Start["merge_change_request(cr)"] --> Check{"Has main_revision<br/>advanced since CR<br/>was created?"}

    Check -->|"No — base == main<br/>(no concurrent edits)"| FF["FAST-FORWARD MERGE"]
    Check -->|"Yes — base != main<br/>(someone else merged)"| TW["THREE-WAY MERGE"]

    FF --> Delta["Apply ONLY changed docs<br/>(delta-only, Phase 5)"]

    TW --> Conflict{"Conflicts?"}
    Conflict -->|No| AutoMerge["Auto-merge disjoint changes"]
    Conflict -->|Yes| Block["Create Wiki Merge Conflict<br/>records, block merge"]

    Delta --> Done["CR status = Merged<br/>space.main_revision updated<br/>Live wiki tree updated"]
    AutoMerge --> Done
    Block --> Resolve["User resolves conflicts"] --> TW

    style FF fill:#d4edda,stroke:#28a745
    style TW fill:#fff3cd,stroke:#ffc107
    style Block fill:#f8d7da,stroke:#dc3545
    style Done fill:#d4edda,stroke:#28a745
Loading

Fast-Forward Merge (simple case)

Nobody else merged anything while this CR was open, so we just apply the changes directly:

gitGraph
    commit id: "main_revision"
    branch change-request
    commit id: "edit Page B"
    commit id: "add Page E"
    checkout main
    merge change-request id: "fast-forward merge"
Loading

The Phase 5 optimization here: instead of re-saving all 500 pages, it computes the delta (what actually changed) and only touches those docs. 1 page changed = 1 page saved.

Three-Way Merge (concurrent edits)

Someone else's CR was merged while this one was still open:

gitGraph
    commit id: "base_revision"
    branch cr-author
    commit id: "edit Page B"
    checkout main
    commit id: "someone else merged (Page D)"
    checkout cr-author
    commit id: "add Page E"
    checkout main
    merge cr-author id: "three-way merge"
Loading

The algorithm compares three versions:

  • Base: state when CR was created
  • Ours: current main (includes other people's merges)
  • Theirs: the CR author's changes
flowchart LR
    Base["BASE<br/>Original state"] --> Ours["OURS<br/>Current main"]
    Base --> Theirs["THEIRS<br/>CR's changes"]
    Ours --> Merge["Merge"]
    Theirs --> Merge
    Merge --> Result["Merged revision"]

    style Base fill:#e2e3f1
    style Ours fill:#cce5ff
    style Theirs fill:#fff3cd
    style Result fill:#d4edda
Loading

If both sides edited different pages — auto-merge works fine. If both sides edited the same page on different lines — auto-merge tries line-by-line. If both sides edited the same lines — conflict, needs manual resolution.


6. After Merge — What Actually Happens to Live Wiki

sequenceDiagram
    participant Merge as Merge Logic
    participant Rev as New Merge Revision
    participant Space as Wiki Space
    participant Docs as Wiki Documents (live)

    Merge->>Merge: Compute delta (changed doc_keys only)

    loop For each changed doc
        alt Content-only change
            Merge->>Docs: frappe.db.set_value() (skip validation, fast)
        else Structural change (parent, slug)
            Merge->>Docs: Full doc.save() (with validation)
        else New page
            Merge->>Docs: Create new Wiki Document
        else Deleted page
            Merge->>Docs: Mark is_deleted=1
        end
    end

    Merge->>Rev: Save merge revision
    Merge->>Space: space.main_revision = merge_revision
Loading

Complete End-to-End Summary

flowchart TD
    A["User clicks Edit"] --> B["Get/create Draft CR<br/>(overlay revision, O(1))"]
    B --> C["User edits pages<br/>(copy-on-write into overlay)"]
    C --> D["Submit for review"]
    D --> E["Reviewers review"]
    E -->|Changes requested| C
    E -->|Approved| F["Wiki Manager merges"]
    F --> G{"Concurrent changes?"}
    G -->|No| H["Fast-forward<br/>(delta-only apply)"]
    G -->|Yes| I["Three-way merge"]
    I -->|Conflict| J["Resolve conflicts"] --> I
    I -->|Clean| K["Apply merged changes"]
    H --> K
    K --> L["Live wiki updated!<br/>main_revision advanced"]

    style A fill:#e2e3f1
    style H fill:#d4edda
    style J fill:#f8d7da
    style L fill:#d4edda
Loading

TL;DR for Junior Engineers

  1. Drafts are cheap — overlay revisions start empty and only store what you change (copy-on-write)
  2. Content is deduplicated — same content = same blob, shared across revisions
  3. Merges are smart — fast-forward when possible, three-way merge when needed
  4. Only deltas are applied — merging 1 page change in a 500-page wiki only touches 1 page
  5. Conflicts are explicit — if two people edit the same lines, a Wiki Merge Conflict record is created and must be resolved manually
  6. The old Wiki Page Patch system is deprecated — everything now goes through Change Requests + Revisions

Key Files to Explore

File What it does
wiki/wiki/doctype/wiki_change_request/wiki_change_request.py CR lifecycle, review actions
wiki/wiki/doctype/wiki_revision/wiki_revision.py Revision creation, overlay logic, effective item maps
wiki/wiki/doctype/wiki_document/wiki_document.py Live wiki page, desk-edit sync to revisions
wiki/wiki/merge.py Merge logic (fast-forward, three-way, conflict detection)
wiki/wiki/doctype/wiki_space/wiki_space.py Space management, main_revision tracking
wiki/wiki/doctype/wiki_content_blob/wiki_content_blob.py Content deduplication via SHA-256
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment