The mental model¶
git-city is built on a small, sharp idea: there are only two kinds of branch, and every feature you work on remembers its parent. From those two rules everything else — stacks, sync order, land, undo — falls out naturally. Once this clicks, the commands stop being a list to memorize and start being obvious.
This page is the conceptual backbone of git-city. Read it once and the rest of the docs will feel like footnotes.
Two branch types¶
git-city divides the world cleanly in two.
| Branch type | What it is | Key behaviors |
|---|---|---|
| trunk | Long-lived, shared history. Your main (or master), plus any branches you list as perennials. |
Never rebased; only ever fast-forwards from its remote. The root of every stack. |
| feature | Everything you actually work on. | Knows its parent (another feature, or the trunk). Rebased onto its parent on sync, then force-pushed with --force-with-lease. |
That's the whole taxonomy. There is no "release", "observed", "contribution", or "prototype" type — just trunk and feature. The behavioral difference is simple: a trunk is something you catch up to, a feature is something you rebuild on top of its parent.
Note
A trunk is sacred. git-city never rewrites it — it only fast-forwards it to match its remote (or, if you have local commits, rebases those onto the remote). All the rewriting energy is directed at features, where it's safe and expected.
The parent hierarchy¶
Every feature records one parent. Follow each feature's parent upward and you always arrive at the trunk. That means your branches form a tree rooted at the trunk.
Here ui's parent is api, and api's parent is main. The filled dot marks the branch you're currently on. The ↑3 ↓0 counts are ahead/behind relative to the parent — api has 3 commits main doesn't, and is behind main by none.
You build this tree with the stack-editing commands:
| Command | Effect on the tree |
|---|---|
git city new <name> [--onto <branch>] |
Create a feature off the trunk, or --onto an existing branch. |
git city reparent <new-parent> |
Re-point a branch's parent (metadata only; the next sync moves the commits). |
git city insert <name> |
Slip a new branch in between a branch and its parent. |
See the dashboard and tree any time with git city and git city tree — both are read-only.
Stacks¶
A stack is just a path down the tree: a branch, its parent, its parent's parent, all the way to the trunk. In the example above, main → api → ui is a stack.
Stacks let you split one large change into a chain of small, reviewable pieces, each building on the last. When the base shifts — someone merges to main, or you amend api — git city sync walks the chain in the right order, rebasing each child onto its freshly-updated parent. You never hand-juggle git rebase --onto again.
git-city · my-project
trunk: main
remote: origin
on ● add-login (feature, parent: main)
working tree: clean
vs main: ↑2 ↓0
vs origin/add-login: ↑0 ↓0
stack:
main (trunk)
└─ ● add-login ↑2 ↓0
Because rebasing is the engine that keeps a stack honest, it's worth understanding why git-city rebases rather than merges.
Where lineage lives¶
The tree is metadata, and git-city stores it where it belongs: in Git config, local to your clone. Nothing is written to a tracked file, so recording a parent never produces a diff or a churn commit.
| Git config key | Type | Meaning |
|---|---|---|
git-city.branch.<name>.parent |
string | This branch's parent. The single source of truth for the tree. |
git-city.branch.<name>.parked |
bool | Skip this branch during sync. |
git-city.branch.<name>.private |
bool | Sync this branch with its parent, but never push it. |
The trunk itself (and any perennials) is recorded separately, in the small TOML configuration files — not as per-branch Git config. That keeps the project-wide truth committable and the per-branch lineage local.
Tip
You can inspect or set lineage by hand with plain git config, for example git config git-city.branch.ui.parent api. But you rarely need to — new, reparent, and insert manage it for you.
Modifier flags: parked and private¶
Beyond its parent, a feature can carry two optional flags. They're stored in Git config (the .parked and .private keys above) and they only change how sync treats the branch.
- parked —
syncskips this branch entirely. Use it for a branch you've set aside and don't want rebased or pushed until you come back to it. - private —
syncstill rebases this branch onto its parent, but never pushes it. Use it for local-only work you want kept current but not published.
A branch can be both: a parked branch is left untouched; a private branch stays current locally but stays off the remote.
Warning
There is no park / unpark / private command yet. For now, set the flags directly:
git config --type=bool git-city.branch.ui.parked true
git config --type=bool git-city.branch.ui.private true
sync reads them on its next run. (Toggle commands are on the roadmap.)
A flagged branch is tagged in git city tree so you can see its state at a glance:
Putting it together¶
That's the entire mental model:
- Trunk is what you catch up to; feature is what you build.
- Every feature names a parent, so your branches form a tree rooted at the trunk.
- A stack is any path down that tree.
- Lineage and flags live in Git config, never in a tracked file.
- parked and private tune what
syncdoes to a branch.
Every git-city command — sync, land, delete, insert, squash, undo — is just a well-behaved transformation of this tree. Hold the model in your head and the commands explain themselves.