Skip to content

Recovering a deleted or force-pushed branch

Every push, branch delete and merge is recorded in the repository’s append-only operation log. That log is internal. neosource has no in-product history of ref updates and no undo: no page lists who moved or deleted a branch, no API or neo command reads the log, and no button reverts a push or a delete.

Getting a branch back is plain Git: find the commit the branch used to point at, fetch it by SHA, and push it to a branch name again.

When a branch is deleted, neosource keeps the commits it pointed at for 30 days. That covers every kind of delete:

  • git push origin --delete <branch> (or git push origin :<branch>);
  • the source branch a pull request merge deletes automatically;
  • a branch that disappears from GitHub on a repository synced from GitHub.

For those 30 days the old tip, and everything it reaches, can still be fetched by its SHA even though no branch points at it. After the window it is reclaimed like any other unreferenced data, and the branch cannot be recovered from neosource.

The retained commits still count toward the repository’s storage until the window ends. Deleting a branch does not free its space immediately.

You need the full 40-character SHA of the branch’s last commit (see Finding the old SHA below). Then, in any clone of the repository:

Terminal window
git fetch origin <sha>
git push origin <sha>:refs/heads/<branch>

The first command downloads the commit even though no branch names it; the second recreates the branch on neosource pointing at it. If you still have the commit locally, skip the fetch.

A force-push that moves a branch is different from a delete. The commits it leaves behind are not kept for a retention window: once nothing references them they can be reclaimed at the next storage compaction. They may still be fetchable by SHA for a while afterwards, but nothing guarantees it.

To undo a force-push, push the old tip back from a clone that still has it:

Terminal window
git push --force-with-lease origin <old-sha>:refs/heads/<branch>

There are two exceptions, and both keep the commit a force-push replaced for 30 days, the same as a deleted branch:

  • A repository synced from GitHub, when a force-push on GitHub moves a branch.
  • An open pull request whose source branch is in the same repository. The head the force-push replaced can still be fetched by SHA. Pull requests from a fork are not covered.

neosource cannot show you a branch’s previous position, so the SHA has to come from somewhere that recorded it:

  • Your own clone. A local branch with the same name still points at its last commit (git rev-parse <branch>). git reflog lists every commit your HEAD has been on. After a force-push from your machine, the push output shows the move as + <old>...<new> <branch> (forced update).
  • A teammate’s clone or a CI checkout that has not fetched since the change.
  • The pull request. Each push to a pull request’s branch appears in its timeline with the commit it pushed, and the Revisions list beside the conversation shows the head commit of every revision. From the terminal, neo pr view <number> --json includes the current head as pull_request.head_commit.
  • A CI run. A run’s page shows and links the exact commit it ran on.

A SHA from the pull request or a CI run is only recoverable if the commit is still on the server: inside the 30-day window for a deleted branch or for a force-pushed head of an open same-repository pull request, or not yet reclaimed after any other force-push.