Move a project's checkout to a branch or commit from the phone
The project settings sheet now lists the checkout's branches and its last fifty commits, and picking one moves the checkout on the build machine and builds what that leaves behind. It is the same act as a pull on the same single checkout, so it goes through the same machinery and reports in the same place: `after_moving` is the half the two share, which is not the git command but handing the run over under one lock, so nothing can observe the moment between the move ending and the builds it decided on starting. The two lists are read when the sheet opens rather than riding on the manifest, since both spawn git and the manifest is fetched on every open, resume and Refresh. Local reads only, so opening the sheet cannot stall on a network round trip: what the remote has and this checkout has not fetched is Pull's business, and the card already says when there is any. Listing branches was wrong twice in ways only a real checkout showed. `git branch --format` adds a `(HEAD detached at abc123)` pseudo-entry that is not a branch and cannot be checked out, so the list comes from `for-each-ref refs/heads`. And `refs/remotes/origin/HEAD` abbreviates to a bare `origin`, so filtering names that end in `/HEAD` matches nothing and a phantom branch called `origin` reached the phone; it is dropped by being a symref instead. The test written to cover the second asserted the same wrong thing and passed. Moving is refused over tracked modifications only, where a pull refuses over any dirt at all. The strict check makes this a one-way door: going back to a commit from before the .gitignore that covers this project's build output leaves that output untracked, so the tree is dirty and every move afterwards is refused -- back but never forward, from a phone, with the way out being the build machine. Nothing is lost by relaxing it, because git refuses to overwrite an untracked file itself and carries across the ones it would not, and its refusal arrives as the error the card already shows. Found by moving a checkout back and forth rather than by reading it: the first version passed its tests and trapped the checkout on the second move. Picking a commit leaves the checkout on no branch, which the card now says in those words rather than showing git's literal `HEAD` -- beside a branch icon that reads as a branch somebody named HEAD. Worth saying now that the sheet can produce the state, where before it was reachable only on the build machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
17d873ab7c
commit
3aa6f2f290
7 files changed
+841
-7
No files matched your search
@@ -340,6 +340,58 @@ 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.
|
||||
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.
|
||||
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**
|
||||
that is not a branch and cannot be checked out, so the list comes from
|
||||
`for-each-ref refs/heads`; and `refs/remotes/origin/HEAD` abbreviates to
|
||||
a bare **`origin`**, so the obvious filter for a name ending in `/HEAD`
|
||||
matches nothing and a phantom branch called `origin` reaches the phone.
|
||||
It is dropped by *being* a symref (`%(symref)` non-empty) instead. The
|
||||
test that was meant to cover the second asserted the same wrong thing
|
||||
and passed.
|
||||
|
||||
- **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
|
||||
`git::pull` refuses on any dirt at all. With the strict check, moving
|
||||
back to a commit from before the `.gitignore` that covers this
|
||||
project's build output leaves that output sitting there untracked, the
|
||||
tree is dirty, and every move afterwards is refused -- you can go back
|
||||
and you cannot come forward, from a phone, with the way out being the
|
||||
build machine. Nothing is given up by relaxing it, because git makes
|
||||
the better check itself: `git checkout` refuses when an untracked file
|
||||
would be overwritten and carries across the ones that would not, and
|
||||
its refusal arrives as the error the card shows. So this guard covers
|
||||
work somebody typed and git's covers the files it would clobber.
|
||||
Found by moving a real checkout back and forth rather than by reading
|
||||
the code; the first version passed its tests and trapped the checkout
|
||||
on the second move.
|
||||
|
||||
- **A detached HEAD is a state the card has to say out loud.** Picking a
|
||||
commit leaves the checkout on no branch, `git::status` reports the
|
||||
branch as the literal `HEAD`, and `can_pull` correctly goes false
|
||||
because there is no upstream. The card draws that as **"no branch"**
|
||||
rather than `HEAD`, which beside a branch icon reads as a branch
|
||||
somebody named HEAD -- worth doing now that the sheet can produce the
|
||||
state, where before it was only reachable on the build machine.
|
||||
|
||||
- **Unrelated histories are the one pull failure the phone may override.**
|
||||
A checkout sharing no commit with its upstream has no fast-forward and
|
||||
never will, so with nothing offered the card is one that can never be
|
||||
|
||||
Reference in new issue
Block a user