Fetch is a button, and moving the checkout builds nothing
Nothing here ever wrote a remote-tracking ref except the fetch inside a
pull, and a pull is reachable only when the branch you are already on is
behind -- so a branch pushed from another machine reached the picker as a
side effect of pulling something else, and a project already up to date
could never be moved onto a new branch at all.
POST /apps/{key}/fetch answers with the whole ref list, so the pickers
repopulate from after the fetch in one round trip. It is the only call
here that asks for --prune: making the picker say what the remote says is
its job, where a pull should change as little as it can. Still not on a
timer and not on opening the sheet, because a fetch mutates the checkout.
Picking a remote-only branch then had to work: `git checkout origin/topic`
detaches HEAD, since git's DWIM fires on the bare name, so the control
that says it is picking a branch produced the state picking a commit is
meant to produce -- and printed its detached-HEAD advice and succeeded.
Moving the checkout no longer builds. A pull is somebody taking new work;
a move is somebody looking, and charging a full build for a look starts a
minute of work an accidental tap cannot call back. What it leaves behind
is a component whose build no longer matches the checkout, so the build
button's word now follows the state: Rebuild only where every component
it covers is known current, Build otherwise.
The sheet loses its Checkout heading and its paragraph, both pickers sit
in a weighted row so a long branch name cannot wrap the label one letter
per line, and the failure text moved below them -- above, it shoved the
pickers down the screen as somebody reached for one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f983db154d
commit
25c069909b
8 files changed
+507
-117
No files matched your search
@@ -340,22 +340,37 @@ mutable at runtime from the phone.
|
||||
remote deliberately, and a card that mentions it every time is nagging
|
||||
about a choice somebody made.
|
||||
|
||||
- **A checkout can be moved from the phone, and it is the same act as a
|
||||
pull.** The project settings sheet lists the checkout's branches and its
|
||||
last fifty commits, and picking one moves the checkout and then builds
|
||||
whatever that left behind. `BuildState::after_moving` is the half a pull
|
||||
and a checkout share -- one copy, because the part that is easy to get
|
||||
wrong is not the git command but handing the run over under a single
|
||||
lock, so nothing can observe the moment between the move ending and the
|
||||
builds it decided on starting. It reports through the project's own
|
||||
state for the same reason: one progress path, not a second to keep in
|
||||
step.
|
||||
- **A checkout can be moved from the phone, and it is nearly the same act
|
||||
as a pull.** The project settings sheet lists the checkout's branches and
|
||||
its last fifty commits, and picking one moves the checkout.
|
||||
`BuildState::after_moving` is the half a pull and a checkout share -- one
|
||||
copy, because the part that is easy to get wrong is not the git command
|
||||
but handing the run over under a single lock, so nothing can observe the
|
||||
moment between the move ending and the builds it decided on starting. It
|
||||
reports through the project's own state for the same reason: one progress
|
||||
path, not a second to keep in step.
|
||||
**Where they differ is that a move builds nothing** (`then_build` is
|
||||
`None`, which is what `BuildAfterMoving` exists to say). A pull is
|
||||
somebody taking new work, so building it is the point; moving the
|
||||
checkout is somebody *looking* -- at another branch, at last week's
|
||||
commit -- and charging a full build of every component for a look means
|
||||
an accidental tap starts a minute of work with no way to cancel it, and
|
||||
replaces outputs that were wanted. What it leaves behind is a component
|
||||
whose build no longer matches the checkout, which the card already says
|
||||
and already carries the button for. Iris asked for this on 2026-09-02.
|
||||
Which is why **the build button's word follows the state**: `buildWord`
|
||||
in `UpdateManifest.kt` says `Rebuild` only where every component it
|
||||
covers is *known* current, and `Build` otherwise -- behind, never built,
|
||||
or parked on a commit nobody measured the output against. One rule at
|
||||
both scales, so the project row and a component row cannot come to
|
||||
disagree about what pressing them means; the project's asks only about
|
||||
the components it actually builds, since one with no command has no
|
||||
output to be current with anything and counting it would pin that button
|
||||
to `Build` for ever.
|
||||
The two lists are read by `GET /apps/{key}/refs` when the sheet is
|
||||
opened, never on the manifest -- both spawn git. They are **local reads
|
||||
only**: what the remote has and this checkout has not fetched is not
|
||||
offered, because Pull is what brings those in and the card already says
|
||||
when there are some, so opening the sheet cannot stall on a round trip
|
||||
or fail the way a remote check can.
|
||||
opened, never on the manifest -- both spawn git, and both are **local
|
||||
reads**, so opening the sheet cannot stall on a round trip or fail the
|
||||
way a remote check can.
|
||||
Two things about listing branches were wrong until a real checkout was
|
||||
looked at, and neither shows up in a fixture built to pass:
|
||||
`git branch --format` adds a **`(HEAD detached at abc123)` pseudo-entry**
|
||||
@@ -367,6 +382,54 @@ mutable at runtime from the phone.
|
||||
test that was meant to cover the second asserted the same wrong thing
|
||||
and passed.
|
||||
|
||||
- **Fetch is a button, because nothing else here writes a
|
||||
remote-tracking ref.** The only `git fetch` this server ran was the one
|
||||
inside a pull, and a pull is reachable only when the branch you are
|
||||
already on is behind -- so a branch pushed from another machine reached
|
||||
the picker only as a side effect of pulling something else, and a project
|
||||
already up to date could never be moved onto a new branch at all. The way
|
||||
out was a terminal on the build machine, which is where the person
|
||||
holding the phone is not. `POST /apps/{key}/fetch` is the fix, sitting
|
||||
under the two pickers in the project settings sheet. Deliberately still
|
||||
not on a timer and not on opening the sheet: a fetch mutates the checkout
|
||||
and pulls down objects, so it stays something somebody pressed, and
|
||||
`/refs` stays the local read described above.
|
||||
Both pickers sit in a `SettingRow`, which weights the label *and* the
|
||||
control rather than letting the control take what it likes: unweighted,
|
||||
a pill showing `second-branch-from-elsewhere` squeezes "Branch" into a
|
||||
three-character column that wraps one letter per line. The same rule the
|
||||
component rows already follow, in the other place a row mixes text with
|
||||
a control. The fetch failure and the "reading branches" note go
|
||||
*below* both pickers for the same family of reason -- above them, each
|
||||
appears and disappears mid-sheet and shoves the pickers down the screen
|
||||
as somebody is reaching for one, and the failure ends up nowhere near
|
||||
the button that produced it.
|
||||
It answers with the **whole ref list** rather than an acknowledgement, so
|
||||
the pickers repopulate from after the fetch in the same round trip --
|
||||
`routes::read_refs` is the one place both routes build that list.
|
||||
`--prune` is asked for here and nowhere else: this is the call whose job
|
||||
is to make the picker say what the remote says, where a pull should
|
||||
change as little as it can. The phone's read timeout for it is longer
|
||||
than the server's own 30s hard stop for a remote command, so a slow
|
||||
remote is reported in git's words rather than as the phone giving up on a
|
||||
request that is still running. While it runs, both pickers are disabled
|
||||
-- it is replacing the lists they are showing -- but uncommitted work is
|
||||
*not* a reason to disable it, since a fetch touches no file in the
|
||||
working tree. It is the one control in that sheet that still works on a
|
||||
dirty checkout.
|
||||
|
||||
- **`git checkout origin/topic` detaches HEAD**, which is why
|
||||
`git::local_branch_for` exists. Git's DWIM that starts a tracking branch
|
||||
fires on the bare name `topic` and on nothing else, and the picker's
|
||||
remote-only entries are named `origin/topic` because that is what they
|
||||
are -- so picking a branch left the checkout on no branch, with no
|
||||
upstream and so no Pull: the state picking a *commit* is meant to
|
||||
produce, reached from the control that says it is picking a branch.
|
||||
Nothing about it looks like a failure either, since `git checkout`
|
||||
prints its detached-HEAD advice and succeeds. Found by running it
|
||||
against a real clone once the Fetch button made remote-only branches
|
||||
something you could actually reach.
|
||||
|
||||
- **The checkout guard is deliberately weaker than the pull guard, and
|
||||
that is what stops it being a one-way door.** `git::checkout` refuses
|
||||
only on **tracked** modifications (`--untracked-files=no`), where
|
||||
|
||||
Reference in new issue
Block a user