Maintaining Shadow Branches for GitHub PRs: A Solution to Force-Push Pain Points
#DevOps

Maintaining Shadow Branches for GitHub PRs: A Solution to Force-Push Pain Points

Tech Essays Reporter
3 min read

A new tool called pr-shadow addresses the workflow friction caused by GitHub's branch-centric pull request model by maintaining a separate, never-force-pushed branch that preserves clean diffs and comment history, even when developers rebase or amend their local work.

The GitHub pull request interface, while revolutionary for collaboration, imposes a workflow that can become cumbersome for projects with high commit velocity. The fundamental issue lies in GitHub's tight coupling of a pull request to a specific branch. When a developer rebases their feature branch against the latest main branch and force-pushes the result, GitHub's UI displays the force push and the diff between the old and new branch heads. For a repository like LLVM, which receives over 100 commits daily, this diff becomes a noisy amalgamation of the developer's changes and all intervening upstream commits, rendering the comparison functionally useless for reviewers. Inline comments attached to specific lines can become "outdated" or misplaced, and any references to the PR in commit messages generate duplicate links on the referenced issue or PR page, creating clutter.

These pain points lead some teams to adopt less flexible workflows, discouraging rebases in favor of simply appending new commits. While this preserves a clean PR history, it forces developers to work against an increasingly stale base, leading to painful rebuilds and merge conflicts when finally integrating the feature. For large-scale projects, avoiding rebases is often impractical; changes frequently interact with nearby lines, and rebasing is the primary method to discover and resolve these conflicts before integration.

The pr-shadow tool, developed by MaskRay, offers a solution by decoupling the developer's local workflow from the public-facing PR branch. It maintains a separate, dedicated branch for the PR (e.g., pr/feature) that is never force-pushed. Developers work freely on their local feature branch—rebase, amend, and squash as needed. When ready to update the PR, they use prs push to sync their changes. The tool uses git commit-tree to create a new commit on the PR branch that has the same tree (content) as the local commit but is parented to the previous PR branch head. This creates a linear history on the PR branch that reviewers can follow, with each push representing a distinct iteration of the patch.

When a rebase is detected (i.e., the merge base with the main branch has changed), pr-shadow creates the new PR commit as a merge commit, with the new merge-base as the second parent. GitHub displays these as "condensed" merges, which preserves a clean diff view for reviewers between the previous and current iteration of the patch, while the underlying commit history on the PR branch remains intact and readable. This approach elegantly solves the core problems: reviewers see only the relevant changes, comments remain anchored correctly, and the PR branch history is a clean, linear progression of the patch's evolution.

The tool's usage is straightforward. After initializing with prs init, which creates the shadow branch, pushes it, and opens a GitHub PR, developers work on their local branch. After committing or rebasing, they run prs push "Commit message" to update the shadow branch. The command prs desc can update the PR title and body from the local commit message. It also supports running GitHub CLI commands directly on the PR with prs gh view or prs gh checks. The tool supports both fork-based workflows (the default, pushing to a personal fork) and same-repo workflows (for branches like user/<name>/feature), and it auto-detects GitHub Enterprise hosts from the repository URL.

This concept is not entirely new; it draws inspiration from spr, which also implements a shadow branch concept. However, spr pushes user branches directly to the main repository, a practice discouraged for single PRs as it can clutter the upstream repository. pr-shadow avoids this by defaulting to pushes to the user's fork. While spr is designed for stacked pull requests, pr-shadow's approach may be more suitable for single PRs, with the developer potentially rebasing stacked PRs manually. The tool represents a thoughtful refinement of the GitHub PR workflow, addressing specific, real-world pain points for large, active open-source projects by preserving the flexibility of local Git operations while maintaining a clean and usable interface for collaboration.

For more details, see the pr-shadow GitHub repository.

Comments

Loading comments...