Open repositories on another machine over ssh; keep the window responsive while git runs #5
@@ -35,7 +35,7 @@ one character, and no local folder with exactly that name exists. Every other te
|
|||||||
local folder. So `C:\code\x` and `C:/code/x` stay local, `./a:b` stays local, and a
|
local folder. So `C:\code\x` and `C:/code/x` stay local, `./a:b` stays local, and a
|
||||||
folder in the current folder named `notes:2024` stays local when it exists.
|
folder in the current folder named `notes:2024` stays local when it exists.
|
||||||
|
|
||||||
A path on the server that starts with `~/` is handed to the server's shell with the
|
A path on the server that is `~` or starts with `~/` is handed to the server's shell with the
|
||||||
`~` outside the quotes, so the shell expands it to the home folder there.
|
`~` outside the quotes, so the shell expands it to the home folder there.
|
||||||
|
|
||||||
## Running git
|
## Running git
|
||||||
@@ -49,6 +49,7 @@ A path on the server that starts with `~/` is handed to the server's shell with
|
|||||||
|
|
||||||
```
|
```
|
||||||
ssh -o BatchMode=yes -o ConnectTimeout=10
|
ssh -o BatchMode=yes -o ConnectTimeout=10
|
||||||
|
-o ServerAliveInterval=5 -o ServerAliveCountMax=2
|
||||||
-o ControlMaster=auto -o ControlPath=<socket> -o ControlPersist=10m
|
-o ControlMaster=auto -o ControlPath=<socket> -o ControlPersist=10m
|
||||||
user@myserver "git -C '/srv/app' 'log' '--format=…' …"
|
user@myserver "git -C '/srv/app' 'log' '--format=…' …"
|
||||||
```
|
```
|
||||||
@@ -57,12 +58,18 @@ A path on the server that starts with `~/` is handed to the server's shell with
|
|||||||
has no terminal to ask in. Keys and the ssh agent work.
|
has no terminal to ask in. Keys and the ssh agent work.
|
||||||
- `ConnectTimeout=10`: a server that does not answer gives an error after 10 seconds
|
- `ConnectTimeout=10`: a server that does not answer gives an error after 10 seconds
|
||||||
instead of a window that waits for ever.
|
instead of a window that waits for ever.
|
||||||
|
- `ServerAliveInterval=5`, `ServerAliveCountMax=2`: when a server stops answering
|
||||||
|
on a connection that is already open (also a shared one), ssh notices it in about
|
||||||
|
10 seconds and gives an error instead of waiting for ever.
|
||||||
- `ControlMaster`, `ControlPath`, `ControlPersist`: the first command opens one
|
- `ControlMaster`, `ControlPath`, `ControlPersist`: the first command opens one
|
||||||
connection and leaves it open; later commands go through it and cost about one
|
connection and leaves it open; later commands go through it and cost about one
|
||||||
network round trip each instead of a new login. The connection closes 10 minutes
|
network round trip each instead of a new login. The connection closes 10 minutes
|
||||||
after the last command. The socket is `gites-%C` in the user's runtime folder
|
after the last command. The socket is `gites-%C` in the user's runtime folder
|
||||||
(`/run/user/<id>` on Linux), or in `/tmp` where there is none; `%C` is a hash ssh
|
(`/run/user/<id>` on Linux). Where there is none (always on macOS), it is in a
|
||||||
makes from the host, port and user, so the path stays short.
|
`gites` folder in the user's cache folder, made so that only the user can open it;
|
||||||
|
never in `/tmp`. If neither folder can be found or made, these three options are
|
||||||
|
left out and every command opens a new connection. `%C` is a hash ssh makes from
|
||||||
|
the host, port and user, so the path stays short.
|
||||||
- On Windows, ssh cannot share connections, so these three options are left out and
|
- On Windows, ssh cannot share connections, so these three options are left out and
|
||||||
every command opens a new connection.
|
every command opens a new connection.
|
||||||
- Quoting: every word of the git command is put in single quotes, and a `'` inside a
|
- Quoting: every word of the git command is put in single quotes, and a `'` inside a
|
||||||
@@ -107,6 +114,12 @@ If the connection drops while a repository is open, the next command shows its e
|
|||||||
as a message at the bottom of the window and the graph that is already on screen
|
as a message at the bottom of the window and the graph that is already on screen
|
||||||
stays. The next reload connects again by itself.
|
stays. The next reload connects again by itself.
|
||||||
|
|
||||||
|
A message that reports an error stays for 8 seconds, or until it is clicked, and each
|
||||||
|
hint line above stays on its own line. Other messages go after 1.8 seconds, as before.
|
||||||
|
|
||||||
|
The commands that run git run away from the thread that draws the window, so the
|
||||||
|
window does not freeze while it waits for a slow server.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
- Reading locations: `host:/path`, `user@host:/path`, `host:~/x`, `C:\x`, `C:/x`, a
|
- Reading locations: `host:/path`, `user@host:/path`, `host:~/x`, `C:\x`, `C:/x`, a
|
||||||
|
|||||||
+19
-7
@@ -70,7 +70,7 @@ watch(
|
|||||||
if (!settingsLoaded) return;
|
if (!settingsLoaded) return;
|
||||||
clearTimeout(settingsTimer);
|
clearTimeout(settingsTimer);
|
||||||
settingsTimer = window.setTimeout(() => {
|
settingsTimer = window.setTimeout(() => {
|
||||||
invoke("save_settings", { value: { ...settings } }).catch((e) => showToast(`could not save settings: ${e}`));
|
invoke("save_settings", { value: { ...settings } }).catch((e) => showToast(`could not save settings: ${e}`, { error: true }));
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
@@ -117,7 +117,7 @@ async function load() {
|
|||||||
if (data) await applyRepo(data);
|
if (data) await applyRepo(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// A repository already on screen stays, for example when a connection drops.
|
// A repository already on screen stays, for example when a connection drops.
|
||||||
if (repo.value) showToast(`could not reload: ${e}`);
|
if (repo.value) showToast(`could not reload: ${e}`, { error: true });
|
||||||
else error.value = String(e);
|
else error.value = String(e);
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
@@ -131,7 +131,7 @@ async function openRepo(path: string) {
|
|||||||
await applyRepo(await invoke<RepoData>("open_repo", { path }));
|
await applyRepo(await invoke<RepoData>("open_repo", { path }));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// The repository that was open stays open.
|
// The repository that was open stays open.
|
||||||
showToast(`could not open ${path}: ${e}`);
|
showToast(`could not open ${path}: ${e}`, { error: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ watch(
|
|||||||
invoke("save_filters", {
|
invoke("save_filters", {
|
||||||
repo: repoRoot,
|
repo: repoRoot,
|
||||||
value: { hidden: filters.hidden, rules: filters.rules, hideTags: filters.hideTags, collapseAll: collapseAll.value },
|
value: { hidden: filters.hidden, rules: filters.rules, hideTags: filters.hideTags, collapseAll: collapseAll.value },
|
||||||
}).catch((e) => showToast(`could not save filters: ${e}`));
|
}).catch((e) => showToast(`could not save filters: ${e}`, { error: true }));
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
@@ -366,10 +366,17 @@ async function copyText(text: string) {
|
|||||||
// ---- actions -------------------------------------------------------------
|
// ---- actions -------------------------------------------------------------
|
||||||
|
|
||||||
let toastTimer: number | undefined;
|
let toastTimer: number | undefined;
|
||||||
function showToast(text: string) {
|
/** Shows a short message. An error message stays longer, 8 seconds, so there is time to
|
||||||
|
* read it; a click on any message closes it. */
|
||||||
|
function showToast(text: string, { error = false } = {}) {
|
||||||
toast.value = text;
|
toast.value = text;
|
||||||
clearTimeout(toastTimer);
|
clearTimeout(toastTimer);
|
||||||
toastTimer = window.setTimeout(() => (toast.value = null), 1800);
|
toastTimer = window.setTimeout(() => (toast.value = null), error ? 8000 : 1800);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeToast() {
|
||||||
|
clearTimeout(toastTimer);
|
||||||
|
toast.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyHash() {
|
async function copyHash() {
|
||||||
@@ -653,7 +660,7 @@ onUnmounted(() => {
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="toast" class="toast">{{ toast }}</div>
|
<div v-if="toast" class="toast" @click="closeToast">{{ toast }}</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -760,6 +767,11 @@ onUnmounted(() => {
|
|||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 6px 24px #0008;
|
box-shadow: 0 6px 24px #0008;
|
||||||
|
/* Keeps each line of a message on its own line, such as the hint under an ssh error. */
|
||||||
|
white-space: pre-line;
|
||||||
|
max-width: min(640px, calc(100vw - 32px));
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s; }
|
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s; }
|
||||||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ const name = (p: string) => p.split(/[\\/]/).filter(Boolean).pop() ?? p;
|
|||||||
/>
|
/>
|
||||||
<button v-else class="browse" @click="startTyping">Open on another machine…</button>
|
<button v-else class="browse" @click="startTyping">Open on another machine…</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">You can also start gites with a path: <code>gites ~/code/my-project</code></p>
|
<p class="hint">
|
||||||
|
You can also start gites with a path: <code>gites ~/code/my-project</code>,
|
||||||
|
or with a repository on another machine: <code>gites myserver:/srv/app</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user