Skip to content

Add remote SSH repositories and worktrees#407

Merged
sbertix merged 13 commits into
mainfrom
sbertix/65-remote-repositories
Jun 18, 2026
Merged

Add remote SSH repositories and worktrees#407
sbertix merged 13 commits into
mainfrom
sbertix/65-remote-repositories

Conversation

@sbertix

@sbertix sbertix commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds first-class support for repositories and worktrees that live on a remote SSH host, alongside the existing local repositories. A remote repository is identified by a RemoteHost and a host-keyed branded id (remote://[user@]host[:port]/path); local repositories keep their path-based ids. The repositories feature, clients, terminal, settings, and menus all route through this branded local/remote identity.

What's included

  • SSH transport + remote host model. A RemoteHost settings model ([user@]host[:port], worktree base path) and an SSH command/transport layer that runs commands on the host.
  • Branded identity. RepositoryID / WorktreeID carry local-vs-remote provenance so nothing has to re-parse a path to know where a repo lives.
  • Remote-aware clients. The git, zmx, github, and deeplink clients run over SSH for a remote target: branch detection, diff stats, worktree add / worktree remove, fetch, and remote info all execute on the host. Archive/delete scripts run on the remote host via ssh -tt.
  • Feature wiring. Remote connections flow through the repositories feature; remote worktrees are wired through the terminal (SSH session, no local working-directory override), the app, the settings UI, and the menu bar. The HEAD watcher routes remote worktrees to an SSH poll loop instead of kqueue.
  • Settings keyed by host. Remote settings are keyed by host, and remote load failures (unreachable host) surface as a failed-repository placeholder rather than silently disappearing.
  • Reorder + Open behavior. Local and remote repositories share one reorderable sidebar order: the persisted order wins, so a drag interleaving local and remote rows sticks across recompute and reload. Open / Reveal in Finder target local paths an SSH host can't serve, so they're disabled for remote rows across the sidebar context menu, toolbar, and Worktrees menu, and rejected in the reducer so a hotkey can't reach the workspace client.
  • Failed remote title. A failed remote repository's window title resolves from its placeholder name instead of mangling the remote:// id through a file URL.

Notes / intentional asymmetries

  • Pull request data is fetched against a local checkout, so PR refresh is skipped for remote repositories (gh-over-SSH is out of scope); remote rows show no PR badge or PR actions.
  • New-worktree creation is supported for remote git repos (over SSH) and rejected for folder repos, mirroring the local rules.

Testing

  • make build-app: Build Succeeded.
  • make test: full suite passes (includes new coverage for remote reorder, interleaved ordering, the remote-Open no-op, and the failed-remote window title).
  • make check: format + lint clean.

Closes #65

sbertix added 10 commits June 13, 2026 01:19
Brand the per-repository settings key with the remote host so two hosts
that point at the same path no longer share settings, and so a remote
repository never reads or writes a local supacode.json at that path on
the local disk. The branded key mirrors RepositoryLocation.id, leaving a
local repository at the same path on its own bare-path key. RemoteHost
gains the id-bearing authority, threaded through every settings call site
and the repository settings feature (which now carries its host through
the settingsChanged delegate).

Stop collapsing a multi-worktree remote to a single synthetic main when
the worktree listing throws transiently: that case now surfaces a load
failure and keeps the placeholder so the next reload re-lists in full,
while a genuinely empty listing still falls back to a synthetic main.

Distinguish a reachable host with a missing path from an unreachable one:
classifyRemotePath reports a dedicated missing kind so the failure names
the path instead of blaming the connection.
Remote repos were pinned below the local ones and excluded from the
reorder machinery, so dragging them did nothing. Treat every repository
uniformly in `orderedRepositoryIDs()`: the persisted sidebar order wins
and local roots and host-keyed remote ids interleave freely, so a drag
sticks across recompute and reload. The sidebar structure now renders in
that single order and exposes every repo id as reorderable.

Open / Reveal in Finder target local paths that an SSH host can't serve,
so disable them for a remote row across the sidebar context menu, the
toolbar Open menu, and the Worktrees menu, and reject them in
`openWorktreeEffect` so a hotkey can't reach the workspace client.
A failed remote repository's id is a `remote://` authority, not a local
path, so deriving the window title name from a file URL mangled it. Prefer
the placeholder repository's resolved name, keeping the file-URL leaf only
for a local failure that has no placeholder.
…ositories

# Conflicts:
#	supacode/Features/Repositories/Reducer/RepositoriesFeature.swift
#	supacode/Features/Repositories/Views/WorktreeDetailView.swift
#	supacode/Features/Terminal/Models/WorktreeTerminalState.swift
#	supacodeTests/AgentPresenceFeatureTests.swift
@tuist

tuist Bot commented Jun 18, 2026

Copy link
Copy Markdown

🛠️ Tuist Run Report 🛠️

Builds 🔨

Scheme Status Duration Commit
supacode 1m 15s a838dc134

The prompted worktree-creation duplicate check and the whole rename-branch
flow (name validation, availability, and the rename itself) resolved the
injected local git client unconditionally, so for a remote repository they
ran against the local machine instead of the SSH host. Route both through a
host-aware client: a `gitClient(for: repository)` overload for the creation
duplicate check, and a host carried on the rename prompt's state.
@sbertix sbertix enabled auto-merge (squash) June 18, 2026 21:43
@sbertix sbertix disabled auto-merge June 18, 2026 21:47
@sbertix sbertix enabled auto-merge (squash) June 18, 2026 21:47
…ositories

# Conflicts:
#	supacode/Features/Terminal/Models/WorktreeTerminalState.swift
#	supacodeTests/WorktreeTerminalManagerTests.swift
@sbertix sbertix merged commit 88e5039 into main Jun 18, 2026
1 check passed
@sbertix sbertix deleted the sbertix/65-remote-repositories branch June 18, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

please support ssh

1 participant