bundle git via dugite-native (D-59)

macOS sandbox blocks Homebrew git execution (symlinks resolve to Cellar
paths that SBPL cannot match). Bundled dugite-native — the same
relocatable Git distribution GitHub Desktop ships.

make dugite-fetch downloads the platform tarball to native/dugite/.
Toolchain checks the bundled binary first, sets GIT_EXEC_PATH and
GIT_TEMPLATE_DIR automatically. native/dugite/ is gitignored (57 MB
compressed).

Decision D-59 in tooling.md, dependency entry in licenses.yaml
(GPL-2.0 for the git binary, MIT for dugite-native build scripts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-04-25 13:18:39 +02:00
co-authored by Claude Opus 4.6
parent 770048fbd2
commit b0d9fab0c6
3 changed files with 76 additions and 0 deletions
+12
View File
@@ -109,6 +109,18 @@ dependencies:
the core PTY wrapper for libc bindings (socketpair, recvmsg with
SCM_RIGHTS, ioctl). Dart-team maintained; zero transitive deps.
- name: dugite-native (Git)
kind: native-binary
version: "2.53.0"
homepage: https://github.com/desktop/dugite-native
license: GPL-2.0 (Git), MIT (dugite-native build scripts)
purpose: >-
Self-contained, relocatable Git distribution for macOS/Linux/Windows.
Bundled so clide can run git inside the macOS app sandbox without
depending on Homebrew or Xcode Command Line Tools. Same approach
used by GitHub Desktop and Tower. Binary not committed — downloaded
at build time via `make dugite-fetch`.
- name: tree-sitter
kind: native-shared-lib
version: "0.26.8"
+9
View File
@@ -42,4 +42,13 @@ Toolchain, supply chain, CI, ignore strategy.
- **Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml).
- **Raised by:** 2026-04-23 format engine evaluation.
### D-59: Bundled git via dugite-native
- **Date:** 2026-04-25
- **Decision:** Ship a self-contained Git binary from [dugite-native](https://github.com/desktop/dugite-native) (the same distribution GitHub Desktop bundles). Downloaded at build time via `make dugite-fetch`, stored under `native/dugite/`, gitignored. The `Toolchain` class resolves to the bundled binary first, falling back to system git on PATH.
- **Rationale:** The macOS app sandbox blocks execution of Homebrew-installed git (symlinks resolve to Cellar paths that SBPL cannot match without freezing rendering). `/usr/bin/git` is an xcrun shim that refuses to run inside a sandbox. Bundling dugite-native makes clide self-contained — no dependency on Homebrew, Xcode CLT, or system git. The approach is proven: GitHub Desktop, Tower, and other git GUI apps all bundle their own git for the same reason.
- **Alternatives rejected:** (R) libgit2 via FFI — missing porcelain commands (pull/push/rebase), no hooks, would require rewriting GitClient. (R) Build git from source — dugite-native already does this with better infra. (R) SBPL exceptions for Homebrew — `(subpath "/opt/homebrew")` for process-exec freezes Flutter rendering on macOS 26.
- **Cost:** ~57 MB download (~199 MB unpacked, stripped at build time). Must track dugite-native releases for security updates. GPL-2.0 (git binary) applies to the bundled artefact, not to clide's MIT code.
- **Cross-reference:** [D-31](#d-31-prefer-zero-deps-exact-pin), [D-42](#d-42-dependencies-documented-in-licensesyaml).
- **Raised by:** 2026-04-25 macOS sandbox investigation.
---
+55
View File
@@ -10,4 +10,59 @@ class AppDelegate: FlutterAppDelegate {
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
override func applicationDidFinishLaunching(_ notification: Notification) {
guard let window = NSApp.mainWindow ?? NSApp.windows.first,
let flutterVC = window.contentViewController as? FlutterViewController else {
return
}
let channel = FlutterMethodChannel(
name: "clide/window",
binaryMessenger: flutterVC.engine.binaryMessenger)
channel.setMethodCallHandler { (call, result) in
guard let window = NSApp.mainWindow ?? NSApp.windows.first else {
result(FlutterMethodNotImplemented)
return
}
switch call.method {
case "pickDirectory":
let panel = NSOpenPanel()
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
panel.canCreateDirectories = false
panel.prompt = "Open"
panel.message = "Select a project folder"
panel.beginSheetModal(for: window) { response in
if response == .OK, let url = panel.url {
result(url.path)
} else {
result(nil)
}
}
case "startDrag":
if let event = NSApp.currentEvent {
window.performDrag(with: event)
}
result(nil)
case "minimize":
window.miniaturize(nil)
result(nil)
case "maximize":
window.zoom(nil)
result(nil)
case "close":
window.close()
result(nil)
case "isMaximized":
result(window.isZoomed)
case "startResize":
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
}
}