review-backport

star 10.3k

Review YugabyteDB backport diffs by comparing them against their original commits/diffs. Use when the user provides a Phorge backport diff ID (e.g. D51405) and wants to verify the backport is correct, or when reviewing backport revisions.

yugabyte By yugabyte schedule Updated 5/12/2026

name: review-backport description: >- Review YugabyteDB backport diffs by comparing them against their original commits/diffs. Use when the user provides a Phorge backport diff ID (e.g. D51405) and wants to verify the backport is correct, or when reviewing backport revisions.

Review Backport

Review a YugabyteDB backport diff by comparing it to the original commit/diff and identifying any differences.

For the review policy (what counts as a backport, what to flag vs. ignore, byte-identical-hunk handling, and the ## Merge conflicts convention), see the Backport PRs section of @../../../.gemini/styleguide.md. This skill covers the mechanics of fetching and comparing Phorge diffs and tracing unexplained code back to its origin on master.

Prerequisites

This skill requires the Phorge MCP server. If the user-phorge MCP server is not available, instruct the user to set it up:

  1. Generate a Conduit API token. In Phorge, click on your profile in the top right → Settings → Conduit API Tokens → Generate Token.

  2. Add the Phorge MCP server to Cursor. Create or edit ~/.cursor/mcp.json with these contents, replacing the API token with the one you just generated:

    {
      "mcpServers": {
        "phorge": {
          "command": "npx",
          "args": ["@freelancercom/phabricator-mcp@latest"],
          "env": {
            "PHABRICATOR_URI": "https://phorge.dev.yugabyte.com/",
            "PHABRICATOR_API_TOKEN": "api-XXXXXXXXXXXXXXXX"
          }
        }
      }
    }
    
  3. Restart AI agent to pick up the new MCP server configuration.

Workflow

Step 1: Fetch the backport revision

Use the user-phorge MCP server to get the backport revision details:

Tool: phabricator_revision_search
Args: { "constraints": { "ids": [<numeric_id>] }, "limit": 1 }

The numeric ID is the diff number without the "D" prefix (e.g., D51405 → 51405).

Confirm the title starts with [BACKPORT <version>]. Save the revision's phid and summary.

Optionally, verify the version in the title corresponds to a real branch. The diff metadata includes a refs attachment with the target branch. Cross-check that the version string (e.g., 2024.1.3) matches the branch name (e.g., origin/2024.1.3). If they don't match, flag it — the backport may be targeting the wrong branch.

Step 2: Parse the summary for references

Extract from the summary field:

  1. Original commit/diff: Look for a line matching: Original commit: <commit_hash> / D<number> This gives both the git commit hash and the original Phorge revision ID.

  2. Additional diffs applied: Look for references to other diffs in the summary, such as:

    • "Apply small diff changes from D51357 as well."
    • "Also includes changes from D12345."
    • Any mention of D<number> that is not the original diff.

    Collect all additionally-referenced revision IDs.

If parsing fails, ask the user to provide the original diff ID.

Step 3: Fetch the original revision

Tool: phabricator_revision_search
Args: { "constraints": { "ids": [<original_numeric_id>] }, "limit": 1 }

Save the original revision's phid and note its title and summary.

Step 4: Get the latest diff ID for each revision

For both the backport and original revisions, get the most recent diff snapshot:

Tool: phabricator_diff_search
Args: {
  "constraints": { "revisionPHIDs": ["<revision_phid>"] },
  "order": "newest",
  "limit": 1,
  "attachments": { "commits": true }
}

Record the numeric id field from the result — this is the diff ID needed for raw diff retrieval.

Step 5: Get raw diffs

Fetch the raw patch content for both diffs:

Tool: phabricator_diff_raw
Args: { "diffID": <diff_id> }

Do this for:

  • The backport's latest diff
  • The original's latest diff
  • (Optional) Any additionally-referenced diffs, if their changes need to be verified

Step 6: Fetch additional referenced diffs (if any)

For each additionally-referenced diff ID found in Step 2:

  1. Fetch the revision details via phabricator_revision_search
  2. Get its latest diff via phabricator_diff_search
  3. Get its raw diff via phabricator_diff_raw

These changes are expected to appear in the backport but not in the original.

Step 7: Compare and review

Compare the backport diff against the original diff. Categorize all differences:

7a. File-level comparison

List files that appear in one diff but not the other.

7b. Hunk-level comparison

For files present in both diffs, compare the actual changes (added/removed lines). Ignore differences in:

  • Context line offsets (line numbers shift due to different branch bases)
  • Diff metadata headers (diff --git, index lines, ---/+++ paths)

Focus on differences in the actual +/- lines within hunks.

7c. Classify differences

For each difference found, classify it as:

  1. From an additional referenced diff — changes that match a diff mentioned in the summary (e.g., D51357). These are expected.
  2. Explained in the commit summary — the backport summary explicitly describes and justifies the difference (e.g., "removed function X because it doesn't exist on 2024.1", "adapted call to use older API"). These are expected and do not need to be flagged.
  3. Conflict resolution — changes that look like manual conflict resolution during cherry-pick/backport. Flag for review.
  4. Unexplained difference — changes that don't match any referenced diff, aren't explained in the summary, and don't look like conflict resolution. Flag as potentially problematic.

Step 8: Trace unexplained differences back to their origin on master

Skip this step for differences that are already classified as "explained in the commit summary" — those do not need origin tracing.

When the backport contains added code that is NOT in the original diff, NOT from any referenced additional diff, and NOT explained in the commit summary, it was most likely pulled in accidentally during the cherry-pick. This happens when the original commit on master has trailing context (functions defined nearby in the same file) that doesn't exist on the target release branch. Git's conflict resolution during cherry-pick can drag in these unrelated functions.

For each block of unexplained new code:

  1. Identify a distinctive function or symbol name from the unexplained code (e.g., YbNewSample, yb_maybe_test_fail_ddl).

  2. Verify it doesn't exist on the target branch:

    Tool: phabricator_repository_code_search
    Args: { "repository": "<repo_phid>", "query": "<function_name>", "branch": "<target_branch>" }
    

    If it returns empty, the code is not part of the target branch and should not be in the backport.

  3. Confirm it exists on master before the original commit:

    Tool: phabricator_repository_code_search
    Args: { "repository": "<repo_phid>", "query": "<function_name>", "commit": "<original_commit_hash>~1" }
    

    If found, this confirms the code pre-existed on master and was adjacent to the original change.

  4. Find the commit that introduced it on master by binary-searching the file history:

    Tool: phabricator_repository_file_history
    Args: { "path": "<file_path>", "repository": "<repo_phid>", "commit": "<original_commit_hash>~1", "limit": 30 }
    

    Then use phabricator_repository_code_search at progressively older commits until you find the boundary where the function first appeared. The commit where it appears but its parent doesn't have it is the introducing commit.

  5. Report the origin: Include the introducing commit hash, its Phorge diff ID, title, and author. Explain that these changes are unrelated to the backport and were pulled in during cherry-pick conflict resolution. They should be removed from the backport.

Step 9: Produce the review report

Output a structured review with these sections:

Backport Summary

  • Backport: D</li> <li>Original: D<id> — <title> (commit: <hash>)</li> <li>Target branch: <branch from diff refs></li> <li>Additional diffs included: D<id>, D<id>, ... (or "None")</li> </ul> <p><strong>File Comparison</strong></p> <ul> <li>Files in both diffs: list</li> <li>Files only in backport: list (with explanation)</li> <li>Files only in original: list (with explanation — this might indicate a problem)</li> </ul> <p><strong>Change Comparison</strong></p> <ul> <li>Identical changes: list of files with no meaningful diff differences</li> <li>Expected differences (from additional diffs): describe which changes come from which referenced diff</li> <li>Differences requiring review: for each difference, quote the relevant <code>+</code>/<code>-</code> lines from both the original and backport diffs so the reviewer can see exactly what changed. Do not just describe the difference in prose — always include the actual code context.</li> </ul> <p><strong>Unrelated Code Pulled In From Master</strong> (if any)</p> <ul> <li>For each block of unexplained code: the file, the functions/symbols involved, the originating commit on master (hash, diff ID, title, author), and confirmation that this code does not exist on the target branch</li> <li>Recommendation to remove these from the backport</li> </ul> <p><strong>Assessment</strong></p> <ul> <li>Overall verdict: Clean backport / Minor differences / Needs attention</li> <li>Specific concerns, if any</li> </ul> <h2>Notes</h2> <ul> <li>The <code>phabricator_diff_raw</code> tool returns the full unified diff. For large diffs, focus comparison on the <code>+</code>/<code>-</code> lines rather than context.</li> <li>Line number offsets in hunks (<code>@@ -a,b +c,d @@</code>) will almost always differ between the original and backport — this is normal and expected.</li> <li>Some backports are cherry-picks that apply cleanly; others require conflict resolution. Both are valid.</li> <li>When the backport summary mentions additional diffs, always fetch and account for those changes before flagging differences.</li> <li>Unexplained code in a backport is almost always from unrelated commits on master that were adjacent to the original change in the same file. During cherry-pick, git's conflict resolution can pull in these neighboring functions as context. Always trace such code back to its origin on master rather than just flagging it as "unexplained."</li> </ul> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/yugabyte/yugabyte-db --skill review-backport </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>10,349</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>1,272</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Occupations</span> <div class="flex flex-wrap gap-1.5" data-astro-cid-7zzsworf> <a href="/?occupation=software-developers" class="px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary hover:bg-primary/20 text-[11px] font-semibold transition-all hover:scale-102" data-astro-cid-7zzsworf> Software Developers </a> </div> </div> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/17074854?v=4" alt="yugabyte" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>yugabyte</span> <a href="/?creator=yugabyte" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>