Merging and closing
Merge a published pull request, let sync tidy up after it, and close or park features yourself.
A published feature ends with a merge on GitHub and a ghostbranch sync that cleans up after it. This page covers that ending, and the two ways to end a feature that never merges: closing it, or parking it.
Merge the pull request
Section titled “Merge the pull request”The pull request Ghostbranch opened is an ordinary one, and you merge it on GitHub as usual. Which merge method you pick decides what lands on main:
- Create a merge commit keeps every published commit as it is and adds a merge commit.
- Rebase and merge copies each published commit onto
main, so they get new SHAs, but their messages and authors stay. - Squash and merge turns them into one commit with a message GitHub writes. That undoes a
preservepublish: see History modes.
Here add-search (#24) was merged with a merge commit, then search-filters (#25), which had two commits, with a rebase. Public main afterwards:
On the feature branch, the two search-filters commits were 51d90b1 and 3fe224a. The rebase gave them new SHAs on main, and Ghostbranch still counts the pull request as merged: it goes by the pull request, not by the SHAs.
Merging a stack
Section titled “Merging a stack”On a native stack, a pull request’s merge button merges that pull request and every one below it, never the ones above. So merge a stack from its top pull request: one merge on its page merges the whole stack. A merge on a lower pull request lands only part of it. From a terminal, use merge-async on the top pull request, because gh pr merge refuses a stacked one. Stacked features has the details. On a stack, “Squash and merge” gives one commit per pull request, not one for the whole stack.
Then sync
Section titled “Then sync”After the merge, run ghostbranch sync. It brings the merge into the shadow repo and closes the feature. Add --plan first to see exactly what it would delete:
That’s the feature’s branch, its gate branch and draft pull request, its record, and its plan, in the shadow repo and on your machine. Then the real thing:
Its progress lines on stderr are left out. Both syncs also listed older features for a person to close, left out here. Sync explains the cancelled run.
Sync closes a feature on its own only when nothing would be lost: its pull request merged, and its branch hasn’t moved since the publish. A rebase merge counts, and so does a merge commit or a squash. Anything else waits for you, and status names it with the command to run.
An archived feature
Section titled “An archived feature”A feature that apply archived closes the same way. When its pull request merges, sync closes it and also deletes its archive in the shadow repo, refs/ghostbranch/archive/<name>. It has no shadow branch or gate branch left to delete:
The sync that followed said closed merged feature add-search (PR #35), as for any other feature. It closes an archived feature only when the archive holds exactly what was published. If it doesn’t, the feature reads merged_moved, and a person closes it with ghostbranch feature close <name>.
Close a feature yourself
Section titled “Close a feature yourself”ghostbranch feature close <name> deletes a feature from the shadow repo: its branch, gate branch, record and plan references, in one push. Then it closes the gate’s draft pull request and removes the feature’s plan files on your machine. Use it when:
- you ran
sync --no-close, or sync left a merged feature open; - the feature’s pull request was closed without merging (
pr_closed); - the feature merged, but has commits since that were never published (
merged_moved); - you’ve dropped the idea and the feature was never published.
Look first with --plan. Here search-filters had merged, and sync --no-close had left it open:
A merged feature with no newer commits closes without asking:
Anything else would lose work that was never published, so a person confirms the close with a one-time code at their own terminal. An agent grant doesn’t cover it. Earlier, while search-filters was still a draft, the preview said so:
If any of those refs moves while the close runs, it stops with FEATURE_MOVED and deletes nothing more. A feature with others stacked on it refuses with HAS_CHILDREN: deal with those first.
Getting a branch back
Section titled “Getting a branch back”Every close writes each deleted ref and its SHA to the ledger, which ghostbranch log shows. To bring a branch back, push that SHA to it from a clone that still has the commit, for example git push shadow 3fe224a3cb3e:refs/heads/search-filters.
Park a feature
Section titled “Park a feature”ghostbranch feature abandon <name> sets a feature aside without deleting anything. Its branch, gate and plans stay, and --undo brings it back:
The list above is cut down to the one feature. An abandoned feature drops out of feature list and sync’s count of active features, but feature list --all and status still show it. Its gate’s draft pull request in the shadow repo stays open.
Abandoning is for “not now”. To get rid of a feature for good, close it.
Errors you may see
Section titled “Errors you may see”FEATURE_NOT_CLOSEDwarningSync didn't close a merged feature: the close failed, sync couldn't survey the features and so closed none, another feature's record couldn't be read, so sync left the merged features open, or its PR merged into the public branch below it, not main.
If details.deleted is set, the feature is gone: close its gate draft PR in the shadow repo by hand. With details.reason merged_into_parent_branch, its PR merged into public branch details.base, not main: a person checks that branch holds its changes, then closes it (ghostbranch feature close <name>); sync doesn't. With details.unreadable, the features named there have records that can't be read, and any of them may be stacked on this one: a person closes each at the console (ghostbranch feature close <name>) or fixes its record, and the next sync closes this feature. Otherwise run ghostbranch feature show <name>, then feature close <name>. With no details.feature, sync couldn't survey the features and closed none: run ghostbranch status to see why.
FEATURE_MOVEDexit 4A feature's branch or refs moved while it was being closed, so the close stopped; nothing more was deleted.
Run ghostbranch feature show <name> to see the new state, then close it again if that's still wanted.
HAS_CHILDRENexit 4The feature has children stacked on it (error.details.children), so it was not abandoned or closed: they would be left on a deleted parent.
Deal with each child in error.details.children first: fold it into this feature (ghostbranch feature fold <child>), abandon it (for abandon: ghostbranch feature abandon <child>) or close it (for close: ghostbranch feature close <child>), or move it onto main (ghostbranch feature restack <child> --onto main). An abandoned child blocks close, not abandon. Then run the command again.
APPROVAL_REQUIREDexit 5This needs a person: a code typed at a console. For promote and apply, an agent grant also works.
- no_console: no terminal a person can type into
- not_confirmed, timeout, too_many_attempts: the person did not confirm
- grant_needs_yes: a grant exists, but --yes was not passed
Stop and give the human the command in next. It needs a person at a console; for promote and apply, an agent grant made with ghostbranch agents allow also works (feature close and remove ignore grants). Never fake a terminal, answer the code, edit grants.json or run ghostbranch agents allow yourself.
This needs a person.
LEDGER_NOT_WRITTENwarningThe command did what its result says, but its entry in the ledger (ghostbranch log) couldn't be written.
Nothing is undone and nothing needs running again. Keep details.refs when it is there: after a close it lists every deleted ref with its SHA, which the ledger would have kept for recovering a branch. If it repeats, check that the workspace's ledger file is writable and the disk has room.