Error messages now stay for 8 seconds or until clicked, and keep each hint on its own line. The welcome screen shows how to start gites with a repository on another machine. The spec follows these changes and the new ssh options and socket folder.
143 lines
7.2 KiB
Markdown
143 lines
7.2 KiB
Markdown
# Repositories on another machine, over ssh
|
|
|
|
## Goal
|
|
|
|
Open a git repository that lives on another machine and see its history exactly as
|
|
gites shows a local one: branches, tags, remote branches, commits, the details panel
|
|
and diffs. gites shows the state of that folder on that machine, including its local
|
|
branches and which branch is checked out there, not the state of its origin.
|
|
|
|
Only history is shown, as for local repositories today. Uncommitted changes are not
|
|
part of this work.
|
|
|
|
Linux and macOS are the platforms that matter. Windows must work, but may be slow.
|
|
|
|
## How the user opens one
|
|
|
|
- Command line: `gites myserver:/srv/app` or `gites user@myserver:/srv/app`.
|
|
`myserver` can be any host name from `~/.ssh/config`, so ports, user names and keys
|
|
set there apply.
|
|
- In the window: the repository menu (`o`) and the welcome screen get an entry
|
|
"Open on another machine…" with one text field for `[user@]host:path`.
|
|
- Remote repositories go into the recent list like local ones, written as
|
|
`myserver:/srv/app`.
|
|
|
|
## Where a repository is
|
|
|
|
The Rust side stores the open repository as one of two kinds:
|
|
|
|
- a local folder, as today;
|
|
- an ssh target (`myserver` or `user@myserver`) and a path on that machine.
|
|
|
|
One function turns text into a location. The text means a remote repository when it
|
|
has the form `[user@]host:path`, where `host` contains no `/` or `\` and is longer than
|
|
one character, and no local folder with exactly that name exists. Every other text is 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.
|
|
|
|
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.
|
|
|
|
## Running git
|
|
|
|
`git()` in `src-tauri/src/git.rs` takes the repository's location instead of a folder.
|
|
|
|
- Local: `git -C <folder> <args>`, as today, with the Windows flag that keeps console
|
|
windows from opening.
|
|
- Remote: the program `ssh`, with these options, then the target, then one command
|
|
string for the server's shell:
|
|
|
|
```
|
|
ssh -o BatchMode=yes -o ConnectTimeout=10
|
|
-o ServerAliveInterval=5 -o ServerAliveCountMax=2
|
|
-o ControlMaster=auto -o ControlPath=<socket> -o ControlPersist=10m
|
|
user@myserver "git -C '/srv/app' 'log' '--format=…' …"
|
|
```
|
|
|
|
- `BatchMode=yes`: ssh never asks for a password or a yes/no answer, because gites
|
|
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
|
|
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
|
|
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
|
|
after the last command. The socket is `gites-%C` in the user's runtime folder
|
|
(`/run/user/<id>` on Linux). Where there is none (always on macOS), it is in a
|
|
`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
|
|
every command opens a new connection.
|
|
- Quoting: every word of the git command is put in single quotes, and a `'` inside a
|
|
word is written as `'\''`. This works in sh, bash, zsh and fish, so a file name with
|
|
spaces, quotes, `$`, `;` or a line break reaches git unchanged and cannot run
|
|
anything else on the server.
|
|
|
|
Nothing above `git()` changes: loading the graph, the details panel, diffs and the
|
|
reload when the window gets focus all run through it.
|
|
|
|
## Names and saved data
|
|
|
|
A remote repository's name is `<target>:<top folder>`, where the top folder is what
|
|
`git rev-parse --show-toplevel` prints on the server. That name is used:
|
|
|
|
- in the window title and the header;
|
|
- in the recent list in `settings.json`;
|
|
- as the key of the repository's filters in `filters.json`, so filters are remembered
|
|
for remote repositories just like for local ones.
|
|
|
|
## Start-up
|
|
|
|
A remote location given on the command line becomes the open repository at once. gites
|
|
does not test the connection before the window appears, so a slow server never delays
|
|
the window. If the server cannot be reached, the first load fails and the window shows
|
|
the error screen, as it does today for a local path that is not a repository.
|
|
|
|
## Errors
|
|
|
|
ssh exits with code 255 when the connection fails, and with git's own exit code
|
|
otherwise. That tells a connection problem from a git error:
|
|
|
|
| What happened | What gites shows |
|
|
|---|---|
|
|
| Connection failed (exit code 255) | "could not connect to myserver:" and ssh's message |
|
|
| …and ssh says "Host key verification failed" | also: "Run `ssh myserver` once in a terminal to accept the server's key." |
|
|
| …and ssh says "Permission denied" | also: "gites logs in with keys or your ssh agent only; check that `ssh myserver` works without a password." |
|
|
| The server does not answer within 10 seconds | the connection error above, with ssh's timeout message |
|
|
| git fails on the server, for example "not a git repository" | git's message, as today |
|
|
|
|
If the connection drops while a repository is open, the next command shows its error
|
|
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.
|
|
|
|
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
|
|
|
|
- Reading locations: `host:/path`, `user@host:/path`, `host:~/x`, `C:\x`, `C:/x`, a
|
|
plain relative path, and an existing local folder with a `:` in its name.
|
|
- Quoting: words with spaces, `'`, `"`, `$`, `;`, `*` and a line break are quoted, run
|
|
through a real `sh -c`, and come out unchanged.
|
|
- End to end without a server: for tests, the ssh program can be replaced by a small
|
|
script that drops the ssh options and runs the command string with `sh -c` on this
|
|
machine, the way a server's shell would. With it, the existing tests for loading a
|
|
repository, commit details and diffs also run against a "remote" location, including
|
|
files with awkward names. CI runs these.
|
|
- By hand, before release: open a repository on a real server, check the graph, a
|
|
commit's details and a diff, then wait for the shared connection to close and reload.
|
|
|
|
## Not part of this work
|
|
|
|
- Uncommitted changes on the remote machine.
|
|
- Passwords and other questions ssh might ask.
|
|
- `ssh://` addresses; ports and users go in `~/.ssh/config` or `user@host`.
|
|
- Closing the shared connection when gites quits; it closes itself after 10 minutes.
|