Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/docs/content/docs/guides/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"json-output",
"multi-trunk",
"github-action-retarget",
"migration-from-graphite"
"migration-from-graphite",
"migration-from-charcoal",
"migration-from-ghstack",
"migration-from-sapling",
"migration-from-spr"
]
}
101 changes: 101 additions & 0 deletions apps/docs/content/docs/guides/migration-from-charcoal.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Migrating from Charcoal
description: Map Charcoal (`gt`) commands to DubStack equivalents and migrate an active stack in under 30 minutes.
---

[Charcoal](https://github.com/danerwilliams/charcoal) is the community-maintained fork of Graphite's CLI that ships under the same `gt` binary. If you have `gt` muscle memory, this guide is the fast path to DubStack — the mental model is identical, and most command names map one-to-one.

If you are migrating from Graphite proper (the SaaS-backed `gt`), the [Graphite migration guide](/docs/guides/migration-from-graphite) covers the same surface plus the cloud-features wind-down.

Comment thread
dubscode marked this conversation as resolved.
## Command Mapping

| Charcoal | DubStack | Notes |
|---|---|---|
| `gt create` / `gt c` | `dub create` | Same flags: `-a`, `-m`, `--ai` |
| `gt modify` / `gt m` | `dub modify` / `dub m` | Amend HEAD and restack descendants |
| `gt submit` / `gt s` | `dub submit` / `dub ss` | Defaults to downstack; pass `--stack` for the full tree |
| `gt sync` | `dub sync` | `--all` covers every tracked stack |
| `gt checkout` / `gt co` | `dub checkout` / `dub co` | Interactive picker with no argument |
| `gt log` / `gt ls` | `dub log` / `dub ls` | `--stack`, `--all`, `--json` |
| `gt up` / `gt down` | `dub up` / `dub down` | Identical |
| `gt top` / `gt bottom` | `dub top` / `dub bottom` | Identical |
| `gt info` | `dub info` | `--json` available |
| `gt pr` | `dub pr` | Opens GitHub PR for the current branch |
| `gt restack` | `dub restack` | Run after `git rebase` to fix up children |
| `gt continue` | `dub continue` | `--ai` to resolve conflicts via LLM |
| `gt abort` | `dub abort` | Restack/rebase rollback |
| `gt track` / `gt trunk` | `dub track` / `dub trunk` | Multi-trunk supported |
| `gt untrack` | `dub untrack` | Keep the branch, drop metadata |
| `gt delete` | `dub delete` | `--upstack`/`--downstack`/`--force` |
| `gt absorb` | `dub absorb` | `--ai` for ambiguous fixups |
| `gt fold` | `dub fold` | `--squash` for single-commit collapse |
| `gt undo` | `dub undo` | 20-entry ring buffer + `dub redo` |

Charcoal inherits Graphite's CLI surface, so anything in the [Graphite mapping](/docs/guides/migration-from-graphite#command-mapping) that is not listed above maps the same way to DubStack.

## Conceptual Differences

- **Local-first by design.** DubStack stores everything in `.git/dubstack/`. No external service, no telemetry, no shared cache file.
- **State mirrored into git refs.** `refs/dubstack/*` lets you rebuild state on a fresh clone with `dub init --restore-from-refs`. Charcoal relies on its own cache files and is sensitive to manual file deletion.
- **Optional AI throughout.** `dub create --ai`, `dub submit --ai`, `dub flow`, `dub absorb --ai`. Configured per-repo with `dub config ai-provider` and friends; none of it is required to use the stacked-diff workflow.
- **Safe merge order.** `dub merge-next` (alias `dub land`) refuses to merge a PR whose ancestors are open and retargets every child PR atomically.
- **Multi-trunk support.** Register additional trunks with `dub trunk add` and `dub trunk set-default`.
- **GitHub Action for retargeting.** `dub install retarget-action` drops a workflow that retargets dependents when a stack PR merges through the GitHub UI.

## Common Pitfalls

- **`gt submit` defaulted to the full stack; `dub submit` defaults to downstack.** Pass `--stack` explicitly in CI scripts.
- **Charcoal's local config is not migrated.** Re-run `dub config ai-provider`, `dub config reviewers`, and `dub config submit-default`.
- **`gt downstack edit` has no direct equivalent.** Use `dub reorder` for interactive reordering or `dub move --before/--after` for one-shot moves.
- **`gt sync --pull` is implicit.** `dub sync` fetches the trunk by default; pass `--fresh` to force a full refetch.

## 30-Minute Migration Script

```bash
# 1. Install DubStack
npm install -g dubstack

# 2. Initialize alongside Charcoal — non-destructive
cd path/to/repo
dub init

# 3. Adopt every Charcoal branch. DubStack walks ancestry to infer parents.
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
if [ "$branch" != "main" ]; then
git switch "$branch"
dub track --no-interactive || true
fi
done

# 4. Verify the tree matches what Charcoal showed
dub log --all

# 5. (Optional) restore AI configuration
dub config ai-assistant on
dub config ai-provider anthropic
dub config ai-model --provider anthropic claude-sonnet-4-7

# 6. (Optional) reviewers
dub config reviewers alice,@org/backend

# 7. Restack to confirm the chain is intact
dub restack

# 8. Refresh PR descriptions with DubStack metadata
dub submit --downstack --no-ai

# 9. Once stable, remove Charcoal. (The exact uninstall depends on how you
# installed it — Homebrew tap, source build, or volta-managed npm bin.)
# Then remove the per-repo cache. Charcoal inherits Graphite's file names:
rm -rf .graphite_cache_persist .graphite_repo_config 2>/dev/null || true
# (If your Charcoal fork renamed these, check the fork's docs for its actual
# cache path before deleting.)
```

If `dub log --all` differs from `gt log`, run `dub doctor` to diagnose. State is recoverable via `dub init --restore-from-refs`.

## See Also

- [Stacking Workflow](/docs/guides/stacking-workflow)
- [Conflict Resolution](/docs/guides/conflict-resolution)
- [GitHub Action: retarget](/docs/guides/github-action-retarget)
96 changes: 96 additions & 0 deletions apps/docs/content/docs/guides/migration-from-ghstack.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
title: Migrating from ghstack
description: Move from Meta's ghstack workflow (`ghstack`) to DubStack without re-creating any branches.
---

[ghstack](https://github.com/ezyang/ghstack) implements stacked diffs on top of GitHub by treating one local commit per PR and synthesising orphan `gh/<user>/<n>/{base,head,orig}` branches on push. DubStack takes the opposite approach: every PR has its own branch in your local checkout, mirroring how you work in `git switch`.

This guide explains the model shift and walks through migrating an in-flight ghstack series in roughly 30 minutes.

## Command Mapping

| ghstack | DubStack | Notes |
|---|---|---|
| `ghstack` | `dub submit --stack` | Push everything in the stack and open/refresh PRs |
| `ghstack land $URL` | `dub merge-next` / `dub land` | Land the next safe PR; retargets dependents |
| `ghstack unlink` | `dub unlink <branch>` | Detach a branch from its parent |
| `ghstack checkout $URL` | `dub co <branch>` | ghstack materialises a local branch from the PR's synthesised remote refs; with DubStack the PR's branch already exists locally, so just `dub co` it |
| (no equivalent) | `dub info` | Stack-aware branch metadata is implicit in ghstack's commit-per-PR model |
| Implicit rebase on push | `dub restack` | Explicit, undo-able restack |
| Implicit PR description prefix | `dub submit --ai` or templated body | DubStack does not auto-prefix; configure via templates |

(ghstack has a narrower surface than Graphite/Charcoal; many DubStack commands have no ghstack analog because ghstack does not model stacked branches locally.)

## Conceptual Differences

- **One commit per PR vs. one branch per PR.** ghstack rewrites your local history so each commit is mapped to its own pseudo-branch on the remote. DubStack keeps a real branch per PR in your working copy. The DubStack model is friendlier to bisect, blame, and IDE workflows because each PR is an ordinary git branch you can check out.
- **No commit re-authoring.** DubStack does not amend or rewrite commits when you submit. ghstack rewrites the `gh-metadata` trailer on every push.
- **PR base is a real branch.** DubStack PRs target the previous branch in your stack directly. ghstack PRs target a synthesised `gh/$user/$n/base` orphan branch, which can confuse code review tooling.
- **Land semantics.** `ghstack land` squash-merges the bottom commit into `main`. `dub merge-next` (alias `dub land`) merges the next mergeable PR and retargets every dependent before deleting the branch — including in the GitHub merge queue when configured.
- **Multi-author safe.** Because each PR is a real branch, collaborators can push to your stack without your local `gh-metadata` getting in the way.
- **No remote write on read.** DubStack never pushes anything during `dub log`, `dub status`, or `dub info`. ghstack's `checkout` walks the remote to assemble the working copy.

## Common Pitfalls

- **Squash vs. merge.** `ghstack land` always squash-merges. DubStack's default is configurable per repo with `dub config submit-default` and per merge with `dub merge-next --method squash|merge|rebase`.
- **PR commit messages.** ghstack uses the commit subject as the PR title and the body as the PR description. DubStack uses the same convention for new PRs but does **not** rewrite the PR title on subsequent submits. Edit titles in GitHub or pass `--ai` for an LLM-suggested rewrite.
- **The `gh-metadata: Pull-Request resolved:` trailer.** DubStack does not add or rely on this trailer. You can leave it on existing commits or strip it during the migration with `git rebase -i`.
- **Submodules and worktrees.** DubStack supports git worktrees and stores per-worktree state under `.git/worktrees/<id>/dubstack/`. ghstack does not.

## 30-Minute Migration Script

The cleanest path: convert each in-flight ghstack PR into a real local branch, then track it.

```bash
# 1. Install DubStack
npm install -g dubstack

# 2. Initialize. Safe alongside ghstack — DubStack writes only under .git/dubstack/
cd path/to/repo
dub init

# 3. For each open ghstack PR you own, materialise a local branch.
# ghstack PR titles include "[ghstack-poisoned]" markers — these refer to
# your `orig` commits. Use the GitHub UI or `gh pr list --author @me` to
# enumerate; for each one, create a local branch off the PR's head SHA:
#
# gh pr checkout <pr-number>
# git switch -c feat/<your-name> # rename to something human
#
# Or, for the entire stack at once, walk from oldest to newest and chain:
git switch main
git switch -c feat/auth-base # bottom of the old ghstack
git cherry-pick <bottom-orig-sha> # the orig commit ghstack tracked
git switch -c feat/auth-login # next one up
git cherry-pick <next-orig-sha>
# ... and so on.

# 4. Track every new branch in DubStack
git switch feat/auth-base
dub track --parent main
git switch feat/auth-login
dub track --parent feat/auth-base
# etc.

# 5. Sanity-check the chain
dub log --stack

# 6. Open new DubStack PRs and close the old ghstack PRs by hand. New PRs use
# real branch bases instead of the synthesised gh/<user>/<n>/base branches.
dub submit --stack

# 7. Once the team has approved + merged the new PRs, prune ghstack:
pip uninstall ghstack
git for-each-ref --format='%(refname)' refs/heads/gh/ \
| xargs -I {} git update-ref -d {}
git push origin --delete $(git for-each-ref \
--format='%(refname:lstrip=3)' refs/heads/gh/)
```

If your team uses ghstack as a merge tool only (and not for daily local stacking), the simpler answer is: keep ghstack for landing, use DubStack for local stack manipulation, and switch fully once the last ghstack PR lands.

## See Also

- [Stacking Workflow](/docs/guides/stacking-workflow)
- [Conflict Resolution](/docs/guides/conflict-resolution)
- [JSON Output](/docs/guides/json-output)
131 changes: 99 additions & 32 deletions apps/docs/content/docs/guides/migration-from-graphite.mdx
Original file line number Diff line number Diff line change
@@ -1,38 +1,105 @@
---
title: Graphite Migration
description: Map Graphite commands to DubStack equivalents.
title: Migrating from Graphite
description: Map Graphite (`gt`) commands to DubStack equivalents and migrate an in-flight stack in under 30 minutes.
---

If you have `gt` muscle memory, use this as a fast map. DubStack follows the same mental model.
If you have `gt` muscle memory, this guide is the fast path to DubStack. The mental model is the same: branches stacked on branches, one PR per branch, automatic restack on rebase.

## Command Mapping

| Graphite | DubStack |
|---|---|
| `gt create` | `dub create` |
| `gt modify` | `dub modify` / `dub m` |
| `gt submit` / `gt ss` | `dub submit` / `dub ss` |
| `gt sync` | `dub sync` |
| `gt checkout` / `gt co` | `dub checkout` / `dub co` |
| `gt log` / `gt ls` | `dub log` / `dub ls` |
| `gt up` / `gt down` | `dub up` / `dub down` |
| `gt top` / `gt bottom` | `dub top` / `dub bottom` |
| `gt info` | `dub info` |
| `gt pr` | `dub pr` |
| `gt restack` | `dub restack` |
| `gt continue` | `dub continue` |
| `gt abort` | `dub abort` |
| `gt track --parent` | `dub track --parent` |
| `gt untrack` | `dub untrack` |
| `gt delete` | `dub delete` |
| `gt parent` | `dub parent` |
| `gt children` | `dub children` |
| `gt trunk` | `dub trunk` |
| `gt undo` | `dub undo` |

## Key Differences

- **Local-first** — DubStack stores all state locally in `.git/dubstack/`. Nothing is pushed to your remote from these files.
- **AI features** — DubStack includes built-in AI for branch naming, commit messages, PR descriptions, `dub flow`, and stack-aware guidance via `dub ai ask`.
- **Agent skills** — DubStack ships packaged skills for coding assistants via `dub skills add`.
- **Safe merging** — `dub merge-next` handles retargeting child PRs before deleting merged branches.
| Graphite | DubStack | Notes |
|---|---|---|
| `gt create` | `dub create` | Same flags: `-a`, `-m`, `--ai` |
| `gt modify` / `gt m` | `dub modify` / `dub m` | Amend current branch and restack descendants |
| `gt submit` / `gt ss` | `dub submit` / `dub ss` | `--stack`, `--upstack`, `--downstack`, `--branch` |
| `gt sync` | `dub sync` | `--all` syncs every tracked stack |
| `gt checkout` / `gt co` | `dub checkout` / `dub co` | Interactive picker with no argument |
| `gt log` / `gt ls` | `dub log` / `dub ls` | `--stack`, `--all`, `--json` |
| `gt up` / `gt down` | `dub up` / `dub down` | Identical |
| `gt top` / `gt bottom` | `dub top` / `dub bottom` | Identical |
| `gt info` | `dub info` | `--json` machine-readable |
| `gt pr` | `dub pr` | Opens the PR for the current branch |
| `gt restack` | `dub restack` | Automatic when a parent changes |
| `gt continue` | `dub continue` | Add `--ai` to resolve conflicts via LLM |
| `gt abort` | `dub abort` | Rolls back restack/rebase |
| `gt track --parent` | `dub track --parent` | Adopt an untracked branch |
| `gt untrack` | `dub untrack` | Keep the branch, drop DubStack metadata |
| `gt delete` | `dub delete` | `--upstack`, `--downstack`, `--force` |
| `gt parent` / `gt children` | `dub parent` / `dub children` | `--json` outputs the relation |
| `gt trunk` | `dub trunk` / `dub trunk add` / `dub trunk set-default` | Multi-trunk supported |
| `gt undo` | `dub undo` | 20-entry ring buffer, supports `dub redo` |
| `gt absorb` | `dub absorb` | `--ai` for ambiguous fixups |
| `gt fold` | `dub fold` | Defaults to keeping commits; `--squash` to collapse |
| `gt move --before/--after` | `dub move --before/--after` | Identical semantics |
| `gt split` | `dub split` | Interactive or AI-assisted commit split |

## Conceptual Differences

- **Local-first.** All state lives in `.git/dubstack/` and never leaves your machine. There is no DubStack account, no remote server, and no synchronization daemon. The downside is that team coordination happens through GitHub PRs alone.
- **Stack metadata in git refs.** DubStack mirrors stack state into `refs/dubstack/*` so a wiped checkout can rebuild from `dub init --restore-from-refs`. Graphite stores the equivalent in `.graphite_cache_persist`.
- **First-class AI.** `dub create --ai`, `dub submit --ai`, `dub flow`, and `dub absorb --ai` all use a provider you configure with `dub config ai-provider`. Set keys with `dub config ai-model --provider anthropic <model>`.
- **Safer merges.** `dub merge-next` (alias `dub land`) refuses to merge a PR whose ancestors are not yet merged, and retargets every dependent PR atomically before deleting the local branch.
- **Multi-trunk.** A single repository can have several trunks (e.g. `main` plus `release/24.x`). Configure with `dub trunk add` and `dub trunk set-default`.
- **MCP server.** `dub mcp` exposes the local stack to AI coding agents via the Model Context Protocol, gated by `dub config mcp-mode`.

## Common Pitfalls

- **`gt submit --stack` defaulted to the whole tree; `dub submit` defaults to downstack.** Pass `--stack` explicitly when migrating CI scripts that assumed Graphite's old default.
- **`gt sync --no-restack` does not exist in DubStack.** Use `dub sync` and then `dub restack --continue` if you wanted to defer restack.
- **`.graphite_user_config` is not read.** Re-configure with `dub config ai-provider`, `dub config reviewers`, and friends.
- **`gt downstack edit` has no direct equivalent.** Use `dub reorder` for interactive reordering, or `dub move --before/--after` for single-branch moves.

## 30-Minute Migration Script

The following walkthrough migrates an active Graphite stack in place without re-creating any branches or losing in-flight PRs.

```bash
# 1. Install DubStack (assumes pnpm/npm available)
npm install -g dubstack

# 2. From the repo root, initialize DubStack alongside Graphite
cd path/to/repo
dub init

# 3. Adopt every Graphite-tracked branch into DubStack. DubStack walks the
# git ancestry to infer parent links — this is non-destructive.
git switch main
for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
if [ "$branch" != "main" ]; then
git switch "$branch"
dub track --no-interactive || true
fi
done

# 4. Verify the stack matches what Graphite showed
dub log --all

# 5. Optional: enable AI features. Pick one provider.
dub config ai-assistant on
dub config ai-provider anthropic
dub config ai-model --provider anthropic claude-sonnet-4-7

# 6. Optional: pre-fetch your default reviewers so `dub submit` requests them
dub config reviewers alice,@org/backend

# 7. Confirm restack works on the active stack
dub restack

# 8. Resync PR descriptions with the new metadata layout
dub submit --downstack --no-ai

# 9. (When you are confident) remove Graphite
npm uninstall -g @withgraphite/graphite-cli
rm -rf .graphite_cache_persist .graphite_repo_config .graphite_user_config

# 10. Add an alias so muscle memory survives:
# alias gt=dub # zsh/bash
```

If anything looks off after step 4, run `dub doctor` for a diagnostic report. The state is recoverable via `dub init --restore-from-refs`.

## See Also

- [Stacking Workflow](/docs/guides/stacking-workflow)
- [Conflict Resolution](/docs/guides/conflict-resolution)
- [Multi-trunk stacks](/docs/guides/multi-trunk)
Loading
Loading