Pull, Update, Remove -- and a card that says when its build is behind
Iris's call: three buttons on a project's row, where Pull takes the
commits and stops and Update goes the whole way -- pull, build what that
brought in, install every APK it produced. Splitting them is the same
reasoning that stopped a checkout building: bringing a checkout up to
date is cheap and something you do while looking, and the expensive half
belongs to the button that says it does it.
Update is composed from the two things the app already does rather than
given a route of its own, so each half reports where it already did. It
skips a component whose build just failed, and hands the installer one
APK at a time: an install intent is modal, so two fired together means
the second replaces the first and a component is silently never
installed. The rest wait in ComponentState.ReadyToInstall, offered again
by continuePendingInstalls -- the same machinery as a download waiting
for a wrongly-signed copy to be removed. Deriving "is the installer
free?" from the component states could not work, because handing a file
over clears the state it would have read; caught by pressing Update on a
project that builds two APKs.
The project-wide Rebuild goes with it, and ProjectState collapses to one
Working(what, status): Pull, Update and moving the checkout differ in
nothing but the label, and the old one said "Pulling and building" for
all three.
Then the two things behind "nothing was flagged as out of date, and Pull
is disabled" after switching ai-app onto another branch:
- hasWorkWaiting is now one predicate for both the "Up to date" heading
and whether Update can be pressed, and it counts a component whose
build no longer matches the checkout. Without that a branch switch
left the card filed under "Up to date" with only a small note in one
component's row.
- A moved checkout re-asks its remote (RemoteChecks::recheck, on top of
a new Checks::forget). The cached answer was about the branch just
left and was reported with checkPending false, so a branch one commit
behind its upstream read as one with nothing to pull. Measured: git
said [behind 1] while the card said newCommits false.
A component's install button says Install rather than Build where nothing
is built yet, since the button beside it now says Build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9efe08e4ce
commit
af605211e2
7 files changed
+515
-162
No files matched your search
@@ -756,6 +756,18 @@ impl BuildState {
|
||||
);
|
||||
}
|
||||
|
||||
/// Takes the commits and stops there.
|
||||
///
|
||||
/// The Pull button, as against Update. Splitting them is what lets
|
||||
/// "bring the checkout up to date" be a cheap thing somebody can do
|
||||
/// while looking, with building and installing behind the button that
|
||||
/// says it does those -- and it is the same reasoning that stopped a
|
||||
/// checkout building. What it leaves behind is a component the card
|
||||
/// reports as out of date, with its own Build beside it.
|
||||
pub fn pull_only(self: &Arc<Self>, force: bool) {
|
||||
self.after_moving(move |this| this.pull(force), None);
|
||||
}
|
||||
|
||||
/// Moves this checkout onto `target` -- a branch or a commit -- and
|
||||
/// builds nothing.
|
||||
///
|
||||
@@ -781,6 +793,14 @@ impl BuildState {
|
||||
this.begin_project("checking out");
|
||||
crate::git::checkout(&this.project_path, &target)?;
|
||||
tracing::info!("checked out {target} in {}", this.project_path.display());
|
||||
// The new-commits answer was about the branch just left,
|
||||
// and nothing downstream can tell that from an answer
|
||||
// about this one -- so it is dropped and asked again here,
|
||||
// where what invalidated it happened.
|
||||
this.shared.remote_checks.recheck(&crate::git::Checkout {
|
||||
path: this.project_path.clone(),
|
||||
ipv4: this.git_ipv4,
|
||||
});
|
||||
Ok(true)
|
||||
},
|
||||
None,
|
||||
@@ -2109,6 +2129,70 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Moving the checkout re-asks the remote, because the last answer was
|
||||
/// about the branch just left.
|
||||
///
|
||||
/// The answer is cached per project, and the only things that used to
|
||||
/// start a check were the whole-list refresh and a card's own Refresh
|
||||
/// -- so after a move the card reported the previous branch's answer
|
||||
/// with nothing marking it as stale. Measured symptom: a branch one
|
||||
/// commit behind its upstream, with Pull disabled and no way to find
|
||||
/// out but pressing Refresh.
|
||||
#[tokio::test]
|
||||
async fn moving_the_checkout_asks_the_remote_again() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let root = dir.path();
|
||||
let work = root.join("work");
|
||||
std::fs::create_dir_all(&work).expect("mkdir");
|
||||
let components = two_component_checkout(&work);
|
||||
let origin = root.join("origin.git");
|
||||
run(root, &["git", "init", "-q", "--bare", "origin.git"]);
|
||||
run(
|
||||
&work,
|
||||
&[
|
||||
"git",
|
||||
"remote",
|
||||
"add",
|
||||
"origin",
|
||||
&origin.display().to_string(),
|
||||
],
|
||||
);
|
||||
run(&work, &["git", "push", "-q", "-u", "origin", "main"]);
|
||||
// A branch whose upstream has one commit this checkout does not,
|
||||
// which is exactly what Pull is for.
|
||||
run(&work, &["git", "checkout", "-qb", "topic"]);
|
||||
std::fs::write(work.join("app/main.kt"), "two").expect("write");
|
||||
run(&work, &["git", "commit", "-qam", "two"]);
|
||||
run(&work, &["git", "push", "-q", "-u", "origin", "topic"]);
|
||||
std::fs::write(work.join("app/main.kt"), "three").expect("write");
|
||||
run(&work, &["git", "commit", "-qam", "three"]);
|
||||
run(&work, &["git", "push", "-q", "origin", "topic"]);
|
||||
run(&work, &["git", "reset", "--hard", "-q", "HEAD~1"]);
|
||||
run(&work, &["git", "checkout", "-q", "main"]);
|
||||
|
||||
let state = state_for(&work, components);
|
||||
// What a check on `main` leaves behind: nothing to pull, said as
|
||||
// settled fact.
|
||||
state.shared.remote_checks.mark_current(&work);
|
||||
assert!(!state.shared.remote_checks.new_commits(&work));
|
||||
|
||||
state.move_checkout("topic".to_string());
|
||||
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while !state.shared.remote_checks.new_commits(&work) {
|
||||
assert!(
|
||||
std::time::Instant::now() < deadline,
|
||||
"the card still reports the branch that was left: no commits to pull, on a \
|
||||
branch that is one behind",
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
}
|
||||
assert_eq!(
|
||||
crate::git::status(&work).expect("a checkout").branch,
|
||||
"topic",
|
||||
);
|
||||
}
|
||||
|
||||
/// A component with nothing built is not "current with the checkout",
|
||||
/// however still the commits are.
|
||||
///
|
||||
|
||||
Reference in new issue
Block a user