# Staqd /stakt/ — like "stacked" Stacked PR merge queue powered by GitHub Actions and PR comments. ## Quick Start Add a workflow file for each of the two events: `.github/workflows/staqd-auto-detect.yml` — runs on pull request events: ```yaml name: Staqd Auto Detect on: pull_request: types: [opened, edited, closed] permissions: pull-requests: write issues: write jobs: staqd: uses: siner308/staqd/.github/workflows/staqd-auto-detect.yml@v1 ``` `.github/workflows/staqd-command.yml` — runs on PR comment commands: ```yaml name: Staqd Command on: issue_comment: types: [created] permissions: contents: write pull-requests: write issues: write checks: read jobs: staqd: uses: siner308/staqd/.github/workflows/staqd-command.yml@v1 ``` Each workflow triggers only on its relevant event, keeping PR checks clean and focused. Deprecation: The single-file `staqd.yml` is deprecated. Use the two-file setup above. ### With GitHub App (recommended for CI auto-trigger) `.github/workflows/staqd-auto-detect.yml`: ```yaml name: Staqd Auto Detect on: pull_request: types: [opened, edited, closed] permissions: pull-requests: write issues: write jobs: staqd: uses: siner308/staqd/.github/workflows/staqd-auto-detect.yml@v1 with: app-id: ${{ vars.STAQD_APP_ID }} secrets: app-private-key: ${{ secrets.STAQD_APP_PRIVATE_KEY }} ``` `.github/workflows/staqd-command.yml`: ```yaml name: Staqd Command on: issue_comment: types: [created] permissions: contents: write pull-requests: write issues: write checks: read jobs: staqd: uses: siner308/staqd/.github/workflows/staqd-command.yml@v1 with: app-id: ${{ vars.STAQD_APP_ID }} secrets: app-private-key: ${{ secrets.STAQD_APP_PRIVATE_KEY }} ``` Why a GitHub App? `GITHUB_TOKEN` pushes don't trigger other workflows (GitHub security policy). If your CI needs to run after a restack force-push, use a GitHub App. ### Reusable Workflow Inputs `staqd-auto-detect.yml`: - `app-id`: GitHub App ID. Used with `app-private-key` to generate an installation token. (Optional) - `runs-on`: Runner label for all jobs. Default: `ubuntu-latest`. (Optional) `staqd-command.yml`: - `app-id`: GitHub App ID. Used with `app-private-key` to generate an installation token. (Optional) - `runs-on`: Runner label for all jobs. Default: `ubuntu-latest`. (Optional) - `timeout-minutes`: Timeout for command job. Default: `30`. (Optional) ### Reusable Workflow Secrets - `app-private-key`: GitHub App private key. Used with `app-id` input. (Optional) ## Commands Comment on a PR to trigger: - `stack merge` (`st merge`) — Auto-discover stack, merge this PR, restack child branches, and delete the merged branch - `stack merge-all` (`st merge-all`) — Auto-discover stack, merge the entire stack in order, deleting each branch after merge (all PRs must be approved) - `stack merge-all --force` (`st merge-all --force`) — Same as `merge-all` but skips the approval check - `stack restack` (`st restack`) — Restack entire stack recursively - `stack discover` (`st discover`) — Auto-discover stack tree from base branches and update metadata - `stack help` (`st help`) — Show usage All commands support the short alias `st` (e.g., `st merge` instead of `stack merge`). ### stack discover Auto-detect the stack tree by scanning open PRs' base branch relationships. No manual metadata setup needed. - When to use: After creating stacked PRs, or when you re-target a PR's base branch to form a new stack. - What it does: Recursively finds child PRs via base branch, updates metadata in each PR body, and reports if any children need restacking. ### stack restack Restack entire stack recursively — rebases all descendant branches in topological order. Use after pushing new commits to a parent PR. - When to use: After updating a parent PR with new commits, or when `st discover` shows warning indicators. - What it does: Auto-discovers the stack, then runs `git rebase --onto` for each descendant branch in topological order. Stops recursing into subtrees on conflict and posts resolution commands. ### stack merge Auto-discover the stack tree, merge this PR into its base branch, restack all child branches, and delete the merged branch. - When to use: When this PR is approved and ready to merge, but child PRs still need more work. - What it does: Runs discover to refresh stack metadata, squash-merges the PR, deletes the merged branch, rebases children onto the base branch (e.g., `main`), and updates each child's base branch reference. ### stack merge-all Auto-discover the stack tree, then merge the entire stack in DFS order. Each PR is rebased, merged, its branch deleted, then its children are processed. - When to use: When the entire stack is reviewed and approved — merge everything at once. - What it does: Runs discover to refresh stack metadata, checks approval status on all PRs, then sequentially rebases and merges each one, deleting each branch after merge. Retries up to 10 min per PR for CI to pass. The result table includes merge order — if a conflict occurs, the failure message shows which PRs to fix and in what order. - Options: Add `--force` to skip the approval check — otherwise identical behavior (e.g., `st merge-all --force`). ### stack help Show available commands and the current stack structure detected from metadata. ## Setting Up a Stack ### 1. Create stacked branches ```bash git checkout main git checkout -b feat-auth # ... make changes, push ... git checkout feat-auth git checkout -b feat-auth-ui # ... make changes, push ... git checkout feat-auth-ui git checkout -b feat-auth-tests # ... make changes, push ... ``` ### 2. Create PRs with correct base branches ```bash gh pr create --base main --head feat-auth --title "feat: add auth module" gh pr create --base feat-auth --head feat-auth-ui --title "feat: add auth UI" gh pr create --base feat-auth-ui --head feat-auth-tests --title "test: add auth tests" ``` ### 3. Add stack metadata Option A: Auto-discover (recommended) — Comment `st discover` on the root PR. Staqd scans all open PRs by base branch relationships, builds the tree, and updates every PR's metadata automatically. Option B: Manual metadata — Each parent PR's body needs an HTML comment listing its direct children: ``` ``` Only list direct children. `merge-all` recursively follows each child's metadata. Linear stack (A -> B -> C): - PR #1 body: `` - PR #2 body: `` - PR #3 body: (none) Tree stack (X and Y branch from base): - PR #10 body: `` - PR #11 body: (none) - PR #12 body: (none) Tree children are siblings — each rebases independently onto the same parent. ### 4. Use commands ``` # During development — sync children after pushing to parent Comment on PR #1: "stack restack" # After review — merge the entire stack at once Comment on PR #1: "stack merge-all" ``` ## How It Works Everything reduces to one git command: ``` git rebase --onto ``` Before (feat-1 squash-merged into main): ``` main: A---B---CD' (C+D squashed into new commit) feat-2: C---D---E---F (still contains original C, D) ``` After rebasing: ``` git rebase --onto main feat-2 main: A---B---CD' \ feat-2: E'---F' (C, D removed; only E, F rebased) ``` The skip SHA (`old_parent_tip_sha`) comes from GitHub's PR API, which preserves `head.sha` even after merge. No database needed. ## Auto-update on Manual Merge When a parent PR is merged through the GitHub UI (instead of `st merge`), Staqd automatically: 1. Updates each child PR's base branch to the parent's base (e.g., `main`) 2. Posts a notification comment on each child PR This prevents child PRs from becoming orphaned with a deleted base branch. The child PR may still need rebasing — run `st restack` on the child if needed. Requires `closed` in your workflow's `pull_request.types`: `[opened, edited, closed]`. ## Comparison | | Staqd | Graphite | ghstack | |---|---|---|---| | Installation | None (workflow file) | CLI + account | CLI | | Stack storage | PR body HTML comments | .graphite_info + server | Commit metadata | | Restack trigger | PR comment | gt restack CLI | ghstack CLI | | Conflict resolution | Async (Actions -> local fix -> push) | Sync (terminal) | Sync (terminal) | | Tree stacks | Supported (siblings) | Supported (DAG) | Linear only | | Merge queue | Comment-based | Dedicated UI | None | | External dependency | None | SaaS | None | ## Troubleshooting - CI doesn't run after restack push: `GITHUB_TOKEN` pushes don't trigger other workflows. Set up a GitHub App or PAT. - merge-all stops at a conflict: The result table shows the merge order and which PRs failed. Fix the conflict locally, push, then run `st merge` on the failed PRs in the order shown. - merge-all CI timeout: Default wait is ~10 min (30s x 20 retries). Increase `tryMerge` retry count for slower CI. - PR diff looks wrong after restack: The PR's base branch may not have been updated. `stack restack` handles this automatically, but you can manually change the base in PR settings. - Second PR in merge-all always fails initially: If branch protection has required checks, CI may not exist yet right after restack. `tryMerge` retries until CI is created and passes. ## License MIT