Skip to content

Reversible operations & undo

Every mutating git-city command is built the same way: instead of firing Git commands as it goes, it first compiles your request into an explicit, ordered list of small steps. Each step is a tiny, well-understood operation — fetch a remote, move a ref, rebase one branch onto another, create or delete a branch, push. Nothing happens until that whole plan exists.

That single idea — commands are plans, not actions — is what makes the two safety features you rely on possible. --dry-run prints the plan and stops. undo reverses the plan that just ran. Neither is bolted on; both fall straight out of the same model.

One model, three behaviors

The plan is rendered as text for --dry-run, executed for a real run, and replayed in reverse for undo. Because all three read the same list of steps, what you see in --dry-run is exactly what runs, and what undo reverses is exactly what ran.


See the plan before it runs

Every mutating command accepts --dry-run. It prints the exact, ordered Git commands the plan would execute, then exits without touching anything.

git city sync --dry-run
  git fetch origin --prune
  git branch -f main origin/main
  git checkout add-login
  git rebase --onto main <base> add-login
  git push --force-with-lease=add-login:<sha> origin add-login

Nothing executed (--dry-run).

This is not a paraphrase of what might happen — it is the plan itself, rendered to text. If a command is about to do something surprising, --dry-run is where you find out, before any ref moves.


Undo: reverse the last command

As git-city executes a plan, it records how to reverse each step before that step runs — capturing the pre-mutation state while it still exists. The result is an inverse program: a second plan that puts every ref back where it was.

git city undo

Undo replays those captured inverses in reverse order. It can restore moved refs, recreate deleted branches, and even put back force-pushed or deleted remote branches — because it knows precisely what the previous command changed.

You ran Undo restores
git city sync branches to their pre-sync shas, including the remote
git city land the landed feature (local and remote) and its children's parents
git city delete the deleted branch (its commits survive in the reflog)
git city squash the original, un-squashed commits
git city new / insert / reparent the prior branch layout and lineage metadata

Single-level undo

Undo reverses exactly one command — the most recent one. Each git-city command overwrites the saved run-state, so there is no multi-step undo history. Undo, then re-inspect, then decide; you cannot peel back two operations in a row.


What undo refuses to do

Undo is deliberately conservative. It would rather stop with a clear message than risk destroying work, so it refuses in three situations:

  • Dirty working tree. Undo will not run while you have uncommitted changes, because reversing refs could leave your files inconsistent. Commit or stash first.
  • A branch carrying unique commits. If undoing would delete a branch whose commits exist on no other branch, undo refuses rather than orphan that work.
  • A paused operation. If a sync is paused on a conflict, undo will not touch it. Finish the pause with git city continue or git city abort first.

Undo refuses; it does not force

These are hard stops, not warnings you can click through. When undo cannot proceed safely, it prints a clean git-city: <message> and exits without changing anything. The way forward is always to resolve the underlying state (clean the tree, finish the pause), then try again.


Pausing and resuming

The same plan-based model handles conflicts. If a rebase inside a sync hits a conflict, git-city does not abandon the run — it pauses. The rebase is left in progress, and the remaining steps (plus their inverses) are saved so the operation can pick up later.

git-city: sync add-login is paused.
  stopped on:  git rebase --onto main <base> add-login
  resolve the conflict and `git add`, then:
    git city continue   resume the operation
    git city abort      undo everything and return to the start
    git city info       show this and the repo status

From a pause you have three choices: resolve and git city continue to run the rest of the plan, git city abort to replay the inverses captured so far and return to where you started, or git city info to re-print this message before the dashboard. Recall that undo itself refuses while paused — abort is the tool for unwinding a paused run.

Conflicts & pausing


Why this is trustworthy

Because reversal data is captured before each mutation, and because force-pushes always use --force-with-lease, undo never has to guess. It also never deletes a remote branch that git-city did not create — so it cannot clobber someone else's work in the name of cleaning up after itself.

If you want to see how plans, steps, and inverses are actually built and run — the functional core, the step types, the interpreter, and the run-state file — the internals page goes all the way down.

The execution engine