Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
168 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.1//! Updates. The app asks g1t.sh/downloads/desktop/latest.json as it
2//! starts and every hour after, and downloads a newer version in the
3//! background. One found as it starts is installed straight away, and g1t
4//! opens again on it, before anyone has begun working in it: so a person
5//! whose copy misbehaves only has to open g1t again to be on the newest,
6//! fixed one (unless the runner is in the middle of a job, which is never
7//! cut short; then it waits like any other). One found later says so, with
8//! a notification and the tray's "Restart to update", since nothing is
9//! installed under someone mid-sentence; the restart, or quitting,
10//! installs it. The manifest is signed with g1t's
preparing for cloud work11//! release key, built into the app, and a download whose signature does
12//! not check out is thrown away (scripts/desktop-release.mjs).
13
14use std::time::Duration;
15
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r916use tauri::{AppHandle, Emitter, Manager};
preparing for cloud work17use tauri_plugin_notification::NotificationExt;
18use tauri_plugin_updater::{Update, UpdaterExt};
19
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r920use crate::{config, settings, tray};
preparing for cloud work21
22/// An update downloaded and checked, waiting to be installed.
23pub struct Ready {
24 pub version: String,
25 update: Update,
26 bytes: Vec<u8>,
27}
28
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.29/// The first look waits only for the window to be up.
30const FIRST_CHECK: Duration = Duration::from_secs(3);
31const EVERY: Duration = Duration::from_secs(60 * 60);
32/// An update downloaded this soon after g1t started is installed at once:
33/// nobody has begun working in it yet.
34const AT_START: Duration = Duration::from_secs(2 * 60);
preparing for cloud work35
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.36/// Checks as g1t starts, installing what it finds then, and every hour
37/// after, unless settings turned that off; a person can still check from
38/// the menu.
preparing for cloud work39pub fn schedule(app: AppHandle) {
40 tauri::async_runtime::spawn(async move {
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.41 let started = std::time::Instant::now();
preparing for cloud work42 tokio::time::sleep(FIRST_CHECK).await;
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.43 let mut first = true;
preparing for cloud work44 loop {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r945 if config::settings(&app).auto_update {
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.46 let at_start = first && started.elapsed() < AT_START;
47 let found = look(&app, false, at_start).await;
48 if at_start && let Some(version) = found.ok().as_deref().and_then(|answer| answer.strip_prefix("ready:")).map(str::to_owned) {
49 if crate::runner::busy(&app) {
50 tell(&app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it."));
51 } else {
52 tell(&app, "Updating g1t", &format!("g1t opens again on version {version} in a moment."));
53 restart(&app);
54 }
55 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r956 }
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.57 first = false;
preparing for cloud work58 tokio::time::sleep(EVERY).await;
59 }
60 });
61}
62
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r963/// When the updater last heard back, in milliseconds since 1970.
64pub fn checked_at(app: &AppHandle) -> Option<u64> {
65 *app.state::<config::State>().checked_at.lock().unwrap()
66}
67
68fn heard_back(app: &AppHandle) {
69 let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|since| since.as_millis() as u64).ok();
70 *app.state::<config::State>().checked_at.lock().unwrap() = now;
71}
72
preparing for cloud work73/// The version downloaded and waiting, if one is.
74pub fn ready(app: &AppHandle) -> Option<String> {
75 app.state::<config::State>().update.lock().unwrap().as_ref().map(|ready| ready.version.clone())
76}
77
78fn tell(app: &AppHandle, title: &str, body: &str) {
79 let _ = app.notification().builder().title(title).body(body).show();
80}
81
82/// Looks for an update and downloads it. `asked` is a person asking from
83/// the tray or the site, who is told the answer either way; the timer
84/// says nothing unless there is something to install.
85pub async fn check(app: &AppHandle, asked: bool) -> Result<String, String> {
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.86 look(app, asked, false).await
87}
88
89/// [`check`]; `at_start` says nothing of a download, which is installed at once.
90async fn look(app: &AppHandle, asked: bool, at_start: bool) -> Result<String, String> {
preparing for cloud work91 if let Some(version) = ready(app) {
92 if asked {
93 tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t to use it."));
94 }
95 return Ok(format!("ready:{version}"));
96 }
97 let updater = app.updater().map_err(|error| error.to_string())?;
98 let update = match updater.check().await {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r999 Ok(Some(update)) => {
100 heard_back(app);
101 update
102 }
preparing for cloud work103 Ok(None) => {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9104 heard_back(app);
preparing for cloud work105 if asked {
106 tell(app, "g1t is up to date", &format!("You have version {}.", env!("CARGO_PKG_VERSION")));
107 }
108 return Ok("current".into());
109 }
110 Err(error) => {
111 if asked {
112 tell(app, "g1t could not check for updates", &error.to_string());
113 }
114 return Err(error.to_string());
115 }
116 };
117 let version = update.version.clone();
118 match update.download(|_, _| {}, || {}).await {
119 Ok(bytes) => {
120 *app.state::<config::State>().update.lock().unwrap() = Some(Ready { version: version.clone(), update, bytes });
121 tray::update_ready(&version);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9122 let _ = app.emit_to(settings::SETTINGS, settings::READY_EVENT, version.clone());
The desktop app is 0.3.3: Settings opens again on Windows, and an update found as g1t starts installs itself. Since 0.3.0, Settings opened from the title bar's button or the site's account menu showed a white window and nothing else: both are synchronous commands, which run on the main thread, and on Windows a webview window made there deadlocks (Tauri's known issue), so the window appeared and its page never loaded. The settings window is now made on a thread of its own whoever asks, and Tauri hands the making to the event loop. And so that opening g1t again is all it takes to leave a copy that misbehaves, the app looks for a newer version as it starts, three seconds after the window rather than fifteen, and one it downloads within its first two minutes is installed straight away, g1t opening again on it, before anyone has begun working in it; a runner in the middle of a job is never cut short for it, and then the update waits for Restart to update as before. After that it looks every hour rather than every six, and what it finds still waits for a restart or quitting. The desktop guide's Updates section says so.123 if !at_start {
124 tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it."));
125 }
preparing for cloud work126 Ok(format!("ready:{version}"))
127 }
128 Err(error) => {
129 if asked {
130 tell(app, "The update could not be downloaded", &error.to_string());
131 }
132 Err(error.to_string())
133 }
134 }
135}
136
137/// Installs what is downloaded, if anything. On Windows the installer
138/// ends this process itself; elsewhere the caller goes on.
139fn install(app: &AppHandle) -> bool {
140 let ready = app.state::<config::State>().update.lock().unwrap().take();
141 let Some(ready) = ready else { return false };
142 match ready.update.install(ready.bytes) {
143 Ok(()) => true,
144 Err(error) => {
145 tell(app, "The update could not be installed", &error.to_string());
146 false
147 }
148 }
149}
150
151/// The tray's restart: installs the update and starts the new version.
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.152/// The runner stops first, since on Windows the installer ends g1t.
preparing for cloud work153pub fn restart(app: &AppHandle) {
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.154 if ready(app).is_none() {
155 return;
156 }
157 crate::runner::shutdown(app);
preparing for cloud work158 if install(app) {
159 app.restart();
160 }
161}
162
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.163/// Quit, stopping the runner and installing a downloaded update on the way out.
preparing for cloud work164pub fn quit(app: &AppHandle) {
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.165 crate::runner::shutdown(app);
preparing for cloud work166 install(app);
167 app.exit(0);
168}

This file's history is long; its oldest lines are credited to the oldest commit read.