Commit and Push
Turning staged changes into a commit and getting it upstream: the commit composer, committing, pushing, setting an upstream on a new branch, and what each success and failure state means.
Committing
The composer sits at the bottom of working-tree mode. It is a monospace textarea that grows from two to six lines as you type. A vertical ruler marks column 72 and a counter tracks your subject line, turning amber when any line runs past 72 characters, a nudge toward the standard Git convention.
With a remote configured you get two buttons: a ghost Commit and a primary Commit & Push (N). With no remote you get a single Commit (N).
Recalling recent messages
With the cursor at the very start of an empty composer, ↑ walks back through the last eight commit messages on that branch, bodies included, and ↓ walks forward again, restoring your in-progress draft at the end. Once you edit the text, the recall index resets.
What blocks a commit
The commit buttons are disabled when nothing is staged, the message is empty, conflicts are unresolved, or the worktree is in detached HEAD. Hover a disabled button to see the reasons. Click it to jump focus to the blocker, expanding the file list if that is what is needed.
When Review Hub is opened from a worktree card carrying an agent note, the composer can be pre-seeded with the note's first line, and only that. Nothing else is ever substituted as a commit message.
Pushing
Where the push actually goes
Every git write that touches a remote (the push, the rescue push that sets an upstream, pull-and-rebase, force-with-lease, and the discard preview behind it) resolves the branch's real destination from git's own configuration instead of assuming origin. Daintree asks git the same question git asks itself, honoring branch.NAME.pushRemote, then remote.pushDefault, then branch.NAME.remote.
When it cannot get an answer, it fails closed. The confirm button is disabled with "No push destination is configured for this branch. Set an upstream, or configure a push remote, before pushing." Two or more remotes with nothing saying which one this branch pushes to is an explicit ambiguity, and it stays an ambiguity even when one of them is named origin. Sending a write to a repository you did not choose is not recoverable; a blocked button is a visible prompt to configure a remote.
upstream/main but pushes to fork/topic. Naming origin would push to the wrong repository, or refuse a push that git would have accepted. Because the destination is resolved rather than assumed, the confirm dialog names the real target ("Push to 'fork/topic'?") and the force-push dialog reads "Force push to fork/topic?". The push refspec is written explicitly as local-to-remote too, since the remote-side branch need not share the local name.Pull-and-rebase deliberately resolves a different ref: your branch's upstream, not its push target. These are the same thing in the ordinary case and genuinely different in a fork workflow, and a rebase that used the push destination would replay your commits on top of a repository you never asked to integrate from.
The same discipline shows up around the change: behind-counts compare against the base branch's own tracking ref rather than assuming origin, and a PR head is fetched from the remote the PR was opened against: with the forge configured as upstream, fetching pull/N/head from origin simply fails with "couldn't find remote ref". Those two resolvers fail soft, falling back to previous behavior, because they feed a displayed number rather than a write.
The confirmation
Clicking Commit & Push opens a confirmation titled "Push to 'REMOTE/BRANCH'?" carrying a destination pill, a full preview of the commit message, and a Don't ask again for this worktree checkbox. Tick it and future pushes from that worktree skip the dialog. On a protected branch the description adds a line noting that most teams use pull requests instead.
The opt-out is not a blanket one. It is stored per worktree path and consulted only by Review Hub's own Commit & Push. Even inside Review Hub it is overridden when the destination will not resolve: an unresolvable push always shows the dialog, because the dialog is where that failure is explained.
During the push you get per-stage progress bars labeled with the resolved target. A branch with no tracking configuration yet (the normal state of a freshly created worktree branch) gets its upstream set for you on the first push, to the destination that was already resolved and confirmed. No need to run git push --set-upstream by hand.
When a push fails
An amber banner explains why and what to do about it. Authentication, rate-limit, network, and filesystem failures keep the detail short and offer a retry where retrying can help. Policy, hook, and unrecognized failures tuck the raw output behind a Show details toggle, because the server's own text is often the only actionable signal. An auth failure may also surface a forge error code and an Open forge settings shortcut routed to that provider's settings.
Calls to action are limited to things the app can actually do. A clear hint with no button beats a button that does not work.