Updating an open PR
Answer a review by promoting again, and bring in commits someone made on GitHub.
A reviewer asks for a change. How you make it depends on what you chose when apply published the feature:
- Archive (the default): the feature was handed off, and its pull request is now its home. Commit the fix on the pull request’s branch and push it there with plain git:
git fetch origin add-search,git switch --detach origin/add-search, commit, thengit push origin HEAD:add-search. No gate, no leak scan, no plan and no code.promote,gateandfeature pullrefuse an archived feature withFEATURE_ARCHIVED, andnextgives these steps. See After publishing: Archive or Keep. - Keep: the feature stays private, and you update its pull request the way you made it, as the rest of this page shows.
For a kept feature, commit on the feature branch, push to the shadow repo, gate, and promote. Promote sees that the feature already has an open pull request and updates it, instead of opening another. The examples update pull request #26 of add-search, which was kept. The part on commits made on GitHub comes from an earlier run, pull request #23.
To hand a kept feature off later, run ghostbranch feature archive add-search. It needs the pull request to be open.
Behind the pull request
Section titled “Behind the pull request”Once you push a new commit to the feature, the pull request no longer has everything the feature has. status shows the feature as pr_behind:
That run had other features too: their rows and Next lines are left out above, and the columns closed up. Gate it and plan the update as before: ghostbranch gate add-search, then ghostbranch promote add-search --plan.
Append or replace
Section titled “Append or replace”An update reaches the pull request in one of two ways, and promote picks it for you:
- Append. If you only added commits, they’re added on top of the pull request’s branch. Nothing is force-pushed, and reviewers see just the new commits.
- Replace. If the history changed, the branch is replaced. Promote pushes it with a lease on the head the plan saw, so if anything else moved the branch in the meantime, the push fails and nothing is lost.
The history counts as changed after a rebase or restack, a reworded message, a conflict with the pull request’s branch, a changed base, or a squash or curated publish, which builds new commits every time. A rebase onto a newer main replaces too, even when no commit changed.
The approval summary says which, on its first line:
These two lines are the format Ghostbranch prints, not a captured run. The rest of the summary is as for a first publish, except that an update keeps the pull request’s title and body as they are on GitHub, unless you pass --title or --body again. Every update needs the same approval as the first publish: your code, or an agent grant.
Here is an append, and then a replace of the same pull request by a squash update:
An agent ran these under a grant, hence --yes. The replace was planned with ghostbranch promote add-search --mode squash -m "…" --plan, which ran the gate again first (“The gate for add-search is missing or stale, so it runs first”): the last gate had scanned the commits for a preserve publish.
With --json, the plan says which too: publish.update is true, publish.style is append or replace, replace_reason says why (squash here), and expected_head is the head the lease checks.
Stacks
Section titled “Stacks”In a stack, promote add-search also updates the open pull requests above it, under the same approval, bottom up. A pull request in a stack, or with open pull requests above it, is always replaced, never appended. Restack and gate up first: promote says so with NOT_A_STACK if you didn’t.
If one of the pushes fails, the ones below it stay updated, and running apply again carries on from it.
Commits made on GitHub
Section titled “Commits made on GitHub”Sometimes a commit lands on the pull request’s branch on GitHub itself: a maintainer’s fix, an accepted review suggestion, or the “Update branch” button. Ghostbranch never overwrites those. status shows the pull request as #23 (changed on GitHub), and promote refuses to build an update that would drop them:
Feature pull
Section titled “Feature pull”ghostbranch feature pull <feature> brings those commits into the feature’s branch in the shadow repo, and replays your later commits on top. --plan shows the branch it would move first:
What happens to the pulled commits:
- They keep their authors, whatever
promote.author_emailsays. Asquashpublish credits them with aCo-authored-by:trailer, when their author isn’t the publishing account. - They’re already public, so the next gate doesn’t scan them again. Your own commits are scanned as always.
- They’re protected: a later update that would drop them is refused.
After the pull above, a gate and a squash promote replaced the pull request’s branch once more, and the commit made on GitHub was folded into it.
A merge commit, such as one from “Update branch”, or a conflict stops feature pull, and next gives the git steps instead: fetch the pull request’s branch from origin, merge it into the feature, push the feature to the shadow repo, and run feature pull again. Ghostbranch never resolves a conflict.
feature pull only works on a feature with an open pull request:
When the pull request moves after the plan
Section titled “When the pull request moves after the plan”If the pull request’s branch moves between promote --plan and apply, apply refuses with PLAN_STALE (exit 6), and public_branch is in error.details.moved. Plan again.
Errors you may see
Section titled “Errors you may see”PUBLIC_BRANCH_CHANGEDexit 4The open PR's public branch holds commits Ghostbranch didn't publish (error.details.foreign), so the PR was not updated: the update would drop them. error.details.reason, when set, names a special case.
- someone pushed a commit to the PR's branch
- a review suggestion was committed on GitHub
- Update branch on GitHub merged main into the PR's branch
- the branch was force-pushed outside Ghostbranch
- unrecorded_update: the branch is at an update Ghostbranch pushed whose record was never written (error.details.plan); nothing is foreign
- adopted_missing: a preserve update would drop commits feature pull brought in from GitHub that the feature no longer has (error.details.foreign)
Bring them into the feature with ghostbranch feature pull <feature>, then gate and promote again. They are already public, so the gate doesn't scan them again; it scans your commits as always. If feature pull can't replay them cleanly, it stops with the git steps to merge them in yourself. For unrecorded_update, run ghostbranch apply <error.details.plan> on the machine that made the plan: it finds the push landed and writes the record (never feature pull, which would copy Ghostbranch's own commits into the feature). For adopted_missing, cherry-pick the listed commits back onto the feature, push it to the shadow repo, then gate and promote again. Ghostbranch never resolves conflicts, and never overwrites commits made on GitHub. error.details.foreign lists each commit's sha, author and subject.
FEATURE_ARCHIVEDexit 2This feature was handed off to the public repo (archived): its shadow branch is archived, so promote, gate, feature pull and waivers add no longer work on it, and feature start --from and feature restack --onto can't build on it (error.details.feature, error.details.public_pr, error.details.branch; error.details.pr_state says whether the PR is still open, merged or closed).
- apply archived it after publishing (Archive is apply's default)
- ghostbranch feature archive archived it
When feature start --from or feature restack --onto refused it (error.field from or onto), start from main or restack onto main instead, after ghostbranch sync if pr_state is merged. Otherwise, fix or change its PR by pushing to the PR's branch: git fetch origin <error.details.branch>, commit on origin/<error.details.branch> (never on the old shadow branch: the guard refuses its private commits), then git push origin HEAD:<error.details.branch>. When the PR merges, ghostbranch sync closes the feature; once it merged (pr_state merged) run sync, and once it was closed without merging (pr_state closed) a person closes the feature at the console. next gives these steps.
NO_OPEN_PRexit 2feature pull and feature archive need a feature's open public PR, and this feature (error.details.feature) has none (error.details.reason; error.details.public_pr once it was published).
- not_published: it was never published
- merged: its PR merged
- closed: its PR was closed without merging
- branch_gone: its public branch is gone
Follow next: for not_published, promote <feature> --plan publishes it; for merged, sync closes the feature; for closed or branch_gone, a person decides whether to promote again (a new PR) or close the feature.
PLAN_STALEexit 6The repos changed since the plan was made.
Make a new plan with ghostbranch promote <feature> --plan and apply that.
NOTHING_TO_PUBLISHexit 1Nothing would be published (error.details.reason).
- no_commits: the feature has no commits of its own yet
- all_stripped: every commit is empty, or the commits cancel out, once overlay paths and internal sections are stripped (error.details.dropped_empty)
- an update of an open PR that already holds every change (no reason)
For no_commits, commit on the feature branch, push it to the shadow repo, then gate. Otherwise there is nothing to do: the feature has no public changes.
REF_MOVEDexit 4A ref changed on the remote while the command ran.
Run the command again.